import { expect, test, type Page } from "@playwright/test"; const canvasKeys = (page: Page) => page.locator('[data-testid="idx-canvas"] .key'); const numberOf = (page: Page, i: number) => canvasKeys(page).nth(i).getByTestId("idx-number"); /** Drags key `from` onto key `to`, releasing at `fraction` of the target's width. */ async function dragKey(page: Page, from: number, to: number, fraction: number) { const a = (await canvasKeys(page).nth(from).locator("rect").boundingBox())!; const b = (await canvasKeys(page).nth(to).locator("rect").boundingBox())!; await page.mouse.move(a.x + a.width / 2, a.y + a.height / 2); await page.mouse.down(); await page.mouse.move(a.x + a.width / 2 + 10, a.y + a.height / 2, { steps: 3 }); await page.mouse.move(b.x + b.width * fraction, b.y + b.height / 2, { steps: 5 }); await page.mouse.up(); } test.beforeEach(async ({ page }) => { await page.goto("/"); await page.getByRole("button", { name: "IDX", exact: true }).click(); await expect(canvasKeys(page)).toHaveCount(16); }); test("drops at the edge of a key insert before or after it, and undo restores", async ({ page }) => { await dragKey(page, 0, 3, 0.9); await expect(numberOf(page, 0)).toHaveText("3"); await expect(numberOf(page, 3)).toHaveText("2"); await expect(numberOf(page, 1)).toHaveText("0"); await dragKey(page, 0, 1, 0.1); await expect(numberOf(page, 0)).toHaveText("0"); await page.getByRole("button", { name: "Undo" }).click(); await expect(numberOf(page, 0)).toHaveText("3"); await page.getByRole("button", { name: "Undo" }).click(); await expect(numberOf(page, 0)).toHaveText("0"); await expect(numberOf(page, 3)).toHaveText("3"); }); test("a drop in the middle of a key swaps the two and leaves the others", async ({ page }) => { await dragKey(page, 0, 5, 0.5); await expect(numberOf(page, 0)).toHaveText("5"); await expect(numberOf(page, 5)).toHaveText("0"); await expect(numberOf(page, 1)).toHaveText("1"); await expect(numberOf(page, 4)).toHaveText("4"); }); test("a key added in PHY is registered at the end and can be dragged to the front", async ({ page }) => { await page.getByRole("button", { name: "PHY", exact: true }).click(); await page.getByRole("button", { name: "キーを足す" }).click(); await page.getByRole("button", { name: "IDX", exact: true }).click(); await expect(numberOf(page, 16)).toHaveText("16"); await dragKey(page, 16, 0, 0.1); await expect(numberOf(page, 16)).toHaveText("0"); await expect(numberOf(page, 0)).toHaveText("1"); }); test("the variant tree switches the shown configuration without touching other keys' order", async ({ page, }) => { await page.getByRole("button", { name: "PHY", exact: true }).click(); const keys = page.locator('[data-node-type="key"]'); const firstId = await keys.nth(0).getAttribute("data-node-id"); await keys.nth(0).click(); await keys.nth(1).click({ modifiers: ["Shift"] }); await page.getByRole("button", { name: "選択からバリアントグループ" }).click(); await page.getByRole("button", { name: "IDX", exact: true }).click(); const optionA = page.locator(".tree").getByRole("button", { name: /Option A/ }); const optionB = page.locator(".tree").getByRole("button", { name: /Option B/ }); await expect(optionA).toHaveAttribute("aria-pressed", "true"); await expect(page.locator(`[data-testid="idx-canvas"] [data-key-id="${firstId}"]`)).toHaveCount(1); await optionB.click(); await expect(optionB).toHaveAttribute("aria-pressed", "true"); await expect(page.locator(`[data-testid="idx-canvas"] [data-key-id="${firstId}"]`)).toHaveCount(0); await expect(canvasKeys(page)).toHaveCount(14); await expect(numberOf(page, 0)).toHaveText("0"); }); test("a column-major preset previews, applies as one undo step", async ({ page }) => { await page.getByLabel("走査").selectOption({ label: "列優先" }); await page.getByRole("button", { name: "プレビュー" }).click(); await expect(numberOf(page, 4)).toHaveText("1"); await expect(page.locator(".scan-line")).toHaveCount(4); await page.getByRole("button", { name: "適用" }).click(); await expect(numberOf(page, 4)).toHaveText("1"); await expect(numberOf(page, 1)).toHaveText("4"); await page.getByRole("button", { name: "Undo" }).click(); await expect(numberOf(page, 1)).toHaveText("1"); }); test("the preview shifts a picked column by one row and Esc discards it", async ({ page }) => { await page.getByRole("button", { name: "プレビュー" }).click(); await canvasKeys(page).nth(0).click(); await page.keyboard.press("ArrowDown"); // column 0 now belongs one row lower: row 0 is keys 1..3 await expect(numberOf(page, 1)).toHaveText("0"); await expect(numberOf(page, 0)).toHaveText("3"); await page.keyboard.press("Escape"); await expect(page.getByRole("button", { name: "プレビュー" })).toBeVisible(); await expect(numberOf(page, 0)).toHaveText("0"); }); test("a range selection renumbers from its first key's position", async ({ page }) => { const k2 = (await canvasKeys(page).nth(2).locator("rect").boundingBox())!; const k15 = (await canvasKeys(page).nth(15).locator("rect").boundingBox())!; await page.mouse.move(k2.x + 5, k2.y - 20); await page.mouse.down(); await page.mouse.move(k15.x + k15.width + 20, k15.y + k15.height + 20, { steps: 6 }); await page.mouse.up(); await expect(page.getByText("選択範囲(8)")).toBeVisible(); await page.getByText("選択範囲(8)").click(); await page.getByLabel("走査").selectOption({ label: "列優先" }); await page.getByRole("button", { name: "プレビュー" }).click(); await page.getByRole("button", { name: "適用" }).click(); // 0, 1, then the right half column by column, then the rest of the left half await expect(numberOf(page, 2)).toHaveText("2"); await expect(numberOf(page, 6)).toHaveText("3"); await expect(numberOf(page, 3)).toHaveText("6"); await expect(numberOf(page, 4)).toHaveText("10"); }); test("importing a QMK layout copies its key order onto matching keys", async ({ page }) => { const layout = [0, 1, 2, 3].flatMap((x) => [0, 1, 2, 3].map((y) => ({ matrix: [y, x], x, y }))); await page.getByTestId("idx-import-file").setInputFiles({ name: "keyboard.json", mimeType: "application/json", buffer: Buffer.from(JSON.stringify({ layouts: { LAYOUT: { layout } } })), }); await expect(page.getByTestId("idx-import-result")).toContainText("16件を照合"); await expect(numberOf(page, 4)).toHaveText("1"); await expect(numberOf(page, 1)).toHaveText("4"); });