diff --git a/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts b/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts
index b13b55ad5..f3adc722b 100644
--- a/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts
+++ b/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts
@@ -42,7 +42,7 @@ function createMockSource({
},
templates: {
item({ item, html }) {
- return html`${item.query}`;
+ return html`${item.name}`;
},
},
...props,
@@ -122,73 +122,12 @@ describe('createRedirectUrlPlugin', () => {
await waitFor(() => {
expect(findHitsSection(panelContainer)).not.toBeInTheDocument();
- expect(findDropdownOptions(findRedirectSection(panelContainer)))
- .toMatchInlineSnapshot(`
- Array [
- HTMLCollection [
-
,
- ],
- ]
- `);
+ const dropdownOptions = findDropdownOptions(
+ findRedirectSection(panelContainer)
+ );
+
+ expect(dropdownOptions).toHaveLength(1);
+ expect(dropdownOptions[0].item(0)).toHaveTextContent(REDIRECT_QUERY);
});
});
@@ -222,18 +161,25 @@ describe('createRedirectUrlPlugin', () => {
await waitFor(() => {
expect(findHitsSection(panelContainer)).not.toBeInTheDocument();
- const dropdownText = findDropdownOptions(
+ const dropdownOptions = findDropdownOptions(
findRedirectSection(panelContainer)
- )[0].item(0)?.textContent;
+ );
- expect(dropdownText).toBe('My custom option: redirect item');
+ expect(dropdownOptions).toHaveLength(1);
+ expect(dropdownOptions[0].item(0)).toHaveTextContent(
+ 'My custom option: redirect item'
+ );
});
});
- test('renders a redirect item when a custom expected payload is returned', async () => {
+ test('does not render a redirect item when the query from the state does not match the response', async () => {
const redirectUrlPlugin = createRedirectUrlPlugin({
transformResponse(response) {
- return (response as Record).customRedirect?.url;
+ return (response as Record).renderingContent?.redirect
+ ?.url;
+ },
+ transformResponseToQuery() {
+ return 'different query';
},
});
@@ -266,7 +212,7 @@ describe('createRedirectUrlPlugin', () => {
await waitFor(() => {
expect(findHitsSection(panelContainer)).not.toBeInTheDocument();
- expect(findRedirectSection(panelContainer)).toBeInTheDocument();
+ expect(findRedirectSection(panelContainer)).not.toBeInTheDocument();
});
});
@@ -284,7 +230,7 @@ describe('createRedirectUrlPlugin', () => {
panelContainer,
plugins: [redirectUrlPlugin],
getSources() {
- return [createMockSource({ results: [{ hits: [{ query }] }] })];
+ return [createMockSource({ results: [{ hits: [{ name: query }] }] })];
},
});
@@ -293,16 +239,14 @@ describe('createRedirectUrlPlugin', () => {
fireEvent.input(input, { target: { value: query } });
await waitFor(() => {
- expect(findDropdownOptions(findHitsSection(panelContainer)))
- .toMatchInlineSnapshot(`
- Array [
- HTMLCollection [
-
- not a redirect item
- ,
- ],
- ]
- `);
+ const dropdownOptions = findDropdownOptions(
+ findHitsSection(panelContainer)
+ );
+
+ expect(dropdownOptions).toHaveLength(1);
+ expect(dropdownOptions[0].item(0)).toHaveTextContent(
+ 'not a redirect item'
+ );
expect(findRedirectSection(panelContainer)).not.toBeInTheDocument();
});
@@ -327,9 +271,9 @@ describe('createRedirectUrlPlugin', () => {
{
...RESPONSE,
hits: [
- { query: 'redirect item' },
- { query: 'not a redirect item 1' },
- { query: 'not a redirect item 2' },
+ { name: 'redirect item' },
+ { name: 'not a redirect item 1' },
+ { name: 'not a redirect item 2' },
],
},
],
@@ -345,26 +289,18 @@ describe('createRedirectUrlPlugin', () => {
await waitFor(() => {
expect(findRedirectSection(panelContainer)).toBeInTheDocument();
- expect(findDropdownOptions(findHitsSection(panelContainer)))
- .toMatchInlineSnapshot(`
- Array [
- HTMLCollection [
-
- redirect item
- ,
- ],
- HTMLCollection [
-
- not a redirect item 1
- ,
- ],
- HTMLCollection [
-
- not a redirect item 2
- ,
- ],
- ]
- `);
+ const dropdownOptions = findDropdownOptions(
+ findHitsSection(panelContainer)
+ );
+
+ expect(dropdownOptions).toHaveLength(3);
+ expect(dropdownOptions[0].item(0)).toHaveTextContent(REDIRECT_QUERY);
+ expect(dropdownOptions[1].item(0)).toHaveTextContent(
+ 'not a redirect item 1'
+ );
+ expect(dropdownOptions[2].item(0)).toHaveTextContent(
+ 'not a redirect item 2'
+ );
});
});
@@ -387,14 +323,14 @@ describe('createRedirectUrlPlugin', () => {
{
...RESPONSE,
hits: [
- { query: 'redirect item' },
- { query: 'not a redirect item 1' },
- { query: 'not a redirect item 2' },
+ { name: REDIRECT_QUERY },
+ { name: 'not a redirect item 1' },
+ { name: 'not a redirect item 2' },
],
},
],
getItemInputValue({ item }) {
- return item.query;
+ return item.name;
},
}),
];
@@ -408,21 +344,16 @@ describe('createRedirectUrlPlugin', () => {
await waitFor(() => {
expect(findRedirectSection(panelContainer)).toBeInTheDocument();
- expect(findDropdownOptions(findHitsSection(panelContainer)))
- .toMatchInlineSnapshot(`
- Array [
- HTMLCollection [
-
- not a redirect item 1
- ,
- ],
- HTMLCollection [
-
- not a redirect item 2
- ,
- ],
- ]
- `);
+ const dropdownOptions = findDropdownOptions(
+ findHitsSection(panelContainer)
+ );
+ expect(dropdownOptions).toHaveLength(2);
+ expect(dropdownOptions[0].item(0)).toHaveTextContent(
+ 'not a redirect item 1'
+ );
+ expect(dropdownOptions[1].item(0)).toHaveTextContent(
+ 'not a redirect item 2'
+ );
});
});
@@ -486,15 +417,18 @@ describe('createRedirectUrlPlugin', () => {
fireEvent.input(input, { target: { value: REDIRECT_QUERY } });
await waitFor(() => {
- expect(
- findDropdownOptions(findRedirectSection(panelContainer))[0][0]
- ).toHaveTextContent(REDIRECT_QUERY);
+ const dropdownOptions = findDropdownOptions(
+ findRedirectSection(panelContainer)
+ );
+
+ expect(dropdownOptions).toHaveLength(1);
+ expect(dropdownOptions[0].item(0)).toHaveTextContent(REDIRECT_QUERY);
});
fireEvent.submit(input);
await waitFor(() => {
- expect(input.value).toBe(REDIRECT_QUERY);
+ expect(input).toHaveValue(REDIRECT_QUERY);
expect(navigator.navigate).toHaveBeenCalledTimes(1);
});
});
@@ -520,7 +454,8 @@ describe('createRedirectUrlPlugin', () => {
query === REDIRECT_QUERY
? [
{
- hits: [{ query: REDIRECT_QUERY }],
+ hits: [{ name: REDIRECT_QUERY }],
+ query: REDIRECT_QUERY,
renderingContent: {
redirect: {
url: 'https://www.algolia.com',
@@ -531,13 +466,14 @@ describe('createRedirectUrlPlugin', () => {
: [
{
hits: [
- { query: 'something else' },
- { query: REDIRECT_QUERY },
+ { name: 'something else' },
+ { name: REDIRECT_QUERY },
],
+ query: 'something else',
},
],
getItemInputValue({ item }) {
- return item.query;
+ return item.name;
},
}),
];
@@ -550,95 +486,25 @@ describe('createRedirectUrlPlugin', () => {
await waitFor(() => {
expect(findRedirectSection(panelContainer)).not.toBeInTheDocument();
- expect(findDropdownOptions(findHitsSection(panelContainer)))
- .toMatchInlineSnapshot(`
- Array [
- HTMLCollection [
-
- something else
- ,
- ],
- HTMLCollection [
-
- redirect item
- ,
- ],
- ]
- `);
+ const dropdownOptions = findDropdownOptions(
+ findHitsSection(panelContainer)
+ );
+ expect(dropdownOptions).toHaveLength(2);
+ expect(dropdownOptions[0].item(0)).toHaveTextContent('something else');
+ expect(dropdownOptions[1].item(0)).toHaveTextContent(REDIRECT_QUERY);
});
fireEvent.click(findDropdownOptions(panelContainer)[1][0]);
await waitFor(() => {
- expect(input.value).toBe(REDIRECT_QUERY);
+ expect(input).toHaveValue(REDIRECT_QUERY);
expect(findHitsSection(panelContainer)).not.toBeInTheDocument();
- expect(findDropdownOptions(findRedirectSection(panelContainer)))
- .toMatchInlineSnapshot(`
- Array [
- HTMLCollection [
- ,
- ],
- ]
- `);
+ const dropdownOptions = findDropdownOptions(
+ findRedirectSection(panelContainer)
+ );
+
+ expect(dropdownOptions).toHaveLength(1);
+ expect(dropdownOptions[0].item(0)).toHaveTextContent(REDIRECT_QUERY);
});
fireEvent.submit(input);
@@ -728,7 +594,7 @@ describe('createRedirectUrlPlugin', () => {
fireEvent.submit(input);
await waitFor(() => {
- expect(input.value).toBe(REDIRECT_QUERY);
+ expect(input).toHaveValue(REDIRECT_QUERY);
expect(navigator.navigate).toHaveBeenCalledWith(
expect.objectContaining({
item: {
@@ -802,7 +668,7 @@ describe('createRedirectUrlPlugin', () => {
fireEvent.submit(input);
await waitFor(() => {
- expect(input.value).toBe(REDIRECT_QUERY);
+ expect(input).toHaveValue(REDIRECT_QUERY);
expect(navigator.navigate).toHaveBeenCalledWith(
expect.objectContaining({
item: {
diff --git a/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts b/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts
index 22adff66d..6804f540a 100644
--- a/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts
+++ b/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts
@@ -21,6 +21,12 @@ function defaultTransformResponse(
return (response as Record).renderingContent?.redirect?.url;
}
+function defaultTransformResponseToQuery(
+ response: TransformResponseParams
+): string | undefined {
+ return (response as Record).query;
+}
+
function defaultOnRedirect(
redirects: RedirectUrlItem[],
{ event, navigator, state }: OnRedirectOptions
@@ -46,6 +52,7 @@ function getOptions(
) {
return {
transformResponse: defaultTransformResponse,
+ transformResponseToQuery: defaultTransformResponseToQuery,
templates: defaultTemplates,
onRedirect: defaultOnRedirect,
...options,
@@ -61,7 +68,8 @@ function getRedirectData({ state }) {
export function createRedirectUrlPlugin(
options: CreateRedirectUrlPluginParams = {}
): AutocompletePlugin {
- const { transformResponse, templates, onRedirect } = getOptions(options);
+ const { transformResponse, transformResponseToQuery, templates, onRedirect } =
+ getOptions(options);
function createRedirects({ results, source, state }): RedirectUrlItem[] {
const redirect: RedirectUrlItem = {
@@ -94,6 +102,14 @@ export function createRedirectUrlPlugin(
name: 'aa.redirectUrlPlugin',
subscribe({ onResolve, onSelect, setContext, setIsOpen }) {
onResolve(({ results, source, state }) => {
+ // Ensure the resolved response matches the input query text before processing redirects.
+ const matchesCurrentQuery = (results as any).some(
+ (result) => transformResponseToQuery(result) === state.query
+ );
+ if (!matchesCurrentQuery) {
+ return;
+ }
+
setContext({
...state.context,
redirectUrlPlugin: {
diff --git a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts
index 1f90e50f3..7ab60b298 100644
--- a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts
+++ b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts
@@ -29,13 +29,44 @@ export type TransformResponseParams =
| SearchForFacetValuesResponse;
export type CreateRedirectUrlPluginParams = {
+ /**
+ * Maps the response to the redirect url that will be used by the plugin.
+ *
+ * Already supports Algolia results by default.
+ */
transformResponse?(
response: TransformResponseParams
): string | undefined;
+ /**
+ * Maps the query used in the request to match with the redirect.
+ * Without this mapping, there is a risk of the race condition when processing
+ * redirect items where the last-resolved response can reflect an earlier query value.
+ * (ex: typing "shoes" quickly risks mismatching the redirect item with "sho" instead)
+ *
+ * Already supports Algolia results by default.
+ */
+ transformResponseToQuery?(
+ response: TransformResponseParams
+ ): string | undefined;
+ /**
+ * Handles the navigation logic once a redirect is triggered.
+ *
+ * Already supports Algolia results by default.
+ */
onRedirect?(
redirects: RedirectUrlItem[],
options: OnRedirectOptions
): void;
+ /**
+ * The template used to render injected redirect dropdown items.
+ */
templates?: SourceTemplates;
+ /**
+ * Waits for all pending requests to complete before handling a form submission.
+ * (ex: pressing the "enter" key in the input)
+ *
+ * A boolean return value will wait for all pending requests to resolve.
+ * A number value will achieve the above with a timeout (in ms) to exit if it takes too long.
+ */
awaitSubmit?: () => boolean | number;
};