Stage workspace controls after bootstrap

This commit is contained in:
2026-05-04 19:03:47 +02:00
parent da813583bd
commit e0b7d27ba7
8 changed files with 542 additions and 575 deletions

View File

@@ -91,6 +91,89 @@ test("successful login transitions to play workspace", async ({ page, context })
await expect(page.locator("#login-username")).toHaveCount(0);
});
test("workspace stays usable when input controls are DOM-wrapped during mount", async ({ page, context }) => {
const username = `wrapped-${Date.now()}`;
await registerAndLogin(context.request, username, "Wrapped Inputs");
const campaign = await postJson(context.request, "/api/campaigns", {
name: "Wrapped Inputs",
rulesetId: "d6"
});
const character = await postJson(context.request, "/api/characters", {
name: "Wrapper Hero",
campaignId: campaign.id
});
await postJson(context.request, `/api/characters/${character.id}/skills`, {
name: "Stealth",
diceRollDefinition: "2D+1",
wildDice: 1,
allowFumble: true
});
await page.addInitScript(() => {
const wrappedMarker = "rrWrappedByTest";
function wrapControl(element) {
if (!(element instanceof HTMLElement) || !element.isConnected || element.dataset[wrappedMarker] === "1") {
return;
}
const parent = element.parentNode;
if (!parent) {
return;
}
const wrapper = document.createElement("span");
wrapper.dataset[wrappedMarker] = "1";
element.dataset[wrappedMarker] = "1";
parent.insertBefore(wrapper, element);
wrapper.appendChild(element);
}
function queueWrap(node) {
if (!(node instanceof Element)) {
return;
}
if (node.matches("input, select")) {
queueMicrotask(() => wrapControl(node));
}
node.querySelectorAll("input, select").forEach((element) => {
queueMicrotask(() => wrapControl(element));
});
}
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach(queueWrap);
});
});
observer.observe(document.documentElement, { childList: true, subtree: true });
document.querySelectorAll("input, select").forEach((element) => queueWrap(element));
});
const blazorErrors = [];
page.on("console", (message) => {
if (message.type() !== "error") {
return;
}
const text = message.text();
if (/error applying batch|unhandled exception on the current circuit/i.test(text)) {
blazorErrors.push(text);
}
});
await page.goto("/");
await expect(page.getByText("Campaign Log")).toBeVisible();
await expect(page.locator("#skill-filter-input")).toBeVisible();
await expect(page.locator("#custom-roll-expression")).toBeVisible();
await expect(page.getByRole("button", { name: "Roll Stealth" })).toBeVisible();
expect(blazorErrors).toEqual([]);
});
test("Rolemaster open-ended roll detail renders specialized dice chips", async ({ page, context }) => {
const username = `rm-${Date.now()}`;
const displayName = "Rolemaster Smoke";