Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
32 changes: 31 additions & 1 deletion packages/autocomplete-core/src/__tests__/getFormProps.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { createPlayground } from '../../../../test/utils';
import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights';
import { createRedirectUrlPlugin } from '@algolia/autocomplete-plugin-redirect-url';

import { createPlayground, runAllMicroTasks } from '../../../../test/utils';
import { createAutocomplete } from '../createAutocomplete';

describe('getFormProps', () => {
Expand Down Expand Up @@ -176,6 +179,33 @@ describe('getFormProps', () => {
})
);
});

describe('a plugin is configured with the option "awaitSubmit" === true', () => {
test('should await pending requests before triggering the submit event', async () => {
const plugins = [
createRedirectUrlPlugin(), // "awaitSubmit" is true by default
createAlgoliaInsightsPlugin({}), // "awaitSubmit" is neither configurable nor defined
];
const onSubmit = jest.fn();
const { getFormProps, inputElement } = createPlayground(
createAutocomplete,
{
onSubmit,
plugins,
}
);

const formProps = getFormProps({ inputElement });

formProps.onSubmit(new Event('submit'));

expect(onSubmit).toHaveBeenCalledTimes(0);

await runAllMicroTasks();

expect(onSubmit).toHaveBeenCalledTimes(1);
});
});
});

describe('onReset', () => {
Expand Down
51 changes: 51 additions & 0 deletions packages/autocomplete-core/src/__tests__/getInputProps.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights';
import { createRedirectUrlPlugin } from '@algolia/autocomplete-plugin-redirect-url';
import { fireEvent, waitFor } from '@testing-library/dom';
import userEvent from '@testing-library/user-event';

Expand All @@ -9,6 +11,14 @@ import {
runAllMicroTasks,
} from '../../../../test/utils';
import { createAutocomplete } from '../createAutocomplete';
import { createCancelablePromiseList } from '../utils';

jest.mock('../utils/createCancelablePromiseList', () => ({
createCancelablePromiseList: jest.fn(
jest.requireActual('../utils/createCancelablePromiseList')
.createCancelablePromiseList
),
}));

describe('getInputProps', () => {
beforeEach(() => {
Expand Down Expand Up @@ -1287,6 +1297,47 @@ describe('getInputProps', () => {
);
});

describe('a plugin is configured with the option "awaitSubmit"', () => {
const cancelAll = jest.fn();
const event = { ...new KeyboardEvent('keydown'), key: 'Enter' };

beforeEach(() => {
(createCancelablePromiseList as jest.Mock).mockReturnValueOnce({
add: jest.fn,
cancelAll,
isEmpty: jest.fn,
wait: jest.fn,
});
});

test('when false or undefined it should not cancel pending requests', () => {
const plugins = [
createRedirectUrlPlugin(), // "awaitSubmit" is true by default
createAlgoliaInsightsPlugin({}), // "awaitSubmit" is neither configurable nor defined
];

const { inputProps } = createPlayground(createAutocomplete, {
plugins,
});

inputProps.onKeyDown(event);

expect(cancelAll).toHaveBeenCalledTimes(0);
});

test('when false it should cancel pending requests', () => {
const plugins = [createRedirectUrlPlugin({ awaitSubmit: false })];

const { inputProps } = createPlayground(createAutocomplete, {
plugins,
});

inputProps.onKeyDown(event);

expect(cancelAll).toHaveBeenCalledTimes(1);
});
});

describe('Plain Enter', () => {
test('calls onSelect with item URL', () => {
const onSelect = jest.fn();
Expand Down
32 changes: 23 additions & 9 deletions packages/autocomplete-core/src/getPropGetters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,22 +126,36 @@ export function getPropGetters<
const getFormProps: GetFormProps<TEvent> = (providedProps) => {
const { inputElement, ...rest } = providedProps;

const handleSubmit = (event: TEvent) => {
props.onSubmit({
event,
refresh,
state: store.getState(),
...setters,
});

store.dispatch('submit', null);
providedProps.inputElement?.blur();
};

return {
action: '',
noValidate: true,
role: 'search',
onSubmit: (event) => {
(event as unknown as Event).preventDefault();

props.onSubmit({
event,
refresh,
state: store.getState(),
...setters,
});

store.dispatch('submit', null);
providedProps.inputElement?.blur();
// Wait for pending requests to resolve before handling
// the submit event if a plugin is configured to do so.
if (
props.plugins.some(
(plugin) => plugin.__autocomplete_pluginOptions?.awaitSubmit
Comment thread
drodriguln marked this conversation as resolved.
Outdated
)
) {
store.pendingRequests.wait().then(() => handleSubmit(event));
} else {
handleSubmit(event);
}
},
onReset: (event) => {
(event as unknown as Event).preventDefault();
Expand Down
10 changes: 8 additions & 2 deletions packages/autocomplete-core/src/onKeyDown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,15 @@ export function onKeyDown<TItem extends BaseItem>({
) {
// If requests are still pending when the panel closes, they could reopen
// the panel once they resolve.
// We want to prevent any subsequent query from reopening the panel
// Unless explicitly configured to await these requests,
// we want to prevent any subsequent query from reopening the panel
// because it would result in an unsolicited UI behavior.
if (!props.debug) {
if (
!props.debug &&
!props.plugins.some(
(plugin) => plugin.__autocomplete_pluginOptions?.awaitSubmit
)
) {
store.pendingRequests.cancelAll();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,18 @@ describe('createCancelablePromiseList', () => {
expect(cancelablePromise3.isCanceled()).toBe(true);
expect(cancelablePromiseList.isEmpty()).toBe(true);
});

test('waits for all promises to resolve', async () => {
const cancelablePromiseList = createCancelablePromiseList();
const cancelablePromise = createCancelablePromise.resolve();

cancelablePromiseList.add(cancelablePromise);
cancelablePromiseList.add(cancelablePromise);

expect(cancelablePromiseList.isEmpty()).toBe(false);

await cancelablePromiseList.wait();

expect(cancelablePromiseList.isEmpty()).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export type CancelablePromiseList<TValue> = {
* Whether there are pending promises in the list.
*/
isEmpty(): boolean;
/**
* Waits for all pending promises to be resolved.
*/
wait(): Promise<Array<Awaited<TValue>>>;
};

export function createCancelablePromiseList<
Expand All @@ -39,5 +43,8 @@ export function createCancelablePromiseList<
isEmpty() {
return list.length === 0;
},
wait() {
return Promise.all(list);
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,27 @@ describe('createRedirectUrlPlugin', () => {
expect(plugin.name).toBe('aa.redirectUrlPlugin');
});

test('exposes passed options and excludes default ones', () => {
test('exposes the default value only for "awaitSubmit" in plugin.__autocomplete_pluginOptions', () => {
const plugin = createRedirectUrlPlugin();

expect(plugin.__autocomplete_pluginOptions).toEqual({
awaitSubmit: true,
});
});

test('exposes all provided options with plugin.__autocomplete_pluginOptions', () => {
const plugin = createRedirectUrlPlugin({
transformResponse: jest.fn(),
templates: { item: () => 'hey' },
onRedirect: jest.fn(),
awaitSubmit: false,
});

expect(plugin.__autocomplete_pluginOptions).toEqual({
transformResponse: expect.any(Function),
templates: expect.any(Object),
onRedirect: expect.any(Function),
awaitSubmit: false,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function getRedirectData({ state }) {
}

export function createRedirectUrlPlugin<TItem extends BaseItem>(
options: CreateRedirectUrlPluginParams<TItem> = {}
options: CreateRedirectUrlPluginParams<TItem> = { awaitSubmit: true }
Comment thread
drodriguln marked this conversation as resolved.
Outdated
): AutocompletePlugin<RedirectUrlItem, undefined> {
const { transformResponse, templates, onRedirect } = getOptions(options);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ export type CreateRedirectUrlPluginParams<TItem extends BaseItem> = {
options: OnRedirectOptions<RedirectUrlItem>
): void;
templates?: SourceTemplates<RedirectUrlItem>;
awaitSubmit?: boolean;
};