1+ import { combineReducers , configureStore } from '@reduxjs/toolkit' ;
2+ import type { AppStateStatus } from 'react-native' ;
3+ import * as ReactNative from 'react-native' ;
4+ import { EventType , IdentifyEventType } from '..' ;
15import { SegmentClient } from '../analytics' ;
6+ import * as checkInstalledVersion from '../internal/checkInstalledVersion' ;
7+ import * as flushRetry from '../internal/flushRetry' ;
8+ import * as handleAppStateChange from '../internal/handleAppStateChange' ;
9+ import * as trackDeepLinks from '../internal/trackDeepLinks' ;
210import { Logger } from '../logger' ;
3- import * as ReactNative from 'react-native' ;
411import * as alias from '../methods/alias' ;
12+ import * as flush from '../methods/flush' ;
513import * as group from '../methods/group' ;
614import * as identify from '../methods/identify' ;
715import * as screen from '../methods/screen' ;
816import * as track from '../methods/track' ;
9- import * as flush from '../methods/flush' ;
10- import * as flushRetry from '../internal/flushRetry' ;
11- import * as checkInstalledVersion from '../internal/checkInstalledVersion' ;
12- import * as handleAppStateChange from '../internal/handleAppStateChange' ;
13- import * as trackDeepLinks from '../internal/trackDeepLinks' ;
14- import type { AppStateStatus } from 'react-native' ;
17+ import { actions , Store } from '../store' ;
18+ import mainSlice from '../store/main' ;
19+ import systemSlice from '../store/system' ;
20+ import userInfo from '../store/userInfo' ;
1521import { getMockStore } from './__helpers__/mockStore' ;
1622
1723jest . mock ( 'redux-persist' , ( ) => {
@@ -109,7 +115,8 @@ describe('SegmentClient initialise', () => {
109115
110116 segmentClient . setupStoreSubscribe ( ) ;
111117
112- expect ( clientArgs . store . subscribe ) . toHaveBeenCalledTimes ( 1 ) ;
118+ // Each watcher generates a subscription so we just check that it has subscribed at least once
119+ expect ( clientArgs . store . subscribe ) . toHaveBeenCalled ( ) ;
113120 } ) ;
114121 } ) ;
115122
@@ -147,7 +154,8 @@ describe('SegmentClient initialise', () => {
147154 const segmentClient = new SegmentClient ( clientArgs ) ;
148155 // @ts -ignore actual value is irrelevant
149156 segmentClient . interval = 'INTERVAL' ;
150- segmentClient . unsubscribe = jest . fn ( ) ;
157+ const unsubscribe = jest . fn ( ) ;
158+ segmentClient . watchers = [ unsubscribe ] ;
151159 // @ts -ignore actual value is irrelevant
152160 segmentClient . refreshTimeout = 'TIMEOUT' ;
153161 segmentClient . appStateSubscription = {
@@ -158,7 +166,7 @@ describe('SegmentClient initialise', () => {
158166 expect ( segmentClient . destroyed ) . toBe ( true ) ;
159167 expect ( clearInterval ) . toHaveBeenCalledTimes ( 1 ) ;
160168 expect ( clearInterval ) . toHaveBeenCalledWith ( 'INTERVAL' ) ;
161- expect ( segmentClient . unsubscribe ) . toHaveBeenCalledTimes ( 1 ) ;
169+ expect ( unsubscribe ) . toHaveBeenCalledTimes ( 1 ) ;
162170 expect ( clearTimeout ) . toHaveBeenCalledTimes ( 1 ) ;
163171 expect ( clearTimeout ) . toHaveBeenCalledWith ( 'TIMEOUT' ) ;
164172 expect ( segmentClient . appStateSubscription . remove ) . toHaveBeenCalledTimes (
@@ -349,152 +357,98 @@ describe('SegmentClient #onUpdateStore', () => {
349357 actions : { } ,
350358 } ;
351359
360+ const sampleEvent : IdentifyEventType = {
361+ userId : 'user-123' ,
362+ anonymousId : 'eWpqvL-EHSHLWoiwagN-T' ,
363+ type : EventType . IdentifyEvent ,
364+ integrations : { } ,
365+ timestamp : '2000-01-01T00:00:00.000Z' ,
366+ traits : {
367+ foo : 'bar' ,
368+ } ,
369+ messageId : 'iDMkR2-I7c2_LCsPPlvwH' ,
370+ } ;
371+
372+ const rootReducer = combineReducers ( {
373+ main : mainSlice . reducer ,
374+ system : systemSlice . reducer ,
375+ userInfo : userInfo . reducer ,
376+ } ) ;
377+ let mockStore = configureStore ( { reducer : rootReducer } ) as Store ;
378+
352379 beforeEach ( ( ) => {
353380 jest . useFakeTimers ( ) ;
381+ // Reset the Redux store to a clean state
382+ mockStore = configureStore ( { reducer : rootReducer } ) as Store ;
354383 } ) ;
355384
356385 afterEach ( ( ) => {
357386 jest . clearAllTimers ( ) ;
358387 jest . clearAllMocks ( ) ;
359388 } ) ;
360389
361- it ( 'calls flush when there are unsent events' , ( ) => {
390+ /**
391+ * Creates a client wired up with store subscriptions and flush mocks for testing automatic flushes
392+ */
393+ const setupClient = ( flushAt : number ) : SegmentClient => {
362394 const args = {
363395 ...clientArgs ,
364396 config : {
365397 ...clientArgs . config ,
366- flushAt : 1 ,
367- } ,
368- store : {
369- ...clientArgs . store ,
370- getState : jest . fn ( ) . mockReturnValue ( {
371- main : {
372- events : [ { messageId : '1' } ] ,
373- eventsToRetry : [ ] ,
374- } ,
375- system : {
376- settings : { } ,
377- } ,
378- } ) ,
398+ flushAt,
379399 } ,
400+ store : mockStore ,
401+ actions : actions ,
380402 } ;
381403 const client = new SegmentClient ( args ) ;
404+ // It is important to setup the flush spy before setting up the subscriptions so that it tracks the calls in the closure
382405 jest . spyOn ( client , 'flush' ) . mockResolvedValueOnce ( ) ;
383- client . onUpdateStore ( ) ;
406+ jest . spyOn ( client , 'flushRetry' ) . mockResolvedValueOnce ( ) ;
407+ client . setupStoreSubscribe ( ) ;
408+ return client ;
409+ } ;
384410
411+ it ( 'calls flush when there are unsent events' , ( ) => {
412+ const client = setupClient ( 1 ) ;
413+ mockStore . dispatch ( mainSlice . actions . addEvent ( { event : sampleEvent } ) ) ;
385414 expect ( client . flush ) . toHaveBeenCalledTimes ( 1 ) ;
386415 } ) ;
387416
388417 it ( 'does not flush when number of events does not exceed the flush threshold' , ( ) => {
389- const args = {
390- ...clientArgs ,
391- config : {
392- ...clientArgs . config ,
393- flushAt : 2 ,
394- } ,
395- store : {
396- ...clientArgs . store ,
397- getState : jest . fn ( ) . mockReturnValue ( {
398- main : {
399- events : [ { messageId : '1' } ] ,
400- eventsToRetry : [ ] ,
401- } ,
402- system : {
403- settings : { } ,
404- } ,
405- } ) ,
406- } ,
407- } ;
408- const client = new SegmentClient ( args ) ;
409- jest . spyOn ( client , 'flush' ) . mockResolvedValueOnce ( ) ;
410- client . onUpdateStore ( ) ;
411-
418+ const client = setupClient ( 2 ) ;
419+ mockStore . dispatch ( mainSlice . actions . addEvent ( { event : sampleEvent } ) ) ;
412420 expect ( client . flush ) . not . toHaveBeenCalled ( ) ;
413421 } ) ;
414422
415423 it ( 'does not call flush when there are no events to send' , ( ) => {
416- const args = {
417- ...clientArgs ,
418- config : {
419- ...clientArgs . config ,
420- flushAt : 1 ,
421- } ,
422- store : {
423- ...clientArgs . store ,
424- getState : jest . fn ( ) . mockReturnValue ( {
425- main : {
426- events : [ ] ,
427- eventsToRetry : [ ] ,
428- } ,
429- system : {
430- settings : { } ,
431- } ,
432- } ) ,
433- } ,
434- } ;
435- const client = new SegmentClient ( args ) ;
436- jest . spyOn ( client , 'flush' ) . mockResolvedValueOnce ( ) ;
437- jest . spyOn ( client , 'flushRetry' ) . mockResolvedValueOnce ( ) ;
438- client . onUpdateStore ( ) ;
439-
424+ const client = setupClient ( 1 ) ;
440425 expect ( client . flush ) . not . toHaveBeenCalled ( ) ;
441426 expect ( client . flushRetry ) . not . toHaveBeenCalled ( ) ;
442427 } ) ;
443428
444429 it ( 'flushes retry queue when it is non-empty' , ( ) => {
445- const args = {
446- ...clientArgs ,
447- config : {
448- ...clientArgs . config ,
449- flushAt : 2 ,
450- } ,
451- store : {
452- ...clientArgs . store ,
453- getState : jest . fn ( ) . mockReturnValue ( {
454- main : {
455- events : [ ] ,
456- eventsToRetry : [ { messageId : '1' } ] ,
457- } ,
458- system : {
459- settings : { } ,
460- } ,
461- } ) ,
462- } ,
463- } ;
464- const client = new SegmentClient ( args ) ;
465- jest . spyOn ( client , 'flush' ) . mockResolvedValueOnce ( ) ;
466- client . onUpdateStore ( ) ;
430+ const client = setupClient ( 2 ) ;
467431
468- expect ( setTimeout ) . toHaveBeenLastCalledWith (
469- expect . any ( Function ) ,
470- args . config . retryInterval ! * 1000
432+ mockStore . dispatch (
433+ mainSlice . actions . addEventsToRetry ( {
434+ events : [ sampleEvent ] ,
435+ config : { ...clientArgs . config } ,
436+ } )
471437 ) ;
472- expect ( client . refreshTimeout ) . not . toBeNull ( ) ;
438+
439+ expect ( client . flushRetry ) . toHaveBeenCalledTimes ( 1 ) ;
473440 } ) ;
474441
475442 it ( 'does not flush the retry queue when the refreshTimeout is not null' , ( ) => {
476- const args = {
477- ...clientArgs ,
478- config : {
479- ...clientArgs . config ,
480- flushAt : 2 ,
481- } ,
482- store : {
483- ...clientArgs . store ,
484- getState : jest . fn ( ) . mockReturnValue ( {
485- main : {
486- events : [ ] ,
487- eventsToRetry : [ { messageId : '1' } ] ,
488- } ,
489- system : {
490- settings : { } ,
491- } ,
492- } ) ,
493- } ,
494- } ;
495- const client = new SegmentClient ( args ) ;
443+ const client = setupClient ( 2 ) ;
496444 client . refreshTimeout = jest . fn ( ) as any ;
497- client . onUpdateStore ( ) ;
445+
446+ mockStore . dispatch (
447+ mainSlice . actions . addEventsToRetry ( {
448+ events : [ sampleEvent ] ,
449+ config : { ...clientArgs . config } ,
450+ } )
451+ ) ;
498452
499453 expect ( setTimeout ) . not . toHaveBeenCalled ( ) ;
500454 } ) ;
@@ -579,8 +533,10 @@ describe('SegmentClient #flushRetry', () => {
579533 it ( 'calls the screen method' , async ( ) => {
580534 const flushRetrySpy = jest . spyOn ( flushRetry , 'default' ) . mockResolvedValue ( ) ;
581535 const client = new SegmentClient ( clientArgs ) ;
536+ client . setupStoreSubscribe ( ) ;
582537
583538 await client . flushRetry ( ) ;
539+ jest . runAllTimers ( ) ;
584540
585541 expect ( flushRetrySpy ) . toHaveBeenCalledTimes ( 1 ) ;
586542 } ) ;
0 commit comments