Skip to content

Commit aa039d6

Browse files
refactor: simplify input validation for the case where the SDK is not operational
1 parent 394a55d commit aa039d6

File tree

11 files changed

+65
-135
lines changed

11 files changed

+65
-135
lines changed

CHANGES.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
2.6.0 (October XX, 2025)
2+
- Updated @splitsoftware/splitio package to version 11.7.1 that includes minor updates:
3+
- Added support for custom loggers: added `logger` configuration option and `factory.Logger.setLogger` method to allow the SDK to use a custom logger.
4+
5+
16
2.5.0 (September 18, 2025)
27
- Updated @splitsoftware/splitio package to version 11.6.0 that includes minor updates:
38
- Added `storage.wrapper` configuration option to allow the SDK to use a custom storage wrapper for the storage type `LOCALSTORAGE`. Default value is `window.localStorage`.

package-lock.json

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
},
6464
"homepage": "https://github.com/splitio/react-client#readme",
6565
"dependencies": {
66-
"@splitsoftware/splitio": "11.6.1-rc.1",
66+
"@splitsoftware/splitio": "11.7.1",
6767
"memoize-one": "^5.1.1",
6868
"shallowequal": "^1.1.0",
6969
"tslib": "^2.3.1"

src/__tests__/SplitTreatments.test.tsx

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,11 @@ import { SplitClient } from '../SplitClient';
1919
import { SplitFactoryProvider } from '../SplitFactoryProvider';
2020
import { useSplitTreatments } from '../useSplitTreatments';
2121

22-
const logSpy = jest.spyOn(console, 'log');
23-
2422
describe('SplitTreatments', () => {
2523

2624
const featureFlagNames = ['split1', 'split2'];
2725
const flagSets = ['set1', 'set2'];
2826

29-
afterEach(() => { logSpy.mockClear() });
30-
3127
it('passes control treatments (empty object if flagSets is provided) if the SDK is not operational.', () => {
3228
render(
3329
<SplitFactoryProvider config={sdkBrowser} >
@@ -105,10 +101,9 @@ describe('SplitTreatments', () => {
105101
});
106102

107103
/**
108-
* Input validation. Passing invalid feature flag names or attributes while the Sdk
109-
* is not ready doesn't emit errors, and logs meaningful messages instead.
104+
* Input validation: sanitize invalid feature flag names and return control while the SDK is not ready.
110105
*/
111-
it('Input validation: invalid "names" and "attributes" props in SplitTreatments.', (done) => {
106+
it('Input validation: invalid names are sanitized.', () => {
112107
render(
113108
<SplitFactoryProvider config={sdkBrowser} >
114109
<SplitClient>
@@ -130,9 +125,9 @@ describe('SplitTreatments', () => {
130125
}}
131126
</SplitTreatments>
132127
{/* @ts-expect-error Test error handling */}
133-
<SplitTreatments names={[true]} attributes={'invalid'} >
128+
<SplitTreatments names={[true, ' flag_1 ', ' ']} >
134129
{({ treatments }: ISplitTreatmentsChildProps) => {
135-
expect(treatments).toEqual({});
130+
expect(treatments).toEqual({ flag_1: CONTROL_WITH_CONFIG });
136131
return null;
137132
}}
138133
</SplitTreatments>
@@ -142,14 +137,9 @@ describe('SplitTreatments', () => {
142137
</SplitClient>
143138
</SplitFactoryProvider>
144139
);
145-
expect(logSpy).toBeCalledWith('[ERROR] splitio => feature flag names must be a non-empty array.');
146-
expect(logSpy).toBeCalledWith('[ERROR] splitio => you passed an invalid feature flag name, feature flag name must be a non-empty string.');
147-
148-
done();
149140
});
150141

151-
152-
test('ignores flagSets and logs a warning if both names and flagSets params are provided.', () => {
142+
test('ignores flagSets if both names and flagSets params are provided.', () => {
153143
render(
154144
<SplitFactoryProvider >
155145
{/* @ts-expect-error flagSets and names are mutually exclusive */}
@@ -161,8 +151,6 @@ describe('SplitTreatments', () => {
161151
</SplitTreatments>
162152
</SplitFactoryProvider>
163153
);
164-
165-
expect(logSpy).toBeCalledWith('[WARN] splitio => Both names and flagSets properties were provided. flagSets will be ignored.');
166154
});
167155

168156
test('returns the treatments from the client at Split context updated by SplitClient, or control if the client is not operational.', async () => {
@@ -190,7 +178,7 @@ describe('SplitTreatments', () => {
190178
act(() => client.__emitter__.emit(Event.SDK_READY_FROM_CACHE));
191179

192180
expect(client.getTreatmentsWithConfig).toBeCalledWith(featureFlagNames, attributes, undefined);
193-
expect(client.getTreatmentsWithConfig).toHaveReturnedWith(treatments);
181+
expect(client.getTreatmentsWithConfig).toHaveReturnedWith(treatments!);
194182
});
195183
});
196184

src/__tests__/testUtils/mockSplitFactory.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { EventEmitter } from 'events';
22
import jsSdkPackageJson from '@splitsoftware/splitio/package.json';
33
import reactSdkPackageJson from '../../../package.json';
4-
import { DEFAULT_LOGGER } from '../../utils';
54

65
export const jsSdkVersion = `javascript-${jsSdkPackageJson.version}`;
76
export const reactSdkVersion = `react-${reactSdkPackageJson.version}`;
@@ -13,6 +12,13 @@ export const Event = {
1312
SDK_UPDATE: 'state::update',
1413
};
1514

15+
const DEFAULT_LOGGER: SplitIO.Logger = {
16+
error(msg) { console.log('[ERROR] splitio => ' + msg); },
17+
warn(msg) { console.log('[WARN] splitio => ' + msg); },
18+
info(msg) { console.log('[INFO] splitio => ' + msg); },
19+
debug(msg) { console.log('[DEBUG] splitio => ' + msg); },
20+
};
21+
1622
function parseKey(key: SplitIO.SplitKey): SplitIO.SplitKey {
1723
if (key && typeof key === 'object' && key.constructor === Object) {
1824
return {

src/__tests__/useSplitTreatments.test.tsx

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import { useSplitTreatments } from '../useSplitTreatments';
1616
import { SplitContext } from '../SplitContext';
1717
import { ISplitTreatmentsChildProps } from '../types';
1818

19-
const logSpy = jest.spyOn(console, 'log');
20-
2119
describe('useSplitTreatments', () => {
2220

2321
const featureFlagNames = ['split1'];
@@ -56,10 +54,10 @@ describe('useSplitTreatments', () => {
5654
act(() => client.__emitter__.emit(Event.SDK_READY));
5755

5856
expect(client.getTreatmentsWithConfig).toBeCalledWith(featureFlagNames, attributes, { properties });
59-
expect(client.getTreatmentsWithConfig).toHaveReturnedWith(treatments);
57+
expect(client.getTreatmentsWithConfig).toHaveReturnedWith(treatments!);
6058

6159
expect(client.getTreatmentsWithConfigByFlagSets).toBeCalledWith(flagSets, attributes, { properties });
62-
expect(client.getTreatmentsWithConfigByFlagSets).toHaveReturnedWith(treatmentsByFlagSets);
60+
expect(client.getTreatmentsWithConfigByFlagSets).toHaveReturnedWith(treatmentsByFlagSets!);
6361
});
6462

6563
test('returns the treatments from a new client given a splitKey, and re-evaluates on SDK events.', () => {
@@ -113,10 +111,9 @@ describe('useSplitTreatments', () => {
113111
});
114112

115113
/**
116-
* Input validation. Passing invalid feature flag names or attributes while the Sdk
117-
* is not ready doesn't emit errors, and logs meaningful messages instead.
114+
* Input validation: sanitize invalid feature flag names and return control while the SDK is not ready.
118115
*/
119-
test('Input validation: invalid "names" and "attributes" params in useSplitTreatments.', () => {
116+
test('Input validation: invalid names are sanitized.', () => {
120117
render(
121118
<SplitFactoryProvider >
122119
{
@@ -125,16 +122,14 @@ describe('useSplitTreatments', () => {
125122
let treatments = useSplitTreatments('split1').treatments;
126123
expect(treatments).toEqual({});
127124
// @ts-expect-error Test error handling
128-
treatments = useSplitTreatments({ names: [true] }).treatments;
129-
expect(treatments).toEqual({});
125+
treatments = useSplitTreatments({ names: [true, ' flag_1 ', ' '] }).treatments;
126+
expect(treatments).toEqual({ flag_1: CONTROL_WITH_CONFIG });
130127

131128
return null;
132129
})
133130
}
134131
</SplitFactoryProvider>
135132
);
136-
expect(logSpy).toBeCalledWith('[ERROR] splitio => feature flag names must be a non-empty array.');
137-
expect(logSpy).toBeCalledWith('[ERROR] splitio => you passed an invalid feature flag name, feature flag name must be a non-empty string.');
138133
});
139134

140135
test('useSplitTreatments must update on SDK events', async () => {
@@ -222,7 +217,7 @@ describe('useSplitTreatments', () => {
222217
expect(user2Client.getTreatmentsWithConfig).toHaveBeenLastCalledWith(['split_test'], undefined, undefined);
223218
});
224219

225-
test('ignores flagSets and logs a warning if both names and flagSets params are provided.', () => {
220+
test('ignores flagSets if both names and flagSets params are provided.', () => {
226221
render(
227222
<SplitFactoryProvider >
228223
{
@@ -235,8 +230,6 @@ describe('useSplitTreatments', () => {
235230
}
236231
</SplitFactoryProvider>
237232
);
238-
239-
expect(logSpy).toHaveBeenLastCalledWith('[WARN] splitio => Both names and flagSets properties were provided. flagSets will be ignored.');
240233
});
241234

242235
});

src/__tests__/utils.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { CONTROL_WITH_CONFIG } from '../constants';
2-
import { DEFAULT_LOGGER, getControlTreatmentsWithConfig } from '../utils';
2+
import { getControlTreatmentsWithConfig } from '../utils';
33

44
describe('getControlTreatmentsWithConfig', () => {
55

66
it('should return an empty object if an empty array is provided', () => {
7-
expect(Object.values(getControlTreatmentsWithConfig(DEFAULT_LOGGER, [])).length).toBe(0);
7+
expect(Object.values(getControlTreatmentsWithConfig([])).length).toBe(0);
88
});
99

1010
it('should return an empty object if an empty array is provided', () => {
1111
const featureFlagNames = ['split1', 'split2'];
12-
const treatments: SplitIO.TreatmentsWithConfig = getControlTreatmentsWithConfig(DEFAULT_LOGGER, featureFlagNames);
12+
const treatments: SplitIO.TreatmentsWithConfig = getControlTreatmentsWithConfig(featureFlagNames);
1313
featureFlagNames.forEach((featureFlagName) => {
1414
expect(treatments[featureFlagName]).toBe(CONTROL_WITH_CONFIG);
1515
});

src/__tests__/withSplitTreatments.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { INITIAL_STATUS } from './testUtils/utils';
1414
import { withSplitFactory } from '../withSplitFactory';
1515
import { withSplitClient } from '../withSplitClient';
1616
import { withSplitTreatments } from '../withSplitTreatments';
17-
import { DEFAULT_LOGGER, getControlTreatmentsWithConfig } from '../utils';
17+
import { getControlTreatmentsWithConfig } from '../utils';
1818

1919
const featureFlagNames = ['split1', 'split2'];
2020

@@ -36,7 +36,7 @@ describe('withSplitTreatments', () => {
3636
...INITIAL_STATUS,
3737
factory: factory, client: clientMock,
3838
outerProp1: 'outerProp1', outerProp2: 2,
39-
treatments: getControlTreatmentsWithConfig(DEFAULT_LOGGER, featureFlagNames),
39+
treatments: getControlTreatmentsWithConfig(featureFlagNames),
4040
});
4141

4242
return null;

src/constants.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,3 @@ export const CONTROL_WITH_CONFIG: SplitIO.TreatmentWithConfig = {
1717
export const WARN_SF_CONFIG_AND_FACTORY: string = 'Both a config and factory props were provided to SplitFactoryProvider. Config prop will be ignored.';
1818

1919
export const EXCEPTION_NO_SFP: string = 'No SplitContext was set. Please ensure the component is wrapped in a SplitFactoryProvider.';
20-
21-
export const WARN_NAMES_AND_FLAGSETS: string = 'Both names and flagSets properties were provided. flagSets will be ignored.';

src/useSplitTreatments.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { memoizeGetTreatmentsWithConfig, DEFAULT_LOGGER } from './utils';
2+
import { memoizeGetTreatmentsWithConfig } from './utils';
33
import { ISplitTreatmentsChildProps, IUseSplitTreatmentsOptions } from './types';
44
import { useSplitClient } from './useSplitClient';
55

@@ -20,14 +20,14 @@ import { useSplitClient } from './useSplitClient';
2020
*/
2121
export function useSplitTreatments(options: IUseSplitTreatmentsOptions): ISplitTreatmentsChildProps {
2222
const context = useSplitClient({ ...options, attributes: undefined });
23-
const { factory, client, lastUpdate } = context;
23+
const { client, lastUpdate } = context;
2424
const { names, flagSets, attributes, properties } = options;
2525

2626
const getTreatmentsWithConfig = React.useMemo(memoizeGetTreatmentsWithConfig, []);
2727

2828
// Shallow copy `client.getAttributes` result for memoization, as it returns the same reference unless `client.clearAttributes` is invoked.
2929
// Note: the same issue occurs with the `names` and `attributes` arguments if they are mutated directly by the user instead of providing a new object.
30-
const treatments = getTreatmentsWithConfig(factory ? (factory.settings as any).log : DEFAULT_LOGGER, client, lastUpdate, names, attributes, client ? { ...client.getAttributes() } : {}, flagSets, properties && { properties });
30+
const treatments = getTreatmentsWithConfig(client, lastUpdate, names, attributes, client ? { ...client.getAttributes() } : {}, flagSets, properties && { properties });
3131

3232
return {
3333
...context,

0 commit comments

Comments
 (0)