Skip to content

Commit e68a7d9

Browse files
chore(lint): Clean up functions tests
1 parent 6291533 commit e68a7d9

File tree

2 files changed

+168
-163
lines changed

2 files changed

+168
-163
lines changed

src/analytics/analytics.service.ts

Lines changed: 161 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { isPlatformBrowser, isPlatformServer } from '@angular/common';
1010

1111
const FIREBASE_EVENT_ORIGIN_KEY = 'firebase_event_origin';
1212
const FIREBASE_PREVIOUS_SCREEN_CLASS_KEY = 'firebase_previous_class';
13-
const FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY = 'firebase_previous_id';
13+
const FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY = 'firebase_previous_id';
1414
const FIREBASE_PREVIOUS_SCREEN_NAME_KEY = 'firebase_previous_screen';
1515
const FIREBASE_SCREEN_CLASS_KEY = 'firebase_screen_class';
1616
const FIREBASE_SCREEN_INSTANCE_ID_KEY = 'firebase_screen_id';
@@ -30,177 +30,184 @@ const SCREEN_INSTANCE_DELIMITER = '#';
3030
const ANNOTATIONS = '__annotations__';
3131

3232
@Injectable({
33-
providedIn: 'any'
33+
providedIn: 'any'
3434
})
3535
export class ScreenTrackingService implements OnDestroy {
3636

37-
private disposable: Subscription|undefined;
38-
39-
constructor(
40-
analytics: AngularFireAnalytics,
41-
@Optional() router: Router,
42-
@Optional() title: Title,
43-
componentFactoryResolver: ComponentFactoryResolver,
44-
@Inject(PLATFORM_ID) platformId: Object,
45-
@Optional() @Inject(DEBUG_MODE) debugModeEnabled: boolean|null,
46-
zone: NgZone,
47-
injector: Injector
48-
) {
49-
if (!router || !isPlatformBrowser(platformId)) { return this; }
50-
zone.runOutsideAngular(() => {
51-
const activationEndEvents = router.events.pipe(filter<ActivationEnd>(e => e instanceof ActivationEnd));
52-
const navigationEndEvents = router.events.pipe(filter<NavigationEnd>(e => e instanceof NavigationEnd));
53-
this.disposable = navigationEndEvents.pipe(
54-
withLatestFrom(activationEndEvents),
55-
switchMap(([navigationEnd, activationEnd]) => {
56-
// SEMVER: start using optional chains and nullish coalescing once we support newer typescript
57-
const page_path = navigationEnd.url;
58-
const screen_name = activationEnd.snapshot.routeConfig && activationEnd.snapshot.routeConfig.path || page_path;
59-
const params = {
60-
[SCREEN_NAME_KEY]: screen_name,
61-
[PAGE_PATH_KEY]: page_path,
62-
[FIREBASE_EVENT_ORIGIN_KEY]: EVENT_ORIGIN_AUTO,
63-
[FIREBASE_SCREEN_NAME_KEY]: screen_name,
64-
[OUTLET_KEY]: activationEnd.snapshot.outlet
65-
};
66-
if (title) {
67-
params[PAGE_TITLE_KEY] = title.getTitle();
68-
}
69-
const component = activationEnd.snapshot.component;
70-
const routeConfig = activationEnd.snapshot.routeConfig;
71-
const loadChildren = routeConfig && routeConfig.loadChildren;
72-
// TODO figure out how to handle minification
73-
if (typeof loadChildren === 'string') {
74-
// SEMVER: this is the older lazy load style "./path#ClassName", drop this when we drop old ng
75-
// TODO is it worth seeing if I can look up the component factory selector from the module name?
76-
// it's lazy so it's not registered with componentFactoryResolver yet... seems a pain for a depreciated style
77-
return of({...params, [SCREEN_CLASS_KEY]: loadChildren.split('#')[1]});
78-
} else if (typeof component === 'string') {
79-
return of({...params, [SCREEN_CLASS_KEY]: component });
80-
} else if (component) {
81-
const componentFactory = componentFactoryResolver.resolveComponentFactory(component);
82-
return of({...params, [SCREEN_CLASS_KEY]: componentFactory.selector });
83-
} else if (loadChildren) {
84-
const loadedChildren = loadChildren();
85-
const loadedChildren$: Observable<any> = (loadedChildren instanceof Observable) ? loadedChildren : from(Promise.resolve(loadedChildren));
86-
return loadedChildren$.pipe(
87-
map(lazyModule => {
88-
if (lazyModule instanceof NgModuleFactory) {
89-
// AOT create an injector
90-
const moduleRef = lazyModule.create(injector);
91-
// INVESTIGATE is this the right way to get at the matching route?
92-
const routes = moduleRef.injector.get(ROUTES);
93-
const component = routes[0][0].component; // should i just be grabbing 0-0 here?
94-
try {
95-
const componentFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(component!);
96-
return {...params, [SCREEN_CLASS_KEY]: componentFactory.selector};
97-
} catch (_) {
98-
return {...params, [SCREEN_CLASS_KEY]: DEFAULT_SCREEN_CLASS};
99-
}
100-
} else {
101-
// JIT look at the annotations
102-
// INVESTIGATE are there public APIs for this stuff?
103-
const declarations = [].concat.apply([], (lazyModule[ANNOTATIONS] || []).map((f: any) => f.declarations));
104-
const selectors = [].concat.apply([], declarations.map((c: any) => (c[ANNOTATIONS] || []).map((f: any) => f.selector)));
105-
// should I just be grabbing the selector like this or should i match against the route component?
106-
// const routerModule = lazyModule.ngInjectorDef.imports.find(i => i.ngModule && ....);
107-
// const route = routerModule.providers[0].find(p => p.provide == ROUTES).useValue[0];
108-
return {...params, [SCREEN_CLASS_KEY]: selectors[0] || DEFAULT_SCREEN_CLASS};
109-
}
110-
})
111-
);
112-
} else {
113-
return of({...params, [SCREEN_CLASS_KEY]: DEFAULT_SCREEN_CLASS});
114-
}
115-
}),
116-
map(params => ({
117-
[FIREBASE_SCREEN_CLASS_KEY]: params[SCREEN_CLASS_KEY],
118-
[FIREBASE_SCREEN_INSTANCE_ID_KEY]: getScreenInstanceID(params),
119-
...params
120-
})),
121-
tap(params => {
122-
// TODO perhaps I can be smarter about this, bubble events up to the nearest outlet?
123-
if (params[OUTLET_KEY] == NG_PRIMARY_OUTLET) {
124-
analytics.setCurrentScreen(params[SCREEN_NAME_KEY]);
125-
analytics.updateConfig({
126-
[PAGE_PATH_KEY]: params[PAGE_PATH_KEY],
127-
[SCREEN_CLASS_KEY]: params[SCREEN_CLASS_KEY]
128-
});
129-
if (title) {
130-
analytics.updateConfig({ [PAGE_TITLE_KEY]: params[PAGE_TITLE_KEY] });
131-
}
132-
}
133-
}),
134-
groupBy(params => params[OUTLET_KEY]),
135-
mergeMap(group => group.pipe(startWith(undefined), pairwise())),
136-
map(([prior, current]) => prior ? {
137-
[FIREBASE_PREVIOUS_SCREEN_CLASS_KEY]: prior[SCREEN_CLASS_KEY],
138-
[FIREBASE_PREVIOUS_SCREEN_NAME_KEY]: prior[SCREEN_NAME_KEY],
139-
[FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY]: prior[FIREBASE_SCREEN_INSTANCE_ID_KEY],
140-
...current!
141-
} : current!),
142-
tap(params => debugModeEnabled && console.info(SCREEN_VIEW_EVENT, params)),
143-
tap(params => zone.runOutsideAngular(() => analytics.logEvent(SCREEN_VIEW_EVENT, params)))
144-
).subscribe();
145-
});
37+
private disposable: Subscription | undefined;
38+
39+
constructor(
40+
analytics: AngularFireAnalytics,
41+
@Optional() router: Router,
42+
@Optional() title: Title,
43+
componentFactoryResolver: ComponentFactoryResolver,
44+
// tslint:disable-next-line:ban-types
45+
@Inject(PLATFORM_ID) platformId: Object,
46+
@Optional() @Inject(DEBUG_MODE) debugModeEnabled: boolean | null,
47+
zone: NgZone,
48+
injector: Injector
49+
) {
50+
if (!router || !isPlatformBrowser(platformId)) {
51+
return this;
14652
}
147-
148-
ngOnDestroy() {
149-
if (this.disposable) { this.disposable.unsubscribe(); }
53+
zone.runOutsideAngular(() => {
54+
const activationEndEvents = router.events.pipe(filter<ActivationEnd>(e => e instanceof ActivationEnd));
55+
const navigationEndEvents = router.events.pipe(filter<NavigationEnd>(e => e instanceof NavigationEnd));
56+
this.disposable = navigationEndEvents.pipe(
57+
withLatestFrom(activationEndEvents),
58+
switchMap(([navigationEnd, activationEnd]) => {
59+
// SEMVER: start using optional chains and nullish coalescing once we support newer typescript
60+
const pagePath = navigationEnd.url;
61+
const screenName = activationEnd.snapshot.routeConfig && activationEnd.snapshot.routeConfig.path || pagePath;
62+
const params = {
63+
[SCREEN_NAME_KEY]: screenName,
64+
[PAGE_PATH_KEY]: pagePath,
65+
[FIREBASE_EVENT_ORIGIN_KEY]: EVENT_ORIGIN_AUTO,
66+
[FIREBASE_SCREEN_NAME_KEY]: screenName,
67+
[OUTLET_KEY]: activationEnd.snapshot.outlet
68+
};
69+
if (title) {
70+
params[PAGE_TITLE_KEY] = title.getTitle();
71+
}
72+
const component = activationEnd.snapshot.component;
73+
const routeConfig = activationEnd.snapshot.routeConfig;
74+
const loadChildren = routeConfig && routeConfig.loadChildren;
75+
// TODO figure out how to handle minification
76+
if (typeof loadChildren === 'string') {
77+
// SEMVER: this is the older lazy load style "./path#ClassName", drop this when we drop old ng
78+
// TODO is it worth seeing if I can look up the component factory selector from the module name?
79+
// it's lazy so it's not registered with componentFactoryResolver yet... seems a pain for a depreciated style
80+
return of({ ...params, [SCREEN_CLASS_KEY]: loadChildren.split('#')[1] });
81+
} else if (typeof component === 'string') {
82+
return of({ ...params, [SCREEN_CLASS_KEY]: component });
83+
} else if (component) {
84+
const componentFactory = componentFactoryResolver.resolveComponentFactory(component);
85+
return of({ ...params, [SCREEN_CLASS_KEY]: componentFactory.selector });
86+
} else if (loadChildren) {
87+
const loadedChildren = loadChildren();
88+
const loadedChildren$: Observable<any> = (loadedChildren instanceof Observable) ? loadedChildren : from(Promise.resolve(loadedChildren));
89+
return loadedChildren$.pipe(
90+
map(lazyModule => {
91+
if (lazyModule instanceof NgModuleFactory) {
92+
// AOT create an injector
93+
const moduleRef = lazyModule.create(injector);
94+
// INVESTIGATE is this the right way to get at the matching route?
95+
const routes = moduleRef.injector.get(ROUTES);
96+
const component = routes[0][0].component; // should i just be grabbing 0-0 here?
97+
try {
98+
const componentFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(component!);
99+
return { ...params, [SCREEN_CLASS_KEY]: componentFactory.selector };
100+
} catch (_) {
101+
return { ...params, [SCREEN_CLASS_KEY]: DEFAULT_SCREEN_CLASS };
102+
}
103+
} else {
104+
// JIT look at the annotations
105+
// INVESTIGATE are there public APIs for this stuff?
106+
const declarations = [].concat.apply([], (lazyModule[ANNOTATIONS] || []).map((f: any) => f.declarations));
107+
const selectors = [].concat.apply([], declarations.map((c: any) => (c[ANNOTATIONS] || []).map((f: any) => f.selector)));
108+
// should I just be grabbing the selector like this or should i match against the route component?
109+
// const routerModule = lazyModule.ngInjectorDef.imports.find(i => i.ngModule && ....);
110+
// const route = routerModule.providers[0].find(p => p.provide == ROUTES).useValue[0];
111+
return { ...params, [SCREEN_CLASS_KEY]: selectors[0] || DEFAULT_SCREEN_CLASS };
112+
}
113+
})
114+
);
115+
} else {
116+
return of({ ...params, [SCREEN_CLASS_KEY]: DEFAULT_SCREEN_CLASS });
117+
}
118+
}),
119+
map(params => ({
120+
[FIREBASE_SCREEN_CLASS_KEY]: params[SCREEN_CLASS_KEY],
121+
[FIREBASE_SCREEN_INSTANCE_ID_KEY]: getScreenInstanceID(params),
122+
...params
123+
})),
124+
tap(params => {
125+
// TODO perhaps I can be smarter about this, bubble events up to the nearest outlet?
126+
if (params[OUTLET_KEY] === NG_PRIMARY_OUTLET) {
127+
analytics.setCurrentScreen(params[SCREEN_NAME_KEY]);
128+
analytics.updateConfig({
129+
[PAGE_PATH_KEY]: params[PAGE_PATH_KEY],
130+
[SCREEN_CLASS_KEY]: params[SCREEN_CLASS_KEY]
131+
});
132+
if (title) {
133+
analytics.updateConfig({ [PAGE_TITLE_KEY]: params[PAGE_TITLE_KEY] });
134+
}
135+
}
136+
}),
137+
groupBy(params => params[OUTLET_KEY]),
138+
mergeMap(group => group.pipe(startWith(undefined), pairwise())),
139+
map(([prior, current]) => prior ? {
140+
[FIREBASE_PREVIOUS_SCREEN_CLASS_KEY]: prior[SCREEN_CLASS_KEY],
141+
[FIREBASE_PREVIOUS_SCREEN_NAME_KEY]: prior[SCREEN_NAME_KEY],
142+
[FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY]: prior[FIREBASE_SCREEN_INSTANCE_ID_KEY],
143+
...current!
144+
} : current!),
145+
tap(params => debugModeEnabled && console.info(SCREEN_VIEW_EVENT, params)),
146+
tap(params => zone.runOutsideAngular(() => analytics.logEvent(SCREEN_VIEW_EVENT, params)))
147+
).subscribe();
148+
});
149+
}
150+
151+
ngOnDestroy() {
152+
if (this.disposable) {
153+
this.disposable.unsubscribe();
150154
}
155+
}
151156

152157
}
153158

154159
@Injectable({
155-
providedIn: 'any'
160+
providedIn: 'any'
156161
})
157162
export class UserTrackingService implements OnDestroy {
158163

159-
private disposable: Subscription|undefined;
160-
161-
// TODO a user properties injector
162-
constructor(
163-
analytics: AngularFireAnalytics,
164-
zone: NgZone,
165-
@Inject(PLATFORM_ID) platformId: Object
166-
) {
167-
const schedulers = new ɵAngularFireSchedulers(zone);
168-
169-
if (!isPlatformServer(platformId)) {
170-
zone.runOutsideAngular(() => {
171-
// @ts-ignore zap the import in the UMD
172-
this.disposable = from(import('firebase/auth')).pipe(
173-
observeOn(schedulers.outsideAngular),
174-
switchMap(() => analytics.app),
175-
map(app => app.auth()),
176-
switchMap(auth => new Observable<User|null>(auth.onAuthStateChanged.bind(auth))),
177-
switchMap(user => analytics.setUserId(user ? user.uid : null!))
178-
).subscribe();
179-
});
180-
}
164+
private disposable: Subscription | undefined;
165+
166+
// TODO a user properties injector
167+
constructor(
168+
analytics: AngularFireAnalytics,
169+
zone: NgZone,
170+
@Inject(PLATFORM_ID) platformId: Object
171+
) {
172+
const schedulers = new ɵAngularFireSchedulers(zone);
173+
174+
if (!isPlatformServer(platformId)) {
175+
zone.runOutsideAngular(() => {
176+
// @ts-ignore zap the import in the UMD
177+
this.disposable = from(import('firebase/auth')).pipe(
178+
observeOn(schedulers.outsideAngular),
179+
switchMap(() => analytics.app),
180+
map(app => app.auth()),
181+
switchMap(auth => new Observable<User | null>(auth.onAuthStateChanged.bind(auth))),
182+
switchMap(user => analytics.setUserId(user ? user.uid : null!))
183+
).subscribe();
184+
});
181185
}
186+
}
182187

183-
ngOnDestroy() {
184-
if (this.disposable) { this.disposable.unsubscribe(); }
188+
ngOnDestroy() {
189+
if (this.disposable) {
190+
this.disposable.unsubscribe();
185191
}
192+
}
186193
}
187194

188195
// this is an INT64 in iOS/Android but use INT32 cause javascript
189196
let nextScreenInstanceID = Math.floor(Math.random() * (2 ** 32 - 1)) - 2 ** 31;
190197

191-
const knownScreenInstanceIDs: {[key: string]: number} = {};
192-
193-
const getScreenInstanceID = (params: {[key: string]: any}) => {
194-
// unique the screen class against the outlet name
195-
const screenInstanceKey = [
196-
params[SCREEN_CLASS_KEY],
197-
params[OUTLET_KEY]
198-
].join(SCREEN_INSTANCE_DELIMITER);
199-
if (knownScreenInstanceIDs.hasOwnProperty(screenInstanceKey)) {
200-
return knownScreenInstanceIDs[screenInstanceKey];
201-
} else {
202-
const ret = nextScreenInstanceID++;
203-
knownScreenInstanceIDs[screenInstanceKey] = ret;
204-
return ret;
205-
}
198+
const knownScreenInstanceIDs: { [key: string]: number } = {};
199+
200+
const getScreenInstanceID = (params: { [key: string]: any }) => {
201+
// unique the screen class against the outlet name
202+
const screenInstanceKey = [
203+
params[SCREEN_CLASS_KEY],
204+
params[OUTLET_KEY]
205+
].join(SCREEN_INSTANCE_DELIMITER);
206+
if (knownScreenInstanceIDs.hasOwnProperty(screenInstanceKey)) {
207+
return knownScreenInstanceIDs[screenInstanceKey];
208+
} else {
209+
const ret = nextScreenInstanceID++;
210+
knownScreenInstanceIDs[screenInstanceKey] = ret;
211+
return ret;
212+
}
206213
};

src/functions/functions.spec.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { inject, TestBed } from '@angular/core/testing';
1+
import { TestBed } from '@angular/core/testing';
22
import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp } from '@angular/fire';
33
import { AngularFireFunctions, AngularFireFunctionsModule, ORIGIN, REGION } from './public_api';
44
import { COMMON_CONFIG } from '../test-config';
@@ -16,10 +16,9 @@ describe('AngularFireFunctions', () => {
1616
AngularFireFunctionsModule
1717
]
1818
});
19-
inject([FirebaseApp, AngularFireFunctions], (app_: FirebaseApp, _fn: AngularFireFunctions) => {
20-
app = app_;
21-
afFns = _fn;
22-
})();
19+
20+
app = TestBed.inject(FirebaseApp);
21+
afFns = TestBed.inject(AngularFireFunctions);
2322
});
2423

2524
afterEach(() => {
@@ -55,10 +54,9 @@ describe('AngularFireFunctions with different app', () => {
5554
{ provide: REGION, useValue: 'asia-northeast1' }
5655
]
5756
});
58-
inject([FirebaseApp, AngularFireFunctions], (app_: FirebaseApp, _fns: AngularFireFunctions) => {
59-
app = app_;
60-
afFns = _fns;
61-
})();
57+
58+
app = TestBed.inject(FirebaseApp);
59+
afFns = TestBed.inject(AngularFireFunctions);
6260
});
6361

6462
afterEach(() => {

0 commit comments

Comments
 (0)