@jskit-ai/shell-web 0.1.151 → 0.1.153
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 +6 -0
- package/fixtures/adaptive-shell/src/main.js +5 -1
- package/package.json +573 -2
- package/src/client/components/ShellLayout.vue +22 -5
- package/src/test/adaptiveShellSmoke.js +58 -12
- package/test/adaptiveShell.browser.test.js +17 -102
- package/test/bootstrapClaimContract.test.js +4 -2
- package/test/linkItemScaffoldContract.test.js +5 -3
- package/test/playwrightContract.test.js +40 -4
- package/test/settingsPlacementContract.test.js +16 -10
- package/package.descriptor.mjs +0 -530
|
@@ -177,22 +177,56 @@ async function expectContentAwareDrawerFit(drawer, expect) {
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
async function
|
|
180
|
+
async function readNavigationIconCenters(drawer) {
|
|
181
|
+
const icons = drawer.locator("a.shell-menu-link-item[href] .v-icon");
|
|
182
|
+
await icons.first().waitFor({ state: "visible" });
|
|
183
|
+
const centers = [];
|
|
184
|
+
const iconCount = await icons.count();
|
|
185
|
+
for (let index = 0; index < iconCount; index += 1) {
|
|
186
|
+
const box = await icons.nth(index).boundingBox();
|
|
187
|
+
if (!box) {
|
|
188
|
+
throw new Error("Shell navigation exposed an unmeasurable icon.");
|
|
189
|
+
}
|
|
190
|
+
centers.push(box.x + box.width / 2);
|
|
191
|
+
}
|
|
192
|
+
return centers;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isRequestedOrDescendantLocation(actualUrl, targetUrl) {
|
|
196
|
+
const normalizePathname = (value) => {
|
|
197
|
+
const pathname = String(value || "/");
|
|
198
|
+
return pathname === "/" ? pathname : pathname.replace(/\/+$/u, "");
|
|
199
|
+
};
|
|
200
|
+
const actualPathname = normalizePathname(actualUrl.pathname);
|
|
201
|
+
const targetPathname = normalizePathname(targetUrl.pathname);
|
|
202
|
+
const pathMatches = actualPathname === targetPathname || (
|
|
203
|
+
targetPathname !== "/" && actualPathname.startsWith(`${targetPathname}/`)
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
return pathMatches && actualUrl.search === targetUrl.search;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function expectCentredRailNavigation(page, drawer, expect, expandedIconCenters) {
|
|
181
210
|
const links = drawer.locator("a.shell-menu-link-item[href]");
|
|
182
211
|
await expect(links.first()).toBeVisible();
|
|
183
212
|
const firstLink = links.first();
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
const [drawerBox, linkBox, iconBox] = geometry;
|
|
213
|
+
const geometry = await Promise.all([drawer.boundingBox(), firstLink.boundingBox()]);
|
|
214
|
+
const [drawerBox, linkBox] = geometry;
|
|
187
215
|
|
|
188
216
|
expect(drawerBox).not.toBeNull();
|
|
189
217
|
expect(linkBox).not.toBeNull();
|
|
190
|
-
expect(iconBox).not.toBeNull();
|
|
191
218
|
expect(linkBox.width).toBeGreaterThanOrEqual(48);
|
|
192
219
|
expect(linkBox.height).toBeGreaterThanOrEqual(48);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
220
|
+
const railIconCenters = await readNavigationIconCenters(drawer);
|
|
221
|
+
expect(railIconCenters.length).toBe(expandedIconCenters.length);
|
|
222
|
+
for (let index = 0; index < railIconCenters.length; index += 1) {
|
|
223
|
+
expect(Math.abs(
|
|
224
|
+
railIconCenters[index] - (drawerBox.x + drawerBox.width / 2)
|
|
225
|
+
)).toBeLessThanOrEqual(1);
|
|
226
|
+
expect(Math.abs(
|
|
227
|
+
railIconCenters[index] - expandedIconCenters[index]
|
|
228
|
+
)).toBeLessThanOrEqual(1);
|
|
229
|
+
}
|
|
196
230
|
|
|
197
231
|
const currentUrl = new URL(page.url());
|
|
198
232
|
const linkCount = await links.count();
|
|
@@ -216,8 +250,14 @@ async function expectCentredRailNavigation(page, drawer, expect) {
|
|
|
216
250
|
await navigationLink.locator(".v-icon").first().click();
|
|
217
251
|
await expect.poll(() => {
|
|
218
252
|
const actual = new URL(page.url());
|
|
219
|
-
|
|
220
|
-
|
|
253
|
+
const leftOriginalLocation = (
|
|
254
|
+
actual.pathname !== currentUrl.pathname ||
|
|
255
|
+
actual.search !== currentUrl.search
|
|
256
|
+
);
|
|
257
|
+
return leftOriginalLocation && isRequestedOrDescendantLocation(actual, targetUrl);
|
|
258
|
+
}, {
|
|
259
|
+
message: "Shell navigation did not reach the requested route or its canonical descendant."
|
|
260
|
+
}).toBe(true);
|
|
221
261
|
}
|
|
222
262
|
|
|
223
263
|
async function openCompactDrawer(page, expect) {
|
|
@@ -288,13 +328,14 @@ async function runAdaptiveShellSmokeCase({
|
|
|
288
328
|
await expect(drawer).toHaveAttribute("data-presentation", "drawer");
|
|
289
329
|
await expectDrawerWidth(drawer, expect, await readConfiguredWidth(drawer, "data-drawer-width"));
|
|
290
330
|
await expectContentAwareDrawerFit(drawer, expect);
|
|
331
|
+
const expandedIconCenters = await readNavigationIconCenters(drawer);
|
|
291
332
|
await page.keyboard.press("Escape");
|
|
292
333
|
await expect(drawer).toHaveAttribute("data-presentation", "drawer");
|
|
293
334
|
|
|
294
335
|
await toggle.click();
|
|
295
336
|
await expect(drawer).toHaveAttribute("data-presentation", "rail");
|
|
296
337
|
await expectDrawerWidth(drawer, expect, await readConfiguredWidth(drawer, "data-rail-width"));
|
|
297
|
-
await expectCentredRailNavigation(page, drawer, expect);
|
|
338
|
+
await expectCentredRailNavigation(page, drawer, expect, expandedIconCenters);
|
|
298
339
|
await expectNoHorizontalOverflow(page, expect);
|
|
299
340
|
}
|
|
300
341
|
}
|
|
@@ -318,4 +359,9 @@ function runAdaptiveShellSmoke({
|
|
|
318
359
|
});
|
|
319
360
|
}
|
|
320
361
|
|
|
321
|
-
export {
|
|
362
|
+
export {
|
|
363
|
+
DEFAULT_VIEWPORTS,
|
|
364
|
+
isRequestedOrDescendantLocation,
|
|
365
|
+
runAdaptiveShellSmoke,
|
|
366
|
+
runAdaptiveShellSmokeCase
|
|
367
|
+
};
|
|
@@ -1,110 +1,23 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import net from "node:net";
|
|
4
2
|
import path from "node:path";
|
|
5
3
|
import process from "node:process";
|
|
6
4
|
import test from "node:test";
|
|
7
5
|
import { fileURLToPath } from "node:url";
|
|
8
6
|
import { chromium, expect } from "@playwright/test";
|
|
7
|
+
import {
|
|
8
|
+
createChromiumLaunchOptions,
|
|
9
|
+
startViteFixture,
|
|
10
|
+
stopProcess
|
|
11
|
+
} from "../../../tooling/testUtils/browserFixture.mjs";
|
|
9
12
|
import {
|
|
10
13
|
DEFAULT_VIEWPORTS,
|
|
11
14
|
runAdaptiveShellSmokeCase
|
|
12
15
|
} from "../src/test/adaptiveShellSmoke.js";
|
|
13
16
|
|
|
14
17
|
const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
const REPOSITORY_ROOT = path.resolve(TEST_DIRECTORY, "../../..");
|
|
16
18
|
const FIXTURE_ROOT = path.resolve(TEST_DIRECTORY, "../fixtures/adaptive-shell");
|
|
17
|
-
const VITE_CLI = path.join(REPOSITORY_ROOT, "node_modules", "vite", "bin", "vite.js");
|
|
18
19
|
const RUN_BROWSER_TEST = process.env.JSKIT_SHELL_WEB_BROWSER_INTEGRATION === "1";
|
|
19
20
|
|
|
20
|
-
function startVite(port) {
|
|
21
|
-
const child = spawn(process.execPath, [
|
|
22
|
-
VITE_CLI,
|
|
23
|
-
"--config",
|
|
24
|
-
path.join(FIXTURE_ROOT, "vite.config.mjs"),
|
|
25
|
-
"--port",
|
|
26
|
-
String(port),
|
|
27
|
-
"--clearScreen",
|
|
28
|
-
"false"
|
|
29
|
-
], {
|
|
30
|
-
cwd: FIXTURE_ROOT,
|
|
31
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
32
|
-
env: {
|
|
33
|
-
...process.env,
|
|
34
|
-
NO_COLOR: "1",
|
|
35
|
-
FORCE_COLOR: "0"
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
let output = "";
|
|
39
|
-
child.stdout.on("data", collectOutput);
|
|
40
|
-
child.stderr.on("data", collectOutput);
|
|
41
|
-
|
|
42
|
-
function collectOutput(chunk) {
|
|
43
|
-
output += chunk.toString("utf8");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function waitUntilReady() {
|
|
47
|
-
await new Promise((resolve, reject) => {
|
|
48
|
-
const startedAt = Date.now();
|
|
49
|
-
const timer = setInterval(checkReadiness, 50);
|
|
50
|
-
|
|
51
|
-
function checkReadiness() {
|
|
52
|
-
if (new RegExp(`http://127\\.0\\.0\\.1:${port}/`, "u").test(output)) {
|
|
53
|
-
clearInterval(timer);
|
|
54
|
-
resolve();
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
if (child.exitCode !== null) {
|
|
58
|
-
clearInterval(timer);
|
|
59
|
-
reject(new Error(`Vite exited before becoming ready.\n${output}`));
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (Date.now() - startedAt >= 30_000) {
|
|
63
|
-
clearInterval(timer);
|
|
64
|
-
reject(new Error(`Timed out waiting for Vite.\n${output}`));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return Object.freeze({ child, waitUntilReady });
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function stopVite(runtime) {
|
|
74
|
-
const child = runtime?.child;
|
|
75
|
-
if (!child || child.exitCode !== null || child.signalCode !== null) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
await new Promise((resolve) => {
|
|
79
|
-
const forceTimer = setTimeout(forceStop, 5_000);
|
|
80
|
-
child.once("exit", finishStop);
|
|
81
|
-
child.kill("SIGTERM");
|
|
82
|
-
|
|
83
|
-
function forceStop() {
|
|
84
|
-
child.kill("SIGKILL");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function finishStop() {
|
|
88
|
-
clearTimeout(forceTimer);
|
|
89
|
-
resolve();
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function reservePort() {
|
|
95
|
-
const server = net.createServer();
|
|
96
|
-
await new Promise((resolve, reject) => {
|
|
97
|
-
server.once("error", reject);
|
|
98
|
-
server.listen(0, "127.0.0.1", resolve);
|
|
99
|
-
});
|
|
100
|
-
const address = server.address();
|
|
101
|
-
assert.ok(address && typeof address === "object");
|
|
102
|
-
await new Promise((resolve, reject) => {
|
|
103
|
-
server.close((error) => error ? reject(error) : resolve());
|
|
104
|
-
});
|
|
105
|
-
return address.port;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
21
|
function parseRgb(color) {
|
|
109
22
|
const channels = String(color || "").match(/[\d.]+/gu)?.map(Number) || [];
|
|
110
23
|
return {
|
|
@@ -166,6 +79,11 @@ async function assertTooltipContrast(page, linkName, interaction) {
|
|
|
166
79
|
async function assertDrawerFit(page) {
|
|
167
80
|
const drawer = page.getByTestId("jskit-shell-drawer");
|
|
168
81
|
await expect(drawer).toHaveAttribute("data-presentation", "drawer");
|
|
82
|
+
assert.equal(
|
|
83
|
+
await drawer.locator(".v-list-subheader").count(),
|
|
84
|
+
0,
|
|
85
|
+
"The drawer must not repeat the surface label from the top app bar."
|
|
86
|
+
);
|
|
169
87
|
const configuredSpacing = Number(await drawer.getAttribute("data-navigation-item-spacing"));
|
|
170
88
|
assert.ok(Number.isFinite(configuredSpacing));
|
|
171
89
|
await expect.poll(async () => {
|
|
@@ -255,25 +173,22 @@ test("shell-web adaptive navigation passes package-owned browser contracts", {
|
|
|
255
173
|
: "set JSKIT_SHELL_WEB_BROWSER_INTEGRATION=1 to run shell-web browser integration",
|
|
256
174
|
timeout: 180_000
|
|
257
175
|
}, async () => {
|
|
258
|
-
const
|
|
259
|
-
const vite = startVite(port);
|
|
176
|
+
const vite = await startViteFixture({ fixtureRoot: FIXTURE_ROOT });
|
|
260
177
|
let browser = null;
|
|
261
178
|
|
|
262
179
|
try {
|
|
263
|
-
await
|
|
264
|
-
const executablePath = String(process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || "").trim();
|
|
265
|
-
browser = await chromium.launch({
|
|
266
|
-
headless: true,
|
|
267
|
-
...(executablePath ? { executablePath } : {})
|
|
268
|
-
});
|
|
180
|
+
browser = await chromium.launch(createChromiumLaunchOptions());
|
|
269
181
|
const context = await browser.newContext({
|
|
270
|
-
baseURL:
|
|
182
|
+
baseURL: vite.baseURL,
|
|
271
183
|
locale: "en-US"
|
|
272
184
|
});
|
|
273
185
|
|
|
274
186
|
for (const viewport of DEFAULT_VIEWPORTS) {
|
|
275
187
|
const page = await context.newPage();
|
|
276
188
|
await runAdaptiveShellSmokeCase({ page, expect, viewport });
|
|
189
|
+
if (viewport.name !== "compact") {
|
|
190
|
+
await expect(page).toHaveURL(/\/home\/settings\/general$/u);
|
|
191
|
+
}
|
|
277
192
|
await page.close();
|
|
278
193
|
}
|
|
279
194
|
|
|
@@ -319,6 +234,6 @@ test("shell-web adaptive navigation passes package-owned browser contracts", {
|
|
|
319
234
|
await context.close();
|
|
320
235
|
} finally {
|
|
321
236
|
await browser?.close();
|
|
322
|
-
await
|
|
237
|
+
await stopProcess(vite);
|
|
323
238
|
}
|
|
324
239
|
});
|
|
@@ -3,14 +3,16 @@ import path from "node:path";
|
|
|
3
3
|
import test from "node:test";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import
|
|
6
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
7
|
+
|
|
8
|
+
const packageMetadata = packageJson.jskit;
|
|
7
9
|
|
|
8
10
|
const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
9
11
|
const PACKAGE_DIR = path.resolve(TEST_DIRECTORY, "..");
|
|
10
12
|
const CREATE_APP_TEMPLATE_DIR = path.resolve(PACKAGE_DIR, "..", "..", "tooling", "create-app", "templates", "minimal-shell");
|
|
11
13
|
|
|
12
14
|
function findFileMutation(id) {
|
|
13
|
-
const files =
|
|
15
|
+
const files = packageMetadata?.mutations?.files;
|
|
14
16
|
return Array.isArray(files)
|
|
15
17
|
? files.find((entry) => String(entry?.id || "").trim() === id) || null
|
|
16
18
|
: null;
|
|
@@ -3,7 +3,9 @@ import path from "node:path";
|
|
|
3
3
|
import test from "node:test";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import
|
|
6
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
7
|
+
|
|
8
|
+
const packageMetadata = packageJson.jskit;
|
|
7
9
|
import {
|
|
8
10
|
LOCAL_LINK_ITEM_COMPONENT_DEFINITIONS,
|
|
9
11
|
findLocalLinkItemDefinition,
|
|
@@ -14,14 +16,14 @@ const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
|
14
16
|
const PACKAGE_DIR = path.resolve(TEST_DIRECTORY, "..");
|
|
15
17
|
|
|
16
18
|
function findFileMutation(id) {
|
|
17
|
-
const files =
|
|
19
|
+
const files = packageMetadata?.mutations?.files;
|
|
18
20
|
return Array.isArray(files)
|
|
19
21
|
? files.find((entry) => String(entry?.id || "").trim() === id) || null
|
|
20
22
|
: null;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
function findSourceMutation(id) {
|
|
24
|
-
const sourceMutations =
|
|
26
|
+
const sourceMutations = packageMetadata?.mutations?.source;
|
|
25
27
|
return Array.isArray(sourceMutations)
|
|
26
28
|
? sourceMutations.find((entry) => String(entry?.id || "").trim() === id) || null
|
|
27
29
|
: null;
|
|
@@ -3,16 +3,19 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import {
|
|
7
|
+
isRequestedOrDescendantLocation
|
|
8
|
+
} from "../src/test/adaptiveShellSmoke.js";
|
|
6
9
|
|
|
7
10
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
11
|
|
|
9
12
|
test("shell-web owns the exact Playwright dependency used by its generated smoke", async () => {
|
|
10
|
-
const
|
|
13
|
+
const packageJson = JSON.parse(await readFile(path.join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
11
14
|
const rootPackage = JSON.parse(await readFile(path.resolve(PACKAGE_ROOT, "../../package.json"), "utf8"));
|
|
12
15
|
|
|
13
|
-
assert.equal(
|
|
16
|
+
assert.equal(packageJson.jskit.mutations.dependencies.dev["@playwright/test"], "1.61.1");
|
|
14
17
|
assert.equal(
|
|
15
|
-
|
|
18
|
+
packageJson.jskit.mutations.dependencies.dev["@playwright/test"],
|
|
16
19
|
rootPackage.devDependencies["@playwright/test"]
|
|
17
20
|
);
|
|
18
21
|
});
|
|
@@ -25,6 +28,39 @@ test("adaptive shell smoke navigates through Playwright baseURL", async () => {
|
|
|
25
28
|
assert.doesNotMatch(source, /http:\/\/127\.0\.0\.1/u);
|
|
26
29
|
});
|
|
27
30
|
|
|
31
|
+
test("adaptive shell navigation accepts canonical descendants without accepting prefix siblings", () => {
|
|
32
|
+
const requested = new URL("https://example.test/home/settings?tab=profile");
|
|
33
|
+
|
|
34
|
+
assert.equal(
|
|
35
|
+
isRequestedOrDescendantLocation(
|
|
36
|
+
new URL("https://example.test/home/settings?tab=profile"),
|
|
37
|
+
requested
|
|
38
|
+
),
|
|
39
|
+
true
|
|
40
|
+
);
|
|
41
|
+
assert.equal(
|
|
42
|
+
isRequestedOrDescendantLocation(
|
|
43
|
+
new URL("https://example.test/home/settings/general?tab=profile"),
|
|
44
|
+
requested
|
|
45
|
+
),
|
|
46
|
+
true
|
|
47
|
+
);
|
|
48
|
+
assert.equal(
|
|
49
|
+
isRequestedOrDescendantLocation(
|
|
50
|
+
new URL("https://example.test/home/settings-other?tab=profile"),
|
|
51
|
+
requested
|
|
52
|
+
),
|
|
53
|
+
false
|
|
54
|
+
);
|
|
55
|
+
assert.equal(
|
|
56
|
+
isRequestedOrDescendantLocation(
|
|
57
|
+
new URL("https://example.test/home/settings/general?tab=security"),
|
|
58
|
+
requested
|
|
59
|
+
),
|
|
60
|
+
false
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
28
64
|
test("adaptive shell smoke follows rendered layout state and waits for drawer transitions", async () => {
|
|
29
65
|
const source = await readFile(path.join(PACKAGE_ROOT, "src/test/adaptiveShellSmoke.js"), "utf8");
|
|
30
66
|
|
|
@@ -35,7 +71,7 @@ test("adaptive shell smoke follows rendered layout state and waits for drawer tr
|
|
|
35
71
|
assert.match(source, /toBeFocused/u);
|
|
36
72
|
assert.match(source, /v-navigation-drawer__scrim/u);
|
|
37
73
|
assert.match(source, /page\.keyboard\.press\("Escape"\)[\s\S]*data-presentation", "drawer"/u);
|
|
38
|
-
assert.match(source, /
|
|
74
|
+
assert.match(source, /centers\.push\(box\.x \+ box\.width \/ 2\)/u);
|
|
39
75
|
assert.match(source, /fit\.endGap - configuredSpacing/u);
|
|
40
76
|
assert.match(source, /fit\.iconLabelGaps/u);
|
|
41
77
|
assert.doesNotMatch(source, /viewport\.name === "compact"/u);
|
|
@@ -4,14 +4,16 @@ import test from "node:test";
|
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { assertGeneratedUiSourceContract } from "@jskit-ai/kernel/shared/support/generatedUiContract";
|
|
7
|
-
import
|
|
7
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
8
|
+
|
|
9
|
+
const packageMetadata = packageJson.jskit;
|
|
8
10
|
import { resolveShellRouteTransitionKey } from "../src/client/support/routeTransitionKey.js";
|
|
9
11
|
|
|
10
12
|
const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
11
13
|
const PACKAGE_DIR = path.resolve(TEST_DIRECTORY, "..");
|
|
12
14
|
|
|
13
15
|
function readOutlets(target = "") {
|
|
14
|
-
const outlets =
|
|
16
|
+
const outlets = packageMetadata?.metadata?.ui?.placements?.outlets;
|
|
15
17
|
const normalizedTarget = String(target || "").trim();
|
|
16
18
|
return Array.isArray(outlets)
|
|
17
19
|
? outlets.filter((entry) => String(entry?.target || "").trim() === normalizedTarget)
|
|
@@ -19,7 +21,7 @@ function readOutlets(target = "") {
|
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
function readContributions(target = "") {
|
|
22
|
-
const contributions =
|
|
24
|
+
const contributions = packageMetadata?.metadata?.ui?.placements?.contributions;
|
|
23
25
|
const normalizedTarget = String(target || "").trim();
|
|
24
26
|
return Array.isArray(contributions)
|
|
25
27
|
? contributions.filter((entry) => String(entry?.target || "").trim() === normalizedTarget)
|
|
@@ -27,7 +29,7 @@ function readContributions(target = "") {
|
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
function readTopology(id = "", owner = "") {
|
|
30
|
-
const placements =
|
|
32
|
+
const placements = packageMetadata?.metadata?.ui?.placements?.topology?.placements;
|
|
31
33
|
const normalizedId = String(id || "").trim();
|
|
32
34
|
const normalizedOwner = String(owner || "").trim();
|
|
33
35
|
return Array.isArray(placements)
|
|
@@ -46,12 +48,12 @@ function readPackageImportSpecifiers(source = "") {
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
function readClientContainerTokens() {
|
|
49
|
-
const tokens =
|
|
51
|
+
const tokens = packageMetadata?.metadata?.apiSummary?.containerTokens?.client;
|
|
50
52
|
return Array.isArray(tokens) ? tokens : [];
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
function findFileMutation(id) {
|
|
54
|
-
const files =
|
|
56
|
+
const files = packageMetadata?.mutations?.files;
|
|
55
57
|
return Array.isArray(files)
|
|
56
58
|
? files.find((entry) => String(entry?.id || "").trim() === id) || null
|
|
57
59
|
: null;
|
|
@@ -110,6 +112,10 @@ test("shell-web shell layout registers navigation at the app layout level", asyn
|
|
|
110
112
|
assert.match(source, /measureDrawerContentWidth/);
|
|
111
113
|
assert.match(source, /DEFAULT_SHELL_NAVIGATION_ITEM_SPACING/);
|
|
112
114
|
assert.match(source, /:prepend-gap="resolvedNavigationItemSpacing"/);
|
|
115
|
+
assert.match(source, /--shell-navigation-drawer-inset:\s*12px/);
|
|
116
|
+
assert.match(source, /--shell-navigation-rail-width/);
|
|
117
|
+
assert.match(source, /padding-inline-start:\s*var\(--shell-navigation-drawer-inset\)/);
|
|
118
|
+
assert.doesNotMatch(source, /<v-list-subheader/);
|
|
113
119
|
assert.doesNotMatch(source, /:width="248"/);
|
|
114
120
|
assert.doesNotMatch(source, /:rail-width="80"/);
|
|
115
121
|
assert.match(source, /:model-value="drawerPresentation\.visible"/);
|
|
@@ -347,7 +353,7 @@ test("shell-web placement topology seeds global actions as a semantic shell plac
|
|
|
347
353
|
assert.match(source, /outlet: "shell-layout:supporting-side-panel"/);
|
|
348
354
|
});
|
|
349
355
|
|
|
350
|
-
test("shell-web
|
|
356
|
+
test("shell-web packageMetadata pre-optimizes package subpaths reached only through dynamic base-shell modules", async () => {
|
|
351
357
|
const [providerSource, placementSource, placementTopologySource, errorSource] = await Promise.all([
|
|
352
358
|
readFile(path.join(PACKAGE_DIR, "src", "client", "providers", "ShellWebClientProvider.js"), "utf8"),
|
|
353
359
|
readFile(path.join(PACKAGE_DIR, "templates", "src", "placement.js"), "utf8"),
|
|
@@ -374,16 +380,16 @@ test("shell-web descriptor pre-optimizes package subpaths reached only through d
|
|
|
374
380
|
"@jskit-ai/shell-web/client/error"
|
|
375
381
|
]);
|
|
376
382
|
|
|
377
|
-
assert.deepEqual(
|
|
383
|
+
assert.deepEqual(packageMetadata?.metadata?.client?.optimizeDeps?.include, [
|
|
378
384
|
"@jskit-ai/shell-web/client/placement",
|
|
379
385
|
"@jskit-ai/shell-web/client/error"
|
|
380
386
|
]);
|
|
381
|
-
assert.deepEqual(
|
|
387
|
+
assert.deepEqual(packageMetadata?.metadata?.client?.optimizeDeps?.exclude, [
|
|
382
388
|
"@jskit-ai/shell-web/client"
|
|
383
389
|
]);
|
|
384
390
|
});
|
|
385
391
|
|
|
386
|
-
test("shell-web
|
|
392
|
+
test("shell-web packageMetadata metadata advertises adaptive shell outlets, default links, and installs the scaffold page", () => {
|
|
387
393
|
const homePageMutationIds = [
|
|
388
394
|
"shell-web-page-home-wrapper",
|
|
389
395
|
"shell-web-page-home",
|