From 6980fcee30220714fd5837770eae725355f81dcf Mon Sep 17 00:00:00 2001 From: kevin Date: Thu, 5 Dec 2024 14:29:47 -0700 Subject: [PATCH] feat(controller): adding setting in autocomplete to opt out of input and submit binding' --- .../AutocompleteController.test.ts | 103 ++++++++++++++++++ .../Autocomplete/AutocompleteController.ts | 15 ++- packages/snap-store-mobx/src/types.ts | 4 + 3 files changed, 119 insertions(+), 3 deletions(-) diff --git a/packages/snap-controller/src/Autocomplete/AutocompleteController.test.ts b/packages/snap-controller/src/Autocomplete/AutocompleteController.test.ts index 7a3be78ef..f50738093 100644 --- a/packages/snap-controller/src/Autocomplete/AutocompleteController.test.ts +++ b/packages/snap-controller/src/Autocomplete/AutocompleteController.test.ts @@ -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; @@ -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, {}), @@ -672,6 +702,79 @@ describe('Autocomplete Controller', () => { beforeSubmitfn.mockClear(); }); + it('can opt out of submit event', async () => { + document.body.innerHTML = '
'; + + 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 } }; diff --git a/packages/snap-controller/src/Autocomplete/AutocompleteController.ts b/packages/snap-controller/src/Autocomplete/AutocompleteController.ts index d452007bc..82b4ceaed 100644 --- a/packages/snap-controller/src/Autocomplete/AutocompleteController.ts +++ b/packages/snap-controller/src/Autocomplete/AutocompleteController.ts @@ -35,6 +35,10 @@ const defaultConfig: AutocompleteControllerConfig = { merchandising: true, singleResult: false, }, + bind: { + input: true, + submit: true, + }, }, }; @@ -216,6 +220,7 @@ export class AutocompleteController extends AbstractController { handlers = { input: { enterKey: async (e: KeyboardEvent): Promise => { + console.log('enter key handler...'); if (e.keyCode == KEY_ENTER) { const input = e.target as HTMLInputElement; let actionUrl = this.store.services.urlManager; @@ -430,6 +435,7 @@ export class AutocompleteController extends AbstractController { } async bind(): Promise { + console.log('binding???'); if (!this.initialized) { await this.init(); } @@ -438,6 +444,7 @@ export class AutocompleteController extends AbstractController { const inputs: NodeListOf = 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'); @@ -445,7 +452,7 @@ export class AutocompleteController extends AbstractController { 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; @@ -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 diff --git a/packages/snap-store-mobx/src/types.ts b/packages/snap-store-mobx/src/types.ts index 5c0770cab..bac85f308 100644 --- a/packages/snap-store-mobx/src/types.ts +++ b/packages/snap-store-mobx/src/types.ts @@ -124,6 +124,10 @@ export type AutocompleteStoreConfigSettings = { merchandising?: boolean; singleResult?: boolean; }; + bind?: { + input?: boolean; + submit?: boolean; + }; }; // Autocomplete config