From 57f317ce580d0b6ab24f667d5d6962fb30bc2204 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Tue, 28 Oct 2025 16:17:23 -0300 Subject: [PATCH 1/7] [FME-10573] update js commons to 2.7.9-rc.2 --- package-lock.json | 4 +- package.json | 2 +- .../evaluations-fallback.spec.js | 269 ++++++++++++++++++ src/__tests__/online/browser.spec.js | 2 + src/settings/defaults.ts | 2 +- 5 files changed, 275 insertions(+), 4 deletions(-) create mode 100644 src/__tests__/browserSuites/evaluations-fallback.spec.js diff --git a/package-lock.json b/package-lock.json index fd053c6..0b8f14b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-browserjs", - "version": "1.5.1", + "version": "1.5.1-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-browserjs", - "version": "1.5.1", + "version": "1.5.1-rc.0", "license": "Apache-2.0", "dependencies": { "@splitsoftware/splitio-commons": "2.7.9-rc.2", diff --git a/package.json b/package.json index 7c20a04..01f87f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-browserjs", - "version": "1.5.1", + "version": "1.5.1-rc.0", "description": "Split SDK for JavaScript on Browser", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/__tests__/browserSuites/evaluations-fallback.spec.js b/src/__tests__/browserSuites/evaluations-fallback.spec.js new file mode 100644 index 0000000..e0f8478 --- /dev/null +++ b/src/__tests__/browserSuites/evaluations-fallback.spec.js @@ -0,0 +1,269 @@ +import sinon from 'sinon'; +import { SplitFactory } from '../../'; +import { base } from '@splitsoftware/splitio-commons/src/utils/settingsValidation'; + +const listener = { + logImpression: sinon.stub() +}; + +const baseConfig = { + core: { + authorizationKey: '', + key: 'facundo@split.io' + }, + sync: { + impressionsMode: 'DEBUG' + }, + streamingEnabled: false +}; + +export default async function (fetchMock, assert) { + + assert.test('FallbackTreatment / Split factory with no fallbackTreatment defined', async t => { + + const splitio = SplitFactory(baseConfig); + 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 = Object.assign({}, baseConfig); + config.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 = Object.assign({}, baseConfig); + config.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 = Object.assign({}, baseConfig); + config.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 = Object.assign({}, baseConfig); + config.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 whenReady', async t => { + + const config = Object.assign({}, baseConfig); + config.urls = { + events: 'https://events.fallbacktreatment/api' + }; + config.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 whenReady yet'); + t.equal(client.getTreatment('user_account_in_whitelist'), 'control', 'The evaluation will return the fallbackTreatment if the client is not whenReady 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 whenReady'); + validateImpressionData('user_account_in_whitelist', 'not whenReady'); + t.end(); + + return 200; + }); + + await client.destroy(); + + }); + + assert.test('FallbackTreatment / Fallback dynamic config propagation', async t => { + + const config = Object.assign({}, baseConfig); + config.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 = Object.assign({}, baseConfig); + config.urls = { + events: 'https://events.fallbacktreatment/api' + }; + config.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; + + 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 = { + ...baseConfig, + core: { + ...baseConfig.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__/online/browser.spec.js b/src/__tests__/online/browser.spec.js index a844787..ef60128 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, 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..cf02fad 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.5.1-rc.0'; /** * In browser, the default debug level, can be set via the `localStorage.splitio_debug` item. From 352b8bc85738e95b2adbf862250537c9ff77c005 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Tue, 28 Oct 2025 16:24:53 -0300 Subject: [PATCH 2/7] Fix --- src/__tests__/browserSuites/evaluations-fallback.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/__tests__/browserSuites/evaluations-fallback.spec.js b/src/__tests__/browserSuites/evaluations-fallback.spec.js index e0f8478..a01557f 100644 --- a/src/__tests__/browserSuites/evaluations-fallback.spec.js +++ b/src/__tests__/browserSuites/evaluations-fallback.spec.js @@ -1,6 +1,5 @@ import sinon from 'sinon'; import { SplitFactory } from '../../'; -import { base } from '@splitsoftware/splitio-commons/src/utils/settingsValidation'; const listener = { logImpression: sinon.stub() From cf950bc60516605c42f2a8e946e61e35bb3d51ed Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Tue, 28 Oct 2025 18:40:45 -0300 Subject: [PATCH 3/7] Fix tests --- .../evaluations-fallback.spec.js | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/__tests__/browserSuites/evaluations-fallback.spec.js b/src/__tests__/browserSuites/evaluations-fallback.spec.js index a01557f..6a6bdcb 100644 --- a/src/__tests__/browserSuites/evaluations-fallback.spec.js +++ b/src/__tests__/browserSuites/evaluations-fallback.spec.js @@ -5,18 +5,7 @@ const listener = { logImpression: sinon.stub() }; -const baseConfig = { - core: { - authorizationKey: '', - key: 'facundo@split.io' - }, - sync: { - impressionsMode: 'DEBUG' - }, - streamingEnabled: false -}; - -export default async function (fetchMock, assert) { +export default function (baseConfig, fetchMock, assert) { assert.test('FallbackTreatment / Split factory with no fallbackTreatment defined', async t => { @@ -122,7 +111,7 @@ export default async function (fetchMock, assert) { }); - assert.test('FallbackTreatment / Impressions correctness with fallback when client is not whenReady', async t => { + assert.test('FallbackTreatment / Impressions correctness with fallback when client is not ready', async t => { const config = Object.assign({}, baseConfig); config.urls = { @@ -136,8 +125,8 @@ export default async function (fetchMock, assert) { 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 whenReady yet'); - t.equal(client.getTreatment('user_account_in_whitelist'), 'control', 'The evaluation will return the fallbackTreatment if the client is not whenReady yet'); + 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(); @@ -151,8 +140,8 @@ export default async function (fetchMock, assert) { t.equal(impressions[0].r, expectedLabel, `${featureFlagName} impression with label ${expectedLabel}`); } - validateImpressionData('any_flag', 'fallback - not whenReady'); - validateImpressionData('user_account_in_whitelist', 'not whenReady'); + validateImpressionData('any_flag', 'fallback - not ready'); + validateImpressionData('user_account_in_whitelist', 'not ready'); t.end(); return 200; @@ -206,7 +195,7 @@ export default async function (fetchMock, assert) { 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; + let POSTED_IMPRESSIONS_COUNT = 0; fetchMock.postOnce(config.urls.events + '/testImpressions/bulk', (_, opts) => { From d3a12899db708288e405e90a5e36bf4807aa00a2 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Tue, 28 Oct 2025 19:06:50 -0300 Subject: [PATCH 4/7] destrutcure config in tests and add inlocalstorage --- .../evaluations-fallback.spec.js | 138 +++++++++++++----- src/__tests__/online/browser.spec.js | 2 +- 2 files changed, 100 insertions(+), 40 deletions(-) diff --git a/src/__tests__/browserSuites/evaluations-fallback.spec.js b/src/__tests__/browserSuites/evaluations-fallback.spec.js index 6a6bdcb..f7f8818 100644 --- a/src/__tests__/browserSuites/evaluations-fallback.spec.js +++ b/src/__tests__/browserSuites/evaluations-fallback.spec.js @@ -5,11 +5,11 @@ const listener = { logImpression: sinon.stub() }; -export default function (baseConfig, fetchMock, assert) { +export default function (configInMemory, configInLocalStorage, fetchMock, assert) { assert.test('FallbackTreatment / Split factory with no fallbackTreatment defined', async t => { - const splitio = SplitFactory(baseConfig); + const splitio = SplitFactory(configInMemory); const client = splitio.client(); await client.whenReady(); @@ -24,9 +24,11 @@ export default function (baseConfig, fetchMock, assert) { assert.test('FallbackTreatment / Split factory with global fallbackTreatment defined', async t => { - const config = Object.assign({}, baseConfig); - config.fallbackTreatments = { - global: 'FALLBACK_TREATMENT' + const config = { + ...configInMemory, + fallbackTreatments: { + global: 'FALLBACK_TREATMENT' + } }; const splitio = SplitFactory(config); const client = splitio.client(); @@ -44,10 +46,12 @@ export default function (baseConfig, fetchMock, assert) { assert.test('FallbackTreatment / Split factory with specific fallbackTreatment defined', async t => { - const config = Object.assign({}, baseConfig); - config.fallbackTreatments = { - byFlag: { - 'non_existent_flag': 'FALLBACK_TREATMENT', + const config = { + ...configInMemory, + fallbackTreatments: { + byFlag: { + 'non_existent_flag': 'FALLBACK_TREATMENT', + } } }; const splitio = SplitFactory(config); @@ -69,11 +73,13 @@ export default function (baseConfig, fetchMock, assert) { assert.test('FallbackTreatment / flag override beats global fallbackTreatment', async t => { - const config = Object.assign({}, baseConfig); - config.fallbackTreatments = { - global: 'OFF_FALLBACK', - byFlag: { - 'my_flag': 'ON_FALLBACK', + const config = { + ...configInMemory, + fallbackTreatments: { + global: 'OFF_FALLBACK', + byFlag: { + 'my_flag': 'ON_FALLBACK', + } } }; const splitio = SplitFactory(config); @@ -94,9 +100,11 @@ export default function (baseConfig, fetchMock, assert) { assert.test('FallbackTreatment / override applies only when original is control', async t => { - const config = Object.assign({}, baseConfig); - config.fallbackTreatments = { - global: 'OFF_FALLBACK' + const config = { + ...configInMemory, + fallbackTreatments: { + global: 'OFF_FALLBACK' + } }; const splitio = SplitFactory(config); const client = splitio.client(); @@ -111,15 +119,39 @@ export default function (baseConfig, fetchMock, assert) { }); - assert.test('FallbackTreatment / Impressions correctness with fallback when client is not ready', async t => { - const config = Object.assign({}, baseConfig); - config.urls = { - events: 'https://events.fallbacktreatment/api' + assert.test('FallbackTreatment / override applies only when original is control - inLocalStorage', async t => { + + const config = { + ...configInLocalStorage, + fallbackTreatments: { + global: 'OFF_FALLBACK' + } }; - config.fallbackTreatments = { - byFlag: { - 'any_flag': '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); @@ -153,11 +185,13 @@ export default function (baseConfig, fetchMock, assert) { assert.test('FallbackTreatment / Fallback dynamic config propagation', async t => { - const config = Object.assign({}, baseConfig); - config.fallbackTreatments = { - global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, - byFlag: { - 'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' } + const config = { + ...configInMemory, + fallbackTreatments: { + global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, + byFlag: { + 'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' } + } } }; const splitio = SplitFactory(config); @@ -173,16 +207,42 @@ export default function (baseConfig, fetchMock, assert) { }); - assert.test('FallbackTreatment / Evaluations non existing flags with fallback do not generate impressions', async t => { + assert.test('FallbackTreatment / Fallback dynamic config propagation - inLocalStorage', async t => { - const config = Object.assign({}, baseConfig); - config.urls = { - events: 'https://events.fallbacktreatment/api' + const config = { + ...configInLocalStorage, + fallbackTreatments: { + global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, + byFlag: { + 'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' } + } + } }; - config.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; @@ -229,9 +289,9 @@ export default function (baseConfig, fetchMock, assert) { assert.test('FallbackTreatment / LocalhostMode', async t => { const config = { - ...baseConfig, + ...configInMemory, core: { - ...baseConfig.core, + ...configInMemory.core, authorizationKey: 'localhost', }, fallbackTreatments: { diff --git a/src/__tests__/online/browser.spec.js b/src/__tests__/online/browser.spec.js index ef60128..42870e2 100644 --- a/src/__tests__/online/browser.spec.js +++ b/src/__tests__/online/browser.spec.js @@ -98,7 +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, 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 */ From 7842417ac877dd9bf268c462a9fd52eb54d97ec3 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Tue, 28 Oct 2025 19:18:14 -0300 Subject: [PATCH 5/7] fix lint --- src/__tests__/browserSuites/evaluations-fallback.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/browserSuites/evaluations-fallback.spec.js b/src/__tests__/browserSuites/evaluations-fallback.spec.js index f7f8818..e18cad7 100644 --- a/src/__tests__/browserSuites/evaluations-fallback.spec.js +++ b/src/__tests__/browserSuites/evaluations-fallback.spec.js @@ -207,7 +207,7 @@ export default function (configInMemory, configInLocalStorage, fetchMock, assert }); - assert.test('FallbackTreatment / Fallback dynamic config propagation - inLocalStorage', async t => { + assert.test('FallbackTreatment / Fallback dynamic config propagation - inLocalStorage', async t => { const config = { ...configInLocalStorage, From c3a6c855cdfad0fee9535de501c4a8fd33782dea Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Wed, 29 Oct 2025 15:17:41 -0300 Subject: [PATCH 6/7] add ts-tests - update commons to 2.7.9-rc.3 --- package-lock.json | 14 +++++++------- package.json | 2 +- ts-tests/index.ts | 18 ++++++++++++++++-- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0b8f14b..43d2d03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.5.1-rc.0", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.7.9-rc.2", + "@splitsoftware/splitio-commons": "2.7.9-rc.3", "tslib": "^2.3.1", "unfetch": "^4.2.0" }, @@ -1396,9 +1396,9 @@ "dev": true }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.7.9-rc.2", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.2.tgz", - "integrity": "sha512-t8YVwDe4UBvD95w+mvKq7Z2khozZXDrIuOWt3ixxtmyeyoZp5L0L9x9E3DWOcQ0EVxfpQv+tAErHG3bw3LkbNg==", + "version": "2.7.9-rc.3", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.3.tgz", + "integrity": "sha512-momlpLuBt0yQXzo7blDWbNIs+H0fIPcxWukZVXMIKHiLiZtfu608diLT8EB/PNtA245OUMIRzachk5If4BBOWw==", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -10493,9 +10493,9 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "2.7.9-rc.2", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.2.tgz", - "integrity": "sha512-t8YVwDe4UBvD95w+mvKq7Z2khozZXDrIuOWt3ixxtmyeyoZp5L0L9x9E3DWOcQ0EVxfpQv+tAErHG3bw3LkbNg==", + "version": "2.7.9-rc.3", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.3.tgz", + "integrity": "sha512-momlpLuBt0yQXzo7blDWbNIs+H0fIPcxWukZVXMIKHiLiZtfu608diLT8EB/PNtA245OUMIRzachk5If4BBOWw==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" diff --git a/package.json b/package.json index 01f87f5..4845d22 100644 --- a/package.json +++ b/package.json @@ -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.9-rc.2", + "@splitsoftware/splitio-commons": "2.7.9-rc.3", "tslib": "^2.3.1", "unfetch": "^4.2.0" }, 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'; From 1af09a55c2562c302d94cf0baa458a25b54d50c9 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 30 Oct 2025 17:18:32 -0300 Subject: [PATCH 7/7] stable version --- CHANGES.txt | 3 ++- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- src/settings/defaults.ts | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4a33f6d..c021991 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ -1.6.0 (October XX, 2025) +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. diff --git a/package-lock.json b/package-lock.json index 43d2d03..aa1e9c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@splitsoftware/splitio-browserjs", - "version": "1.5.1-rc.0", + "version": "1.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-browserjs", - "version": "1.5.1-rc.0", + "version": "1.6.0", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.7.9-rc.3", + "@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.9-rc.3", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.3.tgz", - "integrity": "sha512-momlpLuBt0yQXzo7blDWbNIs+H0fIPcxWukZVXMIKHiLiZtfu608diLT8EB/PNtA245OUMIRzachk5If4BBOWw==", + "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.9-rc.3", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.3.tgz", - "integrity": "sha512-momlpLuBt0yQXzo7blDWbNIs+H0fIPcxWukZVXMIKHiLiZtfu608diLT8EB/PNtA245OUMIRzachk5If4BBOWw==", + "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 4845d22..c710d4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-browserjs", - "version": "1.5.1-rc.0", + "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.9-rc.3", + "@splitsoftware/splitio-commons": "2.8.0", "tslib": "^2.3.1", "unfetch": "^4.2.0" }, diff --git a/src/settings/defaults.ts b/src/settings/defaults.ts index cf02fad..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-rc.0'; +const packageVersion = '1.6.0'; /** * In browser, the default debug level, can be set via the `localStorage.splitio_debug` item.