|
|
@@ -257,6 +257,8 @@ Integration tests truncate between cases (`resetDb()` in `tests/integration/setu
|
|
|
|
- **Tables paginate.** With dozens of accumulated rows a freshly created record often isn't on page 1; `admin-taxonomy.spec.ts` confirms tag creation through the API rather than hunting for the row.
|
|
|
|
- **Tables paginate.** With dozens of accumulated rows a freshly created record often isn't on page 1; `admin-taxonomy.spec.ts` confirms tag creation through the API rather than hunting for the row.
|
|
|
|
- **Clicking a submit button only dispatches the request.** Wait for the resulting confirmation (`'Tag added'`) before querying the API, or the read races the write. This produced a one-in-four flake until fixed.
|
|
|
|
- **Clicking a submit button only dispatches the request.** Wait for the resulting confirmation (`'Tag added'`) before querying the API, or the read races the write. This produced a one-in-four flake until fixed.
|
|
|
|
- **`isVisible()` does not wait.** It answers about *this instant*, so guarding an optional dialog with `if (await x.isVisible())` loses the race whenever the dialog is still on its way — and an antd modal left open then intercepts every later click, which surfaces as an unrelated element "not found" thirty seconds later. If the dialog is deterministic, click it unconditionally and let the locator auto-wait; only use `isVisible()` when it genuinely may never appear, and even then give it something to wait on first.
|
|
|
|
- **`isVisible()` does not wait.** It answers about *this instant*, so guarding an optional dialog with `if (await x.isVisible())` loses the race whenever the dialog is still on its way — and an antd modal left open then intercepts every later click, which surfaces as an unrelated element "not found" thirty seconds later. If the dialog is deterministic, click it unconditionally and let the locator auto-wait; only use `isVisible()` when it genuinely may never appear, and even then give it something to wait on first.
|
|
|
|
|
|
|
|
- **The 5s default `expect` timeout is too tight for anything waiting on a round-trip.** Registration is a bcrypt hash — about half a second unloaded, and well past 5s when the suite's workers all register at once. The failure surfaces on whichever test lost the race, so it looks like an unrelated flake that moves between runs. Give such assertions an explicit generous timeout; they are asserting *that* the server answered, not how fast.
|
|
|
|
|
|
|
|
- **The local database's accumulated junk eventually shows up as flake, not just clutter.** At ~450 items and ~250 customers the storefront render and the parallel bcrypt load together push these round-trips past their timeouts, and the repeated runs also trip the password-reset rate limiter. When failures start rotating between unrelated specs on each run, reset the database before debugging any of them.
|
|
|
|
- **`fullyParallel: true` means a test that mutates a shared fixture races every other test in the file.** A spec that marked an item sold broke the sibling tests reading that same item. Give any test that changes an item's state its own fixture.
|
|
|
|
- **`fullyParallel: true` means a test that mutates a shared fixture races every other test in the file.** A spec that marked an item sold broke the sibling tests reading that same item. Give any test that changes an item's state its own fixture.
|
|
|
|
|
|
|
|
|
|
|
|
## Known gaps / natural next steps
|
|
|
|
## Known gaps / natural next steps
|
|
|
@@ -280,7 +282,7 @@ Integration tests truncate between cases (`resetDb()` in `tests/integration/setu
|
|
|
|
- Change admin-configurable settings → `admin_settings` table + `backend/src/routes/adminSettings.ts` + `frontend/src/admin/Settings.tsx`
|
|
|
|
- Change admin-configurable settings → `admin_settings` table + `backend/src/routes/adminSettings.ts` + `frontend/src/admin/Settings.tsx`
|
|
|
|
- Add a new async route → wrap the handler in `asyncRoute()` from `backend/src/asyncRoute.ts`, or a failure will hang the request instead of returning 500
|
|
|
|
- Add a new async route → wrap the handler in `asyncRoute()` from `backend/src/asyncRoute.ts`, or a failure will hang the request instead of returning 500
|
|
|
|
- Change what an item row returns → `backend/src/itemSelect.ts` (one place, used by both the public and admin routes)
|
|
|
|
- Change what an item row returns → `backend/src/itemSelect.ts` (one place, used by both the public and admin routes)
|
|
|
|
- Change how a customer route is framed (modal vs page) → `frontend/src/main.tsx`. `AppRoutes` renders the route table against a *backdrop* location rather than the real one: `/account` is a modal over the page named in `location.state.background`, falling back to the storefront when there is none (a bookmark, an email link, a post-registration redirect). This is the pattern to copy for the sibling dead-end issues (#49, #50) — it keeps the URL real and linkable while making sure closing always lands somewhere. The link that opens it must pass `state={{ background: location }}`, or closing goes to the fallback instead of where the customer was.
|
|
|
|
- Change how a customer route is framed (modal vs page) → `frontend/src/main.tsx`. `AppRoutes` renders the route table against a *backdrop* location rather than the real one: `/account` is a modal over the page named in `location.state.background`, falling back to the storefront when there is none (a bookmark, an email link, a post-registration redirect). This is the pattern to copy for the sibling dead-end issues (#49, #50) — it came out of #51 — it keeps the URL real and linkable while making sure closing always lands somewhere. The link that opens it must pass `state={{ background: location }}`, or closing goes to the fallback instead of where the customer was.
|
|
|
|
- Change storefront filtering → `backend/src/itemFilters.ts` (parsing + SQL), `backend/src/routes/filters.ts` (`/api/filters`, the drawer's single fetch), `frontend/src/filters.ts` (state, URL round-trip, tree building), `frontend/src/components/FilterDrawer.tsx`, `frontend/src/components/ActiveFilterChips.tsx`
|
|
|
|
- Change storefront filtering → `backend/src/itemFilters.ts` (parsing + SQL), `backend/src/routes/filters.ts` (`/api/filters`, the drawer's single fetch), `frontend/src/filters.ts` (state, URL round-trip, tree building), `frontend/src/components/FilterDrawer.tsx`, `frontend/src/components/ActiveFilterChips.tsx`
|
|
|
|
- Add a filter dimension that depends on who is asking → follow the favorites filter (#35). The identity comes from `req.customerId` (`attachCustomer` runs globally, so it is available on the public `/api/items` too) and is passed into `buildItemFilterSql` as an explicit argument — never parsed from the query string, or a hand-edited URL could name another customer. Each route decides what to do when it cannot satisfy the filter: the storefront answers 401, the admin inventory 400, and the builder throws rather than silently dropping the clause and returning everything.
|
|
|
|
- Add a filter dimension that depends on who is asking → follow the favorites filter (#35). The identity comes from `req.customerId` (`attachCustomer` runs globally, so it is available on the public `/api/items` too) and is passed into `buildItemFilterSql` as an explicit argument — never parsed from the query string, or a hand-edited URL could name another customer. Each route decides what to do when it cannot satisfy the filter: the storefront answers 401, the admin inventory 400, and the builder throws rather than silently dropping the clause and returning everything.
|
|
|
|
- Change category/tag management → `backend/src/routes/adminCategories.ts`, `backend/src/routes/adminTags.ts`, `frontend/src/admin/Categories.tsx`, `frontend/src/admin/Tags.tsx`
|
|
|
|
- Change category/tag management → `backend/src/routes/adminCategories.ts`, `backend/src/routes/adminTags.ts`, `frontend/src/admin/Categories.tsx`, `frontend/src/admin/Tags.tsx`
|
|
|
|