Skip to content

Commit

Permalink
unit tests and fix memoize issue
Browse files Browse the repository at this point in the history
  • Loading branch information
ZamoraEmmanuel committed Feb 14, 2022
1 parent 11cd1cd commit 81534e1
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/SplitClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class SplitComponent extends React.Component<IUpdateProps & { factory: Sp
// But it implies to have another instance property to use instead of the state, because we need a unique reference value for SplitContext.Producer
static getDerivedStateFromProps(props: ISplitClientProps & { factory: SplitIO.IBrowserSDK | null, client: SplitIO.IBrowserClient | null }, state: ISplitContextValues) {
const { client, factory, attributes } = props;
// @TODO check if apply over client or over stage.client
if (attributes)
client?.setAttributes(attributes);
else
Expand Down
7 changes: 4 additions & 3 deletions src/SplitTreatments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ function argsAreEqual(newArgs: any[], lastArgs: any[]): boolean {
return newArgs[0] === lastArgs[0] && // client
newArgs[1] === lastArgs[1] && // lastUpdate
shallowEqual(newArgs[2], lastArgs[2]) && // names
shallowEqual(newArgs[3], lastArgs[3]); // attributes
shallowEqual(newArgs[3], lastArgs[3]) && // attributes
shallowEqual(newArgs[4], lastArgs[4]); // client attributes
}

function evaluateSplits(client: SplitIO.IBrowserClient, lastUpdate: number, names: SplitIO.SplitNames, attributes?: SplitIO.Attributes) {
function evaluateSplits(client: SplitIO.IBrowserClient, lastUpdate: number, names: SplitIO.SplitNames, attributes?: SplitIO.Attributes, clientAttributes?: SplitIO.Attributes) {
return client.getTreatmentsWithConfig(names, attributes);
}

Expand Down Expand Up @@ -40,7 +41,7 @@ class SplitTreatments extends React.Component<ISplitTreatmentsProps> {
let treatments;
const isOperational = !isDestroyed && (isReady || isReadyFromCache);
if (client && isOperational) {
treatments = this.evaluateSplits(client, lastUpdate, names, attributes);
treatments = this.evaluateSplits(client, lastUpdate, names, attributes, client.getAttributes());
} else {
treatments = getControlTreatmentsWithConfig(names);
if (!client) { this.logWarning = true; }
Expand Down
76 changes: 76 additions & 0 deletions src/__tests__/SplitClient.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
// @ts-ignore
import { SplitFactory as originalSplitFactory } from '../../lib/splitio/index';

/** Mocks and test utils */
import { mockSdk, Event, assertNoListeners, clientListenerCount } from './testUtils/mockSplitSdk';
Expand All @@ -15,6 +17,7 @@ import SplitFactory from '../SplitFactory';
import SplitClient from '../SplitClient';
import SplitContext, { ISplitContextValues } from '../SplitContext';
import { ERROR_SC_NO_FACTORY } from '../constants';
import SplitIO from '@splitsoftware/splitio/types/splitio';

describe('SplitClient', () => {

Expand Down Expand Up @@ -392,4 +395,77 @@ describe('SplitClient', () => {
</SplitFactory>);
});

test('calls client setAttributes and clearAttributes.', (done) => {
let renderTimes = 0;
const attributesFactory = { at1: 'at1' };
const attributesClient = { at2: 'at2' };
const factory = originalSplitFactory({
core: {
authorizationKey: 'localhost',
key: 'emma'
},
features: {
test_split: 'on'
}
})

const mainClient = factory?.client();
const mainClientSpy = {
setAttributes: jest.spyOn(mainClient, 'setAttributes'),
clearAttributes: jest.spyOn(mainClient, 'clearAttributes')
}

const client = factory?.client('user1');
const clientSpy = {
setAttributes: jest.spyOn(client, 'setAttributes'),
clearAttributes: jest.spyOn(client, 'clearAttributes'),
}

function Component({ attributesFactory, attributesClient }: { attributesFactory: SplitIO.Attributes, attributesClient: SplitIO.Attributes }) {
return (
<SplitFactory factory={factory} attributes={attributesFactory} >
<SplitClient splitKey='user1' attributes={attributesClient} >
{({ client }: ISplitClientChildProps) => {
renderTimes++;
switch (renderTimes) {
case 1:
expect(mainClientSpy.setAttributes).lastCalledWith(attributesFactory);
expect(clientSpy.setAttributes).lastCalledWith(attributesClient);
expect(mainClient?.getAttributes()).toStrictEqual(attributesFactory);
expect(client?.getAttributes()).toStrictEqual(attributesClient);
break;
case 2:
expect(mainClientSpy.clearAttributes).toBeCalledTimes(1);
expect(clientSpy.setAttributes).lastCalledWith(attributesClient);
expect(mainClient?.getAttributes()).toStrictEqual({});
expect(client?.getAttributes()).toStrictEqual({at2: 'at2', at3:'at3'});
break;
case 3:
expect(mainClientSpy.setAttributes).lastCalledWith({at4: 'at4'});
expect(clientSpy.clearAttributes).toBeCalledTimes(2);
expect(mainClient?.getAttributes()).toStrictEqual({at4: 'at4'});
expect(client?.getAttributes()).toStrictEqual({});
break;
case 4:
expect(mainClientSpy.clearAttributes).toBeCalledTimes(2);
expect(clientSpy.clearAttributes).toBeCalledTimes(4);
expect(mainClient?.getAttributes()).toStrictEqual({});
expect(client?.getAttributes()).toStrictEqual({});
done();
}
return null;
}}
</SplitClient>
</SplitFactory>
);
}

const wrapper = mount(<Component attributesFactory={attributesFactory} attributesClient={attributesClient} />);

wrapper.setProps({ attributesFactory: undefined, attributesClient: {at3: 'at3'} });
wrapper.setProps({ attributesFactory: {at4: 'at4'}, attributesClient: undefined });
wrapper.setProps({ attributesFactory: undefined, attributesClient: undefined });

});

});
12 changes: 12 additions & 0 deletions src/__tests__/testUtils/mockSplitSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ function mockClient(key: SplitIO.SplitKey, trafficType?: string) {
const getTreatmentsWithConfig: jest.Mock = jest.fn(() => {
return 'getTreatmentsWithConfig';
});
const setAttributes: jest.Mock = jest.fn(() => {
return true;
});
const clearAttributes: jest.Mock = jest.fn(() => {
return true;
});
const getAttributes: jest.Mock = jest.fn(() => {
return true;
});
const ready: jest.Mock = jest.fn(() => {
return new Promise((res, rej) => {
if (__isReady__) res();
Expand Down Expand Up @@ -94,6 +103,9 @@ function mockClient(key: SplitIO.SplitKey, trafficType?: string) {
ready,
destroy,
Event,
setAttributes,
clearAttributes,
getAttributes,
// EventEmitter exposed to trigger events manually
__emitter__,
// Count of internal listeners set by the client mock, used to assert how many external listeners were attached
Expand Down
75 changes: 75 additions & 0 deletions src/__tests__/useClient.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React from 'react';
import { mount, shallow } from 'enzyme';

// @ts-ignore
import { SplitFactory as originalSplitFactory } from '../../lib/splitio/index';

/** Mocks */
import { mockSdk } from './testUtils/mockSplitSdk';
jest.mock('@splitsoftware/splitio', () => {
Expand Down Expand Up @@ -76,4 +79,76 @@ describe('useClient', () => {
expect(sharedClient).toBe(null);
});

test('calls client setAttributes and clearAttributes.', (done) => {

let renderTimes = 0;
const attributesFactory = { at1: 'at1' };
const attributesClient = { at2: 'at2' };

const factory = originalSplitFactory({
core: {
authorizationKey: 'localhost',
key: 'emma'
},
features: {
test_split: 'on'
}
})

const mainClient = factory.client();
const mainClientSpy = {
setAttributes: jest.spyOn(mainClient, 'setAttributes'),
clearAttributes: jest.spyOn(mainClient, 'clearAttributes')
}

const client = factory.client('user1', 'user');
const clientSpy = {
setAttributes: jest.spyOn(client, 'setAttributes'),
clearAttributes: jest.spyOn(client, 'clearAttributes'),
}

function Component({ attributesFactory, attributesClient }: { attributesFactory: any, attributesClient: any }) {
return (
<SplitFactory factory={factory} attributes={attributesFactory} >{
React.createElement(() => {
useClient('user1', 'user', attributesClient);
renderTimes++;
switch (renderTimes) {
case 1:
expect(mainClientSpy.setAttributes).lastCalledWith(attributesFactory);
expect(clientSpy.setAttributes).lastCalledWith(attributesClient);
expect(mainClient?.getAttributes()).toStrictEqual(attributesFactory);
expect(client?.getAttributes()).toStrictEqual(attributesClient);
break;
case 2:
expect(mainClientSpy.clearAttributes).toBeCalledTimes(1);
expect(clientSpy.setAttributes).lastCalledWith(attributesClient);
expect(mainClient?.getAttributes()).toStrictEqual({});
expect(client?.getAttributes()).toStrictEqual({at2: 'at2', at3:'at3'});
break;
case 3:
expect(mainClientSpy.setAttributes).lastCalledWith({at4: 'at4'});
expect(clientSpy.clearAttributes).toBeCalledTimes(1);
expect(mainClient?.getAttributes()).toStrictEqual({at4: 'at4'});
expect(client?.getAttributes()).toStrictEqual({});
break;
case 4:
expect(mainClientSpy.clearAttributes).toBeCalledTimes(2);
expect(clientSpy.clearAttributes).toBeCalledTimes(2);
expect(mainClient?.getAttributes()).toStrictEqual({});
expect(client?.getAttributes()).toStrictEqual({});
done()
}
return null
})}</SplitFactory>
)
}

const wrapper = mount(<Component attributesFactory={attributesFactory} attributesClient={attributesClient} />);

wrapper.setProps({ attributesFactory: undefined, attributesClient: {at3: 'at3'} });
wrapper.setProps({ attributesFactory: {at4: 'at4'}, attributesClient: undefined });
wrapper.setProps({ attributesFactory: undefined, attributesClient: undefined });
});

});
74 changes: 74 additions & 0 deletions src/__tests__/withSplitClient.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
// @ts-ignore
import { SplitFactory as originalSplitFactory } from '../../lib/splitio/index';

/** Mocks */
import { mockSdk, Event } from './testUtils/mockSplitSdk';
Expand Down Expand Up @@ -72,4 +74,76 @@ describe('SplitClient', () => {
expect(wrapper.prop('updateOnSdkReadyFromCache')).toBe(updateOnSdkReadyFromCache);
});

test('passes attributes to Splitclient', () => {

let renderTimes = 0;

const attributesFactory = { at1: 'at1' };
const attributesClient = { at2: 'at2' };

const factory = originalSplitFactory({
core: {
authorizationKey: 'localhost',
key: 'emma'
},
features: {
test_split: 'on'
}
})

const mainClient = factory?.client();
const mainClientSpy = {
setAttributes: jest.spyOn(mainClient, 'setAttributes'),
clearAttributes: jest.spyOn(mainClient, 'clearAttributes')
}

const client = factory?.client('user1', 'user');
const clientSpy = {
setAttributes: jest.spyOn(client, 'setAttributes'),
clearAttributes: jest.spyOn(client, 'clearAttributes'),
}

function Component({ attributesFactory, attributesClient }: { attributesFactory: any, attributesClient: any }) {
const Component = withSplitFactory(undefined, factory, attributesFactory)(
withSplitClient('user1', 'user', attributesClient)(
() => {
renderTimes++;
switch (renderTimes) {
case 1:
expect(mainClientSpy.setAttributes).lastCalledWith(attributesFactory);
expect(clientSpy.setAttributes).lastCalledWith(attributesClient);
expect(mainClient?.getAttributes()).toStrictEqual(attributesFactory);
expect(client?.getAttributes()).toStrictEqual(attributesClient);
break;
case 2:
expect(mainClientSpy.clearAttributes).toBeCalledTimes(1);
expect(clientSpy.setAttributes).lastCalledWith(attributesClient);
expect(mainClient?.getAttributes()).toStrictEqual({});
expect(client?.getAttributes()).toStrictEqual({at2: 'at2', at3:'at3'});
break;
case 3:
expect(mainClientSpy.setAttributes).lastCalledWith({at4: 'at4'});
expect(clientSpy.clearAttributes).toBeCalledTimes(1);
expect(mainClient?.getAttributes()).toStrictEqual({at4: 'at4'});
expect(client?.getAttributes()).toStrictEqual({});
break;
case 4:
expect(mainClientSpy.clearAttributes).toBeCalledTimes(2);
expect(clientSpy.clearAttributes).toBeCalledTimes(2);
expect(mainClient?.getAttributes()).toStrictEqual({});
expect(client?.getAttributes()).toStrictEqual({});

}
return null;
}))
return Component}

// @ts-ignore
const wrapper = mount(<Component />);

wrapper.setProps({ attributesFactory: undefined, attributesClient: {at3: 'at3'} });
wrapper.setProps({ attributesFactory: {at4: 'at4'}, attributesClient: undefined });
wrapper.setProps({ attributesFactory: undefined, attributesClient: undefined });
});

});
6 changes: 3 additions & 3 deletions src/useClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import { getSplitSharedClient, checkHooks } from './utils';
const useClient = (key?: SplitIO.SplitKey, trafficType?: string, attributes?: SplitIO.Attributes): SplitIO.IBrowserClient | null => {
if (!checkHooks(ERROR_UC_NO_USECONTEXT)) return null;
const { factory, client } = React.useContext(SplitContext);
if (key) {
return factory ? getSplitSharedClient(factory, key, trafficType, attributes) : null;
}
if (attributes)
client?.setAttributes(attributes);
else
client?.clearAttributes();
if (key) {
return factory ? getSplitSharedClient(factory, key, trafficType, attributes) : null;
}
return client;
};

Expand Down

0 comments on commit 81534e1

Please sign in to comment.