@angular/common 17.1.2 → 17.2.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/http/src/client.mjs +3 -3
- package/esm2022/http/src/fetch.mjs +3 -3
- package/esm2022/http/src/interceptor.mjs +3 -3
- package/esm2022/http/src/jsonp.mjs +6 -6
- package/esm2022/http/src/module.mjs +12 -12
- package/esm2022/http/src/xhr.mjs +3 -3
- package/esm2022/http/src/xsrf.mjs +6 -6
- package/esm2022/http/testing/src/backend.mjs +3 -3
- package/esm2022/http/testing/src/module.mjs +4 -4
- package/esm2022/src/common.mjs +1 -1
- package/esm2022/src/common_module.mjs +4 -4
- package/esm2022/src/directives/ng_class.mjs +3 -3
- package/esm2022/src/directives/ng_component_outlet.mjs +3 -3
- package/esm2022/src/directives/ng_for_of.mjs +3 -3
- package/esm2022/src/directives/ng_if.mjs +3 -3
- package/esm2022/src/directives/ng_optimized_image/image_loaders/image_loader.mjs +1 -1
- package/esm2022/src/directives/ng_optimized_image/index.mjs +1 -1
- package/esm2022/src/directives/ng_optimized_image/lcp_image_observer.mjs +3 -3
- package/esm2022/src/directives/ng_optimized_image/ng_optimized_image.mjs +130 -5
- package/esm2022/src/directives/ng_optimized_image/preconnect_link_checker.mjs +3 -3
- package/esm2022/src/directives/ng_optimized_image/preload-link-creator.mjs +3 -3
- package/esm2022/src/directives/ng_plural.mjs +6 -6
- package/esm2022/src/directives/ng_style.mjs +3 -3
- package/esm2022/src/directives/ng_switch.mjs +9 -9
- package/esm2022/src/directives/ng_template_outlet.mjs +3 -3
- package/esm2022/src/errors.mjs +1 -1
- package/esm2022/src/i18n/localization.mjs +6 -6
- package/esm2022/src/location/hash_location_strategy.mjs +3 -3
- package/esm2022/src/location/location.mjs +3 -3
- package/esm2022/src/location/location_strategy.mjs +6 -6
- package/esm2022/src/location/platform_location.mjs +6 -6
- package/esm2022/src/navigation/navigation_types.mjs +9 -0
- package/esm2022/src/navigation/platform_navigation.mjs +4 -4
- package/esm2022/src/pipes/async_pipe.mjs +3 -3
- package/esm2022/src/pipes/case_conversion_pipes.mjs +9 -9
- package/esm2022/src/pipes/date_pipe.mjs +3 -3
- package/esm2022/src/pipes/i18n_plural_pipe.mjs +3 -3
- package/esm2022/src/pipes/i18n_select_pipe.mjs +3 -3
- package/esm2022/src/pipes/json_pipe.mjs +3 -3
- package/esm2022/src/pipes/keyvalue_pipe.mjs +3 -3
- package/esm2022/src/pipes/number_pipe.mjs +9 -9
- package/esm2022/src/pipes/slice_pipe.mjs +3 -3
- package/esm2022/src/private_export.mjs +2 -1
- package/esm2022/src/version.mjs +1 -1
- package/esm2022/testing/src/location_mock.mjs +3 -3
- package/esm2022/testing/src/mock_location_strategy.mjs +3 -3
- package/esm2022/testing/src/mock_platform_location.mjs +83 -5
- package/esm2022/testing/src/navigation/fake_navigation.mjs +1 -3
- package/esm2022/testing/src/navigation/navigation_types.mjs +9 -0
- package/esm2022/testing/src/navigation/provide_fake_platform_navigation.mjs +7 -4
- package/esm2022/testing/src/private_export.mjs +9 -0
- package/esm2022/testing/src/testing.mjs +2 -1
- package/esm2022/upgrade/src/location_upgrade_module.mjs +4 -4
- package/fesm2022/common.mjs +255 -117
- package/fesm2022/common.mjs.map +1 -1
- package/fesm2022/http/testing.mjs +8 -8
- package/fesm2022/http.mjs +37 -37
- package/fesm2022/testing.mjs +986 -228
- package/fesm2022/testing.mjs.map +1 -1
- package/fesm2022/upgrade.mjs +5 -5
- package/http/index.d.ts +1 -1
- package/http/testing/index.d.ts +1 -1
- package/index.d.ts +197 -2
- package/package.json +2 -2
- package/testing/index.d.ts +6 -1
- package/upgrade/index.d.ts +1 -1
package/fesm2022/testing.mjs
CHANGED
|
@@ -1,14 +1,990 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v17.1
|
|
2
|
+
* @license Angular v17.2.0-next.1
|
|
3
3
|
* (c) 2010-2022 Google LLC. https://angular.io/
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { ɵnormalizeQueryParams, LocationStrategy, Location } from '@angular/common';
|
|
7
|
+
import { ɵPlatformNavigation, DOCUMENT, PlatformLocation, ɵnormalizeQueryParams, LocationStrategy, Location } from '@angular/common';
|
|
8
8
|
import * as i0 from '@angular/core';
|
|
9
|
-
import {
|
|
9
|
+
import { Injectable, InjectionToken, Inject, Optional, inject, EventEmitter } from '@angular/core';
|
|
10
10
|
import { Subject } from 'rxjs';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* This class wraps the platform Navigation API which allows server-specific and test
|
|
14
|
+
* implementations.
|
|
15
|
+
*/
|
|
16
|
+
class PlatformNavigation {
|
|
17
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: PlatformNavigation, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
18
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: PlatformNavigation, providedIn: 'platform', useFactory: () => window.navigation }); }
|
|
19
|
+
}
|
|
20
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: PlatformNavigation, decorators: [{
|
|
21
|
+
type: Injectable,
|
|
22
|
+
args: [{ providedIn: 'platform', useFactory: () => window.navigation }]
|
|
23
|
+
}] });
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Fake implementation of user agent history and navigation behavior. This is a
|
|
27
|
+
* high-fidelity implementation of browser behavior that attempts to emulate
|
|
28
|
+
* things like traversal delay.
|
|
29
|
+
*/
|
|
30
|
+
class FakeNavigation {
|
|
31
|
+
/** Equivalent to `navigation.currentEntry`. */
|
|
32
|
+
get currentEntry() {
|
|
33
|
+
return this.entriesArr[this.currentEntryIndex];
|
|
34
|
+
}
|
|
35
|
+
get canGoBack() {
|
|
36
|
+
return this.currentEntryIndex > 0;
|
|
37
|
+
}
|
|
38
|
+
get canGoForward() {
|
|
39
|
+
return this.currentEntryIndex < this.entriesArr.length - 1;
|
|
40
|
+
}
|
|
41
|
+
constructor(window, startURL) {
|
|
42
|
+
this.window = window;
|
|
43
|
+
/**
|
|
44
|
+
* The fake implementation of an entries array. Only same-document entries
|
|
45
|
+
* allowed.
|
|
46
|
+
*/
|
|
47
|
+
this.entriesArr = [];
|
|
48
|
+
/**
|
|
49
|
+
* The current active entry index into `entriesArr`.
|
|
50
|
+
*/
|
|
51
|
+
this.currentEntryIndex = 0;
|
|
52
|
+
/**
|
|
53
|
+
* The current navigate event.
|
|
54
|
+
*/
|
|
55
|
+
this.navigateEvent = undefined;
|
|
56
|
+
/**
|
|
57
|
+
* A Map of pending traversals, so that traversals to the same entry can be
|
|
58
|
+
* re-used.
|
|
59
|
+
*/
|
|
60
|
+
this.traversalQueue = new Map();
|
|
61
|
+
/**
|
|
62
|
+
* A Promise that resolves when the previous traversals have finished. Used to
|
|
63
|
+
* simulate the cross-process communication necessary for traversals.
|
|
64
|
+
*/
|
|
65
|
+
this.nextTraversal = Promise.resolve();
|
|
66
|
+
/**
|
|
67
|
+
* A prospective current active entry index, which includes unresolved
|
|
68
|
+
* traversals. Used by `go` to determine where navigations are intended to go.
|
|
69
|
+
*/
|
|
70
|
+
this.prospectiveEntryIndex = 0;
|
|
71
|
+
/**
|
|
72
|
+
* A test-only option to make traversals synchronous, rather than emulate
|
|
73
|
+
* cross-process communication.
|
|
74
|
+
*/
|
|
75
|
+
this.synchronousTraversals = false;
|
|
76
|
+
/** Whether to allow a call to setInitialEntryForTesting. */
|
|
77
|
+
this.canSetInitialEntry = true;
|
|
78
|
+
/** `EventTarget` to dispatch events. */
|
|
79
|
+
this.eventTarget = this.window.document.createElement('div');
|
|
80
|
+
/** The next unique id for created entries. Replace recreates this id. */
|
|
81
|
+
this.nextId = 0;
|
|
82
|
+
/** The next unique key for created entries. Replace inherits this id. */
|
|
83
|
+
this.nextKey = 0;
|
|
84
|
+
/** Whether this fake is disposed. */
|
|
85
|
+
this.disposed = false;
|
|
86
|
+
// First entry.
|
|
87
|
+
this.setInitialEntryForTesting(startURL);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Sets the initial entry.
|
|
91
|
+
*/
|
|
92
|
+
setInitialEntryForTesting(url, options = { historyState: null }) {
|
|
93
|
+
if (!this.canSetInitialEntry) {
|
|
94
|
+
throw new Error('setInitialEntryForTesting can only be called before any ' + 'navigation has occurred');
|
|
95
|
+
}
|
|
96
|
+
const currentInitialEntry = this.entriesArr[0];
|
|
97
|
+
this.entriesArr[0] = new FakeNavigationHistoryEntry(new URL(url).toString(), {
|
|
98
|
+
index: 0,
|
|
99
|
+
key: currentInitialEntry?.key ?? String(this.nextKey++),
|
|
100
|
+
id: currentInitialEntry?.id ?? String(this.nextId++),
|
|
101
|
+
sameDocument: true,
|
|
102
|
+
historyState: options?.historyState,
|
|
103
|
+
state: options.state,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Returns whether the initial entry is still eligible to be set. */
|
|
107
|
+
canSetInitialEntryForTesting() {
|
|
108
|
+
return this.canSetInitialEntry;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Sets whether to emulate traversals as synchronous rather than
|
|
112
|
+
* asynchronous.
|
|
113
|
+
*/
|
|
114
|
+
setSynchronousTraversalsForTesting(synchronousTraversals) {
|
|
115
|
+
this.synchronousTraversals = synchronousTraversals;
|
|
116
|
+
}
|
|
117
|
+
/** Equivalent to `navigation.entries()`. */
|
|
118
|
+
entries() {
|
|
119
|
+
return this.entriesArr.slice();
|
|
120
|
+
}
|
|
121
|
+
/** Equivalent to `navigation.navigate()`. */
|
|
122
|
+
navigate(url, options) {
|
|
123
|
+
const fromUrl = new URL(this.currentEntry.url);
|
|
124
|
+
const toUrl = new URL(url, this.currentEntry.url);
|
|
125
|
+
let navigationType;
|
|
126
|
+
if (!options?.history || options.history === 'auto') {
|
|
127
|
+
// Auto defaults to push, but if the URLs are the same, is a replace.
|
|
128
|
+
if (fromUrl.toString() === toUrl.toString()) {
|
|
129
|
+
navigationType = 'replace';
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
navigationType = 'push';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
navigationType = options.history;
|
|
137
|
+
}
|
|
138
|
+
const hashChange = isHashChange(fromUrl, toUrl);
|
|
139
|
+
const destination = new FakeNavigationDestination({
|
|
140
|
+
url: toUrl.toString(),
|
|
141
|
+
state: options?.state,
|
|
142
|
+
sameDocument: hashChange,
|
|
143
|
+
historyState: null,
|
|
144
|
+
});
|
|
145
|
+
const result = new InternalNavigationResult();
|
|
146
|
+
this.userAgentNavigate(destination, result, {
|
|
147
|
+
navigationType,
|
|
148
|
+
cancelable: true,
|
|
149
|
+
canIntercept: true,
|
|
150
|
+
// Always false for navigate().
|
|
151
|
+
userInitiated: false,
|
|
152
|
+
hashChange,
|
|
153
|
+
info: options?.info,
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
committed: result.committed,
|
|
157
|
+
finished: result.finished,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/** Equivalent to `history.pushState()`. */
|
|
161
|
+
pushState(data, title, url) {
|
|
162
|
+
this.pushOrReplaceState('push', data, title, url);
|
|
163
|
+
}
|
|
164
|
+
/** Equivalent to `history.replaceState()`. */
|
|
165
|
+
replaceState(data, title, url) {
|
|
166
|
+
this.pushOrReplaceState('replace', data, title, url);
|
|
167
|
+
}
|
|
168
|
+
pushOrReplaceState(navigationType, data, _title, url) {
|
|
169
|
+
const fromUrl = new URL(this.currentEntry.url);
|
|
170
|
+
const toUrl = url ? new URL(url, this.currentEntry.url) : fromUrl;
|
|
171
|
+
const hashChange = isHashChange(fromUrl, toUrl);
|
|
172
|
+
const destination = new FakeNavigationDestination({
|
|
173
|
+
url: toUrl.toString(),
|
|
174
|
+
sameDocument: true,
|
|
175
|
+
historyState: data,
|
|
176
|
+
});
|
|
177
|
+
const result = new InternalNavigationResult();
|
|
178
|
+
this.userAgentNavigate(destination, result, {
|
|
179
|
+
navigationType,
|
|
180
|
+
cancelable: true,
|
|
181
|
+
canIntercept: true,
|
|
182
|
+
// Always false for pushState() or replaceState().
|
|
183
|
+
userInitiated: false,
|
|
184
|
+
hashChange,
|
|
185
|
+
skipPopState: true,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
/** Equivalent to `navigation.traverseTo()`. */
|
|
189
|
+
traverseTo(key, options) {
|
|
190
|
+
const fromUrl = new URL(this.currentEntry.url);
|
|
191
|
+
const entry = this.findEntry(key);
|
|
192
|
+
if (!entry) {
|
|
193
|
+
const domException = new DOMException('Invalid key', 'InvalidStateError');
|
|
194
|
+
const committed = Promise.reject(domException);
|
|
195
|
+
const finished = Promise.reject(domException);
|
|
196
|
+
committed.catch(() => { });
|
|
197
|
+
finished.catch(() => { });
|
|
198
|
+
return {
|
|
199
|
+
committed,
|
|
200
|
+
finished,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (entry === this.currentEntry) {
|
|
204
|
+
return {
|
|
205
|
+
committed: Promise.resolve(this.currentEntry),
|
|
206
|
+
finished: Promise.resolve(this.currentEntry),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (this.traversalQueue.has(entry.key)) {
|
|
210
|
+
const existingResult = this.traversalQueue.get(entry.key);
|
|
211
|
+
return {
|
|
212
|
+
committed: existingResult.committed,
|
|
213
|
+
finished: existingResult.finished,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const hashChange = isHashChange(fromUrl, new URL(entry.url, this.currentEntry.url));
|
|
217
|
+
const destination = new FakeNavigationDestination({
|
|
218
|
+
url: entry.url,
|
|
219
|
+
state: entry.getState(),
|
|
220
|
+
historyState: entry.getHistoryState(),
|
|
221
|
+
key: entry.key,
|
|
222
|
+
id: entry.id,
|
|
223
|
+
index: entry.index,
|
|
224
|
+
sameDocument: entry.sameDocument,
|
|
225
|
+
});
|
|
226
|
+
this.prospectiveEntryIndex = entry.index;
|
|
227
|
+
const result = new InternalNavigationResult();
|
|
228
|
+
this.traversalQueue.set(entry.key, result);
|
|
229
|
+
this.runTraversal(() => {
|
|
230
|
+
this.traversalQueue.delete(entry.key);
|
|
231
|
+
this.userAgentNavigate(destination, result, {
|
|
232
|
+
navigationType: 'traverse',
|
|
233
|
+
cancelable: true,
|
|
234
|
+
canIntercept: true,
|
|
235
|
+
// Always false for traverseTo().
|
|
236
|
+
userInitiated: false,
|
|
237
|
+
hashChange,
|
|
238
|
+
info: options?.info,
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
committed: result.committed,
|
|
243
|
+
finished: result.finished,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/** Equivalent to `navigation.back()`. */
|
|
247
|
+
back(options) {
|
|
248
|
+
if (this.currentEntryIndex === 0) {
|
|
249
|
+
const domException = new DOMException('Cannot go back', 'InvalidStateError');
|
|
250
|
+
const committed = Promise.reject(domException);
|
|
251
|
+
const finished = Promise.reject(domException);
|
|
252
|
+
committed.catch(() => { });
|
|
253
|
+
finished.catch(() => { });
|
|
254
|
+
return {
|
|
255
|
+
committed,
|
|
256
|
+
finished,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const entry = this.entriesArr[this.currentEntryIndex - 1];
|
|
260
|
+
return this.traverseTo(entry.key, options);
|
|
261
|
+
}
|
|
262
|
+
/** Equivalent to `navigation.forward()`. */
|
|
263
|
+
forward(options) {
|
|
264
|
+
if (this.currentEntryIndex === this.entriesArr.length - 1) {
|
|
265
|
+
const domException = new DOMException('Cannot go forward', 'InvalidStateError');
|
|
266
|
+
const committed = Promise.reject(domException);
|
|
267
|
+
const finished = Promise.reject(domException);
|
|
268
|
+
committed.catch(() => { });
|
|
269
|
+
finished.catch(() => { });
|
|
270
|
+
return {
|
|
271
|
+
committed,
|
|
272
|
+
finished,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const entry = this.entriesArr[this.currentEntryIndex + 1];
|
|
276
|
+
return this.traverseTo(entry.key, options);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Equivalent to `history.go()`.
|
|
280
|
+
* Note that this method does not actually work precisely to how Chrome
|
|
281
|
+
* does, instead choosing a simpler model with less unexpected behavior.
|
|
282
|
+
* Chrome has a few edge case optimizations, for instance with repeated
|
|
283
|
+
* `back(); forward()` chains it collapses certain traversals.
|
|
284
|
+
*/
|
|
285
|
+
go(direction) {
|
|
286
|
+
const targetIndex = this.prospectiveEntryIndex + direction;
|
|
287
|
+
if (targetIndex >= this.entriesArr.length || targetIndex < 0) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
this.prospectiveEntryIndex = targetIndex;
|
|
291
|
+
this.runTraversal(() => {
|
|
292
|
+
// Check again that destination is in the entries array.
|
|
293
|
+
if (targetIndex >= this.entriesArr.length || targetIndex < 0) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const fromUrl = new URL(this.currentEntry.url);
|
|
297
|
+
const entry = this.entriesArr[targetIndex];
|
|
298
|
+
const hashChange = isHashChange(fromUrl, new URL(entry.url, this.currentEntry.url));
|
|
299
|
+
const destination = new FakeNavigationDestination({
|
|
300
|
+
url: entry.url,
|
|
301
|
+
state: entry.getState(),
|
|
302
|
+
historyState: entry.getHistoryState(),
|
|
303
|
+
key: entry.key,
|
|
304
|
+
id: entry.id,
|
|
305
|
+
index: entry.index,
|
|
306
|
+
sameDocument: entry.sameDocument,
|
|
307
|
+
});
|
|
308
|
+
const result = new InternalNavigationResult();
|
|
309
|
+
this.userAgentNavigate(destination, result, {
|
|
310
|
+
navigationType: 'traverse',
|
|
311
|
+
cancelable: true,
|
|
312
|
+
canIntercept: true,
|
|
313
|
+
// Always false for go().
|
|
314
|
+
userInitiated: false,
|
|
315
|
+
hashChange,
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
/** Runs a traversal synchronously or asynchronously */
|
|
320
|
+
runTraversal(traversal) {
|
|
321
|
+
if (this.synchronousTraversals) {
|
|
322
|
+
traversal();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
// Each traversal occupies a single timeout resolution.
|
|
326
|
+
// This means that Promises added to commit and finish should resolve
|
|
327
|
+
// before the next traversal.
|
|
328
|
+
this.nextTraversal = this.nextTraversal.then(() => {
|
|
329
|
+
return new Promise((resolve) => {
|
|
330
|
+
setTimeout(() => {
|
|
331
|
+
resolve();
|
|
332
|
+
traversal();
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
/** Equivalent to `navigation.addEventListener()`. */
|
|
338
|
+
addEventListener(type, callback, options) {
|
|
339
|
+
this.eventTarget.addEventListener(type, callback, options);
|
|
340
|
+
}
|
|
341
|
+
/** Equivalent to `navigation.removeEventListener()`. */
|
|
342
|
+
removeEventListener(type, callback, options) {
|
|
343
|
+
this.eventTarget.removeEventListener(type, callback, options);
|
|
344
|
+
}
|
|
345
|
+
/** Equivalent to `navigation.dispatchEvent()` */
|
|
346
|
+
dispatchEvent(event) {
|
|
347
|
+
return this.eventTarget.dispatchEvent(event);
|
|
348
|
+
}
|
|
349
|
+
/** Cleans up resources. */
|
|
350
|
+
dispose() {
|
|
351
|
+
// Recreate eventTarget to release current listeners.
|
|
352
|
+
// `document.createElement` because NodeJS `EventTarget` is incompatible with Domino's `Event`.
|
|
353
|
+
this.eventTarget = this.window.document.createElement('div');
|
|
354
|
+
this.disposed = true;
|
|
355
|
+
}
|
|
356
|
+
/** Returns whether this fake is disposed. */
|
|
357
|
+
isDisposed() {
|
|
358
|
+
return this.disposed;
|
|
359
|
+
}
|
|
360
|
+
/** Implementation for all navigations and traversals. */
|
|
361
|
+
userAgentNavigate(destination, result, options) {
|
|
362
|
+
// The first navigation should disallow any future calls to set the initial
|
|
363
|
+
// entry.
|
|
364
|
+
this.canSetInitialEntry = false;
|
|
365
|
+
if (this.navigateEvent) {
|
|
366
|
+
this.navigateEvent.cancel(new DOMException('Navigation was aborted', 'AbortError'));
|
|
367
|
+
this.navigateEvent = undefined;
|
|
368
|
+
}
|
|
369
|
+
const navigateEvent = createFakeNavigateEvent({
|
|
370
|
+
navigationType: options.navigationType,
|
|
371
|
+
cancelable: options.cancelable,
|
|
372
|
+
canIntercept: options.canIntercept,
|
|
373
|
+
userInitiated: options.userInitiated,
|
|
374
|
+
hashChange: options.hashChange,
|
|
375
|
+
signal: result.signal,
|
|
376
|
+
destination,
|
|
377
|
+
info: options.info,
|
|
378
|
+
sameDocument: destination.sameDocument,
|
|
379
|
+
skipPopState: options.skipPopState,
|
|
380
|
+
result,
|
|
381
|
+
userAgentCommit: () => {
|
|
382
|
+
this.userAgentCommit();
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
this.navigateEvent = navigateEvent;
|
|
386
|
+
this.eventTarget.dispatchEvent(navigateEvent);
|
|
387
|
+
navigateEvent.dispatchedNavigateEvent();
|
|
388
|
+
if (navigateEvent.commitOption === 'immediate') {
|
|
389
|
+
navigateEvent.commit(/* internal= */ true);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/** Implementation to commit a navigation. */
|
|
393
|
+
userAgentCommit() {
|
|
394
|
+
if (!this.navigateEvent) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const from = this.currentEntry;
|
|
398
|
+
if (!this.navigateEvent.sameDocument) {
|
|
399
|
+
const error = new Error('Cannot navigate to a non-same-document URL.');
|
|
400
|
+
this.navigateEvent.cancel(error);
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
if (this.navigateEvent.navigationType === 'push' ||
|
|
404
|
+
this.navigateEvent.navigationType === 'replace') {
|
|
405
|
+
this.userAgentPushOrReplace(this.navigateEvent.destination, {
|
|
406
|
+
navigationType: this.navigateEvent.navigationType,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
else if (this.navigateEvent.navigationType === 'traverse') {
|
|
410
|
+
this.userAgentTraverse(this.navigateEvent.destination);
|
|
411
|
+
}
|
|
412
|
+
this.navigateEvent.userAgentNavigated(this.currentEntry);
|
|
413
|
+
const currentEntryChangeEvent = createFakeNavigationCurrentEntryChangeEvent({
|
|
414
|
+
from,
|
|
415
|
+
navigationType: this.navigateEvent.navigationType,
|
|
416
|
+
});
|
|
417
|
+
this.eventTarget.dispatchEvent(currentEntryChangeEvent);
|
|
418
|
+
if (!this.navigateEvent.skipPopState) {
|
|
419
|
+
const popStateEvent = createPopStateEvent({
|
|
420
|
+
state: this.navigateEvent.destination.getHistoryState(),
|
|
421
|
+
});
|
|
422
|
+
this.window.dispatchEvent(popStateEvent);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/** Implementation for a push or replace navigation. */
|
|
426
|
+
userAgentPushOrReplace(destination, { navigationType }) {
|
|
427
|
+
if (navigationType === 'push') {
|
|
428
|
+
this.currentEntryIndex++;
|
|
429
|
+
this.prospectiveEntryIndex = this.currentEntryIndex;
|
|
430
|
+
}
|
|
431
|
+
const index = this.currentEntryIndex;
|
|
432
|
+
const key = navigationType === 'push' ? String(this.nextKey++) : this.currentEntry.key;
|
|
433
|
+
const entry = new FakeNavigationHistoryEntry(destination.url, {
|
|
434
|
+
id: String(this.nextId++),
|
|
435
|
+
key,
|
|
436
|
+
index,
|
|
437
|
+
sameDocument: true,
|
|
438
|
+
state: destination.getState(),
|
|
439
|
+
historyState: destination.getHistoryState(),
|
|
440
|
+
});
|
|
441
|
+
if (navigationType === 'push') {
|
|
442
|
+
this.entriesArr.splice(index, Infinity, entry);
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
this.entriesArr[index] = entry;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/** Implementation for a traverse navigation. */
|
|
449
|
+
userAgentTraverse(destination) {
|
|
450
|
+
this.currentEntryIndex = destination.index;
|
|
451
|
+
}
|
|
452
|
+
/** Utility method for finding entries with the given `key`. */
|
|
453
|
+
findEntry(key) {
|
|
454
|
+
for (const entry of this.entriesArr) {
|
|
455
|
+
if (entry.key === key)
|
|
456
|
+
return entry;
|
|
457
|
+
}
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
460
|
+
set onnavigate(_handler) {
|
|
461
|
+
throw new Error('unimplemented');
|
|
462
|
+
}
|
|
463
|
+
get onnavigate() {
|
|
464
|
+
throw new Error('unimplemented');
|
|
465
|
+
}
|
|
466
|
+
set oncurrententrychange(_handler) {
|
|
467
|
+
throw new Error('unimplemented');
|
|
468
|
+
}
|
|
469
|
+
get oncurrententrychange() {
|
|
470
|
+
throw new Error('unimplemented');
|
|
471
|
+
}
|
|
472
|
+
set onnavigatesuccess(_handler) {
|
|
473
|
+
throw new Error('unimplemented');
|
|
474
|
+
}
|
|
475
|
+
get onnavigatesuccess() {
|
|
476
|
+
throw new Error('unimplemented');
|
|
477
|
+
}
|
|
478
|
+
set onnavigateerror(_handler) {
|
|
479
|
+
throw new Error('unimplemented');
|
|
480
|
+
}
|
|
481
|
+
get onnavigateerror() {
|
|
482
|
+
throw new Error('unimplemented');
|
|
483
|
+
}
|
|
484
|
+
get transition() {
|
|
485
|
+
throw new Error('unimplemented');
|
|
486
|
+
}
|
|
487
|
+
updateCurrentEntry(_options) {
|
|
488
|
+
throw new Error('unimplemented');
|
|
489
|
+
}
|
|
490
|
+
reload(_options) {
|
|
491
|
+
throw new Error('unimplemented');
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Fake equivalent of `NavigationHistoryEntry`.
|
|
496
|
+
*/
|
|
497
|
+
class FakeNavigationHistoryEntry {
|
|
498
|
+
constructor(url, { id, key, index, sameDocument, state, historyState, }) {
|
|
499
|
+
this.url = url;
|
|
500
|
+
// tslint:disable-next-line:no-any
|
|
501
|
+
this.ondispose = null;
|
|
502
|
+
this.id = id;
|
|
503
|
+
this.key = key;
|
|
504
|
+
this.index = index;
|
|
505
|
+
this.sameDocument = sameDocument;
|
|
506
|
+
this.state = state;
|
|
507
|
+
this.historyState = historyState;
|
|
508
|
+
}
|
|
509
|
+
getState() {
|
|
510
|
+
// Budget copy.
|
|
511
|
+
return this.state ? JSON.parse(JSON.stringify(this.state)) : this.state;
|
|
512
|
+
}
|
|
513
|
+
getHistoryState() {
|
|
514
|
+
// Budget copy.
|
|
515
|
+
return this.historyState ? JSON.parse(JSON.stringify(this.historyState)) : this.historyState;
|
|
516
|
+
}
|
|
517
|
+
addEventListener(type, callback, options) {
|
|
518
|
+
throw new Error('unimplemented');
|
|
519
|
+
}
|
|
520
|
+
removeEventListener(type, callback, options) {
|
|
521
|
+
throw new Error('unimplemented');
|
|
522
|
+
}
|
|
523
|
+
dispatchEvent(event) {
|
|
524
|
+
throw new Error('unimplemented');
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Create a fake equivalent of `NavigateEvent`. This is not a class because ES5
|
|
529
|
+
* transpiled JavaScript cannot extend native Event.
|
|
530
|
+
*/
|
|
531
|
+
function createFakeNavigateEvent({ cancelable, canIntercept, userInitiated, hashChange, navigationType, signal, destination, info, sameDocument, skipPopState, result, userAgentCommit, }) {
|
|
532
|
+
const event = new Event('navigate', { bubbles: false, cancelable });
|
|
533
|
+
event.canIntercept = canIntercept;
|
|
534
|
+
event.userInitiated = userInitiated;
|
|
535
|
+
event.hashChange = hashChange;
|
|
536
|
+
event.navigationType = navigationType;
|
|
537
|
+
event.signal = signal;
|
|
538
|
+
event.destination = destination;
|
|
539
|
+
event.info = info;
|
|
540
|
+
event.downloadRequest = null;
|
|
541
|
+
event.formData = null;
|
|
542
|
+
event.sameDocument = sameDocument;
|
|
543
|
+
event.skipPopState = skipPopState;
|
|
544
|
+
event.commitOption = 'immediate';
|
|
545
|
+
let handlerFinished = undefined;
|
|
546
|
+
let interceptCalled = false;
|
|
547
|
+
let dispatchedNavigateEvent = false;
|
|
548
|
+
let commitCalled = false;
|
|
549
|
+
event.intercept = function (options) {
|
|
550
|
+
interceptCalled = true;
|
|
551
|
+
event.sameDocument = true;
|
|
552
|
+
const handler = options?.handler;
|
|
553
|
+
if (handler) {
|
|
554
|
+
handlerFinished = handler();
|
|
555
|
+
}
|
|
556
|
+
if (options?.commit) {
|
|
557
|
+
event.commitOption = options.commit;
|
|
558
|
+
}
|
|
559
|
+
if (options?.focusReset !== undefined || options?.scroll !== undefined) {
|
|
560
|
+
throw new Error('unimplemented');
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
event.scroll = function () {
|
|
564
|
+
throw new Error('unimplemented');
|
|
565
|
+
};
|
|
566
|
+
event.commit = function (internal = false) {
|
|
567
|
+
if (!internal && !interceptCalled) {
|
|
568
|
+
throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': intercept() must be ` +
|
|
569
|
+
`called before commit().`, 'InvalidStateError');
|
|
570
|
+
}
|
|
571
|
+
if (!dispatchedNavigateEvent) {
|
|
572
|
+
throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': commit() may not be ` +
|
|
573
|
+
`called during event dispatch.`, 'InvalidStateError');
|
|
574
|
+
}
|
|
575
|
+
if (commitCalled) {
|
|
576
|
+
throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': commit() already ` + `called.`, 'InvalidStateError');
|
|
577
|
+
}
|
|
578
|
+
commitCalled = true;
|
|
579
|
+
userAgentCommit();
|
|
580
|
+
};
|
|
581
|
+
// Internal only.
|
|
582
|
+
event.cancel = function (reason) {
|
|
583
|
+
result.committedReject(reason);
|
|
584
|
+
result.finishedReject(reason);
|
|
585
|
+
};
|
|
586
|
+
// Internal only.
|
|
587
|
+
event.dispatchedNavigateEvent = function () {
|
|
588
|
+
dispatchedNavigateEvent = true;
|
|
589
|
+
if (event.commitOption === 'after-transition') {
|
|
590
|
+
// If handler finishes before commit, call commit.
|
|
591
|
+
handlerFinished?.then(() => {
|
|
592
|
+
if (!commitCalled) {
|
|
593
|
+
event.commit(/* internal */ true);
|
|
594
|
+
}
|
|
595
|
+
}, () => { });
|
|
596
|
+
}
|
|
597
|
+
Promise.all([result.committed, handlerFinished]).then(([entry]) => {
|
|
598
|
+
result.finishedResolve(entry);
|
|
599
|
+
}, (reason) => {
|
|
600
|
+
result.finishedReject(reason);
|
|
601
|
+
});
|
|
602
|
+
};
|
|
603
|
+
// Internal only.
|
|
604
|
+
event.userAgentNavigated = function (entry) {
|
|
605
|
+
result.committedResolve(entry);
|
|
606
|
+
};
|
|
607
|
+
return event;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Create a fake equivalent of `NavigationCurrentEntryChange`. This does not use
|
|
611
|
+
* a class because ES5 transpiled JavaScript cannot extend native Event.
|
|
612
|
+
*/
|
|
613
|
+
function createFakeNavigationCurrentEntryChangeEvent({ from, navigationType, }) {
|
|
614
|
+
const event = new Event('currententrychange', {
|
|
615
|
+
bubbles: false,
|
|
616
|
+
cancelable: false,
|
|
617
|
+
});
|
|
618
|
+
event.from = from;
|
|
619
|
+
event.navigationType = navigationType;
|
|
620
|
+
return event;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Create a fake equivalent of `PopStateEvent`. This does not use a class
|
|
624
|
+
* because ES5 transpiled JavaScript cannot extend native Event.
|
|
625
|
+
*/
|
|
626
|
+
function createPopStateEvent({ state }) {
|
|
627
|
+
const event = new Event('popstate', {
|
|
628
|
+
bubbles: false,
|
|
629
|
+
cancelable: false,
|
|
630
|
+
});
|
|
631
|
+
event.state = state;
|
|
632
|
+
return event;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Fake equivalent of `NavigationDestination`.
|
|
636
|
+
*/
|
|
637
|
+
class FakeNavigationDestination {
|
|
638
|
+
constructor({ url, sameDocument, historyState, state, key = null, id = null, index = -1, }) {
|
|
639
|
+
this.url = url;
|
|
640
|
+
this.sameDocument = sameDocument;
|
|
641
|
+
this.state = state;
|
|
642
|
+
this.historyState = historyState;
|
|
643
|
+
this.key = key;
|
|
644
|
+
this.id = id;
|
|
645
|
+
this.index = index;
|
|
646
|
+
}
|
|
647
|
+
getState() {
|
|
648
|
+
return this.state;
|
|
649
|
+
}
|
|
650
|
+
getHistoryState() {
|
|
651
|
+
return this.historyState;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/** Utility function to determine whether two UrlLike have the same hash. */
|
|
655
|
+
function isHashChange(from, to) {
|
|
656
|
+
return (to.hash !== from.hash &&
|
|
657
|
+
to.hostname === from.hostname &&
|
|
658
|
+
to.pathname === from.pathname &&
|
|
659
|
+
to.search === from.search);
|
|
660
|
+
}
|
|
661
|
+
/** Internal utility class for representing the result of a navigation. */
|
|
662
|
+
class InternalNavigationResult {
|
|
663
|
+
get signal() {
|
|
664
|
+
return this.abortController.signal;
|
|
665
|
+
}
|
|
666
|
+
constructor() {
|
|
667
|
+
this.abortController = new AbortController();
|
|
668
|
+
this.committed = new Promise((resolve, reject) => {
|
|
669
|
+
this.committedResolve = resolve;
|
|
670
|
+
this.committedReject = reject;
|
|
671
|
+
});
|
|
672
|
+
this.finished = new Promise(async (resolve, reject) => {
|
|
673
|
+
this.finishedResolve = resolve;
|
|
674
|
+
this.finishedReject = (reason) => {
|
|
675
|
+
reject(reason);
|
|
676
|
+
this.abortController.abort(reason);
|
|
677
|
+
};
|
|
678
|
+
});
|
|
679
|
+
// All rejections are handled.
|
|
680
|
+
this.committed.catch(() => { });
|
|
681
|
+
this.finished.catch(() => { });
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Parser from https://tools.ietf.org/html/rfc3986#appendix-B
|
|
687
|
+
* ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
|
|
688
|
+
* 12 3 4 5 6 7 8 9
|
|
689
|
+
*
|
|
690
|
+
* Example: http://www.ics.uci.edu/pub/ietf/uri/#Related
|
|
691
|
+
*
|
|
692
|
+
* Results in:
|
|
693
|
+
*
|
|
694
|
+
* $1 = http:
|
|
695
|
+
* $2 = http
|
|
696
|
+
* $3 = //www.ics.uci.edu
|
|
697
|
+
* $4 = www.ics.uci.edu
|
|
698
|
+
* $5 = /pub/ietf/uri/
|
|
699
|
+
* $6 = <undefined>
|
|
700
|
+
* $7 = <undefined>
|
|
701
|
+
* $8 = #Related
|
|
702
|
+
* $9 = Related
|
|
703
|
+
*/
|
|
704
|
+
const urlParse = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
|
|
705
|
+
function parseUrl(urlStr, baseHref) {
|
|
706
|
+
const verifyProtocol = /^((http[s]?|ftp):\/\/)/;
|
|
707
|
+
let serverBase;
|
|
708
|
+
// URL class requires full URL. If the URL string doesn't start with protocol, we need to add
|
|
709
|
+
// an arbitrary base URL which can be removed afterward.
|
|
710
|
+
if (!verifyProtocol.test(urlStr)) {
|
|
711
|
+
serverBase = 'http://empty.com/';
|
|
712
|
+
}
|
|
713
|
+
let parsedUrl;
|
|
714
|
+
try {
|
|
715
|
+
parsedUrl = new URL(urlStr, serverBase);
|
|
716
|
+
}
|
|
717
|
+
catch (e) {
|
|
718
|
+
const result = urlParse.exec(serverBase || '' + urlStr);
|
|
719
|
+
if (!result) {
|
|
720
|
+
throw new Error(`Invalid URL: ${urlStr} with base: ${baseHref}`);
|
|
721
|
+
}
|
|
722
|
+
const hostSplit = result[4].split(':');
|
|
723
|
+
parsedUrl = {
|
|
724
|
+
protocol: result[1],
|
|
725
|
+
hostname: hostSplit[0],
|
|
726
|
+
port: hostSplit[1] || '',
|
|
727
|
+
pathname: result[5],
|
|
728
|
+
search: result[6],
|
|
729
|
+
hash: result[8],
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
if (parsedUrl.pathname && parsedUrl.pathname.indexOf(baseHref) === 0) {
|
|
733
|
+
parsedUrl.pathname = parsedUrl.pathname.substring(baseHref.length);
|
|
734
|
+
}
|
|
735
|
+
return {
|
|
736
|
+
hostname: (!serverBase && parsedUrl.hostname) || '',
|
|
737
|
+
protocol: (!serverBase && parsedUrl.protocol) || '',
|
|
738
|
+
port: (!serverBase && parsedUrl.port) || '',
|
|
739
|
+
pathname: parsedUrl.pathname || '/',
|
|
740
|
+
search: parsedUrl.search || '',
|
|
741
|
+
hash: parsedUrl.hash || '',
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Provider for mock platform location config
|
|
746
|
+
*
|
|
747
|
+
* @publicApi
|
|
748
|
+
*/
|
|
749
|
+
const MOCK_PLATFORM_LOCATION_CONFIG = new InjectionToken('MOCK_PLATFORM_LOCATION_CONFIG');
|
|
750
|
+
/**
|
|
751
|
+
* Mock implementation of URL state.
|
|
752
|
+
*
|
|
753
|
+
* @publicApi
|
|
754
|
+
*/
|
|
755
|
+
class MockPlatformLocation {
|
|
756
|
+
constructor(config) {
|
|
757
|
+
this.baseHref = '';
|
|
758
|
+
this.hashUpdate = new Subject();
|
|
759
|
+
this.popStateSubject = new Subject();
|
|
760
|
+
this.urlChangeIndex = 0;
|
|
761
|
+
this.urlChanges = [{ hostname: '', protocol: '', port: '', pathname: '/', search: '', hash: '', state: null }];
|
|
762
|
+
if (config) {
|
|
763
|
+
this.baseHref = config.appBaseHref || '';
|
|
764
|
+
const parsedChanges = this.parseChanges(null, config.startUrl || 'http://_empty_/', this.baseHref);
|
|
765
|
+
this.urlChanges[0] = { ...parsedChanges };
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
get hostname() {
|
|
769
|
+
return this.urlChanges[this.urlChangeIndex].hostname;
|
|
770
|
+
}
|
|
771
|
+
get protocol() {
|
|
772
|
+
return this.urlChanges[this.urlChangeIndex].protocol;
|
|
773
|
+
}
|
|
774
|
+
get port() {
|
|
775
|
+
return this.urlChanges[this.urlChangeIndex].port;
|
|
776
|
+
}
|
|
777
|
+
get pathname() {
|
|
778
|
+
return this.urlChanges[this.urlChangeIndex].pathname;
|
|
779
|
+
}
|
|
780
|
+
get search() {
|
|
781
|
+
return this.urlChanges[this.urlChangeIndex].search;
|
|
782
|
+
}
|
|
783
|
+
get hash() {
|
|
784
|
+
return this.urlChanges[this.urlChangeIndex].hash;
|
|
785
|
+
}
|
|
786
|
+
get state() {
|
|
787
|
+
return this.urlChanges[this.urlChangeIndex].state;
|
|
788
|
+
}
|
|
789
|
+
getBaseHrefFromDOM() {
|
|
790
|
+
return this.baseHref;
|
|
791
|
+
}
|
|
792
|
+
onPopState(fn) {
|
|
793
|
+
const subscription = this.popStateSubject.subscribe(fn);
|
|
794
|
+
return () => subscription.unsubscribe();
|
|
795
|
+
}
|
|
796
|
+
onHashChange(fn) {
|
|
797
|
+
const subscription = this.hashUpdate.subscribe(fn);
|
|
798
|
+
return () => subscription.unsubscribe();
|
|
799
|
+
}
|
|
800
|
+
get href() {
|
|
801
|
+
let url = `${this.protocol}//${this.hostname}${this.port ? ':' + this.port : ''}`;
|
|
802
|
+
url += `${this.pathname === '/' ? '' : this.pathname}${this.search}${this.hash}`;
|
|
803
|
+
return url;
|
|
804
|
+
}
|
|
805
|
+
get url() {
|
|
806
|
+
return `${this.pathname}${this.search}${this.hash}`;
|
|
807
|
+
}
|
|
808
|
+
parseChanges(state, url, baseHref = '') {
|
|
809
|
+
// When the `history.state` value is stored, it is always copied.
|
|
810
|
+
state = JSON.parse(JSON.stringify(state));
|
|
811
|
+
return { ...parseUrl(url, baseHref), state };
|
|
812
|
+
}
|
|
813
|
+
replaceState(state, title, newUrl) {
|
|
814
|
+
const { pathname, search, state: parsedState, hash } = this.parseChanges(state, newUrl);
|
|
815
|
+
this.urlChanges[this.urlChangeIndex] = {
|
|
816
|
+
...this.urlChanges[this.urlChangeIndex],
|
|
817
|
+
pathname,
|
|
818
|
+
search,
|
|
819
|
+
hash,
|
|
820
|
+
state: parsedState,
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
pushState(state, title, newUrl) {
|
|
824
|
+
const { pathname, search, state: parsedState, hash } = this.parseChanges(state, newUrl);
|
|
825
|
+
if (this.urlChangeIndex > 0) {
|
|
826
|
+
this.urlChanges.splice(this.urlChangeIndex + 1);
|
|
827
|
+
}
|
|
828
|
+
this.urlChanges.push({
|
|
829
|
+
...this.urlChanges[this.urlChangeIndex],
|
|
830
|
+
pathname,
|
|
831
|
+
search,
|
|
832
|
+
hash,
|
|
833
|
+
state: parsedState,
|
|
834
|
+
});
|
|
835
|
+
this.urlChangeIndex = this.urlChanges.length - 1;
|
|
836
|
+
}
|
|
837
|
+
forward() {
|
|
838
|
+
const oldUrl = this.url;
|
|
839
|
+
const oldHash = this.hash;
|
|
840
|
+
if (this.urlChangeIndex < this.urlChanges.length) {
|
|
841
|
+
this.urlChangeIndex++;
|
|
842
|
+
}
|
|
843
|
+
this.emitEvents(oldHash, oldUrl);
|
|
844
|
+
}
|
|
845
|
+
back() {
|
|
846
|
+
const oldUrl = this.url;
|
|
847
|
+
const oldHash = this.hash;
|
|
848
|
+
if (this.urlChangeIndex > 0) {
|
|
849
|
+
this.urlChangeIndex--;
|
|
850
|
+
}
|
|
851
|
+
this.emitEvents(oldHash, oldUrl);
|
|
852
|
+
}
|
|
853
|
+
historyGo(relativePosition = 0) {
|
|
854
|
+
const oldUrl = this.url;
|
|
855
|
+
const oldHash = this.hash;
|
|
856
|
+
const nextPageIndex = this.urlChangeIndex + relativePosition;
|
|
857
|
+
if (nextPageIndex >= 0 && nextPageIndex < this.urlChanges.length) {
|
|
858
|
+
this.urlChangeIndex = nextPageIndex;
|
|
859
|
+
}
|
|
860
|
+
this.emitEvents(oldHash, oldUrl);
|
|
861
|
+
}
|
|
862
|
+
getState() {
|
|
863
|
+
return this.state;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Browsers are inconsistent in when they fire events and perform the state updates
|
|
867
|
+
* The most easiest thing to do in our mock is synchronous and that happens to match
|
|
868
|
+
* Firefox and Chrome, at least somewhat closely
|
|
869
|
+
*
|
|
870
|
+
* https://github.com/WICG/navigation-api#watching-for-navigations
|
|
871
|
+
* https://docs.google.com/document/d/1Pdve-DJ1JCGilj9Yqf5HxRJyBKSel5owgOvUJqTauwU/edit#heading=h.3ye4v71wsz94
|
|
872
|
+
* popstate is always sent before hashchange:
|
|
873
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event#when_popstate_is_sent
|
|
874
|
+
*/
|
|
875
|
+
emitEvents(oldHash, oldUrl) {
|
|
876
|
+
this.popStateSubject.next({
|
|
877
|
+
type: 'popstate',
|
|
878
|
+
state: this.getState(),
|
|
879
|
+
oldUrl,
|
|
880
|
+
newUrl: this.url,
|
|
881
|
+
});
|
|
882
|
+
if (oldHash !== this.hash) {
|
|
883
|
+
this.hashUpdate.next({
|
|
884
|
+
type: 'hashchange',
|
|
885
|
+
state: null,
|
|
886
|
+
oldUrl,
|
|
887
|
+
newUrl: this.url,
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: MockPlatformLocation, deps: [{ token: MOCK_PLATFORM_LOCATION_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
892
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: MockPlatformLocation }); }
|
|
893
|
+
}
|
|
894
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: MockPlatformLocation, decorators: [{
|
|
895
|
+
type: Injectable
|
|
896
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
897
|
+
type: Inject,
|
|
898
|
+
args: [MOCK_PLATFORM_LOCATION_CONFIG]
|
|
899
|
+
}, {
|
|
900
|
+
type: Optional
|
|
901
|
+
}] }] });
|
|
902
|
+
/**
|
|
903
|
+
* Mock implementation of URL state.
|
|
904
|
+
*/
|
|
905
|
+
class FakeNavigationPlatformLocation {
|
|
906
|
+
constructor() {
|
|
907
|
+
this._platformNavigation = inject(ɵPlatformNavigation);
|
|
908
|
+
this.window = inject(DOCUMENT).defaultView;
|
|
909
|
+
this.config = inject(MOCK_PLATFORM_LOCATION_CONFIG, { optional: true });
|
|
910
|
+
if (!(this._platformNavigation instanceof FakeNavigation)) {
|
|
911
|
+
throw new Error('FakePlatformNavigation cannot be used without FakeNavigation. Use ' +
|
|
912
|
+
'`provideFakeNavigation` to have all these services provided together.');
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
getBaseHrefFromDOM() {
|
|
916
|
+
return this.config?.appBaseHref ?? '';
|
|
917
|
+
}
|
|
918
|
+
onPopState(fn) {
|
|
919
|
+
this.window.addEventListener('popstate', fn);
|
|
920
|
+
return () => this.window.removeEventListener('popstate', fn);
|
|
921
|
+
}
|
|
922
|
+
onHashChange(fn) {
|
|
923
|
+
this.window.addEventListener('hashchange', fn);
|
|
924
|
+
return () => this.window.removeEventListener('hashchange', fn);
|
|
925
|
+
}
|
|
926
|
+
get href() {
|
|
927
|
+
return this._platformNavigation.currentEntry.url;
|
|
928
|
+
}
|
|
929
|
+
get protocol() {
|
|
930
|
+
return new URL(this._platformNavigation.currentEntry.url).protocol;
|
|
931
|
+
}
|
|
932
|
+
get hostname() {
|
|
933
|
+
return new URL(this._platformNavigation.currentEntry.url).hostname;
|
|
934
|
+
}
|
|
935
|
+
get port() {
|
|
936
|
+
return new URL(this._platformNavigation.currentEntry.url).port;
|
|
937
|
+
}
|
|
938
|
+
get pathname() {
|
|
939
|
+
return new URL(this._platformNavigation.currentEntry.url).pathname;
|
|
940
|
+
}
|
|
941
|
+
get search() {
|
|
942
|
+
return new URL(this._platformNavigation.currentEntry.url).search;
|
|
943
|
+
}
|
|
944
|
+
get hash() {
|
|
945
|
+
return new URL(this._platformNavigation.currentEntry.url).hash;
|
|
946
|
+
}
|
|
947
|
+
pushState(state, title, url) {
|
|
948
|
+
this._platformNavigation.pushState(state, title, url);
|
|
949
|
+
}
|
|
950
|
+
replaceState(state, title, url) {
|
|
951
|
+
this._platformNavigation.replaceState(state, title, url);
|
|
952
|
+
}
|
|
953
|
+
forward() {
|
|
954
|
+
this._platformNavigation.forward();
|
|
955
|
+
}
|
|
956
|
+
back() {
|
|
957
|
+
this._platformNavigation.back();
|
|
958
|
+
}
|
|
959
|
+
historyGo(relativePosition = 0) {
|
|
960
|
+
this._platformNavigation.go(relativePosition);
|
|
961
|
+
}
|
|
962
|
+
getState() {
|
|
963
|
+
return this._platformNavigation.currentEntry.getHistoryState();
|
|
964
|
+
}
|
|
965
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: FakeNavigationPlatformLocation, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
966
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: FakeNavigationPlatformLocation }); }
|
|
967
|
+
}
|
|
968
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: FakeNavigationPlatformLocation, decorators: [{
|
|
969
|
+
type: Injectable
|
|
970
|
+
}], ctorParameters: () => [] });
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Return a provider for the `FakeNavigation` in place of the real Navigation API.
|
|
974
|
+
*/
|
|
975
|
+
function provideFakePlatformNavigation() {
|
|
976
|
+
return [
|
|
977
|
+
{
|
|
978
|
+
provide: PlatformNavigation,
|
|
979
|
+
useFactory: () => {
|
|
980
|
+
const config = inject(MOCK_PLATFORM_LOCATION_CONFIG, { optional: true });
|
|
981
|
+
return new FakeNavigation(inject(DOCUMENT).defaultView, config?.startUrl ?? 'http://_empty_/');
|
|
982
|
+
},
|
|
983
|
+
},
|
|
984
|
+
{ provide: PlatformLocation, useClass: FakeNavigationPlatformLocation },
|
|
985
|
+
];
|
|
986
|
+
}
|
|
987
|
+
|
|
12
988
|
/**
|
|
13
989
|
* A spy for {@link Location} that allows tests to fire simulated location events.
|
|
14
990
|
*
|
|
@@ -161,10 +1137,10 @@ class SpyLocation {
|
|
|
161
1137
|
this._history.push(new LocationState(path, query, state));
|
|
162
1138
|
this._historyIndex = this._history.length - 1;
|
|
163
1139
|
}
|
|
164
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.1
|
|
165
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.1
|
|
1140
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: SpyLocation, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1141
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: SpyLocation }); }
|
|
166
1142
|
}
|
|
167
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.1
|
|
1143
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: SpyLocation, decorators: [{
|
|
168
1144
|
type: Injectable
|
|
169
1145
|
}] });
|
|
170
1146
|
class LocationState {
|
|
@@ -243,10 +1219,10 @@ class MockLocationStrategy extends LocationStrategy {
|
|
|
243
1219
|
getState() {
|
|
244
1220
|
return this.stateChanges[(this.stateChanges.length || 1) - 1];
|
|
245
1221
|
}
|
|
246
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.1
|
|
247
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.1
|
|
1222
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: MockLocationStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1223
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: MockLocationStrategy }); }
|
|
248
1224
|
}
|
|
249
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.1
|
|
1225
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.0-next.1", ngImport: i0, type: MockLocationStrategy, decorators: [{
|
|
250
1226
|
type: Injectable
|
|
251
1227
|
}], ctorParameters: () => [] });
|
|
252
1228
|
class _MockPopStateEvent {
|
|
@@ -257,224 +1233,6 @@ class _MockPopStateEvent {
|
|
|
257
1233
|
}
|
|
258
1234
|
}
|
|
259
1235
|
|
|
260
|
-
/**
|
|
261
|
-
* Parser from https://tools.ietf.org/html/rfc3986#appendix-B
|
|
262
|
-
* ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
|
|
263
|
-
* 12 3 4 5 6 7 8 9
|
|
264
|
-
*
|
|
265
|
-
* Example: http://www.ics.uci.edu/pub/ietf/uri/#Related
|
|
266
|
-
*
|
|
267
|
-
* Results in:
|
|
268
|
-
*
|
|
269
|
-
* $1 = http:
|
|
270
|
-
* $2 = http
|
|
271
|
-
* $3 = //www.ics.uci.edu
|
|
272
|
-
* $4 = www.ics.uci.edu
|
|
273
|
-
* $5 = /pub/ietf/uri/
|
|
274
|
-
* $6 = <undefined>
|
|
275
|
-
* $7 = <undefined>
|
|
276
|
-
* $8 = #Related
|
|
277
|
-
* $9 = Related
|
|
278
|
-
*/
|
|
279
|
-
const urlParse = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
|
|
280
|
-
function parseUrl(urlStr, baseHref) {
|
|
281
|
-
const verifyProtocol = /^((http[s]?|ftp):\/\/)/;
|
|
282
|
-
let serverBase;
|
|
283
|
-
// URL class requires full URL. If the URL string doesn't start with protocol, we need to add
|
|
284
|
-
// an arbitrary base URL which can be removed afterward.
|
|
285
|
-
if (!verifyProtocol.test(urlStr)) {
|
|
286
|
-
serverBase = 'http://empty.com/';
|
|
287
|
-
}
|
|
288
|
-
let parsedUrl;
|
|
289
|
-
try {
|
|
290
|
-
parsedUrl = new URL(urlStr, serverBase);
|
|
291
|
-
}
|
|
292
|
-
catch (e) {
|
|
293
|
-
const result = urlParse.exec(serverBase || '' + urlStr);
|
|
294
|
-
if (!result) {
|
|
295
|
-
throw new Error(`Invalid URL: ${urlStr} with base: ${baseHref}`);
|
|
296
|
-
}
|
|
297
|
-
const hostSplit = result[4].split(':');
|
|
298
|
-
parsedUrl = {
|
|
299
|
-
protocol: result[1],
|
|
300
|
-
hostname: hostSplit[0],
|
|
301
|
-
port: hostSplit[1] || '',
|
|
302
|
-
pathname: result[5],
|
|
303
|
-
search: result[6],
|
|
304
|
-
hash: result[8],
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
if (parsedUrl.pathname && parsedUrl.pathname.indexOf(baseHref) === 0) {
|
|
308
|
-
parsedUrl.pathname = parsedUrl.pathname.substring(baseHref.length);
|
|
309
|
-
}
|
|
310
|
-
return {
|
|
311
|
-
hostname: (!serverBase && parsedUrl.hostname) || '',
|
|
312
|
-
protocol: (!serverBase && parsedUrl.protocol) || '',
|
|
313
|
-
port: (!serverBase && parsedUrl.port) || '',
|
|
314
|
-
pathname: parsedUrl.pathname || '/',
|
|
315
|
-
search: parsedUrl.search || '',
|
|
316
|
-
hash: parsedUrl.hash || '',
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Provider for mock platform location config
|
|
321
|
-
*
|
|
322
|
-
* @publicApi
|
|
323
|
-
*/
|
|
324
|
-
const MOCK_PLATFORM_LOCATION_CONFIG = new InjectionToken('MOCK_PLATFORM_LOCATION_CONFIG');
|
|
325
|
-
/**
|
|
326
|
-
* Mock implementation of URL state.
|
|
327
|
-
*
|
|
328
|
-
* @publicApi
|
|
329
|
-
*/
|
|
330
|
-
class MockPlatformLocation {
|
|
331
|
-
constructor(config) {
|
|
332
|
-
this.baseHref = '';
|
|
333
|
-
this.hashUpdate = new Subject();
|
|
334
|
-
this.popStateSubject = new Subject();
|
|
335
|
-
this.urlChangeIndex = 0;
|
|
336
|
-
this.urlChanges = [{ hostname: '', protocol: '', port: '', pathname: '/', search: '', hash: '', state: null }];
|
|
337
|
-
if (config) {
|
|
338
|
-
this.baseHref = config.appBaseHref || '';
|
|
339
|
-
const parsedChanges = this.parseChanges(null, config.startUrl || 'http://_empty_/', this.baseHref);
|
|
340
|
-
this.urlChanges[0] = { ...parsedChanges };
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
get hostname() {
|
|
344
|
-
return this.urlChanges[this.urlChangeIndex].hostname;
|
|
345
|
-
}
|
|
346
|
-
get protocol() {
|
|
347
|
-
return this.urlChanges[this.urlChangeIndex].protocol;
|
|
348
|
-
}
|
|
349
|
-
get port() {
|
|
350
|
-
return this.urlChanges[this.urlChangeIndex].port;
|
|
351
|
-
}
|
|
352
|
-
get pathname() {
|
|
353
|
-
return this.urlChanges[this.urlChangeIndex].pathname;
|
|
354
|
-
}
|
|
355
|
-
get search() {
|
|
356
|
-
return this.urlChanges[this.urlChangeIndex].search;
|
|
357
|
-
}
|
|
358
|
-
get hash() {
|
|
359
|
-
return this.urlChanges[this.urlChangeIndex].hash;
|
|
360
|
-
}
|
|
361
|
-
get state() {
|
|
362
|
-
return this.urlChanges[this.urlChangeIndex].state;
|
|
363
|
-
}
|
|
364
|
-
getBaseHrefFromDOM() {
|
|
365
|
-
return this.baseHref;
|
|
366
|
-
}
|
|
367
|
-
onPopState(fn) {
|
|
368
|
-
const subscription = this.popStateSubject.subscribe(fn);
|
|
369
|
-
return () => subscription.unsubscribe();
|
|
370
|
-
}
|
|
371
|
-
onHashChange(fn) {
|
|
372
|
-
const subscription = this.hashUpdate.subscribe(fn);
|
|
373
|
-
return () => subscription.unsubscribe();
|
|
374
|
-
}
|
|
375
|
-
get href() {
|
|
376
|
-
let url = `${this.protocol}//${this.hostname}${this.port ? ':' + this.port : ''}`;
|
|
377
|
-
url += `${this.pathname === '/' ? '' : this.pathname}${this.search}${this.hash}`;
|
|
378
|
-
return url;
|
|
379
|
-
}
|
|
380
|
-
get url() {
|
|
381
|
-
return `${this.pathname}${this.search}${this.hash}`;
|
|
382
|
-
}
|
|
383
|
-
parseChanges(state, url, baseHref = '') {
|
|
384
|
-
// When the `history.state` value is stored, it is always copied.
|
|
385
|
-
state = JSON.parse(JSON.stringify(state));
|
|
386
|
-
return { ...parseUrl(url, baseHref), state };
|
|
387
|
-
}
|
|
388
|
-
replaceState(state, title, newUrl) {
|
|
389
|
-
const { pathname, search, state: parsedState, hash } = this.parseChanges(state, newUrl);
|
|
390
|
-
this.urlChanges[this.urlChangeIndex] = {
|
|
391
|
-
...this.urlChanges[this.urlChangeIndex],
|
|
392
|
-
pathname,
|
|
393
|
-
search,
|
|
394
|
-
hash,
|
|
395
|
-
state: parsedState,
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
pushState(state, title, newUrl) {
|
|
399
|
-
const { pathname, search, state: parsedState, hash } = this.parseChanges(state, newUrl);
|
|
400
|
-
if (this.urlChangeIndex > 0) {
|
|
401
|
-
this.urlChanges.splice(this.urlChangeIndex + 1);
|
|
402
|
-
}
|
|
403
|
-
this.urlChanges.push({
|
|
404
|
-
...this.urlChanges[this.urlChangeIndex],
|
|
405
|
-
pathname,
|
|
406
|
-
search,
|
|
407
|
-
hash,
|
|
408
|
-
state: parsedState,
|
|
409
|
-
});
|
|
410
|
-
this.urlChangeIndex = this.urlChanges.length - 1;
|
|
411
|
-
}
|
|
412
|
-
forward() {
|
|
413
|
-
const oldUrl = this.url;
|
|
414
|
-
const oldHash = this.hash;
|
|
415
|
-
if (this.urlChangeIndex < this.urlChanges.length) {
|
|
416
|
-
this.urlChangeIndex++;
|
|
417
|
-
}
|
|
418
|
-
this.emitEvents(oldHash, oldUrl);
|
|
419
|
-
}
|
|
420
|
-
back() {
|
|
421
|
-
const oldUrl = this.url;
|
|
422
|
-
const oldHash = this.hash;
|
|
423
|
-
if (this.urlChangeIndex > 0) {
|
|
424
|
-
this.urlChangeIndex--;
|
|
425
|
-
}
|
|
426
|
-
this.emitEvents(oldHash, oldUrl);
|
|
427
|
-
}
|
|
428
|
-
historyGo(relativePosition = 0) {
|
|
429
|
-
const oldUrl = this.url;
|
|
430
|
-
const oldHash = this.hash;
|
|
431
|
-
const nextPageIndex = this.urlChangeIndex + relativePosition;
|
|
432
|
-
if (nextPageIndex >= 0 && nextPageIndex < this.urlChanges.length) {
|
|
433
|
-
this.urlChangeIndex = nextPageIndex;
|
|
434
|
-
}
|
|
435
|
-
this.emitEvents(oldHash, oldUrl);
|
|
436
|
-
}
|
|
437
|
-
getState() {
|
|
438
|
-
return this.state;
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Browsers are inconsistent in when they fire events and perform the state updates
|
|
442
|
-
* The most easiest thing to do in our mock is synchronous and that happens to match
|
|
443
|
-
* Firefox and Chrome, at least somewhat closely
|
|
444
|
-
*
|
|
445
|
-
* https://github.com/WICG/navigation-api#watching-for-navigations
|
|
446
|
-
* https://docs.google.com/document/d/1Pdve-DJ1JCGilj9Yqf5HxRJyBKSel5owgOvUJqTauwU/edit#heading=h.3ye4v71wsz94
|
|
447
|
-
* popstate is always sent before hashchange:
|
|
448
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event#when_popstate_is_sent
|
|
449
|
-
*/
|
|
450
|
-
emitEvents(oldHash, oldUrl) {
|
|
451
|
-
this.popStateSubject.next({
|
|
452
|
-
type: 'popstate',
|
|
453
|
-
state: this.getState(),
|
|
454
|
-
oldUrl,
|
|
455
|
-
newUrl: this.url,
|
|
456
|
-
});
|
|
457
|
-
if (oldHash !== this.hash) {
|
|
458
|
-
this.hashUpdate.next({
|
|
459
|
-
type: 'hashchange',
|
|
460
|
-
state: null,
|
|
461
|
-
oldUrl,
|
|
462
|
-
newUrl: this.url,
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.1.2", ngImport: i0, type: MockPlatformLocation, deps: [{ token: MOCK_PLATFORM_LOCATION_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
467
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.1.2", ngImport: i0, type: MockPlatformLocation }); }
|
|
468
|
-
}
|
|
469
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.1.2", ngImport: i0, type: MockPlatformLocation, decorators: [{
|
|
470
|
-
type: Injectable
|
|
471
|
-
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
472
|
-
type: Inject,
|
|
473
|
-
args: [MOCK_PLATFORM_LOCATION_CONFIG]
|
|
474
|
-
}, {
|
|
475
|
-
type: Optional
|
|
476
|
-
}] }] });
|
|
477
|
-
|
|
478
1236
|
/**
|
|
479
1237
|
* Returns mock providers for the `Location` and `LocationStrategy` classes.
|
|
480
1238
|
* The mocks are helpful in tests to fire simulated location events.
|
|
@@ -507,5 +1265,5 @@ function provideLocationMocks() {
|
|
|
507
1265
|
* Generated bundle index. Do not edit.
|
|
508
1266
|
*/
|
|
509
1267
|
|
|
510
|
-
export { MOCK_PLATFORM_LOCATION_CONFIG, MockLocationStrategy, MockPlatformLocation, SpyLocation, provideLocationMocks };
|
|
1268
|
+
export { MOCK_PLATFORM_LOCATION_CONFIG, MockLocationStrategy, MockPlatformLocation, SpyLocation, provideLocationMocks, provideFakePlatformNavigation as ɵprovideFakePlatformNavigation };
|
|
511
1269
|
//# sourceMappingURL=testing.mjs.map
|