Add dependency-free browser test suite and README

49 tests run in headless Chrome via tests/run.sh: unit tests for the pure
modules plus integration tests that drive the real page in an iframe
(filtering, URL state, bag, limits, cross-tab sync, checkout, dialogs,
accessibility and overflow checks).
This commit is contained in:
2026-11-07 21:18:38 -07:00
parent c4c07cbc16
commit a15d0d37b1
10 changed files with 652 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
# Fieldwork Supply
A fictional outdoor-gear storefront, built as a portfolio demo. Static site, no build step, no
dependencies: plain HTML, CSS and ES modules, served by Caddy in Docker.
## Layout
```
dist/ everything that ships (this folder is copied into the image)
index.html semantic markup, skip link, landmarks
style.css design tokens + cascade layers (see the header comment)
js/
catalog.js product data; pure filter / sort / URL-state functions
cart.js pure bag logic (cents, limits, sanitising) + storage load/save
format.js money and plural helpers (integer cents, cached Intl formatters)
dom.js html`` template tag that escapes every interpolation
main.js wires the modules to the page; one delegated click handler
tests/ browser test suite (not shipped)
Dockerfile, Caddyfile, compose.yaml
```
## Run
```sh
docker compose up -d --build # http://127.0.0.1:8088, see DOCKER.md
```
or, for quick edits, serve `dist/` with anything static: `python3 -m http.server -d dist`.
## Test
```sh
tests/run.sh # headless Chrome; needs python3 and Google Chrome
```
Or open `tests/` through any static server to see the results in a browser. There is nothing to
install. The suite has unit tests for the pure modules and integration tests that load the real
page in an iframe and use it like a visitor would: filtering, sorting, shareable URLs, the bag,
quantity limits, cross-tab sync, checkout, dialogs, and accessibility and overflow checks.
## Design notes
- **State and URL.** The category and sort live in the URL (`?category=Camp&sort=low`), so views
are shareable and survive a reload. Invalid values fall back to defaults.
- **Money is integer cents** everywhere, formatted once at the edge.
- **The bag** is `{ productId: quantity }` in `localStorage` (key unchanged from v1, so existing
bags carry over). Loading sanitises it, quantities are capped at 9, and it syncs across tabs.
- **No injection.** Every interpolated value in a template is escaped; `mount()` refuses plain
strings. Combined with the Content-Security-Policy in the Caddyfile (no inline script or style),
injected markup can neither render nor run.
- **Accessibility.** Skip link, landmarks, labelled dialogs, focus kept on the control you used,
announcements for bag changes, focus rings with at least 3:1 contrast on every surface, control
borders at 3:1, and no touch target under 24px.
- **Styling.** One token set (colour, type scale, space, shape, motion) in cascade layers. Pills
are for navigation and toggles, square corners for actions and inputs.
## Known limits
- Products are rendered by JavaScript; without it the page shows a notice instead of the shop.
- Assets are not fingerprinted, so they are cached for only five minutes.
- Mouse-sized controls are 40px; they grow to 44px on touch screens.