import type { DatafileContent } from "@featurevisor/types"; import { createInstance } from "./instance"; import { createLogger } from "./logger"; describe("sdk: instance", function () { it("should be a function", function () { expect(typeof createInstance).toEqual("function"); }); it("should create instance with datafile content", function () { const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: {}, segments: {}, }, }); expect(typeof sdk.getVariation).toEqual("function"); }); it("should configure plain bucketBy", function () { let capturedBucketKey = ""; const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { test: { key: "test", bucketBy: "userId", variations: [{ value: "control" }, { value: "treatment" }], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [ { variation: "control", range: [0, 100000] }, { variation: "treatment", range: [0, 0] }, ], }, ], }, }, segments: {}, }, hooks: [ { name: "unit-test", bucketKey: ({ bucketKey }) => { capturedBucketKey = bucketKey; return bucketKey; }, }, ], }); const featureKey = "test"; const context = { userId: "123", }; expect(sdk.isEnabled(featureKey, context)).toEqual(true); expect(sdk.getVariation(featureKey, context)).toEqual("control"); expect(capturedBucketKey).toEqual("123.test"); }); it("should configure and bucketBy", function () { let capturedBucketKey = ""; const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { test: { key: "test", bucketBy: ["userId", "organizationId"], variations: [{ value: "control" }, { value: "treatment" }], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [ { variation: "control", range: [0, 100000] }, { variation: "treatment", range: [0, 0] }, ], }, ], }, }, segments: {}, }, hooks: [ { name: "unit-test", bucketKey: ({ bucketKey }) => { capturedBucketKey = bucketKey; return bucketKey; }, }, ], }); const featureKey = "test"; const context = { userId: "123", organizationId: "456", }; expect(sdk.getVariation(featureKey, context)).toEqual("control"); expect(capturedBucketKey).toEqual("123.456.test"); }); it("should configure or bucketBy", function () { let capturedBucketKey = ""; const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { test: { key: "test", bucketBy: { or: ["userId", "deviceId"] }, variations: [{ value: "control" }, { value: "treatment" }], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [ { variation: "control", range: [0, 100000] }, { variation: "treatment", range: [0, 0] }, ], }, ], }, }, segments: {}, }, hooks: [ { name: "unit-test", bucketKey: ({ bucketKey }) => { capturedBucketKey = bucketKey; return bucketKey; }, }, ], }); expect( sdk.isEnabled("test", { userId: "123", deviceId: "456", }), ).toEqual(true); expect( sdk.getVariation("test", { userId: "123", deviceId: "456", }), ).toEqual("control"); expect(capturedBucketKey).toEqual("123.test"); expect( sdk.getVariation("test", { deviceId: "456", }), ).toEqual("control"); expect(capturedBucketKey).toEqual("456.test"); }); it("should intercept context: before hook", function () { let intercepted = false; let interceptedFeatureKey = ""; let interceptedVariableKey: string | undefined = ""; const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { test: { key: "test", bucketBy: "userId", variations: [{ value: "control" }, { value: "treatment" }], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [ { variation: "control", range: [0, 100000] }, { variation: "treatment", range: [0, 0] }, ], }, ], }, }, segments: {}, }, hooks: [ { name: "unit-test", before: (options) => { const { featureKey, variableKey } = options; intercepted = true; interceptedFeatureKey = featureKey; interceptedVariableKey = variableKey; return options; }, }, ], }); const variation = sdk.getVariation("test", { userId: "123", }); expect(variation).toEqual("control"); expect(intercepted).toEqual(true); expect(interceptedFeatureKey).toEqual("test"); expect(interceptedVariableKey).toEqual(undefined); }); it("should intercept value: after hook", function () { let intercepted = false; let interceptedFeatureKey = ""; let interceptedVariableKey: string | undefined = ""; const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { test: { key: "test", bucketBy: "userId", variations: [{ value: "control" }, { value: "treatment" }], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [ { variation: "control", range: [0, 100000] }, { variation: "treatment", range: [0, 0] }, ], }, ], }, }, segments: {}, }, hooks: [ { name: "unit-test", after: (options) => { const { featureKey, variableKey } = options; intercepted = true; interceptedFeatureKey = featureKey; interceptedVariableKey = variableKey; options.variationValue = "control_intercepted"; // manipulating value here return options; }, }, ], }); const variation = sdk.getVariation("test", { userId: "123", }); expect(variation).toEqual("control_intercepted"); // should not be "control" any more expect(intercepted).toEqual(true); expect(interceptedFeatureKey).toEqual("test"); expect(interceptedVariableKey).toEqual(undefined); }); it("should initialize with sticky features", function (done) { const datafileContent: DatafileContent = { schemaVersion: "2", revision: "1.0", features: { test: { key: "test", bucketBy: "userId", variations: [{ value: "control" }, { value: "treatment" }], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [ { variation: "control", range: [0, 0] }, { variation: "treatment", range: [0, 100000] }, ], }, ], }, }, segments: {}, }; const sdk = createInstance({ sticky: { test: { enabled: true, variation: "control", variables: { color: "red", }, }, }, }); // initially control expect( sdk.getVariation("test", { userId: "123", }), ).toEqual("control"); expect( sdk.getVariable("test", "color", { userId: "123", }), ).toEqual("red"); sdk.setDatafile(datafileContent); setTimeout(function () { // still control after setting datafile expect( sdk.getVariation("test", { userId: "123", }), ).toEqual("control"); // unsetting sticky features will make it treatment sdk.setSticky({}, true); expect( sdk.getVariation("test", { userId: "123", }), ).toEqual("treatment"); done(); }, 75); }); it("should honour simple required features", function () { const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { requiredKey: { key: "requiredKey", bucketBy: "userId", traffic: [ { key: "1", segments: "*", percentage: 0, // disabled allocation: [], }, ], }, myKey: { key: "myKey", bucketBy: "userId", required: ["requiredKey"], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [], }, ], }, }, segments: {}, }, }); // should be disabled because required is disabled expect(sdk.isEnabled("myKey")).toEqual(false); // enabling required should enable the feature too const sdk2 = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { requiredKey: { key: "requiredKey", bucketBy: "userId", traffic: [ { key: "1", segments: "*", percentage: 100000, // enabled allocation: [], }, ], }, myKey: { key: "myKey", bucketBy: "userId", required: ["requiredKey"], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [], }, ], }, }, segments: {}, }, }); expect(sdk2.isEnabled("myKey")).toEqual(true); }); it("should honour required features with variation", function () { // should be disabled because required has different variation const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { requiredKey: { key: "requiredKey", bucketBy: "userId", variations: [{ value: "control" }, { value: "treatment" }], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [ { variation: "control", range: [0, 0] }, { variation: "treatment", range: [0, 100000] }, ], }, ], }, myKey: { key: "myKey", bucketBy: "userId", required: [ { key: "requiredKey", variation: "control", // different variation }, ], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [], }, ], }, }, segments: {}, }, }); expect(sdk.isEnabled("myKey")).toEqual(false); // child should be enabled because required has desired variation const sdk2 = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { requiredKey: { key: "requiredKey", bucketBy: "userId", variations: [{ value: "control" }, { value: "treatment" }], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [ { variation: "control", range: [0, 0] }, { variation: "treatment", range: [0, 100000] }, ], }, ], }, myKey: { key: "myKey", bucketBy: "userId", required: [ { key: "requiredKey", variation: "treatment", // desired variation }, ], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [], }, ], }, }, segments: {}, }, }); expect(sdk2.isEnabled("myKey")).toEqual(true); }); it("should emit warnings for deprecated feature", function () { let deprecatedCount = 0; const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { test: { key: "test", bucketBy: "userId", variations: [{ value: "control" }, { value: "treatment" }], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [ { variation: "control", range: [0, 100000] }, { variation: "treatment", range: [0, 0] }, ], }, ], }, deprecatedTest: { key: "deprecatedTest", deprecated: true, bucketBy: "userId", variations: [{ value: "control" }, { value: "treatment" }], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [ { variation: "control", range: [0, 100000] }, { variation: "treatment", range: [0, 0] }, ], }, ], }, }, segments: {}, }, logger: createLogger({ handler: function (level, message) { if (level === "warn" && message.indexOf("is deprecated")) { deprecatedCount += 1; } }, }), }); const testVariation = sdk.getVariation("test", { userId: "123", }); const deprecatedTestVariation = sdk.getVariation("deprecatedTest", { userId: "123", }); expect(testVariation).toEqual("control"); expect(deprecatedTestVariation).toEqual("control"); expect(deprecatedCount).toEqual(1); }); it("should check if enabled for overridden flags from rules", function () { const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { test: { key: "test", bucketBy: "userId", traffic: [ { key: "2", segments: ["netherlands"], percentage: 100000, enabled: false, allocation: [], }, { key: "1", segments: "*", percentage: 100000, allocation: [], }, ], }, }, segments: { netherlands: { key: "netherlands", conditions: JSON.stringify([ { attribute: "country", operator: "equals", value: "nl", }, ]), }, }, }, }); expect(sdk.isEnabled("test", { userId: "user-123", country: "de" })).toEqual(true); expect(sdk.isEnabled("test", { userId: "user-123", country: "nl" })).toEqual(false); }); it("should check if enabled for mutually exclusive features", function () { let bucketValue = 10000; const sdk = createInstance({ hooks: [ { name: "unit-test", bucketValue: function () { return bucketValue; }, }, ], datafile: { schemaVersion: "2", revision: "1.0", features: { mutex: { key: "mutex", bucketBy: "userId", ranges: [[0, 50000]], traffic: [{ key: "1", segments: "*", percentage: 50000, allocation: [] }], }, }, segments: {}, }, }); expect(sdk.isEnabled("test")).toEqual(false); expect(sdk.isEnabled("test", { userId: "123" })).toEqual(false); bucketValue = 40000; expect(sdk.isEnabled("mutex", { userId: "123" })).toEqual(true); bucketValue = 60000; expect(sdk.isEnabled("mutex", { userId: "123" })).toEqual(false); }); it("should get variation", function () { const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { test: { key: "test", bucketBy: "userId", variations: [{ value: "control" }, { value: "treatment" }], force: [ { conditions: [{ attribute: "userId", operator: "equals", value: "user-gb" }], enabled: false, }, { segments: ["netherlands"], enabled: false, }, ], traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [ { variation: "control", range: [0, 0] }, { variation: "treatment", range: [0, 100000] }, ], }, ], }, testWithNoVariation: { key: "testWithNoVariation", bucketBy: "userId", traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [], }, ], }, }, segments: { netherlands: { key: "netherlands", conditions: JSON.stringify([ { attribute: "country", operator: "equals", value: "nl", }, ]), }, }, }, }); const context = { userId: "123", }; expect(sdk.getVariation("test", context)).toEqual("treatment"); expect(sdk.getVariation("test", { userId: "user-ch" })).toEqual("treatment"); // non existing expect(sdk.getVariation("nonExistingFeature", context)).toEqual(null); // disabled expect(sdk.getVariation("test", { userId: "user-gb" })).toEqual(null); expect(sdk.getVariation("test", { userId: "user-gb" })).toEqual(null); expect(sdk.getVariation("test", { userId: "123", country: "nl" })).toEqual(null); // no variation expect(sdk.getVariation("testWithNoVariation", context)).toEqual(null); }); it("should get variable", function () { const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { test: { key: "test", bucketBy: "userId", variablesSchema: { color: { key: "color", type: "string", defaultValue: "red", }, showSidebar: { key: "showSidebar", type: "boolean", defaultValue: false, }, sidebarTitle: { key: "sidebarTitle", type: "string", defaultValue: "sidebar title", }, count: { key: "count", type: "integer", defaultValue: 0, }, price: { key: "price", type: "double", defaultValue: 9.99, }, paymentMethods: { key: "paymentMethods", type: "array", defaultValue: ["paypal", "creditcard"], }, flatConfig: { key: "flatConfig", type: "object", defaultValue: { key: "value", }, }, nestedConfig: { key: "nestedConfig", type: "json", defaultValue: JSON.stringify({ key: { nested: "value", }, }), }, }, variations: [ { value: "control" }, { value: "treatment", variables: { showSidebar: true, sidebarTitle: "sidebar title from variation", }, variableOverrides: { showSidebar: [ { segments: ["netherlands"], value: false, }, { conditions: [ { attribute: "country", operator: "equals", value: "de", }, ], value: false, }, ], sidebarTitle: [ { segments: ["netherlands"], value: "Dutch title", }, { conditions: [ { attribute: "country", operator: "equals", value: "de", }, ], value: "German title", }, ], }, }, ], force: [ { conditions: [{ attribute: "userId", operator: "equals", value: "user-ch" }], enabled: true, variation: "control", variables: { color: "red and white", }, }, { conditions: [{ attribute: "userId", operator: "equals", value: "user-gb" }], enabled: false, }, { conditions: [ { attribute: "userId", operator: "equals", value: "user-forced-variation" }, ], enabled: true, variation: "treatment", }, ], traffic: [ // belgium { key: "2", segments: ["belgium"], percentage: 100000, allocation: [ { variation: "control", range: [0, 0] }, { variation: "treatment", range: [0, 100000], }, ], variation: "control", variables: { color: "black", }, }, // everyone { key: "1", segments: "*", percentage: 100000, allocation: [ { variation: "control", range: [0, 0] }, { variation: "treatment", range: [0, 100000], }, ], }, ], }, anotherTest: { key: "test", bucketBy: "userId", traffic: [ // everyone { key: "1", segments: "*", percentage: 100000, }, ], }, }, segments: { netherlands: { key: "netherlands", conditions: JSON.stringify([ { attribute: "country", operator: "equals", value: "nl", }, ]), }, belgium: { key: "belgium", conditions: JSON.stringify([ { attribute: "country", operator: "equals", value: "be", }, ]), }, }, }, }); const context = { userId: "123", }; const evaluatedFeatures = sdk.getAllEvaluations(context); expect(evaluatedFeatures).toEqual({ test: { enabled: true, variation: "treatment", variables: { color: "red", showSidebar: true, sidebarTitle: "sidebar title from variation", count: 0, price: 9.99, paymentMethods: ["paypal", "creditcard"], flatConfig: { key: "value", }, nestedConfig: { key: { nested: "value", }, }, }, }, anotherTest: { enabled: true, }, }); expect(sdk.getVariation("test", context)).toEqual("treatment"); expect( sdk.getVariation("test", { ...context, country: "be", }), ).toEqual("control"); expect(sdk.getVariation("test", { userId: "user-ch" })).toEqual("control"); expect(sdk.getVariable("test", "color", context)).toEqual("red"); expect(sdk.getVariableString("test", "color", context)).toEqual("red"); expect(sdk.getVariable("test", "color", { ...context, country: "be" })).toEqual("black"); expect(sdk.getVariable("test", "color", { userId: "user-ch" })).toEqual("red and white"); expect(sdk.getVariable("test", "showSidebar", context)).toEqual(true); expect(sdk.getVariableBoolean("test", "showSidebar", context)).toEqual(true); expect( sdk.getVariableBoolean("test", "showSidebar", { ...context, country: "nl", }), ).toEqual(false); expect( sdk.getVariableBoolean("test", "showSidebar", { ...context, country: "de", }), ).toEqual(false); expect( sdk.getVariableString("test", "sidebarTitle", { userId: "user-forced-variation", country: "de", }), ).toEqual("German title"); expect( sdk.getVariableString("test", "sidebarTitle", { userId: "user-forced-variation", country: "nl", }), ).toEqual("Dutch title"); expect( sdk.getVariableString("test", "sidebarTitle", { userId: "user-forced-variation", country: "be", }), ).toEqual("sidebar title from variation"); expect(sdk.getVariable("test", "count", context)).toEqual(0); expect(sdk.getVariableInteger("test", "count", context)).toEqual(0); expect(sdk.getVariable("test", "price", context)).toEqual(9.99); expect(sdk.getVariableDouble("test", "price", context)).toEqual(9.99); expect(sdk.getVariable("test", "paymentMethods", context)).toEqual(["paypal", "creditcard"]); expect(sdk.getVariableArray("test", "paymentMethods", context)).toEqual([ "paypal", "creditcard", ]); expect(sdk.getVariable("test", "flatConfig", context)).toEqual({ key: "value", }); expect(sdk.getVariableObject("test", "flatConfig", context)).toEqual({ key: "value", }); expect(sdk.getVariable("test", "nestedConfig", context)).toEqual({ key: { nested: "value", }, }); expect(sdk.getVariableJSON("test", "nestedConfig", context)).toEqual({ key: { nested: "value", }, }); // non existing expect(sdk.getVariable("test", "nonExisting", context)).toEqual(null); expect(sdk.getVariable("nonExistingFeature", "nonExisting", context)).toEqual(null); // disabled expect(sdk.getVariable("test", "color", { userId: "user-gb" })).toEqual(null); }); it("should get variables without any variations", function () { const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", segments: { netherlands: { key: "netherlands", conditions: JSON.stringify([ { attribute: "country", operator: "equals", value: "nl", }, ]), }, }, features: { test: { key: "test", bucketBy: "userId", variablesSchema: { color: { key: "color", type: "string", defaultValue: "red", }, }, traffic: [ { key: "1", segments: "netherlands", percentage: 100000, variables: { color: "orange", }, allocation: [], }, { key: "2", segments: "*", percentage: 100000, allocation: [], }, ], }, }, }, }); const defaultContext = { userId: "123", }; // test default value expect( sdk.getVariable("test", "color", { ...defaultContext, }), ).toEqual("red"); // test override expect( sdk.getVariable("test", "color", { ...defaultContext, country: "nl", }), ).toEqual("orange"); }); it("should apply rule variableOverrides on top of rule variables", function () { const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", segments: { germany: { key: "germany", conditions: JSON.stringify([ { attribute: "country", operator: "equals", value: "de", }, ]), }, mobile: { key: "mobile", conditions: JSON.stringify([ { attribute: "device", operator: "equals", value: "mobile", }, ]), }, }, features: { test: { key: "test", bucketBy: "userId", variablesSchema: { config: { key: "config", type: "object", defaultValue: { source: "default", nested: { value: 0 }, }, }, }, traffic: [ { key: "germany", segments: "germany", percentage: 100000, variables: { config: { source: "rule", nested: { value: 10 }, flag: true, }, }, variableOverrides: { config: [ { segments: "mobile", value: { source: "rule", nested: { value: 20 }, flag: true, }, }, { conditions: [ { attribute: "country", operator: "equals", value: "de", }, ], value: { source: "rule", nested: { value: 30 }, flag: true, }, }, ], }, }, { key: "everyone", segments: "*", percentage: 100000, variables: { config: { source: "everyone", nested: { value: 1 }, }, }, }, ], }, }, }, }); expect( sdk.getVariableObject("test", "config", { userId: "user-1", country: "de", }), ).toEqual({ source: "rule", nested: { value: 30 }, flag: true, }); expect( sdk.getVariableObject("test", "config", { userId: "user-1", country: "de", device: "mobile", }), ).toEqual({ source: "rule", nested: { value: 20 }, flag: true, }); expect( sdk.getVariableObject("test", "config", { userId: "user-1", country: "nl", }), ).toEqual({ source: "everyone", nested: { value: 1 }, }); }); describe("array and object variables", function () { const arrayAndObjectDatafile: DatafileContent = { schemaVersion: "2", revision: "1.0", features: { withArray: { key: "withArray", bucketBy: "userId", variablesSchema: { simpleArray: { key: "simpleArray", type: "array", defaultValue: ["red", "blue", "green"], }, simpleStringArray: { key: "simpleStringArray", type: "array", defaultValue: ["red", "blue", "green"], }, objectArray: { key: "objectArray", type: "array", defaultValue: [ { color: "red", opacity: 100 }, { color: "blue", opacity: 90 }, { color: "green", opacity: 95 }, ], }, }, traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [], }, ], }, withObject: { key: "withObject", bucketBy: "userId", variablesSchema: { themeConfig: { key: "themeConfig", type: "object", defaultValue: { theme: "light", darkMode: false, }, }, layoutConfig: { key: "layoutConfig", type: "object", defaultValue: { width: 1200, align: "center", }, }, headerConfig: { key: "headerConfig", type: "object", defaultValue: { style: { fontSize: 18, bold: true }, title: "Welcome", }, }, contentConfig: { key: "contentConfig", type: "object", defaultValue: { tags: ["news", "featured"], count: 2, }, }, panelConfig: { key: "panelConfig", type: "object", defaultValue: { sections: [ { id: "hero", visible: true }, { id: "sidebar", visible: false }, ], }, }, deepConfig: { key: "deepConfig", type: "object", defaultValue: { level1: { level2: { value: "deep", count: 1, }, }, }, }, mixedConfig: { key: "mixedConfig", type: "object", defaultValue: { name: "mixed", enabled: true, meta: { score: 0.95, items: ["a", "b"], }, }, }, }, traffic: [ { key: "1", segments: "*", percentage: 100000, allocation: [], }, ], }, }, segments: {}, }; const context = { userId: "user-1" }; it("should get array variables via getVariable and getVariableArray (no generics)", function () { const sdk = createInstance({ datafile: arrayAndObjectDatafile }); const simpleArray = sdk.getVariable("withArray", "simpleArray", context); expect(simpleArray).toEqual(["red", "blue", "green"]); const simpleArrayTyped = sdk.getVariableArray("withArray", "simpleArray", context); expect(simpleArrayTyped).toEqual(["red", "blue", "green"]); const simpleStringArray = sdk.getVariable("withArray", "simpleStringArray", context); expect(simpleStringArray).toEqual(["red", "blue", "green"]); const simpleStringArrayTyped = sdk.getVariableArray( "withArray", "simpleStringArray", context, ); expect(simpleStringArrayTyped).toEqual(["red", "blue", "green"]); const objectArray = sdk.getVariable("withArray", "objectArray", context); expect(objectArray).toEqual([ { color: "red", opacity: 100 }, { color: "blue", opacity: 90 }, { color: "green", opacity: 95 }, ]); const objectArrayTyped = sdk.getVariableArray("withArray", "objectArray", context); expect(objectArrayTyped).toEqual([ { color: "red", opacity: 100 }, { color: "blue", opacity: 90 }, { color: "green", opacity: 95 }, ]); }); it("should get array variables with generics for type safety", function () { const sdk = createInstance({ datafile: arrayAndObjectDatafile }); const stringArray = sdk.getVariableArray("withArray", "simpleArray", context); expect(stringArray).toEqual(["red", "blue", "green"]); if (stringArray) { const first: string = stringArray[0]; expect(first).toBe("red"); } interface ColorOpacity { color: string; opacity: number; } const objectArray = sdk.getVariableArray("withArray", "objectArray", context); expect(objectArray).toEqual([ { color: "red", opacity: 100 }, { color: "blue", opacity: 90 }, { color: "green", opacity: 95 }, ]); if (objectArray && objectArray.length > 0) { const first: ColorOpacity = objectArray[0]; expect(first.color).toBe("red"); expect(first.opacity).toBe(100); } }); it("should get object variables via getVariable and getVariableObject (no generics)", function () { const sdk = createInstance({ datafile: arrayAndObjectDatafile }); const themeConfig = sdk.getVariable("withObject", "themeConfig", context); expect(themeConfig).toEqual({ theme: "light", darkMode: false }); const themeConfigTyped = sdk.getVariableObject("withObject", "themeConfig", context); expect(themeConfigTyped).toEqual({ theme: "light", darkMode: false }); const layoutConfig = sdk.getVariable("withObject", "layoutConfig", context); expect(layoutConfig).toEqual({ width: 1200, align: "center" }); const headerConfig = sdk.getVariable("withObject", "headerConfig", context); expect(headerConfig).toEqual({ style: { fontSize: 18, bold: true }, title: "Welcome", }); const contentConfig = sdk.getVariable("withObject", "contentConfig", context); expect(contentConfig).toEqual({ tags: ["news", "featured"], count: 2, }); const panelConfig = sdk.getVariableObject("withObject", "panelConfig", context); expect(panelConfig).toEqual({ sections: [ { id: "hero", visible: true }, { id: "sidebar", visible: false }, ], }); const deepConfig = sdk.getVariableObject("withObject", "deepConfig", context); expect(deepConfig).toEqual({ level1: { level2: { value: "deep", count: 1, }, }, }); const mixedConfig = sdk.getVariableObject("withObject", "mixedConfig", context); expect(mixedConfig).toEqual({ name: "mixed", enabled: true, meta: { score: 0.95, items: ["a", "b"], }, }); }); it("should get object variables with generics for type safety", function () { const sdk = createInstance({ datafile: arrayAndObjectDatafile }); interface ThemeConfig { theme: string; darkMode: boolean; } const themeConfig = sdk.getVariableObject("withObject", "themeConfig", context); expect(themeConfig).toEqual({ theme: "light", darkMode: false }); if (themeConfig) { expect(themeConfig.theme).toBe("light"); expect(themeConfig.darkMode).toBe(false); } interface HeaderConfig { style: { fontSize: number; bold: boolean }; title: string; } const headerConfig = sdk.getVariableObject( "withObject", "headerConfig", context, ); expect(headerConfig).toEqual({ style: { fontSize: 18, bold: true }, title: "Welcome", }); if (headerConfig) { expect(headerConfig.style.fontSize).toBe(18); expect(headerConfig.title).toBe("Welcome"); } interface PanelSection { id: string; visible: boolean; } interface PanelConfig { sections: PanelSection[]; } const panelConfig = sdk.getVariableObject("withObject", "panelConfig", context); expect(panelConfig?.sections).toHaveLength(2); expect(panelConfig?.sections[0]).toEqual({ id: "hero", visible: true }); }); it("should return null for getVariableArray and getVariableObject when variable or feature missing", function () { const sdk = createInstance({ datafile: arrayAndObjectDatafile }); expect(sdk.getVariableArray("withArray", "nonExisting", context)).toBeNull(); expect(sdk.getVariableObject("withObject", "nonExisting", context)).toBeNull(); expect(sdk.getVariableArray("nonExistingFeature", "simpleArray", context)).toBeNull(); expect(sdk.getVariableObject("nonExistingFeature", "themeConfig", context)).toBeNull(); }); it("should include array and object variables in getAllEvaluations", function () { const sdk = createInstance({ datafile: arrayAndObjectDatafile }); const all = sdk.getAllEvaluations(context); expect(all.withArray).toBeDefined(); expect(all.withArray?.enabled).toBe(true); expect(all.withArray?.variables?.simpleArray).toEqual(["red", "blue", "green"]); expect(all.withArray?.variables?.simpleStringArray).toEqual(["red", "blue", "green"]); expect(all.withArray?.variables?.objectArray).toEqual([ { color: "red", opacity: 100 }, { color: "blue", opacity: 90 }, { color: "green", opacity: 95 }, ]); expect(all.withObject).toBeDefined(); expect(all.withObject?.enabled).toBe(true); expect(all.withObject?.variables?.themeConfig).toEqual({ theme: "light", darkMode: false, }); expect(all.withObject?.variables?.headerConfig).toEqual({ style: { fontSize: 18, bold: true }, title: "Welcome", }); expect(all.withObject?.variables?.mixedConfig).toEqual({ name: "mixed", enabled: true, meta: { score: 0.95, items: ["a", "b"] }, }); }); }); it("should check if enabled for individually named segments", function () { const sdk = createInstance({ datafile: { schemaVersion: "2", revision: "1.0", features: { test: { key: "test", bucketBy: "userId", traffic: [ { key: "1", segments: "netherlands", percentage: 100000, allocation: [] }, { key: "2", segments: JSON.stringify(["iphone", "unitedStates"]), percentage: 100000, allocation: [], }, ], }, }, segments: { netherlands: { key: "netherlands", conditions: JSON.stringify([ { attribute: "country", operator: "equals", value: "nl", }, ]), }, iphone: { key: "iphone", conditions: JSON.stringify([ { attribute: "device", operator: "equals", value: "iphone", }, ]), }, unitedStates: { key: "unitedStates", conditions: JSON.stringify([ { attribute: "country", operator: "equals", value: "us", }, ]), }, }, }, }); expect(sdk.isEnabled("test")).toEqual(false); expect(sdk.isEnabled("test", { userId: "123" })).toEqual(false); expect(sdk.isEnabled("test", { userId: "123", country: "de" })).toEqual(false); expect(sdk.isEnabled("test", { userId: "123", country: "us" })).toEqual(false); expect(sdk.isEnabled("test", { userId: "123", country: "nl" })).toEqual(true); expect(sdk.isEnabled("test", { userId: "123", country: "us", device: "iphone" })).toEqual(true); }); });