@boopkit/cap-web 0.5.6 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,180 @@
1
+ ---
2
+ name: boop-web-testing
3
+ description: "When you need to write or run tests for the Express server — services, controllers, middleware, and HTTP routes — or to confirm in a browser that a change to something a person looks at does what its issue said, since a green suite proves behaviour but not what renders."
4
+ metadata:
5
+ category: test
6
+ ---
7
+ > **Read `PROJECT.md`, beside this file, first.**
8
+ > It records how this project applies the guidance below, and where it deliberately differs.
9
+ > Where the two conflict, `PROJECT.md` wins.
10
+ > Record project-specific rules there, never in this file.
11
+
12
+ # Testing: Express App (Web & APIs)
13
+
14
+
15
+ Parent skill: `boop-testing-testing` (philosophy, generic approach)
16
+
17
+ ---
18
+
19
+ ## Running Tests
20
+
21
+ ```bash
22
+ npm test # Full suite
23
+ npx jest tests/services/foo.test.ts --no-coverage # Single file
24
+ npx jest tests/services/foo --no-coverage # Pattern match
25
+ npx jest tests/services/foo.test.ts -t "handles replay" # By test name
26
+ ```
27
+
28
+ The focused forms are safe to reach for: the concurrency and per-worker memory settings live in `jest.config.js`, not in the `test` script, so `npx jest <path>` runs under the same settings the full suite does. Which of these you should be running on a given piece of work is a separate question — see "Which tests to run, and when" in the parent skill.
29
+
30
+ **Prerequisites**: integration tests run against the project's real backing services rather than mocks, so whatever the app talks to at runtime — the database first of all — has to be reachable from the machine running the tests. Which services those are depends on the capabilities the project has installed; its README and environment template are the authority.
31
+
32
+ ## Test File Layout
33
+
34
+ ```
35
+ tests/
36
+ helpers/ # shared setup, factories, harnesses (see below)
37
+ services/
38
+ foo.test.ts # Unit tests (mocked deps)
39
+ foo.integration.test.ts # Integration tests (real services)
40
+ controllers/
41
+ itemsAPI.test.ts # HTTP route tests
42
+ middleware/
43
+ utils/
44
+ ```
45
+
46
+ - `.test.ts` for Jest auto-discovery. Keep the mocked tests and the real-service tests for the same subject in **separate files**: `jest.mock` is hoisted above the imports and applies to every test in the module, so one file cannot both stub a dependency and exercise the real one. `.integration.test.ts` is the conventional name for the half that uses the real services.
47
+ - Group by domain: `services/`, `controllers/`, `middleware/`
48
+
49
+ ## Test Helpers
50
+
51
+ Shared test setup belongs in one place under `tests/`, not re-derived in each spec file: booting the app, creating the entities a test needs and tearing them down again, and harnesses that make an awkward dependency — a broker, a queue, a rendered template — drivable from a test.
52
+
53
+ **Read what the project already has before you write one.** These helpers are not scaffolded and cannot be, because they follow whichever capabilities and domain the project has. Every project grows its own set, and the shape varies from one shared module to a directory of purpose-built ones. Reuse what fits and extend it, rather than starting a private copy inside your test file. A helper used by exactly one test is not a helper — leave it in the test.
54
+
55
+ When you do add one, keep two properties. A factory returns a real, persisted entity rather than a hand-built object literal, so the test exercises the same shape production does. And whatever creates data owns its teardown, so one failed run does not poison the next.
56
+
57
+ ## Integration Test Data Isolation
58
+
59
+ Integration tests share their backing services with every other integration test, and with whatever the last run left behind. Three habits keep them from interfering with each other:
60
+
61
+ - **Namespace whatever you create.** A per-run prefix (`_test/${Date.now()}`, a UUID) keeps parallel runs and repeat runs from colliding on unique constraints.
62
+ - **Track created records and remove them in `afterAll`**, each cleanup wrapped in its own `try/catch` so one failure doesn't strand the rest.
63
+ - **Scope instead of deleting where deletes are expensive.** Some stores — append-only logs, analytics columnstores, anything with asynchronous merge semantics — make row deletion slow or eventually-consistent. Isolate those by writing under a test-specific tenant or key and leave the rows in place.
64
+
65
+ ## HTTP Route Tests
66
+ Test Express routes via **real HTTP requests** (`fetch()`) against a running local server. This exercises the full middleware chain (auth → controller → service → DB → response).
67
+
68
+ Why real HTTP over supertest: supertest skips TCP/listen and can mask middleware ordering or config bugs. Auth middleware is a critical path worth testing through, not around.
69
+
70
+ **Setup:**
71
+ ```typescript
72
+ const BASE_URL = process.env.TEST_BASE_URL || 'http://localhost:3000';
73
+ const TEST_API_KEY = process.env.TEST_API_KEY; // however this project issues API keys
74
+ const authHeaders = { 'Authorization': `Bearer ${TEST_API_KEY}`, 'Content-Type': 'application/json' };
75
+ ```
76
+
77
+ **What to cover:** auth rejection (401/403), request validation (400), the happy-path response shape, and the authorization boundary — that one account cannot reach another's data. If the project gates routes on anything further (feature flags, roles, plan tiers), cover the closed gate too, and assert the status code it actually returns rather than the one that seems right.
78
+
79
+ **Prereqs:** a local server running against a reachable database, a durable test account, and whatever credential the auth middleware expects (`TEST_API_KEY` by convention).
80
+
81
+ ## Ad-Hoc Testing Tools
82
+
83
+ Jest is what runs in CI and before a commit. Alongside it, projects grow small CLIs for the things a test suite is the wrong shape for: generating fixture data, poking a live local server, checking what actually landed in the database.
84
+
85
+ Boop scaffolds one — `npm run db:query` (database capability) — for reading the local database without opening Prisma Studio. Anything beyond that is project-grown; `package.json` scripts and `src/scripts/` are where to look for what yours has, and `--help` on each is faster than reading its source.
86
+
87
+ These are exploratory aids. They are not part of the automated suite, and none of them substitutes for a Jest test that can go red on its own.
88
+
89
+ ## Verifying a change someone will look at
90
+
91
+ When a change alters something a person looks at, such as a page, a form, a list, an empty state or a message, finish by driving it in a browser against seeded data.
92
+ **A passing suite proves the behaviour. Only the rendered page proves that a person can reach it.**
93
+
94
+ A change can be correct and still fail the person it was built for.
95
+ A confirmation renders six screens below the fold; an error appears at the bottom of a form while the reader looks at the top; a button's label does not say what it does; a control lands on the wrong one of two pages.
96
+ In each, the code does what it should and its test passes, because a test asserts that the confirmation renders and none asserts where on the page it lands.
97
+ In one downstream project a change shipped with over eight thousand passing tests and a clean typecheck, and twenty minutes of driving it found a P1: the confirmation that was the only defence against a repeated submit rendered more than four thousand pixels down the page, so the reader saw a form redisplay unchanged and pressed submit again.
98
+
99
+ So the question here is not whether it works, which the suite answered, but **whether the person this is for would get what the issue promised.**
100
+ Get the suite green first, or you will spend the session diagnosing a failure a test would have named in seconds.
101
+ This is no substitute for tests, and it does not re-prove logic a unit test already covers.
102
+
103
+ ### The rendered page is the authority for what a page shows
104
+
105
+ Before you say a surface works or is broken, sign in as the person it is for and render it.
106
+ A database query and a code trace each describe something next to the page, not the page.
107
+ In the same project, a query found the value a fix had written and the fix was called done, while the view read a different, empty field and still showed the old text; a later fix was then called broken from a code trace, and the render showed it worked.
108
+
109
+ - Verify the broad claim the change makes across the whole surface, not only the row you changed.
110
+ - Before handing to the operator, ask what they would catch on it, and go and find that first.
111
+ - A sub-agent's "verified on real data" is a lead to check, not a verdict.
112
+
113
+ A surface you are making no claim about needs no render.
114
+
115
+ ### Stand up an environment with the states the surface will meet
116
+
117
+ - **Never the shared development database or a deployed environment.**
118
+ Use a throwaway database and a server started from the worktree whose code you are verifying.
119
+ A server running another checkout's code produces a page that looks plausible and is wrong, so confirm the server process's working directory is your worktree.
120
+ - **Rebuild the compiled stylesheet first.**
121
+ It is usually gitignored, so a fresh worktree lacks it or holds a build from before the branch's CSS, and nothing warns you: the page still renders, and you judge a layout you did not ship.
122
+ - **Seed the variety of states.**
123
+ A fault that shows only in an empty, single, partial, finished or failed state is invisible while the database holds one well-formed example.
124
+ Reuse a seed the project already has that writes those states.
125
+ When a feature brings a new kind of list, roster, directory or inbox, scope a seed-data spec beside the feature spec, naming the permutations it must write; the two can run as parallel loops, neither gating the other, so the feature develops against the seed and exposes shapes the seed missed.
126
+ - **Run the background workers the pages depend on.**
127
+ A web process alone leaves the jobs a page waits on sitting in their queue, and the page eventually reports a failure that nothing logs as an error.
128
+
129
+ ### Drive it
130
+
131
+ Use the browser tools against the real pages: read the page, fill the real inputs, press the real buttons.
132
+ A bare HTTP request to the route is not a check of the surface, because what renders is the thing under test.
133
+ Walk the issue's Done-when one line at a time, and for each ask what a person would see, not whether the write happened.
134
+
135
+ These traps each cost a downstream session time:
136
+
137
+ - **Page-text extraction reports screen-reader-only text as if it were visible.**
138
+ Confirm anything you rely on with a screenshot or by measuring the element.
139
+ - **Presence is not position.**
140
+ When something must catch the reader's attention, such as a confirmation, an error or a warning, measure where it lands rather than confirming it exists:
141
+ ```js
142
+ const r = el.getBoundingClientRect();
143
+ ({ top: Math.round(r.top), aboveFold: r.top < window.innerHeight })
144
+ ```
145
+ - **A Chromium capture can miss a mobile overflow caused by a native form control.**
146
+ Chromium shrinks a `<select>` with long options; Safari sizes it to its longest option, so a picker that fitted 390px and 320px captures overflowed on an iPhone.
147
+ Where `<select>`, date or file inputs sit in a narrow layout, reason about the layout's structure, prefer fixes that hold in any browser (`min-width: 0` on flex children, a column layout, `max-width: 100%`), and have the change confirmed on a real device.
148
+ - **`:focus` did not match under a headless Playwright probe**, measured once: a focused input read back its unfocused border colour, including one whose focus rule had worked for months.
149
+ Read focus and hover rules from the compiled stylesheet instead, and compare any probe with a sibling known to work, so that a false result shows itself.
150
+ - **The seed may not hold the case you need.**
151
+ Rather than hunting for a record that happens to match, change one you control so that it does; that tests your change and the path beneath it in one move.
152
+
153
+ ### Judge what you see
154
+
155
+ Once a flow works, whether it reads well is a separate judgement.
156
+ When the project has the ux capability, run `boop-ux-purpose-judgement` for whether each thing on the page belongs there, then `boop-ux-visual-judgement` for how it looks and scans.
157
+
158
+ - **Read each page, message or notification as its recipient would:** addressed to its reader, warm and professional, free of internal identifiers and jargon, and carrying nothing that reader should not see, such as the operator's budget.
159
+ Wording that confuses you will confuse the person it is for.
160
+ - **When a count or a set appears on more than one surface**, such as a badge in the navigation, a summary on a hub and the rows of a list, put the surfaces side by side.
161
+ A difference between them is the defect, and comparing them finds what a test of one surface cannot.
162
+ - **When the page and the data could disagree, look at the data** with the project's query tool (`npm run db:query` above).
163
+ A row that repeats one value five times is either a rendering fault or a storage fault, and the two have different owners, so establish which before you file it.
164
+
165
+ ### Record and tear down
166
+
167
+ **Findings are observations, not commits.**
168
+ Capture what you saw and file it; do not start fixing mid-run, because editing the code restarts the server and destroys the state you are standing in.
169
+
170
+ File each finding as its own issue, saying what the person sees, why it matters, and what good looks like.
171
+ Put a finding caused by the work under test beneath that work's epic, and file a pre-existing one standalone, saying in its body how you established it was not this branch: a seed fault blamed on the branch sends the next person to the wrong code.
172
+
173
+ Then stop the server you started and drop the throwaway database, and confirm that the port is free and the database is gone rather than assuming the teardown worked.
174
+
175
+ ## References
176
+
177
+ - `boop-testing-testing` — testing philosophy and the generic approach
178
+ - `boop-testing-tdd-light` — the red/green loop for a single change
179
+ - The project's own shared test setup under `tests/` — read it before adding another helper
180
+ - `jest.config.js` at the project root — test discovery, transform, and match config
@@ -11,7 +11,7 @@ metadata:
11
11
 
12
12
  # Web UI Design Principles
13
13
 
14
- For controller design (the code that handles requests and renders views), apply the core principles in `design.controllers.skill.md` from the `services` capability: thin dispatch, no business logic in controllers, services handle the work. This skill adds web-specific guidance on top: view rendering, template structure, CSS patterns.
14
+ For controller design (the code that handles requests and renders views), apply the core principles in `design.controllers.skill.md` from the `services` capability: thin dispatch, no business logic in controllers, services handle the work. This skill adds web-specific guidance on top: view rendering, template structure and markup patterns.
15
15
 
16
16
  ## Core Philosophy
17
17
  When building UI components, align with the **Spartan Code Philosophy** (see `boop-coding-development`):
@@ -20,44 +20,15 @@ When building UI components, align with the **Spartan Code Philosophy** (see `bo
20
20
  - Resist adding extra styling, animations, or features beyond what's requested
21
21
  - If additional UI enhancements seem beneficial, discuss with the team first
22
22
 
23
- ## Semantic CSS Approach
24
- We use **semantic CSS classes** that describe the purpose of elements rather than their appearance:
23
+ ## CSS
24
+ Before you add or change a class, a selector or a stylesheet rule, read [`boop-web-css`](../boop-web-css/SKILL.md).
25
+ It states how classes are named and nested, rules out BEM, and says when to reuse a class.
26
+ The class names in the markup patterns below follow it.
25
27
 
26
- **Good (semantic):**
27
- ```html
28
- <h1 class="page-title">Dashboard</h1>
29
- <p class="page-subtitle">Welcome back</p>
30
- <button class="btn-primary">Save Changes</button>
31
- <div class="policy-item">...</div>
32
- ```
33
-
34
- **Avoid (presentational):**
35
- ```html
36
- <h1 class="text-2xl font-bold text-gray-900">Dashboard</h1>
37
- <p class="text-sm text-gray-600 mt-2">Welcome back</p>
38
- <button class="bg-blue-500 text-white px-4 py-2 rounded">Save Changes</button>
39
- ```
40
-
41
- ### Implementation
42
- - Define semantic class names in `styles.css` using nested structure
43
- - Apply Tailwind utility classes with `@apply` directive inside semantic classes
44
- - Use semantic classes in HTML/Handlebars templates
45
- - Group related styles together with CSS nesting
46
-
47
- **Example:**
48
- ```css
49
- .settings-page {
50
- @apply px-6 py-4;
51
-
52
- .settings-block {
53
- @apply mb-8 bg-white border border-gray-200 rounded-lg p-6;
54
- }
55
-
56
- .block-heading {
57
- @apply text-lg font-semibold text-gray-900 mb-4;
58
- }
59
- }
60
- ```
28
+ ## State labels and action verbs
29
+ Before you name a state, choose its tone or signal, or write the label of a button or link, read the "Status communication" and "Affordance signaling" sections of `boop-ux-visual-judgement`, if the project has the `ux` capability.
30
+ They say to take each state's label, tone and signal from one shared table, and to use one verb for an action on every surface.
31
+ Whether or not it does, keep the project's state table and chosen verbs in its reference pages, and check them before adding a new state word or action label.
61
32
 
62
33
  ## Modular Templates and Partials
63
34
  Structure templates for reusability while maintaining simplicity:
@@ -83,28 +54,8 @@ When considering whether to reuse existing components:
83
54
  - **DON'T make** components overly configurable to handle every edge case
84
55
  - **DO create** a second simpler component if the existing one requires too many parameters or conditions
85
56
 
86
- ### Handlebars Helpers
87
- `@boopkit/cap-web` includes the [`handlebars-helpers`](https://github.com/helpers/handlebars-helpers) library (~130 helpers) plus custom formatting helpers.
88
-
89
- **Standard helpers (from handlebars-helpers) — most useful:**
90
- - Comparison: `gt`, `lt`, `gte`, `lte`, `eq`, `is`, `not`, `compare`
91
- - Logical: `and`, `or`, `not`
92
- - String: `capitalize`, `trim`, `replace`, `startsWith`, `contains`
93
- - Array: `first`, `last`, `sort`, `forEach`, `join`
94
- - Math: `add`, `subtract`, `multiply`, `divide`, `ceil`, `floor`, `round`
95
-
96
- **Custom helpers (from @boopkit/cap-web):**
97
- - `fmt_dollars` — format number as currency: `{{fmt_dollars 1234}}` → `$1,234` (optional `sigfigs` for data viz: `{{fmt_dollars 12345 sigfigs=4}}` → `$12,350`)
98
- - `fmt_percent` — format as percentage: `{{fmt_percent 45}}` → `45%`
99
- - `fmt_date` — human-readable date: `{{fmt_date createdAt}}` → `March 28, 2026`
100
- - `markdown` — render markdown to HTML: `{{markdown text}}` or `{{#markdown}}...{{/markdown}}`
101
- - `icon` — inline SVG icon from heroicons: `{{icon "user" "24/outline"}}` or with CSS class: `{{icon "check" "24/solid" class="w-5 h-5 text-green-600"}}`. Styles: `24/outline`, `24/solid`, `20/solid`, `16/solid`. Icons are read from `node_modules/heroicons/` and cached.
102
- - `json` — JSON stringify: `{{json obj}}`
103
- - `truncate` — truncate string: `{{truncate text 100}}`
104
- - `urlencode` — percent-encode a value for a query parameter: `<a href="/signup?next={{urlencode next}}">`. Handlebars escapes an interpolated value for HTML, not for a URL, so `?next={{next}}` renders a return path of `/a&b=c` as `next=/a&amp;b&#x3D;c` and the browser reads `next=/a`. Use this helper for **every** value that lands in a query string; it encodes rather than validates, so whatever gates the value keeps doing that job.
105
- - `upper` / `lower` — case conversion
106
- - `eq` — strict equality check (also available from handlebars-helpers)
107
-
57
+ ### Template logic
58
+ The Handlebars helpers the web capability registers are listed in [`reference/web-ui.ref.md`](../../reference/web-ui.ref.md#helpers).
108
59
  Keep template logic minimal—complex logic belongs in the controller/service layer.
109
60
 
110
61
  ## UI Structure Patterns
@@ -113,10 +64,10 @@ Keep template logic minimal—complex logic belongs in the controller/service la
113
64
  Follow consistent page patterns:
114
65
  ```html
115
66
  <div class="[page-type]-page">
116
- <div class="page-header mb-6">
117
- <h1 class="page-title">Page Title</h1>
118
- <p class="page-subtitle">Brief description of the page</p>
119
- </div>
67
+ <header class="header">
68
+ <h1 class="title">Page Title</h1>
69
+ <p class="subtitle">Brief description of the page</p>
70
+ </header>
120
71
 
121
72
  <section class="[content-section-class]">
122
73
  <!-- Page content -->
@@ -128,60 +79,37 @@ Follow consistent page patterns:
128
79
  Use consistent form patterns:
129
80
  ```html
130
81
  <form method="POST" action="/path" class="form">
131
- <div class="form-group horizontal">
132
- <label for="field-id" class="form-label">Field Name</label>
133
- <input id="field-id" name="fieldName" type="text" class="form-input">
82
+ <div class="field horizontal">
83
+ <label for="field-id">Field Name</label>
84
+ <input id="field-id" name="fieldName" type="text">
134
85
  </div>
135
-
136
- <div class="form-actions">
86
+
87
+ <div class="actions">
137
88
  <button type="submit" class="btn-primary">Save</button>
138
- <button type="button" class="btn-cancel">Cancel</button>
89
+ <button type="button" class="btn-secondary">Cancel</button>
139
90
  </div>
140
91
  </form>
141
92
  ```
142
93
 
143
94
  ### Component Structure
144
- Group related elements with semantic containers:
95
+ Group related elements with semantic containers, a block class on the outside and short child names inside it:
145
96
  ```html
146
97
  <div class="policy-list">
147
- <div class="policy-item">
148
- <div class="policy-header">
149
- <h2 class="policy-name">Policy Name</h2>
98
+ <div class="policy{{#if archived}} archived{{/if}}">
99
+ <div class="header">
100
+ <h2 class="name">Policy Name</h2>
150
101
  <!-- controls -->
151
102
  </div>
152
- <p class="policy-description">Description text</p>
103
+ <p class="description">Description text</p>
153
104
  </div>
154
105
  </div>
155
106
  ```
156
107
 
157
- ## Best Practices
158
-
159
- ### Naming Conventions
160
- - Use lowercase with hyphens: `page-title`, `btn-primary`, `policy-item`
161
- - Be descriptive of purpose, not appearance: `block-heading` not `large-bold-text`
162
- - Group related classes with common prefixes: `policy-item`, `policy-header`, `policy-name`
163
-
164
- ### CSS Organization
165
- - Group page-specific styles under page class selector
166
- - Use nesting to show hierarchy and relationships
167
- - Add comments to mark major sections: `/** === Settings Page === **/`
168
- - Keep specificity low—avoid deep nesting unless necessary
169
-
170
- ### Reuse Strategy
171
- 1. **First**, check if an existing semantic class fits your needs
172
- 2. **If close**, use the existing class—don't create near-duplicates
173
- 3. **If not quite right**, evaluate:
174
- - Is it worth making the existing class more flexible?
175
- - Or is a new simple class clearer?
176
- 4. **Remember**: Two simple classes are better than one complex parameterized system
177
-
178
108
  ## Anti-Patterns to Avoid
179
- - ❌ Using Tailwind utility classes directly in templates (except for one-off spacing tweaks)
180
109
  - ❌ Creating overly abstract/configurable components for simple use cases
181
110
  - ❌ Deep partial nesting that makes template flow hard to follow
182
111
  - ❌ Adding UI elements or styles "just in case" they're needed later
183
112
  - ❌ Creating partials for markup that only appears once
184
- - ❌ Duplicating semantic classes with slightly different names
185
113
 
186
114
  ## "Paper-Style" Reports (Analytics & Insights)
187
115
  When designing report/analytics UIs, prefer a **paper-like** presentation:
@@ -198,7 +126,6 @@ When designing report/analytics UIs, prefer a **paper-like** presentation:
198
126
  See also:
199
127
  - `boop-coding-development` for Spartan Code Philosophy
200
128
  - [`reference/web-ui.ref.md`](../../reference/web-ui.ref.md) for the current implementation: the view engine, the CSS pipeline and the page pattern
201
- - [`reference/boop-guide.ref.md`](../../reference/boop-guide.ref.md) for the CSS extension model (`@theme` tokens, package styles, cascade overrides)
202
129
 
203
130
  ## Visually verify before declaring done
204
131
 
@@ -208,7 +135,7 @@ The flow:
208
135
 
209
136
  1. **Build the CSS bundle.** `npm run tailwind:build` (or the project's equivalent). Semantic classes compiled via `@apply` do not exist in the served bundle until it is rebuilt.
210
137
  2. **Restart the dev server if it caches the bundle at boot.** Many dev servers read the compiled CSS once at startup and a file watcher that ignores CSS will not pick up the rebuild.
211
- 3. **Capture the affected page** at desktop (1280px) and mobile (390px) widths via a headless capture tool — for example the `scripts/ux-eval-capture.ts` script `cap-ux` ships, or the project's equivalent.
138
+ 3. **Capture the affected page** at desktop (1280px) and mobile (390px) widths with `npm run ux:capture -- <path> <label>`, the Playwright capture script `cap-web` ships, or the project's equivalent.
212
139
  4. **Read the captures.** Confirm the new fields appear, the layout reads coherently, and nothing collapsed or stacked in a way the structural tests cannot see.
213
140
 
214
141
  If the capture surfaces a layout issue, fix it before closing the issue, even if all unit tests pass. A green test suite is not evidence that the surface looks right; only the capture is.