docs: record the favorites filter and the Node version trap
Notes that the default local Node (18.16.1) cannot run either the integration suite or Playwright, and that neither failure names the version as the cause — the integration one reads like a broken lru-cache dependency. Records the newer version's path so a single command can be run against it without switching what the user has active. Adds two e2e lessons from this change: isVisible() does not wait, so guarding an optional dialog with it loses the race and leaves an antd modal open to intercept every later click; and under fullyParallel a test that mutates a shared fixture races its siblings. Records that the storefront listing sold items is now load-bearing rather than merely tolerated, since the favorites filter deliberately shows sold favorites, and points at the favorites filter as the pattern for any future filter dimension that depends on who is asking.
This commit is contained in:
@@ -229,6 +229,21 @@ sudo docker exec -it redefined-designs-db-syn psql -U redefined -d redefined -c
|
||||
- **E2e (Playwright)**: needs backend running against a migrated DB; `cd frontend && npm run test:e2e`
|
||||
- CI (`tests.yml`) runs all three as separate jobs, each posting a pass/fail summary to Gitea's job Summary tab. The `frontend-e2e` job runs `node migrate.js up` against a Postgres service container — same migration mechanism as everywhere else, no schema duplication anywhere in the project anymore.
|
||||
|
||||
### The local Node version will not run the integration or e2e suites
|
||||
|
||||
`nvm4w` has both **18.16.1 (active by default)** and **24.13.1** installed, and the active one is too old for two of the three suites:
|
||||
|
||||
- Integration tests fail in `globalSetup` with `(0 , U.tracingChannel) is not a function` — a transitive `lru-cache` needs `diagnostics_channel.tracingChannel`, added in Node 20.2.
|
||||
- Playwright refuses outright: "Playwright requires Node.js 20 or higher."
|
||||
|
||||
Neither failure mentions the Node version as the cause, and the first one reads like a broken dependency. Rather than switching the user's active version, prepend the newer one for the single command:
|
||||
|
||||
```bash
|
||||
export PATH="/c/Users/tlamb/AppData/Local/nvm/v24.13.1:$PATH"
|
||||
```
|
||||
|
||||
Unit tests and `tsc` run fine on 18, so a green `npm test` says nothing about whether the other two suites can even start.
|
||||
|
||||
### E2E constraints — the local database is never reset
|
||||
|
||||
Integration tests truncate between cases (`resetDb()` in `tests/integration/setup/testDb.ts` — **add any new table to that TRUNCATE list**, or state leaks between tests). Playwright specs have no such hook and run against whatever is already there, which locally accumulates across every previous run. Consequences worth knowing before writing a new spec:
|
||||
@@ -237,6 +252,8 @@ Integration tests truncate between cases (`resetDb()` in `tests/integration/setu
|
||||
- **`beforeAll` runs once per worker, but a worker can be handed the same spec file in more than one batch**, re-running it against the module-cached suffix. `filters.spec.ts` therefore checks whether its fixtures already exist and returns early — without that, the second pass 409s on category names and duplicates every item, which then breaks strict-mode locators.
|
||||
- **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.
|
||||
- **`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.
|
||||
- **`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
|
||||
|
||||
@@ -249,7 +266,7 @@ Integration tests truncate between cases (`resetDb()` in `tests/integration/setu
|
||||
- **`backend/src/routes/shippingAddresses.ts` USPS OAuth token format** was implemented against the current (2026) USPS Addresses API docs at time of writing, using a JSON-body `client_credentials` request — if USPS changes their API again, this is the first place to check.
|
||||
- **`TAG_COLORS` is duplicated** between `backend/src/utils.ts` and `frontend/src/admin/Tags.tsx`. The server validates against its copy, so editing one alone makes the admin colour picker offer values that get rejected with a `400`. There's no shared module between backend and frontend in this repo to put it in.
|
||||
- **The local e2e database accumulates junk indefinitely** — every Playwright run seeds categories, tags, and items that are never cleaned up, so the admin tables and filter drawer fill with `Furniture fmsxb…` noise over time. Harmless, but `npm run db:test:down` + `db:test:up` + `migrate:up` resets it when the clutter starts getting in the way. CI is unaffected (fresh service container per run).
|
||||
- **The storefront still lists sold items** and the price-range bounds are computed across all items regardless of status. Unchanged by the filters work — deliberately left as-is, but worth revisiting if sold stock ever outnumbers available stock.
|
||||
- **The storefront still lists sold items** and the price-range bounds are computed across all items regardless of status. Deliberately left as-is, and now load-bearing: the favorites filter (#35) shows sold favorites on purpose, since an item that just sold is often what the customer came back to look at after the #34 email. Anyone wanting only purchasable stock combines the favorites toggle with the status filter. Worth revisiting if sold stock ever outnumbers available stock — but changing the default would change what a favorites view means.
|
||||
- **No admin-side filtering.** The admin inventory table shows category and tag columns but can't filter or search on them; with 100+ items that will start to hurt.
|
||||
|
||||
## Where to look first for common tasks
|
||||
@@ -259,7 +276,8 @@ 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`
|
||||
- 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 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`
|
||||
- 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.
|
||||
- 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 tag colours → `TAG_COLORS` + `tagColorFor()` in `backend/src/utils.ts`; the list is **duplicated** in `frontend/src/admin/Tags.tsx` for the override picker, and the server rejects anything outside it, so the two must be changed together
|
||||
- NPM/authentik/DSM reverse-proxy config for this app → not in this repo; documented in the broader homelab's Claude Project knowledge base, not here
|
||||
Reference in New Issue
Block a user