Skip to content

Commit 9f479ae

Browse files
atscottleonsenft
authored andcommitted
feat(core): Update Testability to use PendingTasks for stability indicator
Since 12181b9, zone stability contributes to the PendingTasks. There is now a single source of truth for application stability tracked in PendingTasks. This change makes protractor's whenStable compatible with zoneless. The `Router` and `HttpClient` also contribute to stability using the `PendingTasks` injectable. There will likely be more updates in the future to have more features contribute to stableness in a zoneless compatible way. This update uses PendingTasks for stability by default when ZoneJS is not present or can be enabled with an option when ZoneJS is present (but otherwise ignored with ZoneJS). fixes #68180
1 parent 3ae40e6 commit 9f479ae

7 files changed

Lines changed: 178 additions & 77 deletions

File tree

goldens/public-api/platform-browser/index.api.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,9 @@ export const platformBrowser: (extraProviders?: StaticProvider[]) => PlatformRef
159159
export function provideClientHydration(...features: HydrationFeature<HydrationFeatureKind>[]): EnvironmentProviders;
160160

161161
// @public
162-
export function provideProtractorTestingSupport(): Provider[];
162+
export function provideProtractorTestingSupport(options?: {
163+
usePendingTasksForStability?: boolean;
164+
}): Provider[];
163165

164166
// @public
165167
export const REMOVE_STYLES_ON_COMPONENT_DESTROY: InjectionToken<boolean>;

packages/core/src/core_private_export.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export {_sanitizeUrl as ɵ_sanitizeUrl} from './sanitization/url_sanitizer';
153153
export {
154154
TESTABILITY as ɵTESTABILITY,
155155
TESTABILITY_GETTER as ɵTESTABILITY_GETTER,
156+
USE_PENDING_TASKS as ɵUSE_PENDING_TASKS,
156157
} from './testability/testability';
157158
export {booleanAttribute, numberAttribute} from './util/coercion';
158159
export {devModeEqual as ɵdevModeEqual} from './util/comparison';

packages/core/src/testability/testability.ts

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {inject, Inject, Injectable, InjectionToken} from '../di';
9+
import {Inject, Injectable, InjectionToken, inject} from '../di';
1010
import {isInInjectionContext} from '../di/contextual';
1111
import {DestroyRef} from '../linker/destroy_ref';
12+
import {PendingTasksInternal} from '../pending_tasks_internal';
1213
import {NgZone} from '../zone/ng_zone';
1314

1415
/**
@@ -61,6 +62,14 @@ export const TESTABILITY = new InjectionToken<Testability>('');
6162
*/
6263
export const TESTABILITY_GETTER = new InjectionToken<GetTestability>('');
6364

65+
/**
66+
* Internal injection token to signal whether to use pending tasks for stability.
67+
*/
68+
export const USE_PENDING_TASKS = new InjectionToken<boolean>('USE_PENDING_TASKS', {
69+
providedIn: 'root',
70+
factory: () => typeof Zone === 'undefined',
71+
});
72+
6473
/**
6574
* The Testability service provides testing hooks that can be accessed from
6675
* the browser.
@@ -90,6 +99,8 @@ export class Testability implements PublicTestability {
9099

91100
private _destroyRef?: DestroyRef;
92101

102+
private readonly pendingTasksInternal = inject(PendingTasksInternal);
103+
private readonly _usePendingTasks = inject(USE_PENDING_TASKS);
93104
constructor(
94105
private _ngZone: NgZone,
95106
private registry: TestabilityRegistry,
@@ -121,20 +132,36 @@ export class Testability implements PublicTestability {
121132
},
122133
});
123134

124-
const onStableSubscription = this._ngZone.runOutsideAngular(() =>
125-
this._ngZone.onStable.subscribe({
135+
let pendingTasksSubscription: any;
136+
let onStableSubscription: any;
137+
138+
this._ngZone.runOutsideAngular(() => {
139+
if (this._usePendingTasks) {
140+
pendingTasksSubscription = this.pendingTasksInternal.hasPendingTasksObservable.subscribe(
141+
() => {
142+
if (this.isStable()) {
143+
this._ngZone.runOutsideAngular(() => {
144+
this._runCallbacksIfReady();
145+
});
146+
}
147+
},
148+
);
149+
}
150+
151+
onStableSubscription = this._ngZone.onStable.subscribe({
126152
next: () => {
127153
NgZone.assertNotInAngularZone();
128154
queueMicrotask(() => {
129155
this._isZoneStable = true;
130156
this._runCallbacksIfReady();
131157
});
132158
},
133-
}),
134-
);
159+
});
160+
});
135161

136162
this._destroyRef?.onDestroy(() => {
137163
onUnstableSubscription.unsubscribe();
164+
pendingTasksSubscription?.unsubscribe();
138165
onStableSubscription.unsubscribe();
139166
});
140167
}
@@ -143,7 +170,11 @@ export class Testability implements PublicTestability {
143170
* Whether an associated application is stable
144171
*/
145172
isStable(): boolean {
146-
return this._isZoneStable && !this._ngZone.hasPendingMacrotasks;
173+
return (
174+
this._isZoneStable &&
175+
!this._ngZone.hasPendingMacrotasks &&
176+
(!this._usePendingTasks || !this.pendingTasksInternal.hasPendingTasks)
177+
);
147178
}
148179

149180
private _runCallbacksIfReady(): void {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@
267267
"TracingAction",
268268
"TracingService",
269269
"UNSET",
270+
"USE_PENDING_TASKS",
270271
"USE_VALUE",
271272
"UnsubscriptionError",
272273
"VALID",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@
261261
"TracingAction",
262262
"TracingService",
263263
"UNSET",
264+
"USE_PENDING_TASKS",
264265
"USE_VALUE",
265266
"UnsubscriptionError",
266267
"VALID",

0 commit comments

Comments
 (0)