@jskit-ai/shell-web 0.1.152 → 0.1.154
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/fixtures/adaptive-shell/src/main.js +5 -1
- package/package.json +573 -2
- package/src/test/adaptiveShellSmoke.js +28 -3
- package/test/adaptiveShell.browser.test.js +12 -102
- package/test/bootstrapClaimContract.test.js +4 -2
- package/test/linkItemScaffoldContract.test.js +5 -3
- package/test/playwrightContract.test.js +39 -3
- package/test/settingsPlacementContract.test.js +12 -10
- package/package.descriptor.mjs +0 -530
|
@@ -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 {
|
|
@@ -260,25 +173,22 @@ test("shell-web adaptive navigation passes package-owned browser contracts", {
|
|
|
260
173
|
: "set JSKIT_SHELL_WEB_BROWSER_INTEGRATION=1 to run shell-web browser integration",
|
|
261
174
|
timeout: 180_000
|
|
262
175
|
}, async () => {
|
|
263
|
-
const
|
|
264
|
-
const vite = startVite(port);
|
|
176
|
+
const vite = await startViteFixture({ fixtureRoot: FIXTURE_ROOT });
|
|
265
177
|
let browser = null;
|
|
266
178
|
|
|
267
179
|
try {
|
|
268
|
-
await
|
|
269
|
-
const executablePath = String(process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || "").trim();
|
|
270
|
-
browser = await chromium.launch({
|
|
271
|
-
headless: true,
|
|
272
|
-
...(executablePath ? { executablePath } : {})
|
|
273
|
-
});
|
|
180
|
+
browser = await chromium.launch(createChromiumLaunchOptions());
|
|
274
181
|
const context = await browser.newContext({
|
|
275
|
-
baseURL:
|
|
182
|
+
baseURL: vite.baseURL,
|
|
276
183
|
locale: "en-US"
|
|
277
184
|
});
|
|
278
185
|
|
|
279
186
|
for (const viewport of DEFAULT_VIEWPORTS) {
|
|
280
187
|
const page = await context.newPage();
|
|
281
188
|
await runAdaptiveShellSmokeCase({ page, expect, viewport });
|
|
189
|
+
if (viewport.name !== "compact") {
|
|
190
|
+
await expect(page).toHaveURL(/\/home\/settings\/general$/u);
|
|
191
|
+
}
|
|
282
192
|
await page.close();
|
|
283
193
|
}
|
|
284
194
|
|
|
@@ -324,6 +234,6 @@ test("shell-web adaptive navigation passes package-owned browser contracts", {
|
|
|
324
234
|
await context.close();
|
|
325
235
|
} finally {
|
|
326
236
|
await browser?.close();
|
|
327
|
-
await
|
|
237
|
+
await stopProcess(vite);
|
|
328
238
|
}
|
|
329
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
|
|
|
@@ -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;
|
|
@@ -351,7 +353,7 @@ test("shell-web placement topology seeds global actions as a semantic shell plac
|
|
|
351
353
|
assert.match(source, /outlet: "shell-layout:supporting-side-panel"/);
|
|
352
354
|
});
|
|
353
355
|
|
|
354
|
-
test("shell-web
|
|
356
|
+
test("shell-web packageMetadata pre-optimizes package subpaths reached only through dynamic base-shell modules", async () => {
|
|
355
357
|
const [providerSource, placementSource, placementTopologySource, errorSource] = await Promise.all([
|
|
356
358
|
readFile(path.join(PACKAGE_DIR, "src", "client", "providers", "ShellWebClientProvider.js"), "utf8"),
|
|
357
359
|
readFile(path.join(PACKAGE_DIR, "templates", "src", "placement.js"), "utf8"),
|
|
@@ -378,16 +380,16 @@ test("shell-web descriptor pre-optimizes package subpaths reached only through d
|
|
|
378
380
|
"@jskit-ai/shell-web/client/error"
|
|
379
381
|
]);
|
|
380
382
|
|
|
381
|
-
assert.deepEqual(
|
|
383
|
+
assert.deepEqual(packageMetadata?.metadata?.client?.optimizeDeps?.include, [
|
|
382
384
|
"@jskit-ai/shell-web/client/placement",
|
|
383
385
|
"@jskit-ai/shell-web/client/error"
|
|
384
386
|
]);
|
|
385
|
-
assert.deepEqual(
|
|
387
|
+
assert.deepEqual(packageMetadata?.metadata?.client?.optimizeDeps?.exclude, [
|
|
386
388
|
"@jskit-ai/shell-web/client"
|
|
387
389
|
]);
|
|
388
390
|
});
|
|
389
391
|
|
|
390
|
-
test("shell-web
|
|
392
|
+
test("shell-web packageMetadata metadata advertises adaptive shell outlets, default links, and installs the scaffold page", () => {
|
|
391
393
|
const homePageMutationIds = [
|
|
392
394
|
"shell-web-page-home-wrapper",
|
|
393
395
|
"shell-web-page-home",
|