import * as React from 'react'; import { act, render } from '@testing-library/react'; /** Mocks */ import { mockSdk, Event } from './testUtils/mockSplitFactory'; jest.mock('@splitsoftware/splitio/client', () => { return { SplitFactory: mockSdk() }; }); import { SplitFactory } from '@splitsoftware/splitio/client'; import { sdkBrowser, sdkBrowserWithConfig } from './testUtils/sdkConfigs'; import { CONTROL, EXCEPTION_NO_SFP } from '../constants'; /** Test target */ import { SplitFactoryProvider } from '../SplitFactoryProvider'; import { useTreatment } from '../useTreatment'; import { SplitContext } from '../SplitContext'; import { IUseTreatmentResult } from '../types'; describe('useTreatment', () => { const featureFlagName = 'split1'; const attributes = { att1: 'att1' }; const properties = { prop1: 'prop1' }; test('returns the treatment evaluated by the main client of the factory at Split context, or control if the client is not operational.', () => { const outerFactory = SplitFactory(sdkBrowser); const client: any = outerFactory.client(); let treatment: SplitIO.Treatment; render( {React.createElement(() => { treatment = useTreatment({ name: featureFlagName, attributes, properties }).treatment; return null; })} ); // returns control treatment if not operational (SDK not ready or destroyed), without calling `getTreatment` method expect(client.getTreatment).not.toBeCalled(); expect(treatment!).toEqual(CONTROL); // once operational (SDK_READY), it evaluates feature flags act(() => client.__emitter__.emit(Event.SDK_READY)); expect(client.getTreatment).toBeCalledWith(featureFlagName, attributes, { properties }); expect(client.getTreatment).toHaveReturnedWith(treatment!); }); test('returns the treatments from a new client given a splitKey, and re-evaluates on SDK events.', () => { const outerFactory = SplitFactory(sdkBrowser); const client: any = outerFactory.client('user2'); let renderTimes = 0; render( {React.createElement(() => { const treatment = useTreatment({ name: featureFlagName, attributes, properties, splitKey: 'user2', updateOnSdkUpdate: false }).treatment; renderTimes++; switch (renderTimes) { case 1: // returns control if not operational (SDK not ready), without calling `getTreatment` method expect(client.getTreatment).not.toBeCalled(); expect(treatment).toEqual(CONTROL); break; case 2: case 3: // once operational (SDK_READY or SDK_READY_FROM_CACHE), it evaluates feature flags expect(client.getTreatment).toHaveBeenLastCalledWith(featureFlagName, attributes, { properties }); expect(client.getTreatment).toHaveLastReturnedWith(treatment); break; default: throw new Error('Unexpected render'); } return null; })} ); act(() => client.__emitter__.emit(Event.SDK_READY_FROM_CACHE)); act(() => client.__emitter__.emit(Event.SDK_READY)); act(() => client.__emitter__.emit(Event.SDK_UPDATE)); expect(client.getTreatment).toBeCalledTimes(2); }); test('throws error if invoked outside of SplitFactoryProvider.', () => { expect(() => { render( React.createElement(() => { useTreatment({ name: featureFlagName, attributes }).treatment; return null; }) ); }).toThrow(EXCEPTION_NO_SFP); }); test('must update on SDK events', async () => { const outerFactory = SplitFactory(sdkBrowser); const mainClient = outerFactory.client() as any; const user2Client = outerFactory.client('user_2') as any; let countSplitContext = 0, countUseTreatment = 0, countUseTreatmentUser2 = 0, countUseTreatmentUser2WithoutUpdate = 0; const lastUpdateSetUser2 = new Set(); const lastUpdateSetUser2WithUpdate = new Set(); function validateTreatment({ treatment, isReady, isReadyFromCache }: IUseTreatmentResult) { if (isReady || isReadyFromCache) { expect(treatment).toEqual('on') } else { expect(treatment).toEqual('control') } } render( <> {() => countSplitContext++} {React.createElement(() => { const context = useTreatment({ name: 'split_test', attributes: { att1: 'att1' } }); expect(context.client).toBe(mainClient); // Assert that the main client was retrieved. validateTreatment(context); countUseTreatment++; return null; })} {React.createElement(() => { const context = useTreatment({ name: 'split_test', splitKey: 'user_2' }); expect(context.client).toBe(user2Client); validateTreatment(context); lastUpdateSetUser2.add(context.lastUpdate); countUseTreatmentUser2++; return null; })} {React.createElement(() => { const context = useTreatment({ name: 'split_test', splitKey: 'user_2', updateOnSdkUpdate: false }); expect(context.client).toBe(user2Client); validateTreatment(context); lastUpdateSetUser2WithUpdate.add(context.lastUpdate); countUseTreatmentUser2WithoutUpdate++; return null; })} ); act(() => mainClient.__emitter__.emit(Event.SDK_READY_FROM_CACHE)); act(() => mainClient.__emitter__.emit(Event.SDK_READY)); act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); act(() => user2Client.__emitter__.emit(Event.SDK_READY_FROM_CACHE)); act(() => user2Client.__emitter__.emit(Event.SDK_READY)); act(() => user2Client.__emitter__.emit(Event.SDK_UPDATE)); // SplitFactoryProvider renders once expect(countSplitContext).toEqual(1); // If useTreatment evaluates with the main client and have default update options, it re-renders for each main client event. expect(countUseTreatment).toEqual(4); expect(mainClient.getTreatment).toHaveBeenCalledTimes(3); // when ready from cache, ready and update expect(mainClient.getTreatment).toHaveBeenLastCalledWith('split_test', { att1: 'att1' }, undefined); // If useTreatment evaluates with a different client and have default update options, it re-renders for each event of the new client. expect(countUseTreatmentUser2).toEqual(4); expect(lastUpdateSetUser2.size).toEqual(4); // If it is used with `updateOnSdkUpdate: false`, it doesn't render when the client emits an SDK_UPDATE event. expect(countUseTreatmentUser2WithoutUpdate).toEqual(3); expect(lastUpdateSetUser2WithUpdate.size).toEqual(3); expect(user2Client.getTreatment).toHaveBeenCalledTimes(5); // when ready from cache x2, ready x2 and update x1 expect(user2Client.getTreatment).toHaveBeenLastCalledWith('split_test', undefined, undefined); }); test('returns fallback treatment if the client is not operational', () => { render( {React.createElement(() => { expect(useTreatment({ name: featureFlagName, attributes, properties }).treatment).toEqual('control_global'); expect(useTreatment({ name: 'ff1', attributes, properties }).treatment).toEqual('control_ff1'); return null; })} ); }); });