15
15
*/
16
16
17
17
/// <reference types="jest" />
18
-
19
18
import * as React from 'react' ;
20
19
import { act } from 'react-dom/test-utils' ;
21
20
import { render , renderHook , screen , waitFor } from '@testing-library/react' ;
22
21
import '@testing-library/jest-dom' ;
23
22
24
23
import { OptimizelyProvider } from './Provider' ;
25
24
import { NotReadyReason , OnReadyResult , ReactSDKClient , VariableValuesObject } from './client' ;
26
- import { useExperiment , useFeature , useDecision , useTrackEvent , hooksLogger } from './hooks' ;
25
+ import { useExperiment , useFeature , useDecision , useTrackEvent } from './hooks' ;
27
26
import { OptimizelyDecision } from './utils' ;
27
+ import { getLogger } from '@optimizely/optimizely-sdk' ;
28
+
29
+ jest . mock ( '@optimizely/optimizely-sdk' , ( ) => {
30
+ const originalModule = jest . requireActual ( '@optimizely/optimizely-sdk' ) ;
31
+ return {
32
+ ...originalModule ,
33
+ getLogger : jest . fn ( ) . mockReturnValue ( {
34
+ error : jest . fn ( ) ,
35
+ warn : jest . fn ( ) ,
36
+ info : jest . fn ( ) ,
37
+ debug : jest . fn ( ) ,
38
+ } ) ,
39
+ } ;
40
+ } ) ;
41
+
42
+ const hooksLogger = getLogger ( 'ReactSDK' ) ;
43
+
28
44
const defaultDecision : OptimizelyDecision = {
29
45
enabled : false ,
30
46
variables : { } ,
@@ -79,7 +95,6 @@ describe('hooks', () => {
79
95
let forcedVariationUpdateCallbacks : Array < ( ) => void > ;
80
96
let decideMock : jest . Mock < OptimizelyDecision > ;
81
97
let setForcedDecisionMock : jest . Mock < void > ;
82
- let hooksLoggerErrorSpy : jest . SpyInstance ;
83
98
const REJECTION_REASON = 'A rejection reason you should never see in the test runner' ;
84
99
85
100
beforeEach ( ( ) => {
@@ -125,7 +140,6 @@ describe('hooks', () => {
125
140
forcedVariationUpdateCallbacks = [ ] ;
126
141
decideMock = jest . fn ( ) ;
127
142
setForcedDecisionMock = jest . fn ( ) ;
128
- hooksLoggerErrorSpy = jest . spyOn ( hooksLogger , 'error' ) ;
129
143
optimizelyMock = {
130
144
activate : activateMock ,
131
145
onReady : jest . fn ( ) . mockImplementation ( ( config ) => getOnReadyPromise ( config || { } ) ) ,
@@ -185,7 +199,7 @@ describe('hooks', () => {
185
199
( res ) => res . dataReadyPromise ,
186
200
( err ) => null
187
201
) ;
188
- hooksLoggerErrorSpy . mockReset ( ) ;
202
+ jest . resetAllMocks ( ) ;
189
203
} ) ;
190
204
191
205
describe ( 'useExperiment' , ( ) => {
@@ -426,6 +440,16 @@ describe('hooks', () => {
426
440
} ) ;
427
441
428
442
describe ( 'useFeature' , ( ) => {
443
+ it ( 'should print error if optimizely is not provided' , async ( ) => {
444
+ render (
445
+ // @ts -ignore
446
+ < OptimizelyProvider optimizely = { null } >
447
+ < MyFeatureComponent />
448
+ </ OptimizelyProvider >
449
+ ) ;
450
+ await waitFor ( ( ) => expect ( hooksLogger . error ) . toHaveBeenCalled ( ) ) ;
451
+ } ) ;
452
+
429
453
it ( 'should render true when the feature is enabled' , async ( ) => {
430
454
isFeatureEnabledMock . mockReturnValue ( true ) ;
431
455
@@ -655,6 +679,160 @@ describe('hooks', () => {
655
679
} ) ;
656
680
657
681
describe ( 'useDecision' , ( ) => {
682
+ it ( 'should handle no client promise response' , async ( ) => {
683
+ getOnReadyPromise = ( ) =>
684
+ new Promise ( ( resolve ) => {
685
+ setTimeout ( ( ) => {
686
+ resolve ( {
687
+ success : false ,
688
+ reason : NotReadyReason . NO_CLIENT ,
689
+ dataReadyPromise : new Promise ( ( r ) => setTimeout ( ( ) => r ( { success : false } ) , mockDelay ) ) ,
690
+ } ) ;
691
+ } ) ;
692
+ } ) ;
693
+ decideMock . mockReturnValue ( { ...defaultDecision } ) ;
694
+
695
+ render (
696
+ < OptimizelyProvider optimizely = { optimizelyMock } >
697
+ < MyDecideComponent />
698
+ </ OptimizelyProvider >
699
+ ) ;
700
+
701
+ await waitFor ( ( ) => {
702
+ expect ( screen . getByTestId ( 'result' ) ) . toHaveTextContent ( 'false|{}|false|false' ) ;
703
+ expect ( hooksLogger . warn ) . toHaveBeenCalled ( ) ;
704
+ } ) ;
705
+ } ) ;
706
+
707
+ it ( 'should handle no client, but data ready promise success' , ( ) => {
708
+ getOnReadyPromise = ( ) =>
709
+ new Promise ( ( resolve ) => {
710
+ setTimeout ( ( ) => {
711
+ resolve ( {
712
+ success : false ,
713
+ reason : NotReadyReason . NO_CLIENT ,
714
+ dataReadyPromise : new Promise ( ( r ) => setTimeout ( ( ) => r ( { success : true } ) , mockDelay ) ) ,
715
+ } ) ;
716
+ } ) ;
717
+ } ) ;
718
+ decideMock . mockReturnValue ( { ...defaultDecision } ) ;
719
+
720
+ render (
721
+ < OptimizelyProvider optimizely = { optimizelyMock } >
722
+ < MyDecideComponent />
723
+ </ OptimizelyProvider >
724
+ ) ;
725
+
726
+ expect ( screen . getByTestId ( 'result' ) ) . toHaveTextContent ( 'false|{}|true|false' ) ;
727
+ } ) ;
728
+
729
+ it ( 'should handle user not ready promise response' , async ( ) => {
730
+ getOnReadyPromise = ( ) =>
731
+ new Promise ( ( resolve ) => {
732
+ setTimeout ( ( ) => {
733
+ resolve ( {
734
+ success : false ,
735
+ reason : NotReadyReason . USER_NOT_READY ,
736
+ dataReadyPromise : new Promise ( ( r ) => setTimeout ( ( ) => r ( { success : false } ) , mockDelay ) ) ,
737
+ } ) ;
738
+ } ) ;
739
+ } ) ;
740
+ decideMock . mockReturnValue ( { ...defaultDecision } ) ;
741
+
742
+ render (
743
+ < OptimizelyProvider optimizely = { optimizelyMock } >
744
+ < MyDecideComponent />
745
+ </ OptimizelyProvider >
746
+ ) ;
747
+
748
+ await waitFor ( ( ) => {
749
+ expect ( screen . getByTestId ( 'result' ) ) . toHaveTextContent ( 'false|{}|false|false' ) ;
750
+ expect ( hooksLogger . warn ) . toHaveBeenCalled ( ) ;
751
+ } ) ;
752
+ } ) ;
753
+
754
+ it ( 'should handle user not ready, but data ready promise success' , ( ) => {
755
+ getOnReadyPromise = ( ) =>
756
+ new Promise ( ( resolve ) => {
757
+ setTimeout ( ( ) => {
758
+ resolve ( {
759
+ success : false ,
760
+ reason : NotReadyReason . USER_NOT_READY ,
761
+ dataReadyPromise : new Promise ( ( r ) => setTimeout ( ( ) => r ( { success : true } ) , mockDelay ) ) ,
762
+ } ) ;
763
+ } ) ;
764
+ } ) ;
765
+ decideMock . mockReturnValue ( { ...defaultDecision } ) ;
766
+
767
+ render (
768
+ < OptimizelyProvider optimizely = { optimizelyMock } >
769
+ < MyDecideComponent />
770
+ </ OptimizelyProvider >
771
+ ) ;
772
+
773
+ expect ( screen . getByTestId ( 'result' ) ) . toHaveTextContent ( 'false|{}|true|false' ) ;
774
+ } ) ;
775
+
776
+ it ( 'should handle default success false case' , async ( ) => {
777
+ getOnReadyPromise = ( ) =>
778
+ new Promise ( ( resolve ) => {
779
+ setTimeout ( ( ) => {
780
+ resolve ( {
781
+ success : false ,
782
+ reason : 'UNKNOWN' ,
783
+ dataReadyPromise : new Promise ( ( r ) => setTimeout ( ( ) => r ( { success : false } ) , mockDelay ) ) ,
784
+ } ) ;
785
+ } ) ;
786
+ } ) ;
787
+ decideMock . mockReturnValue ( { ...defaultDecision } ) ;
788
+
789
+ render (
790
+ < OptimizelyProvider optimizely = { optimizelyMock } >
791
+ < MyDecideComponent />
792
+ </ OptimizelyProvider >
793
+ ) ;
794
+
795
+ await waitFor ( ( ) => {
796
+ expect ( hooksLogger . warn ) . toHaveBeenCalled ( ) ;
797
+ expect ( screen . getByTestId ( 'result' ) ) . toHaveTextContent ( 'false|{}|false|false' ) ;
798
+ } ) ;
799
+ } ) ;
800
+
801
+ it ( 'should handle default success true case' , async ( ) => {
802
+ getOnReadyPromise = ( ) =>
803
+ new Promise ( ( resolve ) => {
804
+ setTimeout ( ( ) => {
805
+ resolve ( {
806
+ success : false ,
807
+ reason : 'UNKNOWN' ,
808
+ dataReadyPromise : new Promise ( ( r ) => setTimeout ( ( ) => r ( { success : true } ) , mockDelay ) ) ,
809
+ } ) ;
810
+ } ) ;
811
+ } ) ;
812
+ decideMock . mockReturnValue ( { ...defaultDecision } ) ;
813
+
814
+ render (
815
+ < OptimizelyProvider optimizely = { optimizelyMock } >
816
+ < MyDecideComponent />
817
+ </ OptimizelyProvider >
818
+ ) ;
819
+
820
+ await waitFor ( ( ) => {
821
+ expect ( hooksLogger . warn ) . toHaveBeenCalled ( ) ;
822
+ expect ( screen . getByTestId ( 'result' ) ) . toHaveTextContent ( 'false|{}|true|false' ) ;
823
+ } ) ;
824
+ } ) ;
825
+
826
+ it ( 'should print error if optimizely is not provided' , async ( ) => {
827
+ render (
828
+ // @ts -ignore
829
+ < OptimizelyProvider optimizely = { null } >
830
+ < MyDecideComponent />
831
+ </ OptimizelyProvider >
832
+ ) ;
833
+ await waitFor ( ( ) => expect ( hooksLogger . error ) . toHaveBeenCalled ( ) ) ;
834
+ } ) ;
835
+
658
836
it ( 'should render true when the flag is enabled' , async ( ) => {
659
837
decideMock . mockReturnValue ( {
660
838
...defaultDecision ,
@@ -705,10 +883,34 @@ describe('hooks', () => {
705
883
variables : { foo : 'bar' } ,
706
884
} ) ;
707
885
readySuccess = true ;
886
+
708
887
// When timeout is reached, but dataReadyPromise is resolved later with the decision value
709
888
await waitFor ( ( ) => expect ( screen . getByTestId ( 'result' ) ) . toHaveTextContent ( 'true|{"foo":"bar"}|true|true' ) ) ;
710
889
} ) ;
711
890
891
+ it ( 'should log warn message if dataReadyPromise resolves as false' , async ( ) => {
892
+ decideMock . mockReturnValue ( { ...defaultDecision } ) ;
893
+ readySuccess = false ;
894
+ mockDelay = 20 ;
895
+
896
+ render (
897
+ < OptimizelyProvider optimizely = { optimizelyMock } >
898
+ < MyDecideComponent options = { { timeout : mockDelay - 10 } } />
899
+ </ OptimizelyProvider >
900
+ ) ;
901
+
902
+ // Initial render
903
+ expect ( screen . getByTestId ( 'result' ) ) . toHaveTextContent ( 'false|{}|false|false' ) ;
904
+
905
+ readySuccess = false ;
906
+
907
+ // When timeout is reached, but dataReadyPromise is resolved later with the decision value
908
+ await waitFor ( ( ) => {
909
+ expect ( hooksLogger . warn ) . toHaveBeenCalled ( ) ;
910
+ expect ( screen . getByTestId ( 'result' ) ) . toHaveTextContent ( 'false|{}|false|true' ) ;
911
+ } ) ;
912
+ } ) ;
913
+
712
914
it ( 'should gracefully handle the client promise rejecting after timeout' , async ( ) => {
713
915
readySuccess = false ;
714
916
decideMock . mockReturnValue ( { ...defaultDecision } ) ;
@@ -721,7 +923,9 @@ describe('hooks', () => {
721
923
</ OptimizelyProvider >
722
924
) ;
723
925
724
- await waitFor ( ( ) => expect ( screen . getByTestId ( 'result' ) ) . toHaveTextContent ( 'false|{}|false|false' ) ) ;
926
+ await waitFor ( ( ) => {
927
+ expect ( screen . getByTestId ( 'result' ) ) . toHaveTextContent ( 'false|{}|false|false' ) ;
928
+ } ) ;
725
929
} ) ;
726
930
727
931
it ( 'should re-render when the user attributes change using autoUpdate' , async ( ) => {
@@ -1002,16 +1206,17 @@ describe('hooks', () => {
1002
1206
await waitFor ( ( ) => expect ( screen . getByTestId ( 'result' ) ) . toHaveTextContent ( 'false|{}|true|false' ) ) ;
1003
1207
} ) ;
1004
1208
} ) ;
1209
+
1005
1210
describe ( 'useTrackEvent' , ( ) => {
1006
1211
it ( 'returns track method and false states when optimizely is not provided' , ( ) => {
1007
1212
const wrapper = ( { children } : { children : React . ReactNode } ) : React . ReactElement => {
1008
1213
//@ts -ignore
1009
1214
return < OptimizelyProvider > { children } </ OptimizelyProvider > ;
1010
1215
} ;
1216
+
1011
1217
const { result } = renderHook ( ( ) => useTrackEvent ( ) , { wrapper } ) ;
1012
- expect ( result . current [ 0 ] ) . toBeInstanceOf ( Function ) ;
1013
- expect ( result . current [ 1 ] ) . toBe ( false ) ;
1014
- expect ( result . current [ 2 ] ) . toBe ( false ) ;
1218
+
1219
+ expect ( result . current ) . toEqual ( [ expect . any ( Function ) , false , false ] ) ;
1015
1220
} ) ;
1016
1221
1017
1222
it ( 'returns track method along with clientReady and didTimeout state when optimizely instance is provided' , ( ) => {
@@ -1022,9 +1227,10 @@ describe('hooks', () => {
1022
1227
) ;
1023
1228
1024
1229
const { result } = renderHook ( ( ) => useTrackEvent ( ) , { wrapper } ) ;
1025
- expect ( result . current [ 0 ] ) . toBeInstanceOf ( Function ) ;
1026
- expect ( typeof result . current [ 1 ] ) . toBe ( 'boolean' ) ;
1027
- expect ( typeof result . current [ 2 ] ) . toBe ( 'boolean' ) ;
1230
+ result . current [ 0 ] ( 'eventKey' ) ;
1231
+
1232
+ expect ( optimizelyMock . track ) . toHaveBeenCalledTimes ( 1 ) ;
1233
+ expect ( result . current ) . toEqual ( [ expect . any ( Function ) , true , false ] ) ;
1028
1234
} ) ;
1029
1235
1030
1236
it ( 'Log error when track method is called and optimizely instance is not provided' , ( ) => {
@@ -1034,7 +1240,7 @@ describe('hooks', () => {
1034
1240
} ;
1035
1241
const { result } = renderHook ( ( ) => useTrackEvent ( ) , { wrapper } ) ;
1036
1242
result . current [ 0 ] ( 'eventKey' ) ;
1037
- expect ( hooksLogger . error ) . toHaveBeenCalledTimes ( 1 ) ;
1243
+ expect ( hooksLogger . error ) . toHaveBeenCalled ( ) ;
1038
1244
} ) ;
1039
1245
1040
1246
it ( 'Log error when track method is called and client is not ready' , ( ) => {
@@ -1048,7 +1254,8 @@ describe('hooks', () => {
1048
1254
1049
1255
const { result } = renderHook ( ( ) => useTrackEvent ( ) , { wrapper } ) ;
1050
1256
result . current [ 0 ] ( 'eventKey' ) ;
1051
- expect ( hooksLogger . error ) . toHaveBeenCalledTimes ( 1 ) ;
1257
+
1258
+ expect ( hooksLogger . error ) . toHaveBeenCalled ( ) ;
1052
1259
} ) ;
1053
1260
} ) ;
1054
1261
} ) ;
0 commit comments