Skip to content

Commit a67ad8b

Browse files
feat(ForcedDecisions): Add forced-decisions APIs to ReactSDKClient and update useDecision hook to reflect changes (#133)
## Summary Add a set of new APIs for forced-decisions to ReactSDKClient. - setForcedDecision - getForcedDecision - removeForcedDecision - removeAllForcedDecisions Also updated `useDecision` hook to autoupdate and reflect changes when forced decisions are set and removed. ## Test Plan - Added unit tests. - Manually tested thoroughly.
1 parent 2731ec8 commit a67ad8b

File tree

9 files changed

+829
-110
lines changed

9 files changed

+829
-110
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
},
3030
"dependencies": {
3131
"@optimizely/js-sdk-logging": "^0.1.0",
32-
"@optimizely/optimizely-sdk": "^4.7.0",
32+
"@optimizely/optimizely-sdk": "^4.9.1",
3333
"hoist-non-react-statics": "^3.3.0",
3434
"prop-types": "^15.6.2",
3535
"utility-types": "^2.1.0 || ^3.0.0"

src/client.spec.ts

Lines changed: 240 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2019-2020, Optimizely
2+
* Copyright 2019-2022, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,6 +33,9 @@ describe('ReactSDKClient', () => {
3333
decide: jest.fn(),
3434
decideAll: jest.fn(),
3535
decideForKeys: jest.fn(),
36+
setForcedDecision: jest.fn(),
37+
removeForcedDecision: jest.fn(),
38+
removeAllForcedDecisions: jest.fn(),
3639
} as any;
3740

3841
mockInnerClient = {
@@ -526,19 +529,19 @@ describe('ReactSDKClient', () => {
526529
const mockFn = mockOptimizelyUserContext.decideAll as jest.Mock;
527530
const mockCreateUserContext = mockInnerClient.createUserContext as jest.Mock;
528531
mockFn.mockReturnValue({
529-
'theFlag1': {
532+
theFlag1: {
530533
enabled: true,
531534
flagKey: 'theFlag1',
532535
reasons: [],
533536
ruleKey: '',
534537
userContext: mockOptimizelyUserContext,
535538
variables: {},
536539
variationKey: 'varition1',
537-
}
540+
},
538541
});
539542
let result = instance.decideAll();
540543
expect(result).toEqual({
541-
'theFlag1': {
544+
theFlag1: {
542545
enabled: true,
543546
flagKey: 'theFlag1',
544547
reasons: [],
@@ -549,26 +552,26 @@ describe('ReactSDKClient', () => {
549552
},
550553
variables: {},
551554
variationKey: 'varition1',
552-
}
555+
},
553556
});
554557
expect(mockFn).toBeCalledTimes(1);
555558
expect(mockFn).toBeCalledWith([]);
556-
expect(mockCreateUserContext).toBeCalledWith('user1', { foo: 'bar' });
559+
expect(mockCreateUserContext).toBeCalledWith('user1', { foo: 'bar' });
557560
mockFn.mockReset();
558561
mockFn.mockReturnValue({
559-
'theFlag2': {
562+
theFlag2: {
560563
enabled: true,
561564
flagKey: 'theFlag2',
562565
reasons: [],
563566
ruleKey: '',
564567
userContext: mockOptimizelyUserContext,
565568
variables: {},
566569
variationKey: 'varition2',
567-
}
570+
},
568571
});
569572
result = instance.decideAll([optimizely.OptimizelyDecideOption.INCLUDE_REASONS], 'user2', { bar: 'baz' });
570573
expect(result).toEqual({
571-
'theFlag2': {
574+
theFlag2: {
572575
enabled: true,
573576
flagKey: 'theFlag2',
574577
reasons: [],
@@ -579,7 +582,7 @@ describe('ReactSDKClient', () => {
579582
},
580583
variables: {},
581584
variationKey: 'varition2',
582-
}
585+
},
583586
});
584587
expect(mockFn).toBeCalledTimes(1);
585588
expect(mockFn).toBeCalledWith([optimizely.OptimizelyDecideOption.INCLUDE_REASONS]);
@@ -590,19 +593,19 @@ describe('ReactSDKClient', () => {
590593
const mockFn = mockOptimizelyUserContext.decideForKeys as jest.Mock;
591594
const mockCreateUserContext = mockInnerClient.createUserContext as jest.Mock;
592595
mockFn.mockReturnValue({
593-
'theFlag1': {
596+
theFlag1: {
594597
enabled: true,
595598
flagKey: 'theFlag1',
596599
reasons: [],
597600
ruleKey: '',
598601
userContext: mockOptimizelyUserContext,
599602
variables: {},
600603
variationKey: 'varition1',
601-
}
604+
},
602605
});
603606
let result = instance.decideForKeys(['theFlag1']);
604607
expect(result).toEqual({
605-
'theFlag1': {
608+
theFlag1: {
606609
enabled: true,
607610
flagKey: 'theFlag1',
608611
reasons: [],
@@ -613,26 +616,28 @@ describe('ReactSDKClient', () => {
613616
},
614617
variables: {},
615618
variationKey: 'varition1',
616-
}
619+
},
617620
});
618621
expect(mockFn).toBeCalledTimes(1);
619622
expect(mockFn).toBeCalledWith(['theFlag1'], []);
620623
expect(mockCreateUserContext).toBeCalledWith('user1', { foo: 'bar' });
621624
mockFn.mockReset();
622625
mockFn.mockReturnValue({
623-
'theFlag2': {
626+
theFlag2: {
624627
enabled: true,
625628
flagKey: 'theFlag2',
626629
reasons: [],
627630
ruleKey: '',
628631
userContext: mockOptimizelyUserContext,
629632
variables: {},
630633
variationKey: 'varition2',
631-
}
634+
},
635+
});
636+
result = instance.decideForKeys(['theFlag1'], [optimizely.OptimizelyDecideOption.INCLUDE_REASONS], 'user2', {
637+
bar: 'baz',
632638
});
633-
result = instance.decideForKeys(['theFlag1'], [optimizely.OptimizelyDecideOption.INCLUDE_REASONS], 'user2', { bar: 'baz' });
634639
expect(result).toEqual({
635-
'theFlag2': {
640+
theFlag2: {
636641
enabled: true,
637642
flagKey: 'theFlag2',
638643
reasons: [],
@@ -643,7 +648,7 @@ describe('ReactSDKClient', () => {
643648
},
644649
variables: {},
645650
variationKey: 'varition2',
646-
}
651+
},
647652
});
648653
expect(mockFn).toBeCalledTimes(1);
649654
expect(mockFn).toBeCalledWith(['theFlag1'], [optimizely.OptimizelyDecideOption.INCLUDE_REASONS]);
@@ -856,4 +861,220 @@ describe('ReactSDKClient', () => {
856861
expect(handler).not.toBeCalled();
857862
});
858863
});
864+
865+
describe('removeAllForcedDecisions', () => {
866+
let instance: ReactSDKClient;
867+
beforeEach(() => {
868+
instance = createInstance(config);
869+
});
870+
871+
it('should return false if no user context has been set ', () => {
872+
const mockFn = mockOptimizelyUserContext.removeAllForcedDecisions as jest.Mock;
873+
874+
mockFn.mockReturnValue(false);
875+
876+
const result = instance.removeAllForcedDecisions();
877+
expect(result).toBeDefined();
878+
expect(result).toEqual(false);
879+
});
880+
881+
it('should return true if user context has been set ', () => {
882+
instance.setUser({
883+
id: 'user1',
884+
});
885+
const mockFn = mockOptimizelyUserContext.removeAllForcedDecisions as jest.Mock;
886+
887+
mockFn.mockReturnValue(true);
888+
889+
const result = instance.removeAllForcedDecisions();
890+
expect(mockFn).toBeCalledTimes(1);
891+
expect(result).toBeDefined();
892+
expect(result).toEqual(true);
893+
});
894+
});
895+
896+
describe('setForcedDecision', () => {
897+
let instance: ReactSDKClient;
898+
beforeEach(() => {
899+
instance = createInstance(config);
900+
instance.setUser({
901+
id: 'user1',
902+
attributes: {
903+
foo: 'bar',
904+
},
905+
});
906+
});
907+
908+
it('should overwrite decide when forcedDecision is envoked', () => {
909+
const mockFn = mockOptimizelyUserContext.decide as jest.Mock;
910+
mockFn.mockReturnValue({
911+
enabled: true,
912+
flagKey: 'theFlag1',
913+
reasons: [],
914+
ruleKey: '',
915+
userContext: mockOptimizelyUserContext,
916+
variables: {},
917+
variationKey: 'varition1',
918+
});
919+
const result = instance.decide('theFlag1');
920+
expect(result).toEqual({
921+
enabled: true,
922+
flagKey: 'theFlag1',
923+
reasons: [],
924+
ruleKey: '',
925+
userContext: {
926+
id: 'user1',
927+
attributes: { foo: 'bar' },
928+
},
929+
variables: {},
930+
variationKey: 'varition1',
931+
});
932+
expect(mockFn).toBeCalledTimes(1);
933+
expect(mockFn).toBeCalledWith('theFlag1', []);
934+
935+
const mockFnForcedDecision = mockOptimizelyUserContext.setForcedDecision as jest.Mock;
936+
mockFnForcedDecision.mockReturnValue(true);
937+
instance.setForcedDecision(
938+
{
939+
flagKey: 'theFlag1',
940+
ruleKey: 'experiment',
941+
},
942+
{ variationKey: 'varition2' }
943+
);
944+
945+
expect(mockFnForcedDecision).toBeCalledTimes(1);
946+
947+
mockFn.mockReset();
948+
mockFn.mockReturnValue({
949+
enabled: true,
950+
flagKey: 'theFlag1',
951+
reasons: [],
952+
ruleKey: '',
953+
userContext: mockOptimizelyUserContext,
954+
variables: {},
955+
variationKey: 'varition2',
956+
});
957+
const result2 = instance.decide('theFlag1', []);
958+
959+
expect(mockFn).toBeCalledTimes(1);
960+
expect(mockFn).toBeCalledWith('theFlag1', []);
961+
expect(result2).toEqual({
962+
enabled: true,
963+
flagKey: 'theFlag1',
964+
reasons: [],
965+
ruleKey: '',
966+
userContext: { id: 'user1', attributes: { foo: 'bar' } },
967+
variables: {},
968+
variationKey: 'varition2',
969+
});
970+
});
971+
});
972+
973+
describe('removeForcedDecision', () => {
974+
let instance: ReactSDKClient;
975+
beforeEach(() => {
976+
instance = createInstance(config);
977+
instance.setUser({
978+
id: 'user1',
979+
attributes: {
980+
foo: 'bar',
981+
},
982+
});
983+
});
984+
985+
it('should revert back to the decide default value when removeForcedDecision is envoked after settingup the forced decision', () => {
986+
const mockFn = mockOptimizelyUserContext.decide as jest.Mock;
987+
mockFn.mockReturnValue({
988+
enabled: true,
989+
flagKey: 'theFlag1',
990+
reasons: [],
991+
ruleKey: '',
992+
userContext: mockOptimizelyUserContext,
993+
variables: {},
994+
variationKey: 'varition1',
995+
});
996+
const result = instance.decide('theFlag1');
997+
expect(result).toEqual({
998+
enabled: true,
999+
flagKey: 'theFlag1',
1000+
reasons: [],
1001+
ruleKey: '',
1002+
userContext: {
1003+
id: 'user1',
1004+
attributes: { foo: 'bar' },
1005+
},
1006+
variables: {},
1007+
variationKey: 'varition1',
1008+
});
1009+
expect(mockFn).toBeCalledTimes(1);
1010+
expect(mockFn).toBeCalledWith('theFlag1', []);
1011+
1012+
const mockFnForcedDecision = mockOptimizelyUserContext.setForcedDecision as jest.Mock;
1013+
mockFnForcedDecision.mockReturnValue(true);
1014+
instance.setForcedDecision(
1015+
{
1016+
flagKey: 'theFlag1',
1017+
ruleKey: 'experiment',
1018+
},
1019+
{ variationKey: 'varition2' }
1020+
);
1021+
1022+
expect(mockFnForcedDecision).toBeCalledTimes(1);
1023+
1024+
mockFn.mockReset();
1025+
mockFn.mockReturnValue({
1026+
enabled: true,
1027+
flagKey: 'theFlag1',
1028+
reasons: [],
1029+
ruleKey: '',
1030+
userContext: mockOptimizelyUserContext,
1031+
variables: {},
1032+
variationKey: 'varition2',
1033+
});
1034+
const result2 = instance.decide('theFlag1', []);
1035+
1036+
expect(mockFn).toBeCalledTimes(1);
1037+
expect(mockFn).toBeCalledWith('theFlag1', []);
1038+
expect(result2).toEqual({
1039+
enabled: true,
1040+
flagKey: 'theFlag1',
1041+
reasons: [],
1042+
ruleKey: '',
1043+
userContext: { id: 'user1', attributes: { foo: 'bar' } },
1044+
variables: {},
1045+
variationKey: 'varition2',
1046+
});
1047+
1048+
const mockFnRemoveForcedDecision = mockOptimizelyUserContext.removeForcedDecision as jest.Mock;
1049+
mockFnRemoveForcedDecision.mockReturnValue(true);
1050+
instance.removeForcedDecision({
1051+
flagKey: 'theFlag1',
1052+
ruleKey: 'experiment',
1053+
});
1054+
1055+
mockFn.mockReset();
1056+
mockFn.mockReturnValue({
1057+
enabled: true,
1058+
flagKey: 'theFlag1',
1059+
reasons: [],
1060+
ruleKey: '',
1061+
userContext: mockOptimizelyUserContext,
1062+
variables: {},
1063+
variationKey: 'varition1',
1064+
});
1065+
const result3 = instance.decide('theFlag1', []);
1066+
1067+
expect(mockFn).toBeCalledTimes(1);
1068+
expect(mockFn).toBeCalledWith('theFlag1', []);
1069+
expect(result3).toEqual({
1070+
enabled: true,
1071+
flagKey: 'theFlag1',
1072+
reasons: [],
1073+
ruleKey: '',
1074+
userContext: { id: 'user1', attributes: { foo: 'bar' } },
1075+
variables: {},
1076+
variationKey: 'varition1',
1077+
});
1078+
});
1079+
});
8591080
});

0 commit comments

Comments
 (0)