1
1
import { 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'
3
7
import unfetch from 'unfetch'
4
8
import { AnalyticsBrowser } from '..'
5
9
import { Analytics } from '../../core/analytics'
6
- import { createSuccess } from '../../test-helpers/factories'
7
10
import { createDeferred } from '@segment/analytics-generic-utils'
8
11
import { PluginFactory } from '../../plugins/remote-loader'
9
12
10
13
const nextTickP = ( ) => new Promise ( ( r ) => setTimeout ( r , 0 ) )
11
14
12
15
jest . mock ( 'unfetch' )
13
16
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
+ } )
19
21
20
22
describe ( 'Lazy initialization' , ( ) => {
21
23
let trackSpy : jest . SpiedFunction < Analytics [ 'track' ] >
22
24
let fetched : jest . MockedFn < typeof unfetch >
23
25
beforeEach ( ( ) => {
24
- fetched = mockFetchSettingsSuccessResponse ( )
26
+ fetched = jest
27
+ . mocked ( unfetch )
28
+ . mockImplementation ( createMockFetchImplementation ( ) )
25
29
trackSpy = jest . spyOn ( Analytics . prototype , 'track' )
26
30
} )
27
31
@@ -56,7 +60,10 @@ describe('Lazy initialization', () => {
56
60
const createTestPluginFactory = ( name : string , type : PluginType ) => {
57
61
const lock = createDeferred < void > ( )
58
62
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
+ } )
60
67
61
68
const factory : PluginFactory = ( ) => {
62
69
return {
@@ -83,91 +90,158 @@ const createTestPluginFactory = (name: string, type: PluginType) => {
83
90
84
91
describe ( 'Lazy destination loading' , ( ) => {
85
92
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
+ } ,
90
99
remotePlugins : [
91
- {
92
- name : 'braze' ,
93
- libraryName : 'braze' ,
94
- } ,
95
- {
96
- name : 'google' ,
97
- libraryName : 'google' ,
98
- } ,
100
+ createRemotePlugin ( 'braze' ) ,
101
+ createRemotePlugin ( 'google' ) ,
99
102
] ,
100
103
} )
101
104
)
102
105
} )
103
106
104
107
afterAll ( ( ) => jest . resetAllMocks ( ) )
105
108
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
+ )
113
120
114
- const analytics = new AnalyticsBrowser ( )
121
+ const analytics = new AnalyticsBrowser ( )
115
122
116
- const testEnrichmentPlugin = testEnrichmentHarness . factory (
117
- null
118
- ) as CorePlugin
123
+ const testPlugin = testEnrichmentHarness . factory ( null ) as CorePlugin
119
124
120
- analytics . register ( testEnrichmentPlugin ) . catch ( ( ) => { } )
125
+ analytics . register ( testPlugin ) . catch ( ( ) => { } )
126
+ analytics . track ( 'test event 1' ) . catch ( ( ) => { } )
121
127
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 )
125
171
} )
172
+ } )
126
173
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' )
131
182
132
- await testEnrichmentHarness . loadPromise
133
- await dest1Harness . loadPromise
183
+ const analytics = new AnalyticsBrowser ( )
134
184
135
- analytics . track ( 'test event 1' ) . catch ( ( ) => { } )
185
+ const testEnrichmentPlugin = testEnrichmentHarness . factory (
186
+ null
187
+ ) as CorePlugin
136
188
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 ( ( ) => { } )
139
190
140
- await nextTickP ( )
141
- expect ( testEnrichmentHarness . trackSpy ) . toHaveBeenCalledTimes ( 1 )
191
+ const p = analytics . load ( {
192
+ writeKey : 'abc' ,
193
+ plugins : [ dest1Harness . factory , dest2Harness . factory ] ,
194
+ } )
142
195
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 ( )
145
201
146
- // now we'll send another event
202
+ analytics . track ( 'test event 1' ) . catch ( ( ) => { } )
147
203
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
149
206
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 )
152
209
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
+ )
155
215
156
- await nextTickP ( )
157
- expect ( dest1Harness . trackSpy ) . toHaveBeenCalledTimes ( 2 )
216
+ // now we'll send another event
158
217
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 ( ( ) => { } )
161
219
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
165
222
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 ( )
170
231
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
+ } )
171
245
it ( 'emits initialize regardless of whether all destinations have loaded' , async ( ) => {
172
246
const dest1Harness = createTestPluginFactory ( 'braze' , 'destination' )
173
247
const dest2Harness = createTestPluginFactory ( 'google' , 'destination' )
0 commit comments