11import { CorePlugin , PluginType , sleep } from '@segment/analytics-core'
2- import { getBufferedPageCtxFixture } from '../../test-helpers/fixtures'
2+ import {
3+ createMockFetchImplementation ,
4+ createRemotePlugin ,
5+ getBufferedPageCtxFixture ,
6+ } from '../../test-helpers/fixtures'
37import unfetch from 'unfetch'
48import { AnalyticsBrowser } from '..'
59import { Analytics } from '../../core/analytics'
6- import { createSuccess } from '../../test-helpers/factories'
710import { createDeferred } from '@segment/analytics-generic-utils'
811import { PluginFactory } from '../../plugins/remote-loader'
912
1013const nextTickP = ( ) => new Promise ( ( r ) => setTimeout ( r , 0 ) )
1114
1215jest . mock ( 'unfetch' )
1316
14- const mockFetchSettingsSuccessResponse = ( ) => {
15- return jest
16- . mocked ( unfetch )
17- . mockImplementation ( ( ) => createSuccess ( { integrations : { } } ) )
18- }
17+ beforeEach ( ( ) => {
18+ document . head . innerHTML = `
19+ <script id="initial"></script>` . trim ( )
20+ } )
1921
2022describe ( 'Lazy initialization' , ( ) => {
2123 let trackSpy : jest . SpiedFunction < Analytics [ 'track' ] >
2224 let fetched : jest . MockedFn < typeof unfetch >
2325 beforeEach ( ( ) => {
24- fetched = mockFetchSettingsSuccessResponse ( )
26+ fetched = jest
27+ . mocked ( unfetch )
28+ . mockImplementation ( createMockFetchImplementation ( ) )
2529 trackSpy = jest . spyOn ( Analytics . prototype , 'track' )
2630 } )
2731
@@ -56,7 +60,10 @@ describe('Lazy initialization', () => {
5660const createTestPluginFactory = ( name : string , type : PluginType ) => {
5761 const lock = createDeferred < void > ( )
5862 const load = createDeferred < void > ( )
59- const trackSpy = jest . fn ( )
63+ const trackSpy = jest . fn ( ) . mockImplementation ( ( ctx ) => {
64+ ctx . event . context ! . ran = true
65+ return ctx
66+ } )
6067
6168 const factory : PluginFactory = ( ) => {
6269 return {
@@ -83,91 +90,158 @@ const createTestPluginFactory = (name: string, type: PluginType) => {
8390
8491describe ( 'Lazy destination loading' , ( ) => {
8592 beforeEach ( ( ) => {
86- jest . mock ( 'unfetch' )
87- jest . mocked ( unfetch ) . mockImplementation ( ( ) =>
88- createSuccess ( {
89- integrations : { } ,
93+ jest . mocked ( unfetch ) . mockImplementation (
94+ createMockFetchImplementation ( {
95+ integrations : {
96+ braze : { } ,
97+ google : { } ,
98+ } ,
9099 remotePlugins : [
91- {
92- name : 'braze' ,
93- libraryName : 'braze' ,
94- } ,
95- {
96- name : 'google' ,
97- libraryName : 'google' ,
98- } ,
100+ createRemotePlugin ( 'braze' ) ,
101+ createRemotePlugin ( 'google' ) ,
99102 ] ,
100103 } )
101104 )
102105 } )
103106
104107 afterAll ( ( ) => jest . resetAllMocks ( ) )
105108
106- it ( 'loads destinations in the background' , async ( ) => {
107- const testEnrichmentHarness = createTestPluginFactory (
108- 'enrichIt' ,
109- 'enrichment'
110- )
111- const dest1Harness = createTestPluginFactory ( 'braze' , 'destination' )
112- const dest2Harness = createTestPluginFactory ( 'google' , 'destination' )
109+ describe ( 'critical plugins (plugins that block the event pipeline)' , ( ) => {
110+ test ( 'pipeline _will_ wait for *enrichment* plugins to load' , async ( ) => {
111+ jest . mocked ( unfetch ) . mockImplementation (
112+ createMockFetchImplementation ( {
113+ remotePlugins : [ ] ,
114+ } )
115+ )
116+ const testEnrichmentHarness = createTestPluginFactory (
117+ 'enrichIt' ,
118+ 'enrichment'
119+ )
113120
114- const analytics = new AnalyticsBrowser ( )
121+ const analytics = new AnalyticsBrowser ( )
115122
116- const testEnrichmentPlugin = testEnrichmentHarness . factory (
117- null
118- ) as CorePlugin
123+ const testPlugin = testEnrichmentHarness . factory ( null ) as CorePlugin
119124
120- analytics . register ( testEnrichmentPlugin ) . catch ( ( ) => { } )
125+ analytics . register ( testPlugin ) . catch ( ( ) => { } )
126+ analytics . track ( 'test event 1' ) . catch ( ( ) => { } )
121127
122- await analytics . load ( {
123- writeKey : 'abc' ,
124- plugins : [ dest1Harness . factory , dest2Harness . factory ] ,
128+ const analyticsLoaded = analytics . load ( {
129+ writeKey : 'abc' ,
130+ plugins : [ ] ,
131+ } )
132+
133+ expect ( testEnrichmentHarness . trackSpy ) . not . toHaveBeenCalled ( )
134+
135+ // now we'll let the enrichment plugin load
136+ testEnrichmentHarness . loadingGuard . resolve ( )
137+
138+ await analyticsLoaded
139+ await sleep ( 200 )
140+ expect ( testEnrichmentHarness . trackSpy ) . toHaveBeenCalledTimes ( 1 )
141+ } )
142+
143+ test ( 'pipeline _will_ wait for *before* plugins to load' , async ( ) => {
144+ jest . mocked ( unfetch ) . mockImplementation (
145+ createMockFetchImplementation ( {
146+ remotePlugins : [ ] ,
147+ } )
148+ )
149+ const testBeforeHarness = createTestPluginFactory ( 'enrichIt' , 'before' )
150+
151+ const analytics = new AnalyticsBrowser ( )
152+
153+ const testPlugin = testBeforeHarness . factory ( null ) as CorePlugin
154+
155+ analytics . register ( testPlugin ) . catch ( ( ) => { } )
156+ analytics . track ( 'test event 1' ) . catch ( ( ) => { } )
157+
158+ const analyticsLoaded = analytics . load ( {
159+ writeKey : 'abc' ,
160+ plugins : [ ] ,
161+ } )
162+
163+ expect ( testBeforeHarness . trackSpy ) . not . toHaveBeenCalled ( )
164+
165+ // now we'll let the before plugin load
166+ testBeforeHarness . loadingGuard . resolve ( )
167+
168+ await analyticsLoaded
169+ await sleep ( 200 )
170+ expect ( testBeforeHarness . trackSpy ) . toHaveBeenCalledTimes ( 1 )
125171 } )
172+ } )
126173
127- // we won't hold enrichment plugin from loading since they are not lazy loaded
128- testEnrichmentHarness . loadingGuard . resolve ( )
129- // and we'll also let one destination load so we can assert some behaviours
130- dest1Harness . loadingGuard . resolve ( )
174+ describe ( 'non-critical plugins (plugins that do not block the event pipeline)' , ( ) => {
175+ it ( 'destination loading does not block the event pipeline, but enrichment plugins do' , async ( ) => {
176+ const testEnrichmentHarness = createTestPluginFactory (
177+ 'enrichIt' ,
178+ 'enrichment'
179+ )
180+ const dest1Harness = createTestPluginFactory ( 'braze' , 'destination' )
181+ const dest2Harness = createTestPluginFactory ( 'google' , 'destination' )
131182
132- await testEnrichmentHarness . loadPromise
133- await dest1Harness . loadPromise
183+ const analytics = new AnalyticsBrowser ( )
134184
135- analytics . track ( 'test event 1' ) . catch ( ( ) => { } )
185+ const testEnrichmentPlugin = testEnrichmentHarness . factory (
186+ null
187+ ) as CorePlugin
136188
137- // even though there's one destination that still hasn't loaded, the next assertions
138- // prove that the event pipeline is flowing regardless
189+ analytics . register ( testEnrichmentPlugin ) . catch ( ( ) => { } )
139190
140- await nextTickP ( )
141- expect ( testEnrichmentHarness . trackSpy ) . toHaveBeenCalledTimes ( 1 )
191+ const p = analytics . load ( {
192+ writeKey : 'abc' ,
193+ plugins : [ dest1Harness . factory , dest2Harness . factory ] ,
194+ } )
142195
143- await nextTickP ( )
144- expect ( dest1Harness . trackSpy ) . toHaveBeenCalledTimes ( 1 )
196+ // we won't hold enrichment plugin from loading since they are not lazy loaded
197+ testEnrichmentHarness . loadingGuard . resolve ( )
198+ await p
199+ // and we'll also let one destination load so we can assert some behaviours
200+ dest1Harness . loadingGuard . resolve ( )
145201
146- // now we'll send another event
202+ analytics . track ( 'test event 1' ) . catch ( ( ) => { } )
147203
148- analytics . track ( 'test event 2' ) . catch ( ( ) => { } )
204+ // even though there's one destination that still hasn't loaded, the next assertions
205+ // prove that the event pipeline is flowing regardless
149206
150- // even though there's one destination that still hasn't loaded, the next assertions
151- // prove that the event pipeline is flowing regardless
207+ await nextTickP ( )
208+ expect ( testEnrichmentHarness . trackSpy ) . toHaveBeenCalledTimes ( 1 )
152209
153- await nextTickP ( )
154- expect ( testEnrichmentHarness . trackSpy ) . toHaveBeenCalledTimes ( 2 )
210+ await nextTickP ( )
211+ expect ( dest1Harness . trackSpy ) . toHaveBeenCalledTimes ( 1 )
212+ expect ( dest1Harness . trackSpy . mock . calls [ 0 ] [ 0 ] . event . context . ran ) . toBe (
213+ true
214+ )
155215
156- await nextTickP ( )
157- expect ( dest1Harness . trackSpy ) . toHaveBeenCalledTimes ( 2 )
216+ // now we'll send another event
158217
159- // this whole time the other destination was not engaged with at all
160- expect ( dest2Harness . trackSpy ) . not . toHaveBeenCalled ( )
218+ analytics . track ( 'test event 2' ) . catch ( ( ) => { } )
161219
162- // now "after some time" the other destination will load
163- dest2Harness . loadingGuard . resolve ( )
164- await dest2Harness . loadPromise
220+ // even though there's one destination that still hasn't loaded, the next assertions
221+ // prove that the event pipeline is flowing regardless
165222
166- // and now that it is "online" - the previous events that it missed will be handed over
167- await nextTickP ( )
168- expect ( dest2Harness . trackSpy ) . toHaveBeenCalledTimes ( 2 )
169- } )
223+ await nextTickP ( )
224+ expect ( testEnrichmentHarness . trackSpy ) . toHaveBeenCalledTimes ( 2 )
225+
226+ await nextTickP ( )
227+ expect ( dest1Harness . trackSpy ) . toHaveBeenCalledTimes ( 2 )
228+
229+ // this whole time the other destination was not engaged with at all
230+ expect ( dest2Harness . trackSpy ) . not . toHaveBeenCalled ( )
170231
232+ // now "after some time" the other destination will load
233+ dest2Harness . loadingGuard . resolve ( )
234+ await dest2Harness . loadPromise
235+
236+ // and now that it is "online" - the previous events that it missed will be handed over
237+ await nextTickP ( )
238+ expect ( dest2Harness . trackSpy ) . toHaveBeenCalledTimes ( 2 )
239+
240+ // should not add any other script tags
241+ expect ( document . querySelectorAll ( 'script' ) . length ) . toBe ( 1 )
242+ expect ( document . getElementsByTagName ( 'script' ) [ 0 ] . id ) . toBe ( 'initial' )
243+ } )
244+ } )
171245 it ( 'emits initialize regardless of whether all destinations have loaded' , async ( ) => {
172246 const dest1Harness = createTestPluginFactory ( 'braze' , 'destination' )
173247 const dest2Harness = createTestPluginFactory ( 'google' , 'destination' )
0 commit comments