Skip to content

test(strategy): stand up the testing trophy - #8

Closed
arnaud-zg wants to merge 2 commits into
fix/domain-red-suite-and-dead-repofrom
test/testing-trophy
Closed

arnaud-zg wants to merge 2 commits into
fix/domain-red-suite-and-dead-repofrom
test/testing-trophy

Conversation

@arnaud-zg

Copy link
Copy Markdown
Owner

Stacked on #7.

Summary

  • Two Vitest projects: unit (node, src/domain/**) and integration (jsdom, src/ui/**
    and src/app/**), replacing a vite.config.ts that never had a test block
  • Coverage thresholds (v8 provider): 90% on src/domain, 70% on src/ui, enforced in CI
  • Integration tests for the flows that matter: list products, add to cart, change
    quantity, remove an item, the free-shipping banner, the welcome survey's
    auto-open/save/close cycle
  • Added the ZustandWelcomeSurveyRepository spec it never had
  • Playwright, 2 smoke specs against a built vite preview: home to shop, and add to
    cart through to the checkout modal. Own CI job, report uploaded on failure
  • CONTRIBUTING.md documents TDD, with two real examples from this repo's own history,
    and the trophy shape

Writing real tests against real components found and fixed two bugs:

  • every umami?.track(...) call threw ReferenceError whenever the analytics script
    hadn't loaded, which is exactly what a test environment looks like, and also a real
    ad-blocker or a slow network. Switched to window.umami?.track(...)
  • the app tsconfig was type-checking spec files with none of the test types available
flowchart TD
  A[Static: TypeScript, Biome] --> B[Unit: src/domain]
  B --> C[Integration: src/ui, real components]
  C --> D[2 Playwright smoke specs]
Loading

Test plan

  • pnpm typecheck
  • pnpm check
  • pnpm test:coverage, 18 files, 102 tests, thresholds pass
  • pnpm build
  • pnpm test:e2e, both specs pass against a real built preview server

🤖 Generated with Claude Code

https://claude.ai/code/session_014BcGPRc3sgUJYKihGRXh5g

arnaud-zg and others added 2 commits September 1, 2026 23:45
- CartItems: a first-class collection absorbing the find/map/filter/reduce that used to
  be duplicated three times in Cart, replacing getItemsCopy()
- Cart now takes item ids (increaseQuantity, decreaseQuantity, removeItem), so the UI no
  longer pulls a Product back out of a CartItem just to hand it back to Cart. Added
  remainingForFreeShipping(), the Money arithmetic that used to live in JSX
- dropped CartItem.getProduct() (the source of that Demeter chain) and the dead
  getPrice(); renamed the rest to id()/name()/image()/quantity() to match the rest of
  the domain's naming
- quantity clamping moved off CartItem onto Quantity (clampBetween, isAtLeast, isAtMost),
  Money got isAtLeast/isZero so policies stop comparing raw .toAmount() values
- Skill's SkillValue and Product's KnowledgeProfile were the same concept with two
  vocabularies (intermediate vs adventurer) bridged by a ProductCardLogic shim. Unified
  on one type, deleted the shim
- GoldSilverCopperFormatter split into GoldSilverCopperAmount (conversion) and
  GoldSilverCopperFormatter (presentation), both got the specs they never had
- Stats.asObject() dissolved the value object into a plain record the UI had to
  re-derive labels from (and rendered "ManaBoost: 0" rows). Stats.list() now returns
  labeled, non-zero entries directly
- a product's lore link and video never actually varied by knowledge level, only the
  data entry did (same two URLs repeated three times per product). Moved ExtraResources
  onto Product itself
- data/products.ts is now a ProductCatalog built from named blueprints instead of a bare
  array of five-deep positional constructor calls
- moved the three WelcomeSurvey storage repositories out of a UI component folder into
  domain/welcomeSurvey/infrastructure/
- shared test fixtures (productFixtures.ts) replace the ~90 lines of product
  construction duplicated across Cart.spec.ts and CartItem.spec.ts

Cart went from 90 lines to 58, CartItem from 75 to 51: close to the 50-line rule, not
exactly on it, and a first-class collection exists now where none did before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BcGPRc3sgUJYKihGRXh5g
- vitest.config.ts: two projects, unit (node, src/domain/**) and integration (jsdom,
  src/ui/** and src/app/**), replacing the vite.config.ts that never had a test block
- coverage thresholds (v8 provider): 90% on src/domain, 70% on src/ui, enforced in CI
- integration tests for the flows that matter: list products, add to cart, change
  quantity, remove an item, the free-shipping banner and the "Free" badge, the welcome
  survey's auto-open/save/close cycle
- add the ZustandWelcomeSurveyRepository spec it never had, the third storage
  implementation now has the same coverage as the other two
- Playwright, 2 smoke specs against a built vite preview: home to shop, and add to cart
  through to the checkout modal. Runs as its own CI job with the report uploaded on
  failure
- CONTRIBUTING.md documents TDD (with two real examples from this repo's own history)
  and the trophy shape

Found and fixed along the way, surfaced by writing real tests against real components:
- every umami?.track(...) call site threw ReferenceError whenever the global umami
  script hadn't loaded (blocked, offline, or simply not present, which is exactly what
  jsdom is). Switched to window.umami?.track(...), and typed it as a Window property
  instead of a bare ambient global
- tsconfig.json (the app build) was type-checking spec files with none of the vitest/
  jest-dom types available, and tsconfig.vitest.json was missing globals.d.ts and
  vite-env.d.ts, so window and import.meta.env typings weren't visible to it either
- every real catalog product costs far more than COMMERCE_CONFIG.freeShippingThreshold,
  so the "add X more to earn free shipping" banner can never appear against real data.
  Covered with a mocked, deliberately cheap catalog instead of leaving it untested

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BcGPRc3sgUJYKihGRXh5g
@arnaud-zg

Copy link
Copy Markdown
Owner Author

Folded into #18, which combines this with #9: standing up the testing trophy alongside the UI rework it backs.

@arnaud-zg arnaud-zg closed this Sep 2, 2026
@arnaud-zg
arnaud-zg deleted the test/testing-trophy branch September 2, 2026 19:01
@arnaud-zg
arnaud-zg restored the test/testing-trophy branch September 2, 2026 19:13
@arnaud-zg arnaud-zg reopened this Sep 2, 2026
Base automatically changed from refactor/object-calisthenics-domain to fix/domain-red-suite-and-dead-repo September 2, 2026 19:16
@arnaud-zg

Copy link
Copy Markdown
Owner Author

Closing as stale/superseded — this branch stack's work already landed on main via a reorganized set of PRs (see #17, #7, #18, #10, #12, #13, #19, #16, #14, #20). Re-merging this branch now would conflict with / partially revert that work.

@arnaud-zg arnaud-zg closed this Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant