Skip to content

Commit

Permalink
feat(controller): adding setting in autocomplete to opt out of input …
Browse files Browse the repository at this point in the history
…and submit binding'
  • Loading branch information
korgon committed Dec 5, 2024
1 parent 2099968 commit 6980fce
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AutocompleteController, INPUT_DELAY as _INPUT_DELAY } from './Autocompl
import { waitFor } from '@testing-library/preact';

import { MockClient } from '@searchspring/snap-shared';
import deepmerge from 'deepmerge';

const KEY_ENTER = 13;
const KEY_ESCAPE = 27;
Expand Down Expand Up @@ -226,6 +227,35 @@ describe('Autocomplete Controller', () => {
expect(controller.urlManager.state.query).toBe(undefined);
});

it('can opt out of binding input event', async () => {
const bindingConfig = deepmerge(acConfig, { settings: { bind: { input: false } } });

const controller = new AutocompleteController(bindingConfig, {
client: new MockClient(globals, {}),
store: new AutocompleteStore(bindingConfig, services),
urlManager,
eventManager: new EventManager(),
profiler: new Profiler(),
logger: new Logger(),
tracker: new Tracker(globals),
});

await controller.bind();

let inputEl: HTMLInputElement | null;

await waitFor(() => {
inputEl = document.querySelector(controller.config.selector);
expect(inputEl).toBeDefined();
});

const query = 'bumpers';
inputEl!.value = query;
inputEl!.focus();
inputEl!.dispatchEvent(new Event('input'));
expect(controller.urlManager.state.query).toBe(undefined);
});

it('can bind to input after input has been focused', async () => {
const controller = new AutocompleteController(acConfig, {
client: new MockClient(globals, {}),
Expand Down Expand Up @@ -672,6 +702,79 @@ describe('Autocomplete Controller', () => {
beforeSubmitfn.mockClear();
});

it('can opt out of submit event', async () => {
document.body.innerHTML = '<div><form action="/search.html"><input type="text" id="search_query"></form></div>';

const bindingConfig = deepmerge(acConfig, { settings: { bind: { submit: false } } });

const controller = new AutocompleteController(bindingConfig, {
client: new MockClient(globals, {}),
store: new AutocompleteStore(bindingConfig, services),
urlManager,
eventManager: new EventManager(),
profiler: new Profiler(),
logger: new Logger(),
tracker: new Tracker(globals),
});

await controller.bind();
(controller.client as MockClient).mockData.updateConfig({ autocomplete: 'autocomplete.query.bumpers' });

const inputEl: HTMLInputElement | null = document.querySelector(controller.config.selector);

const query = 'bumpers';
inputEl!.value = query;

const form = inputEl!.form;
const beforeSubmitfn = jest.spyOn(controller.eventManager, 'fire');

form?.dispatchEvent(new Event('submit', { bubbles: true }));
//this timeout seems to be needed. Cant replace with waitFor
await new Promise((resolve) => setTimeout(resolve, INPUT_DELAY));

expect(beforeSubmitfn).not.toHaveBeenCalledWith('beforeSubmit', {
controller,
input: inputEl!,
});

beforeSubmitfn.mockClear();
});

it('can opt out of submit event (with no form)', async () => {
const bindingConfig = deepmerge(acConfig, { action: '/search', settings: { bind: { submit: false } } });

const controller = new AutocompleteController(bindingConfig, {
client: new MockClient(globals, {}),
store: new AutocompleteStore(bindingConfig, services),
urlManager,
eventManager: new EventManager(),
profiler: new Profiler(),
logger: new Logger(),
tracker: new Tracker(globals),
});

await controller.bind();
(controller.client as MockClient).mockData.updateConfig({ autocomplete: 'autocomplete.query.bumpers' });

const beforeSubmitfn = jest.spyOn(controller.eventManager, 'fire');
const inputEl: HTMLInputElement | null = document.querySelector(controller.config.selector);

const query = 'bumpers';
inputEl!.value = query;

inputEl!.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, keyCode: KEY_ENTER }));

// this timeout seems to be needed. Cant replace with waitFor
await new Promise((resolve) => setTimeout(resolve, INPUT_DELAY));

expect(beforeSubmitfn).not.toHaveBeenCalledWith('beforeSubmit', {
controller,
input: inputEl!,
});

beforeSubmitfn.mockClear();
});

it('adds fallback query when integrated spell correct setting is enabled', async () => {
let acConfig2 = { ...acConfig, settings: { integratedSpellCorrection: true } };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ const defaultConfig: AutocompleteControllerConfig = {
merchandising: true,
singleResult: false,
},
bind: {
input: true,
submit: true,
},
},
};

Expand Down Expand Up @@ -216,6 +220,7 @@ export class AutocompleteController extends AbstractController {
handlers = {
input: {
enterKey: async (e: KeyboardEvent): Promise<boolean | undefined> => {
console.log('enter key handler...');
if (e.keyCode == KEY_ENTER) {
const input = e.target as HTMLInputElement;
let actionUrl = this.store.services.urlManager;
Expand Down Expand Up @@ -430,6 +435,7 @@ export class AutocompleteController extends AbstractController {
}

async bind(): Promise<void> {
console.log('binding???');
if (!this.initialized) {
await this.init();
}
Expand All @@ -438,14 +444,15 @@ export class AutocompleteController extends AbstractController {

const inputs: NodeListOf<HTMLInputElement> = document.querySelectorAll(this.config.selector);
inputs.forEach((input) => {
console.log('binding input???', input);
input.setAttribute('spellcheck', 'false');
input.setAttribute('autocomplete', 'off');
input.setAttribute('autocorrect', 'off');
input.setAttribute('autocapitalize', 'none');

input.setAttribute(INPUT_ATTRIBUTE, '');

input.addEventListener('input', this.handlers.input.input);
this.config.settings?.bind?.input && input.addEventListener('input', this.handlers.input.input);

if (this.config?.settings?.initializeFromUrl && !input.value && this.store.state.input) {
input.value = this.store.state.input;
Expand All @@ -457,11 +464,13 @@ export class AutocompleteController extends AbstractController {
const form = input.form;
let formActionUrl: string | undefined;

console.log('binding action???', this.config.action);
if (this.config.action) {
input.addEventListener('keydown', this.handlers.input.enterKey);
console.log('binding action???');
this.config.settings?.bind?.submit && input.addEventListener('keydown', this.handlers.input.enterKey);
formActionUrl = this.config.action;
} else if (form) {
form.addEventListener('submit', this.handlers.input.formSubmit as unknown as EventListener);
this.config.settings?.bind?.submit && form.addEventListener('submit', this.handlers.input.formSubmit as unknown as EventListener);
formActionUrl = form.action || '';

// serializeForm will include additional form element in our urlManager as globals
Expand Down
4 changes: 4 additions & 0 deletions packages/snap-store-mobx/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ export type AutocompleteStoreConfigSettings = {
merchandising?: boolean;
singleResult?: boolean;
};
bind?: {
input?: boolean;
submit?: boolean;
};
};

// Autocomplete config
Expand Down

0 comments on commit 6980fce

Please sign in to comment.