diff --git a/CHANGES.txt b/CHANGES.txt index 65e8a75..c021991 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,10 @@ +1.6.0 (October 30, 2025) + - 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. + - Added `client.getStatus()` method to retrieve the client readiness status properties (`isReady`, `isReadyFromCache`, etc). + - 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. + - Updated the SDK_READY_FROM_CACHE event to be emitted alongside the SDK_READY event if it hasn’t already been emitted. + - Updated @splitsoftware/splitio-commons package to version 2.8.0. + 1.5.1 (October 8, 2025) - 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. diff --git a/package-lock.json b/package-lock.json index d184611..aa1e9c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@splitsoftware/splitio-browserjs", - "version": "1.5.1", + "version": "1.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-browserjs", - "version": "1.5.1", + "version": "1.6.0", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.7.1", + "@splitsoftware/splitio-commons": "2.8.0", "tslib": "^2.3.1", "unfetch": "^4.2.0" }, @@ -1396,9 +1396,9 @@ "dev": true }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.1.tgz", - "integrity": "sha512-7a4VVMczh0YKVRi35EhD0FOAEwzqfJRcCiKqLLhZCxAvrZBpE2khpGn8pOP+y6TefdPVtblW8GIku4O4r0KRdQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.8.0.tgz", + "integrity": "sha512-QgHUreMOEDwf4GZzVPu4AzkZJvuaeSoHsiJc4tT3CxSIYl2bKMz1SSDlI1tW/oVbIFeWjkrIp2lCYEyUBgcvyA==", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -10493,9 +10493,9 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.1.tgz", - "integrity": "sha512-7a4VVMczh0YKVRi35EhD0FOAEwzqfJRcCiKqLLhZCxAvrZBpE2khpGn8pOP+y6TefdPVtblW8GIku4O4r0KRdQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.8.0.tgz", + "integrity": "sha512-QgHUreMOEDwf4GZzVPu4AzkZJvuaeSoHsiJc4tT3CxSIYl2bKMz1SSDlI1tW/oVbIFeWjkrIp2lCYEyUBgcvyA==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" diff --git a/package.json b/package.json index cc1c8e2..c710d4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-browserjs", - "version": "1.5.1", + "version": "1.6.0", "description": "Split SDK for JavaScript on Browser", "main": "cjs/index.js", "module": "esm/index.js", @@ -59,7 +59,7 @@ "bugs": "https://github.com/splitio/javascript-browser-client/issues", "homepage": "https://github.com/splitio/javascript-browser-client#readme", "dependencies": { - "@splitsoftware/splitio-commons": "2.7.1", + "@splitsoftware/splitio-commons": "2.8.0", "tslib": "^2.3.1", "unfetch": "^4.2.0" }, diff --git a/src/__tests__/browserSuites/evaluations-fallback.spec.js b/src/__tests__/browserSuites/evaluations-fallback.spec.js new file mode 100644 index 0000000..e18cad7 --- /dev/null +++ b/src/__tests__/browserSuites/evaluations-fallback.spec.js @@ -0,0 +1,317 @@ +import sinon from 'sinon'; +import { SplitFactory } from '../../'; + +const listener = { + logImpression: sinon.stub() +}; + +export default function (configInMemory, configInLocalStorage, fetchMock, assert) { + + assert.test('FallbackTreatment / Split factory with no fallbackTreatment defined', async t => { + + const splitio = SplitFactory(configInMemory); + const client = splitio.client(); + + await client.whenReady(); + + t.equal(client.getTreatment('non_existent_flag'), 'control', 'The evaluation will return `control` if the flag does not exist and no fallbackTreatment is defined'); + 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'); + + await client.destroy(); + t.end(); + + }); + + assert.test('FallbackTreatment / Split factory with global fallbackTreatment defined', async t => { + + const config = { + ...configInMemory, + fallbackTreatments: { + global: 'FALLBACK_TREATMENT' + } + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.whenReady(); + + + 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'); + 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'); + + await client.destroy(); + t.end(); + + }); + + assert.test('FallbackTreatment / Split factory with specific fallbackTreatment defined', async t => { + + const config = { + ...configInMemory, + fallbackTreatments: { + byFlag: { + 'non_existent_flag': 'FALLBACK_TREATMENT', + } + } + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.whenReady(); + + 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'); + 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'); + + 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'); + 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'); + + await client.destroy(); + t.end(); + + }); + + + assert.test('FallbackTreatment / flag override beats global fallbackTreatment', async t => { + + const config = { + ...configInMemory, + fallbackTreatments: { + global: 'OFF_FALLBACK', + byFlag: { + 'my_flag': 'ON_FALLBACK', + } + } + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.whenReady(); + + 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'); + 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'); + + 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'); + 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'); + + await client.destroy(); + t.end(); + + }); + + assert.test('FallbackTreatment / override applies only when original is control', async t => { + + const config = { + ...configInMemory, + fallbackTreatments: { + global: 'OFF_FALLBACK' + } + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.whenReady(); + + t.equal(client.getTreatment('user_account_in_whitelist'), 'off', 'The evaluation will return the treatment defined in the flag if it exists'); + 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'); + + await client.destroy(); + t.end(); + + }); + + + assert.test('FallbackTreatment / override applies only when original is control - inLocalStorage', async t => { + + const config = { + ...configInLocalStorage, + fallbackTreatments: { + global: 'OFF_FALLBACK' + } + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.whenReady(); + + t.equal(client.getTreatment('user_account_in_whitelist'), 'off', 'The evaluation will return the treatment defined in the flag if it exists'); + 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'); + + await client.destroy(); + t.end(); + + }); + + assert.test('FallbackTreatment / Impressions correctness with fallback when client is not ready', async t => { + + const config = { + ...configInMemory, + urls: { + events: 'https://events.fallbacktreatment/api' + }, + fallbackTreatments: { + byFlag: { + 'any_flag': 'OFF_FALLBACK' + } + } + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + t.equal(client.getTreatment('any_flag'), 'OFF_FALLBACK', 'The evaluation will return the fallbackTreatment if the client is not ready yet'); + t.equal(client.getTreatment('user_account_in_whitelist'), 'control', 'The evaluation will return the fallbackTreatment if the client is not ready yet'); + + await client.whenReady(); + + fetchMock.postOnce(config.urls.events + '/testImpressions/bulk', (_, opts) => { + + const payload = JSON.parse(opts.body); + + function validateImpressionData(featureFlagName, expectedLabel) { + const impressions = payload.find(e => e.f === featureFlagName).i; + + t.equal(impressions[0].r, expectedLabel, `${featureFlagName} impression with label ${expectedLabel}`); + } + + validateImpressionData('any_flag', 'fallback - not ready'); + validateImpressionData('user_account_in_whitelist', 'not ready'); + t.end(); + + return 200; + }); + + await client.destroy(); + + }); + + assert.test('FallbackTreatment / Fallback dynamic config propagation', async t => { + + const config = { + ...configInMemory, + fallbackTreatments: { + global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, + byFlag: { + 'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' } + } + } + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.whenReady(); + + 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'); + 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'); + + await client.destroy(); + t.end(); + + }); + + assert.test('FallbackTreatment / Fallback dynamic config propagation - inLocalStorage', async t => { + + const config = { + ...configInLocalStorage, + fallbackTreatments: { + global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, + byFlag: { + 'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' } + } + } + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.whenReady(); + + 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'); + 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'); + + await client.destroy(); + t.end(); + + }); + + assert.test('FallbackTreatment / Evaluations non existing flags with fallback do not generate impressions', async t => { + + const config = { + ...configInMemory, + urls: { + events: 'https://events.fallbacktreatment/api' + }, + fallbackTreatments: { + global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, + byFlag: { + 'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' } + } + } + }; + config.impressionListener = listener; + + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.whenReady(); + + 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'); + 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'); + + let POSTED_IMPRESSIONS_COUNT = 0; + + fetchMock.postOnce(config.urls.events + '/testImpressions/bulk', (_, opts) => { + + const payload = JSON.parse(opts.body); + t.equal(payload.length, 1, 'We should have just one impression for the two evaluated flags'); + + function validateImpressionData(featureFlagName, expectedLength) { + + const impressions = payload.find(e => e.f === featureFlagName).i; + t.equal(impressions.length, expectedLength, `${featureFlagName} has ${expectedLength} impressions`); + } + + validateImpressionData('my_flag', 1); + validateImpressionData('non_existent_flag', 0); + POSTED_IMPRESSIONS_COUNT = payload.reduce((acc, curr) => acc + curr.i.length, 0); + t.equal(POSTED_IMPRESSIONS_COUNT, 1, 'We should have just one impression in total.'); + + return 200; + }); + + setTimeout(() => { + t.equal(listener.logImpression.callCount, POSTED_IMPRESSIONS_COUNT, 'Impression listener should be called once per each impression generated.'); + + t.end(); + }, 0); + await client.destroy(); + + + }); + + assert.test('FallbackTreatment / LocalhostMode', async t => { + + const config = { + ...configInMemory, + core: { + ...configInMemory.core, + authorizationKey: 'localhost', + }, + fallbackTreatments: { + global: 'OFF_FALLBACK' + }, + features: { + testing_split: 'on', + } + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.whenReady(); + + t.deepEqual(client.getTreatment('testing_split'), 'on', 'The evaluation should return the treatment defined in localhost mode'); + t.deepEqual(client.getTreatment('non_existent_flag'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist'); + + await client.destroy(); + + t.end(); + }); + +} diff --git a/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js b/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js index aa4d8fd..80013c6 100644 --- a/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js +++ b/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js @@ -69,17 +69,17 @@ export default function (fetchMock, assert) { t.end(); }); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); }); client.on(client.Event.SDK_READY, () => { - t.true(client.__getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); + t.true(client.getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); }); client2.on(client.Event.SDK_READY, () => { - t.true(client2.__getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); + t.true(client2.getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); }); client3.on(client.Event.SDK_READY, () => { - t.true(client2.__getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); + t.true(client2.getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); }); }); @@ -351,7 +351,7 @@ export default function (fetchMock, assert) { let manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); }); await client.ready(); @@ -395,7 +395,7 @@ export default function (fetchMock, assert) { manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); }); await new Promise(res => client.once(client.Event.SDK_READY, res)); diff --git a/src/__tests__/browserSuites/ready-from-cache.spec.js b/src/__tests__/browserSuites/ready-from-cache.spec.js index 6a08d05..a2da273 100644 --- a/src/__tests__/browserSuites/ready-from-cache.spec.js +++ b/src/__tests__/browserSuites/ready-from-cache.spec.js @@ -140,17 +140,17 @@ export default function (fetchMock, assert) { t.end(); }); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); }); client.on(client.Event.SDK_READY, () => { - t.true(client.__getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); + t.true(client.getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); }); client2.on(client.Event.SDK_READY, () => { - t.true(client.__getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); + t.true(client.getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); }); client3.on(client.Event.SDK_READY, () => { - t.true(client.__getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); + t.true(client.getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); }); }); @@ -521,7 +521,7 @@ export default function (fetchMock, assert) { const client = splitio.client(); const client2 = splitio.client('emi@split.io'); - t.equal(client.__getStatus().isReadyFromCache, true, 'Client is ready from cache'); + t.equal(client.getStatus().isReadyFromCache, true, 'Client is ready from cache'); t.equal(client.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache. Key without memberships'); t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with data from cache. Key with memberships'); @@ -581,7 +581,7 @@ export default function (fetchMock, assert) { const manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); }); client.once(client.Event.SDK_READY, () => { @@ -624,7 +624,7 @@ export default function (fetchMock, assert) { const manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); }); client.once(client.Event.SDK_READY, () => { @@ -723,7 +723,7 @@ export default function (fetchMock, assert) { const manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); }); client.once(client.Event.SDK_READY, () => { @@ -783,7 +783,7 @@ export default function (fetchMock, assert) { const manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); }); client.once(client.Event.SDK_READY, () => { @@ -835,7 +835,7 @@ export default function (fetchMock, assert) { const manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); }); client.once(client.Event.SDK_READY, () => { @@ -886,7 +886,7 @@ export default function (fetchMock, assert) { let manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); }); await client.ready(); @@ -926,7 +926,7 @@ export default function (fetchMock, assert) { manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); }); await new Promise(res => client.once(client.Event.SDK_READY, res)); diff --git a/src/__tests__/browserSuites/ready-promise.spec.js b/src/__tests__/browserSuites/ready-promise.spec.js index 9b38460..47395a7 100644 --- a/src/__tests__/browserSuites/ready-promise.spec.js +++ b/src/__tests__/browserSuites/ready-promise.spec.js @@ -31,7 +31,7 @@ function assertGetTreatmentWhenReady(assert, client) { function assertGetTreatmentControlNotReady(assert, client) { consoleSpy.log.resetHistory(); assert.equal(client.getTreatment('hierarchical_splits_test'), 'control', 'We should get control if client is not ready.'); - assert.true(consoleSpy.log.calledWithExactly('[WARN] splitio => getTreatment: the SDK is not ready, results may be incorrect for feature flag hierarchical_splits_test. Make sure to wait for SDK readiness before using this method.'), 'Telling us that calling getTreatment would return CONTROL since SDK is not ready at this point.'); + assert.true(consoleSpy.log.calledWithExactly('[WARN] splitio => getTreatment: the SDK is not ready to evaluate. Results may be incorrect for feature flag hierarchical_splits_test. Make sure to wait for SDK readiness before using this method.'), 'Telling us that calling getTreatment would return CONTROL since SDK is not ready at this point.'); } function assertGetTreatmentControlNotReadyOnDestroy(assert, client) { @@ -68,7 +68,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { const splitio = SplitFactory(config); const client = splitio.client(); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -77,7 +77,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { assertGetTreatmentControlNotReady(t, client); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - It should not in this scenario.'); t.end(); @@ -118,13 +118,13 @@ export default function readyPromiseAssertions(fetchMock, assert) { // In this case, we use the manager instead of the client to get the ready promise const manager = splitio.manager(); - manager.ready() + manager.whenReady() .then(() => { t.pass('### SDK IS READY - the retry request is under the limits.'); assertGetTreatmentWhenReady(t, client); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client); @@ -166,7 +166,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { const splitio = SplitFactory(config); const client = splitio.client(); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -175,13 +175,13 @@ export default function readyPromiseAssertions(fetchMock, assert) { assertGetTreatmentControlNotReady(t, client); setTimeout(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - retry attempt finishes before the requestTimeoutBeforeReady limit'); assertGetTreatmentWhenReady(t, client); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client); @@ -244,7 +244,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { const client = splitio.client(); const nicolasClient = splitio.client('nicolas@split.io'); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -253,25 +253,25 @@ export default function readyPromiseAssertions(fetchMock, assert) { assertGetTreatmentControlNotReady(t, client); client.on(client.Event.SDK_READY, () => { - client.ready().then(() => { + client.whenReady().then(() => { t.pass('### SDK IS READY - the scheduled refresh changes the client state into "ready"'); assertGetTreatmentWhenReady(t, client); const tStart = Date.now(); - nicolasClient.ready().then(() => { + nicolasClient.whenReady().then(() => { const delta = Date.now() - tStart; t.true(nearlyEqual(delta, 0), 'shared client is ready as soon as main client is ready (i.e., splits have arrived)'); const timeoutClient = splitio.client('emiliano@split.io'); - timeoutClient.ready().then(undefined, () => { // setting onRejected handler via `then` method + timeoutClient.whenReady().then(undefined, () => { // setting onRejected handler via `then` method t.pass('### Shared client TIMED OUT - promise rejected since memberships fetch took more time than readyTimeout'); - timeoutClient.ready().catch(() => { // setting onRejected handler via `catch` method + timeoutClient.whenReady().catch(() => { // setting onRejected handler via `catch` method t.pass('### Shared client TIMED OUT - promise keeps being rejected'); timeoutClient.on(timeoutClient.Event.SDK_READY, () => { - timeoutClient.ready().then(() => { + timeoutClient.whenReady().then(() => { t.pass('### Shared client READY - `ready` returns a new resolved promise'); Promise.all([timeoutClient.destroy(), nicolasClient.destroy(), client.destroy()]).then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client); @@ -318,7 +318,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { const splitio = SplitFactory(config); const client = splitio.client(); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); client.destroy().then(() => { t.end(); }); @@ -330,7 +330,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { .catch((error) => { t.equal(error, 'error', '### Handled thrown exception on onRejected callback.'); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - It should not in this scenario.'); t.end(); @@ -368,7 +368,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { const splitio = SplitFactory(config); const client = splitio.client(); - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY as it should, request is under the limits.'); assertGetTreatmentWhenReady(t, client); @@ -377,7 +377,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { .catch((error) => { t.equal(error, 'error', '### Handled thrown exception on onRejected callback.'); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client); @@ -421,7 +421,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { // `ready` is called immediately. Thus, the 'reject' callback is expected to be called in 0.15 seconds aprox. setTimeout(() => { const tStart = Date.now(); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -436,7 +436,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { // `ready` is called in 0.15 seconds, when the promise is just rejected. Thus, the 'reject' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const tStart = Date.now(); - manager.ready() + manager.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -451,7 +451,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { // `ready` is called in 0.25 seconds, right after the promise is resolved (0.2 secs). Thus, the 'resolve' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const tStart = Date.now(); - manager.ready() + manager.whenReady() .then(() => { t.pass('### SDK IS READY - retry attempt finishes before the requestTimeoutBeforeReady limit'); assertGetTreatmentWhenReady(t, client); @@ -465,7 +465,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { }) .then(() => { client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client); @@ -506,26 +506,24 @@ export default function readyPromiseAssertions(fetchMock, assert) { const splitio = SplitFactory(config); - const onReadycallback = function () { }; + const onReadyCallback = function () { }; // We invoke the ready method and also add and remove SDK_READY event listeners using the client and manager instances const client = splitio.client(); - client.ready(); - client.on(client.Event.SDK_READY, onReadycallback); - client.off(client.Event.SDK_READY, onReadycallback); + client.whenReady().then(() => t.fail('SDK TIMED OUT - Should not resolve')).catch(() => t.pass('SDK TIMED OUT - Should reject')); + client.on(client.Event.SDK_READY, onReadyCallback); + client.off(client.Event.SDK_READY, onReadyCallback); const manager = splitio.manager(); - manager.ready(); - manager.on(manager.Event.SDK_READY, onReadycallback); - manager.off(manager.Event.SDK_READY, onReadycallback); + manager.whenReadyFromCache().then(() => t.fail('SDK TIMED OUT - Should not resolve')).catch(() => t.pass('SDK TIMED OUT - Should reject')); + manager.on(manager.Event.SDK_READY, onReadyCallback); + manager.off(manager.Event.SDK_READY, onReadyCallback); consoleSpy.log.resetHistory(); setTimeout(() => { - client.ready(); + client.whenReadyFromCache().then((isReady) => t.true(isReady, 'SDK IS READY (& READY FROM CACHE) - Should resolve')).catch(() => t.fail('SDK TIMED OUT - Should not reject')); assertGetTreatmentWhenReady(t, client); - t.true(consoleSpy.log.calledWithExactly('[WARN] splitio => No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.'), - 'Warning that there are not listeners for SDK_READY event'); // assert error messages when adding event listeners after SDK has already triggered them consoleSpy.log.resetHistory(); @@ -540,15 +538,15 @@ export default function readyPromiseAssertions(fetchMock, assert) { consoleSpy.log.resetHistory(); const sharedClientWithCb = splitio.client('nicolas@split.io'); sharedClientWithCb.on(client.Event.SDK_READY, () => { - t.false(consoleSpy.log.calledWithExactly('[WARN] splitio => No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.'), + t.false(consoleSpy.log.calledWithExactly('[WARN] splitio => No listeners for SDK_READY event detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet synchronized with the backend.'), 'No warning logged'); const sharedClientWithoutCb = splitio.client('emiliano@split.io'); setTimeout(() => { - t.true(consoleSpy.log.calledWithExactly('[WARN] splitio => No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.'), + t.true(consoleSpy.log.calledWithExactly('[WARN] splitio => No listeners for SDK_READY event detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet synchronized with the backend.'), 'Warning logged'); Promise.all([sharedClientWithoutCb.destroy(), client.destroy()]).then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client); @@ -594,18 +592,12 @@ export default function readyPromiseAssertions(fetchMock, assert) { // Assert getTreatment return CONTROL and trigger warning when SDK is not ready yet assertGetTreatmentControlNotReady(t, client); - client.ready() - .then(() => { - t.fail('### SDK IS READY - not TIMED OUT when it should.'); - }); - otherClient.ready() - .then(() => { - t.fail('### SDK IS READY - not TIMED OUT when it should.'); - }); + client.whenReady().then(() => t.fail('SDK TIMED OUT - Should not resolve')).catch(() => t.pass('SDK TIMED OUT - Should reject')); + otherClient.whenReady().then(() => t.fail('SDK TIMED OUT - Should not resolve')).catch(() => t.pass('SDK TIMED OUT - Should reject')); setTimeout(() => { Promise.all([client.destroy(), otherClient.destroy()]).then(() => { - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - It should not in this scenario.'); t.end(); diff --git a/src/__tests__/browserSuites/telemetry.spec.js b/src/__tests__/browserSuites/telemetry.spec.js index 74f98d0..986ffd3 100644 --- a/src/__tests__/browserSuites/telemetry.spec.js +++ b/src/__tests__/browserSuites/telemetry.spec.js @@ -100,14 +100,14 @@ export default async function telemetryBrowserSuite(fetchMock, assert) { const data = JSON.parse(opts.body); assert.true(data.tR > 0, 'timeUntilReady is larger than 0'); - delete data.tR; // delete to validate other properties + assert.true(data.tC > 0, 'timeUntilReadyFromCache is larger than 0'); assert.deepEqual(data, { oM: 0, st: 'memory', aF: 1, rF: 0, sE: false, rR: { sp: 99999, ms: 60, im: 300, ev: 60, te: 1 } /* override featuresRefreshRate */, uO: { s: true, e: true, a: false, st: false, t: true } /* override sdk, events and telemetry URLs */, iQ: 30000, eQ: 500, iM: 0, iL: false, hP: false, nR: 1 /* 1 non ready usage */, t: [], i: [], uC: 2 /* Default GRANTED */, - fsT: 0, fsI: 0 /* Flag sets */ + fsT: 0, fsI: 0 /* Flag sets */, tR: data.tR, tC: data.tC }, 'metrics/config JSON payload should be the expected'); finish.next(); diff --git a/src/__tests__/consumer/browser_consumer.spec.js b/src/__tests__/consumer/browser_consumer.spec.js index d77be2b..b002dae 100644 --- a/src/__tests__/consumer/browser_consumer.spec.js +++ b/src/__tests__/consumer/browser_consumer.spec.js @@ -62,8 +62,8 @@ tape('Browser Consumer mode with pluggable storage', function (t) { /** Evaluation, track and manager methods before SDK_READY */ - assert.equal(client.__getStatus().isReadyFromCache, false, 'SDK in consumer mode is not operational immediately'); - assert.equal(client.__getStatus().isReady, false, 'SDK in consumer mode is not operational immediately'); + assert.equal(client.getStatus().isReadyFromCache, false, 'SDK in consumer mode is not operational immediately'); + assert.equal(client.getStatus().isReady, false, 'SDK in consumer mode is not operational immediately'); client.getTreatment('UT_IN_SEGMENT').then(treatment => assert.equal(treatment, 'control', 'Evaluations using pluggable storage returns a promise that resolves to control if initiated before SDK_READY')); otherClient.track('user', 'test.event', 18).then(result => assert.true(result, 'Track calls returns a promise on consumer mode, that resolves to true if the wrapper push operation success to queue the event')); @@ -218,8 +218,8 @@ tape('Browser Consumer mode with pluggable storage', function (t) { assert.equal(typeof getTreatmentResult.then, 'function', 'GetTreatment calls should always return a promise on Consumer mode.'); assert.equal(await getTreatmentResult, 'control', 'Evaluations using pluggable storage should be control if initiated before SDK_READY.'); - assert.equal(client.__getStatus().isReadyFromCache, false, 'SDK in consumer mode is not operational immediately'); - assert.equal(client.__getStatus().isReady, false, 'SDK in consumer mode is not operational immediately'); + assert.equal(client.getStatus().isReadyFromCache, false, 'SDK in consumer mode is not operational immediately'); + assert.equal(client.getStatus().isReady, false, 'SDK in consumer mode is not operational immediately'); const trackResult = otherClient.track('user', 'test.event', 18); assert.equal(typeof trackResult.then, 'function', 'Track calls should always return a promise on Consumer mode.'); @@ -350,8 +350,8 @@ tape('Browser Consumer mode with pluggable storage', function (t) { assert.equal(typeof getTreatmentResult.then, 'function', 'GetTreatment calls should always return a promise on Consumer mode.'); assert.equal(await getTreatmentResult, 'control', 'Evaluations using pluggable storage should be control if initiated before SDK_READY.'); - assert.equal(client.__getStatus().isReadyFromCache, false, 'SDK in consumer mode is not operational immediately'); - assert.equal(client.__getStatus().isReady, false, 'SDK in consumer mode is not operational immediately'); + assert.equal(client.getStatus().isReadyFromCache, false, 'SDK in consumer mode is not operational immediately'); + assert.equal(client.getStatus().isReady, false, 'SDK in consumer mode is not operational immediately'); const trackResult = otherClient.track('user', 'test.event', 18); assert.equal(typeof trackResult.then, 'function', 'Track calls should always return a promise on Consumer mode.'); diff --git a/src/__tests__/consumer/browser_consumer_partial.spec.js b/src/__tests__/consumer/browser_consumer_partial.spec.js index 25d3411..a83386d 100644 --- a/src/__tests__/consumer/browser_consumer_partial.spec.js +++ b/src/__tests__/consumer/browser_consumer_partial.spec.js @@ -98,8 +98,8 @@ tape('Browser Consumer Partial mode with pluggable storage', function (t) { /** Evaluation, track and manager methods before SDK_READY */ - assert.equal(client.__getStatus().isReadyFromCache, false, 'SDK in consumer mode is not operational immediately'); - assert.equal(client.__getStatus().isReady, false, 'SDK in consumer mode is not operational immediately'); + assert.equal(client.getStatus().isReadyFromCache, false, 'SDK in consumer mode is not operational immediately'); + assert.equal(client.getStatus().isReady, false, 'SDK in consumer mode is not operational immediately'); client.getTreatment('UT_IN_SEGMENT').then(treatment => assert.equal(treatment, 'control', 'Evaluations using pluggable storage returns a promise that resolves to control if initiated before SDK_READY')); otherClient.track('user', 'test.event', 18).then(result => assert.true(result, 'Track calls returns a promise on consumer mode, that resolves to true if the wrapper push operation success to queue the event')); @@ -261,8 +261,8 @@ tape('Browser Consumer Partial mode with pluggable storage', function (t) { assert.equal(typeof getTreatmentResult.then, 'function', 'GetTreatment calls should always return a promise on Consumer mode.'); assert.equal(await getTreatmentResult, 'control', 'Evaluations using pluggable storage should be control if initiated before SDK_READY.'); - assert.equal(client.__getStatus().isReadyFromCache, false, 'SDK in consumer mode is not operational immediately'); - assert.equal(client.__getStatus().isReady, false, 'SDK in consumer mode is not operational immediately'); + assert.equal(client.getStatus().isReadyFromCache, false, 'SDK in consumer mode is not operational immediately'); + assert.equal(client.getStatus().isReady, false, 'SDK in consumer mode is not operational immediately'); const trackResult = otherClient.track('user', 'test.event', 18); assert.equal(typeof trackResult.then, 'function', 'Track calls should always return a promise on Consumer mode.'); diff --git a/src/__tests__/errorCatching/browser.spec.js b/src/__tests__/errorCatching/browser.spec.js index 38248e3..e909d5f 100644 --- a/src/__tests__/errorCatching/browser.spec.js +++ b/src/__tests__/errorCatching/browser.spec.js @@ -90,13 +90,13 @@ tape('Error catching on callbacks - Browsers', assert => { } client.on(client.Event.SDK_READY_TIMED_OUT, () => { - assert.true(client.__getStatus().hasTimedout); // SDK status should be already updated + assert.true(client.getStatus().hasTimedout); // SDK status should be already updated attachErrorHandlerIfApplicable(); null.willThrowForTimedOut(); }); client.once(client.Event.SDK_READY, () => { - assert.true(client.__getStatus().isReady); // SDK status should be already updated + assert.true(client.getStatus().isReady); // SDK status should be already updated attachErrorHandlerIfApplicable(); null.willThrowForReady(); }); @@ -107,7 +107,7 @@ tape('Error catching on callbacks - Browsers', assert => { }); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - assert.true(client.__getStatus().isReadyFromCache); // SDK status should be already updated + assert.true(client.getStatus().isReadyFromCache); // SDK status should be already updated attachErrorHandlerIfApplicable(); null.willThrowForReadyFromCache(); }); diff --git a/src/__tests__/offline/browser.spec.js b/src/__tests__/offline/browser.spec.js index 95ed166..51b60d8 100644 --- a/src/__tests__/offline/browser.spec.js +++ b/src/__tests__/offline/browser.spec.js @@ -3,7 +3,6 @@ import sinon from 'sinon'; import fetchMock from '../testUtils/fetchMock'; import { url } from '../testUtils'; import { SplitFactory, InLocalStorage } from '../../full'; -import { SplitFactory as SplitFactorySlim } from '../../'; import { settingsFactory } from '../../settings'; const settings = settingsFactory({ core: { key: 'facundo@split.io' } }); @@ -88,19 +87,13 @@ tape('Browser offline mode', function (assert) { sharedUpdateCount++; }); - const factoriesReadyFromCache = [ - SplitFactory({ ...config, storage: InLocalStorage() }), - SplitFactorySlim({ ...config, storage: InLocalStorage() }) - ]; const configs = [ { ...config, features: { ...config.features }, storage: InLocalStorage /* invalid */ }, { ...config }, config, + { ...config, storage: InLocalStorage() } ]; - const factories = [ - ...configs.map(config => SplitFactory(config)), - ...factoriesReadyFromCache - ]; + const factories = configs.map(config => SplitFactory(config)); let readyCount = 0, updateCount = 0, readyFromCacheCount = 0; @@ -124,23 +117,25 @@ tape('Browser offline mode', function (assert) { const sdkReadyFromCache = (client) => () => { assert.equal(factory.settings.storage.type, 'MEMORY', 'In localhost mode, storage must fallback to memory storage'); - const clientStatus = client.__getStatus(); + const clientStatus = client.getStatus(); assert.equal(clientStatus.isReadyFromCache, true, 'If ready from cache, READY_FROM_CACHE status must be true'); - assert.equal(clientStatus.isReady, false, 'READY status must not be set before READY_FROM_CACHE'); + assert.equal(clientStatus.isReady, configs[i].storage && configs[i].storage.type === 'LOCALSTORAGE' ? false : true, 'When not using LOCALSTORAGE, READY status is set together with READY_FROM_CACHE'); + if (!clientStatus.isReady) readyFromCacheCount++; assert.deepEqual(manager.names(), ['testing_split', 'testing_split_with_config']); assert.equal(client.getTreatment('testing_split_with_config'), 'off'); - readyFromCacheCount++; client.on(client.Event.SDK_READY_FROM_CACHE, () => { assert.fail('It should not emit SDK_READY_FROM_CACHE again'); }); - const newClient = factory.client('another'); - assert.equal(newClient.getTreatment('testing_split_with_config'), 'off', 'It should evaluate treatments with data from cache instead of control'); - newClient.on(newClient.Event.SDK_READY_FROM_CACHE, () => { - assert.fail('It should not emit SDK_READY_FROM_CACHE if already done.'); - }); + if (configs[i].storage && configs[i].storage.type === 'LOCALSTORAGE') { + const newClient = factory.client('another'); + assert.equal(newClient.getTreatment('testing_split_with_config'), 'off', 'It should evaluate treatments with data from cache instead of control'); + newClient.on(newClient.Event.SDK_READY_FROM_CACHE, () => { + assert.fail('It should not emit SDK_READY_FROM_CACHE if already done.'); + }); + } }; client.on(client.Event.SDK_READY_FROM_CACHE, sdkReadyFromCache(client)); @@ -361,7 +356,7 @@ tape('Browser offline mode', function (assert) { // SDK events on other factory clients assert.equal(readyCount, factories.length, 'Each factory client should have emitted SDK_READY event once'); assert.equal(updateCount, factories.length - 1, 'Each factory client except one should have emitted SDK_UPDATE event once'); - assert.equal(readyFromCacheCount, factoriesReadyFromCache.length * 2, 'The main and shared client of the factories with LOCALSTORAGE should have emitted SDK_READY_FROM_CACHE event'); + assert.equal(readyFromCacheCount, 2, 'The main and shared client of the factory with LOCALSTORAGE should have emitted SDK_READY_FROM_CACHE event'); assert.end(); }); diff --git a/src/__tests__/online/browser.spec.js b/src/__tests__/online/browser.spec.js index a844787..42870e2 100644 --- a/src/__tests__/online/browser.spec.js +++ b/src/__tests__/online/browser.spec.js @@ -28,6 +28,7 @@ import membershipsNicolas from '../mocks/memberships.nicolas@split.io.json'; import membershipsMarcio from '../mocks/memberships.marcio@split.io.json'; import membershipsEmmanuel from '../mocks/memberships.emmanuel@split.io.json'; import { InLocalStorage } from '../../index'; +import evaluationsFallbackSuite from '../browserSuites/evaluations-fallback.spec'; const settings = settingsFactory({ core: { @@ -97,6 +98,7 @@ tape('## E2E CI Tests ##', function (assert) { /* Check client evaluations. */ assert.test('E2E / In Memory', evaluationsSuite.bind(null, configInMemory, fetchMock)); + assert.test('E2E / In Memory Fallback', evaluationsFallbackSuite.bind(null, configInMemory, configInLocalStorage, fetchMock)); assert.test('E2E / In Memory with Bucketing Key', evaluationsSuite.bind(null, configInMemoryWithBucketingKey, fetchMock)); assert.test('E2E / In LocalStorage with In Memory Fallback', evaluationsSuite.bind(null, configInLocalStorage, fetchMock)); /* Check impressions */ diff --git a/src/settings/defaults.ts b/src/settings/defaults.ts index 35fdff3..199eb8d 100644 --- a/src/settings/defaults.ts +++ b/src/settings/defaults.ts @@ -2,7 +2,7 @@ import type SplitIO from '@splitsoftware/splitio-commons/types/splitio'; import { LogLevels, isLogLevelString } from '@splitsoftware/splitio-commons/src/logger/index'; import { CONSENT_GRANTED } from '@splitsoftware/splitio-commons/src/utils/constants'; -const packageVersion = '1.5.1'; +const packageVersion = '1.6.0'; /** * In browser, the default debug level, can be set via the `localStorage.splitio_debug` item. diff --git a/ts-tests/index.ts b/ts-tests/index.ts index b7d27e0..bc6feba 100644 --- a/ts-tests/index.ts +++ b/ts-tests/index.ts @@ -612,7 +612,14 @@ let fullBrowserSettings: SplitIO.IClientSideSettings = { getHeaderOverrides(context) { return { ...context.headers, 'header': 'value' }; }, } }, - userConsent: 'GRANTED' + userConsent: 'GRANTED', + fallbackTreatments: { + global: { treatment: 'global-treatment', config: '{"global": true}' }, + byFlag: { + 'my_flag': { treatment: 'flag-treatment', config: '{"flag": true}' }, + 'my_other_flag': 'other-flag-treatment' + } + } }; fullBrowserSettings.userConsent = 'DECLINED'; fullBrowserSettings.userConsent = 'UNKNOWN'; @@ -657,7 +664,14 @@ let fullBrowserAsyncSettings: SplitIO.IClientSideAsyncSettings = { getHeaderOverrides(context) { return { ...context.headers, 'header': 'value' }; }, } }, - userConsent: 'GRANTED' + userConsent: 'GRANTED', + fallbackTreatments: { + global: 'global-treatment', + byFlag: { + 'my_flag': { treatment: 'flag-treatment', config: '{"flag": true}' }, + 'my_other_flag': 'other-flag-treatment' + } + } }; fullBrowserAsyncSettings.mode = 'consumer_partial'; fullBrowserAsyncSettings.userConsent = 'DECLINED'; diff --git a/types/index.d.ts b/types/index.d.ts index 53f7c3b..66025ff 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -19,7 +19,7 @@ declare module JsSdk { /** * Persistent storage. By default, it uses the browser's LocalStorage API if available. * - * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#configuring-cache} + * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#configure-cache-behavior} */ export function InLocalStorage(options?: SplitIO.InLocalStorageOptions): SplitIO.StorageSyncFactory;