From 159d2cff0663031258e24e5cdd820c726a5a80e1 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Tue, 21 Apr 2026 18:50:54 -0600 Subject: [PATCH] feat(chess/ui,e2e): add data-testid to PreviewPane + scaffold visual-builder e2e spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tiny preview-pane data-testid added for Playwright targeting (T25 setup), plus an initial e2e spec file covering the visual-mode authoring flow — to be expanded in T25. Spec covers happy path: open editor, toggle to Visual, add on-move from palette, configure nested add-to-attribute, verify preview narrative renders. Real runtime execution against the chess dev server is Wave 4 work. --- .../chess/e2e/custom-modifier-visual.spec.ts | 130 ++++++++++++++++++ .../ui/visual-builder/preview/PreviewPane.tsx | 2 +- 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 packages/chess/e2e/custom-modifier-visual.spec.ts diff --git a/packages/chess/e2e/custom-modifier-visual.spec.ts b/packages/chess/e2e/custom-modifier-visual.spec.ts new file mode 100644 index 0000000..94b2cf2 --- /dev/null +++ b/packages/chess/e2e/custom-modifier-visual.spec.ts @@ -0,0 +1,130 @@ +import { test, expect, type Page } from '@playwright/test'; + +const CUSTOM_LIBRARY_KEY = 'houserules:custom-modifiers:v1'; +const EDITOR_MODE_KEY = 'houserules:custom-modifier-editor-mode:v1'; + +async function freshLobby(page: Page): Promise { + await page.goto('/'); + await page.evaluate( + ({ customKey, modeKey }) => { + localStorage.removeItem(customKey); + localStorage.removeItem(modeKey); + }, + { customKey: CUSTOM_LIBRARY_KEY, modeKey: EDITOR_MODE_KEY }, + ); + await page.reload(); + await page.waitForSelector('[data-testid="page-home"]'); +} + +test.describe('T25 — Custom modifier visual builder e2e', () => { + test.beforeEach(async ({ page }) => { + await freshLobby(page); + }); + + test('visual authoring flow: create, save, load', async ({ page }) => { + // 1. Open editor + await page.getByTestId('profile-picker').selectOption('custom'); + await expect(page.getByTestId('per-type-panel-paste').or( + page.locator('[role="dialog"], .fixed.inset-0').first(), + )).toBeVisible({ timeout: 3000 }); + + await page.getByTestId('open-custom-modifier-editor').click(); + await expect(page.getByTestId('custom-modifier-editor')).toBeVisible(); + + // 2. Toggle to visual mode + await page.getByTestId('custom-modifier-editor-mode-visual').click(); + + // Assert visual mode components are visible + await expect(page.getByTestId('palette-btn-on-move')).toBeVisible(); + await expect(page.getByTestId('preview-pane')).toBeVisible(); + + // Give it a name + await page.getByPlaceholder('Modifier Name').fill('Visual Test Modifier'); + + // 3. Add on-move primitive + await page.getByTestId('palette-btn-on-move').click(); + + // It should appear in the block list + const onMoveBlock = page.getByTestId('block-card-on-move'); + await expect(onMoveBlock).toBeVisible(); + await expect(onMoveBlock.getByText('On Move')).toBeVisible(); + + // 4. Add add-to-attribute inside on-move + // Open the block to show dropzones + await onMoveBlock.click(); + await page.getByTestId('palette-btn-add-to-attribute').click(); + + // Fill in the add-to-attribute form fields + // Target the specific inputs inside the visual builder + const attrInput = page.locator('[data-testid="primitive-add-to-attribute-attr-input"]').first(); + await attrInput.fill('Hp'); + + // The delta field is typically a generic input without a testid right now, + // so we target the first number input in the inspector panel + const deltaInput = page.locator('input[type="number"]').first(); + await deltaInput.fill('1'); + + // 5. Verify preview narrative + const previewPane = page.getByTestId('preview-pane'); + await expect(previewPane).toContainText('When this piece moves'); + await expect(previewPane).toContainText('add 1 to Hp'); + + // Take screenshot of composed state + await page.screenshot({ path: '.sisyphus/evidence/task-25-visual-composed.png' }); + + // 6. Save to library + await page.getByTestId('custom-save').click(); + await expect(page.getByText('Custom modifier saved to library')).toBeVisible(); + + // 7. Reload page + await page.reload(); + await page.waitForSelector('[data-testid="page-home"]'); + + // 8. Re-open editor & load + await page.getByTestId('profile-picker').selectOption('custom'); + await expect(page.getByTestId('per-type-panel-paste').or( + page.locator('[role="dialog"], .fixed.inset-0').first(), + )).toBeVisible({ timeout: 3000 }); + + await page.getByTestId('open-custom-modifier-editor').click(); + await expect(page.getByTestId('custom-modifier-editor')).toBeVisible(); + + // Should remember visual mode preference + await expect(page.getByTestId('custom-modifier-editor-mode-visual')).toHaveAttribute('aria-pressed', 'true'); + + await page.getByTestId('custom-load').click(); + await page.getByText('Visual Test Modifier').click(); + + // 9. Verify loaded structure + const loadedBlock = page.getByTestId('block-card-on-move'); + await expect(loadedBlock).toBeVisible(); + await expect(loadedBlock.getByText('On Move')).toBeVisible(); + + // Open the block to see children + await loadedBlock.click(); + await expect(page.getByText('Add To Attribute')).toBeVisible(); + await expect(page.locator('[data-testid="primitive-add-to-attribute-attr-input"]').first()).toHaveValue('Hp'); + await expect(page.locator('input[type="number"]').first()).toHaveValue('1'); + + // 10. Negative: try to add beyond max depth (validator prevents save) + // Add conditional -> on-capture -> on-damaged + // The block list allows arbitrary adding, but validation fails + await page.getByTestId('palette-btn-conditional').click(); + const condBlock = page.getByTestId('block-card-conditional'); + await condBlock.click(); + + await page.getByTestId('palette-btn-on-capture').click(); + const captureBlock = page.getByTestId('block-card-on-capture'); + await captureBlock.click(); + + await page.getByTestId('palette-btn-on-damaged').click(); + + // The validator banner should show depth exceeded + await expect(page.getByText(/exceed 3|Fix errors in Form mode/)).toBeVisible(); + + // Save should be disabled + await expect(page.getByTestId('custom-save')).toBeDisabled(); + + await page.screenshot({ path: '.sisyphus/evidence/task-25-visual-validation-error.png' }); + }); +}); diff --git a/packages/chess/src/ui/visual-builder/preview/PreviewPane.tsx b/packages/chess/src/ui/visual-builder/preview/PreviewPane.tsx index 5db0034..7558e8a 100644 --- a/packages/chess/src/ui/visual-builder/preview/PreviewPane.tsx +++ b/packages/chess/src/ui/visual-builder/preview/PreviewPane.tsx @@ -18,7 +18,7 @@ export function PreviewPane({ descriptor }: PreviewPaneProps) { const boardContent = useMemo(() => , [descriptor]); return ( -
+