@featurevisor/sdk 1.35.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +2 -381
- package/coverage/clover.xml +707 -643
- package/coverage/coverage-final.json +11 -9
- package/coverage/lcov-report/{segments.ts.html → bucketer.ts.html} +155 -77
- package/coverage/lcov-report/child.ts.html +940 -0
- package/coverage/lcov-report/conditions.ts.html +107 -158
- package/coverage/lcov-report/datafileReader.ts.html +763 -103
- package/coverage/lcov-report/emitter.ts.html +77 -59
- package/coverage/lcov-report/evaluate.ts.html +689 -416
- package/coverage/lcov-report/events.ts.html +334 -0
- package/coverage/lcov-report/helpers.ts.html +184 -0
- package/coverage/lcov-report/{feature.ts.html → hooks.ts.html} +90 -237
- package/coverage/lcov-report/index.html +119 -89
- package/coverage/lcov-report/instance.ts.html +341 -773
- package/coverage/lcov-report/logger.ts.html +64 -64
- package/coverage/lcov.info +1433 -1223
- package/dist/bucketer.d.ts +11 -0
- package/dist/child.d.ts +26 -0
- package/dist/compareVersions.d.ts +4 -0
- package/dist/conditions.d.ts +4 -4
- package/dist/datafileReader.d.ts +26 -6
- package/dist/emitter.d.ts +8 -9
- package/dist/evaluate.d.ts +31 -29
- package/dist/events.d.ts +5 -0
- package/dist/helpers.d.ts +5 -0
- package/dist/hooks.d.ts +45 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.gz +0 -0
- package/dist/index.mjs.map +1 -1
- package/dist/instance.d.ts +40 -72
- package/dist/logger.d.ts +6 -5
- package/dist/murmurhash.d.ts +1 -0
- package/jest.config.js +2 -0
- package/lib/bucketer.d.ts +11 -0
- package/lib/child.d.ts +26 -0
- package/lib/compareVersions.d.ts +4 -0
- package/lib/conditions.d.ts +4 -4
- package/lib/datafileReader.d.ts +26 -6
- package/lib/emitter.d.ts +8 -9
- package/lib/evaluate.d.ts +31 -29
- package/lib/events.d.ts +5 -0
- package/lib/helpers.d.ts +5 -0
- package/lib/hooks.d.ts +45 -0
- package/lib/index.d.ts +3 -2
- package/lib/instance.d.ts +40 -72
- package/lib/logger.d.ts +6 -5
- package/lib/murmurhash.d.ts +1 -0
- package/package.json +3 -5
- package/src/bucketer.spec.ts +165 -0
- package/src/bucketer.ts +84 -0
- package/src/child.spec.ts +267 -0
- package/src/child.ts +285 -0
- package/src/compareVersions.ts +93 -0
- package/src/conditions.spec.ts +563 -353
- package/src/conditions.ts +46 -63
- package/src/datafileReader.spec.ts +396 -84
- package/src/datafileReader.ts +280 -60
- package/src/emitter.spec.ts +27 -86
- package/src/emitter.ts +38 -32
- package/src/evaluate.ts +349 -258
- package/src/events.spec.ts +154 -0
- package/src/events.ts +83 -0
- package/src/helpers.ts +33 -0
- package/src/hooks.ts +88 -0
- package/src/index.ts +3 -2
- package/src/instance.spec.ts +305 -489
- package/src/instance.ts +247 -391
- package/src/logger.spec.ts +212 -134
- package/src/logger.ts +36 -36
- package/src/murmurhash.ts +71 -0
- package/coverage/lcov-report/bucket.ts.html +0 -502
- package/dist/bucket.d.ts +0 -30
- package/dist/feature.d.ts +0 -16
- package/dist/segments.d.ts +0 -5
- package/lib/bucket.d.ts +0 -30
- package/lib/feature.d.ts +0 -16
- package/lib/segments.d.ts +0 -5
- package/src/bucket.spec.ts +0 -37
- package/src/bucket.ts +0 -139
- package/src/feature.ts +0 -137
- package/src/segments.spec.ts +0 -468
- package/src/segments.ts +0 -58
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@featurevisor/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Featurevisor SDK for Node.js and the browser",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -49,9 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"license": "MIT",
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@featurevisor/types": "
|
|
53
|
-
"compare-versions": "^6.0.0-rc.1",
|
|
54
|
-
"murmurhash": "^2.0.1"
|
|
52
|
+
"@featurevisor/types": "2.0.0"
|
|
55
53
|
},
|
|
56
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "9817e05a07735294c750ee921991509b67015afd"
|
|
57
55
|
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { getBucketedNumber, MAX_BUCKETED_NUMBER, getBucketKey } from "./bucketer";
|
|
2
|
+
import { createLogger } from "./logger";
|
|
3
|
+
|
|
4
|
+
describe("sdk: Bucket", function () {
|
|
5
|
+
describe("getBucketedNumber", function () {
|
|
6
|
+
it("should be a function", function () {
|
|
7
|
+
expect(typeof getBucketedNumber).toEqual("function");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should return a number between 0 and 100000", function () {
|
|
11
|
+
const keys = ["foo", "bar", "baz", "123adshlk348-93asdlk"];
|
|
12
|
+
|
|
13
|
+
keys.forEach((key) => {
|
|
14
|
+
const n = getBucketedNumber(key);
|
|
15
|
+
|
|
16
|
+
expect(n >= 0).toEqual(true);
|
|
17
|
+
expect(n <= MAX_BUCKETED_NUMBER).toEqual(true);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// these assertions will be copied to unit tests of SDKs ported to other languages,
|
|
22
|
+
// so we can keep consistent bucketing across all SDKs
|
|
23
|
+
it("should return expected number for known keys", function () {
|
|
24
|
+
const expectedResults = {
|
|
25
|
+
foo: 20602,
|
|
26
|
+
bar: 89144,
|
|
27
|
+
"123.foo": 3151,
|
|
28
|
+
"123.bar": 9710,
|
|
29
|
+
"123.456.foo": 14432,
|
|
30
|
+
"123.456.bar": 1982,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
Object.keys(expectedResults).forEach((key) => {
|
|
34
|
+
const n = getBucketedNumber(key);
|
|
35
|
+
|
|
36
|
+
expect(n).toEqual(expectedResults[key]);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("getBucketKey", function () {
|
|
42
|
+
const logger = createLogger({
|
|
43
|
+
level: "warn",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should be a function", function () {
|
|
47
|
+
expect(typeof getBucketKey).toEqual("function");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("plain: should return a bucket key for a plain bucketBy", function () {
|
|
51
|
+
const featureKey = "test-feature";
|
|
52
|
+
const bucketBy = "userId";
|
|
53
|
+
const context = { userId: "123", browser: "chrome" };
|
|
54
|
+
|
|
55
|
+
const bucketKey = getBucketKey({
|
|
56
|
+
featureKey,
|
|
57
|
+
bucketBy,
|
|
58
|
+
context,
|
|
59
|
+
logger,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(bucketKey).toEqual("123.test-feature");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("plain: should return a bucket key with feature key only if value is missing in context", function () {
|
|
66
|
+
const featureKey = "test-feature";
|
|
67
|
+
const bucketBy = "userId";
|
|
68
|
+
const context = { browser: "chrome" };
|
|
69
|
+
|
|
70
|
+
const bucketKey = getBucketKey({
|
|
71
|
+
featureKey,
|
|
72
|
+
bucketBy,
|
|
73
|
+
context,
|
|
74
|
+
logger,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(bucketKey).toEqual("test-feature");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("and: should combine multiple field values together if present", function () {
|
|
81
|
+
const featureKey = "test-feature";
|
|
82
|
+
const bucketBy = ["organizationId", "userId"];
|
|
83
|
+
const context = { organizationId: "123", userId: "234", browser: "chrome" };
|
|
84
|
+
|
|
85
|
+
const bucketKey = getBucketKey({
|
|
86
|
+
featureKey,
|
|
87
|
+
bucketBy,
|
|
88
|
+
context,
|
|
89
|
+
logger,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(bucketKey).toEqual("123.234.test-feature");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("and: should combine only available field values together if present", function () {
|
|
96
|
+
const featureKey = "test-feature";
|
|
97
|
+
const bucketBy = ["organizationId", "userId"];
|
|
98
|
+
const context = { organizationId: "123", browser: "chrome" };
|
|
99
|
+
|
|
100
|
+
const bucketKey = getBucketKey({
|
|
101
|
+
featureKey,
|
|
102
|
+
bucketBy,
|
|
103
|
+
context,
|
|
104
|
+
logger,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(bucketKey).toEqual("123.test-feature");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("and: should combine all available fields, with dot separated paths", function () {
|
|
111
|
+
const featureKey = "test-feature";
|
|
112
|
+
const bucketBy = ["organizationId", "user.id"];
|
|
113
|
+
const context = {
|
|
114
|
+
organizationId: "123",
|
|
115
|
+
user: {
|
|
116
|
+
id: "234",
|
|
117
|
+
},
|
|
118
|
+
browser: "chrome",
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const bucketKey = getBucketKey({
|
|
122
|
+
featureKey,
|
|
123
|
+
bucketBy,
|
|
124
|
+
context,
|
|
125
|
+
logger,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(bucketKey).toEqual("123.234.test-feature");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("or: should take first available field value", function () {
|
|
132
|
+
const featureKey = "test-feature";
|
|
133
|
+
const bucketBy = {
|
|
134
|
+
or: ["userId", "deviceId"],
|
|
135
|
+
};
|
|
136
|
+
const context = { deviceId: "deviceIdHere", userId: "234", browser: "chrome" };
|
|
137
|
+
|
|
138
|
+
const bucketKey = getBucketKey({
|
|
139
|
+
featureKey,
|
|
140
|
+
bucketBy,
|
|
141
|
+
context,
|
|
142
|
+
logger,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(bucketKey).toEqual("234.test-feature");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("or: should take first available field value", function () {
|
|
149
|
+
const featureKey = "test-feature";
|
|
150
|
+
const bucketBy = {
|
|
151
|
+
or: ["userId", "deviceId"],
|
|
152
|
+
};
|
|
153
|
+
const context = { deviceId: "deviceIdHere", browser: "chrome" };
|
|
154
|
+
|
|
155
|
+
const bucketKey = getBucketKey({
|
|
156
|
+
featureKey,
|
|
157
|
+
bucketBy,
|
|
158
|
+
context,
|
|
159
|
+
logger,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(bucketKey).toEqual("deviceIdHere.test-feature");
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
package/src/bucketer.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { BucketKey, Context, AttributeValue, FeatureKey, BucketBy } from "@featurevisor/types";
|
|
2
|
+
|
|
3
|
+
import { Logger } from "./logger";
|
|
4
|
+
import { getValueFromContext } from "./conditions";
|
|
5
|
+
import { MurmurHashV3 } from "./murmurhash";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generic hashing
|
|
9
|
+
*/
|
|
10
|
+
const HASH_SEED = 1;
|
|
11
|
+
const MAX_HASH_VALUE = Math.pow(2, 32);
|
|
12
|
+
|
|
13
|
+
export const MAX_BUCKETED_NUMBER = 100000; // 100% * 1000 to include three decimal places in the same integer value
|
|
14
|
+
|
|
15
|
+
export function getBucketedNumber(bucketKey: string): number {
|
|
16
|
+
const hashValue = MurmurHashV3(bucketKey, HASH_SEED);
|
|
17
|
+
const ratio = hashValue / MAX_HASH_VALUE;
|
|
18
|
+
|
|
19
|
+
return Math.floor(ratio * MAX_BUCKETED_NUMBER);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Bucket key
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_BUCKET_KEY_SEPARATOR = ".";
|
|
26
|
+
|
|
27
|
+
export interface GetBucketKeyOptions {
|
|
28
|
+
featureKey: FeatureKey;
|
|
29
|
+
bucketBy: BucketBy;
|
|
30
|
+
context: Context;
|
|
31
|
+
|
|
32
|
+
logger: Logger;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getBucketKey(options: GetBucketKeyOptions): BucketKey {
|
|
36
|
+
const {
|
|
37
|
+
featureKey,
|
|
38
|
+
bucketBy,
|
|
39
|
+
context,
|
|
40
|
+
|
|
41
|
+
logger,
|
|
42
|
+
} = options;
|
|
43
|
+
|
|
44
|
+
let type;
|
|
45
|
+
let attributeKeys;
|
|
46
|
+
|
|
47
|
+
if (typeof bucketBy === "string") {
|
|
48
|
+
type = "plain";
|
|
49
|
+
attributeKeys = [bucketBy];
|
|
50
|
+
} else if (Array.isArray(bucketBy)) {
|
|
51
|
+
type = "and";
|
|
52
|
+
attributeKeys = bucketBy;
|
|
53
|
+
} else if (typeof bucketBy === "object" && Array.isArray(bucketBy.or)) {
|
|
54
|
+
type = "or";
|
|
55
|
+
attributeKeys = bucketBy.or;
|
|
56
|
+
} else {
|
|
57
|
+
logger.error("invalid bucketBy", { featureKey, bucketBy });
|
|
58
|
+
|
|
59
|
+
throw new Error("invalid bucketBy");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const bucketKey: AttributeValue[] = [];
|
|
63
|
+
|
|
64
|
+
attributeKeys.forEach((attributeKey) => {
|
|
65
|
+
const attributeValue = getValueFromContext(context, attributeKey);
|
|
66
|
+
|
|
67
|
+
if (typeof attributeValue === "undefined") {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (type === "plain" || type === "and") {
|
|
72
|
+
bucketKey.push(attributeValue);
|
|
73
|
+
} else {
|
|
74
|
+
// or
|
|
75
|
+
if (bucketKey.length === 0) {
|
|
76
|
+
bucketKey.push(attributeValue);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
bucketKey.push(featureKey);
|
|
82
|
+
|
|
83
|
+
return bucketKey.join(DEFAULT_BUCKET_KEY_SEPARATOR);
|
|
84
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { createInstance } from "./instance";
|
|
2
|
+
|
|
3
|
+
describe("sdk: child", function () {
|
|
4
|
+
it("should create a child instance", function () {
|
|
5
|
+
const f = createInstance({
|
|
6
|
+
datafile: {
|
|
7
|
+
schemaVersion: "2",
|
|
8
|
+
revision: "1.0",
|
|
9
|
+
features: {
|
|
10
|
+
test: {
|
|
11
|
+
key: "test",
|
|
12
|
+
bucketBy: "userId",
|
|
13
|
+
variablesSchema: {
|
|
14
|
+
color: {
|
|
15
|
+
key: "color",
|
|
16
|
+
type: "string",
|
|
17
|
+
defaultValue: "red",
|
|
18
|
+
},
|
|
19
|
+
showSidebar: {
|
|
20
|
+
key: "showSidebar",
|
|
21
|
+
type: "boolean",
|
|
22
|
+
defaultValue: false,
|
|
23
|
+
},
|
|
24
|
+
sidebarTitle: {
|
|
25
|
+
key: "sidebarTitle",
|
|
26
|
+
type: "string",
|
|
27
|
+
defaultValue: "sidebar title",
|
|
28
|
+
},
|
|
29
|
+
count: {
|
|
30
|
+
key: "count",
|
|
31
|
+
type: "integer",
|
|
32
|
+
defaultValue: 0,
|
|
33
|
+
},
|
|
34
|
+
price: {
|
|
35
|
+
key: "price",
|
|
36
|
+
type: "double",
|
|
37
|
+
defaultValue: 9.99,
|
|
38
|
+
},
|
|
39
|
+
paymentMethods: {
|
|
40
|
+
key: "paymentMethods",
|
|
41
|
+
type: "array",
|
|
42
|
+
defaultValue: ["paypal", "creditcard"],
|
|
43
|
+
},
|
|
44
|
+
flatConfig: {
|
|
45
|
+
key: "flatConfig",
|
|
46
|
+
type: "object",
|
|
47
|
+
defaultValue: {
|
|
48
|
+
key: "value",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
nestedConfig: {
|
|
52
|
+
key: "nestedConfig",
|
|
53
|
+
type: "json",
|
|
54
|
+
defaultValue: JSON.stringify({
|
|
55
|
+
key: {
|
|
56
|
+
nested: "value",
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
variations: [
|
|
62
|
+
{ value: "control" },
|
|
63
|
+
{
|
|
64
|
+
value: "treatment",
|
|
65
|
+
variables: {
|
|
66
|
+
showSidebar: true,
|
|
67
|
+
sidebarTitle: "sidebar title from variation",
|
|
68
|
+
},
|
|
69
|
+
variableOverrides: {
|
|
70
|
+
showSidebar: [
|
|
71
|
+
{
|
|
72
|
+
segments: ["netherlands"],
|
|
73
|
+
value: false,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
conditions: [
|
|
77
|
+
{
|
|
78
|
+
attribute: "country",
|
|
79
|
+
operator: "equals",
|
|
80
|
+
value: "de",
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
value: false,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
sidebarTitle: [
|
|
87
|
+
{
|
|
88
|
+
segments: ["netherlands"],
|
|
89
|
+
value: "Dutch title",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
conditions: [
|
|
93
|
+
{
|
|
94
|
+
attribute: "country",
|
|
95
|
+
operator: "equals",
|
|
96
|
+
value: "de",
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
value: "German title",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
force: [
|
|
106
|
+
{
|
|
107
|
+
conditions: [{ attribute: "userId", operator: "equals", value: "user-ch" }],
|
|
108
|
+
enabled: true,
|
|
109
|
+
variation: "control",
|
|
110
|
+
variables: {
|
|
111
|
+
color: "red and white",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
conditions: [{ attribute: "userId", operator: "equals", value: "user-gb" }],
|
|
116
|
+
enabled: false,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
conditions: [
|
|
120
|
+
{ attribute: "userId", operator: "equals", value: "user-forced-variation" },
|
|
121
|
+
],
|
|
122
|
+
enabled: true,
|
|
123
|
+
variation: "treatment",
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
traffic: [
|
|
127
|
+
// belgium
|
|
128
|
+
{
|
|
129
|
+
key: "2",
|
|
130
|
+
segments: ["belgium"],
|
|
131
|
+
percentage: 100000,
|
|
132
|
+
allocation: [
|
|
133
|
+
{ variation: "control", range: [0, 0] },
|
|
134
|
+
{
|
|
135
|
+
variation: "treatment",
|
|
136
|
+
range: [0, 100000],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
variation: "control",
|
|
140
|
+
variables: {
|
|
141
|
+
color: "black",
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// everyone
|
|
146
|
+
{
|
|
147
|
+
key: "1",
|
|
148
|
+
segments: "*",
|
|
149
|
+
percentage: 100000,
|
|
150
|
+
allocation: [
|
|
151
|
+
{ variation: "control", range: [0, 0] },
|
|
152
|
+
{
|
|
153
|
+
variation: "treatment",
|
|
154
|
+
range: [0, 100000],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
anotherTest: {
|
|
161
|
+
key: "test",
|
|
162
|
+
bucketBy: "userId",
|
|
163
|
+
traffic: [
|
|
164
|
+
// everyone
|
|
165
|
+
{
|
|
166
|
+
key: "1",
|
|
167
|
+
segments: "*",
|
|
168
|
+
percentage: 100000,
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
segments: {
|
|
174
|
+
netherlands: {
|
|
175
|
+
key: "netherlands",
|
|
176
|
+
conditions: JSON.stringify([
|
|
177
|
+
{
|
|
178
|
+
attribute: "country",
|
|
179
|
+
operator: "equals",
|
|
180
|
+
value: "nl",
|
|
181
|
+
},
|
|
182
|
+
]),
|
|
183
|
+
},
|
|
184
|
+
belgium: {
|
|
185
|
+
key: "belgium",
|
|
186
|
+
conditions: JSON.stringify([
|
|
187
|
+
{
|
|
188
|
+
attribute: "country",
|
|
189
|
+
operator: "equals",
|
|
190
|
+
value: "be",
|
|
191
|
+
},
|
|
192
|
+
]),
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
context: {
|
|
197
|
+
appVersion: "1.0.0",
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(f).toBeDefined();
|
|
202
|
+
expect(f.getContext()).toEqual({ appVersion: "1.0.0" });
|
|
203
|
+
|
|
204
|
+
const childF = f.spawn({
|
|
205
|
+
userId: "123",
|
|
206
|
+
country: "nl",
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
expect(childF).toBeDefined();
|
|
210
|
+
expect(childF.getContext()).toEqual({ appVersion: "1.0.0", userId: "123", country: "nl" });
|
|
211
|
+
|
|
212
|
+
let contextUpdated = false;
|
|
213
|
+
const unsubscribeContext = childF.on("context_set", () => {
|
|
214
|
+
contextUpdated = true;
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
childF.setContext({ country: "be" });
|
|
218
|
+
expect(childF.getContext()).toEqual({ appVersion: "1.0.0", userId: "123", country: "be" });
|
|
219
|
+
|
|
220
|
+
expect(childF.isEnabled("test")).toBe(true);
|
|
221
|
+
expect(childF.getVariation("test")).toEqual("control");
|
|
222
|
+
|
|
223
|
+
expect(childF.getVariable("test", "color")).toEqual("black");
|
|
224
|
+
expect(childF.getVariableString("test", "color")).toEqual("black");
|
|
225
|
+
|
|
226
|
+
expect(childF.getVariable("test", "showSidebar")).toEqual(false);
|
|
227
|
+
expect(childF.getVariableBoolean("test", "showSidebar")).toEqual(false);
|
|
228
|
+
|
|
229
|
+
expect(childF.getVariable("test", "sidebarTitle")).toEqual("sidebar title");
|
|
230
|
+
expect(childF.getVariableString("test", "sidebarTitle")).toEqual("sidebar title");
|
|
231
|
+
|
|
232
|
+
expect(childF.getVariable("test", "count")).toEqual(0);
|
|
233
|
+
expect(childF.getVariableInteger("test", "count")).toEqual(0);
|
|
234
|
+
|
|
235
|
+
expect(childF.getVariable("test", "price")).toEqual(9.99);
|
|
236
|
+
expect(childF.getVariableDouble("test", "price")).toEqual(9.99);
|
|
237
|
+
|
|
238
|
+
expect(childF.getVariable("test", "paymentMethods")).toEqual(["paypal", "creditcard"]);
|
|
239
|
+
expect(childF.getVariableArray("test", "paymentMethods")).toEqual(["paypal", "creditcard"]);
|
|
240
|
+
|
|
241
|
+
expect(childF.getVariable("test", "flatConfig")).toEqual({ key: "value" });
|
|
242
|
+
expect(childF.getVariableObject("test", "flatConfig")).toEqual({ key: "value" });
|
|
243
|
+
|
|
244
|
+
expect(childF.getVariable("test", "nestedConfig")).toEqual({
|
|
245
|
+
key: { nested: "value" },
|
|
246
|
+
});
|
|
247
|
+
expect(childF.getVariableJSON("test", "nestedConfig")).toEqual({
|
|
248
|
+
key: { nested: "value" },
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
expect(contextUpdated).toBe(true);
|
|
252
|
+
unsubscribeContext();
|
|
253
|
+
|
|
254
|
+
expect(childF.isEnabled("newFeature")).toEqual(false);
|
|
255
|
+
childF.setSticky({
|
|
256
|
+
newFeature: {
|
|
257
|
+
enabled: true,
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
expect(childF.isEnabled("newFeature")).toEqual(true);
|
|
261
|
+
|
|
262
|
+
const allEvaluations = childF.getAllEvaluations();
|
|
263
|
+
expect(Object.keys(allEvaluations)).toEqual(["test", "anotherTest"]);
|
|
264
|
+
|
|
265
|
+
childF.close();
|
|
266
|
+
});
|
|
267
|
+
});
|