diff --git a/tests/browser/example-board-ui.test.js b/tests/browser/example-board-ui.test.js
index 9eaaaf9b0..b610fbf89 100644
--- a/tests/browser/example-board-ui.test.js
+++ b/tests/browser/example-board-ui.test.js
@@ -38,6 +38,101 @@ function drawerMarkup() {
return `
`;
}
+test("selecting an example pen color switches from erasing to drawing", async (t) => {
+ if (!browser) return t.skip("playwright chromium unavailable");
+ const page = await browser.newPage();
+ t.after(() => page.close());
+ await page.goto(base);
+ await page.setContent(exampleMarkup());
+ await page.evaluate(async (base) => {
+ const { mountExampleBoard } = await import(`${base}/example-board.js`);
+ window.ops = [];
+ const root = document.querySelector("#example-board");
+ root.open = true;
+ mountExampleBoard(root, {
+ locked: () => false,
+ connected: () => false,
+ record: (ops) => window.ops.push(...ops),
+ publish: async () => true,
+ });
+ }, base);
+
+ const pen = page.getByRole("button", { name: "Pen", exact: true });
+ const eraser = page.getByRole("button", { name: "Eraser", exact: true });
+ const red = page.getByRole("button", { name: "Pen Red", exact: true });
+ const pressedColors = () =>
+ page
+ .locator(".board-color")
+ .evaluateAll((buttons) =>
+ buttons
+ .filter((button) => button.getAttribute("aria-pressed") === "true")
+ .map((button) => button.getAttribute("aria-label")),
+ );
+ const canvas = page.locator("canvas");
+ async function draw() {
+ const box = await canvas.boundingBox();
+ await page.mouse.move(box.x + 20, box.y + 20);
+ await page.mouse.down();
+ await page.mouse.move(box.x + 80, box.y + 40);
+ await page.mouse.up();
+ }
+
+ assert.deepEqual(await pressedColors(), ["Pen Black"]);
+ await draw();
+ assert.equal(await page.evaluate(() => window.ops.length), 1);
+ await eraser.click();
+ await draw();
+ assert.deepEqual(
+ await page.evaluate(() => {
+ const { color, width } = window.ops.at(-1);
+ return { color, width };
+ }),
+ { color: "#ffffff", width: 24 },
+ );
+ await red.click();
+ assert.equal(await page.evaluate(() => window.ops.length), 2);
+ await draw();
+ assert.deepEqual(
+ await page.evaluate(() =>
+ window.ops.slice(2).map(({ color, width }) => ({ color, width })),
+ ),
+ [{ color: "#c2261b", width: 3 }],
+ );
+ assert.equal(await pen.getAttribute("aria-pressed"), "true");
+ assert.equal(await eraser.getAttribute("aria-pressed"), "false");
+ assert.deepEqual(await pressedColors(), ["Pen Red"]);
+
+ await eraser.click();
+ assert.equal(await pen.getAttribute("aria-pressed"), "false");
+ assert.equal(await eraser.getAttribute("aria-pressed"), "true");
+ assert.deepEqual(await pressedColors(), ["Pen Red"]);
+ await draw();
+ assert.deepEqual(
+ await page.evaluate(() => {
+ const { color, width } = window.ops.at(-1);
+ return { color, width };
+ }),
+ { color: "#ffffff", width: 24 },
+ );
+ await page.getByRole("button", { name: "Undo", exact: true }).click();
+ assert.equal(await page.evaluate(() => window.ops.at(-1).op), "undo");
+
+ await red.click();
+ await draw();
+ await eraser.click();
+ await pen.click();
+ await draw();
+ assert.deepEqual(
+ await page.evaluate(() =>
+ window.ops.slice(-2).map(({ color, width }) => ({ color, width })),
+ ),
+ [
+ { color: "#c2261b", width: 3 },
+ { color: "#c2261b", width: 3 },
+ ],
+ );
+});
+
test("the example drawer preserves code, journals pen strokes, and sends the cleared state after reconnecting", async (t) => {
if (!browser) return t.skip("playwright chromium unavailable");
const page = await browser.newPage({
diff --git a/web/example-board.js b/web/example-board.js
index 004b70499..777b31ba1 100644
--- a/web/example-board.js
+++ b/web/example-board.js
@@ -128,17 +128,26 @@ export function mountExampleBoard(
schedule();
}
+ function paintToolState() {
+ for (const button of tools.querySelectorAll("button"))
+ button.setAttribute("aria-pressed", String(button.dataset.tool === tool));
+ for (const button of root.querySelectorAll(".board-color"))
+ button.setAttribute(
+ "aria-pressed",
+ String(button.dataset.color === color),
+ );
+ }
+
for (const name of ["pen", "eraser"]) {
const button = document.createElement("button");
button.type = "button";
button.className = "board-tool";
+ button.dataset.tool = name;
setToolIcon(button, name);
- button.setAttribute("aria-pressed", String(name === tool));
button.addEventListener("click", () => {
if (drag) return;
tool = name;
- for (const other of tools.querySelectorAll("button"))
- other.setAttribute("aria-pressed", String(other === button));
+ paintToolState();
});
tools.append(button);
}
@@ -146,17 +155,18 @@ export function mountExampleBoard(
const button = document.createElement("button");
button.type = "button";
button.className = "board-color";
+ button.dataset.color = value;
button.style.background = value;
button.setAttribute("aria-label", `Pen ${name}`);
- button.setAttribute("aria-pressed", String(value === color));
button.addEventListener("click", () => {
if (drag) return;
color = value;
- for (const other of root.querySelectorAll(".board-color"))
- other.setAttribute("aria-pressed", String(other === button));
+ tool = "pen";
+ paintToolState();
});
root.querySelector("[data-colors]").append(button);
}
+ paintToolState();
for (const action of ["undo", "clear"]) {
const button = root.querySelector(`[data-action=${action}]`);
setToolIcon(button, action);