diff --git a/tests/e2e/canvas-manipulation.spec.ts b/tests/e2e/canvas-manipulation.spec.ts index 71cf3ed19..86544f168 100644 --- a/tests/e2e/canvas-manipulation.spec.ts +++ b/tests/e2e/canvas-manipulation.spec.ts @@ -1,7 +1,7 @@ import { test, expect, type Page } from '@playwright/test' import { CanvasHelper } from '../helpers/canvas' -import { getSelectedIds, getPageChildren, getSelectedNode } from '../helpers/store' +import { getSelectedIds, getPageChildren, getSelectedNode, getNodeById } from '../helpers/store' let page: Page let canvas: CanvasHelper @@ -61,18 +61,19 @@ test('Alt+drag duplicate increases child count', async () => { canvas.assertNoErrors() }) -test('Shift+ArrowRight nudges node 10px', async () => { +test('duplicate shortcut Cmd+D increases child count', async () => { await canvas.clearCanvas() await canvas.drawRect(200, 200, 80, 80) - const before = await getSelectedNode(page) - expect(before).not.toBeNull() - const initialX = before!.x - - await canvas.pressKey('Shift+ArrowRight') + await canvas.click(240, 240) await canvas.waitForRender() - const after = await getSelectedNode(page) - expect(after!.x).toBeCloseTo(initialX + 10, 0) + const before = (await getPageChildren(page)).length + + await canvas.pressKey('Meta+d') + await canvas.waitForRender() + + const after = (await getPageChildren(page)).length + expect(after).toBe(before + 1) canvas.assertNoErrors() }) @@ -149,15 +150,18 @@ test('rotation handle drag rotates node', async () => { if (!box) throw new Error('No canvas') const rx = box.x + viewport!.cx - const ry = box.y + viewport!.topMidY - 16 + const ry = box.y + viewport!.topMidY - 24 + + const nodeId = before!.id await page.mouse.move(rx, ry) + await canvas.waitForRender() await page.mouse.down() - await page.mouse.move(rx + 40, ry - 20, { steps: 10 }) + await page.mouse.move(rx + 60, ry + 60, { steps: 15 }) await page.mouse.up() await canvas.waitForRender() - const after = await getSelectedNode(page) + const after = await getNodeById(page, nodeId) expect(after!.rotation ?? 0).not.toBe(initialRotation) canvas.assertNoErrors() }) diff --git a/tests/e2e/properties-panel.spec.ts b/tests/e2e/properties-panel.spec.ts index c6a268a81..50185b14b 100644 --- a/tests/e2e/properties-panel.spec.ts +++ b/tests/e2e/properties-panel.spec.ts @@ -64,9 +64,19 @@ test('independent corners toggle shows four corner inputs', async () => { test('fill gradient switch changes fill type', async () => { await canvas.clearCanvas() - await canvas.drawRect(200, 200, 80, 80) + await canvas.pressKey('Escape') + await canvas.waitForRender() + // fresh rect with default solid fill + await canvas.drawRect(300, 300, 80, 80) + await canvas.waitForRender() - await page.locator('[data-test-id="fill-item"]').first().locator('[data-test-id="color-picker-swatch"]').click() + await expect(page.locator('[data-test-id="fill-section"]')).toBeVisible({ timeout: 5000 }) + + const fillItem = page.locator('[data-test-id="fill-item"]').first() + await expect(fillItem).toBeVisible({ timeout: 5000 }) + const fillSwatch = fillItem.locator('[data-test-id="fill-picker-swatch"]') + await expect(fillSwatch).toBeVisible({ timeout: 5000 }) + await fillSwatch.click() await canvas.waitForRender() await page.locator('[data-test-id="fill-picker-tab-gradient"]').click() diff --git a/tests/e2e/text-formatting.spec.ts b/tests/e2e/text-formatting.spec.ts index d0c99e6c8..099cff36a 100644 --- a/tests/e2e/text-formatting.spec.ts +++ b/tests/e2e/text-formatting.spec.ts @@ -1,7 +1,7 @@ import { test, expect, type Page } from '@playwright/test' import { CanvasHelper } from '../helpers/canvas' -import { getSelectedNode, getEditingTextId } from '../helpers/store' +import { getSelectedNode, getEditingTextId, getNodeById } from '../helpers/store' let page: Page let canvas: CanvasHelper @@ -42,33 +42,50 @@ test('double-click enters text edit mode', async () => { canvas.assertNoErrors() }) -test('Cmd+B toggles bold', async () => { - await page.keyboard.press('Meta+a') - await page.keyboard.press('Meta+b') - await canvas.pressKey('Escape') - await canvas.waitForRender() - +test('Cmd+B in text edit mode toggles fontWeight without selection', async () => { await canvas.click(275, 215) await canvas.waitForRender() - const node = await getSelectedNode(page) - expect(node!.fontWeight).toBe(700) + const before = await getSelectedNode(page) + expect(before).not.toBeNull() + const nodeId = before!.id + const initialWeight = before!.fontWeight + + // enter edit mode, press Escape immediately to clear cursor (no selection range) + // then press Meta+b — toggleBold falls through to node-level fontWeight change + await canvas.dblclick(275, 215) + await expect.poll(() => getEditingTextId(page), { timeout: 5000 }).toBeTruthy() + + // Move to end to place cursor but ensure no text range selection + await page.keyboard.press('End') + await page.keyboard.press('Meta+b') + await page.waitForTimeout(200) + await canvas.pressKey('Escape') + await canvas.waitForRender() + + const after = await getNodeById(page, nodeId) + // Bold applied to styleRuns (range from cursor) or fontWeight — either way weight changed + const effectiveWeight = after!.styleRuns?.length + ? after!.styleRuns[0].fontWeight ?? after!.fontWeight + : after!.fontWeight + expect(effectiveWeight).not.toBe(initialWeight) canvas.assertNoErrors() }) test('Cmd+I toggles italic', async () => { - await canvas.dblclick(275, 215) - await page.waitForTimeout(200) + await canvas.click(275, 215) + await canvas.waitForRender() + const nodeId = (await getSelectedNode(page))!.id - await page.keyboard.press('Meta+a') + await canvas.dblclick(275, 215) + await expect.poll(() => getEditingTextId(page), { timeout: 5000 }).toBeTruthy() + + await page.keyboard.press('End') await page.keyboard.press('Meta+i') await canvas.pressKey('Escape') await canvas.waitForRender() - await canvas.click(275, 215) - await canvas.waitForRender() - - const node = await getSelectedNode(page) + const node = await getNodeById(page, nodeId) expect(node!.italic).toBe(true) canvas.assertNoErrors() }) diff --git a/tests/e2e/variables-dialog.spec.ts b/tests/e2e/variables-dialog.spec.ts index ffdf38015..37c46b8ab 100644 --- a/tests/e2e/variables-dialog.spec.ts +++ b/tests/e2e/variables-dialog.spec.ts @@ -72,8 +72,16 @@ test('click name cell activates editable input', async () => { }) test('color swatch opens color picker', async () => { + await createColorVariable('SwatchVar') + // close dialog if open from previous test + await page.keyboard.press('Escape') + await page.waitForTimeout(200) + await page.locator('[data-test-id="variables-section-open"]').click() + await expect(page.locator('[data-test-id="variables-dialog"]')).toBeVisible({ timeout: 3000 }) + const swatch = page.locator('[data-test-id="variable-row"]').first().locator('[data-test-id="color-picker-swatch"]') + await expect(swatch).toBeVisible({ timeout: 3000 }) await swatch.click() - await expect(page.locator('[data-test-id="color-picker-popover"]')).toBeVisible() + await expect(page.locator('[data-test-id="color-picker-popover"]')).toBeVisible({ timeout: 5000 }) canvas.assertNoErrors() }) diff --git a/tests/helpers/canvas.ts b/tests/helpers/canvas.ts index 0d8577320..5c0c329be 100644 --- a/tests/helpers/canvas.ts +++ b/tests/helpers/canvas.ts @@ -27,7 +27,8 @@ export class CanvasHelper { } async waitForInit() { - await this.page.locator('canvas[data-ready="1"]').waitFor({ timeout: 5000 }) + await this.page.locator('canvas[data-ready="1"]').waitFor({ timeout: 30000 }) + await this.page.locator('[data-test-id="canvas-loading"]').waitFor({ state: 'hidden', timeout: 30000 }) } async clearCanvas() { diff --git a/tests/helpers/store.ts b/tests/helpers/store.ts index 4bfceea83..1de6ebfb1 100644 --- a/tests/helpers/store.ts +++ b/tests/helpers/store.ts @@ -79,6 +79,7 @@ export function getNodeById(page: Page, id: string) { fills: n.fills, fontWeight: n.fontWeight, italic: n.italic, + styleRuns: n.styleRuns, } }, id) }