Acceptance & Testing
The pricing engine is a pure module pinned by a Vitest suite. The math that decides every
quote lives in src/pricing/engine.ts as a single side-effect-free function,
computePricing, and src/pricing/engine.test.ts locks its outputs to exact
numbers. Because the engine takes no clock, no randomness, and no IO, the same inputs always
produce the same price — so the fixtures can assert dollar amounts to the cent.
How testing works
Run the suite from the app root:
npm testThat runs vitest run (see package.json). The file src/pricing/engine.test.ts contains
30 test cases across 8 describe blocks. Tests build inputs from the real seeds —
SEED_CAFE_CONFIG, SEED_LAW_OFFICE_CONFIG, and DEFAULT_SETTINGS from
src/data/defaults.ts — and compare computePricing output against the
acceptance numbers from the spec.
The acceptance criteria split into two kinds of verification:
- Pinned as unit fixtures: AC-1, AC-2, AC-4, AC-5, AC-6, AC-7, AC-8. These are deterministic
engine outputs, so they are asserted directly in
engine.test.ts. - Verified in-browser: AC-3 (the 2-minute path), AC-9 (local run, reset/seed), and AC-10 (the role boundary). These are interaction- and environment-level guarantees that a unit test on the engine cannot reach, so they were checked by hand in the running app. AC-4’s “constants are live” behavior is pinned as a fixture and was browser-verified end-to-end (edit a constant in Settings, reopen the quote, watch the total move).
Two of the 8 describe blocks — additional engine rules and AC-8: engine purity — go beyond
the numbered criteria. The purity block reads engine.ts as text and asserts the source contains
no new Date, Date.now, Math.random, localStorage, fetch(, or node: import, then
re-runs the CAFE quote twice and asserts the two results are deeply equal.
The fixture numbers are computed exactly from the seeded constants and the §4.2 formulas. When
a value below is quoted to the cent, it matches an assertion in engine.test.ts — see
Pricing Engine for where each number comes from and
Settings & Constants for the seeded rates.
AC-1 — Seeded CAFE quote (channel letters)
Opening the seeded quote 1001 (front-lit channel letters, 4 letters × 18”, 5” returns, white acrylic, flush stud on brick, bucket access, permit on) must produce:
| Line / value | Expected |
|---|---|
| Fabrication (per-inch) | $1,368.00 (72” × $19) |
| Install | $500.00 (5.0 hrs × $100, bucket ×1.25, brick ×1.0, above the $175 min) |
| Permit | $300.00 |
| Design | $150.00 |
| Engineering / minimum lines | none |
| TOTAL | $2,318 |
| Margin badge | green at 74.7% (marginPct ≈ 0.7472) |
| Cost subtotal | $345.81 = materials $173.31 (incl. 7% waste) + labor $172.50 |
| Cost-plus reference price | $691.62 (fabCostPlus) |
| Derived chips | 5 modules/letter, 20 total, 20 W, 1 power supply |
The test asserts each lineItems entry by key (fabrication, install, permit, design),
confirms engineering and minimum are undefined, and checks the derived object matches
{ modsPerLetter: 5, jobModules: 20, totalWatts: 20, psCount: 1 }.
AC-2 — Seeded LAW OFFICE quote (dimensional)
Opening the seeded quote 1002 (dimensional cast-aluminum letters, 8 letters × 12”, standard finish, flush stud on drywall, ground/ladder access, markup 1.5) must produce:
| Line / value | Expected |
|---|---|
| Supplied letters | $856.56 (8 × $71.38 × 1.5) |
| Install | $300.00 (3.0 hrs × $100; under the 2×wholesale cap of $1,142.08; above the $175 min) |
installCapApplied | false |
| Permit / engineering / design / minimum lines | none |
Margin badge / marginPct | none (undefined) |
| TOTAL | $1,157 (total ≈ 1156.56) |
This quote reproduces the research taxonomy’s Example 2 exactly — every number above matches the taxonomy’s worked dimensional example. Dimensional quotes carry no margin badge because the supplied-letter price is a wholesale-plus-markup pass-through, not a cost-built fabrication.
AC-3 — Two-minute path
From the Quotes list, a tester can create a new front-lit quote (any text, 6 letters, 24”, otherwise defaults), enter a customer name, and download the branded PDF in ≤ 8 meaningful interactions and under 2 minutes, with the PDF containing every block specified in §4.4.
This is a UX-timing guarantee, so it was verified in-browser, not by a unit fixture. See the User Guide for the click-by-click path.
AC-4 — Constants are live
Changing price_per_inch_frontlit from 19 → 25 in Settings and reopening quote 1001 yields
fabrication $1,800 and TOTAL $2,750. The fixture builds DEFAULT_SETTINGS with that one
constant overridden and asserts both numbers; the same edit was also exercised through the
Settings screen in the browser to confirm the UI re-prices live.
AC-5 — Margin warning fires
Setting price_per_inch_frontlit to 5 turns quote 1001’s badge red. Fabrication drops to
$360 (72” × $5), the cost floor stays at $345.81, so the margin is (360 − 345.81) / 360 = 3.9%
(marginPct ≈ 0.0394) and badge === 'red'. In the app this badge is accompanied by the floor
warning text.
AC-6 — Halo & raceway rates
Three illumination/mount variants of quote 1001 are pinned:
| Variant | Fabrication (per-inch) |
|---|---|
| Halo / reverse-lit | $1,584 (72” × $22) |
| Front-lit + raceway | $1,512 (72” × $21) |
| Halo + raceway together | $1,584 — halo takes precedence over raceway |
The raceway also adds a material line inside the cost-plus reference, not the displayed
price: the fixture computes raceway material at ≈ $122.65 and confirms the raceway cost
subtotal exceeds the flush-mount subtotal by ≈ $131.23, while the displayed per-inch fabrication
moves only by the $21 vs $19 rate difference. The “halo takes precedence over raceway”
assertion is the engine’s [A10] tie-break rule.
AC-7 — Dimensional grid interpolation
The dimensional price grid is anchored at fixed letter heights and linearly interpolated between them:
| Input | Expected gridBasePerLetter | Notes |
|---|---|---|
| 15” cast aluminum | $112.17 | lerp(71.38, 152.96); heightClamped === false |
| 18” cast aluminum | $152.96 | exact grid anchor |
| 30” cast aluminum | $249.36 | clamped to the 6–24” range; heightClamped === true |
Heights outside 6–24” are clamped, and heightClamped is set so the UI can show a visible note.
A separate assertion confirms finish and mount multipliers compound onto the base
(71.38 × 1.5 polished × 1.35 rail).
AC-8 — Engine integrity (purity)
The Vitest suite pins the AC-1/2/4/5/6/7 numbers as fixtures and passes, and the engine is proven pure. The purity block:
- reads
engine.tsas source text and asserts it contains nonew Date,Date.now,Math.random,localStorage,fetch(, ornode:import; - runs
computePricingon the CAFE quote twice and asserts the results are deeply equal (atoEqualb).
This is what lets every other fixture assert exact cents — there is no nondeterminism to flake on.
AC-9 — Never empty, runs locally
A fresh clone followed by npm install && npm run dev renders the seeded Quotes list with both
quote 1001 and 1002, and the “Reset demo data” action restores them. This is an
environment/persistence guarantee, so it was verified in-browser rather than by a unit
fixture. See Getting Started.
AC-10 — Role boundary
No cost, rate, labor-hour, or margin number appears anywhere in the quote editor or the PDF — the only exceptions are the margin badge and the two prices in the editor’s “Pricing detail” row. The full cost breakdown renders only on Settings. This is a UI-surface guarantee about what is rendered where, so it was verified in-browser. The underlying rule is the two-table model described on the Overview and Architecture pages.
Coverage matrix
| AC | What it covers | Covered by | Status |
|---|---|---|---|
| AC-1 | Seeded CAFE quote totals & chips | unit fixture | green |
| AC-2 | Seeded LAW OFFICE quote (taxonomy Ex.2) | unit fixture | green |
| AC-3 | Two-minute / ≤ 8-interaction path | browser | green |
| AC-4 | Constants are live | unit fixture + browser | green |
| AC-5 | Margin warning fires (red badge) | unit fixture | green |
| AC-6 | Halo & raceway rates + precedence | unit fixture | green |
| AC-7 | Dimensional grid interpolation & clamp | unit fixture | green |
| AC-8 | Engine integrity / purity | unit fixture | green |
| AC-9 | Never empty, runs locally; reset/seed | browser | green |
| AC-10 | Role boundary | browser | green |
Taxonomy cross-check
The seeded quotes are validated against the research taxonomy’s worked examples:
- AC-2 reproduces the taxonomy’s Example 2 exactly — every dimensional number matches.
- AC-1’s per-inch fabrication matches exactly ($1,368). The install differs from the taxonomy narrative: Example 1 hand-waved a ~$600 install for a total of $2,418, whereas the deterministic engine yields a $500 install (5.0 hrs × $100, bucket ×1.25) and a total of $2,318 — within 4.2% of the narrative. The engine prefers a reproducible, formula-driven number over the narrative’s round figure.
All 10 acceptance criteria pass. The seven deterministic criteria are green under
npm test (30 cases), and the three interaction/environment criteria were verified in the
running app. The git tag demo-v1 marks the repository state where this full AC-1…AC-10
pass holds.