feature-toggle-api 3.4.2 → 4.1.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.
Files changed (36) hide show
  1. package/README.md +129 -103
  2. package/dist/feature-toggle.d.ts +134 -0
  3. package/{src/index.js → dist/feature-toggle.js} +193 -114
  4. package/dist/feature-toggle.min.cjs +2 -0
  5. package/dist/feature-toggle.min.cjs.map +1 -0
  6. package/dist/feature-toggle.min.js +2 -0
  7. package/dist/feature-toggle.min.js.map +1 -0
  8. package/dist/feature-toggle.umd.min.js +2 -0
  9. package/dist/feature-toggle.umd.min.js.map +1 -0
  10. package/dist/html-plugin.umd.min.js +2 -0
  11. package/dist/html-plugin.umd.min.js.map +1 -0
  12. package/dist/url-plugin.umd.min.js +2 -0
  13. package/dist/url-plugin.umd.min.js.map +1 -0
  14. package/examples/01_basic_esmodule.js +10 -0
  15. package/examples/02_basic_cjsmodule.cjs +6 -0
  16. package/examples/{basic.html → 03_basic_scripttag.html} +3 -2
  17. package/examples/{example-htmlplugin.html → 04_example-htmlplugin.html} +2 -2
  18. package/examples/{example-urlplugin.html → 05_example-urlplugin.html} +2 -2
  19. package/examples/{withListener.html → 06_withListener.html} +1 -1
  20. package/package.json +32 -43
  21. package/rollup.config.js +107 -0
  22. package/src/featureToggle.ts +466 -0
  23. package/src/index.ts +10 -0
  24. package/src/plugins/htmlplugin/{plugin-html.js → plugin-html.ts} +22 -10
  25. package/src/plugins/urlplugin/{plugin-url.js → plugin-url.ts} +36 -10
  26. package/tests/featuretoggle.test.ts +450 -0
  27. package/tests/polyfills.ts +20 -0
  28. package/tsconfig.cjs.json +12 -0
  29. package/tsconfig.json +12 -0
  30. package/feature-toggle-api.js +0 -243
  31. package/feature-toggle-api.min.js +0 -1
  32. package/feature-toggle-api.module.js +0 -243
  33. package/spec/support/index.spec.js +0 -378
  34. package/spec/support/jasmine.json +0 -11
  35. package/spec/support/polyfills.js +0 -6
  36. package/src/index.cjs.js +0 -8
@@ -0,0 +1,450 @@
1
+ import { describe, it, } from "node:test";
2
+ import assert from "node:assert";
3
+ import {useWindowMock} from './polyfills';
4
+ import { useFeatureToggle, urlPlugin } from '../src/';
5
+
6
+ describe("Initialisation / Basic Tests", function() {
7
+ it("should return a function", function() {
8
+ const type = typeof useFeatureToggle;
9
+ assert.strictEqual(type,'function');
10
+ });
11
+
12
+ it("should return 'feature-toggle-api' for property name", function() {
13
+ const api = useFeatureToggle();
14
+ assert.strictEqual(api.name,'feature-toggle-api');
15
+ });
16
+
17
+ it("should return true if feature is initialized in constructor", function() {
18
+ const api = useFeatureToggle({ feature: true });
19
+ assert.strictEqual(api.isActive('feature'),true);
20
+ });
21
+
22
+ it("should return true if feature with variant is initialized in constructor", function() {
23
+ const api = useFeatureToggle({ "feature#variant": true });
24
+ assert.strictEqual(api.isActive('feature','variant'),true);
25
+ });
26
+
27
+ it("should return true if feature with variant is initialized in constructor with function", function() {
28
+ const api = useFeatureToggle({ "feature#variant": true });
29
+ assert.strictEqual(api.isActive('feature','variant'),true);
30
+ });
31
+ });
32
+
33
+ describe("Basic visibility of features", function() {
34
+ const api = useFeatureToggle();
35
+
36
+ it("should return true if parameter is boolean with legacy fn isVisible", function() {
37
+ api.setFlag('featurebool', true);
38
+ const visibility = api.isVisible('featurebool');
39
+ assert.strictEqual(visibility,true);
40
+ });
41
+
42
+ it("should return true if parameter is boolean", function() {
43
+ api.setFlag('featurebool', true);
44
+ const visibility = api.isActive('featurebool');
45
+ assert.strictEqual(visibility,true);
46
+ });
47
+
48
+ it("should ignore case for the api-keys", function() {
49
+ api.setFlag('ignoreCase', true);
50
+ const visibility1 = api.isActive('ignorecase');
51
+ const visibility2 = api.isActive('iGNOrEcaSe');
52
+ assert.strictEqual(visibility1,true);
53
+ assert.strictEqual(visibility2,true);
54
+ });
55
+
56
+ it("should return true if parameter is function", function() {
57
+ api.setFlag('featurebool', function() { return true });
58
+ const visibility = api.isActive('featurebool');
59
+ assert.strictEqual(visibility,true);
60
+ });
61
+
62
+ it("should return true if feature is true", function() {
63
+ api.setFlag('featureTrue', true);
64
+ const visibility = api.isActive('featureTrue');
65
+ assert.strictEqual(visibility,true);
66
+ });
67
+
68
+ it("should return false if feature is false", function() {
69
+ api.setFlag('featureFalse', false);
70
+ const visibility = api.isActive('featureFalse');
71
+ assert.strictEqual(visibility,false);
72
+ });
73
+
74
+ it("should return false if feature does not exist", function() {
75
+ const visibility = api.isActive('featureNotExists');
76
+ assert.strictEqual(visibility,false);
77
+ });
78
+
79
+ it("should return true if feature with variant is true", function() {
80
+ api.setFlag('featureTrue', 'variant', true);
81
+ const visibility = api.isActive('featureTrue', 'variant');
82
+ assert.strictEqual(visibility,true);
83
+ });
84
+
85
+ it("should return false if feature with variant is false", function() {
86
+ api.setFlag('featureFalse', 'variant', false);
87
+ const visibility = api.isActive('featureFalse', 'variant');
88
+ assert.strictEqual(visibility,false);
89
+ });
90
+
91
+ it("should return false if feature with variant does not exist", function() {
92
+ const visibility = api.isActive('featureNotExists', 'variant', false);
93
+ assert.strictEqual(visibility,false);
94
+ });
95
+
96
+ it("should return true if feature with variant is requested but only featurerule exists", function() {
97
+ api.setFlag('featureTrue', true);
98
+ const visibility = api.isActive('featureTrue', 'variant');
99
+ assert.strictEqual(visibility,true);
100
+ });
101
+
102
+ it("should return false if data returns false", function() {
103
+ api.setFlag('featureFalse', 'variant', function(rule) { return rule.data == 'succeed' });
104
+ const visibility = api.isActive('featureFalse', 'variant', 'fail');
105
+ assert.strictEqual(visibility,false);
106
+ });
107
+
108
+ it("should return false if data returns true", function() {
109
+ api.setFlag('featureFalse', 'variant', function(rule) { return rule.data == 'succeed' });
110
+ const visibility = api.isActive('featureFalse', 'variant', 'succeed');
111
+ assert.strictEqual(visibility,true);
112
+ });
113
+
114
+ it("should pass correct parameters for visibilityrule", function() {
115
+ api.setFlag('featureFalse2', 'variant', function(rule) {
116
+ if (rule._internalCall)
117
+ return;
118
+ assert.strictEqual(rule.name,'featureFalse2');
119
+ assert.strictEqual(rule.variant,'variant');
120
+ assert.strictEqual(rule.data,'succeed');
121
+ });
122
+
123
+ api.isActive('featureFalse2', 'variant', 'succeed');
124
+
125
+ });
126
+ });
127
+
128
+ describe("Default Visibility", function() {
129
+ const api = useFeatureToggle();
130
+ api.setDefaultFlag((result) => {
131
+ return true;
132
+ });
133
+
134
+ it("should return true with legacy function-call defaultFlagif feature does not exist", function() {
135
+ const legacyApi = useFeatureToggle();
136
+ legacyApi.defaultVisibility((result) => {
137
+ return true;
138
+ });
139
+ const visibility = legacyApi.isActive('featureNotExists');
140
+ assert.strictEqual(visibility,true);
141
+ });
142
+ it("should return true if feature is passed by parameter", function() {
143
+ const apiByConstructor = useFeatureToggle({
144
+ $default:(result)=>{
145
+ return true;
146
+ }
147
+ });
148
+ const visibility = apiByConstructor.isActive('featureNotExists');
149
+ assert.strictEqual(visibility,true);
150
+ });
151
+
152
+ it("should return true if feature does not exist", function() {
153
+ const visibility = api.isActive('featureNotExists');
154
+ assert.strictEqual(visibility,true);
155
+ });
156
+
157
+ it("should return true if feature withVariant does not exist", function() {
158
+ const visibility = api.isActive('featureNotExists', 'variant');
159
+ assert.strictEqual(visibility,true);
160
+ });
161
+
162
+ it("should return false if feature is False", function() {
163
+ api.setFlag('featureFalse', false);
164
+ const visibility = api.isActive('featureFalse');
165
+ assert.strictEqual(visibility,false);
166
+ });
167
+
168
+ it("should return false if feature with variant isFalse", function() {
169
+ api.setFlag('featureFalse2', false)
170
+ const visibility = api.isActive('featureFalse2', 'variant', false);
171
+ assert.strictEqual(visibility,false);
172
+ });
173
+ });
174
+
175
+ describe("Required Flag", function() {
176
+ const api = useFeatureToggle();
177
+ api.setRequiredFlag((result) => {
178
+ return result.variant == "valid";
179
+ });
180
+
181
+ it("should return true with legacy function requiredFlag if required rule matches and visibiltiyrule", function() {
182
+ const legacyApi = useFeatureToggle();
183
+ legacyApi.requiredVisibility((result) => {
184
+ return result.variant == "valid";
185
+ });
186
+ legacyApi.setFlag('featureTrue', true);
187
+ const visibility = legacyApi.isActive('featureTrue', 'valid');
188
+ assert.strictEqual(visibility,true);
189
+ });
190
+
191
+ it("should return false if feature does not exist and requirerule matches", function() {
192
+ api.setFlag('featureNotExists', 'valid', false);
193
+ const visibility = api.isActive('featureNotExists', 'valid');
194
+ assert.strictEqual(visibility,false);
195
+ });
196
+ it("should return false if feature does not exist and requirerule matches not", function() {
197
+ api.setFlag('featureNotExists', 'invalid', false);
198
+ const visibility = api.isActive('featureNotExists', 'invalid');
199
+ assert.strictEqual(visibility,false);
200
+ });
201
+
202
+
203
+ it("should return false if only required rule matches", function() {
204
+ api.setFlag('featureFalse', false);
205
+ const visibility = api.isActive('featureFalse', 'valid');
206
+ assert.strictEqual(visibility,false);
207
+ });
208
+
209
+ it("should return true if required rule is passed by the constructor", function() {
210
+ const apiByConstructor = useFeatureToggle({
211
+ $required:(result)=>{
212
+ return result.variant == "valid";
213
+ }
214
+ });
215
+ apiByConstructor.setFlag('featureTrue','valid',true);
216
+ const visibility = apiByConstructor.isActive('featureTrue', 'valid');
217
+ assert.strictEqual(visibility,true);
218
+ });
219
+
220
+ it("should return true if required rule and featureFlagis passed by the constructor", function() {
221
+ const apiByConstructor = useFeatureToggle({
222
+ "featureTrue:valid": true,
223
+ $required:(result)=>{
224
+ return result.variant == "valid";
225
+ }
226
+ });
227
+ const visibility = apiByConstructor.isActive('featureTrue', 'valid');
228
+ assert.strictEqual(visibility,true);
229
+ });
230
+
231
+ it("should return true if required rule matches and visibiltiyrule", function() {
232
+ api.setFlag('featureTrue', true);
233
+ const visibility = api.isActive('featureTrue', 'valid');
234
+ assert.strictEqual(visibility,true);
235
+ });
236
+
237
+ it("should return false if defaultFlag returns true", function() {
238
+ api.setDefaultFlag((result) => {
239
+ return true;
240
+ });
241
+ const visibility = api.isActive('featureTrue', 'invalid');
242
+ assert.strictEqual(visibility,false);
243
+ });
244
+ });
245
+
246
+ describe("Listener", function() {
247
+ const api = useFeatureToggle({
248
+ feature: true,
249
+ "feature2#variant": true
250
+ });
251
+
252
+ it("should have an event called on('visibilityrule')", function() {
253
+ api.on('visibilityrule', function() {
254
+
255
+ });
256
+ });
257
+
258
+ it("should access function twice for two initial eventnames", function() {
259
+ var listenedEvents : string[] = [];
260
+
261
+ api.on('visibilityrule', function(event) {
262
+ listenedEvents.push(event.name + "," + event.variant + "," + event.result);
263
+ })
264
+
265
+ assert.strictEqual(JSON.stringify(listenedEvents),JSON.stringify(['feature,undefined,true', 'feature2,variant,true']));
266
+ });
267
+
268
+ it("should access function twice if visibilityrule is added before listener", function() {
269
+ var listenedEvents :string[]= [];
270
+ var localapi = useFeatureToggle({ feature: true });
271
+ localapi.setFlag("feature2", "variant", true);
272
+ localapi.on('visibilityrule', function(event) {
273
+ listenedEvents.push(event.name + "," + event.variant + "," + event.result);
274
+ })
275
+
276
+ assert.strictEqual(JSON.stringify(listenedEvents),JSON.stringify(['feature,undefined,true', 'feature2,variant,true']));
277
+ });
278
+
279
+ it("should access function twice if visibilityrule is added after listener", function() {
280
+ var listenedEvents :string[]= [];
281
+ var localapi = useFeatureToggle({ feature: true });
282
+ localapi.on('visibilityrule', function(event) {
283
+ listenedEvents.push(event.name + "," + event.variant + "," + event.result);
284
+ });
285
+ localapi.setFlag("feature2", "variant", true);
286
+
287
+ assert.strictEqual(JSON.stringify(listenedEvents),JSON.stringify(['feature,undefined,true', 'feature2,variant,true']));
288
+ });
289
+
290
+ it("should pass parameters as an object with result,name,variant,data", function() {
291
+ var api = useFeatureToggle();
292
+ api.setFlag('feature', 'variant', 'gruempfel', function(rule) {
293
+ return (rule.data + rule.name + rule.variant) == 'gruempfelfeaturevariant';
294
+ });
295
+
296
+ api.on('visibilityrule', function(event) {
297
+ assert.strictEqual(event.result,true);
298
+ assert.strictEqual(event.name,"feature");
299
+ assert.strictEqual(event.variant,"variant");
300
+ assert.strictEqual(event.data,"gruempfel");
301
+ })
302
+ });
303
+
304
+ it("should be executed, when data has changed", function() {
305
+ var api = useFeatureToggle();
306
+ var totalData = '';
307
+
308
+ api.on('visibilityrule', function(event) {
309
+ totalData += event.data + ',';
310
+ });
311
+
312
+ api.setFlag('feature', 'variant', 'gruempfel', true);
313
+ api.setData('feature', 'variant', 'newgruempfel');
314
+ api.setFlag('feature2', null, 'gruempfel2', true);
315
+ api.setData('feature2', 'newgruempfel2');
316
+ assert.strictEqual(totalData,'gruempfel,newgruempfel,gruempfel2,newgruempfel2,');
317
+ });
318
+
319
+ it("should trigger a custom defined event", function() {
320
+ var api = useFeatureToggle();
321
+
322
+ let customEvent :any= false;
323
+ api.on('customevent', function(param) {
324
+ customEvent = param;
325
+ });
326
+
327
+ api.trigger('customevent', 'fired');
328
+ assert.strictEqual(customEvent,'fired');
329
+ });
330
+ });
331
+
332
+ describe("Plugins", function() {
333
+
334
+ it("function addPlugin should exist", function() {
335
+ var api = useFeatureToggle();
336
+
337
+ assert.notStrictEqual(api.addPlugin,null);
338
+ });
339
+
340
+ it("should be possible with legacy-key _plugins", function() {
341
+ function newPlugin(api) {
342
+ return {
343
+ newplugin: true
344
+ }
345
+ }
346
+
347
+ var api = useFeatureToggle({
348
+ _plugins:[newPlugin]
349
+ });
350
+ assert.strictEqual(api.newplugin,true);
351
+ });
352
+
353
+ it("should add attribute 'newplugin' to the api", function() {
354
+ function newPlugin(api) {
355
+
356
+ return {
357
+ newplugin: true
358
+ }
359
+ }
360
+
361
+ var api = useFeatureToggle();
362
+ api.addPlugin(newPlugin);
363
+ assert.strictEqual(api.newplugin,true);
364
+ });
365
+
366
+ it("should add plugins via attribute constructor config", function() {
367
+ function newPlugin(api) {
368
+ return {
369
+ newplugin: true
370
+ }
371
+ }
372
+
373
+ var api = useFeatureToggle({ $plugins: [newPlugin] });
374
+
375
+ assert.strictEqual(api.newplugin,true);
376
+ assert.strictEqual(api.isActive('_plugin'),false);
377
+ });
378
+
379
+ it("should only be executed once", function() {
380
+ var api = useFeatureToggle();
381
+ api.count = 0;
382
+
383
+ function countPlugin(api) {
384
+ api.count += 1;
385
+ return {}
386
+ }
387
+
388
+ api.addPlugin(countPlugin);
389
+ api.addPlugin(countPlugin);
390
+ assert.strictEqual(api.count,1);
391
+ });
392
+
393
+ it("should trigger the init-event", function() {
394
+ function newPlugin(api) {
395
+ api.on('init', function() {
396
+ api.initTriggered = true;
397
+ })
398
+ return {
399
+ initTriggered: false
400
+ }
401
+ }
402
+
403
+ var api = useFeatureToggle({ $plugins: [newPlugin] });
404
+ assert.strictEqual(api.initTriggered,true);
405
+ });
406
+ });
407
+
408
+ describe("URL-Plugin", function() {
409
+ it("should return empty string for url", function() {
410
+ var api = useFeatureToggle({
411
+ $plugins: [urlPlugin({ useMockedWindow:true})]
412
+ });
413
+ assert.strictEqual(api.url,"");
414
+ })
415
+ it("should have api.url = anydomain.de if set in pluginconfig", function() {
416
+ var api = useFeatureToggle({
417
+ $plugins: [urlPlugin({
418
+ url: "http://anydomain.de",
419
+ useMockedWindow:true
420
+ })]
421
+ });
422
+ assert.strictEqual(api.url,"http://anydomain.de");
423
+ })
424
+ it("should have feature1 visible if param feature1=true exists", function() {
425
+ var api = useFeatureToggle({
426
+ $plugins: [urlPlugin({ url: 'anydomain.de?feature1=true', useMockedWindow:true })]
427
+ });
428
+ const visibilityF1 = api.isActive("feature1");
429
+ const visibilityF2 = api.isActive("feature2");
430
+ assert.strictEqual(visibilityF1,true);
431
+ assert.strictEqual(visibilityF2,false);
432
+ })
433
+ it("should only regard params with prefix", function() {
434
+ var api = useFeatureToggle({
435
+ $plugins: [urlPlugin({
436
+ url: 'anydomain.de?feature1=true&_feature2=true',
437
+ prefix: '_',
438
+ useMockedWindow:true
439
+ })]
440
+ });
441
+ const visibilityF1 = api.isActive("feature1");
442
+ const visibilityF2 = api.isActive("feature2");
443
+ const visibility_F1 = api.isActive("_feature1");
444
+ const visibility_F2 = api.isActive("_feature2");
445
+ assert.strictEqual(visibilityF1,false);
446
+ assert.strictEqual(visibilityF2,true);
447
+ assert.strictEqual(visibility_F1,false);
448
+ assert.strictEqual(visibility_F2,false);
449
+ })
450
+ });
@@ -0,0 +1,20 @@
1
+ const window = {
2
+ isMocked: true,
3
+ decodeURIComponent:function(param1){
4
+ return param1;
5
+ }
6
+ }
7
+
8
+ function useWindowMock(){
9
+ return {
10
+ isMocked: true,
11
+ decodeURIComponent:function(param1){
12
+ return param1;
13
+ }
14
+ }
15
+ }
16
+
17
+ export {
18
+ window,
19
+ useWindowMock
20
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "emitDeclarationOnly": false, // Nur Deklarationen ausgeben (false, damit auch JS kompiliert wird)
4
+ "outDir": "dist", // Ausgabeort für die Typen
5
+ "module": "ESNext", // Oder das Modulformat deiner Wahl
6
+ "target": "ESNext",
7
+ "moduleResolution": "node"
8
+ },
9
+ "include": ["src/**/*.ts"], // Eingeschlossene Dateien
10
+ "exclude": ["node_modules"] ,
11
+
12
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "emitDeclarationOnly": false, // Nur Deklarationen ausgeben (false, damit auch JS kompiliert wird)
4
+ "outDir": "dist", // Ausgabeort für die Typen
5
+ "module": "ESNext", // Oder das Modulformat deiner Wahl
6
+ "target": "ESNext",
7
+ "moduleResolution": "bundler"
8
+ },
9
+ "include": ["src/**/*.ts"], // Eingeschlossene Dateien
10
+ "exclude": ["node_modules"] ,
11
+
12
+ }