UNPKG

salsify-experiences-sdk

Version:

SDK to be used by commerce websites to implement product experiences.

194 lines (147 loc) 6.68 kB
/** * @jest-environment jsdom * @jest-environment-options {"url": "https://salsify-ecdn.com/sdk/client-id/lang-code/BTF/id-type/existing-product/index.html"} */ import SdkApi from '../api' import { MessageChannel as WorkerThreadsMessageChannel } from 'worker_threads' import { makeResponse } from '../__tests__/helpers' import request from '../utils/request' import { createLogger } from '../utils/logger' jest.mock('../utils/request') jest.mock('../utils/logger') const headMock = request.head as jest.Mock const getMock = request.get as jest.Mock const logMock = jest.fn() ;(createLogger as jest.Mock).mockReturnValue({ log: logMock }) const exampleContent = '<div>enhanced-content</div>' const emptyContent = '' const uuidRegex = /^[0-9a-f]{8}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{12}$/ const defaultOptions = { clientId: 'client-id', } function expectPageSessionId(messageListener: jest.Mock, pageSessionId: unknown): void { expect(messageListener).toHaveBeenLastCalledWith( expect.objectContaining({ data: expect.objectContaining({ pageSessionId, }), }) ) } function expectPageSessionIdUpdated(messageListener: jest.Mock, lastPageSessionId: string): void { expectPageSessionId(messageListener, expect.stringMatching(uuidRegex)) expect(messageListener).not.toHaveBeenLastCalledWith( expect.objectContaining({ data: expect.objectContaining({ pageSessionId: lastPageSessionId, }), }) ) } describe('SdkApi', () => { let sdk: SdkApi beforeEach(() => { sdk = new SdkApi('npm') headMock.mockImplementation(() => makeResponse(exampleContent)) getMock.mockImplementation(() => makeResponse(emptyContent)) }) afterEach(() => { headMock.mockClear() getMock.mockClear() }) describe('Render', () => { let channel: MessageChannel | undefined // workaround for https://github.com/jsdom/jsdom/issues/2745 const messageMonkeyPatch = (event: MessageEvent): void => { if (event.origin === '' && event.data.messageType === 'contextRequest') { event.stopImmediatePropagation() const eventWithOriginInitDict: MessageEventInit = { data: event.data, origin: 'https://salsify-ecdn.com', source: window, } if (channel) { eventWithOriginInitDict.ports = [channel.port2] } const eventWithOrigin: MessageEvent = new MessageEvent('message', eventWithOriginInitDict) window.dispatchEvent(eventWithOrigin) } } beforeAll(() => { window.addEventListener('message', messageMonkeyPatch) }) afterAll(() => { window.removeEventListener('message', messageMonkeyPatch) }) test('context pageSessionId resets on subsequent renders unless navigation event has occurred', async () => { channel = new WorkerThreadsMessageChannel() as unknown as MessageChannel let contextReceived: (value?: unknown) => void const waitForContext = (): Promise<unknown> => new Promise(resolve => (contextReceived = resolve)) let pageSessionId = '' let lastPageSessionId = '' const messageListener = jest.fn().mockImplementation((event: MessageEvent) => { if (event.data.pageSessionId) { pageSessionId = event.data.pageSessionId contextReceived() } }) sdk.init(defaultOptions) channel!.port1.onmessage = messageListener window.postMessage({ messageType: 'contextRequest' }, '*', [channel!.port2]) await waitForContext() expect(messageListener).toHaveBeenCalledTimes(1) expectPageSessionId(messageListener, expect.stringMatching(uuidRegex)) lastPageSessionId = pageSessionId // no render or navigation (pageSessionId should NOT have updated) window.postMessage({ messageType: 'contextRequest' }, '*', [channel!.port2]) await waitForContext() expect(messageListener).toHaveBeenCalledTimes(2) expectPageSessionId(messageListener, lastPageSessionId) lastPageSessionId = pageSessionId // first render (pageSessionId should NOT have updated) const container = document.createElement('div') await sdk.enhancedContent.renderIframe(container, 'foo', 'bar') window.postMessage({ messageType: 'contextRequest' }, '*', [channel!.port2]) await waitForContext() expect(messageListener).toHaveBeenCalledTimes(3) expectPageSessionId(messageListener, lastPageSessionId) lastPageSessionId = pageSessionId // second render without navigation (pageSessionId should have updated) await sdk.enhancedContent.renderIframe(container, 'foo', 'bar') window.postMessage({ messageType: 'contextRequest' }, '*', [channel!.port2]) await waitForContext() expect(messageListener).toHaveBeenCalledTimes(4) expectPageSessionIdUpdated(messageListener, lastPageSessionId) lastPageSessionId = pageSessionId // third render without navigation (pageSessionId should have updated) await sdk.enhancedContent.renderIframe(container, 'foo', 'bar') window.postMessage({ messageType: 'contextRequest' }, '*', [channel!.port2]) await waitForContext() expect(messageListener).toHaveBeenCalledTimes(5) expectPageSessionIdUpdated(messageListener, lastPageSessionId) lastPageSessionId = pageSessionId // navigation (pageSessionId should have updated) sdk.events.navigation({ productIdType: 'foo', productId: 'bar' }) window.postMessage({ messageType: 'contextRequest' }, '*', [channel!.port2]) await waitForContext() expect(messageListener).toHaveBeenCalledTimes(6) expectPageSessionIdUpdated(messageListener, lastPageSessionId) lastPageSessionId = pageSessionId // first render after navigation (pageSessionId should NOT have updated) await sdk.enhancedContent.renderIframe(container, 'foo', 'bar') window.postMessage({ messageType: 'contextRequest' }, '*', [channel!.port2]) await waitForContext() expect(messageListener).toHaveBeenCalledTimes(7) expectPageSessionId(messageListener, lastPageSessionId) lastPageSessionId = pageSessionId // second render after navigation (pageSessionId should have updated) await sdk.enhancedContent.renderIframe(container, 'foo', 'bar') window.postMessage({ messageType: 'contextRequest' }, '*', [channel!.port2]) await waitForContext() expect(messageListener).toHaveBeenCalledTimes(8) expectPageSessionIdUpdated(messageListener, lastPageSessionId) channel!.port1.onmessage = null channel = undefined }) }) })