From b0fe7cc70dcaf476d6fca89ff3377a35bdc98044 Mon Sep 17 00:00:00 2001 From: Bryce Date: Fri, 26 Jun 2026 00:13:03 -0700 Subject: [PATCH] =?UTF-8?q?test(ssr):=20Phase=2010=20parity=20gate=20?= =?UTF-8?q?=E2=80=94=20characterization=20spec=20for=20the=20Client=20wiza?= =?UTF-8?q?rd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pin the current (pre-migration) behavior of the Client wizard — the largest SSR modal: seven linear steps (info → matches → contact → bank-accounts → integrations → cash-flow → other-settings) plus the parameterized bank-account sub-editor — so the upcoming engine migration preserves it. - e2e/client-wizard.spec.ts: new dialog renders info + the 7-step timeline; edit opens prefilled with a disabled code; the bank-accounts step shows the seeded account card and the add-account affordance (the crux sub-step); and an edit-through-to-save round-trip keeps the client in the grid. - test_server.clj: give the seeded TEST client a :client/name so the row is selectable in the admin grid (its base query requires :client/name); also honor a TEST_SERVER_PORT env var so a from-disk e2e JVM can run on a free port alongside a REPL-held one (the same parallel-run need the playwright config notes). Full e2e suite green (69/69) against a fresh from-disk server. Co-Authored-By: Claude Opus 4.8 --- e2e/client-wizard.spec.ts | 88 ++++++++++++++++++++++++++++++++ test/clj/auto_ap/test_server.clj | 6 ++- 2 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 e2e/client-wizard.spec.ts diff --git a/e2e/client-wizard.spec.ts b/e2e/client-wizard.spec.ts new file mode 100644 index 00000000..6d9b6fea --- /dev/null +++ b/e2e/client-wizard.spec.ts @@ -0,0 +1,88 @@ +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'); + }); +}); diff --git a/test/clj/auto_ap/test_server.clj b/test/clj/auto_ap/test_server.clj index 79c8a798..a1549e6f 100644 --- a/test/clj/auto_ap/test_server.clj +++ b/test/clj/auto_ap/test_server.clj @@ -68,6 +68,7 @@ (let [tx-result @(dc/transact conn [(assoc (test-client :db/id "client-id" :client/code "TEST" + :client/name "Test Client" :client/locations ["DT"]) :client/bank-accounts [(test-bank-account :db/id "bank-account-id" :bank-account/code "TEST-CHK" :bank-account/visible true :bank-account/name "Test Checking")]) @@ -300,8 +301,9 @@ (let [test-conn (create-test-db) tx-id (seed-test-data test-conn)] (reset! test-transaction-id tx-id) - (let [server (run-jetty (test-app) {:port 3333 :join? false})] - (println "Test server started on http://localhost:3333") + (let [port (Integer/parseInt (or (System/getenv "TEST_SERVER_PORT") "3333")) + server (run-jetty (test-app) {:port port :join? false})] + (println (str "Test server started on http://localhost:" port)) (println "Transaction entity ID:" tx-id) server)))