Repository navigation
Expand file tree
/
Copy pathchange-detection.ts
More file actions
195 lines (178 loc) · 7.49 KB
/
Copy pathchange-detection.ts
File metadata and controls
195 lines (178 loc) · 7.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {BehaviorSubject, Subscription} from 'rxjs';
/**
* The status of the test harness auto change detection. If not diabled test harnesses will
* automatically trigger change detection after every action (such as a click) and before every read
* (such as getting the text of an element).
*/
export interface AutoChangeDetectionStatus {
/** Whether auto change detection is disabled. */
isDisabled: boolean;
/**
* An optional callback, if present it indicates that change detection should be run immediately,
* while handling the status change. The callback should then be called as soon as change
* detection is done.
*/
onDetectChangesNow?: () => void;
}
/** Subject used to dispatch and listen for changes to the auto change detection status . */
const autoChangeDetectionSubject = new BehaviorSubject<AutoChangeDetectionStatus>({
isDisabled: false,
});
/** The current subscription to `autoChangeDetectionSubject`. */
let autoChangeDetectionSubscription: Subscription | null;
/**
* The default handler for auto change detection status changes. This handler will be used if the
* specific environment does not install its own.
* @param status The new auto change detection status.
*/
function defaultAutoChangeDetectionHandler(status: AutoChangeDetectionStatus) {
status.onDetectChangesNow?.();
}
/**
* Allows a test `HarnessEnvironment` to install its own handler for auto change detection status
* changes.
* @param handler The handler for the auto change detection status.
*/
export function handleAutoChangeDetectionStatus(
handler: (status: AutoChangeDetectionStatus) => void,
) {
stopHandlingAutoChangeDetectionStatus();
autoChangeDetectionSubscription = autoChangeDetectionSubject.subscribe(handler);
}
/** Allows a `HarnessEnvironment` to stop handling auto change detection status changes. */
export function stopHandlingAutoChangeDetectionStatus() {
autoChangeDetectionSubscription?.unsubscribe();
autoChangeDetectionSubscription = null;
}
/**
* Batches together triggering of change detection over the duration of the given function.
* @param fn The function to call with batched change detection.
* @param triggerBeforeAndAfter Optionally trigger change detection once before and after the batch
* operation. If false, change detection will not be triggered.
* @return The result of the given function.
*/
async function batchChangeDetection<T>(fn: () => Promise<T>, triggerBeforeAndAfter: boolean) {
// If change detection batching is already in progress, just run the function.
if (autoChangeDetectionSubject.getValue().isDisabled) {
return await fn();
}
// If nothing is handling change detection batching, install the default handler.
if (!autoChangeDetectionSubscription) {
handleAutoChangeDetectionStatus(defaultAutoChangeDetectionHandler);
}
if (triggerBeforeAndAfter) {
await new Promise(resolve =>
autoChangeDetectionSubject.next({
isDisabled: true,
onDetectChangesNow: resolve as () => void,
}),
);
// The function passed in may throw (e.g. if the user wants to make an expectation of an error
// being thrown. If this happens, we need to make sure we still re-enable change detection, so
// we wrap it in a `finally` block.
try {
return await fn();
} finally {
await new Promise(resolve =>
autoChangeDetectionSubject.next({
isDisabled: false,
onDetectChangesNow: resolve as () => void,
}),
);
}
} else {
autoChangeDetectionSubject.next({isDisabled: true});
// The function passed in may throw (e.g. if the user wants to make an expectation of an error
// being thrown. If this happens, we need to make sure we still re-enable change detection, so
// we wrap it in a `finally` block.
try {
return await fn();
} finally {
autoChangeDetectionSubject.next({isDisabled: false});
}
}
}
/**
* Disables the harness system's auto change detection for the duration of the given function.
* @param fn The function to disable auto change detection for.
* @return The result of the given function.
*/
export async function manualChangeDetection<T>(fn: () => Promise<T>) {
return batchChangeDetection(fn, false);
}
/**
* Resolves the given list of async values in parallel (i.e. via Promise.all) while batching change
* detection over the entire operation such that change detection occurs exactly once before
* resolving the values and once after.
* @param values A getter for the async values to resolve in parallel with batched change detection.
* @return The resolved values.
*/
export function parallel<T1, T2, T3, T4, T5>(
values: () => [
T1 | PromiseLike<T1>,
T2 | PromiseLike<T2>,
T3 | PromiseLike<T3>,
T4 | PromiseLike<T4>,
T5 | PromiseLike<T5>,
],
): Promise<[T1, T2, T3, T4, T5]>;
/**
* Resolves the given list of async values in parallel (i.e. via Promise.all) while batching change
* detection over the entire operation such that change detection occurs exactly once before
* resolving the values and once after.
* @param values A getter for the async values to resolve in parallel with batched change detection.
* @return The resolved values.
*/
export function parallel<T1, T2, T3, T4>(
values: () => [
T1 | PromiseLike<T1>,
T2 | PromiseLike<T2>,
T3 | PromiseLike<T3>,
T4 | PromiseLike<T4>,
],
): Promise<[T1, T2, T3, T4]>;
/**
* Resolves the given list of async values in parallel (i.e. via Promise.all) while batching change
* detection over the entire operation such that change detection occurs exactly once before
* resolving the values and once after.
* @param values A getter for the async values to resolve in parallel with batched change detection.
* @return The resolved values.
*/
export function parallel<T1, T2, T3>(
values: () => [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>],
): Promise<[T1, T2, T3]>;
/**
* Resolves the given list of async values in parallel (i.e. via Promise.all) while batching change
* detection over the entire operation such that change detection occurs exactly once before
* resolving the values and once after.
* @param values A getter for the async values to resolve in parallel with batched change detection.
* @return The resolved values.
*/
export function parallel<T1, T2>(
values: () => [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>],
): Promise<[T1, T2]>;
/**
* Resolves the given list of async values in parallel (i.e. via Promise.all) while batching change
* detection over the entire operation such that change detection occurs exactly once before
* resolving the values and once after.
* @param values A getter for the async values to resolve in parallel with batched change detection.
* @return The resolved values.
*/
export function parallel<T>(values: () => (T | PromiseLike<T>)[]): Promise<T[]>;
/**
* Resolves the given list of async values in parallel (i.e. via Promise.all) while batching change
* detection over the entire operation such that change detection occurs exactly once before
* resolving the values and once after.
* @param values A getter for the async values to resolve in parallel with batched change detection.
* @return The resolved values.
*/
export async function parallel<T>(values: () => Iterable<T | PromiseLike<T>>): Promise<T[]> {
return batchChangeDetection(() => Promise.all(values()), true);
}