PR #317 was merged while its run was still failing, so four failures landed on main: one integration and three end-to-end. All of them are consequences of #56, and none could have been caught on the dev machine, which has no database and no browser to run those suites with. The schema mirror was never regenerated. The migration added three columns to customers and src/db-kysely/schema.ts still described the table without them, which is the drift guard from #305 doing exactly what it exists for. Hand-edited to match what kysely-codegen emits — alphabetical, and Generated on the column that has a default — because regenerating properly needs a live database. The other three are the same mistake three times: a control addressed by position, and the position moved. An unscoped getByRole('checkbox') became ambiguous once the register form had two consents. A toHaveCount(2) on the account modal's switches became three. And favoriteAlertsSwitch was getByRole('switch').last(), which did not error when a switch was appended below it — it silently retargeted, toggled analytics consent instead of favourite alerts, and then failed on a text assertion in favorites.spec.ts, naming neither the file nor the control actually at fault. The reason position was ever used is that antd's Switch renders a bare role="switch" with no accessible name; the adjacent Text is a sibling, not a label. So each one now carries an explicit aria-label and is addressed by it. That is what makes them addressable from a test, and it is what a screen reader needed regardless — the fix and the accessibility improvement are the same change. The count assertion stays, but alongside naming each switch, because a count on its own would pass if two of them were swapped for each other. Two coverage gaps closed while here, both properties the compliance work in #56 depends on and neither previously asserted anywhere a customer could see: the analytics checkbox is unchecked on the register form, and the account toggle is off for a new customer. Quebec's Law 25 s.8.1 requires profiling to start off, the integration suite asserts the server half of that, and nothing asserted the half rendered on screen. The fourth Playwright entry, the logged-out header surviving a reload, is reported flaky rather than failed and passed on retry. Left alone; it is unrelated to #56 and #257 covers flakes in this suite. Verified: backend tsc clean, frontend tsc against the test config clean, production build green, both lint suites 0 errors, 478 unit tests passing. The integration and e2e suites still cannot run here, so whether this actually clears run 875's failures is for CI to say — which is the same gap that produced them. Closes #320 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
138 lines
4.6 KiB
TypeScript
138 lines
4.6 KiB
TypeScript
import { test, expect } from './fixtures';
|
|
|
|
test.describe('My Account opens as a modal', () => {
|
|
test('opens over the storefront and closes back to it, filters and all', async ({
|
|
page,
|
|
customer,
|
|
accountModal,
|
|
header
|
|
}) => {
|
|
// A filtered view, to prove closing restores where the customer actually
|
|
// was rather than a bare storefront.
|
|
await page.goto('/?max_price=50000');
|
|
await header.waitForSignedIn();
|
|
|
|
await accountModal.openFromHeader();
|
|
// Still a real, linkable URL rather than hidden view state.
|
|
await expect(page).toHaveURL(/\/account/);
|
|
|
|
await accountModal.close();
|
|
await expect(page).toHaveURL(/max_price=50000/);
|
|
});
|
|
|
|
test('the browser back button closes it, the same as the close control', async ({
|
|
page,
|
|
customer,
|
|
accountModal,
|
|
header
|
|
}) => {
|
|
await page.goto('/?max_price=50000');
|
|
await header.waitForSignedIn();
|
|
await accountModal.openFromHeader();
|
|
|
|
await page.goBack();
|
|
|
|
await expect(accountModal.dialog).toBeHidden();
|
|
await expect(page).toHaveURL(/max_price=50000/);
|
|
});
|
|
|
|
test('a direct visit renders the storefront behind it, so closing lands somewhere real', async ({
|
|
page,
|
|
customer,
|
|
accountModal,
|
|
header
|
|
}) => {
|
|
// A bookmark, or the link in a verification email. There is no page behind
|
|
// in this case, which is what used to make /account a dead end.
|
|
await accountModal.open();
|
|
|
|
await expect(header.siteTitle).toBeVisible();
|
|
|
|
await accountModal.close();
|
|
await expect(page).toHaveURL(/\/$/);
|
|
await expect(header.myAccountButton).toBeVisible();
|
|
});
|
|
|
|
test('survives a reload, since it is a route rather than view state', async ({
|
|
page,
|
|
customer,
|
|
accountModal,
|
|
header
|
|
}) => {
|
|
await page.goto('/?max_price=50000');
|
|
await header.waitForSignedIn();
|
|
await accountModal.openFromHeader();
|
|
|
|
await page.reload();
|
|
|
|
await expect(accountModal.dialog).toBeVisible();
|
|
await expect(accountModal.dialog).toContainText(customer.email);
|
|
});
|
|
|
|
test('shows the signed-in account and its settings', async ({ customer, accountModal }) => {
|
|
await accountModal.open();
|
|
|
|
await expect(accountModal.dialog).toContainText(customer.email);
|
|
// The orders table lives at /orders now. What the account view still owes
|
|
// the customer is a way to reach it.
|
|
await expect(accountModal.orderHistoryButton).toBeVisible();
|
|
// Scoped to the modal: the storefront behind it has a theme switch of its
|
|
// own, so an unscoped switch locator would be ambiguous.
|
|
//
|
|
// Three since #56 added analytics consent: marketing email, favourite
|
|
// alerts, analytics. The count is a guard against one appearing or
|
|
// vanishing unnoticed, so it is asserted alongside naming each of them —
|
|
// a count alone would pass if two were swapped for each other.
|
|
await expect(accountModal.themeSwitches).toHaveCount(3);
|
|
await expect(accountModal.marketingConsentSwitch).toBeVisible();
|
|
await expect(accountModal.favoriteAlertsSwitch).toBeVisible();
|
|
await expect(accountModal.analyticsConsentSwitch).toBeVisible();
|
|
});
|
|
|
|
// Quebec's Law 25 s.8.1 requires profiling to be off until the person turns
|
|
// it on. The server defaults the column to false and the integration suite
|
|
// asserts that; this is the half of the promise the customer can actually
|
|
// see, and the two have to agree.
|
|
test('analytics consent is off until the customer turns it on', async ({
|
|
customer,
|
|
accountModal
|
|
}) => {
|
|
await accountModal.open();
|
|
|
|
await expect(accountModal.analyticsConsentSwitch).not.toBeChecked();
|
|
});
|
|
|
|
test('deleting the account does not leave the page behind it looking signed in', async ({
|
|
page,
|
|
customer,
|
|
accountModal,
|
|
header
|
|
}) => {
|
|
await accountModal.open();
|
|
await accountModal.deleteAccount();
|
|
|
|
// The storefront is rendered behind the modal, so a session left in place
|
|
// would visibly go on offering My Account for an account that is gone.
|
|
await expect(header.signUpButton).toBeVisible();
|
|
await expect(header.myAccountButton).toBeHidden();
|
|
await expect(page).toHaveURL(/\/$/);
|
|
});
|
|
|
|
test('stays usable on a phone, with the close control in reach', async ({
|
|
page,
|
|
customer,
|
|
accountModal
|
|
}) => {
|
|
await page.setViewportSize({ width: 390, height: 664 });
|
|
await accountModal.open();
|
|
|
|
// The view is taller than the viewport, so the body scrolls rather than
|
|
// pushing the title and close control off-screen.
|
|
await expect(accountModal.closeButton).toBeInViewport();
|
|
await expect(accountModal.orderHistoryButton).toBeVisible();
|
|
|
|
await accountModal.close();
|
|
await expect(page).toHaveURL(/\/$/);
|
|
});
|
|
});
|