diff --git a/src/consent/get-all.spec.ts b/src/consent/get-all.spec.ts index e808f99..e0e1f36 100644 --- a/src/consent/get-all.spec.ts +++ b/src/consent/get-all.spec.ts @@ -18,8 +18,18 @@ afterAll(() => { }); describe('getAll()', () => { - it('should return all consents from zaraz consent', () => { - const consentsMock = { key1: true, key2: false }; + it("should return an empty object when Zaraz hasn't been initialized", () => { + const consents = getAll(); + expect(consents).toEqual({}); + }); + + it('should return all consents when Zaraz has been initialised', () => { + const consentsMock = { + key1: true, + key2: false, + key3: undefined, + }; + window.zaraz = { consent: { getAll: jest.fn().mockReturnValue(consentsMock), diff --git a/src/consent/get-all.ts b/src/consent/get-all.ts index 0324706..213d045 100644 --- a/src/consent/get-all.ts +++ b/src/consent/get-all.ts @@ -1,8 +1,18 @@ import { getZaraz } from '../helpers/get-zaraz'; +export type PurposeStatuses = { + [key: string]: undefined | boolean; // The key is equal to the ID in Zaraz, e.g. "OVAL" or "DVEA". +}; + /** - * Returns an object with the consent status of all purposes. + * Returns an object with the status of all purposes. + * + * - When the value is undefined, the consent hasn't been given yet + * - When the value is true, the consent has been given + * - When the value is false, the consent has been denied + * + * When Zaraz hasn't been initalised, it returns an empty object. */ -export function getAll(): { [key: string]: boolean } { - return getZaraz().consent.getAll(); +export function getAll(): PurposeStatuses { + return getZaraz({ skipQueue: true })?.consent?.getAll() || {}; } diff --git a/src/consent/get-api-ready.spec.ts b/src/consent/get-api-ready.spec.ts new file mode 100644 index 0000000..4e9a8d0 --- /dev/null +++ b/src/consent/get-api-ready.spec.ts @@ -0,0 +1,38 @@ +import { getAPIReady } from './get-api-ready'; + +declare global { + interface Window { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + zaraz: any; + } +} + +let windowObj: Window & typeof globalThis; + +beforeAll(() => { + windowObj = window; +}); + +afterAll(() => { + window = windowObj; +}); + +describe('getAPIReady()', () => { + it('should return false when the Zaraz consent API is not ready', () => { + const apiReady = getAPIReady(); + + expect(apiReady).toEqual(false); + }); + + it('should return true when the Zaraz consent API is ready', () => { + window.zaraz = { + consent: { + APIReady: true, + }, + }; + + const apiReady = getAPIReady(); + + expect(apiReady).toEqual(true); + }); +}); diff --git a/src/consent/get-api-ready.ts b/src/consent/get-api-ready.ts new file mode 100644 index 0000000..d31315d --- /dev/null +++ b/src/consent/get-api-ready.ts @@ -0,0 +1,8 @@ +import { getZaraz } from '../helpers/get-zaraz'; + +/** + * Indicates whether the Consent API is currently available on the page. + */ +export function getAPIReady(): boolean { + return getZaraz({ skipQueue: true })?.consent?.APIReady || false; +} diff --git a/src/consent/get-purposes.spec.ts b/src/consent/get-purposes.spec.ts index 63de7a4..bd05a55 100644 --- a/src/consent/get-purposes.spec.ts +++ b/src/consent/get-purposes.spec.ts @@ -1,4 +1,4 @@ -import { getPurposes } from './get-purposes'; +import { getPurposes, Purposes } from './get-purposes'; declare global { interface Window { @@ -18,11 +18,25 @@ afterAll(() => { }); describe('getPurposes()', () => { - it('should return all purposes from zaraz consent', () => { - const purposesMock = { - key1: { name: 'Name 1', description: 'Description 1', order: 1 }, - key2: { name: 'Name 2', description: 'Description 2', order: 2 }, + it("should return an empty object when the Zaraz consent API isn't initialised", () => { + const purposes = getPurposes(); + expect(purposes).toEqual({}); + }); + + it('should return all purposes from the Zaraz consent API when Zaraz is initialised', () => { + const purposesMock: Purposes = { + ORFL: { + name: { en: 'Name 1' }, + description: { en: 'Description 1' }, + order: 100, + }, + OAVL: { + name: { en: 'Name 2' }, + description: { en: 'Description 2' }, + order: 200, + }, }; + window.zaraz = { consent: { purposes: purposesMock, diff --git a/src/consent/get-purposes.ts b/src/consent/get-purposes.ts index 382b3fa..f8aa4e2 100644 --- a/src/consent/get-purposes.ts +++ b/src/consent/get-purposes.ts @@ -1,14 +1,22 @@ import { getZaraz } from '../helpers/get-zaraz'; -type Purpose = { - name: string; - description: string; +export type Purpose = { order: number; + name: { + [key: string]: string; // the key is the language set in Zaraz (e.g. "en") + }; + description: { + [key: string]: string; // the key is the language set in Zaraz (e.g. "en") + }; +}; + +export type Purposes = { + [key: string]: Purpose; // the key is also the ID visible in Zaraz (e.g. "OrGL" or "OAVL") }; /** * An object containing all configured purposes, with their ID, name, description, and order. */ -export function getPurposes(): { [key: string]: Purpose } { - return getZaraz().consent.purposes; +export function getPurposes(): Purposes { + return getZaraz({ skipQueue: true })?.consent?.purposes || {}; } diff --git a/src/consent/get.spec.ts b/src/consent/get.spec.ts index db1b3cc..619920d 100644 --- a/src/consent/get.spec.ts +++ b/src/consent/get.spec.ts @@ -18,8 +18,14 @@ afterAll(() => { }); describe('get()', () => { - it('should return the consent status for a purpose from zaraz consent', () => { + it("should return undefined when Zaraz hasn't been initialised", () => { + const consentStatus = get('key'); + expect(consentStatus).toBe(undefined); + }); + + it('should return the consent status for a purpose when Zaraz has been initialised', () => { const getMock = jest.fn().mockReturnValue(true); + window.zaraz = { consent: { get: getMock, diff --git a/src/consent/get.ts b/src/consent/get.ts index 7f11ebc..dbc90dd 100644 --- a/src/consent/get.ts +++ b/src/consent/get.ts @@ -1,7 +1,7 @@ import { getZaraz } from '../helpers/get-zaraz'; /** - * Get the current consent status for a purpose using the purpose ID. + * Get the consent status for a specific purpose using the purpose ID. * * ``` * true: The consent was granted. @@ -10,5 +10,5 @@ import { getZaraz } from '../helpers/get-zaraz'; * ``` */ export function get(purposeId: string): boolean | undefined { - return getZaraz().consent.get(purposeId); + return getZaraz({ skipQueue: true })?.consent?.get(purposeId) || undefined; } diff --git a/src/consent/index.spec.ts b/src/consent/index.spec.ts index 0e3271f..01d02f9 100644 --- a/src/consent/index.spec.ts +++ b/src/consent/index.spec.ts @@ -9,6 +9,7 @@ describe('consent', () => { setAll: expect.any(Function), getAllCheckboxes: expect.any(Function), getPurposes: expect.any(Function), + getAPIReady: expect.any(Function), setCheckboxes: expect.any(Function), setAllCheckboxes: expect.any(Function), sendQueuedEvents: expect.any(Function), diff --git a/src/consent/index.ts b/src/consent/index.ts index 65ff013..1fcf163 100644 --- a/src/consent/index.ts +++ b/src/consent/index.ts @@ -1,6 +1,7 @@ import { get } from './get'; import { getAll } from './get-all'; import { getAllCheckboxes } from './get-all-checkboxes'; +import { getAPIReady } from './get-api-ready'; import { getPurposes } from './get-purposes'; import { sendQueuedEvents } from './send-queued-events'; import { set } from './set'; @@ -14,6 +15,7 @@ export const consent = { getAll, setAll, getPurposes, + getAPIReady, getAllCheckboxes, setCheckboxes, setAllCheckboxes, diff --git a/src/consent/send-queued-event.spec.ts b/src/consent/send-queued-event.spec.ts index 12c6656..ec619fb 100644 --- a/src/consent/send-queued-event.spec.ts +++ b/src/consent/send-queued-event.spec.ts @@ -18,8 +18,15 @@ afterAll(() => { }); describe('sendQueuedEvents()', () => { + it("should not break when Zaraz hasn't been initialised yet", () => { + const sendQueuedEventsMock = jest.fn(); + sendQueuedEvents(); + expect(sendQueuedEventsMock).toHaveBeenCalledTimes(0); + }); + it('should call sendQueuedEvents method on zaraz consent', () => { const sendQueuedEventsMock = jest.fn(); + window.zaraz = { consent: { sendQueuedEvents: sendQueuedEventsMock, diff --git a/src/consent/send-queued-events.ts b/src/consent/send-queued-events.ts index 70ef5d7..2d99b28 100644 --- a/src/consent/send-queued-events.ts +++ b/src/consent/send-queued-events.ts @@ -4,5 +4,5 @@ import { getZaraz } from '../helpers/get-zaraz'; * If some Pageview-based events were not sent due to a lack of consent, they can be sent using this method after consent was granted. */ export function sendQueuedEvents(): void { - getZaraz().consent.sendQueuedEvents(); + getZaraz({ skipQueue: true })?.consent?.sendQueuedEvents(); } diff --git a/src/consent/set-all.spec.ts b/src/consent/set-all.spec.ts index 35e086b..ed54d9a 100644 --- a/src/consent/set-all.spec.ts +++ b/src/consent/set-all.spec.ts @@ -18,8 +18,14 @@ afterAll(() => { }); describe('setAll()', () => { + it("should not break when Zaraz hasn't been initialised yet", () => { + const result = setAll(true); + expect(result).toEqual(undefined); + }); + it('should call setAll method on zaraz consent with the correct argument', () => { const setAllMock = jest.fn(); + window.zaraz = { consent: { setAll: setAllMock, diff --git a/src/consent/set-all.ts b/src/consent/set-all.ts index 54e5e2b..09848b5 100644 --- a/src/consent/set-all.ts +++ b/src/consent/set-all.ts @@ -4,5 +4,5 @@ import { getZaraz } from '../helpers/get-zaraz'; * Set the consent status for all purposes at once. */ export function setAll(consentStatus: boolean): void { - getZaraz().consent.setAll(consentStatus); + getZaraz({ skipQueue: true })?.consent?.setAll(consentStatus); } diff --git a/src/consent/set.spec.ts b/src/consent/set.spec.ts index febc75f..be8905a 100644 --- a/src/consent/set.spec.ts +++ b/src/consent/set.spec.ts @@ -18,8 +18,14 @@ afterAll(() => { }); describe('set()', () => { + it("should not break when Zaraz hasn't been initialised yet", () => { + const result = set({ key1: true, key2: false }); + expect(result).toEqual(undefined); + }); + it('should call set method on zaraz consent with the correct argument', () => { const setMock = jest.fn(); + window.zaraz = { consent: { set: setMock, diff --git a/src/consent/set.ts b/src/consent/set.ts index 0476980..4b1e2b8 100644 --- a/src/consent/set.ts +++ b/src/consent/set.ts @@ -1,8 +1,15 @@ import { getZaraz } from '../helpers/get-zaraz'; +export type ConsentPreferences = { [key: string]: boolean }; + /** - * Set the consent status for some purposes using the purpose ID. + * Set the consent status for a specific purpose using the purpose ID. + * + * ``` + * true: The consent was granted. + * false: The consent was not granted. + * ``` */ -export function set(consentPreferences: { [key: string]: boolean }): void { - getZaraz().consent.set(consentPreferences); +export function set(consentPreferences: ConsentPreferences): void { + getZaraz({ skipQueue: true })?.consent?.set(consentPreferences); } diff --git a/src/helpers/get-zaraz.ts b/src/helpers/get-zaraz.ts index 25070a6..c158002 100644 --- a/src/helpers/get-zaraz.ts +++ b/src/helpers/get-zaraz.ts @@ -5,17 +5,27 @@ type QueueItem = { let queue: QueueItem[] = []; +type getZarazOptions = { + skipQueue?: boolean; +}; + /** * A utility that checks if zaraz exists on the window object. If not it queues * the events until zaraz is initialized. If zaraz is initialized, it flushes * the queue. */ -export function getZaraz() { +export function getZaraz(options?: getZarazOptions) { + const skipQueue = options?.skipQueue || false; + if (typeof window === 'undefined') { throw new Error(`Cannot use Zaraz Web API, because window is not defined.`); } - if (!window?.zaraz) { + if (!window?.zaraz && skipQueue) { + return undefined; + } + + if (!window?.zaraz && !skipQueue) { // eslint-disable-next-line no-console console.log(`Zaraz Web API is not initialized. Queueing events...`);