Skip to content

Commit e446685

Browse files
Merge pull request #159 from splitio/readiness-tests
Update tests
2 parents 80b9ae3 + 9c0dfd0 commit e446685

17 files changed

+443
-113
lines changed

CHANGES.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
1.6.0 (October 30, 2025)
2+
- Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs.
3+
- Added `client.getStatus()` method to retrieve the client readiness status properties (`isReady`, `isReadyFromCache`, etc).
4+
- Added `client.whenReady()` and `client.whenReadyFromCache()` methods to replace the deprecated `client.ready()` method, which has an issue causing the returned promise to hang when using async/await syntax if it was rejected.
5+
- Updated the SDK_READY_FROM_CACHE event to be emitted alongside the SDK_READY event if it hasn’t already been emitted.
6+
- Updated @splitsoftware/splitio-commons package to version 2.8.0.
7+
8+
1.5.1 (October 8, 2025)
9+
- Bugfix - Updated @splitsoftware/splitio-commons package to version 2.7.1, which fixes the `debug` option to support log levels when the `logger` option is used.
10+
111
1.5.0 (October 7, 2025)
212
- Added support for custom loggers: added `logger` configuration option and `factory.Logger.setLogger` method to allow the SDK to use a custom logger.
313
- Updated @splitsoftware/splitio-commons package to version 2.7.0.

package-lock.json

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

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@splitsoftware/splitio-browserjs",
3-
"version": "1.5.0",
3+
"version": "1.6.0",
44
"description": "Split SDK for JavaScript on Browser",
55
"main": "cjs/index.js",
66
"module": "esm/index.js",
@@ -59,7 +59,7 @@
5959
"bugs": "https://github.com/splitio/javascript-browser-client/issues",
6060
"homepage": "https://github.com/splitio/javascript-browser-client#readme",
6161
"dependencies": {
62-
"@splitsoftware/splitio-commons": "2.7.0",
62+
"@splitsoftware/splitio-commons": "2.8.0",
6363
"tslib": "^2.3.1",
6464
"unfetch": "^4.2.0"
6565
},
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
import sinon from 'sinon';
2+
import { SplitFactory } from '../../';
3+
4+
const listener = {
5+
logImpression: sinon.stub()
6+
};
7+
8+
export default function (configInMemory, configInLocalStorage, fetchMock, assert) {
9+
10+
assert.test('FallbackTreatment / Split factory with no fallbackTreatment defined', async t => {
11+
12+
const splitio = SplitFactory(configInMemory);
13+
const client = splitio.client();
14+
15+
await client.whenReady();
16+
17+
t.equal(client.getTreatment('non_existent_flag'), 'control', 'The evaluation will return `control` if the flag does not exist and no fallbackTreatment is defined');
18+
t.equal(client.getTreatment('non_existent_flag_2'), 'control', 'The evaluation will return `control` if the flag does not exist and no fallbackTreatment is defined');
19+
20+
await client.destroy();
21+
t.end();
22+
23+
});
24+
25+
assert.test('FallbackTreatment / Split factory with global fallbackTreatment defined', async t => {
26+
27+
const config = {
28+
...configInMemory,
29+
fallbackTreatments: {
30+
global: 'FALLBACK_TREATMENT'
31+
}
32+
};
33+
const splitio = SplitFactory(config);
34+
const client = splitio.client();
35+
36+
await client.whenReady();
37+
38+
39+
t.equal(client.getTreatment('non_existent_flag'), 'FALLBACK_TREATMENT', 'The evaluation will return `FALLBACK_TREATMENT` if the flag does not exist and no fallbackTreatment is defined');
40+
t.equal(client.getTreatment('non_existent_flag_2'), 'FALLBACK_TREATMENT', 'The evaluation will return `FALLBACK_TREATMENT` if the flag does not exist and no fallbackTreatment is defined');
41+
42+
await client.destroy();
43+
t.end();
44+
45+
});
46+
47+
assert.test('FallbackTreatment / Split factory with specific fallbackTreatment defined', async t => {
48+
49+
const config = {
50+
...configInMemory,
51+
fallbackTreatments: {
52+
byFlag: {
53+
'non_existent_flag': 'FALLBACK_TREATMENT',
54+
}
55+
}
56+
};
57+
const splitio = SplitFactory(config);
58+
const client = splitio.client();
59+
60+
await client.whenReady();
61+
62+
t.equal(client.getTreatment('non_existent_flag'), 'FALLBACK_TREATMENT', 'The evaluation will return `FALLBACK_TREATMENT` if the flag does not exist and no fallbackTreatment is defined');
63+
t.equal(client.getTreatment('non_existent_flag_2'), 'control', 'The evaluation will return `control` if the flag does not exist and no fallbackTreatment is defined');
64+
65+
t.equal(client.getTreatment('non_existent_flag'), 'FALLBACK_TREATMENT', 'The evaluation will return `FALLBACK_TREATMENT` if the flag does not exist and no fallbackTreatment is defined');
66+
t.equal(client.getTreatment('non_existent_flag_2'), 'control', 'The evaluation will return `control` if the flag does not exist and no fallbackTreatment is defined');
67+
68+
await client.destroy();
69+
t.end();
70+
71+
});
72+
73+
74+
assert.test('FallbackTreatment / flag override beats global fallbackTreatment', async t => {
75+
76+
const config = {
77+
...configInMemory,
78+
fallbackTreatments: {
79+
global: 'OFF_FALLBACK',
80+
byFlag: {
81+
'my_flag': 'ON_FALLBACK',
82+
}
83+
}
84+
};
85+
const splitio = SplitFactory(config);
86+
const client = splitio.client();
87+
88+
await client.whenReady();
89+
90+
t.equal(client.getTreatment('my_flag'), 'ON_FALLBACK', 'The evaluation will return `ON_FALLBACK` if the flag does not exist and no fallbackTreatment is defined');
91+
t.equal(client.getTreatment('non_existent_flag_2'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist and no fallbackTreatment is defined');
92+
93+
t.equal(client.getTreatment('my_flag'), 'ON_FALLBACK', 'The evaluation will return `ON_FALLBACK` if the flag does not exist and no fallbackTreatment is defined');
94+
t.equal(client.getTreatment('non_existent_flag_2'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist and no fallbackTreatment is defined');
95+
96+
await client.destroy();
97+
t.end();
98+
99+
});
100+
101+
assert.test('FallbackTreatment / override applies only when original is control', async t => {
102+
103+
const config = {
104+
...configInMemory,
105+
fallbackTreatments: {
106+
global: 'OFF_FALLBACK'
107+
}
108+
};
109+
const splitio = SplitFactory(config);
110+
const client = splitio.client();
111+
112+
await client.whenReady();
113+
114+
t.equal(client.getTreatment('user_account_in_whitelist'), 'off', 'The evaluation will return the treatment defined in the flag if it exists');
115+
t.equal(client.getTreatment('non_existent_flag'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist and no fallbackTreatment is defined');
116+
117+
await client.destroy();
118+
t.end();
119+
120+
});
121+
122+
123+
assert.test('FallbackTreatment / override applies only when original is control - inLocalStorage', async t => {
124+
125+
const config = {
126+
...configInLocalStorage,
127+
fallbackTreatments: {
128+
global: 'OFF_FALLBACK'
129+
}
130+
};
131+
const splitio = SplitFactory(config);
132+
const client = splitio.client();
133+
134+
await client.whenReady();
135+
136+
t.equal(client.getTreatment('user_account_in_whitelist'), 'off', 'The evaluation will return the treatment defined in the flag if it exists');
137+
t.equal(client.getTreatment('non_existent_flag'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist and no fallbackTreatment is defined');
138+
139+
await client.destroy();
140+
t.end();
141+
142+
});
143+
144+
assert.test('FallbackTreatment / Impressions correctness with fallback when client is not ready', async t => {
145+
146+
const config = {
147+
...configInMemory,
148+
urls: {
149+
events: 'https://events.fallbacktreatment/api'
150+
},
151+
fallbackTreatments: {
152+
byFlag: {
153+
'any_flag': 'OFF_FALLBACK'
154+
}
155+
}
156+
};
157+
const splitio = SplitFactory(config);
158+
const client = splitio.client();
159+
160+
t.equal(client.getTreatment('any_flag'), 'OFF_FALLBACK', 'The evaluation will return the fallbackTreatment if the client is not ready yet');
161+
t.equal(client.getTreatment('user_account_in_whitelist'), 'control', 'The evaluation will return the fallbackTreatment if the client is not ready yet');
162+
163+
await client.whenReady();
164+
165+
fetchMock.postOnce(config.urls.events + '/testImpressions/bulk', (_, opts) => {
166+
167+
const payload = JSON.parse(opts.body);
168+
169+
function validateImpressionData(featureFlagName, expectedLabel) {
170+
const impressions = payload.find(e => e.f === featureFlagName).i;
171+
172+
t.equal(impressions[0].r, expectedLabel, `${featureFlagName} impression with label ${expectedLabel}`);
173+
}
174+
175+
validateImpressionData('any_flag', 'fallback - not ready');
176+
validateImpressionData('user_account_in_whitelist', 'not ready');
177+
t.end();
178+
179+
return 200;
180+
});
181+
182+
await client.destroy();
183+
184+
});
185+
186+
assert.test('FallbackTreatment / Fallback dynamic config propagation', async t => {
187+
188+
const config = {
189+
...configInMemory,
190+
fallbackTreatments: {
191+
global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' },
192+
byFlag: {
193+
'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' }
194+
}
195+
}
196+
};
197+
const splitio = SplitFactory(config);
198+
const client = splitio.client();
199+
200+
await client.whenReady();
201+
202+
t.deepEqual(client.getTreatmentWithConfig('my_flag'), { treatment: 'ON_FALLBACK', config: '{"flag": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment');
203+
t.deepEqual(client.getTreatmentWithConfig('non_existent_flag'), { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment');
204+
205+
await client.destroy();
206+
t.end();
207+
208+
});
209+
210+
assert.test('FallbackTreatment / Fallback dynamic config propagation - inLocalStorage', async t => {
211+
212+
const config = {
213+
...configInLocalStorage,
214+
fallbackTreatments: {
215+
global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' },
216+
byFlag: {
217+
'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' }
218+
}
219+
}
220+
};
221+
const splitio = SplitFactory(config);
222+
const client = splitio.client();
223+
224+
await client.whenReady();
225+
226+
t.deepEqual(client.getTreatmentWithConfig('my_flag'), { treatment: 'ON_FALLBACK', config: '{"flag": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment');
227+
t.deepEqual(client.getTreatmentWithConfig('non_existent_flag'), { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment');
228+
229+
await client.destroy();
230+
t.end();
231+
232+
});
233+
234+
assert.test('FallbackTreatment / Evaluations non existing flags with fallback do not generate impressions', async t => {
235+
236+
const config = {
237+
...configInMemory,
238+
urls: {
239+
events: 'https://events.fallbacktreatment/api'
240+
},
241+
fallbackTreatments: {
242+
global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' },
243+
byFlag: {
244+
'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' }
245+
}
246+
}
247+
};
248+
config.impressionListener = listener;
249+
250+
const splitio = SplitFactory(config);
251+
const client = splitio.client();
252+
253+
await client.whenReady();
254+
255+
t.deepEqual(client.getTreatmentWithConfig('my_flag'), { treatment: 'ON_FALLBACK', config: '{"flag": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment');
256+
t.deepEqual(client.getTreatmentWithConfig('non_existent_flag'), { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment');
257+
258+
let POSTED_IMPRESSIONS_COUNT = 0;
259+
260+
fetchMock.postOnce(config.urls.events + '/testImpressions/bulk', (_, opts) => {
261+
262+
const payload = JSON.parse(opts.body);
263+
t.equal(payload.length, 1, 'We should have just one impression for the two evaluated flags');
264+
265+
function validateImpressionData(featureFlagName, expectedLength) {
266+
267+
const impressions = payload.find(e => e.f === featureFlagName).i;
268+
t.equal(impressions.length, expectedLength, `${featureFlagName} has ${expectedLength} impressions`);
269+
}
270+
271+
validateImpressionData('my_flag', 1);
272+
validateImpressionData('non_existent_flag', 0);
273+
POSTED_IMPRESSIONS_COUNT = payload.reduce((acc, curr) => acc + curr.i.length, 0);
274+
t.equal(POSTED_IMPRESSIONS_COUNT, 1, 'We should have just one impression in total.');
275+
276+
return 200;
277+
});
278+
279+
setTimeout(() => {
280+
t.equal(listener.logImpression.callCount, POSTED_IMPRESSIONS_COUNT, 'Impression listener should be called once per each impression generated.');
281+
282+
t.end();
283+
}, 0);
284+
await client.destroy();
285+
286+
287+
});
288+
289+
assert.test('FallbackTreatment / LocalhostMode', async t => {
290+
291+
const config = {
292+
...configInMemory,
293+
core: {
294+
...configInMemory.core,
295+
authorizationKey: 'localhost',
296+
},
297+
fallbackTreatments: {
298+
global: 'OFF_FALLBACK'
299+
},
300+
features: {
301+
testing_split: 'on',
302+
}
303+
};
304+
const splitio = SplitFactory(config);
305+
const client = splitio.client();
306+
307+
await client.whenReady();
308+
309+
t.deepEqual(client.getTreatment('testing_split'), 'on', 'The evaluation should return the treatment defined in localhost mode');
310+
t.deepEqual(client.getTreatment('non_existent_flag'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist');
311+
312+
await client.destroy();
313+
314+
t.end();
315+
});
316+
317+
}

0 commit comments

Comments
 (0)