@equinor/fusion-framework-plugin-context-navigation 0.1.0 → 0.1.2
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/dist/esm/ContextNavigationConfigurator.js +178 -0
- package/dist/esm/ContextNavigationConfigurator.js.map +1 -0
- package/dist/esm/__tests__/active-app-navigation-events.test.js +115 -0
- package/dist/esm/__tests__/active-app-navigation-events.test.js.map +1 -0
- package/dist/esm/__tests__/apply-navigation.test.js +152 -0
- package/dist/esm/__tests__/apply-navigation.test.js.map +1 -0
- package/dist/esm/__tests__/enable.test.js +91 -0
- package/dist/esm/__tests__/enable.test.js.map +1 -0
- package/dist/esm/__tests__/guard-handlers.test.js +82 -0
- package/dist/esm/__tests__/guard-handlers.test.js.map +1 -0
- package/dist/esm/__tests__/plugin.test.js +483 -0
- package/dist/esm/__tests__/plugin.test.js.map +1 -0
- package/dist/esm/__tests__/reconcile.test.js +164 -0
- package/dist/esm/__tests__/reconcile.test.js.map +1 -0
- package/dist/esm/adapters/create-path-adapter.js +96 -0
- package/dist/esm/adapters/create-path-adapter.js.map +1 -0
- package/dist/esm/adapters/create-query-adapter.js +83 -0
- package/dist/esm/adapters/create-query-adapter.js.map +1 -0
- package/dist/esm/adapters/custom-adapter/create-custom-adapter.js +116 -0
- package/dist/esm/adapters/custom-adapter/create-custom-adapter.js.map +1 -0
- package/dist/esm/adapters/custom-adapter/index.js +5 -0
- package/dist/esm/adapters/custom-adapter/index.js.map +1 -0
- package/dist/esm/adapters/custom-adapter/normalize-string-result.js +37 -0
- package/dist/esm/adapters/custom-adapter/normalize-string-result.js.map +1 -0
- package/dist/esm/adapters/custom-adapter/to-app-relative.js +34 -0
- package/dist/esm/adapters/custom-adapter/to-app-relative.js.map +1 -0
- package/dist/esm/adapters/custom-adapter/to-full-path.js +28 -0
- package/dist/esm/adapters/custom-adapter/to-full-path.js.map +1 -0
- package/dist/esm/adapters/index.js +4 -0
- package/dist/esm/adapters/index.js.map +1 -0
- package/dist/esm/adapters/types.js +2 -0
- package/dist/esm/adapters/types.js.map +1 -0
- package/dist/esm/apply-navigation.js +102 -0
- package/dist/esm/apply-navigation.js.map +1 -0
- package/dist/esm/constants/context-query-param-key.js +8 -0
- package/dist/esm/constants/context-query-param-key.js.map +1 -0
- package/dist/esm/constants/index.js +3 -0
- package/dist/esm/constants/index.js.map +1 -0
- package/dist/esm/constants/uuid-pattern.js +8 -0
- package/dist/esm/constants/uuid-pattern.js.map +1 -0
- package/dist/esm/create-context-navigation-plugin.js +124 -0
- package/dist/esm/create-context-navigation-plugin.js.map +1 -0
- package/dist/esm/enable-context-navigation.js +40 -0
- package/dist/esm/enable-context-navigation.js.map +1 -0
- package/dist/esm/events.js +2 -0
- package/dist/esm/events.js.map +1 -0
- package/dist/esm/guard-handlers/consume-own-nav-token.js +29 -0
- package/dist/esm/guard-handlers/consume-own-nav-token.js.map +1 -0
- package/dist/esm/guard-handlers/guard-tick-deps.js +2 -0
- package/dist/esm/guard-handlers/guard-tick-deps.js.map +1 -0
- package/dist/esm/guard-handlers/guard-tick-payload.js +2 -0
- package/dist/esm/guard-handlers/guard-tick-payload.js.map +1 -0
- package/dist/esm/guard-handlers/handle-push-mode-guard.js +44 -0
- package/dist/esm/guard-handlers/handle-push-mode-guard.js.map +1 -0
- package/dist/esm/guard-handlers/handle-replace-mode-guard.js +40 -0
- package/dist/esm/guard-handlers/handle-replace-mode-guard.js.map +1 -0
- package/dist/esm/guard-handlers/index.js +5 -0
- package/dist/esm/guard-handlers/index.js.map +1 -0
- package/dist/esm/guard-handlers/is-in-app-scope.js +19 -0
- package/dist/esm/guard-handlers/is-in-app-scope.js.map +1 -0
- package/dist/esm/helpers/get-current-url.js +11 -0
- package/dist/esm/helpers/get-current-url.js.map +1 -0
- package/dist/esm/helpers/index.js +6 -0
- package/dist/esm/helpers/index.js.map +1 -0
- package/dist/esm/helpers/normalize-path-from-url.js +14 -0
- package/dist/esm/helpers/normalize-path-from-url.js.map +1 -0
- package/dist/esm/helpers/normalize-path.js +30 -0
- package/dist/esm/helpers/normalize-path.js.map +1 -0
- package/dist/esm/helpers/resolve-adapter.js +23 -0
- package/dist/esm/helpers/resolve-adapter.js.map +1 -0
- package/dist/esm/helpers/strip-query-params.js +14 -0
- package/dist/esm/helpers/strip-query-params.js.map +1 -0
- package/dist/esm/index.js +13 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/operators/active-app-navigation-events.js +52 -0
- package/dist/esm/operators/active-app-navigation-events.js.map +1 -0
- package/dist/esm/reconcile.js +76 -0
- package/dist/esm/reconcile.js.map +1 -0
- package/dist/esm/sources/app-first-source.js +41 -0
- package/dist/esm/sources/app-first-source.js.map +1 -0
- package/dist/esm/sources/context-first-source.js +47 -0
- package/dist/esm/sources/context-first-source.js.map +1 -0
- package/dist/esm/sources/index.js +4 -0
- package/dist/esm/sources/index.js.map +1 -0
- package/dist/esm/sources/types.js +16 -0
- package/dist/esm/sources/types.js.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/utils/has-custom-context-generators.js +14 -0
- package/dist/esm/utils/has-custom-context-generators.js.map +1 -0
- package/dist/esm/utils/legacy-app-navigation-fix.js +45 -0
- package/dist/esm/utils/legacy-app-navigation-fix.js.map +1 -0
- package/dist/esm/utils/resolve-context-from-url.js +37 -0
- package/dist/esm/utils/resolve-context-from-url.js.map +1 -0
- package/dist/esm/utils/url/build-app-route.js +20 -0
- package/dist/esm/utils/url/build-app-route.js.map +1 -0
- package/dist/esm/utils/url/build-context-url-for-strategy.js +43 -0
- package/dist/esm/utils/url/build-context-url-for-strategy.js.map +1 -0
- package/dist/esm/utils/url/index.js +7 -0
- package/dist/esm/utils/url/index.js.map +1 -0
- package/dist/esm/utils/url/parse-app-route.js +47 -0
- package/dist/esm/utils/url/parse-app-route.js.map +1 -0
- package/dist/esm/utils/url/resolve-context-id-from-url.js +29 -0
- package/dist/esm/utils/url/resolve-context-id-from-url.js.map +1 -0
- package/dist/esm/utils/url/strip-context-query-param.js +20 -0
- package/dist/esm/utils/url/strip-context-query-param.js.map +1 -0
- package/dist/esm/version.js +3 -0
- package/dist/esm/version.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/ContextNavigationConfigurator.d.ts +125 -0
- package/dist/types/ContextNavigationConfigurator.d.ts.map +1 -0
- package/dist/types/__tests__/active-app-navigation-events.test.d.ts +2 -0
- package/dist/types/__tests__/active-app-navigation-events.test.d.ts.map +1 -0
- package/dist/types/__tests__/apply-navigation.test.d.ts +2 -0
- package/dist/types/__tests__/apply-navigation.test.d.ts.map +1 -0
- package/dist/types/__tests__/enable.test.d.ts +2 -0
- package/dist/types/__tests__/enable.test.d.ts.map +1 -0
- package/dist/types/__tests__/guard-handlers.test.d.ts +2 -0
- package/dist/types/__tests__/guard-handlers.test.d.ts.map +1 -0
- package/dist/types/__tests__/plugin.test.d.ts +2 -0
- package/dist/types/__tests__/plugin.test.d.ts.map +1 -0
- package/dist/types/__tests__/reconcile.test.d.ts +2 -0
- package/dist/types/__tests__/reconcile.test.d.ts.map +1 -0
- package/dist/types/adapters/create-path-adapter.d.ts +27 -0
- package/dist/types/adapters/create-path-adapter.d.ts.map +1 -0
- package/dist/types/adapters/create-query-adapter.d.ts +34 -0
- package/dist/types/adapters/create-query-adapter.d.ts.map +1 -0
- package/dist/types/adapters/custom-adapter/create-custom-adapter.d.ts +29 -0
- package/dist/types/adapters/custom-adapter/create-custom-adapter.d.ts.map +1 -0
- package/dist/types/adapters/custom-adapter/index.d.ts +5 -0
- package/dist/types/adapters/custom-adapter/index.d.ts.map +1 -0
- package/dist/types/adapters/custom-adapter/normalize-string-result.d.ts +21 -0
- package/dist/types/adapters/custom-adapter/normalize-string-result.d.ts.map +1 -0
- package/dist/types/adapters/custom-adapter/to-app-relative.d.ts +25 -0
- package/dist/types/adapters/custom-adapter/to-app-relative.d.ts.map +1 -0
- package/dist/types/adapters/custom-adapter/to-full-path.d.ts +22 -0
- package/dist/types/adapters/custom-adapter/to-full-path.d.ts.map +1 -0
- package/dist/types/adapters/index.d.ts +5 -0
- package/dist/types/adapters/index.d.ts.map +1 -0
- package/dist/types/adapters/types.d.ts +73 -0
- package/dist/types/adapters/types.d.ts.map +1 -0
- package/dist/types/apply-navigation.d.ts +88 -0
- package/dist/types/apply-navigation.d.ts.map +1 -0
- package/dist/types/constants/context-query-param-key.d.ts +8 -0
- package/dist/types/constants/context-query-param-key.d.ts.map +1 -0
- package/dist/types/constants/index.d.ts +3 -0
- package/dist/types/constants/index.d.ts.map +1 -0
- package/dist/types/constants/uuid-pattern.d.ts +8 -0
- package/dist/types/constants/uuid-pattern.d.ts.map +1 -0
- package/dist/types/create-context-navigation-plugin.d.ts +58 -0
- package/dist/types/create-context-navigation-plugin.d.ts.map +1 -0
- package/dist/types/enable-context-navigation.d.ts +21 -0
- package/dist/types/enable-context-navigation.d.ts.map +1 -0
- package/dist/types/events.d.ts +16 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/guard-handlers/consume-own-nav-token.d.ts +21 -0
- package/dist/types/guard-handlers/consume-own-nav-token.d.ts.map +1 -0
- package/dist/types/guard-handlers/guard-tick-deps.d.ts +20 -0
- package/dist/types/guard-handlers/guard-tick-deps.d.ts.map +1 -0
- package/dist/types/guard-handlers/guard-tick-payload.d.ts +15 -0
- package/dist/types/guard-handlers/guard-tick-payload.d.ts.map +1 -0
- package/dist/types/guard-handlers/handle-push-mode-guard.d.ts +24 -0
- package/dist/types/guard-handlers/handle-push-mode-guard.d.ts.map +1 -0
- package/dist/types/guard-handlers/handle-replace-mode-guard.d.ts +19 -0
- package/dist/types/guard-handlers/handle-replace-mode-guard.d.ts.map +1 -0
- package/dist/types/guard-handlers/index.d.ts +7 -0
- package/dist/types/guard-handlers/index.d.ts.map +1 -0
- package/dist/types/guard-handlers/is-in-app-scope.d.ts +15 -0
- package/dist/types/guard-handlers/is-in-app-scope.d.ts.map +1 -0
- package/dist/types/helpers/get-current-url.d.ts +10 -0
- package/dist/types/helpers/get-current-url.d.ts.map +1 -0
- package/dist/types/helpers/index.d.ts +6 -0
- package/dist/types/helpers/index.d.ts.map +1 -0
- package/dist/types/helpers/normalize-path-from-url.d.ts +11 -0
- package/dist/types/helpers/normalize-path-from-url.d.ts.map +1 -0
- package/dist/types/helpers/normalize-path.d.ts +14 -0
- package/dist/types/helpers/normalize-path.d.ts.map +1 -0
- package/dist/types/helpers/resolve-adapter.d.ts +14 -0
- package/dist/types/helpers/resolve-adapter.d.ts.map +1 -0
- package/dist/types/helpers/strip-query-params.d.ts +12 -0
- package/dist/types/helpers/strip-query-params.d.ts.map +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/operators/active-app-navigation-events.d.ts +47 -0
- package/dist/types/operators/active-app-navigation-events.d.ts.map +1 -0
- package/dist/types/reconcile.d.ts +43 -0
- package/dist/types/reconcile.d.ts.map +1 -0
- package/dist/types/sources/app-first-source.d.ts +14 -0
- package/dist/types/sources/app-first-source.d.ts.map +1 -0
- package/dist/types/sources/context-first-source.d.ts +15 -0
- package/dist/types/sources/context-first-source.d.ts.map +1 -0
- package/dist/types/sources/index.d.ts +4 -0
- package/dist/types/sources/index.d.ts.map +1 -0
- package/dist/types/sources/types.d.ts +61 -0
- package/dist/types/sources/types.d.ts.map +1 -0
- package/dist/types/types.d.ts +126 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/utils/has-custom-context-generators.d.ts +13 -0
- package/dist/types/utils/has-custom-context-generators.d.ts.map +1 -0
- package/dist/types/utils/legacy-app-navigation-fix.d.ts +33 -0
- package/dist/types/utils/legacy-app-navigation-fix.d.ts.map +1 -0
- package/dist/types/utils/resolve-context-from-url.d.ts +20 -0
- package/dist/types/utils/resolve-context-from-url.d.ts.map +1 -0
- package/dist/types/utils/url/build-app-route.d.ts +11 -0
- package/dist/types/utils/url/build-app-route.d.ts.map +1 -0
- package/dist/types/utils/url/build-context-url-for-strategy.d.ts +14 -0
- package/dist/types/utils/url/build-context-url-for-strategy.d.ts.map +1 -0
- package/dist/types/utils/url/index.d.ts +8 -0
- package/dist/types/utils/url/index.d.ts.map +1 -0
- package/dist/types/utils/url/parse-app-route.d.ts +20 -0
- package/dist/types/utils/url/parse-app-route.d.ts.map +1 -0
- package/dist/types/utils/url/resolve-context-id-from-url.d.ts +8 -0
- package/dist/types/utils/url/resolve-context-id-from-url.d.ts.map +1 -0
- package/dist/types/utils/url/strip-context-query-param.d.ts +12 -0
- package/dist/types/utils/url/strip-context-query-param.d.ts.map +1 -0
- package/dist/types/version.d.ts +2 -0
- package/dist/types/version.d.ts.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createPathAdapter } from './adapters/create-path-adapter';
|
|
2
|
+
import { createQueryAdapter } from './adapters/create-query-adapter';
|
|
3
|
+
import { createCustomAdapter } from './adapters/custom-adapter/create-custom-adapter';
|
|
4
|
+
import { resolveContextFromUrl } from './utils/resolve-context-from-url';
|
|
5
|
+
import { appFirstSource } from './sources/app-first-source';
|
|
6
|
+
/**
|
|
7
|
+
* Resolves the default origin used for URL construction.
|
|
8
|
+
*
|
|
9
|
+
* Browser environments use `window.location.origin`. Non-browser environments
|
|
10
|
+
* fall back to a stable localhost origin so tests and SSR code can still build
|
|
11
|
+
* deterministic URLs.
|
|
12
|
+
*
|
|
13
|
+
* @returns The origin string used by the context-navigation plugin.
|
|
14
|
+
*/
|
|
15
|
+
const resolveDefaultOrigin = () => typeof window === 'undefined' ? 'http://localhost' : window.location.origin;
|
|
16
|
+
/**
|
|
17
|
+
* Configurator for the context-navigation plugin.
|
|
18
|
+
*
|
|
19
|
+
* Provides a fluent builder API for portal developers.
|
|
20
|
+
* Adapters are registered with priority — the first adapter whose
|
|
21
|
+
* `canHandle()` returns `true` is used for navigation.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* enableContextNavigation(configurator, (builder) => {
|
|
26
|
+
* builder.setPortalName('my-portal');
|
|
27
|
+
* builder.setDebug(true);
|
|
28
|
+
* // Custom adapters registered at higher priority override defaults
|
|
29
|
+
* builder.registerAdapter(myHashAdapter);
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export class ContextNavigationConfigurator {
|
|
34
|
+
#config = {};
|
|
35
|
+
#adapters = [];
|
|
36
|
+
/**
|
|
37
|
+
* Register a navigation adapter or adapter factory.
|
|
38
|
+
*
|
|
39
|
+
* - **Object** — a static adapter with `canHandle`, `encode`, `decode`.
|
|
40
|
+
* - **Function** — a factory called with the resolution context that
|
|
41
|
+
* returns a bound adapter or `null` to skip.
|
|
42
|
+
*
|
|
43
|
+
* Adapters are evaluated in registration order. The first match wins.
|
|
44
|
+
*
|
|
45
|
+
* **Note:** Registering any adapter disables the built-in defaults
|
|
46
|
+
* (custom, query, path). Register all adapters you need explicitly.
|
|
47
|
+
*
|
|
48
|
+
* @param adapter - The adapter instance or factory function to register.
|
|
49
|
+
* @returns The configurator instance for chaining.
|
|
50
|
+
*/
|
|
51
|
+
registerAdapter(adapter) {
|
|
52
|
+
this.#adapters.push(adapter);
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Set the portal name used in debug output.
|
|
57
|
+
* @default 'Portal'
|
|
58
|
+
*
|
|
59
|
+
* @param name - The portal name to use in debug output.
|
|
60
|
+
* @returns The configurator instance for chaining.
|
|
61
|
+
*/
|
|
62
|
+
setPortalName(name) {
|
|
63
|
+
this.#config.portalName = name;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Set the origin for constructing absolute URLs.
|
|
68
|
+
* @default window.location.origin
|
|
69
|
+
*
|
|
70
|
+
* @param origin - The origin string to use for URL construction.
|
|
71
|
+
* @returns The configurator instance for chaining.
|
|
72
|
+
*/
|
|
73
|
+
setOrigin(origin) {
|
|
74
|
+
this.#config.origin = origin;
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Enable or disable the URL guard.
|
|
79
|
+
* @default true
|
|
80
|
+
*
|
|
81
|
+
* @param enabled - Whether to enable the URL guard.
|
|
82
|
+
* @returns The configurator instance for chaining.
|
|
83
|
+
*/
|
|
84
|
+
setUrlGuard(enabled) {
|
|
85
|
+
this.#config.enableUrlGuard = enabled;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Enable or disable verbose debug logging.
|
|
90
|
+
* @default false
|
|
91
|
+
*
|
|
92
|
+
* @param enabled - Whether to enable debug logging.
|
|
93
|
+
* @returns The configurator instance for chaining.
|
|
94
|
+
*/
|
|
95
|
+
setDebug(enabled) {
|
|
96
|
+
this.#config.debug = enabled;
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Set a function that computes the URL to navigate to when context is cleared.
|
|
101
|
+
*
|
|
102
|
+
* When set, the reconciler bypasses adapter encoding for null context
|
|
103
|
+
* and navigates directly to the returned URL. Use in context-portal where
|
|
104
|
+
* clearing context should return to the portal landing page.
|
|
105
|
+
*
|
|
106
|
+
* Accepts either a static string (shorthand) or a function that receives
|
|
107
|
+
* the current app key and URL for dynamic path construction.
|
|
108
|
+
*
|
|
109
|
+
* @param urlOrFn - A static path (e.g. `'/'`) or a function returning one.
|
|
110
|
+
* @returns The configurator instance for chaining.
|
|
111
|
+
*/
|
|
112
|
+
setNullContextUrl(urlOrFn) {
|
|
113
|
+
this.#config.nullContextUrl = typeof urlOrFn === 'string' ? () => urlOrFn : urlOrFn;
|
|
114
|
+
return this;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Set a side-effect hook called after each successful navigation.
|
|
118
|
+
* @param fn - The callback function to invoke after navigation.
|
|
119
|
+
* @returns The configurator instance for chaining.
|
|
120
|
+
*/
|
|
121
|
+
setOnTransition(fn) {
|
|
122
|
+
this.#config.onTransition = fn;
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Set the options passed to `navigation.navigate()` during URL updates.
|
|
127
|
+
*
|
|
128
|
+
* @default { replace: true }
|
|
129
|
+
* @param options - The navigation options to use for URL updates.
|
|
130
|
+
* @returns The configurator instance for chaining.
|
|
131
|
+
* @see INavigationProvider.navigate
|
|
132
|
+
* @see ContextNavigationConfig.navigationOptions
|
|
133
|
+
*/
|
|
134
|
+
setNavigationOptions(options) {
|
|
135
|
+
this.#config.navigationOptions = options;
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Set the source factory that drives the reconciler's observable stream.
|
|
140
|
+
*
|
|
141
|
+
* @default createAppFirstSource()
|
|
142
|
+
* @param factory - The source factory to use for the reconciler.
|
|
143
|
+
* @returns The configurator instance for chaining.
|
|
144
|
+
* @see ReconcilerSourceFactory
|
|
145
|
+
* @see appFirstSource
|
|
146
|
+
* @see contextFirstSource
|
|
147
|
+
*/
|
|
148
|
+
setSourceFactory(factory) {
|
|
149
|
+
this.#config.sourceFactory = factory;
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Builds the resolved plugin configuration from the registered options.
|
|
154
|
+
*
|
|
155
|
+
* @returns The resolved configuration used by the context-navigation plugin.
|
|
156
|
+
*/
|
|
157
|
+
createConfig() {
|
|
158
|
+
const origin = this.#config.origin ?? resolveDefaultOrigin();
|
|
159
|
+
// If user registered adapters, use those exclusively.
|
|
160
|
+
// Otherwise fall back to built-in defaults (custom → query → path).
|
|
161
|
+
const adapters = this.#adapters.length > 0
|
|
162
|
+
? [...this.#adapters]
|
|
163
|
+
: [createCustomAdapter(), createQueryAdapter(), createPathAdapter()];
|
|
164
|
+
return {
|
|
165
|
+
portalName: this.#config.portalName ?? 'Portal',
|
|
166
|
+
origin,
|
|
167
|
+
enableUrlGuard: this.#config.enableUrlGuard ?? true,
|
|
168
|
+
debug: this.#config.debug ?? false,
|
|
169
|
+
nullContextUrl: this.#config.nullContextUrl,
|
|
170
|
+
onTransition: this.#config.onTransition,
|
|
171
|
+
navigationOptions: this.#config.navigationOptions ?? { replace: true },
|
|
172
|
+
sourceFactory: this.#config.sourceFactory ?? appFirstSource(),
|
|
173
|
+
resolveInitialContext: this.#config.resolveInitialContext ?? resolveContextFromUrl(adapters, origin),
|
|
174
|
+
adapters,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=ContextNavigationConfigurator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContextNavigationConfigurator.js","sourceRoot":"","sources":["../../src/ContextNavigationConfigurator.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iDAAiD,CAAC;AACtF,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D;;;;;;;;GAQG;AACH,MAAM,oBAAoB,GAAG,GAAW,EAAE,CACxC,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;AAE9E;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,6BAA6B;IAC/B,OAAO,GAAqC,EAAE,CAAC;IAC/C,SAAS,GAAoC,EAAE,CAAC;IAEzD;;;;;;;;;;;;;;OAcG;IACH,eAAe,CAAC,OAAsC;QACpD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;OAMG;IACH,aAAa,CAAC,IAAY;QACxB,IAAI,CAAC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;OAMG;IACH,SAAS,CAAC,MAAc;QACtB,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;OAMG;IACH,WAAW,CAAC,OAAgB;QAC1B,IAAI,CAAC,OAAO,CAAC,cAAc,GAAG,OAAO,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;OAMG;IACH,QAAQ,CAAC,OAAgB;QACvB,IAAI,CAAC,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,iBAAiB,CACf,OAAyE;QAEzE,IAAI,CAAC,OAAO,CAAC,cAAc,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QACpF,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,eAAe,CAAC,EAAsD;QACpE,IAAI,CAAC,OAAO,CAAC,YAAY,GAAG,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;OAQG;IACH,oBAAoB,CAAC,OAA+C;QAClE,IAAI,CAAC,OAAO,CAAC,iBAAiB,GAAG,OAAO,CAAC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;;OASG;IACH,gBAAgB,CAAC,OAAgC;QAC/C,IAAI,CAAC,OAAO,CAAC,aAAa,GAAG,OAAO,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,YAAY;QACV,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,oBAAoB,EAAE,CAAC;QAE7D,sDAAsD;QACtD,oEAAoE;QACpE,MAAM,QAAQ,GACZ,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;YACvB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;YACrB,CAAC,CAAC,CAAC,mBAAmB,EAAE,EAAE,kBAAkB,EAAE,EAAE,iBAAiB,EAAE,CAAC,CAAC;QAEzE,OAAO;YACL,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,QAAQ;YAC/C,MAAM;YACN,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,IAAI,IAAI;YACnD,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,KAAK;YAClC,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc;YAC3C,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY;YACvC,iBAAiB,EAAE,IAAI,CAAC,OAAO,CAAC,iBAAiB,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;YACtE,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,cAAc,EAAE;YAC7D,qBAAqB,EACnB,IAAI,CAAC,OAAO,CAAC,qBAAqB,IAAI,qBAAqB,CAAC,QAAQ,EAAE,MAAM,CAAC;YAC/E,QAAQ;SACT,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { BehaviorSubject, Subject } from 'rxjs';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { activeAppNavigationEvents } from '../operators/active-app-navigation-events';
|
|
4
|
+
describe('activeAppNavigationEvents$', () => {
|
|
5
|
+
it('emits when app, instance, and navigation state are all present', () => {
|
|
6
|
+
// Test double — only the context property is accessed by the operator under test
|
|
7
|
+
const appModules = { context: {} };
|
|
8
|
+
const navigationState$ = new Subject();
|
|
9
|
+
const currentApp$ = new BehaviorSubject({
|
|
10
|
+
appKey: 'my-app',
|
|
11
|
+
instance$: new BehaviorSubject(appModules),
|
|
12
|
+
manifest$: new BehaviorSubject(null),
|
|
13
|
+
});
|
|
14
|
+
// Test double — only current$ is read by the operator under test
|
|
15
|
+
const app = { current$: currentApp$ };
|
|
16
|
+
// Test double — only state$ is read by the operator under test
|
|
17
|
+
const navigation = {
|
|
18
|
+
state$: navigationState$,
|
|
19
|
+
};
|
|
20
|
+
const emissions = [];
|
|
21
|
+
const sub = activeAppNavigationEvents(app, navigation).subscribe((val) => emissions.push(val));
|
|
22
|
+
navigationState$.next({});
|
|
23
|
+
expect(emissions).toHaveLength(1);
|
|
24
|
+
expect(emissions[0]).toEqual({ appKey: 'my-app', appModules, routingStrategy: undefined });
|
|
25
|
+
sub.unsubscribe();
|
|
26
|
+
});
|
|
27
|
+
it('does not emit when currentApp is null', () => {
|
|
28
|
+
const navigationState$ = new Subject();
|
|
29
|
+
const currentApp$ = new BehaviorSubject(null);
|
|
30
|
+
// Test double — only current$ is read by the operator under test
|
|
31
|
+
const app = { current$: currentApp$ };
|
|
32
|
+
// Test double — only state$ is read by the operator under test
|
|
33
|
+
const navigation = {
|
|
34
|
+
state$: navigationState$,
|
|
35
|
+
};
|
|
36
|
+
const emissions = [];
|
|
37
|
+
const sub = activeAppNavigationEvents(app, navigation).subscribe((val) => emissions.push(val));
|
|
38
|
+
navigationState$.next({});
|
|
39
|
+
expect(emissions).toHaveLength(0);
|
|
40
|
+
sub.unsubscribe();
|
|
41
|
+
});
|
|
42
|
+
it('does not emit when app instance is null', () => {
|
|
43
|
+
const navigationState$ = new Subject();
|
|
44
|
+
const currentApp$ = new BehaviorSubject({
|
|
45
|
+
appKey: 'my-app',
|
|
46
|
+
instance$: new BehaviorSubject(null),
|
|
47
|
+
manifest$: new BehaviorSubject(null),
|
|
48
|
+
});
|
|
49
|
+
// Test double — only current$ is read by the operator under test
|
|
50
|
+
const app = { current$: currentApp$ };
|
|
51
|
+
// Test double — only state$ is read by the operator under test
|
|
52
|
+
const navigation = {
|
|
53
|
+
state$: navigationState$,
|
|
54
|
+
};
|
|
55
|
+
const emissions = [];
|
|
56
|
+
const sub = activeAppNavigationEvents(app, navigation).subscribe((val) => emissions.push(val));
|
|
57
|
+
navigationState$.next({});
|
|
58
|
+
expect(emissions).toHaveLength(0);
|
|
59
|
+
sub.unsubscribe();
|
|
60
|
+
});
|
|
61
|
+
it('re-emits on each navigation state change', () => {
|
|
62
|
+
// Test double — only the context property is accessed by the operator under test
|
|
63
|
+
const appModules = { context: {} };
|
|
64
|
+
const navigationState$ = new Subject();
|
|
65
|
+
const currentApp$ = new BehaviorSubject({
|
|
66
|
+
appKey: 'my-app',
|
|
67
|
+
instance$: new BehaviorSubject(appModules),
|
|
68
|
+
manifest$: new BehaviorSubject(null),
|
|
69
|
+
});
|
|
70
|
+
// Test double — only current$ is read by the operator under test
|
|
71
|
+
const app = { current$: currentApp$ };
|
|
72
|
+
// Test double — only state$ is read by the operator under test
|
|
73
|
+
const navigation = {
|
|
74
|
+
state$: navigationState$,
|
|
75
|
+
};
|
|
76
|
+
const emissions = [];
|
|
77
|
+
const sub = activeAppNavigationEvents(app, navigation).subscribe((val) => emissions.push(val));
|
|
78
|
+
navigationState$.next({});
|
|
79
|
+
navigationState$.next({});
|
|
80
|
+
navigationState$.next({});
|
|
81
|
+
expect(emissions).toHaveLength(3);
|
|
82
|
+
sub.unsubscribe();
|
|
83
|
+
});
|
|
84
|
+
it('switches to new app when current app changes', () => {
|
|
85
|
+
// Test double — only the context property is accessed by the operator under test
|
|
86
|
+
const appModulesA = { context: { name: 'A' } };
|
|
87
|
+
// Test double — only the context property is accessed by the operator under test
|
|
88
|
+
const appModulesB = { context: { name: 'B' } };
|
|
89
|
+
const navigationState$ = new Subject();
|
|
90
|
+
const currentApp$ = new BehaviorSubject({
|
|
91
|
+
appKey: 'app-a',
|
|
92
|
+
instance$: new BehaviorSubject(appModulesA),
|
|
93
|
+
manifest$: new BehaviorSubject(null),
|
|
94
|
+
});
|
|
95
|
+
// Test double — only current$ is read by the operator under test
|
|
96
|
+
const app = { current$: currentApp$ };
|
|
97
|
+
// Test double — only state$ is read by the operator under test
|
|
98
|
+
const navigation = {
|
|
99
|
+
state$: navigationState$,
|
|
100
|
+
};
|
|
101
|
+
const emissions = [];
|
|
102
|
+
const sub = activeAppNavigationEvents(app, navigation).subscribe((val) => emissions.push(val));
|
|
103
|
+
navigationState$.next({});
|
|
104
|
+
expect(emissions[0]?.appKey).toBe('app-a');
|
|
105
|
+
currentApp$.next({
|
|
106
|
+
appKey: 'app-b',
|
|
107
|
+
instance$: new BehaviorSubject(appModulesB),
|
|
108
|
+
manifest$: new BehaviorSubject(null),
|
|
109
|
+
});
|
|
110
|
+
navigationState$.next({});
|
|
111
|
+
expect(emissions[1]?.appKey).toBe('app-b');
|
|
112
|
+
sub.unsubscribe();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
//# sourceMappingURL=active-app-navigation-events.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"active-app-navigation-events.test.js","sourceRoot":"","sources":["../../../src/__tests__/active-app-navigation-events.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { applyNavigation, } from '../apply-navigation';
|
|
3
|
+
function makeContext(id) {
|
|
4
|
+
// Test double — only id and title are read by the code under test
|
|
5
|
+
return { id, title: `Context ${id}` };
|
|
6
|
+
}
|
|
7
|
+
function makeAdapter(overrides = {}) {
|
|
8
|
+
return {
|
|
9
|
+
id: 'test-adapter',
|
|
10
|
+
canHandle: () => true,
|
|
11
|
+
encode: ({ context, currentURL }) => {
|
|
12
|
+
// Guard — adapter cannot encode without a context item
|
|
13
|
+
if (!context)
|
|
14
|
+
return null;
|
|
15
|
+
return new URL(`/apps/my-app/${context.id}${currentURL.search}`, currentURL.origin);
|
|
16
|
+
},
|
|
17
|
+
decode: (url) => {
|
|
18
|
+
const match = url.pathname.match(/\/apps\/my-app\/([^/]+)/);
|
|
19
|
+
return match?.[1] ?? null;
|
|
20
|
+
},
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function makeDeps(overrides = {}) {
|
|
25
|
+
// Test doubles — only the methods exercised by applyNavigation are provided
|
|
26
|
+
return {
|
|
27
|
+
event: {
|
|
28
|
+
dispatchEvent: vi.fn(async () => ({ canceled: false })),
|
|
29
|
+
},
|
|
30
|
+
navigation: { navigate: vi.fn() },
|
|
31
|
+
config: {
|
|
32
|
+
portalName: 'Test',
|
|
33
|
+
origin: 'https://example.com',
|
|
34
|
+
adapters: [],
|
|
35
|
+
enableUrlGuard: false,
|
|
36
|
+
debug: false,
|
|
37
|
+
sourceFactory: () => ({ subscribe: vi.fn() }),
|
|
38
|
+
navigationOptions: { replace: true },
|
|
39
|
+
},
|
|
40
|
+
eventSource: { name: 'contextNavigation', version: '0.0.0' },
|
|
41
|
+
ownNavTokens: new Set(),
|
|
42
|
+
log: vi.fn(),
|
|
43
|
+
...overrides,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
describe('applyNavigation', () => {
|
|
47
|
+
const currentURL = new URL('/apps/my-app', 'https://example.com');
|
|
48
|
+
// Test double — only the context property is accessed by the code under test
|
|
49
|
+
const appModules = { context: {} };
|
|
50
|
+
let deps;
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
deps = makeDeps();
|
|
53
|
+
});
|
|
54
|
+
it('dispatches encode-returned-null skip when adapter returns null', async () => {
|
|
55
|
+
const adapter = makeAdapter({ encode: () => null });
|
|
56
|
+
const payload = {
|
|
57
|
+
appKey: 'my-app',
|
|
58
|
+
appModules,
|
|
59
|
+
adapter,
|
|
60
|
+
context: makeContext('ctx-1'),
|
|
61
|
+
currentURL,
|
|
62
|
+
};
|
|
63
|
+
await applyNavigation(payload, deps);
|
|
64
|
+
expect(deps.event.dispatchEvent).toHaveBeenCalledWith('onContextNavigationSkipped', expect.objectContaining({ detail: { appKey: 'my-app', reason: 'encode-returned-null' } }));
|
|
65
|
+
expect(deps.navigation.navigate).not.toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
it('dispatches url-matches skip when target equals current URL', async () => {
|
|
68
|
+
const adapter = makeAdapter({
|
|
69
|
+
encode: ({ currentURL: url }) => new URL(url.pathname, url.origin),
|
|
70
|
+
});
|
|
71
|
+
const payload = {
|
|
72
|
+
appKey: 'my-app',
|
|
73
|
+
appModules,
|
|
74
|
+
adapter,
|
|
75
|
+
context: makeContext('ctx-1'),
|
|
76
|
+
currentURL,
|
|
77
|
+
};
|
|
78
|
+
await applyNavigation(payload, deps);
|
|
79
|
+
expect(deps.event.dispatchEvent).toHaveBeenCalledWith('onContextNavigationSkipped', expect.objectContaining({ detail: { appKey: 'my-app', reason: 'url-matches' } }));
|
|
80
|
+
});
|
|
81
|
+
it('navigates and records own-nav token on success', async () => {
|
|
82
|
+
const adapter = makeAdapter();
|
|
83
|
+
const payload = {
|
|
84
|
+
appKey: 'my-app',
|
|
85
|
+
appModules,
|
|
86
|
+
adapter,
|
|
87
|
+
context: makeContext('ctx-1'),
|
|
88
|
+
currentURL,
|
|
89
|
+
};
|
|
90
|
+
await applyNavigation(payload, deps);
|
|
91
|
+
expect(deps.navigation.navigate).toHaveBeenCalledWith(expect.objectContaining({ pathname: '/apps/my-app/ctx-1' }), { replace: true });
|
|
92
|
+
expect(deps.ownNavTokens.has('/apps/my-app/ctx-1')).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
it('dispatches canceled skip when navigate event is canceled', async () => {
|
|
95
|
+
// Test double — simulates dispatch returning canceled=true only for the navigate event
|
|
96
|
+
deps = makeDeps({
|
|
97
|
+
event: {
|
|
98
|
+
dispatchEvent: vi.fn(async (name) => ({
|
|
99
|
+
canceled: name === 'onContextNavigationNavigate',
|
|
100
|
+
})),
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
const adapter = makeAdapter();
|
|
104
|
+
const payload = {
|
|
105
|
+
appKey: 'my-app',
|
|
106
|
+
appModules,
|
|
107
|
+
adapter,
|
|
108
|
+
context: makeContext('ctx-1'),
|
|
109
|
+
currentURL,
|
|
110
|
+
};
|
|
111
|
+
await applyNavigation(payload, deps);
|
|
112
|
+
expect(deps.event.dispatchEvent).toHaveBeenCalledWith('onContextNavigationSkipped', expect.objectContaining({ detail: { appKey: 'my-app', reason: 'canceled' } }));
|
|
113
|
+
expect(deps.navigation.navigate).not.toHaveBeenCalled();
|
|
114
|
+
});
|
|
115
|
+
it('logs and does not throw when dispatch rejects', async () => {
|
|
116
|
+
// Test double — simulates dispatch throwing to verify the .catch() path
|
|
117
|
+
deps = makeDeps({
|
|
118
|
+
event: {
|
|
119
|
+
dispatchEvent: vi.fn(async () => {
|
|
120
|
+
throw new Error('dispatch failure');
|
|
121
|
+
}),
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
const adapter = makeAdapter();
|
|
125
|
+
const payload = {
|
|
126
|
+
appKey: 'my-app',
|
|
127
|
+
appModules,
|
|
128
|
+
adapter,
|
|
129
|
+
context: makeContext('ctx-1'),
|
|
130
|
+
currentURL,
|
|
131
|
+
};
|
|
132
|
+
// Should not reject — the .catch() handles it.
|
|
133
|
+
await expect(applyNavigation(payload, deps)).resolves.toBeUndefined();
|
|
134
|
+
expect(deps.log).toHaveBeenCalledWith(expect.stringContaining('dispatch failure'));
|
|
135
|
+
});
|
|
136
|
+
it('calls onTransition callback after navigation', async () => {
|
|
137
|
+
const onTransition = vi.fn();
|
|
138
|
+
deps = makeDeps();
|
|
139
|
+
deps.config = { ...deps.config, onTransition };
|
|
140
|
+
const adapter = makeAdapter();
|
|
141
|
+
const payload = {
|
|
142
|
+
appKey: 'my-app',
|
|
143
|
+
appModules,
|
|
144
|
+
adapter,
|
|
145
|
+
context: makeContext('ctx-1'),
|
|
146
|
+
currentURL,
|
|
147
|
+
};
|
|
148
|
+
await applyNavigation(payload, deps);
|
|
149
|
+
expect(onTransition).toHaveBeenCalledWith(expect.objectContaining({ appKey: 'my-app', adapterId: 'test-adapter' }));
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
//# sourceMappingURL=apply-navigation.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-navigation.test.js","sourceRoot":"","sources":["../../../src/__tests__/apply-navigation.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAK9D,OAAO,EACL,eAAe,GAIhB,MAAM,qBAAqB,CAAC;AAG7B,SAAS,WAAW,CAAC,EAAU;IAC7B,kEAAkE;IAClE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAA4B,CAAC;AAClE,CAAC;AAED,SAAS,WAAW,CAAC,SAAS,GAAsC,EAAE;IACpE,OAAO;QACL,EAAE,EAAE,cAAc;QAClB,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI;QACrB,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE;YAClC,uDAAuD;YACvD,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC;YAC1B,OAAO,IAAI,GAAG,CAAC,gBAAgB,OAAO,CAAC,EAAE,GAAG,UAAU,CAAC,MAAM,EAAE,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;QACtF,CAAC;QACD,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;YACd,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;YAC5D,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;QAC5B,CAAC;QACD,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,SAAS,GAAiC,EAAE;IAC5D,4EAA4E;IAC5E,OAAO;QACL,KAAK,EAAE;YACL,aAAa,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;SACb;QAC5C,UAAU,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAkD;QACjF,MAAM,EAAE;YACN,UAAU,EAAE,MAAM;YAClB,MAAM,EAAE,qBAAqB;YAC7B,QAAQ,EAAE,EAAE;YACZ,cAAc,EAAE,KAAK;YACrB,KAAK,EAAE,KAAK;YACZ,aAAa,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAU;YACtD,iBAAiB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;SACrC;QACD,WAAW,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,OAAO,EAAE;QAC5D,YAAY,EAAE,IAAI,GAAG,EAAyB;QAC9C,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE;QACZ,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,cAAc,EAAE,qBAAqB,CAAC,CAAC;IAClE,6EAA6E;IAC7E,MAAM,UAAU,GAAG,EAAE,OAAO,EAAE,EAAE,EAAoD,CAAC;IACrF,IAAI,IAAyB,CAAC;IAE9B,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,GAAG,QAAQ,EAAE,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,MAAM,OAAO,GAAG,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;QACpD,MAAM,OAAO,GAA2B;YACtC,MAAM,EAAE,QAAQ;YAChB,UAAU;YACV,OAAO;YACP,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC;YAC7B,UAAU;SACX,CAAC;QAEF,MAAM,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAErC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,oBAAoB,CACnD,4BAA4B,EAC5B,MAAM,CAAC,gBAAgB,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,sBAAsB,EAAE,EAAE,CAAC,CAC1F,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,OAAO,GAAG,WAAW,CAAC;YAC1B,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC;SACnE,CAAC,CAAC;QACH,MAAM,OAAO,GAA2B;YACtC,MAAM,EAAE,QAAQ;YAChB,UAAU;YACV,OAAO;YACP,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC;YAC7B,UAAU;SACX,CAAC;QAEF,MAAM,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAErC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,oBAAoB,CACnD,4BAA4B,EAC5B,MAAM,CAAC,gBAAgB,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,CAAC,CACjF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;QAC9B,MAAM,OAAO,GAA2B;YACtC,MAAM,EAAE,QAAQ;YAChB,UAAU;YACV,OAAO;YACP,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC;YAC7B,UAAU;SACX,CAAC;QAEF,MAAM,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAErC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CACnD,MAAM,CAAC,gBAAgB,CAAC,EAAE,QAAQ,EAAE,oBAAoB,EAAE,CAAC,EAC3D,EAAE,OAAO,EAAE,IAAI,EAAE,CAClB,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;QACxE,uFAAuF;QACvF,IAAI,GAAG,QAAQ,CAAC;YACd,KAAK,EAAE;gBACL,aAAa,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;oBACpC,QAAQ,EAAE,IAAI,KAAK,6BAA6B;iBACjD,CAAC,CAAC;aACuC;SAC7C,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;QAC9B,MAAM,OAAO,GAA2B;YACtC,MAAM,EAAE,QAAQ;YAChB,UAAU;YACV,OAAO;YACP,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC;YAC7B,UAAU;SACX,CAAC;QAEF,MAAM,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAErC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,oBAAoB,CACnD,4BAA4B,EAC5B,MAAM,CAAC,gBAAgB,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,CAC9E,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,wEAAwE;QACxE,IAAI,GAAG,QAAQ,CAAC;YACd,KAAK,EAAE;gBACL,aAAa,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE;oBAC9B,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;gBACtC,CAAC,CAAC;aACwC;SAC7C,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;QAC9B,MAAM,OAAO,GAA2B;YACtC,MAAM,EAAE,QAAQ;YAChB,UAAU;YACV,OAAO;YACP,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC;YAC7B,UAAU;SACX,CAAC;QAEF,+CAA+C;QAC/C,MAAM,MAAM,CAAC,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;QACtE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,MAAM,YAAY,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,IAAI,GAAG,QAAQ,EAAE,CAAC;QAClB,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC;QAC/C,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;QAC9B,MAAM,OAAO,GAA2B;YACtC,MAAM,EAAE,QAAQ;YAChB,UAAU;YACV,OAAO;YACP,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC;YAC7B,UAAU;SACX,CAAC;QAEF,MAAM,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAErC,MAAM,CAAC,YAAY,CAAC,CAAC,oBAAoB,CACvC,MAAM,CAAC,gBAAgB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CACzE,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { EMPTY } from 'rxjs';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
const builderInstances = [];
|
|
4
|
+
const teardownFns = [];
|
|
5
|
+
const createConfigSpy = vi.fn(() => ({
|
|
6
|
+
debug: false,
|
|
7
|
+
origin: 'https://example.com',
|
|
8
|
+
portalName: 'Portal',
|
|
9
|
+
enableUrlGuard: true,
|
|
10
|
+
navigationOptions: { replace: true },
|
|
11
|
+
adapters: [],
|
|
12
|
+
sourceFactory: vi.fn(() => EMPTY),
|
|
13
|
+
resolveInitialContext: vi.fn(async () => undefined),
|
|
14
|
+
}));
|
|
15
|
+
/** Mock configurator that captures calls for assertion in tests. */
|
|
16
|
+
class MockContextNavigationConfigurator {
|
|
17
|
+
/**
|
|
18
|
+
* Records that debug mode was configured; returns `this` for chaining.
|
|
19
|
+
*
|
|
20
|
+
* @param _enabled - Whether debug mode should be enabled.
|
|
21
|
+
* @returns The configurator instance for chaining.
|
|
22
|
+
*/
|
|
23
|
+
setDebug(_enabled) {
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
/** Delegates to the shared spy so tests can assert how config was created. */
|
|
27
|
+
createConfig() {
|
|
28
|
+
return createConfigSpy();
|
|
29
|
+
}
|
|
30
|
+
/** Registers this instance in the outer `builderInstances` array for test inspection. */
|
|
31
|
+
constructor() {
|
|
32
|
+
builderInstances.push(this);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
vi.mock('../configurator', () => ({
|
|
36
|
+
ContextNavigationConfigurator: MockContextNavigationConfigurator,
|
|
37
|
+
}));
|
|
38
|
+
vi.mock('../plugin', () => ({
|
|
39
|
+
createContextNavigationPlugin: vi.fn().mockImplementation(() => {
|
|
40
|
+
const teardown = vi.fn();
|
|
41
|
+
teardownFns.push(teardown);
|
|
42
|
+
return teardown;
|
|
43
|
+
}),
|
|
44
|
+
}));
|
|
45
|
+
describe('enableContextNavigation', () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
builderInstances.length = 0;
|
|
48
|
+
createConfigSpy.mockClear();
|
|
49
|
+
});
|
|
50
|
+
it('registers a plugin instead of module config', async () => {
|
|
51
|
+
const registerPlugin = vi.fn();
|
|
52
|
+
const addConfig = vi.fn();
|
|
53
|
+
const { enableContextNavigation } = await import('../enable-context-navigation');
|
|
54
|
+
// Test double — only registerPlugin and addConfig are called by enableContextNavigation
|
|
55
|
+
enableContextNavigation({
|
|
56
|
+
registerPlugin,
|
|
57
|
+
addConfig,
|
|
58
|
+
});
|
|
59
|
+
expect(registerPlugin).toHaveBeenCalledOnce();
|
|
60
|
+
expect(registerPlugin.mock.calls[0]?.[0]?.name).toBe('contextNavigation');
|
|
61
|
+
expect(addConfig).not.toHaveBeenCalled();
|
|
62
|
+
});
|
|
63
|
+
it('creates and disposes the runtime plugin from the registered plugin callback', async () => {
|
|
64
|
+
const registerPlugin = vi.fn();
|
|
65
|
+
const builder = vi.fn((configurator) => {
|
|
66
|
+
configurator.setDebug(true);
|
|
67
|
+
});
|
|
68
|
+
const ref = { parent: true };
|
|
69
|
+
const modules = {
|
|
70
|
+
app: { id: 'app', current$: EMPTY },
|
|
71
|
+
navigation: {
|
|
72
|
+
id: 'navigation',
|
|
73
|
+
path: { pathname: '/', search: '' },
|
|
74
|
+
state$: EMPTY,
|
|
75
|
+
},
|
|
76
|
+
context: { id: 'context', currentContext$: EMPTY },
|
|
77
|
+
event: { id: 'event', dispatchEvent: vi.fn() },
|
|
78
|
+
};
|
|
79
|
+
const { enableContextNavigation } = await import('../enable-context-navigation');
|
|
80
|
+
// Test double — only registerPlugin is called by enableContextNavigation
|
|
81
|
+
enableContextNavigation({
|
|
82
|
+
registerPlugin,
|
|
83
|
+
}, builder);
|
|
84
|
+
const plugin = registerPlugin.mock.calls[0]?.[0];
|
|
85
|
+
const registration = await plugin({ modules, ref });
|
|
86
|
+
expect(builder).toHaveBeenCalledOnce();
|
|
87
|
+
expect(registration).toEqual(expect.any(Function));
|
|
88
|
+
expect(() => registration()).not.toThrow();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=enable.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enable.test.js","sourceRoot":"","sources":["../../../src/__tests__/enable.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { consumeOwnNavToken, isInAppScope } from '../guard-handlers';
|
|
3
|
+
import { stripQueryParams } from '../helpers';
|
|
4
|
+
describe('isInAppScope', () => {
|
|
5
|
+
it('returns true when pathname equals the app base path', () => {
|
|
6
|
+
const url = new URL('/apps/my-app', 'https://example.com');
|
|
7
|
+
expect(isInAppScope(url, 'my-app')).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
it('returns true when pathname starts with the app base path followed by /', () => {
|
|
10
|
+
const url = new URL('/apps/my-app/some/context', 'https://example.com');
|
|
11
|
+
expect(isInAppScope(url, 'my-app')).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
it('returns false when pathname belongs to a different app', () => {
|
|
14
|
+
const url = new URL('/apps/other-app/page', 'https://example.com');
|
|
15
|
+
expect(isInAppScope(url, 'my-app')).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
it('returns false when pathname is a prefix match but not a path segment boundary', () => {
|
|
18
|
+
// "/apps/my-appx" should NOT match "/apps/my-app"
|
|
19
|
+
const url = new URL('/apps/my-appx', 'https://example.com');
|
|
20
|
+
expect(isInAppScope(url, 'my-app')).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
it('returns false for unrelated paths', () => {
|
|
23
|
+
const url = new URL('/settings/profile', 'https://example.com');
|
|
24
|
+
expect(isInAppScope(url, 'my-app')).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe('consumeOwnNavToken', () => {
|
|
28
|
+
it('returns true and removes the token when URL matches a stored token', () => {
|
|
29
|
+
const tokens = new Set(['/apps/my-app/ctx-1']);
|
|
30
|
+
const url = new URL('/apps/my-app/ctx-1', 'https://example.com');
|
|
31
|
+
expect(consumeOwnNavToken(url, tokens)).toBe(true);
|
|
32
|
+
expect(tokens.has('/apps/my-app/ctx-1')).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
it('returns false when URL does not match any stored token', () => {
|
|
35
|
+
const tokens = new Set(['/apps/my-app/ctx-1']);
|
|
36
|
+
const url = new URL('/apps/my-app/ctx-2', 'https://example.com');
|
|
37
|
+
expect(consumeOwnNavToken(url, tokens)).toBe(false);
|
|
38
|
+
// The existing token should remain untouched.
|
|
39
|
+
expect(tokens.has('/apps/my-app/ctx-1')).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
it('returns false when the token set is empty', () => {
|
|
42
|
+
const tokens = new Set();
|
|
43
|
+
const url = new URL('/apps/my-app/ctx-1', 'https://example.com');
|
|
44
|
+
expect(consumeOwnNavToken(url, tokens)).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
it('consumes only the matching token when multiple tokens exist', () => {
|
|
47
|
+
const tokens = new Set(['/apps/my-app/ctx-1', '/apps/my-app/ctx-2']);
|
|
48
|
+
const url = new URL('/apps/my-app/ctx-1', 'https://example.com');
|
|
49
|
+
expect(consumeOwnNavToken(url, tokens)).toBe(true);
|
|
50
|
+
expect(tokens.has('/apps/my-app/ctx-1')).toBe(false);
|
|
51
|
+
expect(tokens.has('/apps/my-app/ctx-2')).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
it('normalizes trailing slashes when comparing', () => {
|
|
54
|
+
const tokens = new Set(['/apps/my-app/ctx-1']);
|
|
55
|
+
const url = new URL('/apps/my-app/ctx-1/', 'https://example.com');
|
|
56
|
+
expect(consumeOwnNavToken(url, tokens)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('stripQueryParams', () => {
|
|
60
|
+
it('removes all query params', () => {
|
|
61
|
+
const url = new URL('/apps/my-app?tab=settings&filter=active', 'https://example.com');
|
|
62
|
+
stripQueryParams(url);
|
|
63
|
+
expect(url.search).toBe('');
|
|
64
|
+
});
|
|
65
|
+
it('removes framework params too', () => {
|
|
66
|
+
const url = new URL('/apps/my-app?$contextId=abc-123&tab=settings', 'https://example.com');
|
|
67
|
+
stripQueryParams(url);
|
|
68
|
+
expect(url.search).toBe('');
|
|
69
|
+
});
|
|
70
|
+
it('handles URLs with no query params', () => {
|
|
71
|
+
const url = new URL('/apps/my-app', 'https://example.com');
|
|
72
|
+
stripQueryParams(url);
|
|
73
|
+
expect(url.search).toBe('');
|
|
74
|
+
});
|
|
75
|
+
it('preserves pathname', () => {
|
|
76
|
+
const url = new URL('/apps/my-app/route?tab=settings', 'https://example.com');
|
|
77
|
+
stripQueryParams(url);
|
|
78
|
+
expect(url.pathname).toBe('/apps/my-app/route');
|
|
79
|
+
expect(url.search).toBe('');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
//# sourceMappingURL=guard-handlers.test.js.map
|