@equinor/fusion-framework-module-navigation 7.0.5 → 7.0.7
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/CHANGELOG.md +24 -0
- package/dist/esm/NavigationProvider.js +82 -15
- package/dist/esm/NavigationProvider.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/NavigationProvider.d.ts +9 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +8 -8
- package/src/NavigationProvider.ts +88 -15
- package/src/__tests__/NavigationProvider.test.ts +233 -2
- package/src/version.ts +1 -1
|
@@ -128,13 +128,21 @@ export declare class NavigationProvider extends BaseModuleProvider<INavigationCo
|
|
|
128
128
|
/**
|
|
129
129
|
* Checks whether a pathname falls within the configured basename scope.
|
|
130
130
|
*
|
|
131
|
+
* Uses a path-boundary check to avoid false positives from apps with
|
|
132
|
+
* overlapping name prefixes (e.g. `/apps/my-app` must not match
|
|
133
|
+
* `/apps/my-app-other/foo`).
|
|
134
|
+
*
|
|
131
135
|
* @param pathname - The pathname to check
|
|
132
|
-
* @returns `true` if the pathname
|
|
136
|
+
* @returns `true` if the pathname matches the basename exactly or starts
|
|
137
|
+
* with the basename followed by `/` (or no basename is set)
|
|
133
138
|
*/
|
|
134
139
|
protected _isWithinBasenameScope(pathname: string): boolean;
|
|
135
140
|
/**
|
|
136
141
|
* Localizes a path by stripping the basename prefix from the pathname.
|
|
137
142
|
*
|
|
143
|
+
* Only removes the basename when it matches on a path boundary to avoid
|
|
144
|
+
* incorrectly stripping partial matches.
|
|
145
|
+
*
|
|
138
146
|
* @param location - The full path to localize
|
|
139
147
|
* @returns A new {@link Path} with the basename removed from the pathname
|
|
140
148
|
*/
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "7.0.
|
|
1
|
+
export declare const version = "7.0.7";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@equinor/fusion-framework-module-navigation",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.7",
|
|
4
4
|
"description": "Navigation module for Fusion Framework providing routing and navigation capabilities using React Router 7",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"types": "./dist/types/lib/index.d.ts"
|
|
16
16
|
},
|
|
17
17
|
"./utils": {
|
|
18
|
-
"import": "./dist/esm/utils/index.js",
|
|
19
|
-
"types": "./dist/types/utils/index.d.ts"
|
|
18
|
+
"import": "./dist/esm/lib/utils/index.js",
|
|
19
|
+
"types": "./dist/types/lib/utils/index.d.ts"
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"keywords": [],
|
|
@@ -36,19 +36,19 @@
|
|
|
36
36
|
"zod": "^4.4.3"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"jsdom": "^
|
|
39
|
+
"jsdom": "^30.0.1",
|
|
40
40
|
"rxjs": "^7.8.1",
|
|
41
41
|
"typescript": "^7.0.2",
|
|
42
42
|
"vitest": "^4.1.0",
|
|
43
|
-
"@equinor/fusion-framework-module": "^6.1.1",
|
|
44
43
|
"@equinor/fusion-framework-module-event": "^6.0.1",
|
|
45
|
-
"@equinor/fusion-framework-module
|
|
46
|
-
"@equinor/fusion-observable": "^9.1.1"
|
|
44
|
+
"@equinor/fusion-framework-module": "^6.1.2",
|
|
45
|
+
"@equinor/fusion-observable": "^9.1.1",
|
|
46
|
+
"@equinor/fusion-framework-module-telemetry": "^7.0.2"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@remix-run/router": "^1.0.0",
|
|
50
50
|
"rxjs": "^7.0.0",
|
|
51
|
-
"@equinor/fusion-framework-module": "^6.1.
|
|
51
|
+
"@equinor/fusion-framework-module": "^6.1.2",
|
|
52
52
|
"@equinor/fusion-observable": "^9.1.1"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
@@ -24,16 +24,54 @@ import type { BaseHistory } from './lib';
|
|
|
24
24
|
/**
|
|
25
25
|
* Normalizes a pathname by:
|
|
26
26
|
* - Collapsing multiple consecutive slashes into a single slash
|
|
27
|
-
* - Removing trailing slashes
|
|
28
27
|
*
|
|
29
28
|
* @example
|
|
30
|
-
* normalizePathname("/app//users///profile/") // returns "/app/users/profile"
|
|
31
|
-
* normalizePathname("///multiple///slashes///") // returns "/multiple/slashes"
|
|
29
|
+
* normalizePathname("/app//users///profile/") // returns "/app/users/profile/"
|
|
30
|
+
* normalizePathname("///multiple///slashes///") // returns "/multiple/slashes/"
|
|
32
31
|
*
|
|
33
32
|
* @param path - The pathname to normalize
|
|
34
|
-
* @returns The normalized pathname without consecutive
|
|
33
|
+
* @returns The normalized pathname without consecutive slashes
|
|
35
34
|
*/
|
|
36
|
-
const normalizePathname = (path: string) =>
|
|
35
|
+
const normalizePathname = (path: string): string => {
|
|
36
|
+
// Use iterative approach instead of regex to avoid potential ReDoS with untrusted input
|
|
37
|
+
let result = '';
|
|
38
|
+
let lastWasSlash = false;
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < path.length; i++) {
|
|
41
|
+
const char = path[i];
|
|
42
|
+
if (char === '/') {
|
|
43
|
+
if (!lastWasSlash) {
|
|
44
|
+
result += char;
|
|
45
|
+
lastWasSlash = true;
|
|
46
|
+
}
|
|
47
|
+
// Skip consecutive slashes
|
|
48
|
+
} else {
|
|
49
|
+
result += char;
|
|
50
|
+
lastWasSlash = false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Removes trailing slashes from a path string.
|
|
59
|
+
*
|
|
60
|
+
* @param path - The path to trim
|
|
61
|
+
* @returns The path without trailing slashes
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* stripTrailingSlashes("/apps/my-app/") // returns "/apps/my-app"
|
|
65
|
+
* stripTrailingSlashes("/apps/my-app///") // returns "/apps/my-app"
|
|
66
|
+
*/
|
|
67
|
+
const stripTrailingSlashes = (path: string): string => {
|
|
68
|
+
// Use iterative approach to avoid ReDoS vulnerability
|
|
69
|
+
let endIndex = path.length;
|
|
70
|
+
while (endIndex > 0 && path[endIndex - 1] === '/') {
|
|
71
|
+
endIndex--;
|
|
72
|
+
}
|
|
73
|
+
return path.substring(0, endIndex);
|
|
74
|
+
};
|
|
37
75
|
|
|
38
76
|
/**
|
|
39
77
|
* Navigation provider implementation.
|
|
@@ -132,13 +170,13 @@ export class NavigationProvider
|
|
|
132
170
|
// Extract configuration values
|
|
133
171
|
const { basename, history, telemetry, eventProvider } = args.config;
|
|
134
172
|
|
|
135
|
-
// Normalize the basename to strip trailing slashes
|
|
136
|
-
// the current URL to start with the exact
|
|
137
|
-
// of "/apps/my-app/" would fail to match
|
|
138
|
-
// render nothing (blank page).
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
this.#basename =
|
|
173
|
+
// Normalize the basename to strip trailing slashes and collapse consecutive
|
|
174
|
+
// slashes. React Router requires the current URL to start with the exact
|
|
175
|
+
// basename string, so a basename of "/apps/my-app/" would fail to match
|
|
176
|
+
// the URL "/apps/my-app" and render nothing (blank page).
|
|
177
|
+
// Treat '/' as "no basename" (empty string) since all paths start with '/'.
|
|
178
|
+
const normalizedBasename = basename ? stripTrailingSlashes(normalizePathname(basename)) : '';
|
|
179
|
+
this.#basename = normalizedBasename || undefined;
|
|
142
180
|
this.#event = eventProvider;
|
|
143
181
|
this.#telemetry = telemetry;
|
|
144
182
|
|
|
@@ -316,23 +354,58 @@ export class NavigationProvider
|
|
|
316
354
|
/**
|
|
317
355
|
* Checks whether a pathname falls within the configured basename scope.
|
|
318
356
|
*
|
|
357
|
+
* Uses a path-boundary check to avoid false positives from apps with
|
|
358
|
+
* overlapping name prefixes (e.g. `/apps/my-app` must not match
|
|
359
|
+
* `/apps/my-app-other/foo`).
|
|
360
|
+
*
|
|
319
361
|
* @param pathname - The pathname to check
|
|
320
|
-
* @returns `true` if the pathname
|
|
362
|
+
* @returns `true` if the pathname matches the basename exactly or starts
|
|
363
|
+
* with the basename followed by `/` (or no basename is set)
|
|
321
364
|
*/
|
|
322
365
|
protected _isWithinBasenameScope(pathname: string): boolean {
|
|
323
|
-
|
|
366
|
+
// No basename means everything is in scope
|
|
367
|
+
if (!this.#basename) return true;
|
|
368
|
+
|
|
369
|
+
// Normalize the pathname for comparison (collapse consecutive slashes)
|
|
370
|
+
const normalized = normalizePathname(pathname);
|
|
371
|
+
|
|
372
|
+
// Check exact match or path-boundary prefix
|
|
373
|
+
return normalized === this.#basename || normalized.startsWith(`${this.#basename}/`);
|
|
324
374
|
}
|
|
325
375
|
|
|
326
376
|
/**
|
|
327
377
|
* Localizes a path by stripping the basename prefix from the pathname.
|
|
328
378
|
*
|
|
379
|
+
* Only removes the basename when it matches on a path boundary to avoid
|
|
380
|
+
* incorrectly stripping partial matches.
|
|
381
|
+
*
|
|
329
382
|
* @param location - The full path to localize
|
|
330
383
|
* @returns A new {@link Path} with the basename removed from the pathname
|
|
331
384
|
*/
|
|
332
385
|
protected _localizePath(location: Path): Path {
|
|
333
386
|
const { pathname, search, hash } = location;
|
|
387
|
+
|
|
388
|
+
// No basename - return normalized pathname as-is
|
|
389
|
+
if (!this.#basename) {
|
|
390
|
+
return {
|
|
391
|
+
pathname: normalizePathname(pathname) || '/',
|
|
392
|
+
search,
|
|
393
|
+
hash,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const normalized = normalizePathname(pathname);
|
|
398
|
+
let localized = normalized;
|
|
399
|
+
|
|
400
|
+
// Strip basename only if it matches at path boundary
|
|
401
|
+
if (normalized === this.#basename) {
|
|
402
|
+
localized = '/';
|
|
403
|
+
} else if (normalized.startsWith(`${this.#basename}/`)) {
|
|
404
|
+
localized = normalized.slice(this.#basename.length);
|
|
405
|
+
}
|
|
406
|
+
|
|
334
407
|
return {
|
|
335
|
-
pathname:
|
|
408
|
+
pathname: localized || '/',
|
|
336
409
|
search,
|
|
337
410
|
hash,
|
|
338
411
|
};
|
|
@@ -58,14 +58,245 @@ describe('NavigationProvider', () => {
|
|
|
58
58
|
expect(disposeSpy).toHaveBeenCalled();
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
it('should
|
|
61
|
+
it('should normalize root basename "/" to empty string', () => {
|
|
62
62
|
const providerWithRootBasename = new NavigationProvider({
|
|
63
63
|
version: '1.0.0',
|
|
64
64
|
config: { history, basename: '/' },
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
// Root basename '/' should be treated as "no basename"
|
|
68
|
+
expect(providerWithRootBasename.basename).toBe('');
|
|
68
69
|
|
|
69
70
|
providerWithRootBasename.dispose();
|
|
70
71
|
});
|
|
72
|
+
|
|
73
|
+
describe('basename normalization', () => {
|
|
74
|
+
it('should strip trailing slashes from basename', () => {
|
|
75
|
+
const provider1 = new NavigationProvider({
|
|
76
|
+
version: '1.0.0',
|
|
77
|
+
config: { history, basename: '/apps/my-app/' },
|
|
78
|
+
});
|
|
79
|
+
expect(provider1.basename).toBe('/apps/my-app');
|
|
80
|
+
provider1.dispose();
|
|
81
|
+
|
|
82
|
+
const provider2 = new NavigationProvider({
|
|
83
|
+
version: '1.0.0',
|
|
84
|
+
config: { history, basename: '/apps/my-app///' },
|
|
85
|
+
});
|
|
86
|
+
expect(provider2.basename).toBe('/apps/my-app');
|
|
87
|
+
provider2.dispose();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should collapse consecutive slashes in basename', () => {
|
|
91
|
+
const provider = new NavigationProvider({
|
|
92
|
+
version: '1.0.0',
|
|
93
|
+
config: { history, basename: '/apps//my-app' },
|
|
94
|
+
});
|
|
95
|
+
expect(provider.basename).toBe('/apps/my-app');
|
|
96
|
+
provider.dispose();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should handle pathological input efficiently (ReDoS protection)', () => {
|
|
100
|
+
// Create a string with many consecutive slashes to test performance
|
|
101
|
+
// This would cause ReDoS with certain regex patterns
|
|
102
|
+
const manySlashes = `/apps/${'/'.repeat(10000)}my-app`;
|
|
103
|
+
|
|
104
|
+
const startTime = Date.now();
|
|
105
|
+
const provider = new NavigationProvider({
|
|
106
|
+
version: '1.0.0',
|
|
107
|
+
config: { history, basename: manySlashes },
|
|
108
|
+
});
|
|
109
|
+
const endTime = Date.now();
|
|
110
|
+
|
|
111
|
+
// Should complete in reasonable time (< 100ms for 10k slashes)
|
|
112
|
+
expect(endTime - startTime).toBeLessThan(100);
|
|
113
|
+
expect(provider.basename).toBe('/apps/my-app');
|
|
114
|
+
provider.dispose();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle pathological trailing slashes efficiently (ReDoS protection)', () => {
|
|
118
|
+
// Create a string with many trailing slashes
|
|
119
|
+
// The /\/+$/ regex pattern would cause ReDoS with this input
|
|
120
|
+
const manyTrailingSlashes = `/apps/my-app${'/'.repeat(10000)}`;
|
|
121
|
+
|
|
122
|
+
const startTime = Date.now();
|
|
123
|
+
const provider = new NavigationProvider({
|
|
124
|
+
version: '1.0.0',
|
|
125
|
+
config: { history, basename: manyTrailingSlashes },
|
|
126
|
+
});
|
|
127
|
+
const endTime = Date.now();
|
|
128
|
+
|
|
129
|
+
// Should complete in reasonable time (< 100ms for 10k trailing slashes)
|
|
130
|
+
expect(endTime - startTime).toBeLessThan(100);
|
|
131
|
+
expect(provider.basename).toBe('/apps/my-app');
|
|
132
|
+
provider.dispose();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should handle undefined basename', () => {
|
|
136
|
+
const provider = new NavigationProvider({
|
|
137
|
+
version: '1.0.0',
|
|
138
|
+
config: { history, basename: undefined },
|
|
139
|
+
});
|
|
140
|
+
expect(provider.basename).toBe('');
|
|
141
|
+
provider.dispose();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('_isWithinBasenameScope', () => {
|
|
146
|
+
it('should allow all paths when basename is "/" (no basename)', () => {
|
|
147
|
+
const provider = new NavigationProvider({
|
|
148
|
+
version: '1.0.0',
|
|
149
|
+
config: { history, basename: '/' },
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// All paths should be in scope
|
|
153
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
154
|
+
expect((provider as any)._isWithinBasenameScope('/')).toBe(true);
|
|
155
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
156
|
+
expect((provider as any)._isWithinBasenameScope('/apps')).toBe(true);
|
|
157
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
158
|
+
expect((provider as any)._isWithinBasenameScope('/apps/my-app')).toBe(true);
|
|
159
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
160
|
+
expect((provider as any)._isWithinBasenameScope('/apps/my-app/users')).toBe(true);
|
|
161
|
+
|
|
162
|
+
provider.dispose();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should check path boundaries correctly with basename', () => {
|
|
166
|
+
const provider = new NavigationProvider({
|
|
167
|
+
version: '1.0.0',
|
|
168
|
+
config: { history, basename: '/apps/my-app' },
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Exact match should be in scope
|
|
172
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
173
|
+
expect((provider as any)._isWithinBasenameScope('/apps/my-app')).toBe(true);
|
|
174
|
+
|
|
175
|
+
// Paths starting with basename/ should be in scope
|
|
176
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
177
|
+
expect((provider as any)._isWithinBasenameScope('/apps/my-app/')).toBe(true);
|
|
178
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
179
|
+
expect((provider as any)._isWithinBasenameScope('/apps/my-app/users')).toBe(true);
|
|
180
|
+
|
|
181
|
+
// Similar but different path should NOT be in scope (path boundary check)
|
|
182
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
183
|
+
expect((provider as any)._isWithinBasenameScope('/apps/my-app-other')).toBe(false);
|
|
184
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
185
|
+
expect((provider as any)._isWithinBasenameScope('/apps/my-app-other/users')).toBe(false);
|
|
186
|
+
|
|
187
|
+
// Completely different paths should NOT be in scope
|
|
188
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
189
|
+
expect((provider as any)._isWithinBasenameScope('/other')).toBe(false);
|
|
190
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
191
|
+
expect((provider as any)._isWithinBasenameScope('/')).toBe(false);
|
|
192
|
+
|
|
193
|
+
provider.dispose();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should handle consecutive slashes in pathname', () => {
|
|
197
|
+
const provider = new NavigationProvider({
|
|
198
|
+
version: '1.0.0',
|
|
199
|
+
config: { history, basename: '/apps/my-app' },
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Pathname with consecutive slashes should be normalized
|
|
203
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
204
|
+
expect((provider as any)._isWithinBasenameScope('/apps//my-app')).toBe(true);
|
|
205
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
206
|
+
expect((provider as any)._isWithinBasenameScope('/apps/my-app//users')).toBe(true);
|
|
207
|
+
|
|
208
|
+
provider.dispose();
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('_localizePath', () => {
|
|
213
|
+
it('should strip basename from paths on boundary', () => {
|
|
214
|
+
const provider = new NavigationProvider({
|
|
215
|
+
version: '1.0.0',
|
|
216
|
+
config: { history, basename: '/apps/my-app' },
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Exact match should become '/'
|
|
220
|
+
expect(
|
|
221
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
222
|
+
(provider as any)._localizePath({ pathname: '/apps/my-app', search: '', hash: '' }),
|
|
223
|
+
).toEqual({ pathname: '/', search: '', hash: '' });
|
|
224
|
+
|
|
225
|
+
// Path with basename prefix should have it stripped
|
|
226
|
+
expect(
|
|
227
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
228
|
+
(provider as any)._localizePath({
|
|
229
|
+
pathname: '/apps/my-app/users',
|
|
230
|
+
search: '?q=test',
|
|
231
|
+
hash: '#section',
|
|
232
|
+
}),
|
|
233
|
+
).toEqual({ pathname: '/users', search: '?q=test', hash: '#section' });
|
|
234
|
+
|
|
235
|
+
provider.dispose();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should not strip similar paths without boundary match', () => {
|
|
239
|
+
const provider = new NavigationProvider({
|
|
240
|
+
version: '1.0.0',
|
|
241
|
+
config: { history, basename: '/apps/my-app' },
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Path that starts similarly but isn't a real match should not be stripped
|
|
245
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
246
|
+
const result = (provider as any)._localizePath({
|
|
247
|
+
pathname: '/apps/my-app-other/users',
|
|
248
|
+
search: '',
|
|
249
|
+
hash: '',
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Should not strip anything since it doesn't match on path boundary
|
|
253
|
+
expect(result.pathname).toBe('/apps/my-app-other/users');
|
|
254
|
+
|
|
255
|
+
provider.dispose();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should handle root basename "/" correctly', () => {
|
|
259
|
+
const provider = new NavigationProvider({
|
|
260
|
+
version: '1.0.0',
|
|
261
|
+
config: { history, basename: '/' },
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// With no basename, paths should be returned normalized
|
|
265
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
266
|
+
expect((provider as any)._localizePath({ pathname: '/', search: '', hash: '' })).toEqual({
|
|
267
|
+
pathname: '/',
|
|
268
|
+
search: '',
|
|
269
|
+
hash: '',
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
273
|
+
expect((provider as any)._localizePath({ pathname: '/apps', search: '', hash: '' })).toEqual({
|
|
274
|
+
pathname: '/apps',
|
|
275
|
+
search: '',
|
|
276
|
+
hash: '',
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
expect(
|
|
280
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
281
|
+
(provider as any)._localizePath({ pathname: '/apps/my-app', search: '', hash: '' }),
|
|
282
|
+
).toEqual({ pathname: '/apps/my-app', search: '', hash: '' });
|
|
283
|
+
|
|
284
|
+
provider.dispose();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should normalize consecutive slashes', () => {
|
|
288
|
+
const provider = new NavigationProvider({
|
|
289
|
+
version: '1.0.0',
|
|
290
|
+
config: { history, basename: '/apps/my-app' },
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Consecutive slashes should be collapsed
|
|
294
|
+
expect(
|
|
295
|
+
// biome-ignore lint/suspicious/noExplicitAny: Allow usage of 'any' in test files for mocking purposes
|
|
296
|
+
(provider as any)._localizePath({ pathname: '/apps//my-app//users', search: '', hash: '' }),
|
|
297
|
+
).toEqual({ pathname: '/users', search: '', hash: '' });
|
|
298
|
+
|
|
299
|
+
provider.dispose();
|
|
300
|
+
});
|
|
301
|
+
});
|
|
71
302
|
});
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '7.0.
|
|
2
|
+
export const version = '7.0.7';
|