test(ssr): Phase 10 parity gate — characterization spec for the Client wizard
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 <noreply@anthropic.com>
This commit is contained in:
88
e2e/client-wizard.spec.ts
Normal file
88
e2e/client-wizard.spec.ts
Normal file
@@ -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=:<step>` (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');
|
||||
});
|
||||
});
|
||||
@@ -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)))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user