salsify-experiences-sdk
Version:
SDK to be used by commerce websites to implement product experiences.
182 lines (144 loc) • 5.54 kB
text/typescript
/**
* @jest-environment jsdom
*/
import { createLogger } from '../utils/logger'
import SdkApi, { Context } from '../api'
import * as EnhancedContentApi from '../enhancedContent'
import { getCookie, setCookie } from '../utils/cookies'
import { makeResponse } from '../__tests__/helpers'
import request from '../utils/request'
jest.mock('../utils/request')
const headMock = request.head as jest.Mock
const getMock = request.get as jest.Mock
const exampleContent = '<div>enhanced-content</div>'
const emptyContent = ''
const logMock = jest.fn()
jest.mock('../utils/logger')
let context: Context | undefined
;(createLogger as jest.Mock).mockImplementation(function (ctx: Context) {
context = ctx
return { log: logMock }
})
interface Cookies {
[key: string]: string | undefined
}
let cookies: Cookies = {}
jest.mock('../utils/cookies', () => {
return {
setCookie: jest.fn((key: string, val: string) => (cookies[key] = val)),
getCookie: jest.fn((key: string) => cookies[key]),
deleteCookie: jest.fn((key: string) => delete cookies[key]),
}
})
const defaultOptions = {
clientId: 'client-id',
}
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}$/
describe('SdkApi', () => {
let sdk: SdkApi
beforeEach(() => {
sdk = new SdkApi('npm')
cookies = {}
headMock.mockImplementation(() => makeResponse(exampleContent))
getMock.mockImplementation(() => makeResponse(emptyContent))
context = undefined
})
afterEach(() => {
logMock.mockReset()
headMock.mockClear()
getMock.mockClear()
})
test('constructed uninitialized', () => {
expect(sdk.initialized).toBe(false)
const accessEc = (): EnhancedContentApi.default => sdk.enhancedContent
expect(accessEc).toThrow('Salsify Experiences SDK has not been initialized.')
})
test('can only be initialized once', () => {
sdk.init(defaultOptions)
const reinit = (): void => sdk.init(defaultOptions)
expect(reinit).toThrow('Salsify Experiences SDK has already been initialized.')
})
test('allows API access once initialized', () => {
sdk.init(defaultOptions)
expect(sdk.enhancedContent).toBeTruthy()
})
test('allow events API access once initialized', () => {
sdk.init(defaultOptions)
expect(sdk.events).toBeTruthy()
})
test('cookie is not set when tracking is false', () => {
sdk.init({ ...defaultOptions, ...{ tracking: false } })
expect(getCookie('salsify_session_id')).toBeUndefined()
})
test('cookie is set by default', () => {
sdk.init(defaultOptions)
expect(getCookie('salsify_session_id')).toBeDefined()
})
test('cookie is set when tracking is true', () => {
sdk.init({ ...defaultOptions, ...{ tracking: true } })
expect(getCookie('salsify_session_id')).toBeDefined()
})
test('cookie is remove if already set when tracking is set to false', () => {
setCookie('salsify_session_id', 'abc')
sdk.init({ ...defaultOptions, ...{ tracking: false } })
expect(getCookie('salsify_session_id')).toBeUndefined()
})
test('iframe context listener is attached', () => {
window.addEventListener = jest.fn()
sdk.init({ ...defaultOptions, ...{ tracking: false } })
expect(window.addEventListener).toHaveBeenCalledTimes(2)
})
test('iframe context listener called with tracking: false', () => {
const attachIframeContextListenerSpy = jest.spyOn(EnhancedContentApi, 'attachIframeContextListener')
sdk.init({ ...defaultOptions, ...{ tracking: false } })
expect(attachIframeContextListenerSpy).toHaveBeenCalledWith(
expect.objectContaining({
tracking: false,
sessionId: 'NOT_TRACKED',
pageSessionId: expect.stringMatching(uuidRegex),
})
)
})
test('iframe context listener called with tracking: true', () => {
const attachIframeContextListenerSpy = jest.spyOn(EnhancedContentApi, 'attachIframeContextListener')
sdk.init({ ...defaultOptions, ...{ tracking: true } })
expect(attachIframeContextListenerSpy).toHaveBeenCalledWith(
expect.objectContaining({
tracking: true,
sessionId: expect.stringMatching(uuidRegex),
pageSessionId: expect.stringMatching(uuidRegex),
})
)
})
test('it sends and restarts time-on-page on navigation event', async () => {
sdk.init({ ...defaultOptions, ...{ tracking: true } })
await sdk.enhancedContent.renderIframe(document.createElement('div'), 'foo', 'bar')
sdk.events.navigation({ productIdType: 'foo', productId: 'bar' })
expect(logMock.mock.calls.filter(([event]) => event === 'time_on_page')).toEqual([
['time_on_page', expect.objectContaining({ timeOnPage: expect.any(Number) })],
])
})
describe('crypto.randomUUID not available (e.g. insecure context)', () => {
const { randomUUID } = crypto
beforeEach(() => {
// @ts-ignore
crypto.randomUUID = undefined
})
afterEach(() => {
crypto.randomUUID = randomUUID
})
test('sessionId is undefined', () => {
sdk.init(defaultOptions)
expect(context).toMatchObject({ sessionId: undefined })
})
test('pageSessionId is undefined', () => {
sdk.init(defaultOptions)
expect(context).toMatchObject({ pageSessionId: undefined })
})
test('pageSessionId is still undefined after reset', () => {
sdk.init(defaultOptions)
sdk.events.navigation({ productIdType: 'foo', productId: 'bar' })
expect(context).toMatchObject({ pageSessionId: undefined })
})
})
})