import { test, expect } from '@playwright/test'; // Characterization spec for the Client wizard — the largest SSR modal in the app: // seven linear steps (info → matches → contact → bank-accounts → integrations → // cash-flow → other-settings) PLUS a parameterized bank-account sub-editor reached // from the bank-accounts step. This pins the CURRENT (pre-migration) behavior so the // Phase 10 migration onto the session-backed engine preserves it. // // The seed (test_server.clj) exposes client "Test Client" (code TEST, location DT) which // owns one "Test Checking" (TEST-CHK) bank account. The admin grid's base query requires // :client/name, so the seed gives TEST a name purely so the row is selectable here. test.beforeEach(async ({ request }) => { await request.post('/test-reset'); }); // The grid lazy-loads its rows into #entity-table after the page renders. async function openClientList(page: any) { await page.goto('/admin/client'); await page.waitForSelector('#entity-table tbody tr[data-id]'); } async function openNewClient(page: any) { await openClientList(page); await page.locator('button:has-text("New Client")').first().click(); await page.waitForSelector('#wizard-form'); await page.waitForTimeout(400); } async function openEditTestClient(page: any) { await openClientList(page); await page.locator('#entity-table tbody tr', { hasText: 'Test Client' }).first() .locator('[hx-get*="/edit"]').first().click(); await page.waitForSelector('#wizard-form'); await page.waitForTimeout(400); } // Jump to a step via its timeline button (validates the current step first). Steps are // keyed in the navigate URL as `to=:` (the colon is %3A url-encoded in the attr). async function gotoStep(page: any, step: string) { await page.locator(`#wizard-form [hx-put*="to=%3A${step}"]`).first().click(); await page.waitForTimeout(500); } test.describe.configure({ mode: 'serial' }); test.describe('Client wizard (characterization)', () => { test('new dialog renders the info step with the 7-step timeline', async ({ page }) => { await openNewClient(page); const form = page.locator('#wizard-form'); await expect(form.locator('input[name="step-params[client/name]"]')).toBeVisible(); await expect(form.locator('input[name="step-params[client/code]"]').first()).toBeVisible(); await expect(form).toContainText('Locations'); // the timeline lists all seven steps for (const label of ['Info', 'Matches', 'Contact', 'Bank Accounts', 'Integrations', 'Cash Flow', 'Other Settings']) { await expect(form).toContainText(label); } }); test('edit opens prefilled with the name and a disabled code', async ({ page }) => { await openEditTestClient(page); const form = page.locator('#wizard-form'); await expect(form.locator('input[name="step-params[client/name]"]')).toHaveValue('Test Client'); // the visible code input is disabled on edit (a hidden twin carries the value on submit) const code = form.locator('input[name="step-params[client/code]"]').first(); await expect(code).toHaveValue('TEST'); await expect(code).toBeDisabled(); }); test('bank-accounts step shows the seeded account card and a new-account affordance', async ({ page }) => { await openEditTestClient(page); await gotoStep(page, 'bank-accounts'); const form = page.locator('#wizard-form'); // the seeded bank account renders as a card showing its name await expect(form).toContainText('Test Checking'); // the add-a-bank-account affordance is present await expect(form).toContainText('Add a new cash account'); }); test('editing through to the last step and saving keeps the client in the grid', async ({ page }) => { await openEditTestClient(page); // rename, then jump to the final step and save await page.locator('#wizard-form input[name="step-params[client/name]"]').fill('Test Client RENAMED'); await gotoStep(page, 'other-settings'); await page.locator('#wizard-form button[type="submit"]:has-text("Save")').first().click(); await page.waitForTimeout(1200); await openClientList(page); await expect(page.locator('#entity-table')).toContainText('Test Client RENAMED'); }); });