Skip to content

Commit c9b40b5

Browse files
committed
refactor(core): make Testability compatible with tree-shaking (#45657)
This commit refactors the `Testability`-related logic to extract the necessary providers into a separate array, so that it can later become it's own NgModule (or exposed as an array of providers) and be excluded from the new APIs by default. PR Close #45657
1 parent d380bb4 commit c9b40b5

File tree

16 files changed

+149
-67
lines changed

16 files changed

+149
-67
lines changed

goldens/public-api/core/index.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,7 +1272,7 @@ export abstract class TemplateRef<C> {
12721272

12731273
// @public
12741274
export class Testability implements PublicTestability {
1275-
constructor(_ngZone: NgZone);
1275+
constructor(_ngZone: NgZone, registry: TestabilityRegistry, testabilityGetter: GetTestability);
12761276
// @deprecated
12771277
decreasePendingRequestCount(): number;
12781278
findProviders(using: any, provider: string, exactMatch: boolean): any[];
@@ -1290,7 +1290,6 @@ export class Testability implements PublicTestability {
12901290

12911291
// @public
12921292
export class TestabilityRegistry {
1293-
constructor();
12941293
findTestabilityInTree(elem: Node, findInAncestors?: boolean): Testability | null;
12951294
getAllRootElements(): any[];
12961295
getAllTestabilities(): Testability[];

goldens/size-tracking/aio-payloads.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"aio-local": {
1313
"uncompressed": {
1414
"runtime": 4343,
15-
"main": 454248,
15+
"main": 454794,
1616
"polyfills": 33980,
1717
"styles": 71714,
1818
"light-theme": 78213,

goldens/size-tracking/integration-payloads.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"cli-hello-world": {
33
"uncompressed": {
44
"runtime": 1083,
5-
"main": 126738,
5+
"main": 126542,
66
"polyfills": 33824
77
}
88
},
@@ -19,7 +19,7 @@
1919
"cli-hello-world-ivy-compat": {
2020
"uncompressed": {
2121
"runtime": 1102,
22-
"main": 133348,
22+
"main": 133968,
2323
"polyfills": 33957
2424
}
2525
},
@@ -41,7 +41,7 @@
4141
"forms": {
4242
"uncompressed": {
4343
"runtime": 1063,
44-
"main": 158922,
44+
"main": 159280,
4545
"polyfills": 33804
4646
}
4747
},

packages/core/src/application_ref.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import {setJitOptions} from './render3/jit/jit_options';
4040
import {isStandalone} from './render3/jit/module';
4141
import {createEnvironmentInjector, NgModuleFactory as R3NgModuleFactory} from './render3/ng_module_ref';
4242
import {publishDefaultGlobalUtils as _publishDefaultGlobalUtils} from './render3/util/global_utils';
43-
import {Testability, TestabilityRegistry} from './testability/testability';
43+
import {TESTABILITY} from './testability/testability';
4444
import {isPromise} from './util/lang';
4545
import {scheduleMicroTask} from './util/microtask';
4646
import {stringify} from './util/stringify';
@@ -944,18 +944,13 @@ export class ApplicationRef {
944944
const selectorOrNode = rootSelectorOrNode || componentFactory.selector;
945945
const compRef = componentFactory.create(Injector.NULL, [], selectorOrNode, ngModule);
946946
const nativeElement = compRef.location.nativeElement;
947-
const testability = compRef.injector.get(Testability, null);
948-
const testabilityRegistry = testability && compRef.injector.get(TestabilityRegistry);
949-
if (testability && testabilityRegistry) {
950-
testabilityRegistry.registerApplication(nativeElement, testability);
951-
}
947+
const testability = compRef.injector.get(TESTABILITY, null);
948+
testability?.registerApplication(nativeElement);
952949

953950
compRef.onDestroy(() => {
954951
this.detachView(compRef.hostView);
955952
remove(this.components, compRef);
956-
if (testabilityRegistry) {
957-
testabilityRegistry.unregisterApplication(nativeElement);
958-
}
953+
testability?.unregisterApplication(nativeElement);
959954
});
960955

961956
this._loadComponent(compRef);

packages/core/src/core_private_export.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/r
2424
export {allowSanitizationBypassAndThrow as ɵallowSanitizationBypassAndThrow, BypassType as ɵBypassType, getSanitizationBypassType as ɵgetSanitizationBypassType, SafeHtml as ɵSafeHtml, SafeResourceUrl as ɵSafeResourceUrl, SafeScript as ɵSafeScript, SafeStyle as ɵSafeStyle, SafeUrl as ɵSafeUrl, SafeValue as ɵSafeValue, unwrapSafeValue as ɵunwrapSafeValue} from './sanitization/bypass';
2525
export {_sanitizeHtml as ɵ_sanitizeHtml} from './sanitization/html_sanitizer';
2626
export {_sanitizeUrl as ɵ_sanitizeUrl} from './sanitization/url_sanitizer';
27+
export {TESTABILITY as ɵTESTABILITY, TESTABILITY_GETTER as ɵTESTABILITY_GETTER} from './testability/testability';
2728
export {coerceToBoolean as ɵcoerceToBoolean} from './util/coercion';
2829
export {devModeEqual as ɵdevModeEqual} from './util/comparison';
2930
export {makeDecorator as ɵmakeDecorator} from './util/decorators';

packages/core/src/testability/testability.ts

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Injectable} from '../di';
9+
import {Inject, Injectable, InjectionToken} from '../di';
1010
import {scheduleMicroTask} from '../util/microtask';
1111
import {NgZone} from '../zone/ng_zone';
1212

@@ -48,6 +48,22 @@ interface WaitCallback {
4848
updateCb?: UpdateCallback;
4949
}
5050

51+
/**
52+
* Internal injection token that can used to access an instance of a Testability class.
53+
*
54+
* This token acts as a bridge between the core bootstrap code and the `Testability` class. This is
55+
* needed to ensure that there are no direct references to the `Testability` class, so it can be
56+
* tree-shaken away (if not referenced). For the environments/setups when the `Testability` class
57+
* should be available, this token is used to add a provider that references the `Testability`
58+
* class. Otherwise, only this token is retained in a bundle, but the `Testability` class is not.
59+
*/
60+
export const TESTABILITY = new InjectionToken<Testability>('');
61+
62+
/**
63+
* Internal injection token to retrieve Testability getter class instance.
64+
*/
65+
export const TESTABILITY_GETTER = new InjectionToken<GetTestability>('');
66+
5167
/**
5268
* The Testability service provides testing hooks that can be accessed from
5369
* the browser. Each bootstrapped Angular application on the page will have
@@ -69,7 +85,15 @@ export class Testability implements PublicTestability {
6985

7086
private taskTrackingZone: {macroTasks: Task[]}|null = null;
7187

72-
constructor(private _ngZone: NgZone) {
88+
constructor(
89+
private _ngZone: NgZone, private registry: TestabilityRegistry,
90+
@Inject(TESTABILITY_GETTER) testabilityGetter: GetTestability) {
91+
// If there was no Testability logic registered in the global scope
92+
// before, register the current testability getter as a global one.
93+
if (!_testabilityGetter) {
94+
setTestabilityGetter(testabilityGetter);
95+
testabilityGetter.addToWindow(registry);
96+
}
7397
this._watchAngularEvents();
7498
_ngZone.run(() => {
7599
this.taskTrackingZone =
@@ -213,6 +237,25 @@ export class Testability implements PublicTestability {
213237
getPendingRequestCount(): number {
214238
return this._pendingCount;
215239
}
240+
/**
241+
* Registers an application with a testability hook so that it can be tracked.
242+
* @param token token of application, root element
243+
*
244+
* @internal
245+
*/
246+
registerApplication(token: any) {
247+
this.registry.registerApplication(token, this);
248+
}
249+
250+
/**
251+
* Unregisters an application.
252+
* @param token token of application, root element
253+
*
254+
* @internal
255+
*/
256+
unregisterApplication(token: any) {
257+
this.registry.unregisterApplication(token);
258+
}
216259

217260
/**
218261
* Find providers by name
@@ -235,10 +278,6 @@ export class TestabilityRegistry {
235278
/** @internal */
236279
_applications = new Map<any, Testability>();
237280

238-
constructor() {
239-
_testabilityGetter.addToWindow(this);
240-
}
241-
242281
/**
243282
* Registers an application with a testability hook so that it can be tracked
244283
* @param token token of application, root element
@@ -292,7 +331,7 @@ export class TestabilityRegistry {
292331
* current node
293332
*/
294333
findTestabilityInTree(elem: Node, findInAncestors: boolean = true): Testability|null {
295-
return _testabilityGetter.findTestabilityInTree(this, elem, findInAncestors);
334+
return _testabilityGetter?.findTestabilityInTree(this, elem, findInAncestors) ?? null;
296335
}
297336
}
298337

@@ -308,14 +347,6 @@ export interface GetTestability {
308347
Testability|null;
309348
}
310349

311-
class _NoopGetTestability implements GetTestability {
312-
addToWindow(registry: TestabilityRegistry): void {}
313-
findTestabilityInTree(registry: TestabilityRegistry, elem: any, findInAncestors: boolean):
314-
Testability|null {
315-
return null;
316-
}
317-
}
318-
319350
/**
320351
* Set the {@link GetTestability} implementation used by the Angular testing framework.
321352
* @publicApi
@@ -324,4 +355,4 @@ export function setTestabilityGetter(getter: GetTestability): void {
324355
_testabilityGetter = getter;
325356
}
326357

327-
let _testabilityGetter: GetTestability = new _NoopGetTestability();
358+
let _testabilityGetter: GetTestability|undefined;

packages/core/test/bundling/animations/bundle.golden_symbols.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,6 @@
9595
{
9696
"name": "BrowserDomAdapter"
9797
},
98-
{
99-
"name": "BrowserGetTestability"
100-
},
10198
{
10299
"name": "BrowserModule"
103100
},
@@ -446,6 +443,15 @@
446443
{
447444
"name": "Subscription"
448445
},
446+
{
447+
"name": "TESTABILITY"
448+
},
449+
{
450+
"name": "TESTABILITY_GETTER"
451+
},
452+
{
453+
"name": "TESTABILITY_PROVIDERS"
454+
},
449455
{
450456
"name": "THROW_IF_NOT_FOUND"
451457
},

packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@
4747
{
4848
"name": "BrowserDomAdapter"
4949
},
50-
{
51-
"name": "BrowserGetTestability"
52-
},
5350
{
5451
"name": "BrowserModule"
5552
},
@@ -458,6 +455,15 @@
458455
{
459456
"name": "Subscription"
460457
},
458+
{
459+
"name": "TESTABILITY"
460+
},
461+
{
462+
"name": "TESTABILITY_GETTER"
463+
},
464+
{
465+
"name": "TESTABILITY_PROVIDERS"
466+
},
461467
{
462468
"name": "THROW_IF_NOT_FOUND"
463469
},

packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,6 @@
5050
{
5151
"name": "BrowserDomAdapter"
5252
},
53-
{
54-
"name": "BrowserGetTestability"
55-
},
5653
{
5754
"name": "BrowserModule"
5855
},
@@ -449,6 +446,15 @@
449446
{
450447
"name": "Subscription"
451448
},
449+
{
450+
"name": "TESTABILITY"
451+
},
452+
{
453+
"name": "TESTABILITY_GETTER"
454+
},
455+
{
456+
"name": "TESTABILITY_PROVIDERS"
457+
},
452458
{
453459
"name": "THROW_IF_NOT_FOUND"
454460
},

packages/core/test/bundling/router/bundle.golden_symbols.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,6 @@
5353
{
5454
"name": "BrowserDomAdapter"
5555
},
56-
{
57-
"name": "BrowserGetTestability"
58-
},
5956
{
6057
"name": "BrowserModule"
6158
},
@@ -641,6 +638,15 @@
641638
{
642639
"name": "SwitchMapSubscriber"
643640
},
641+
{
642+
"name": "TESTABILITY"
643+
},
644+
{
645+
"name": "TESTABILITY_GETTER"
646+
},
647+
{
648+
"name": "TESTABILITY_PROVIDERS"
649+
},
644650
{
645651
"name": "THROW_IF_NOT_FOUND"
646652
},

0 commit comments

Comments
 (0)