From 67286d063fc76d6acf7b04c7705d6b98cc8e47bf Mon Sep 17 00:00:00 2001 From: Dan Rodriguez Date: Tue, 15 Apr 2025 14:44:05 -0600 Subject: [PATCH 01/13] fix(redirect): item mismatch with last search --- .../src/createRedirectUrlPlugin.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts b/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts index 22adff66d..c15ffedc7 100644 --- a/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts +++ b/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts @@ -94,6 +94,12 @@ export function createRedirectUrlPlugin( name: 'aa.redirectUrlPlugin', subscribe({ onResolve, onSelect, setContext, setIsOpen }) { onResolve(({ results, source, state }) => { + // Since searches can be resolved in any order, verify the query from the search + // matches the input query to ensure a redirect item accurately reflects the input + if (results[0].query !== state.query) { + return; + } + setContext({ ...state.context, redirectUrlPlugin: { From 54c861740fd1f46538ec6bb93d7e7dbbaf2e982f Mon Sep 17 00:00:00 2001 From: Dan Rodriguez Date: Tue, 22 Apr 2025 10:46:52 -0600 Subject: [PATCH 02/13] test: add test to check for mismatched query --- .../src/__tests__/createRedirectUrlPlugin.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 54c74271c..c0b41f6aa 100644 --- a/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts +++ b/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts @@ -225,7 +225,7 @@ describe('createRedirectUrlPlugin', () => { Array [ HTMLCollection [ - My custom option: + My custom option: redirect item , ], @@ -234,7 +234,7 @@ describe('createRedirectUrlPlugin', () => { }); }); - 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; @@ -270,7 +270,7 @@ describe('createRedirectUrlPlugin', () => { await waitFor(() => { expect(findHitsSection(panelContainer)).not.toBeInTheDocument(); - expect(findRedirectSection(panelContainer)).toBeInTheDocument(); + expect(findRedirectSection(panelContainer)).not.toBeInTheDocument(); }); }); From bf7061bd082c7d900c0121fef1beb207e3b3e13e Mon Sep 17 00:00:00 2001 From: Dan Rodriguez Date: Tue, 22 Apr 2025 12:41:16 -0600 Subject: [PATCH 03/13] test: clean up redirect plugin tests --- .../__tests__/createRedirectUrlPlugin.test.ts | 101 ++++++++---------- 1 file changed, 43 insertions(+), 58 deletions(-) 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 b13b5eea1..519696180 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, @@ -284,7 +284,7 @@ describe('createRedirectUrlPlugin', () => { panelContainer, plugins: [redirectUrlPlugin], getSources() { - return [createMockSource({ results: [{ hits: [{ query }] }] })]; + return [createMockSource({ results: [{ hits: [{ name: query }] }] })]; }, }); @@ -293,16 +293,11 @@ describe('createRedirectUrlPlugin', () => { fireEvent.input(input, { target: { value: query } }); await waitFor(() => { - expect(findDropdownOptions(findHitsSection(panelContainer))) - .toMatchInlineSnapshot(` - Array [ - HTMLCollection [ - - not a redirect item - , - ], - ] - `); + const dropdownText = findDropdownOptions( + findHitsSection(panelContainer) + )[0].item(0)?.textContent; + + expect(dropdownText).toBe('not a redirect item'); expect(findRedirectSection(panelContainer)).not.toBeInTheDocument(); }); @@ -327,9 +322,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 +340,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)?.textContent).toBe(REDIRECT_QUERY); + expect(dropdownOptions[1].item(0)?.textContent).toBe( + 'not a redirect item 1' + ); + expect(dropdownOptions[2].item(0)?.textContent).toBe( + 'not a redirect item 2' + ); }); }); @@ -385,16 +372,17 @@ describe('createRedirectUrlPlugin', () => { createMockSource({ results: [ { + query: REDIRECT_QUERY, ...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 +396,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)?.textContent).toBe( + 'not a redirect item 1' + ); + expect(dropdownOptions[1].item(0)?.textContent).toBe( + 'not a redirect item 2' + ); }); }); @@ -520,7 +503,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 +515,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; }, }), ]; From 56e3c8895fd1e3b4a92641c72a506018371d72b0 Mon Sep 17 00:00:00 2001 From: Dan Rodriguez Date: Tue, 22 Apr 2025 12:56:56 -0600 Subject: [PATCH 04/13] chore: cleanup --- .../src/__tests__/createRedirectUrlPlugin.test.ts | 1 - 1 file changed, 1 deletion(-) 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 519696180..3429e1ce5 100644 --- a/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts +++ b/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts @@ -372,7 +372,6 @@ describe('createRedirectUrlPlugin', () => { createMockSource({ results: [ { - query: REDIRECT_QUERY, ...RESPONSE, hits: [ { name: REDIRECT_QUERY }, From b8c0cea70a1e7b1cbe817b039149ae12e8aa50d4 Mon Sep 17 00:00:00 2001 From: Dan Rodriguez Date: Wed, 23 Apr 2025 10:41:23 -0600 Subject: [PATCH 05/13] feat: add new type and doc comments --- .../src/types/Redirect.ts | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts index 1f90e50f3..a020724d7 100644 --- a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts +++ b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts @@ -4,7 +4,10 @@ import { InternalAutocompleteOptions, } from '@algolia/autocomplete-core'; import { SourceTemplates } from '@algolia/autocomplete-js'; -import { SearchForFacetValuesResponse } from '@algolia/autocomplete-preset-algolia'; +import { + SearchForFacetValuesResponse, + TransformedRequesterResponse, +} from '@algolia/autocomplete-preset-algolia'; import type { SearchResponse } from '@algolia/autocomplete-shared'; export interface RedirectUrlPlugin { @@ -24,18 +27,43 @@ export type OnRedirectOptions = { state: AutocompleteState; }; -export type TransformResponseParams = +export interface TransformResponse { + url: string | undefined; + query: string | undefined; +} + +export type Response = | SearchResponse | SearchForFacetValuesResponse; export type CreateRedirectUrlPluginParams = { + /** + * Map the response to values that can be interpreted by the plugin to correctly parse redirects. + * + * Supports Algolia results out of the box. + */ transformResponse?( - response: TransformResponseParams - ): string | undefined; + response: Response + ): TransformResponse | string | undefined; + /** + * Handles the navigation logic once a redirect is triggered + * + * Supports Algolia results out of the box. + */ 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; }; From 89b1c5533f29abe1ca495dd6b133a6388f8ed5e4 Mon Sep 17 00:00:00 2001 From: Dan Rodriguez Date: Wed, 23 Apr 2025 12:38:26 -0600 Subject: [PATCH 06/13] test: more test cleanup --- .../__tests__/createRedirectUrlPlugin.test.ts | 217 ++++-------------- 1 file changed, 49 insertions(+), 168 deletions(-) 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 3429e1ce5..52550cc97 100644 --- a/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts +++ b/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts @@ -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,24 @@ 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('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 { + queryUsed: REDIRECT_QUERY, + redirectUrl: (response as Record).customRedirect?.url, + }; }, }); @@ -293,11 +238,14 @@ describe('createRedirectUrlPlugin', () => { fireEvent.input(input, { target: { value: query } }); await waitFor(() => { - const dropdownText = findDropdownOptions( + const dropdownOptions = findDropdownOptions( findHitsSection(panelContainer) - )[0].item(0)?.textContent; + ); - expect(dropdownText).toBe('not a redirect item'); + expect(dropdownOptions).toHaveLength(1); + expect(dropdownOptions[0].item(0)).toHaveTextContent( + 'not a redirect item' + ); expect(findRedirectSection(panelContainer)).not.toBeInTheDocument(); }); @@ -345,11 +293,11 @@ describe('createRedirectUrlPlugin', () => { ); expect(dropdownOptions).toHaveLength(3); - expect(dropdownOptions[0].item(0)?.textContent).toBe(REDIRECT_QUERY); - expect(dropdownOptions[1].item(0)?.textContent).toBe( + expect(dropdownOptions[0].item(0)).toHaveTextContent(REDIRECT_QUERY); + expect(dropdownOptions[1].item(0)).toHaveTextContent( 'not a redirect item 1' ); - expect(dropdownOptions[2].item(0)?.textContent).toBe( + expect(dropdownOptions[2].item(0)).toHaveTextContent( 'not a redirect item 2' ); }); @@ -399,10 +347,10 @@ describe('createRedirectUrlPlugin', () => { findHitsSection(panelContainer) ); expect(dropdownOptions).toHaveLength(2); - expect(dropdownOptions[0].item(0)?.textContent).toBe( + expect(dropdownOptions[0].item(0)).toHaveTextContent( 'not a redirect item 1' ); - expect(dropdownOptions[1].item(0)?.textContent).toBe( + expect(dropdownOptions[1].item(0)).toHaveTextContent( 'not a redirect item 2' ); }); @@ -468,15 +416,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); }); }); @@ -534,95 +485,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); @@ -712,7 +593,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: { @@ -786,7 +667,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: { From bd201c33b4b1161775a7fae12351032802d4379d Mon Sep 17 00:00:00 2001 From: Dan Rodriguez Date: Wed, 23 Apr 2025 12:39:45 -0600 Subject: [PATCH 07/13] feat: require new transformResponse --- .../src/createRedirectUrlPlugin.ts | 23 ++++++++++++------- .../src/types/Redirect.ts | 23 ++++++++++++++----- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts b/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts index c15ffedc7..0567fcead 100644 --- a/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts +++ b/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts @@ -12,13 +12,18 @@ import { OnRedirectOptions, RedirectUrlItem, RedirectUrlPlugin as RedirectUrlPluginData, - TransformResponseParams, + Response, + TransformResponse, } from './types'; function defaultTransformResponse( - response: TransformResponseParams -): string | undefined { - return (response as Record).renderingContent?.redirect?.url; + response: Response +): TransformResponse | undefined { + return { + queryUsed: (response as Record).query, + redirectUrl: (response as Record).renderingContent?.redirect + ?.url, + }; } function defaultOnRedirect( @@ -67,7 +72,7 @@ export function createRedirectUrlPlugin( const redirect: RedirectUrlItem = { sourceId: source.sourceId, urls: results - .map((result) => transformResponse(result)) + .map((result) => transformResponse(result)?.redirectUrl) .filter((url) => url !== undefined), }; @@ -94,9 +99,11 @@ export function createRedirectUrlPlugin( name: 'aa.redirectUrlPlugin', subscribe({ onResolve, onSelect, setContext, setIsOpen }) { onResolve(({ results, source, state }) => { - // Since searches can be resolved in any order, verify the query from the search - // matches the input query to ensure a redirect item accurately reflects the input - if (results[0].query !== state.query) { + // Ensure the resolved response matches the input query text before processing redirects. + const hasMatchedQuery = (results as any).some( + (result) => transformResponse(result)?.queryUsed === state.query + ); + if (!hasMatchedQuery) { return; } diff --git a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts index a020724d7..367eb31be 100644 --- a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts +++ b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts @@ -27,9 +27,22 @@ export type OnRedirectOptions = { state: AutocompleteState; }; +/** + * Used to map response data into values required by the plugin. + */ export interface TransformResponse { - url: string | undefined; - query: string | undefined; + /** + * The query used in the request. + * + * This is needed to accurately match the redirect with the correct query. + * Otherwise, there is a risk of the last request reflecting a different query value. + * (ex: typing "shoes" quickly risks mismatching the redirect item with "sho" instead) + */ + queryUsed: string | undefined; + /** + * The redirect url to use from the response data. + */ + redirectUrl: string | undefined; } export type Response = @@ -42,9 +55,7 @@ export type CreateRedirectUrlPluginParams = { * * Supports Algolia results out of the box. */ - transformResponse?( - response: Response - ): TransformResponse | string | undefined; + transformResponse?(response: Response): TransformResponse; /** * Handles the navigation logic once a redirect is triggered * @@ -59,7 +70,7 @@ export type CreateRedirectUrlPluginParams = { */ templates?: SourceTemplates; /** - * Waits for all pending requests to complete before handling a form submission . + * 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. From 470e82761e2c25c2541feadf1f9326ca2b5082ce Mon Sep 17 00:00:00 2001 From: Dan Rodriguez Date: Wed, 23 Apr 2025 12:46:41 -0600 Subject: [PATCH 08/13] test: cleanup --- .../src/__tests__/createRedirectUrlPlugin.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 52550cc97..b9615fe49 100644 --- a/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts +++ b/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts @@ -176,8 +176,9 @@ describe('createRedirectUrlPlugin', () => { const redirectUrlPlugin = createRedirectUrlPlugin({ transformResponse(response) { return { - queryUsed: REDIRECT_QUERY, - redirectUrl: (response as Record).customRedirect?.url, + queryUsed: 'different query', + redirectUrl: (response as Record).renderingContent + ?.redirect?.url, }; }, }); From 2bcdf0f240f34daf1babb87b05ba403a6d58a95f Mon Sep 17 00:00:00 2001 From: Dan Rodriguez Date: Wed, 23 Apr 2025 12:53:22 -0600 Subject: [PATCH 09/13] chore: address import lint --- .../autocomplete-plugin-redirect-url/src/types/Redirect.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts index 367eb31be..474c6c3ed 100644 --- a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts +++ b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts @@ -4,10 +4,7 @@ import { InternalAutocompleteOptions, } from '@algolia/autocomplete-core'; import { SourceTemplates } from '@algolia/autocomplete-js'; -import { - SearchForFacetValuesResponse, - TransformedRequesterResponse, -} from '@algolia/autocomplete-preset-algolia'; +import { SearchForFacetValuesResponse } from '@algolia/autocomplete-preset-algolia'; import type { SearchResponse } from '@algolia/autocomplete-shared'; export interface RedirectUrlPlugin { From 3943b98dc86248a27e193db6db12b18370a18ef6 Mon Sep 17 00:00:00 2001 From: Dan Rodriguez Date: Thu, 24 Apr 2025 07:41:57 -0600 Subject: [PATCH 10/13] fix: split transform response into two fns --- .../__tests__/createRedirectUrlPlugin.test.ts | 10 +++--- .../src/createRedirectUrlPlugin.ts | 23 ++++++------ .../src/types/Redirect.ts | 35 +++++++------------ 3 files changed, 31 insertions(+), 37 deletions(-) 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 b9615fe49..f3adc722b 100644 --- a/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts +++ b/packages/autocomplete-plugin-redirect-url/src/__tests__/createRedirectUrlPlugin.test.ts @@ -175,11 +175,11 @@ describe('createRedirectUrlPlugin', () => { 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 { - queryUsed: 'different query', - redirectUrl: (response as Record).renderingContent - ?.redirect?.url, - }; + return (response as Record).renderingContent?.redirect + ?.url; + }, + transformResponseToQuery() { + return 'different query'; }, }); diff --git a/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts b/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts index 0567fcead..eff10439e 100644 --- a/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts +++ b/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts @@ -13,17 +13,18 @@ import { RedirectUrlItem, RedirectUrlPlugin as RedirectUrlPluginData, Response, - TransformResponse, } from './types'; function defaultTransformResponse( response: Response -): TransformResponse | undefined { - return { - queryUsed: (response as Record).query, - redirectUrl: (response as Record).renderingContent?.redirect - ?.url, - }; +): string | undefined { + return (response as Record).renderingContent?.redirect?.url; +} + +function defaultTransformResponseToQuery( + response: Response +): string | undefined { + return (response as Record).query; } function defaultOnRedirect( @@ -51,6 +52,7 @@ function getOptions( ) { return { transformResponse: defaultTransformResponse, + transformResponseToQuery: defaultTransformResponseToQuery, templates: defaultTemplates, onRedirect: defaultOnRedirect, ...options, @@ -66,13 +68,14 @@ 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 = { sourceId: source.sourceId, urls: results - .map((result) => transformResponse(result)?.redirectUrl) + .map((result) => transformResponse(result)) .filter((url) => url !== undefined), }; @@ -101,7 +104,7 @@ export function createRedirectUrlPlugin( onResolve(({ results, source, state }) => { // Ensure the resolved response matches the input query text before processing redirects. const hasMatchedQuery = (results as any).some( - (result) => transformResponse(result)?.queryUsed === state.query + (result) => transformResponseToQuery(result) === state.query ); if (!hasMatchedQuery) { return; diff --git a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts index 474c6c3ed..74e8e023e 100644 --- a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts +++ b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts @@ -24,39 +24,30 @@ export type OnRedirectOptions = { state: AutocompleteState; }; -/** - * Used to map response data into values required by the plugin. - */ -export interface TransformResponse { - /** - * The query used in the request. - * - * This is needed to accurately match the redirect with the correct query. - * Otherwise, there is a risk of the last request reflecting a different query value. - * (ex: typing "shoes" quickly risks mismatching the redirect item with "sho" instead) - */ - queryUsed: string | undefined; - /** - * The redirect url to use from the response data. - */ - redirectUrl: string | undefined; -} - export type Response = | SearchResponse | SearchForFacetValuesResponse; export type CreateRedirectUrlPluginParams = { /** - * Map the response to values that can be interpreted by the plugin to correctly parse redirects. + * Maps the response to the redirect url that will be used by the plugin. + * + * Already supports Algolia results by default. + */ + transformResponse?(response: Response): 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) * - * Supports Algolia results out of the box. + * Already supports Algolia results by default. */ - transformResponse?(response: Response): TransformResponse; + transformResponseToQuery?(response: Response): string | undefined; /** * Handles the navigation logic once a redirect is triggered * - * Supports Algolia results out of the box. + * Already supports Algolia results by default. */ onRedirect?( redirects: RedirectUrlItem[], From abd6c67519ae6566aef355e9c4c3f1ebee8f3289 Mon Sep 17 00:00:00 2001 From: Dan Rodriguez Date: Thu, 24 Apr 2025 07:42:28 -0600 Subject: [PATCH 11/13] chore: cleanup --- packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts index 74e8e023e..2cb978ed9 100644 --- a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts +++ b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts @@ -45,7 +45,7 @@ export type CreateRedirectUrlPluginParams = { */ transformResponseToQuery?(response: Response): string | undefined; /** - * Handles the navigation logic once a redirect is triggered + * Handles the navigation logic once a redirect is triggered. * * Already supports Algolia results by default. */ From aa30c9ac40fab44ad81f92b4c43376f09b0a9472 Mon Sep 17 00:00:00 2001 From: Dan Rodriguez Date: Thu, 24 Apr 2025 08:07:36 -0600 Subject: [PATCH 12/13] chore: revert transform response type name --- .../src/createRedirectUrlPlugin.ts | 6 +++--- .../src/types/Redirect.ts | 10 +++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts b/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts index eff10439e..567d94b8f 100644 --- a/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts +++ b/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts @@ -12,17 +12,17 @@ import { OnRedirectOptions, RedirectUrlItem, RedirectUrlPlugin as RedirectUrlPluginData, - Response, + TransformResponseParams, } from './types'; function defaultTransformResponse( - response: Response + response: TransformResponseParams ): string | undefined { return (response as Record).renderingContent?.redirect?.url; } function defaultTransformResponseToQuery( - response: Response + response: TransformResponseParams ): string | undefined { return (response as Record).query; } diff --git a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts index 2cb978ed9..7ab60b298 100644 --- a/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts +++ b/packages/autocomplete-plugin-redirect-url/src/types/Redirect.ts @@ -24,7 +24,7 @@ export type OnRedirectOptions = { state: AutocompleteState; }; -export type Response = +export type TransformResponseParams = | SearchResponse | SearchForFacetValuesResponse; @@ -34,7 +34,9 @@ export type CreateRedirectUrlPluginParams = { * * Already supports Algolia results by default. */ - transformResponse?(response: Response): string | undefined; + 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 @@ -43,7 +45,9 @@ export type CreateRedirectUrlPluginParams = { * * Already supports Algolia results by default. */ - transformResponseToQuery?(response: Response): string | undefined; + transformResponseToQuery?( + response: TransformResponseParams + ): string | undefined; /** * Handles the navigation logic once a redirect is triggered. * From 31f1287972d489f78327968186c9e0d6b46a7e29 Mon Sep 17 00:00:00 2001 From: Dan Rodriguez Date: Thu, 24 Apr 2025 08:08:45 -0600 Subject: [PATCH 13/13] chore: pr feedback --- .../src/createRedirectUrlPlugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts b/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts index 567d94b8f..6804f540a 100644 --- a/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts +++ b/packages/autocomplete-plugin-redirect-url/src/createRedirectUrlPlugin.ts @@ -103,10 +103,10 @@ export function createRedirectUrlPlugin( subscribe({ onResolve, onSelect, setContext, setIsOpen }) { onResolve(({ results, source, state }) => { // Ensure the resolved response matches the input query text before processing redirects. - const hasMatchedQuery = (results as any).some( + const matchesCurrentQuery = (results as any).some( (result) => transformResponseToQuery(result) === state.query ); - if (!hasMatchedQuery) { + if (!matchesCurrentQuery) { return; }