import { SEGMENT_DESTINATION_KEY } from '../plugins/SegmentDestination'; import type { DeepLinkData, Dictionary, Queue, Settable, Storage, Watchable, } from '../storage'; import type { Context, DeepPartial, DestinationFilters, EdgeFunctionSettings, IntegrationSettings, RoutingRule, SegmentAPIConsentSettings, SegmentAPIIntegrations, SegmentEvent, UserInfoState, } from '../types'; import { createCallbackManager } from './utils'; import { createGetter } from '../storage/helpers'; export type StoreData = { isReady: boolean; enabled: boolean; running: boolean; context?: DeepPartial; settings: SegmentAPIIntegrations; consentSettings?: SegmentAPIConsentSettings; edgeFunctionSettings?: EdgeFunctionSettings; filters: DestinationFilters; userInfo: UserInfoState; deepLinkData: DeepLinkData; pendingEvents: SegmentEvent[]; }; const INITIAL_VALUES: StoreData = { isReady: true, enabled: true, running: true, context: undefined, settings: { [SEGMENT_DESTINATION_KEY]: {}, }, consentSettings: undefined, filters: {}, userInfo: { anonymousId: 'anonymousId', userId: undefined, traits: undefined, }, deepLinkData: { referring_application: '', url: '', }, pendingEvents: [], }; export function createMockStoreGetter(fn: () => T) { return createGetter(fn, () => { return new Promise((resolve) => { resolve(fn()); }); }); } export class MockSegmentStore implements Storage { private data: StoreData; private initialData: StoreData; reset = () => { this.data = JSON.parse(JSON.stringify(this.initialData)) as StoreData; }; constructor(initialData?: Partial) { this.data = { ...INITIAL_VALUES, ...initialData }; this.initialData = JSON.parse( JSON.stringify({ ...INITIAL_VALUES, ...initialData }) ) as StoreData; } private callbacks = { context: createCallbackManager | undefined>(), settings: createCallbackManager(), consentSettings: createCallbackManager< SegmentAPIConsentSettings | undefined >(), edgeFunctionSettings: createCallbackManager< EdgeFunctionSettings | undefined >(), filters: createCallbackManager(), userInfo: createCallbackManager(), deepLinkData: createCallbackManager(), pendingEvents: createCallbackManager(), enabled: createCallbackManager(), running: createCallbackManager(), }; readonly isReady = { get: createMockStoreGetter(() => { return this.data.isReady; }), onChange: (_callback: (value: boolean) => void) => { return () => { return; }; }, }; readonly enabled = { get: createMockStoreGetter(() => { return this.data.enabled; }), onChange: (_callback: (value: boolean) => void) => { return () => { return; }; }, set: async (value: boolean | ((prev: boolean) => boolean)) => { this.data.enabled = value instanceof Function ? value(this.data.enabled ?? true) : value; this.callbacks.enabled.run(this.data.enabled); return this.data.enabled; }, }; readonly running = { get: createMockStoreGetter(() => { return this.data.running; }), onChange: (_callback: (value: boolean) => void) => { return () => { return; }; }, set: async (value: boolean | ((prev: boolean) => boolean)) => { this.data.running = value instanceof Function ? value(this.data.running ?? true) : value; this.callbacks.running.run(this.data.running); return this.data.running; }, }; readonly context: Watchable | undefined> & Settable> = { get: createMockStoreGetter(() => ({ ...this.data.context })), onChange: (callback: (value?: DeepPartial) => void) => this.callbacks.context.register(callback), set: (value) => { this.data.context = value instanceof Function ? value(this.data.context ?? {}) : { ...value }; this.callbacks.context.run(this.data.context); return this.data.context; }, }; readonly settings: Watchable & Settable & Dictionary = { get: createMockStoreGetter(() => this.data.settings), onChange: (callback: (value?: SegmentAPIIntegrations) => void) => this.callbacks.settings.register(callback), set: (value) => { this.data.settings = value instanceof Function ? value(this.data.settings ?? {}) : { ...value }; this.callbacks.settings.run(this.data.settings); return this.data.settings; }, add: (key: string, value: IntegrationSettings) => { this.data.settings[key] = value; this.callbacks.settings.run(this.data.settings); return Promise.resolve(this.data.settings); }, }; readonly consentSettings: Watchable & Settable = { get: createMockStoreGetter(() => this.data.consentSettings), onChange: (callback: (value?: SegmentAPIConsentSettings) => void) => this.callbacks.consentSettings.register(callback), set: (value) => { this.data.consentSettings = value instanceof Function ? value(this.data.consentSettings) : value; this.callbacks.consentSettings.run(this.data.consentSettings); return this.data.consentSettings; }, }; readonly edgeFunctionSettings: Watchable & Settable = { get: createMockStoreGetter(() => this.data.edgeFunctionSettings), onChange: (callback: (value?: EdgeFunctionSettings) => void) => this.callbacks.edgeFunctionSettings.register(callback), set: (value) => { this.data.edgeFunctionSettings = value instanceof Function ? value(this.data.edgeFunctionSettings) : value; this.callbacks.edgeFunctionSettings.run(this.data.edgeFunctionSettings); return this.data.edgeFunctionSettings; }, }; readonly filters: Watchable & Settable & Dictionary = { get: createMockStoreGetter(() => this.data.filters), onChange: (callback: (value?: DestinationFilters) => void) => this.callbacks.filters.register(callback), set: (value) => { this.data.filters = value instanceof Function ? value(this.data.filters ?? {}) : { ...value }; this.callbacks.filters.run(this.data.filters); return this.data.filters; }, add: (key: string, value: RoutingRule) => { this.data.filters[key] = value; this.callbacks.filters.run(this.data.filters); return Promise.resolve(this.data.filters); }, }; readonly userInfo: Watchable & Settable = { get: createMockStoreGetter(() => this.data.userInfo), onChange: (callback: (value: UserInfoState) => void) => this.callbacks.userInfo.register(callback), set: (value) => { this.data.userInfo = value instanceof Function ? value(this.data.userInfo ?? {}) : { ...value }; this.callbacks.userInfo.run(this.data.userInfo); return this.data.userInfo; }, }; readonly deepLinkData = { get: createMockStoreGetter(() => { return this.data.deepLinkData; }), set: (value: DeepLinkData) => { this.data.deepLinkData = value; this.callbacks.deepLinkData.run(value); }, onChange: (callback: (value: DeepLinkData) => void) => this.callbacks.deepLinkData.register(callback), }; readonly pendingEvents: Watchable & Settable & Queue = { get: createMockStoreGetter(() => { return this.data.pendingEvents; }), set: (value) => { this.data.pendingEvents = value instanceof Function ? value(this.data.pendingEvents ?? []) : [...value]; this.callbacks.pendingEvents.run(this.data.pendingEvents); return this.data.pendingEvents; }, add: (value: SegmentEvent) => { this.data.pendingEvents.push(value); this.callbacks.pendingEvents.run(this.data.pendingEvents); return Promise.resolve(this.data.pendingEvents); }, remove: (value: SegmentEvent) => { this.data.pendingEvents = this.data.pendingEvents.filter( (e) => e.messageId != value.messageId ); this.callbacks.pendingEvents.run(this.data.pendingEvents); return Promise.resolve(this.data.pendingEvents); }, onChange: (callback: (value: SegmentEvent[]) => void) => this.callbacks.pendingEvents.register(callback), }; }