Files
jzzliu eb101c3e05 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).
2026-11-07 21:18:38 -07:00

2.9 KiB

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

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

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.