Files
ttrpg_manager/e2e/v2-surfaces.spec.ts
T
NilsBriggen ba5ae37111 V2 P5-backend + P7 + P8: conflict-safe sync, revocable invites, feats, multiclass, tests
P5 backend (the deferred items):
- conflict-aware cloud sync via optimistic concurrency: the server versions each
  backup blob (file mtime) and rejects a push whose base version is stale (409),
  so a second device can't silently overwrite a newer cloud copy. The client
  tracks its last-synced version; the SyncStatusIndicator surfaces a "Sync
  conflict" with one-click resolution (Use cloud / Keep this device), and the
  Settings push offers the same choice.
- revocable invites: owner can rotate a campaign's invite code (kills the old
  one) and remove a member (drops them + deletes their published characters).
  (Skipped editor/player roles — no enforcement target in the current model.)
- image storage: deliberately deferred (live images already stream de-duped on a
  separate channel; a backup blob-store is infra with no correctness gain).

P7 — subclass/feat/multiclass depth:
- feats are now first-class tracked records (name + effect text) in a Feats &
  Features sheet section, not buried in notes. Dexie v15 migration.
- 5e multiclass spell-slot calculator: a pure multiclassSlots() (full ×1, half
  ÷2, third ÷3 → full-caster table) + a sheet control to fill the slot table for
  multiclass casters. (Honest scope: tracking + slots, not per-subclass feature
  automation.)

P8 — test hardening: v2-surfaces e2e (feats, multiclass calc, signals bell,
World nav) + server tests (blob versioning, invite rotation, member removal) +
multiclass unit tests.

Gate green: 283 unit + 35 e2e + 2 realtime. App + server build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:34:29 +02:00

43 lines
1.9 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { createCharacter } from './helpers';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.evaluate(async () => {
indexedDB.deleteDatabase('ttrpg-manager');
localStorage.clear();
});
await page.reload();
});
test('v2 surfaces: feats tracking, multiclass slot calculator, signals bell, World nav', async ({ page }) => {
await page.getByRole('button', { name: '+ New campaign' }).first().click();
await page.locator('input[data-autofocus]').fill('V2 Test');
await page.getByRole('button', { name: 'Create' }).click();
await page.getByLabel('Primary').getByRole('link', { name: 'Characters' }).click();
await createCharacter(page, 'Vex');
// Feats: add one and confirm it becomes a tracked record.
await page.getByPlaceholder(/Great Weapon Master/).fill('Sentinel');
await page.getByPlaceholder(/Great Weapon Master/).press('Enter');
await expect(page.getByLabel('Feat name')).toHaveValue('Sentinel');
// Multiclass slot calculator: 5 full-caster levels → standard 4/3/2 table.
await page.getByText('Multiclass slot calculator').click();
await page.getByLabel('Full caster levels').fill('5');
await page.getByRole('button', { name: 'Apply slots' }).click();
await expect(page.getByLabel('Level 1 max slots')).toHaveValue('4');
await expect(page.getByLabel('Level 3 max slots')).toHaveValue('2');
// Ambient Signals bell opens and renders its panel.
await page.getByRole('button', { name: /Signals/ }).click();
await expect(page.getByText('Signals', { exact: true })).toBeVisible();
await page.keyboard.press('Escape');
// The previously-orphaned routes are now reachable from the rail's World group.
await page.getByLabel('Primary').getByRole('link', { name: 'Notes' }).click();
await expect(page).toHaveURL(/\/notes/);
await page.getByLabel('Primary').getByRole('link', { name: 'Quests' }).click();
await expect(page).toHaveURL(/\/quests/);
});