@splitsoftware/splitio-react
Version:
A React library to easily integrate and use Split JS SDK
170 lines (150 loc) • 5.84 kB
text/typescript
import { EventEmitter } from 'events';
import jsSdkPackageJson from '@splitsoftware/splitio/package.json';
import reactSdkPackageJson from '../../../package.json';
export const jsSdkVersion = `javascript-${jsSdkPackageJson.version}`;
export const reactSdkVersion = `react-${reactSdkPackageJson.version}`;
export const Event = {
SDK_READY_TIMED_OUT: 'init::timeout',
SDK_READY: 'init::ready',
SDK_READY_FROM_CACHE: 'init::cache-ready',
SDK_UPDATE: 'state::update',
};
function parseKey(key: SplitIO.SplitKey): SplitIO.SplitKey {
if (key && typeof key === 'object' && key.constructor === Object) {
return {
matchingKey: (key as SplitIO.SplitKeyObject).matchingKey,
bucketingKey: (key as SplitIO.SplitKeyObject).bucketingKey,
};
} else {
return {
matchingKey: (key as string),
bucketingKey: (key as string),
};
}
}
function buildInstanceId(key: any, trafficType?: string) {
return `${key.matchingKey ? key.matchingKey : key}-${key.bucketingKey ? key.bucketingKey : key}-${trafficType !== undefined ? trafficType : ''}`;
}
export function mockSdk() {
return jest.fn((config: SplitIO.IBrowserSettings, __updateModules) => {
function mockClient(_key: SplitIO.SplitKey) {
// Readiness
let isReady = false;
let isReadyFromCache = false;
let hasTimedout = false;
let isDestroyed = false;
let lastUpdate = 0;
function syncLastUpdate() {
const dateNow = Date.now();
lastUpdate = dateNow > lastUpdate ? dateNow : lastUpdate + 1;
}
const __emitter__ = new EventEmitter();
__emitter__.on(Event.SDK_READY, () => { isReady = true; syncLastUpdate(); });
__emitter__.on(Event.SDK_READY_FROM_CACHE, () => { isReadyFromCache = true; syncLastUpdate(); });
__emitter__.on(Event.SDK_READY_TIMED_OUT, () => { hasTimedout = true; syncLastUpdate(); });
__emitter__.on(Event.SDK_UPDATE, () => { syncLastUpdate(); });
let attributesCache = {};
// Client methods
const track: jest.Mock = jest.fn(() => {
return true;
});
const getTreatmentsWithConfig: jest.Mock = jest.fn((featureFlagNames: string[]) => {
return featureFlagNames.reduce((result: SplitIO.TreatmentsWithConfig, featureName: string) => {
result[featureName] = { treatment: 'on', config: null };
return result;
}, {});
});
const getTreatmentsWithConfigByFlagSets: jest.Mock = jest.fn((flagSets: string[]) => {
return flagSets.reduce((result: SplitIO.TreatmentsWithConfig, flagSet: string) => {
result[flagSet + '_feature_flag'] = { treatment: 'on', config: null };
return result;
}, {});
});
const setAttributes: jest.Mock = jest.fn((attributes) => {
attributesCache = Object.assign(attributesCache, attributes);
return true;
});
const clearAttributes: jest.Mock = jest.fn(() => {
attributesCache = {};
return true;
});
const getAttributes: jest.Mock = jest.fn(() => {
return attributesCache;
});
const ready: jest.Mock = jest.fn(() => {
return new Promise<void>((res, rej) => {
if (isReady) res();
else { __emitter__.on(Event.SDK_READY, res); }
if (hasTimedout) rej();
else { __emitter__.on(Event.SDK_READY_TIMED_OUT, rej); }
});
});
const __getStatus = () => ({
isReady,
isReadyFromCache,
isTimedout: hasTimedout && !isReady,
hasTimedout,
isDestroyed,
isOperational: (isReady || isReadyFromCache) && !isDestroyed,
lastUpdate,
});
const destroy: jest.Mock = jest.fn(() => {
isDestroyed = true;
syncLastUpdate();
// __emitter__.removeAllListeners();
return Promise.resolve();
});
return Object.assign(Object.create(__emitter__), {
getTreatmentsWithConfig,
getTreatmentsWithConfigByFlagSets,
track,
ready,
destroy,
Event,
setAttributes,
clearAttributes,
getAttributes,
// EventEmitter exposed to trigger events manually
__emitter__,
// Clients expose a `__getStatus` method, that is not considered part of the public API, to get client readiness status (isReady, isReadyFromCache, isOperational, hasTimedout, isDestroyed)
__getStatus,
// Restore the mock client to its initial NO-READY status.
// Useful when you want to reuse the same mock between tests after emitting events or destroying the instance.
__restore() {
isReady = isReadyFromCache = hasTimedout = isDestroyed = false;
lastUpdate = 0;
}
});
}
// Manager
const names: jest.Mock = jest.fn().mockReturnValue([]);
const manager: jest.Mock = jest.fn().mockReturnValue({ names });
// Cache of clients
const __clients__: { [instanceId: string]: any } = {};
const client = jest.fn((key?: string) => {
const clientKey = key || parseKey(config.core.key);
const instanceId = buildInstanceId(clientKey);
return __clients__[instanceId] || (__clients__[instanceId] = mockClient(clientKey));
});
// Factory destroy
const destroy = jest.fn(() => {
return Promise.all(Object.keys(__clients__).map(instanceId => __clients__[instanceId].destroy()));
});
// SDK factory
const factory = {
client,
manager,
destroy,
__names__: names,
__clients__,
settings: Object.assign({
version: jsSdkVersion,
}, config),
};
if (__updateModules) __updateModules(factory);
return factory;
});
}
export function getLastInstance(SplitFactoryMock: any) {
return SplitFactoryMock.mock.results.slice(-1)[0].value;
}