storefront, storefront-errors, theme, error-boundary and filters. filters.spec.ts carried the last hand-rolled copies of createCategory, createTag and createItem, and the hardcoded `http://localhost:5173` that meant changing the port in the config would have moved every test except this one. Seeding now goes through support/api, and the host it needs lives in one constant. It has to be a constant rather than the config's baseURL: beforeAll runs with worker-scoped fixtures only and cannot read a test-scoped option, which is why the URL was inlined there in the first place. New FilterDrawer object. The two rules it encodes are the ones the tests exist to pin down and neither is guessable from a locator: categories are a tree because the filter matches a node and everything filed beneath it, and tags combine with AND rather than OR, so selecting two means "must have both". The active-filter chips go on StorefrontPage rather than the drawer, because that is where they render — and the scoping matters, since the drawer carries a "Clear all" of its own that an unscoped locator also matches. StorefrontPage gains the three things the catalogue says instead of listing items. They are named together deliberately: the distinction between "No items yet" and "Couldn't load items" is the point, and several tests assert one is showing while the other is not, because telling a customer the shop is empty when the server is broken hides the outage. The theme switch and the attribute it writes are both on Header now. The switch is in the header and `data-theme` lands on <body>, so a spec previously had to know about `body` to observe the control it had just clicked. Verified: 12/12 across the four small specs, 8/8 on filters, tsc clean, lint unchanged at the 30-warning src baseline. Refs #137
64 lines
2.5 KiB
TypeScript
64 lines
2.5 KiB
TypeScript
import { test, expect } from './fixtures';
|
|
|
|
// The ?boom= trigger only exists on the dev server, which is what Playwright
|
|
// runs against. A production build drops it entirely — verified separately by
|
|
// grepping dist for the marker.
|
|
test.describe('Error boundaries', () => {
|
|
test('a throw below the root shows a message rather than a blank page', async ({ page }) => {
|
|
await page.goto('/?boom=page');
|
|
|
|
await expect(page.getByRole('heading', { name: 'Something went wrong' })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'Reload' })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'Back to the shop' })).toBeVisible();
|
|
});
|
|
|
|
test('a throw in the item grid leaves the header and theme switch usable', async ({
|
|
page,
|
|
storefront,
|
|
header
|
|
}) => {
|
|
await page.goto('/?boom=catalogue');
|
|
|
|
await expect(storefront.catalogueBoundaryHeading).toBeVisible();
|
|
|
|
// The claim this boundary exists to make: a bad item no longer takes
|
|
// navigation down with it.
|
|
await expect(header.siteTitle).toBeVisible();
|
|
await expect(header.themeToggle).toBeVisible();
|
|
|
|
// And the root boundary did not also fire — only the nearest one should.
|
|
await expect(page.getByRole('heading', { name: 'Something went wrong' })).toHaveCount(0);
|
|
});
|
|
|
|
test('a throw in the modal block leaves the storefront behind it intact', async ({
|
|
page,
|
|
header
|
|
}) => {
|
|
await page.goto('/?boom=modal');
|
|
|
|
await expect(page.getByRole('heading', { name: "Couldn't open that" })).toBeVisible();
|
|
await expect(header.siteTitle).toBeVisible();
|
|
});
|
|
|
|
test('a caught error is reported to the server', async ({ page, storefront }) => {
|
|
const reports: string[] = [];
|
|
page.on('request', (request) => {
|
|
if (request.url().includes('/api/client-errors')) {
|
|
reports.push(request.postData() ?? '');
|
|
}
|
|
});
|
|
|
|
await page.goto('/?boom=catalogue');
|
|
await expect(storefront.catalogueBoundaryHeading).toBeVisible();
|
|
|
|
// Observed on the wire rather than trusting that the reporter was called.
|
|
//
|
|
// Greater-than-zero, not exactly one: StrictMode double-invokes render in
|
|
// development, so the dev server this runs against may report twice where
|
|
// production reports once. Asserting an exact count would make the test
|
|
// fail for a reason that has nothing to do with the boundary.
|
|
await expect.poll(() => reports.length).toBeGreaterThan(0);
|
|
expect(reports[0]).toContain('"context":"catalogue"');
|
|
});
|
|
});
|