/
spectacular-feature-router.ts
97 lines (85 loc) · 3.54 KB
/
spectacular-feature-router.ts
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
import { inject, Injectable, NgZone } from '@angular/core';
import type { NavigationExtras, UrlTree } from '@angular/router';
import { Router, UrlSegment } from '@angular/router';
import { featurePathToken } from '../configuration/feature-path.token';
import { ensureLeadingCharacter } from '../util-text/ensure-leading-character';
import { relativeFeatureUrlPrefix } from './relative-feature-url-prefix';
/**
* A subset of Angular's `Router` server adjusted to the Angular feature module
* under test.
*/
@Injectable()
export class SpectacularFeatureRouter {
readonly #featurePath = inject(featurePathToken);
readonly #router = inject(Router);
readonly #ngZone = inject(NgZone);
/**
* Navigate based on the provided array of commands and a starting point. If
* no starting route is provided, the navigation is absolute.
*
* If the first command is a tilde (`~`), the navigation is relative to the
* feature under test.
*
* Wraps `Router#navigate`.
*
* @param commands An array of URL fragments with which to construct the
* target URL. If the path is static, can be the literal URL string. For a
* dynamic path, pass an array of path segments, followed by the parameters
* for each segment. The fragments are applied to the current URL or the one
* provided in the relativeTo property of the options object, if supplied.
* If the first command is a tilde (`~`), the navigation is relative to the
* feature under test.
* @param extras An options object that determines how the URL should be
* constructed or interpreted. Optional. Default is `{ skipLocationChange: false }`.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
navigate(commands: any[], extras?: NavigationExtras): Promise<boolean> {
const [head, ...tail] = commands;
if (head === relativeFeatureUrlPrefix) {
commands = [this.#featurePath, ...tail];
}
return this.#ngZone.run(() => this.#router.navigate(commands, extras));
}
/**
* Navigates to a view using an absolute route path.
*
* If the URL is prefixed with tilde (`~`), the navigation is relative to the
* feature under test.
*
* Wraps `Router#navigateByUrl`.
*
* @param url An absolute path for a defined route. The function does not
* apply any delta to the current URL. If the URL is prefixed with tilde
* (`~`), the navigation is relative to the feature under test.
* @param extras An object containing properties that modify the navigation
* strategy. Optional. Default is `{ skipLocationChange: false }`.
*/
navigateByUrl(
url: string | UrlTree,
extras?: NavigationExtras
): Promise<boolean> {
if (typeof url !== 'string') {
// url is UrlTree
const isRelativeFeatureUrlTree =
url.root.children['primary']?.segments[0].path === '~';
if (isRelativeFeatureUrlTree) {
const [, ...tail] = url.root.children['primary'].segments;
const featureSegment = new UrlSegment(this.#featurePath, {});
url.root.children['primary'].segments = [featureSegment, ...tail];
}
url = this.#router.serializeUrl(url);
}
const needle = relativeFeatureUrlPrefix + '/';
if (url.startsWith(needle)) {
url = url.substr(needle.length);
url = this.#prependFeaturePath(url);
}
return this.#ngZone.run(() => this.#router.navigateByUrl(url, extras));
}
#prependFeaturePath(url: string): string {
return (
this.#router.serializeUrl(this.#router.parseUrl(this.#featurePath)) +
ensureLeadingCharacter('/', url)
);
}
}