wgsl-play 0.0.39 → 0.0.40
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/README.md +59 -2
- package/dist/{WgslPlay-CSjRo-5Z.d.ts → WgslPlay-ouKx64GN.d.ts} +52 -3
- package/dist/WgslPlay-zK9mE5rV.js +1544 -0
- package/dist/WgslPlay.d.ts +2 -2
- package/dist/WgslPlay.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/jsx-preact.d.ts +12 -0
- package/dist/jsx-preact.js +0 -0
- package/dist/wgsl-play.js +1834 -754
- package/package.json +12 -8
- package/src/CanvasSize.ts +27 -0
- package/src/ComputeBuild.ts +151 -0
- package/src/FragmentRender.ts +147 -0
- package/src/PlaybackControls.ts +36 -13
- package/src/RenderResources.ts +132 -0
- package/src/Renderer.ts +112 -138
- package/src/ResultsPanel.ts +261 -0
- package/src/WgslPlay.css +80 -8
- package/src/WgslPlay.ts +346 -103
- package/src/icons/refresh.svg +1 -0
- package/src/jsx-preact.ts +15 -0
- package/src/test/BundleHydrator.test.ts +8 -11
- package/src/test/CanvasSize.test.ts +48 -0
- package/src/test/E2eHelpers.ts +117 -0
- package/src/test/RenderFrame.e2e.ts +33 -0
- package/src/test/ResultsPanel.test.ts +164 -0
- package/src/test/WgslPlay.basics.e2e.ts +87 -0
- package/src/test/WgslPlay.compute.e2e.ts +147 -0
- package/src/test/WgslPlay.connect.e2e.ts +101 -0
- package/src/test/WgslPlay.loading.e2e.ts +68 -0
- package/src/test/WgslPlay.resize.e2e.ts +199 -0
- package/src/test/WgslPlay.resources.e2e.ts +67 -0
- package/src/test/WgslPlay.resources.e2e.ts-snapshots/buffer-zero-init-chromium-darwin.png +0 -0
- package/src/test/WgslPlay.resources.e2e.ts-snapshots/texture-grid-chromium-darwin.png +0 -0
- package/src/test/WgslPlay.resources.e2e.ts-snapshots/texture-img-green-chromium-darwin.png +0 -0
- package/src/test/WgslPlay.resources.e2e.ts-snapshots/texture-img-magenta-chromium-darwin.png +0 -0
- package/src/test/WgslPlay.resources.e2e.ts-snapshots/texture-multi-chromium-darwin.png +0 -0
- package/src/test/WgslPlay.uniforms.e2e.ts +57 -0
- package/dist/WgslPlay-LsU6XE09.js +0 -933
- package/src/test/WgslPlay.e2e.ts +0 -327
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.basics.e2e.ts-snapshots}/basic-shader-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.basics.e2e.ts-snapshots}/conditions-after-red-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.basics.e2e.ts-snapshots}/conditions-initial-green-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.connect.e2e.ts-snapshots}/connect-conditions-green-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.connect.e2e.ts-snapshots}/connect-conditions-red-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.connect.e2e.ts-snapshots}/connect-dynamic-npm-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.connect.e2e.ts-snapshots}/connect-source-external-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.connect.e2e.ts-snapshots}/connect-source-multifile-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.loading.e2e.ts-snapshots}/link-import-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.loading.e2e.ts-snapshots}/npm-cdn-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.loading.e2e.ts-snapshots}/shader-root-internal-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.loading.e2e.ts-snapshots}/shader-root-src-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.loading.e2e.ts-snapshots}/static-import-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.uniforms.e2e.ts-snapshots}/uniforms-initial-chromium-darwin.png +0 -0
- /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.uniforms.e2e.ts-snapshots}/uniforms-slider-changed-chromium-darwin.png +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** E2E tests for connect page (wgsl-edit connection, virtualLibs). */
|
|
2
|
+
import { expect, test } from "@playwright/test";
|
|
3
|
+
import {
|
|
4
|
+
expectCanvasSnapshot,
|
|
5
|
+
waitForFrame,
|
|
6
|
+
waitForNewFrame,
|
|
7
|
+
waitForWgslPlay,
|
|
8
|
+
} from "./E2eHelpers.ts";
|
|
9
|
+
|
|
10
|
+
test("connectToSource - multi-file from wgsl-edit renders", async ({
|
|
11
|
+
page,
|
|
12
|
+
}) => {
|
|
13
|
+
await page.goto("/connect.html");
|
|
14
|
+
await waitForFrame(page, "#multifilePlayer");
|
|
15
|
+
await expectCanvasSnapshot(
|
|
16
|
+
page,
|
|
17
|
+
"#multifilePlayer",
|
|
18
|
+
"connect-source-multifile.png",
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("connectToSource - condition toggle re-renders", async ({ page }) => {
|
|
23
|
+
await page.goto("/connect.html");
|
|
24
|
+
await waitForFrame(page, "#connectCondPlayer");
|
|
25
|
+
|
|
26
|
+
await expectCanvasSnapshot(
|
|
27
|
+
page,
|
|
28
|
+
"#connectCondPlayer",
|
|
29
|
+
"connect-conditions-green.png",
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
await page.click("#set-connect-red");
|
|
33
|
+
await waitForNewFrame(page, "#connectCondPlayer");
|
|
34
|
+
|
|
35
|
+
await expectCanvasSnapshot(
|
|
36
|
+
page,
|
|
37
|
+
"#connectCondPlayer",
|
|
38
|
+
"connect-conditions-red.png",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("connectToSource - external deps are fetched", {
|
|
43
|
+
tag: "@network",
|
|
44
|
+
}, async ({ page }) => {
|
|
45
|
+
await page.goto("/connect.html");
|
|
46
|
+
await page.waitForLoadState("networkidle");
|
|
47
|
+
await waitForFrame(page, "#connectExtPlayer");
|
|
48
|
+
|
|
49
|
+
// If discoverAndRebuild wasn't called, this would show an error overlay
|
|
50
|
+
const hasError = await page.evaluate(
|
|
51
|
+
() =>
|
|
52
|
+
(document.querySelector("#connectExtPlayer") as any)?.hasError ?? true,
|
|
53
|
+
);
|
|
54
|
+
expect(hasError).toBe(false);
|
|
55
|
+
await expectCanvasSnapshot(
|
|
56
|
+
page,
|
|
57
|
+
"#connectExtPlayer",
|
|
58
|
+
"connect-source-external.png",
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("connectToSource - dynamic npm package loading", {
|
|
63
|
+
tag: "@network",
|
|
64
|
+
}, async ({ page }) => {
|
|
65
|
+
await page.goto("/connect.html");
|
|
66
|
+
await waitForFrame(page, "#dynamicNpmPlayer");
|
|
67
|
+
|
|
68
|
+
await page.click("#inject-dynamic-npm");
|
|
69
|
+
await page.waitForLoadState("networkidle");
|
|
70
|
+
await waitForFrame(page, "#dynamicNpmPlayer");
|
|
71
|
+
|
|
72
|
+
const hasError = await page.evaluate(
|
|
73
|
+
() =>
|
|
74
|
+
(document.querySelector("#dynamicNpmPlayer") as any)?.hasError ?? true,
|
|
75
|
+
);
|
|
76
|
+
expect(hasError).toBe(false);
|
|
77
|
+
await expectCanvasSnapshot(
|
|
78
|
+
page,
|
|
79
|
+
"#dynamicNpmPlayer",
|
|
80
|
+
"connect-dynamic-npm.png",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("editor.link() with virtualLibs resolves env:: module", async ({
|
|
85
|
+
page,
|
|
86
|
+
}) => {
|
|
87
|
+
await page.goto("/connect.html");
|
|
88
|
+
await waitForWgslPlay(page);
|
|
89
|
+
|
|
90
|
+
await page.click("#link-virtual-libs");
|
|
91
|
+
await page.waitForFunction(
|
|
92
|
+
() =>
|
|
93
|
+
document.querySelector("#virtual-libs-output")?.textContent !==
|
|
94
|
+
"Not linked yet",
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const output = await page.textContent("#virtual-libs-output");
|
|
98
|
+
expect(output).toContain("var<uniform>");
|
|
99
|
+
expect(output).toContain("fs_main");
|
|
100
|
+
expect(output).not.toContain("Error");
|
|
101
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** E2E tests for loading page (npm CDN, shaderRoot, ?static, ?link). */
|
|
2
|
+
import { test } from "@playwright/test";
|
|
3
|
+
import {
|
|
4
|
+
expectCanvasSnapshot,
|
|
5
|
+
waitForFrame,
|
|
6
|
+
waitForWgslPlay,
|
|
7
|
+
} from "./E2eHelpers.ts";
|
|
8
|
+
|
|
9
|
+
test("npm CDN - external imports work", { tag: "@network" }, async ({
|
|
10
|
+
page,
|
|
11
|
+
}) => {
|
|
12
|
+
await page.goto("/loading.html");
|
|
13
|
+
await waitForWgslPlay(page);
|
|
14
|
+
|
|
15
|
+
await page.click("#load-npm-shader");
|
|
16
|
+
await page.waitForLoadState("networkidle");
|
|
17
|
+
await waitForFrame(page, "#npmPlayer");
|
|
18
|
+
|
|
19
|
+
// Rewind to time=0 for deterministic snapshot (rewind() renders synchronously)
|
|
20
|
+
await page.click("#npm-rewind");
|
|
21
|
+
await expectCanvasSnapshot(page, "#npmPlayer", "npm-cdn.png");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("shaderRoot - package:: imports resolve", async ({ page }) => {
|
|
25
|
+
await page.goto("/loading.html");
|
|
26
|
+
await waitForWgslPlay(page);
|
|
27
|
+
|
|
28
|
+
await page.click("#load-internal-shader");
|
|
29
|
+
await waitForFrame(page, "#internalImportPlayer");
|
|
30
|
+
|
|
31
|
+
await expectCanvasSnapshot(
|
|
32
|
+
page,
|
|
33
|
+
"#internalImportPlayer",
|
|
34
|
+
"shader-root-internal.png",
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("shaderRoot - src attribute with super:: and package:: imports", async ({
|
|
39
|
+
page,
|
|
40
|
+
}) => {
|
|
41
|
+
await page.goto("/loading.html");
|
|
42
|
+
await waitForWgslPlay(page);
|
|
43
|
+
|
|
44
|
+
await page.click("#load-src-shader");
|
|
45
|
+
await waitForFrame(page, "#srcAttrPlayer");
|
|
46
|
+
|
|
47
|
+
await expectCanvasSnapshot(page, "#srcAttrPlayer", "shader-root-src.png");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("?static import - wesl-plugin build-time linking", async ({ page }) => {
|
|
51
|
+
await page.goto("/loading.html");
|
|
52
|
+
await waitForWgslPlay(page);
|
|
53
|
+
|
|
54
|
+
await page.click("#load-static-shader");
|
|
55
|
+
await waitForFrame(page, "#staticImportPlayer");
|
|
56
|
+
|
|
57
|
+
await expectCanvasSnapshot(page, "#staticImportPlayer", "static-import.png");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("?link import - wesl-plugin runtime linking", async ({ page }) => {
|
|
61
|
+
await page.goto("/loading.html");
|
|
62
|
+
await waitForWgslPlay(page);
|
|
63
|
+
|
|
64
|
+
await page.click("#load-link-shader");
|
|
65
|
+
await waitForFrame(page, "#linkImportPlayer");
|
|
66
|
+
|
|
67
|
+
await expectCanvasSnapshot(page, "#linkImportPlayer", "link-import.png");
|
|
68
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/** E2E tests for two-corner resize handles + corner-local hover-reveal. */
|
|
2
|
+
import { expect, type Page, test } from "@playwright/test";
|
|
3
|
+
import { waitForWgslPlay } from "./E2eHelpers.ts";
|
|
4
|
+
|
|
5
|
+
/** Get a resize handle's bounding box (in viewport coords) from inside shadow DOM. */
|
|
6
|
+
async function handleBox(page: Page, playerId: string, corner: "br" | "bl") {
|
|
7
|
+
return page.evaluate(
|
|
8
|
+
([id, c]) => {
|
|
9
|
+
const el = document.querySelector(id) as HTMLElement | null;
|
|
10
|
+
const handle = el?.shadowRoot?.querySelector(
|
|
11
|
+
`.resize-handle.${c}`,
|
|
12
|
+
) as HTMLElement | null;
|
|
13
|
+
if (!handle) return null;
|
|
14
|
+
const r = handle.getBoundingClientRect();
|
|
15
|
+
return { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
16
|
+
},
|
|
17
|
+
[playerId, corner] as const,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Read computed opacity (numeric) of a resize handle. */
|
|
22
|
+
async function handleOpacity(
|
|
23
|
+
page: Page,
|
|
24
|
+
playerId: string,
|
|
25
|
+
corner: "br" | "bl",
|
|
26
|
+
): Promise<number> {
|
|
27
|
+
return page.evaluate(
|
|
28
|
+
([id, c]) => {
|
|
29
|
+
const el = document.querySelector(id) as HTMLElement | null;
|
|
30
|
+
const handle = el?.shadowRoot?.querySelector(
|
|
31
|
+
`.resize-handle.${c}`,
|
|
32
|
+
) as HTMLElement | null;
|
|
33
|
+
return handle ? Number(getComputedStyle(handle).opacity) : -1;
|
|
34
|
+
},
|
|
35
|
+
[playerId, corner] as const,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Get an element's bounding box (in viewport coords). */
|
|
40
|
+
async function elBox(page: Page, selector: string) {
|
|
41
|
+
return page.evaluate(sel => {
|
|
42
|
+
const el = document.querySelector(sel) as HTMLElement | null;
|
|
43
|
+
if (!el) return null;
|
|
44
|
+
const r = el.getBoundingClientRect();
|
|
45
|
+
return { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
46
|
+
}, selector);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Drag from start to (start + dx, start + dy) via raw mouse events. */
|
|
50
|
+
async function dragBy(
|
|
51
|
+
page: Page,
|
|
52
|
+
startX: number,
|
|
53
|
+
startY: number,
|
|
54
|
+
dx: number,
|
|
55
|
+
dy: number,
|
|
56
|
+
) {
|
|
57
|
+
await page.mouse.move(startX, startY);
|
|
58
|
+
await page.mouse.down();
|
|
59
|
+
await page.mouse.move(startX + dx, startY + dy, { steps: 8 });
|
|
60
|
+
await page.mouse.up();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test("both handles render in shadow DOM with opacity 0 at rest", async ({
|
|
64
|
+
page,
|
|
65
|
+
}) => {
|
|
66
|
+
await page.goto("/resize.html");
|
|
67
|
+
await waitForWgslPlay(page);
|
|
68
|
+
|
|
69
|
+
expect(await handleOpacity(page, "#blockPlayer", "br")).toBe(0);
|
|
70
|
+
expect(await handleOpacity(page, "#blockPlayer", "bl")).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("BR handle reveals only on BR corner hover", async ({ page }) => {
|
|
74
|
+
await page.goto("/resize.html");
|
|
75
|
+
await waitForWgslPlay(page);
|
|
76
|
+
|
|
77
|
+
const br = await handleBox(page, "#blockPlayer", "br");
|
|
78
|
+
if (!br) throw new Error("br handle not found");
|
|
79
|
+
await page.mouse.move(br.x + br.width / 2, br.y + br.height / 2);
|
|
80
|
+
|
|
81
|
+
// opacity transitions over 0.15s — poll until past midpoint
|
|
82
|
+
await expect
|
|
83
|
+
.poll(() => handleOpacity(page, "#blockPlayer", "br"), { timeout: 2000 })
|
|
84
|
+
.toBeGreaterThan(0.9);
|
|
85
|
+
expect(await handleOpacity(page, "#blockPlayer", "bl")).toBe(0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("BL handle reveals only on BL corner hover", async ({ page }) => {
|
|
89
|
+
await page.goto("/resize.html");
|
|
90
|
+
await waitForWgslPlay(page);
|
|
91
|
+
|
|
92
|
+
const bl = await handleBox(page, "#blockPlayer", "bl");
|
|
93
|
+
if (!bl) throw new Error("bl handle not found");
|
|
94
|
+
await page.mouse.move(bl.x + bl.width / 2, bl.y + bl.height / 2);
|
|
95
|
+
|
|
96
|
+
await expect
|
|
97
|
+
.poll(() => handleOpacity(page, "#blockPlayer", "bl"), { timeout: 2000 })
|
|
98
|
+
.toBeGreaterThan(0.9);
|
|
99
|
+
expect(await handleOpacity(page, "#blockPlayer", "br")).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("canvas-interior hover does not reveal either handle", async ({
|
|
103
|
+
page,
|
|
104
|
+
}) => {
|
|
105
|
+
await page.goto("/resize.html");
|
|
106
|
+
await waitForWgslPlay(page);
|
|
107
|
+
|
|
108
|
+
const player = await elBox(page, "#blockPlayer");
|
|
109
|
+
if (!player) throw new Error("player not found");
|
|
110
|
+
// Center of the canvas, away from any corner.
|
|
111
|
+
await page.mouse.move(
|
|
112
|
+
player.x + player.width / 2,
|
|
113
|
+
player.y + player.height / 2,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
expect(await handleOpacity(page, "#blockPlayer", "br")).toBe(0);
|
|
117
|
+
expect(await handleOpacity(page, "#blockPlayer", "bl")).toBe(0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("BR drag grows width and height", async ({ page }) => {
|
|
121
|
+
await page.goto("/resize.html");
|
|
122
|
+
await waitForWgslPlay(page);
|
|
123
|
+
|
|
124
|
+
const before = await elBox(page, "#blockPlayer");
|
|
125
|
+
const br = await handleBox(page, "#blockPlayer", "br");
|
|
126
|
+
if (!before || !br) throw new Error("setup failed");
|
|
127
|
+
|
|
128
|
+
await dragBy(page, br.x + br.width / 2, br.y + br.height / 2, 50, 50);
|
|
129
|
+
|
|
130
|
+
const after = await elBox(page, "#blockPlayer");
|
|
131
|
+
if (!after) throw new Error("missing after rect");
|
|
132
|
+
expect(after.width).toBeGreaterThan(before.width + 40);
|
|
133
|
+
expect(after.height).toBeGreaterThan(before.height + 40);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("BL drag in flex layout grows play width, shrinks edit, pins right edge", async ({
|
|
137
|
+
page,
|
|
138
|
+
}) => {
|
|
139
|
+
await page.goto("/resize.html");
|
|
140
|
+
await waitForWgslPlay(page);
|
|
141
|
+
await page.locator("#flexPlayer").scrollIntoViewIfNeeded();
|
|
142
|
+
|
|
143
|
+
const playBefore = await elBox(page, "#flexPlayer");
|
|
144
|
+
const editBefore = await elBox(page, ".splitter > .edit");
|
|
145
|
+
const bl = await handleBox(page, "#flexPlayer", "bl");
|
|
146
|
+
if (!playBefore || !editBefore || !bl) throw new Error("setup failed");
|
|
147
|
+
|
|
148
|
+
await dragBy(page, bl.x + bl.width / 2, bl.y + bl.height / 2, -50, 0);
|
|
149
|
+
|
|
150
|
+
const playAfter = await elBox(page, "#flexPlayer");
|
|
151
|
+
const editAfter = await elBox(page, ".splitter > .edit");
|
|
152
|
+
if (!playAfter || !editAfter) throw new Error("missing after rect");
|
|
153
|
+
|
|
154
|
+
expect(playAfter.width).toBeGreaterThan(playBefore.width + 40);
|
|
155
|
+
expect(editAfter.width).toBeLessThan(editBefore.width - 40);
|
|
156
|
+
// Right edge of the player stays pinned (flex container fills its parent).
|
|
157
|
+
const playRightBefore = playBefore.x + playBefore.width;
|
|
158
|
+
const playRightAfter = playAfter.x + playAfter.width;
|
|
159
|
+
expect(Math.abs(playRightAfter - playRightBefore)).toBeLessThan(2);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("min-size guard clamps width to 16px on aggressive shrink", async ({
|
|
163
|
+
page,
|
|
164
|
+
}) => {
|
|
165
|
+
await page.goto("/resize.html");
|
|
166
|
+
await waitForWgslPlay(page);
|
|
167
|
+
|
|
168
|
+
const before = await elBox(page, "#blockPlayer");
|
|
169
|
+
const bl = await handleBox(page, "#blockPlayer", "bl");
|
|
170
|
+
if (!before || !bl) throw new Error("setup failed");
|
|
171
|
+
|
|
172
|
+
// Drag BL far to the right (shrinks width — clamps at 16).
|
|
173
|
+
await dragBy(
|
|
174
|
+
page,
|
|
175
|
+
bl.x + bl.width / 2,
|
|
176
|
+
bl.y + bl.height / 2,
|
|
177
|
+
before.width + 200,
|
|
178
|
+
0,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const after = await elBox(page, "#blockPlayer");
|
|
182
|
+
if (!after) throw new Error("missing after rect");
|
|
183
|
+
expect(after.width).toBeGreaterThanOrEqual(16);
|
|
184
|
+
expect(after.width).toBeLessThan(32);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("non-resizable element renders no visible handles", async ({ page }) => {
|
|
188
|
+
await page.goto("/resize.html");
|
|
189
|
+
await waitForWgslPlay(page);
|
|
190
|
+
|
|
191
|
+
const display = await page.evaluate(() => {
|
|
192
|
+
const el = document.querySelector("#plainPlayer") as HTMLElement | null;
|
|
193
|
+
const h = el?.shadowRoot?.querySelector(
|
|
194
|
+
".resize-handle",
|
|
195
|
+
) as HTMLElement | null;
|
|
196
|
+
return h ? getComputedStyle(h).display : "missing";
|
|
197
|
+
});
|
|
198
|
+
expect(display).toBe("none");
|
|
199
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** E2E tests for resources page (@texture, @sampler, @buffer). */
|
|
2
|
+
import { expect, test } from "@playwright/test";
|
|
3
|
+
import {
|
|
4
|
+
expectCanvasSnapshot,
|
|
5
|
+
waitForFrame,
|
|
6
|
+
waitForNewFrame,
|
|
7
|
+
waitForWgslPlay,
|
|
8
|
+
} from "./E2eHelpers.ts";
|
|
9
|
+
|
|
10
|
+
test("@texture single resolves <canvas data-texture> child", async ({
|
|
11
|
+
page,
|
|
12
|
+
}) => {
|
|
13
|
+
await page.goto("/resources.html");
|
|
14
|
+
await waitForFrame(page, "#singleTexturePlayer");
|
|
15
|
+
await expectCanvasSnapshot(page, "#singleTexturePlayer", "texture-grid.png");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("@texture multi blends two host canvases", async ({ page }) => {
|
|
19
|
+
await page.goto("/resources.html");
|
|
20
|
+
await waitForFrame(page, "#multiTexturePlayer");
|
|
21
|
+
await expectCanvasSnapshot(page, "#multiTexturePlayer", "texture-multi.png");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("@buffer zero-init storage buffer renders deterministically", async ({
|
|
25
|
+
page,
|
|
26
|
+
}) => {
|
|
27
|
+
await page.goto("/resources.html");
|
|
28
|
+
await waitForFrame(page, "#bufferPlayer");
|
|
29
|
+
await expectCanvasSnapshot(page, "#bufferPlayer", "buffer-zero-init.png");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("@texture from <img> decodes and rebuilds on src swap", async ({
|
|
33
|
+
page,
|
|
34
|
+
}) => {
|
|
35
|
+
await page.goto("/resources.html");
|
|
36
|
+
await waitForFrame(page, "#imgTexturePlayer");
|
|
37
|
+
await expectCanvasSnapshot(
|
|
38
|
+
page,
|
|
39
|
+
"#imgTexturePlayer",
|
|
40
|
+
"texture-img-magenta.png",
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
await page.click("#swap-img-texture");
|
|
44
|
+
await waitForNewFrame(page, "#imgTexturePlayer");
|
|
45
|
+
await expectCanvasSnapshot(
|
|
46
|
+
page,
|
|
47
|
+
"#imgTexturePlayer",
|
|
48
|
+
"texture-img-green.png",
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("@test_texture is rejected with structured compile-error", async ({
|
|
53
|
+
page,
|
|
54
|
+
}) => {
|
|
55
|
+
await page.goto("/resources.html");
|
|
56
|
+
await waitForWgslPlay(page);
|
|
57
|
+
|
|
58
|
+
await page.waitForFunction(
|
|
59
|
+
() =>
|
|
60
|
+
(document.querySelector("#rejected-texture-status") as HTMLElement)
|
|
61
|
+
?.dataset.rejected === "true",
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const text = await page.textContent("#rejected-texture-status");
|
|
65
|
+
expect(text).toContain("rejected (resource)");
|
|
66
|
+
expect(text).toContain("@test_texture");
|
|
67
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** E2E tests for uniforms page (@uniforms controls, mouse). */
|
|
2
|
+
import { expect, test } from "@playwright/test";
|
|
3
|
+
import { expectCanvasSnapshot, waitForFrame } from "./E2eHelpers.ts";
|
|
4
|
+
|
|
5
|
+
test("@uniforms controls panel renders and responds", async ({ page }) => {
|
|
6
|
+
await page.goto("/uniforms.html");
|
|
7
|
+
await waitForFrame(page, "#uniformsPlayer");
|
|
8
|
+
|
|
9
|
+
const hasError = await page.evaluate(
|
|
10
|
+
() => (document.querySelector("#uniformsPlayer") as any)?.hasError ?? true,
|
|
11
|
+
);
|
|
12
|
+
expect(hasError).toBe(false);
|
|
13
|
+
|
|
14
|
+
// Rewind so snapshot is deterministic (brightness set by page script on compile-success)
|
|
15
|
+
// rewind() synchronously renders one frame, so no need to waitForNewFrame
|
|
16
|
+
await page.evaluate(() => {
|
|
17
|
+
(document.querySelector("#uniformsPlayer") as any)?.rewind();
|
|
18
|
+
});
|
|
19
|
+
await expectCanvasSnapshot(page, "#uniformsPlayer", "uniforms-initial.png");
|
|
20
|
+
|
|
21
|
+
// Hover to reveal controls toggle, click to expand
|
|
22
|
+
await page.locator("#uniformsPlayer").hover({ position: { x: 16, y: 16 } });
|
|
23
|
+
await page.evaluate(() => {
|
|
24
|
+
const el = document.querySelector("#uniformsPlayer");
|
|
25
|
+
(el?.shadowRoot?.querySelector(".uniform-toggle") as HTMLElement)?.click();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Verify controls panel expanded with 3 controls (brightness is plain, no UI)
|
|
29
|
+
const controlCount = await page.evaluate(() => {
|
|
30
|
+
const el = document.querySelector("#uniformsPlayer");
|
|
31
|
+
const panel = el?.shadowRoot?.querySelector(".uniform-panel");
|
|
32
|
+
const rows = panel?.querySelectorAll(".uniform-row");
|
|
33
|
+
return {
|
|
34
|
+
visible: panel?.checkVisibility() ?? false,
|
|
35
|
+
count: rows?.length ?? 0,
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
expect(controlCount.visible).toBe(true);
|
|
39
|
+
expect(controlCount.count).toBe(3);
|
|
40
|
+
|
|
41
|
+
// Adjust the frequency slider and verify re-render
|
|
42
|
+
await page.evaluate(() => {
|
|
43
|
+
const el = document.querySelector("#uniformsPlayer");
|
|
44
|
+
const slider = el?.shadowRoot?.querySelector(
|
|
45
|
+
'input[type="range"]',
|
|
46
|
+
) as HTMLInputElement;
|
|
47
|
+
if (slider) {
|
|
48
|
+
slider.value = "16";
|
|
49
|
+
slider.dispatchEvent(new Event("input", { bubbles: true }));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
await expectCanvasSnapshot(
|
|
53
|
+
page,
|
|
54
|
+
"#uniformsPlayer",
|
|
55
|
+
"uniforms-slider-changed.png",
|
|
56
|
+
);
|
|
57
|
+
});
|