17
17
18
18
import { expect } from 'chai' ;
19
19
import { FirebaseAnalytics } from '@firebase/analytics-types' ;
20
- import { SinonStub , stub } from 'sinon' ;
20
+ import { SinonStub , stub , useFakeTimers } from 'sinon' ;
21
21
import './testing/setup' ;
22
22
import {
23
23
settings as analyticsSettings ,
@@ -34,51 +34,146 @@ import { GtagCommand, EventName } from './src/constants';
34
34
import { findGtagScriptOnPage } from './src/helpers' ;
35
35
import { removeGtagScript } from './testing/gtag-script-util' ;
36
36
import { Deferred } from '@firebase/util' ;
37
+ import { AnalyticsError } from './src/errors' ;
37
38
38
39
let analyticsInstance : FirebaseAnalytics = { } as FirebaseAnalytics ;
39
- const analyticsId = 'abcd-efgh' ;
40
- const gtagStub : SinonStub = stub ( ) ;
40
+ const fakeMeasurementId = 'abcd-efgh' ;
41
+ const fakeAppParams = { appId : 'abcdefgh12345:23405' , apiKey : 'AAbbCCdd12345' } ;
42
+ let fetchStub : SinonStub = stub ( ) ;
41
43
const customGtagName = 'customGtag' ;
42
44
const customDataLayerName = 'customDataLayer' ;
45
+ let clock : sinon . SinonFakeTimers ;
43
46
44
- describe ( 'FirebaseAnalytics instance tests' , ( ) => {
45
- it ( 'Throws if no analyticsId in config' , ( ) => {
46
- const app = getFakeApp ( ) ;
47
- const installations = getFakeInstallations ( ) ;
48
- expect ( ( ) => analyticsFactory ( app , installations ) ) . to . throw (
49
- 'field is empty'
50
- ) ;
47
+ function stubFetch ( status : number , body : object ) : void {
48
+ fetchStub = stub ( window , 'fetch' ) ;
49
+ const mockResponse = new Response ( JSON . stringify ( body ) , {
50
+ status
51
51
} ) ;
52
- it ( 'Throws if creating an instance with already-used analytics ID' , ( ) => {
53
- const app = getFakeApp ( analyticsId ) ;
54
- const installations = getFakeInstallations ( ) ;
55
- resetGlobalVars ( false , { [ analyticsId ] : Promise . resolve ( ) } ) ;
56
- expect ( ( ) => analyticsFactory ( app , installations ) ) . to . throw (
57
- 'already exists'
58
- ) ;
52
+ fetchStub . returns ( Promise . resolve ( mockResponse ) ) ;
53
+ }
54
+
55
+ describe ( 'FirebaseAnalytics instance tests' , ( ) => {
56
+ describe ( 'Initialization' , ( ) => {
57
+ beforeEach ( ( ) => resetGlobalVars ( ) ) ;
58
+
59
+ it ( 'Throws if no appId in config' , ( ) => {
60
+ const app = getFakeApp ( { apiKey : fakeAppParams . apiKey } ) ;
61
+ const installations = getFakeInstallations ( ) ;
62
+ expect ( ( ) => analyticsFactory ( app , installations ) ) . to . throw (
63
+ AnalyticsError . NO_APP_ID
64
+ ) ;
65
+ } ) ;
66
+ it ( 'Throws if no apiKey or measurementId in config' , ( ) => {
67
+ const app = getFakeApp ( { appId : fakeAppParams . appId } ) ;
68
+ const installations = getFakeInstallations ( ) ;
69
+ expect ( ( ) => analyticsFactory ( app , installations ) ) . to . throw (
70
+ AnalyticsError . NO_API_KEY
71
+ ) ;
72
+ } ) ;
73
+ it ( 'Warns if config has no apiKey but does have a measurementId' , ( ) => {
74
+ const warnStub = stub ( console , 'warn' ) ;
75
+ const app = getFakeApp ( {
76
+ appId : fakeAppParams . appId ,
77
+ measurementId : fakeMeasurementId
78
+ } ) ;
79
+ const installations = getFakeInstallations ( ) ;
80
+ analyticsFactory ( app , installations ) ;
81
+ expect ( warnStub . args [ 0 ] [ 1 ] ) . to . include (
82
+ `Falling back to the measurement ID ${ fakeMeasurementId } `
83
+ ) ;
84
+ warnStub . restore ( ) ;
85
+ } ) ;
86
+ it ( 'Throws if cookies are not enabled' , ( ) => {
87
+ const cookieStub = stub ( navigator , 'cookieEnabled' ) . value ( false ) ;
88
+ const app = getFakeApp ( {
89
+ appId : fakeAppParams . appId ,
90
+ apiKey : fakeAppParams . apiKey
91
+ } ) ;
92
+ const installations = getFakeInstallations ( ) ;
93
+ expect ( ( ) => analyticsFactory ( app , installations ) ) . to . throw (
94
+ AnalyticsError . COOKIES_NOT_ENABLED
95
+ ) ;
96
+ cookieStub . restore ( ) ;
97
+ } ) ;
98
+ it ( 'Throws if browser extension environment' , ( ) => {
99
+ window . chrome = { runtime : { id : 'blah' } } ;
100
+ const app = getFakeApp ( {
101
+ appId : fakeAppParams . appId ,
102
+ apiKey : fakeAppParams . apiKey
103
+ } ) ;
104
+ const installations = getFakeInstallations ( ) ;
105
+ expect ( ( ) => analyticsFactory ( app , installations ) ) . to . throw (
106
+ AnalyticsError . INVALID_ANALYTICS_CONTEXT
107
+ ) ;
108
+ window . chrome = undefined ;
109
+ } ) ;
110
+ it ( 'Throws if indexedDB does not exist' , ( ) => {
111
+ const idbStub = stub ( window , 'indexedDB' ) . value ( undefined ) ;
112
+ const app = getFakeApp ( {
113
+ appId : fakeAppParams . appId ,
114
+ apiKey : fakeAppParams . apiKey
115
+ } ) ;
116
+ const installations = getFakeInstallations ( ) ;
117
+ expect ( ( ) => analyticsFactory ( app , installations ) ) . to . throw (
118
+ AnalyticsError . INDEXED_DB_UNSUPPORTED
119
+ ) ;
120
+ idbStub . restore ( ) ;
121
+ } ) ;
122
+ it ( 'Warns eventually if indexedDB.open() does not work' , async ( ) => {
123
+ clock = useFakeTimers ( ) ;
124
+ stubFetch ( 200 , { measurementId : fakeMeasurementId } ) ;
125
+ const warnStub = stub ( console , 'warn' ) ;
126
+ const idbOpenStub = stub ( indexedDB , 'open' ) . throws (
127
+ 'idb open throw message'
128
+ ) ;
129
+ const app = getFakeApp ( {
130
+ appId : fakeAppParams . appId ,
131
+ apiKey : fakeAppParams . apiKey
132
+ } ) ;
133
+ const installations = getFakeInstallations ( ) ;
134
+ analyticsFactory ( app , installations ) ;
135
+ await clock . runAllAsync ( ) ;
136
+ expect ( warnStub . args [ 0 ] [ 1 ] ) . to . include (
137
+ AnalyticsError . INVALID_INDEXED_DB_CONTEXT
138
+ ) ;
139
+ expect ( warnStub . args [ 0 ] [ 1 ] ) . to . include ( 'idb open throw message' ) ;
140
+ warnStub . restore ( ) ;
141
+ idbOpenStub . restore ( ) ;
142
+ fetchStub . restore ( ) ;
143
+ clock . restore ( ) ;
144
+ } ) ;
145
+ it ( 'Throws if creating an instance with already-used appId' , ( ) => {
146
+ const app = getFakeApp ( fakeAppParams ) ;
147
+ const installations = getFakeInstallations ( ) ;
148
+ resetGlobalVars ( false , { [ fakeAppParams . appId ] : Promise . resolve ( ) } ) ;
149
+ expect ( ( ) => analyticsFactory ( app , installations ) ) . to . throw (
150
+ AnalyticsError . ALREADY_EXISTS
151
+ ) ;
152
+ } ) ;
59
153
} ) ;
60
154
describe ( 'Standard app, page already has user gtag script' , ( ) => {
61
155
let app : FirebaseApp = { } as FirebaseApp ;
62
156
let fidDeferred : Deferred < void > ;
157
+ const gtagStub : SinonStub = stub ( ) ;
63
158
before ( ( ) => {
159
+ clock = useFakeTimers ( ) ;
64
160
resetGlobalVars ( ) ;
65
- app = getFakeApp ( analyticsId ) ;
161
+ app = getFakeApp ( fakeAppParams ) ;
66
162
fidDeferred = new Deferred < void > ( ) ;
67
163
const installations = getFakeInstallations ( 'fid-1234' , ( ) =>
68
164
fidDeferred . resolve ( )
69
165
) ;
70
-
71
166
window [ 'gtag' ] = gtagStub ;
72
167
window [ 'dataLayer' ] = [ ] ;
168
+ stubFetch ( 200 , { measurementId : fakeMeasurementId } ) ;
73
169
analyticsInstance = analyticsFactory ( app , installations ) ;
74
170
} ) ;
75
171
after ( ( ) => {
76
172
delete window [ 'gtag' ] ;
77
173
delete window [ 'dataLayer' ] ;
78
174
removeGtagScript ( ) ;
79
- } ) ;
80
- afterEach ( ( ) => {
81
- gtagStub . reset ( ) ;
175
+ fetchStub . restore ( ) ;
176
+ clock . restore ( ) ;
82
177
} ) ;
83
178
it ( 'Contains reference to parent app' , ( ) => {
84
179
expect ( analyticsInstance . app ) . to . equal ( app ) ;
@@ -87,13 +182,12 @@ describe('FirebaseAnalytics instance tests', () => {
87
182
analyticsInstance . logEvent ( EventName . ADD_PAYMENT_INFO , {
88
183
currency : 'USD'
89
184
} ) ;
90
- // Clear event stack of initialization promise.
91
- const { initializedIdPromisesMap } = getGlobalVars ( ) ;
92
- await Promise . all ( Object . values ( initializedIdPromisesMap ) ) ;
185
+ // Clear promise chain started by logEvent.
186
+ await clock . runAllAsync ( ) ;
93
187
expect ( gtagStub ) . to . have . been . calledWith ( 'js' ) ;
94
188
expect ( gtagStub ) . to . have . been . calledWith (
95
189
GtagCommand . CONFIG ,
96
- analyticsId ,
190
+ fakeMeasurementId ,
97
191
{
98
192
'firebase_id' : 'fid-1234' ,
99
193
origin : 'firebase' ,
@@ -126,10 +220,13 @@ describe('FirebaseAnalytics instance tests', () => {
126
220
} ) ;
127
221
128
222
describe ( 'Page has user gtag script with custom gtag and dataLayer names' , ( ) => {
223
+ let app : FirebaseApp = { } as FirebaseApp ;
129
224
let fidDeferred : Deferred < void > ;
225
+ const gtagStub : SinonStub = stub ( ) ;
130
226
before ( ( ) => {
227
+ clock = useFakeTimers ( ) ;
131
228
resetGlobalVars ( ) ;
132
- const app = getFakeApp ( analyticsId ) ;
229
+ app = getFakeApp ( fakeAppParams ) ;
133
230
fidDeferred = new Deferred < void > ( ) ;
134
231
const installations = getFakeInstallations ( 'fid-1234' , ( ) =>
135
232
fidDeferred . resolve ( )
@@ -140,27 +237,26 @@ describe('FirebaseAnalytics instance tests', () => {
140
237
dataLayerName : customDataLayerName ,
141
238
gtagName : customGtagName
142
239
} ) ;
240
+ stubFetch ( 200 , { measurementId : fakeMeasurementId } ) ;
143
241
analyticsInstance = analyticsFactory ( app , installations ) ;
144
242
} ) ;
145
243
after ( ( ) => {
146
244
delete window [ customGtagName ] ;
147
245
delete window [ customDataLayerName ] ;
148
246
removeGtagScript ( ) ;
149
- } ) ;
150
- afterEach ( ( ) => {
151
- gtagStub . reset ( ) ;
247
+ fetchStub . restore ( ) ;
248
+ clock . restore ( ) ;
152
249
} ) ;
153
250
it ( 'Calls gtag correctly on logEvent (instance)' , async ( ) => {
154
251
analyticsInstance . logEvent ( EventName . ADD_PAYMENT_INFO , {
155
252
currency : 'USD'
156
253
} ) ;
157
- // Clear event stack of initialization promise.
158
- const { initializedIdPromisesMap } = getGlobalVars ( ) ;
159
- await Promise . all ( Object . values ( initializedIdPromisesMap ) ) ;
254
+ // Clear promise chain started by logEvent.
255
+ await clock . runAllAsync ( ) ;
160
256
expect ( gtagStub ) . to . have . been . calledWith ( 'js' ) ;
161
257
expect ( gtagStub ) . to . have . been . calledWith (
162
258
GtagCommand . CONFIG ,
163
- analyticsId ,
259
+ fakeMeasurementId ,
164
260
{
165
261
'firebase_id' : 'fid-1234' ,
166
262
origin : 'firebase' ,
@@ -179,23 +275,23 @@ describe('FirebaseAnalytics instance tests', () => {
179
275
} ) ;
180
276
181
277
describe ( 'Page has no existing gtag script or dataLayer' , ( ) => {
182
- before ( ( ) => {
278
+ it ( 'Adds the script tag to the page' , async ( ) => {
183
279
resetGlobalVars ( ) ;
184
- const app = getFakeApp ( analyticsId ) ;
280
+ const app = getFakeApp ( fakeAppParams ) ;
185
281
const installations = getFakeInstallations ( ) ;
282
+ stubFetch ( 200 , { } ) ;
186
283
analyticsInstance = analyticsFactory ( app , installations ) ;
187
- } ) ;
188
- after ( ( ) => {
189
- delete window [ 'gtag' ] ;
190
- delete window [ 'dataLayer' ] ;
191
- removeGtagScript ( ) ;
192
- } ) ;
193
- it ( 'Adds the script tag to the page' , async ( ) => {
194
- const { initializedIdPromisesMap } = getGlobalVars ( ) ;
195
- await initializedIdPromisesMap [ analyticsId ] ;
284
+
285
+ const { initializationPromisesMap } = getGlobalVars ( ) ;
286
+ await initializationPromisesMap [ fakeAppParams . appId ] ;
196
287
expect ( findGtagScriptOnPage ( ) ) . to . not . be . null ;
197
288
expect ( typeof window [ 'gtag' ] ) . to . equal ( 'function' ) ;
198
289
expect ( Array . isArray ( window [ 'dataLayer' ] ) ) . to . be . true ;
290
+
291
+ delete window [ 'gtag' ] ;
292
+ delete window [ 'dataLayer' ] ;
293
+ removeGtagScript ( ) ;
294
+ fetchStub . restore ( ) ;
199
295
} ) ;
200
296
} ) ;
201
297
} ) ;
0 commit comments