Skip to content

Commit

Permalink
refactor(autocomplete): rewrite geolocation source (#2304)
Browse files Browse the repository at this point in the history
  • Loading branch information
thecristen authored Jan 8, 2025
1 parent 4cdc6e8 commit 69d25e6
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 504 deletions.
120 changes: 0 additions & 120 deletions assets/ts/ui/autocomplete/__tests__/__snapshots__/config-test.ts.snap

This file was deleted.

49 changes: 22 additions & 27 deletions assets/ts/ui/autocomplete/__tests__/config-test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { GetSourcesParams } from "@algolia/autocomplete-core";
import { Item, LocationItem } from "../__autocomplete";
import {
AutocompleteState,
GetSourcesParams,
OnSelectParams,
StateUpdater
} from "@algolia/autocomplete-core";
import { Item } from "../__autocomplete";
import configs from "./../config";
import { AutocompleteSource } from "@algolia/autocomplete-js";
import { render, screen, waitFor } from "@testing-library/react";
import { mockTemplateParam } from "./templates-test";
import userEvent from "@testing-library/user-event";
import { waitFor } from "@testing-library/react";

const sourceIds = (sources: any[]) =>
(sources as AutocompleteSource<any>[]).map(s => s.sourceId);
Expand Down Expand Up @@ -189,29 +192,21 @@ describe("Trip planner configuration", () => {
const { getSources } = config;
const mockSetQuery = jest.fn();
// @ts-ignore
const [geolocationSource] = await getSources({
...baseSourceParams,
setQuery: mockSetQuery
});
const geolocationTemplate = (geolocationSource as AutocompleteSource<
LocationItem
>).templates.item;

render(
geolocationTemplate(
mockTemplateParam<LocationItem>({} as LocationItem, "")
) as React.ReactElement
);
const button = screen.getByRole("button");
const user = userEvent.setup();
await user.click(button);
const locationName = `Near ${mockCoordinates.latitude}, ${mockCoordinates.longitude}`;
const [geolocationSource] = await getSources(baseSourceParams);
(geolocationSource as AutocompleteSource<any>)?.onSelect!({
setContext: jest.fn() as StateUpdater<
AutocompleteState<{ value: string }>["context"]
>,
setQuery: mockSetQuery as StateUpdater<
AutocompleteState<{ value: string }>["query"]
>
} as OnSelectParams<{ value: string }>);

await waitFor(() => {
expect(mockSetQuery).toHaveBeenCalledWith(locationName);
expect(pushToLiveViewMock).toHaveBeenCalledWith({
...mockCoordinates,
name: locationName
});
expect(mockSetQuery).toHaveBeenCalledWith(
`${mockCoordinates.latitude}, ${mockCoordinates.longitude}`
);
expect(pushToLiveViewMock).toHaveBeenCalledWith(mockCoordinates);
});
});
});
165 changes: 154 additions & 11 deletions assets/ts/ui/autocomplete/__tests__/sources-test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { OnInputParams } from "@algolia/autocomplete-core";
import {
AutocompleteState,
OnInputParams,
OnSelectParams,
StateUpdater
} from "@algolia/autocomplete-core";
import {
algoliaSource,
geolocationSource,
locationSource,
popularLocationSource
} from "../sources";
import { AutocompleteItem, LocationItem, PopularItem } from "../__autocomplete";

const setIsOpenMock = jest.fn();
import { UrlType } from "../helpers";
import { waitFor } from "@testing-library/dom";

beforeEach(() => {
global.fetch = jest.fn(() =>
Expand All @@ -19,22 +24,160 @@ beforeEach(() => {
});
afterEach(() => jest.resetAllMocks());

const mockCoords = {
latitude: 40,
longitude: -71
};
const mockUrlsResponse = {
result: {
urls: {
"transit-near-me": `/transit-near-me?latitude=${mockCoords.latitude}&longitude=${mockCoords.longitude}`,
"retail-sales-locations": `/fares/retail-sales-locations?latitude=${mockCoords.latitude}&longitude=${mockCoords.longitude}`,
"proposed-sales-locations": `/fare-transformation/proposed-sales-locations?latitude=${mockCoords.latitude}&longitude=${mockCoords.longitude}`
},
longitude: mockCoords.longitude,
latitude: mockCoords.latitude
}
};

function setMocks(geoSuccess: boolean, fetchSuccess: boolean): void {
const getCurrentPositionMock = geoSuccess
? jest.fn().mockImplementationOnce(success =>
Promise.resolve(
success({
coords: mockCoords
})
)
)
: jest
.fn()
.mockImplementationOnce((success, error) =>
Promise.resolve(error({ code: 1, message: "GeoLocation Error" }))
);

(global.navigator as any).geolocation = {
getCurrentPosition: getCurrentPositionMock
};

if (fetchSuccess) {
jest.spyOn(global, "fetch").mockResolvedValue({
ok: true,
status: 200,
json: () => Promise.resolve(mockUrlsResponse)
} as Response);
} else {
jest.spyOn(global, "fetch").mockRejectedValue({
ok: false,
status: 404
});
}
}

describe("geolocationSource", () => {
Object.defineProperty(window, "location", {
value: {
assign: jest.fn()
}
});

test("defines a template", () => {
expect(geolocationSource(setIsOpenMock).templates.item).toBeTruthy();
expect(geolocationSource().templates.item).toBeTruthy();
});
test("defines a getItems function", () => {
expect(
geolocationSource(setIsOpenMock).getItems(
{} as OnInputParams<LocationItem>
)
geolocationSource().getItems({} as OnInputParams<{ value: string }>)
).toBeTruthy();
});
test("has optional getItemUrl", () => {
expect(geolocationSource(setIsOpenMock)).not.toContainKey("getItemUrl");
expect(
geolocationSource(setIsOpenMock, "proposed-sales-locations")
).toContainKey("getItemUrl");
expect(geolocationSource("proposed-sales-locations")).toContainKey(
"getItemUrl"
);
});
describe("onSelect", () => {
function setupGeolocationSource(
urlType?: UrlType,
onLocationFound?: () => void
) {
const source = geolocationSource(urlType, onLocationFound);
const setContextMock = jest.fn();
const setQueryMock = jest.fn();
const onSelectParams = {
setContext: setContextMock as StateUpdater<
AutocompleteState<{ value: string }>["context"]
>,
setQuery: setQueryMock as StateUpdater<
AutocompleteState<{ value: string }>["query"]
>
} as OnSelectParams<{ value: string }>;
return { source, onSelectParams };
}

test("redirects to a URL on success", async () => {
setMocks(true, true);
const { source, onSelectParams } = setupGeolocationSource(
"transit-near-me"
);
expect(source.getItems({} as OnInputParams<{ value: string }>)).toEqual([
{ value: "Use my location to find transit near me" }
]);
source.onSelect!(onSelectParams);

await waitFor(() => {
expect(onSelectParams.setQuery).toHaveBeenCalledWith(
"Getting your location..."
);
expect(global.fetch).toHaveBeenCalledWith(
`/places/urls?latitude=${mockCoords.latitude}&longitude=${mockCoords.longitude}`
);
expect(window.location.assign).toHaveBeenCalledExactlyOnceWith(
`/transit-near-me?latitude=${mockCoords.latitude}&longitude=${mockCoords.longitude}`
);
});
});
test("fires onLocationFound on success", async () => {
setMocks(true, true);
const onLocationFoundMock = jest.fn();
const { source, onSelectParams } = setupGeolocationSource(
undefined,
onLocationFoundMock
);
source.onSelect!(onSelectParams);
await waitFor(() => {
expect(onLocationFoundMock).toHaveBeenCalledWith(mockCoords);
});
});
test("displays error on geolocation error", async () => {
setMocks(false, true);
const { source, onSelectParams } = setupGeolocationSource(
"transit-near-me"
);
source.onSelect!(onSelectParams);
await waitFor(() => {
expect(onSelectParams.setQuery).toHaveBeenCalledWith(
"undefined needs permission to use your location."
);
expect(global.fetch).not.toHaveBeenCalled();
expect(window.location.assign).not.toHaveBeenCalled();
});
});
test("displays error on fetch error", async () => {
setMocks(true, false);
const { source, onSelectParams } = setupGeolocationSource(
"proposed-sales-locations"
);
source.onSelect!(onSelectParams);
await waitFor(() => {
expect(global.fetch).toHaveBeenCalledWith(
`/places/urls?latitude=${mockCoords.latitude}&longitude=${mockCoords.longitude}`
);
expect(window.location.assign).not.toHaveBeenCalledWith(
mockUrlsResponse.result.urls["proposed-sales-locations"]
);
expect(onSelectParams.setQuery).toHaveBeenCalledWith(
"undefined needs permission to use your location."
);
});
});
});
});

Expand Down
Loading

0 comments on commit 69d25e6

Please sign in to comment.