Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(autocomplete): rewrite geolocation source #2304

Merged
merged 2 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

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
Loading