salsify-experiences-sdk
Version:
SDK to be used by commerce websites to implement product experiences.
194 lines (147 loc) • 6.68 kB
text/typescript
/**
* @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
})
})
})