diff --git a/packages/snap-controller/src/Recommendation/RecommendationController.test.ts b/packages/snap-controller/src/Recommendation/RecommendationController.test.ts index 9d67009f9..fdf5c56c7 100644 --- a/packages/snap-controller/src/Recommendation/RecommendationController.test.ts +++ b/packages/snap-controller/src/Recommendation/RecommendationController.test.ts @@ -8,7 +8,7 @@ import { EventManager } from '@searchspring/snap-event-manager'; import { Profiler } from '@searchspring/snap-profiler'; import { Logger } from '@searchspring/snap-logger'; import { MockClient } from '@searchspring/snap-shared'; - +import { waitFor } from '@testing-library/preact'; import { RecommendationController } from './RecommendationController'; const globals = { siteId: '8uyt2m' }; @@ -118,6 +118,135 @@ describe('Recommendation Controller', () => { }); }); + it(`tests searchOnPageShow triggers search on persisted pageshow event `, async function () { + const controller = new RecommendationController(recommendConfig, { + client: new MockClient(globals, {}), + store: new RecommendationStore(recommendConfig, services), + urlManager, + eventManager: new EventManager(), + profiler: new Profiler(), + logger: new Logger(), + tracker: new Tracker(globals), + }); + + await controller.search(); + + const searchSpy = jest.spyOn(controller, 'search'); + + expect(searchSpy).not.toHaveBeenCalled(); + + // Mock PageTransitionEvent + class MockPageTransitionEvent extends Event { + public persisted: boolean; + + constructor(type: string, eventInitDict?: EventInit & { persisted?: boolean }) { + super(type, eventInitDict); + this.persisted = eventInitDict?.persisted ?? false; + } + } + + const event = new MockPageTransitionEvent('pageshow', { + bubbles: true, + persisted: true, + }); + + window.dispatchEvent(event); + + await waitFor(() => { + expect(searchSpy).toHaveBeenCalled(); + }); + }); + + it(`can turn off searchOnPageShow`, async function () { + const customConfig = { + ...recommendConfig, + settings: { + searchOnPageShow: false, + }, + }; + const controller = new RecommendationController(customConfig, { + client: new MockClient(globals, {}), + store: new RecommendationStore(recommendConfig, services), + urlManager, + eventManager: new EventManager(), + profiler: new Profiler(), + logger: new Logger(), + tracker: new Tracker(globals), + }); + + await controller.search(); + + const searchSpy = jest.spyOn(controller, 'search'); + + expect(searchSpy).not.toHaveBeenCalled(); + + // Mock PageTransitionEvent + class MockPageTransitionEvent extends Event { + public persisted: boolean; + + constructor(type: string, eventInitDict?: EventInit & { persisted?: boolean }) { + super(type, eventInitDict); + this.persisted = eventInitDict?.persisted ?? false; + } + } + + const event = new MockPageTransitionEvent('pageshow', { + bubbles: true, + persisted: true, + }); + + window.dispatchEvent(event); + + await waitFor(() => { + expect(searchSpy).not.toHaveBeenCalled(); + }); + }); + + it(`tests searchOnPageShow doesnt trigger search if persisted is false or undefined on the pageshow event`, async function () { + const controller = new RecommendationController(recommendConfig, { + client: new MockClient(globals, {}), + store: new RecommendationStore(recommendConfig, services), + urlManager, + eventManager: new EventManager(), + profiler: new Profiler(), + logger: new Logger(), + tracker: new Tracker(globals), + }); + + await controller.search(); + + const searchSpy = jest.spyOn(controller, 'search'); + + expect(searchSpy).not.toHaveBeenCalled(); + + // Mock PageTransitionEvent + class MockPageTransitionEvent extends Event { + public persisted: boolean; + + constructor(type: string, eventInitDict?: EventInit & { persisted?: boolean }) { + super(type, eventInitDict); + this.persisted = eventInitDict?.persisted ?? false; + } + } + + const event = new MockPageTransitionEvent('pageshow', { + bubbles: true, + persisted: false, + }); + + window.dispatchEvent(event); + + const event2 = new MockPageTransitionEvent('pageshow', { + bubbles: true, + }); + + window.dispatchEvent(event2); + + await waitFor(() => { + expect(searchSpy).not.toHaveBeenCalled(); + }); + }); + it('can invoke controller track.click and track.product.click', async () => { const controller = new RecommendationController(recommendConfig, { client: new MockClient(globals, {}), diff --git a/packages/snap-controller/src/Recommendation/RecommendationController.ts b/packages/snap-controller/src/Recommendation/RecommendationController.ts index 430ac32e9..31155842b 100644 --- a/packages/snap-controller/src/Recommendation/RecommendationController.ts +++ b/packages/snap-controller/src/Recommendation/RecommendationController.ts @@ -61,6 +61,16 @@ export class RecommendationController extends AbstractController { throw new Error(`Invalid config passed to RecommendationController. The "tag" attribute is required.`); } + // attach to bfCache restore event and re-run search on the controller + // enabled by default + if (config.settings?.searchOnPageShow !== false) { + window.addEventListener('pageshow', (e) => { + if (e.persisted && !this.store.error && this.store.loaded && !this.store.loading) { + this.search(); + } + }); + } + // deep merge config with defaults this.config = deepmerge(defaultConfig, this.config); this.store.setConfig(this.config); diff --git a/packages/snap-preact/src/Instantiators/RecommendationInstantiator.test.tsx b/packages/snap-preact/src/Instantiators/RecommendationInstantiator.test.tsx index 22c91f75a..584613cba 100644 --- a/packages/snap-preact/src/Instantiators/RecommendationInstantiator.test.tsx +++ b/packages/snap-preact/src/Instantiators/RecommendationInstantiator.test.tsx @@ -5,6 +5,7 @@ import { cookies } from '@searchspring/snap-toolbox'; import { Logger } from '@searchspring/snap-logger'; import { MockClient } from '@searchspring/snap-shared'; import { Next } from '@searchspring/snap-event-manager'; +import { waitFor } from '@testing-library/preact'; const DEFAULT_PROFILE = 'trending'; const CART_COOKIE = 'ssCartProducts'; @@ -936,4 +937,45 @@ describe('RecommendationInstantiator', () => { expect(plugin2).toHaveBeenCalledWith(controller); }); }); + + it(`searchOnPageShow triggers search on persisted pageshow event `, async function () { + document.body.innerHTML = ``; + + const attachmentConfig = { + ...baseConfig, + config: { + branch: baseConfig.config.branch, + }, + }; + + const client = new MockClient(baseConfig.client!.globals, {}); + const recommendationInstantiator = new RecommendationInstantiator(attachmentConfig as RecommendationInstantiatorConfig, { client }); + await wait(); + + Object.keys(recommendationInstantiator.controller).forEach(async (controllerId) => { + const controller = recommendationInstantiator.controller[controllerId]; + const searchSpy = jest.spyOn(controller, 'search'); + expect(searchSpy).not.toHaveBeenCalled(); + + // Mock PageTransitionEvent + class MockPageTransitionEvent extends Event { + public persisted: boolean; + + constructor(type: string, eventInitDict?: EventInit & { persisted?: boolean }) { + super(type, eventInitDict); + this.persisted = eventInitDict?.persisted ?? false; + } + } + const event = new MockPageTransitionEvent('pageshow', { + bubbles: true, + persisted: true, + }); + + window.dispatchEvent(event); + + await waitFor(() => { + expect(searchSpy).toHaveBeenCalled(); + }); + }); + }); }); diff --git a/packages/snap-store-mobx/src/types.ts b/packages/snap-store-mobx/src/types.ts index 2d8cbb2c0..5c0770cab 100644 --- a/packages/snap-store-mobx/src/types.ts +++ b/packages/snap-store-mobx/src/types.ts @@ -145,6 +145,7 @@ export type RecommendationStoreConfig = StoreConfig & { batchId?: number; settings?: { variants?: VariantConfig; + searchOnPageShow: boolean; }; };