|
| 1 | +/** |
| 2 | + * Canvas Selection — verifies Figma-like selection interactions: |
| 3 | + * click to select, shift-click for multi-select, click canvas to deselect, |
| 4 | + * and Escape to clear selection. |
| 5 | + */ |
| 6 | +import { test, expect } from './fixtures'; |
| 7 | + |
| 8 | +/** Helper: add N elements to the canvas by clicking palette items. */ |
| 9 | +async function addElements(window: Awaited<ReturnType<typeof import('./fixtures').test['step']>>, count: number) { |
| 10 | + const items = window.locator('.palette-item'); |
| 11 | + for (let i = 0; i < count; i++) { |
| 12 | + await items.nth(i % (await items.count())).click(); |
| 13 | + } |
| 14 | +} |
| 15 | + |
| 16 | +test.describe('Canvas Selection', () => { |
| 17 | + test('clicking a canvas element selects it', async ({ window }) => { |
| 18 | + await addElements(window, 1); |
| 19 | + |
| 20 | + // Click the first added canvas element (skip the root wrapper) |
| 21 | + const elements = window.locator('.canvas-element'); |
| 22 | + const addedElement = elements.last(); |
| 23 | + await addedElement.click(); |
| 24 | + |
| 25 | + // Should be marked as selected |
| 26 | + await expect(addedElement).toHaveAttribute('data-selected', 'true'); |
| 27 | + }); |
| 28 | + |
| 29 | + test('clicking empty canvas area clears selection', async ({ window }) => { |
| 30 | + await addElements(window, 1); |
| 31 | + |
| 32 | + // Select the element first |
| 33 | + const element = window.locator('.canvas-element').last(); |
| 34 | + await element.click(); |
| 35 | + await expect(element).toHaveAttribute('data-selected', 'true'); |
| 36 | + |
| 37 | + // Click the canvas background (the .editor-canvas container) |
| 38 | + const canvas = window.locator('.editor-canvas'); |
| 39 | + // Click at the very bottom-right of the canvas to hit the background |
| 40 | + const box = await canvas.boundingBox(); |
| 41 | + if (box) { |
| 42 | + await window.mouse.click(box.x + box.width - 10, box.y + box.height - 10); |
| 43 | + } |
| 44 | + |
| 45 | + // Selection should be cleared |
| 46 | + const selectedElements = window.locator('.canvas-element[data-selected="true"]'); |
| 47 | + expect(await selectedElements.count()).toBe(0); |
| 48 | + }); |
| 49 | + |
| 50 | + test('pressing Escape clears selection', async ({ window }) => { |
| 51 | + await addElements(window, 1); |
| 52 | + |
| 53 | + const element = window.locator('.canvas-element').last(); |
| 54 | + await element.click(); |
| 55 | + await expect(element).toHaveAttribute('data-selected', 'true'); |
| 56 | + |
| 57 | + // Press Escape |
| 58 | + await window.keyboard.press('Escape'); |
| 59 | + |
| 60 | + // Selection should be cleared |
| 61 | + const selected = window.locator('.canvas-element[data-selected="true"]'); |
| 62 | + expect(await selected.count()).toBe(0); |
| 63 | + }); |
| 64 | + |
| 65 | + test('selecting an element shows its properties', async ({ window }) => { |
| 66 | + await addElements(window, 1); |
| 67 | + |
| 68 | + // Switch right panel to Properties |
| 69 | + const propsTab = window.locator('.editor-right-panel button.panel-tab', { hasText: 'Properties' }); |
| 70 | + await propsTab.click(); |
| 71 | + |
| 72 | + // Before selection: empty state |
| 73 | + await expect(window.locator('.empty-state')).toBeVisible(); |
| 74 | + |
| 75 | + // Select the element |
| 76 | + await window.locator('.canvas-element').last().click(); |
| 77 | + |
| 78 | + // Properties panel should now show element info |
| 79 | + const propsPanel = window.locator('.props-panel'); |
| 80 | + await expect(propsPanel).toBeVisible(); |
| 81 | + await expect(propsPanel.locator('.props-section-title', { hasText: 'Element' })).toBeVisible(); |
| 82 | + await expect(propsPanel.locator('.props-section-title', { hasText: 'Properties' })).toBeVisible(); |
| 83 | + }); |
| 84 | +}); |
0 commit comments