feat(ui): per-instance modifier panel

- Export LayoutBoardView from LayoutEditor.tsx (reusable board display)
- Create PerInstancePanel.tsx: 8x8 board + per-square modifier list/form
- Wire PerInstancePanel into ModifierProfileEditor center panel
- Add bound-layout-picker select in editor header (uses LAYOUT_REGISTRY)
- 2 new e2e scenarios: no-layout prompt, attach modifier to b1 square
This commit is contained in:
Joey Yakimowich-Payne 2026-04-18 22:56:31 -06:00
commit 28e03d06fa
No known key found for this signature in database
4 changed files with 766 additions and 36 deletions

View file

@ -52,4 +52,92 @@ test.describe('Modifier Profiles', () => {
page.locator('[data-testid="modifier-editor-modal"]'),
).not.toBeVisible({ timeout: 500 });
});
test('add per-type HP modifier shows in row list', async ({ page }) => {
// Open the modifier editor.
await page.click('[data-testid="open-modifier-editor"]');
await expect(
page.locator('[data-testid="modifier-editor-modal"]'),
).toBeVisible();
// Open the inline add form.
await page.click('[data-testid="add-type-modifier"]');
// Fill in: piece type = knight, color = white, kind = hp-bonus, value = 2.
await page.selectOption('[data-testid="piece-type-select"]', 'knight');
await page.selectOption('[data-testid="color-select"]', 'white');
await page.selectOption('[data-testid="kind-select"]', 'hp-bonus');
await page.fill('[data-testid="value-input"]', '2');
// Save the modifier.
await page.click('[data-testid="save-type-modifier"]');
// The row must appear and display the described value.
await expect(
page.locator('[data-testid="type-modifier-row"]'),
).toBeVisible();
await expect(
page.locator('[data-testid="type-modifier-row"]'),
).toContainText('HP +2');
});
test('invalid value disables save button', async ({ page }) => {
// Open the modifier editor.
await page.click('[data-testid="open-modifier-editor"]');
await expect(
page.locator('[data-testid="modifier-editor-modal"]'),
).toBeVisible();
// Open the inline add form.
await page.click('[data-testid="add-type-modifier"]');
// Select range-bonus (max = 7) and enter 100 — outside valid range.
await page.selectOption('[data-testid="kind-select"]', 'range-bonus');
await page.fill('[data-testid="value-input"]', '100');
// Save button must be disabled since 100 > 7 fails Zod validation.
await expect(
page.locator('[data-testid="save-type-modifier"]'),
).toBeDisabled();
});
// ── T22: per-instance modifier panel ───────────────────────────────────────
test('no-layout state shows select-a-layout prompt', async ({ page }) => {
await page.click('[data-testid="open-modifier-editor"]');
await expect(
page.locator('[data-testid="modifier-editor-modal"]'),
).toBeVisible();
// With no layout bound, the center panel shows the empty-state prompt.
await expect(page.getByText('Select a layout first')).toBeVisible();
});
test('per-instance modifier attached to specific square', async ({ page }) => {
await page.click('[data-testid="open-modifier-editor"]');
await expect(
page.locator('[data-testid="modifier-editor-modal"]'),
).toBeVisible();
// Bind the Classic layout via the header picker.
await page.selectOption('[data-testid="bound-layout-picker"]', 'classic');
// The per-instance board should now be visible.
await expect(
page.locator('[data-testid="per-instance-board"]'),
).toBeVisible();
// Click the b1 square (white knight in the classic starting position).
await page.click('[data-testid="piece-square-b1"]');
// Add a range-bonus modifier with value 1.
await page.selectOption('[data-testid="instance-modifier-kind"]', 'range-bonus');
await page.fill('[data-testid="instance-modifier-value"]', '1');
await page.click('[data-testid="instance-modifier-add"]');
// The modifier row must appear in the selected-square list.
await expect(
page.locator('[data-testid="instance-modifier-b1-range-bonus"]'),
).toBeVisible();
});
});