e7b01fdb360ccc221a0d2cc0f7c59b94d52c7598
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3b3bd06bbe |
test(e2e): convert the remaining admin specs, completing the POM refactor (#137)
The last nine: admin-taxonomy, admin-save-failures, admin-theme, admin-inline-category, admin-item-preview, admin-disable-customer, admin-reserved-items, admin-inventory-filters, email-templates and admin-email-settings. Every spec in tests/e2e now goes through page objects. Measured rather than asserted: - raw CSS and antd-internal locators in spec files: 0 (was 13) - local `register` helpers: 0 (was 9) - hardcoded http://localhost:5173: 0 (was 3) The antd knowledge that was spread across seven spec files is now in four page objects, each with the reason written next to it. Three of those are things no reader could have guessed from the locator: Segmented hides its real radio behind a styled label, so the input is found by role and cannot be clicked — the title attribute is the handle. The status multi-select renders an invisible role="listbox" shim beside the real list, so getByRole('option') finds something zero-sized; and a selected status renders again as a tag carrying the same title, so an unscoped getByTitle is ambiguous. Matching the visible option class avoids both. The dropdown is also opened only when closed, because antd keeps it open after a selection in multiple mode. Select popups render into a portal at the end of <body>, outside the tab panel they belong to, so a dropdown cannot be found by scoping to the panel. AdminPage.tab gained an `exact` flag for one specific collision: the Emails tab contains a rail that also renders tabs, and "Email verification" contains "Email". Without exact matching, opening the Emails tab is ambiguous with the template inside it. Two helpers stayed local rather than moving into page objects, because they belong to their file's subject rather than to a surface: favorites-filter's `favorite()`, which settles the opt-in modal and waits for its fade before the wrapper stops intercepting pointer events, and admin-theme's `luminance()`, which is a WCAG calculation and not a locator. Both now take page objects as parameters instead of reaching for locators themselves. Verified: 26/26 spec files converted, tsc clean over the whole tree, lint at the 30-warning src baseline with nothing added. Full suite 123 passed / 5 failed; all five pass in a 33/33 serial re-run, which is the load-related flakiness this suite has had throughout and not a change here — the backend hashes passwords with bcryptjs, a pure-JS implementation that blocks the event loop for every request while it runs. Closes #137 |
||
|
|
2f7268704a |
feat(admin): make the token lifetimes, cart hold and greeting configurable (#136)
Giving the customer emails a tab of their own (#135) made a family of related holes visible: an admin could edit the wording of every customer email, but not the facts that wording asserted, and two templates could not address the customer at all. Both token lifetimes were hardcoded twice. VERIFY_TOKEN_TTL_MS sat in routes/customers.ts while the verification body separately said "This link expires in 24 hours", and RESET_TOKEN_TTL_MS sat beside a body separately saying "one hour". The prose was not derived from the constant, it was a second hand-written copy of the same fact — so making the constants configurable without addressing that would have made things worse, not better: the setting moves to two hours and the email keeps confidently promising one. Both are now settings, and both templates state their lifetime through an {{expiresIn}} placeholder rendered from the setting. The cart reminder gains {{holdDuration}} for the same reason. Per-item deadlines were already inside {{itemList}}, but there was no way to write a sentence about the hold itself without hardcoding a number the cart expiry setting could change underneath the author. All three durations render through one formatDuration(), so the reset email and the cart reminder say "one hour" the same way rather than in two authors' phrasing. A fractional hour drops to minutes, because "0.5 hours" reads badly and "1.5 hours" reads worse in a sentence a customer is meant to act on. Every template now offers greeting, firstName and lastName. favoriteSold and favoriteWithdrawn previously offered only itemName and siteUrl and could not address anyone — the query behind them never selected a name, so it does now. The greeting itself is two settings, a format and a fallback, rather than the wording baked into greeting(). The fallback is separate rather than the format with the name edited out: that editing is guesswork that has to be right every time, and getting it wrong ships "Hi ," to everyone who registered while first names were still optional (#106). Those customers exist, which is why greeting() guarded the case in the first place. The admin preview renders the durations and the greeting from the live settings rather than from a static sample. The preview exists so an admin sees the email that will be sent, and a sample reading "one hour" while the setting says two is the precise failure these placeholders were added to remove. Settings are read through a new adminSettings accessor. cart_expiry_hours was previously read by an inline query in two places, each with its own `|| '24'`; with five settings and read sites across three routes and the cron job, a default written twice is a default that will eventually disagree with itself. Values are stored as text, so each definition declares how to read it back — numbers were the only kind until the greeting format arrived. The new placeholders are available but never required, so every template an admin has already saved keeps rendering and keeps sending. Also fixes updateAdminSettings announcing success for a save the server refused: it returned the 400 body as though it were the saved settings, so the form reported "Settings saved" either way. Closes #136 |