@jskit-ai/shell-web 0.1.64 → 0.1.66
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/package.descriptor.mjs +200 -16
- package/package.json +8 -7
- package/src/client/components/ShellErrorHost.vue +88 -15
- package/src/client/components/ShellLayout.vue +551 -50
- package/src/client/components/ShellOutlet.vue +34 -4
- package/src/client/components/ShellOutletMenuWidget.vue +1 -8
- package/src/client/components/ShellRouteTransition.vue +480 -0
- package/src/client/components/ShellTabLinkItem.vue +22 -6
- package/src/client/composables/useShellLayoutState.js +12 -1
- package/src/client/error/normalize.js +17 -0
- package/src/client/error/policy.js +25 -11
- package/src/client/error/runtime.js +2 -0
- package/src/client/index.js +1 -0
- package/src/client/placement/index.js +5 -0
- package/src/client/placement/runtime.js +149 -16
- package/src/client/placement/validators.js +36 -8
- package/src/client/providers/ShellWebClientProvider.js +189 -24
- package/src/client/stores/useShellLayoutStore.js +21 -1
- package/src/test/adaptiveShellSmoke.js +121 -0
- package/templates/expected-existing/src/pages/home/index.vue +40 -10
- package/templates/src/components/ShellLayout.vue +10 -90
- package/templates/src/components/menus/TabLinkItem.vue +4 -0
- package/templates/src/error.js +7 -1
- package/templates/src/pages/home/index.vue +64 -23
- package/templates/src/pages/home/settings/general/index.vue +12 -9
- package/templates/src/pages/home/settings.vue +68 -24
- package/templates/src/placement.js +7 -6
- package/templates/src/placementTopology.js +149 -0
- package/templates/tests/e2e/adaptive-shell.spec.ts +4 -0
- package/test/errorRuntime.test.js +42 -0
- package/test/linkItemScaffoldContract.test.js +9 -2
- package/test/outletMenuWidgetContract.test.js +2 -2
- package/test/placementRegistry.test.js +3 -3
- package/test/placementRuntime.test.js +144 -14
- package/test/provider.test.js +97 -5
- package/test/settingsPlacementContract.test.js +234 -20
- package/test/useShellLayoutState.test.js +19 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const placements = [];
|
|
2
|
+
|
|
3
|
+
function addPlacementTopology(value = {}) {
|
|
4
|
+
placements.push(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export { addPlacementTopology };
|
|
8
|
+
export default { placements };
|
|
9
|
+
|
|
10
|
+
const menuLinkRenderers = Object.freeze({
|
|
11
|
+
link: "local.main.ui.surface-aware-menu-link-item"
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const bottomNavLinkRenderers = Object.freeze({
|
|
15
|
+
link: "local.main.ui.tab-link-item"
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
addPlacementTopology({
|
|
19
|
+
id: "shell.primary-nav",
|
|
20
|
+
description: "Primary top-level navigation for the current surface.",
|
|
21
|
+
surfaces: ["*"],
|
|
22
|
+
default: true,
|
|
23
|
+
variants: {
|
|
24
|
+
compact: {
|
|
25
|
+
outlet: "shell-layout:primary-bottom-nav",
|
|
26
|
+
renderers: bottomNavLinkRenderers
|
|
27
|
+
},
|
|
28
|
+
medium: {
|
|
29
|
+
outlet: "shell-layout:primary-menu",
|
|
30
|
+
renderers: menuLinkRenderers
|
|
31
|
+
},
|
|
32
|
+
expanded: {
|
|
33
|
+
outlet: "shell-layout:primary-menu",
|
|
34
|
+
renderers: menuLinkRenderers
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
addPlacementTopology({
|
|
40
|
+
id: "shell.secondary-nav",
|
|
41
|
+
description: "Secondary navigation for the current surface.",
|
|
42
|
+
surfaces: ["*"],
|
|
43
|
+
variants: {
|
|
44
|
+
compact: {
|
|
45
|
+
outlet: "shell-layout:secondary-menu",
|
|
46
|
+
renderers: menuLinkRenderers
|
|
47
|
+
},
|
|
48
|
+
medium: {
|
|
49
|
+
outlet: "shell-layout:secondary-menu",
|
|
50
|
+
renderers: menuLinkRenderers
|
|
51
|
+
},
|
|
52
|
+
expanded: {
|
|
53
|
+
outlet: "shell-layout:secondary-menu",
|
|
54
|
+
renderers: menuLinkRenderers
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
addPlacementTopology({
|
|
60
|
+
id: "shell.identity",
|
|
61
|
+
description: "Current surface identity and switcher controls.",
|
|
62
|
+
surfaces: ["*"],
|
|
63
|
+
variants: {
|
|
64
|
+
compact: {
|
|
65
|
+
outlet: "shell-layout:top-left"
|
|
66
|
+
},
|
|
67
|
+
medium: {
|
|
68
|
+
outlet: "shell-layout:top-left"
|
|
69
|
+
},
|
|
70
|
+
expanded: {
|
|
71
|
+
outlet: "shell-layout:top-left"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
addPlacementTopology({
|
|
77
|
+
id: "shell.status",
|
|
78
|
+
description: "Surface status, connection, and utility indicators.",
|
|
79
|
+
surfaces: ["*"],
|
|
80
|
+
variants: {
|
|
81
|
+
compact: {
|
|
82
|
+
outlet: "shell-layout:top-right"
|
|
83
|
+
},
|
|
84
|
+
medium: {
|
|
85
|
+
outlet: "shell-layout:top-right"
|
|
86
|
+
},
|
|
87
|
+
expanded: {
|
|
88
|
+
outlet: "shell-layout:top-right"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
addPlacementTopology({
|
|
94
|
+
id: "shell.global-actions",
|
|
95
|
+
description: "Global surface actions that should stay outside primary navigation.",
|
|
96
|
+
surfaces: ["*"],
|
|
97
|
+
variants: {
|
|
98
|
+
compact: {
|
|
99
|
+
outlet: "shell-layout:top-right",
|
|
100
|
+
renderers: menuLinkRenderers
|
|
101
|
+
},
|
|
102
|
+
medium: {
|
|
103
|
+
outlet: "shell-layout:top-right",
|
|
104
|
+
renderers: menuLinkRenderers
|
|
105
|
+
},
|
|
106
|
+
expanded: {
|
|
107
|
+
outlet: "shell-layout:top-right",
|
|
108
|
+
renderers: menuLinkRenderers
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
addPlacementTopology({
|
|
114
|
+
id: "page.supporting-content",
|
|
115
|
+
description: "Supporting page content that opens as a bottom sheet on compact layouts and a side panel on wider layouts.",
|
|
116
|
+
surfaces: ["*"],
|
|
117
|
+
variants: {
|
|
118
|
+
compact: {
|
|
119
|
+
outlet: "shell-layout:supporting-bottom-sheet"
|
|
120
|
+
},
|
|
121
|
+
medium: {
|
|
122
|
+
outlet: "shell-layout:supporting-side-panel"
|
|
123
|
+
},
|
|
124
|
+
expanded: {
|
|
125
|
+
outlet: "shell-layout:supporting-side-panel"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
addPlacementTopology({
|
|
131
|
+
id: "page.section-nav",
|
|
132
|
+
owner: "home-settings",
|
|
133
|
+
description: "Navigation between child pages in the home settings section.",
|
|
134
|
+
surfaces: ["home"],
|
|
135
|
+
variants: {
|
|
136
|
+
compact: {
|
|
137
|
+
outlet: "home-settings:primary-menu",
|
|
138
|
+
renderers: menuLinkRenderers
|
|
139
|
+
},
|
|
140
|
+
medium: {
|
|
141
|
+
outlet: "home-settings:primary-menu",
|
|
142
|
+
renderers: menuLinkRenderers
|
|
143
|
+
},
|
|
144
|
+
expanded: {
|
|
145
|
+
outlet: "home-settings:primary-menu",
|
|
146
|
+
renderers: menuLinkRenderers
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
+
import { createDefaultErrorPolicy } from "../src/client/error/policy.js";
|
|
3
4
|
import { createErrorRuntime } from "../src/client/error/runtime.js";
|
|
4
5
|
|
|
5
6
|
function createPresenter(id, {
|
|
@@ -25,6 +26,47 @@ function createPresenter(id, {
|
|
|
25
26
|
});
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
test("default error policy maps intent to presentation instead of status alone", () => {
|
|
30
|
+
const policy = createDefaultErrorPolicy();
|
|
31
|
+
|
|
32
|
+
assert.equal(policy({ intent: "resource-load", message: "Load failed" }).channel, "silent");
|
|
33
|
+
assert.equal(policy({ intent: "action-feedback", message: "Save failed" }).channel, "snackbar");
|
|
34
|
+
assert.equal(policy({ intent: "app-recoverable", message: "Offline" }).channel, "banner");
|
|
35
|
+
assert.equal(policy({ intent: "blocking", message: "Fatal" }).channel, "dialog");
|
|
36
|
+
assert.equal(policy({ blocking: true, message: "Fatal" }).channel, "dialog");
|
|
37
|
+
assert.equal(policy({ status: 500, message: "Server failed" }).channel, "snackbar");
|
|
38
|
+
assert.equal(
|
|
39
|
+
policy({ intent: "resource-load", channel: "banner", message: "Load failed" }).channel,
|
|
40
|
+
"banner"
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("error runtime treats resource load errors as silent by default", () => {
|
|
45
|
+
const calls = [];
|
|
46
|
+
const runtime = createErrorRuntime({
|
|
47
|
+
presenters: [
|
|
48
|
+
createPresenter("module.presenter", { calls })
|
|
49
|
+
],
|
|
50
|
+
moduleDefaultPresenterId: "module.presenter"
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const result = runtime.report({
|
|
54
|
+
kind: "resource-load",
|
|
55
|
+
message: "Unable to load records.",
|
|
56
|
+
status: 500,
|
|
57
|
+
action: {
|
|
58
|
+
label: "Retry",
|
|
59
|
+
handler() {}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
assert.equal(result.skipped, true);
|
|
64
|
+
assert.equal(result.reason, "silent");
|
|
65
|
+
assert.equal(result.event.intent, "resource-load");
|
|
66
|
+
assert.equal(result.decision.channel, "silent");
|
|
67
|
+
assert.equal(calls.length, 0);
|
|
68
|
+
});
|
|
69
|
+
|
|
28
70
|
test("error runtime prefers policy presenter over app and module defaults", () => {
|
|
29
71
|
const calls = [];
|
|
30
72
|
const runtime = createErrorRuntime({
|
|
@@ -32,8 +32,13 @@ test("shell-web exports generic link-item components for app-owned shell wrapper
|
|
|
32
32
|
assert.match(clientIndexSource, /ShellMenuLinkItem/);
|
|
33
33
|
assert.match(clientIndexSource, /ShellSurfaceAwareMenuLinkItem/);
|
|
34
34
|
assert.match(clientIndexSource, /ShellTabLinkItem/);
|
|
35
|
+
assert.match(clientIndexSource, /ShellRouteTransition/);
|
|
35
36
|
|
|
36
37
|
const packageJson = JSON.parse(await readFile(path.join(PACKAGE_DIR, "package.json"), "utf8"));
|
|
38
|
+
assert.equal(
|
|
39
|
+
packageJson?.exports?.["./client/components/ShellRouteTransition"],
|
|
40
|
+
"./src/client/components/ShellRouteTransition.vue"
|
|
41
|
+
);
|
|
37
42
|
assert.equal(
|
|
38
43
|
packageJson?.exports?.["./client/components/ShellMenuLinkItem"],
|
|
39
44
|
"./src/client/components/ShellMenuLinkItem.vue"
|
|
@@ -150,8 +155,10 @@ test("shell-web generic link items support the expected shared route and icon be
|
|
|
150
155
|
assert.match(shellSurfaceAwareSource, /:exact="props\.exact"/);
|
|
151
156
|
assert.match(shellTabSource, /icon:\s*\{/);
|
|
152
157
|
assert.match(shellTabSource, /resolveMenuLinkIcon/);
|
|
153
|
-
assert.match(shellTabSource, /<v-
|
|
154
|
-
assert.match(shellTabSource,
|
|
158
|
+
assert.match(shellTabSource, /<v-btn/);
|
|
159
|
+
assert.match(shellTabSource, /stacked/);
|
|
160
|
+
assert.match(shellTabSource, /min-height:\s*48px/);
|
|
161
|
+
assert.match(shellTabSource, /<v-icon v-if="resolvedIcon" :icon="resolvedIcon" \/>/);
|
|
155
162
|
});
|
|
156
163
|
|
|
157
164
|
test("shell-web binds the local link-item wrapper tokens into MainClientProvider", () => {
|
|
@@ -14,9 +14,9 @@ test("shell-web outlet menu widget exposes a configurable nested outlet", async
|
|
|
14
14
|
);
|
|
15
15
|
|
|
16
16
|
assert.match(source, /import \{ mdiCogOutline \} from "@mdi\/js";/);
|
|
17
|
-
assert.match(source, /defaultLinkComponentToken: \{/);
|
|
18
17
|
assert.match(source, /:target="props\.target"/);
|
|
19
|
-
assert.
|
|
18
|
+
assert.doesNotMatch(source, /defaultLinkComponentToken/);
|
|
19
|
+
assert.doesNotMatch(source, /default-link-component-token/);
|
|
20
20
|
assert.match(source, /default: mdiCogOutline/);
|
|
21
21
|
assert.doesNotMatch(source, /mdi-[a-z0-9-]+/);
|
|
22
22
|
});
|
|
@@ -7,13 +7,13 @@ test("placement registry stores unique entries and builds immutable array", () =
|
|
|
7
7
|
|
|
8
8
|
const firstAdded = registry.addPlacement({
|
|
9
9
|
id: "example.profile",
|
|
10
|
-
target: "shell
|
|
10
|
+
target: "shell.status",
|
|
11
11
|
surfaces: ["*"],
|
|
12
12
|
componentToken: "example.profile.component"
|
|
13
13
|
});
|
|
14
14
|
const duplicateAdded = registry.addPlacement({
|
|
15
15
|
id: "example.profile",
|
|
16
|
-
target: "shell
|
|
16
|
+
target: "shell.status",
|
|
17
17
|
surfaces: ["*"],
|
|
18
18
|
componentToken: "example.profile.component.duplicate"
|
|
19
19
|
});
|
|
@@ -33,7 +33,7 @@ test("placement registry accepts explicit non-global surface ids", () => {
|
|
|
33
33
|
|
|
34
34
|
const added = registry.addPlacement({
|
|
35
35
|
id: "example.admin",
|
|
36
|
-
target: "shell
|
|
36
|
+
target: "shell.status",
|
|
37
37
|
surfaces: ["admin"],
|
|
38
38
|
componentToken: "example.admin.component"
|
|
39
39
|
});
|
|
@@ -32,6 +32,119 @@ function createPlacementContext() {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function semanticTopologyEntry({
|
|
36
|
+
id = "shell.primary-nav",
|
|
37
|
+
owner = "",
|
|
38
|
+
surfaces = ["*"],
|
|
39
|
+
compact = "shell-layout:primary-menu",
|
|
40
|
+
medium = "shell-layout:primary-menu",
|
|
41
|
+
expanded = "shell-layout:primary-menu",
|
|
42
|
+
compactRenderer = "component.menu",
|
|
43
|
+
mediumRenderer = "component.menu",
|
|
44
|
+
expandedRenderer = "component.menu"
|
|
45
|
+
} = {}) {
|
|
46
|
+
const createVariant = (outlet, renderer) => ({
|
|
47
|
+
outlet,
|
|
48
|
+
renderers: renderer ? { link: renderer } : {}
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
id,
|
|
52
|
+
owner,
|
|
53
|
+
surfaces,
|
|
54
|
+
variants: {
|
|
55
|
+
compact: createVariant(compact, compactRenderer),
|
|
56
|
+
medium: createVariant(medium, mediumRenderer),
|
|
57
|
+
expanded: createVariant(expanded, expandedRenderer)
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
test("web placement runtime resolves semantic targets through topology variants", () => {
|
|
63
|
+
const app = createAppStub({
|
|
64
|
+
tokens: {
|
|
65
|
+
"component.bottom": () => null,
|
|
66
|
+
"component.menu": () => null
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const runtime = createWebPlacementRuntime({ app });
|
|
71
|
+
runtime.replacePlacementTopology([
|
|
72
|
+
semanticTopologyEntry({
|
|
73
|
+
id: "shell.primary-nav",
|
|
74
|
+
compact: "shell-layout:bottom-nav",
|
|
75
|
+
medium: "shell-layout:primary-menu",
|
|
76
|
+
expanded: "shell-layout:primary-menu",
|
|
77
|
+
compactRenderer: "component.bottom",
|
|
78
|
+
mediumRenderer: "component.menu",
|
|
79
|
+
expandedRenderer: "component.menu"
|
|
80
|
+
})
|
|
81
|
+
]);
|
|
82
|
+
runtime.replacePlacements([
|
|
83
|
+
definePlacement({
|
|
84
|
+
id: "test.home",
|
|
85
|
+
target: "shell.primary-nav",
|
|
86
|
+
kind: "link",
|
|
87
|
+
surfaces: ["app"],
|
|
88
|
+
order: 10
|
|
89
|
+
})
|
|
90
|
+
]);
|
|
91
|
+
runtime.setContext(createPlacementContext());
|
|
92
|
+
|
|
93
|
+
const compactEntries = runtime.getPlacements({
|
|
94
|
+
surface: "app",
|
|
95
|
+
target: "shell-layout:bottom-nav",
|
|
96
|
+
layoutClass: "compact"
|
|
97
|
+
});
|
|
98
|
+
assert.deepEqual(compactEntries.map((entry) => entry.id), ["test.home"]);
|
|
99
|
+
assert.equal(compactEntries[0].semanticTarget, "shell.primary-nav");
|
|
100
|
+
assert.equal(compactEntries[0].componentToken, "component.bottom");
|
|
101
|
+
|
|
102
|
+
const mediumEntries = runtime.getPlacements({
|
|
103
|
+
surface: "app",
|
|
104
|
+
target: "shell-layout:primary-menu",
|
|
105
|
+
layoutClass: "medium"
|
|
106
|
+
});
|
|
107
|
+
assert.deepEqual(mediumEntries.map((entry) => entry.id), ["test.home"]);
|
|
108
|
+
assert.equal(mediumEntries[0].componentToken, "component.menu");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("web placement runtime accepts append-only topology objects", () => {
|
|
112
|
+
const app = createAppStub({
|
|
113
|
+
tokens: {
|
|
114
|
+
"component.menu": () => null
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const runtime = createWebPlacementRuntime({ app });
|
|
119
|
+
runtime.replacePlacementTopology({
|
|
120
|
+
placements: [
|
|
121
|
+
semanticTopologyEntry({
|
|
122
|
+
id: "shell.primary-nav",
|
|
123
|
+
compactRenderer: "component.menu",
|
|
124
|
+
mediumRenderer: "component.menu",
|
|
125
|
+
expandedRenderer: "component.menu"
|
|
126
|
+
})
|
|
127
|
+
]
|
|
128
|
+
});
|
|
129
|
+
runtime.replacePlacements([
|
|
130
|
+
definePlacement({
|
|
131
|
+
id: "test.home",
|
|
132
|
+
target: "shell.primary-nav",
|
|
133
|
+
kind: "link",
|
|
134
|
+
surfaces: ["app"],
|
|
135
|
+
order: 10
|
|
136
|
+
})
|
|
137
|
+
]);
|
|
138
|
+
runtime.setContext(createPlacementContext());
|
|
139
|
+
|
|
140
|
+
const entries = runtime.getPlacements({
|
|
141
|
+
surface: "app",
|
|
142
|
+
target: "shell-layout:primary-menu",
|
|
143
|
+
layoutClass: "expanded"
|
|
144
|
+
});
|
|
145
|
+
assert.deepEqual(entries.map((entry) => entry.id), ["test.home"]);
|
|
146
|
+
});
|
|
147
|
+
|
|
35
148
|
test("web placement runtime filters by surface/host/position, resolves component tokens, and sorts by order", () => {
|
|
36
149
|
const app = createAppStub({
|
|
37
150
|
tokens: {
|
|
@@ -48,21 +161,24 @@ test("web placement runtime filters by surface/host/position, resolves component
|
|
|
48
161
|
target: "shell-layout:primary-menu",
|
|
49
162
|
surfaces: ["app"],
|
|
50
163
|
order: 30,
|
|
51
|
-
componentToken: "component.menu"
|
|
164
|
+
componentToken: "component.menu",
|
|
165
|
+
internal: true
|
|
52
166
|
}),
|
|
53
167
|
definePlacement({
|
|
54
168
|
id: "test.profile",
|
|
55
169
|
target: "shell-layout:top-right",
|
|
56
170
|
surfaces: ["*"],
|
|
57
171
|
order: 20,
|
|
58
|
-
componentToken: "component.profile"
|
|
172
|
+
componentToken: "component.profile",
|
|
173
|
+
internal: true
|
|
59
174
|
}),
|
|
60
175
|
definePlacement({
|
|
61
176
|
id: "test.alerts",
|
|
62
177
|
target: "shell-layout:top-right",
|
|
63
178
|
surfaces: ["app"],
|
|
64
179
|
order: 10,
|
|
65
|
-
componentToken: "component.alerts"
|
|
180
|
+
componentToken: "component.alerts",
|
|
181
|
+
internal: true
|
|
66
182
|
})
|
|
67
183
|
]);
|
|
68
184
|
runtime.setContext(createPlacementContext());
|
|
@@ -93,14 +209,16 @@ test("web placement runtime preserves source order when placements share the sam
|
|
|
93
209
|
target: "home-settings:primary-menu",
|
|
94
210
|
surfaces: ["app"],
|
|
95
211
|
order: 155,
|
|
96
|
-
componentToken: "component.beta"
|
|
212
|
+
componentToken: "component.beta",
|
|
213
|
+
internal: true
|
|
97
214
|
}),
|
|
98
215
|
definePlacement({
|
|
99
216
|
id: "test.alpha",
|
|
100
217
|
target: "home-settings:primary-menu",
|
|
101
218
|
surfaces: ["app"],
|
|
102
219
|
order: 155,
|
|
103
|
-
componentToken: "component.alpha"
|
|
220
|
+
componentToken: "component.alpha",
|
|
221
|
+
internal: true
|
|
104
222
|
})
|
|
105
223
|
]);
|
|
106
224
|
runtime.setContext(createPlacementContext());
|
|
@@ -129,6 +247,7 @@ test("web placement runtime applies context contributors and placement when() pr
|
|
|
129
247
|
target: "auth-profile-menu:primary-menu",
|
|
130
248
|
surfaces: ["*"],
|
|
131
249
|
componentToken: "component.guest",
|
|
250
|
+
internal: true,
|
|
132
251
|
when: ({ auth }) => !Boolean(auth?.authenticated)
|
|
133
252
|
}),
|
|
134
253
|
definePlacement({
|
|
@@ -136,6 +255,7 @@ test("web placement runtime applies context contributors and placement when() pr
|
|
|
136
255
|
target: "auth-profile-menu:primary-menu",
|
|
137
256
|
surfaces: ["*"],
|
|
138
257
|
componentToken: "component.authenticated",
|
|
258
|
+
internal: true,
|
|
139
259
|
when: ({ auth }) => Boolean(auth?.authenticated)
|
|
140
260
|
})
|
|
141
261
|
]);
|
|
@@ -165,6 +285,7 @@ test("web placement runtime uses runtime context and local context overrides con
|
|
|
165
285
|
target: "auth-profile-menu:primary-menu",
|
|
166
286
|
surfaces: ["*"],
|
|
167
287
|
componentToken: "component.allowed",
|
|
288
|
+
internal: true,
|
|
168
289
|
when: ({ auth }) => Boolean(auth?.authenticated)
|
|
169
290
|
})
|
|
170
291
|
]);
|
|
@@ -224,13 +345,15 @@ test("web placement runtime rejects duplicate placement ids", () => {
|
|
|
224
345
|
id: "dup.entry",
|
|
225
346
|
target: "shell-layout:top-right",
|
|
226
347
|
surfaces: ["*"],
|
|
227
|
-
componentToken: "component.a"
|
|
348
|
+
componentToken: "component.a",
|
|
349
|
+
internal: true
|
|
228
350
|
}),
|
|
229
351
|
definePlacement({
|
|
230
352
|
id: "dup.entry",
|
|
231
353
|
target: "shell-layout:primary-menu",
|
|
232
354
|
surfaces: ["*"],
|
|
233
|
-
componentToken: "component.b"
|
|
355
|
+
componentToken: "component.b",
|
|
356
|
+
internal: true
|
|
234
357
|
})
|
|
235
358
|
]);
|
|
236
359
|
}, /Duplicate placement id/);
|
|
@@ -271,13 +394,15 @@ test("web placement runtime skips throwing component tokens and logs resolution
|
|
|
271
394
|
id: "bad",
|
|
272
395
|
target: "shell-layout:top-right",
|
|
273
396
|
surfaces: ["*"],
|
|
274
|
-
componentToken: "component.bad"
|
|
397
|
+
componentToken: "component.bad",
|
|
398
|
+
internal: true
|
|
275
399
|
}),
|
|
276
400
|
definePlacement({
|
|
277
401
|
id: "good",
|
|
278
402
|
target: "shell-layout:top-right",
|
|
279
403
|
surfaces: ["*"],
|
|
280
|
-
componentToken: "component.good"
|
|
404
|
+
componentToken: "component.good",
|
|
405
|
+
internal: true
|
|
281
406
|
})
|
|
282
407
|
]);
|
|
283
408
|
|
|
@@ -323,7 +448,8 @@ test("web placement runtime clears failed token cache when placements are replac
|
|
|
323
448
|
id: "toggle",
|
|
324
449
|
target: "shell-layout:top-right",
|
|
325
450
|
surfaces: ["*"],
|
|
326
|
-
componentToken: "component.toggle"
|
|
451
|
+
componentToken: "component.toggle",
|
|
452
|
+
internal: true
|
|
327
453
|
})
|
|
328
454
|
]);
|
|
329
455
|
|
|
@@ -339,7 +465,8 @@ test("web placement runtime clears failed token cache when placements are replac
|
|
|
339
465
|
id: "toggle",
|
|
340
466
|
target: "shell-layout:top-right",
|
|
341
467
|
surfaces: ["*"],
|
|
342
|
-
componentToken: "component.toggle"
|
|
468
|
+
componentToken: "component.toggle",
|
|
469
|
+
internal: true
|
|
343
470
|
})
|
|
344
471
|
]);
|
|
345
472
|
|
|
@@ -362,21 +489,24 @@ test("web placement runtime follows explicit surface targeting without role indi
|
|
|
362
489
|
target: "shell-layout:top-right",
|
|
363
490
|
surfaces: ["*"],
|
|
364
491
|
order: 10,
|
|
365
|
-
componentToken: "component.global"
|
|
492
|
+
componentToken: "component.global",
|
|
493
|
+
internal: true
|
|
366
494
|
}),
|
|
367
495
|
definePlacement({
|
|
368
496
|
id: "app.link",
|
|
369
497
|
target: "shell-layout:top-right",
|
|
370
498
|
surfaces: ["app"],
|
|
371
499
|
order: 20,
|
|
372
|
-
componentToken: "component.app"
|
|
500
|
+
componentToken: "component.app",
|
|
501
|
+
internal: true
|
|
373
502
|
}),
|
|
374
503
|
definePlacement({
|
|
375
504
|
id: "admin.link",
|
|
376
505
|
target: "shell-layout:top-right",
|
|
377
506
|
surfaces: ["admin"],
|
|
378
507
|
order: 30,
|
|
379
|
-
componentToken: "component.admin"
|
|
508
|
+
componentToken: "component.admin",
|
|
509
|
+
internal: true
|
|
380
510
|
})
|
|
381
511
|
]);
|
|
382
512
|
runtime.setContext(createPlacementContext());
|