---
title: Give SPA component defects somewhere to be caught
type: task
status: planned
description: >-
  Add a React render harness and make reducers importable under mocha, so league SPA components
  reading props in the wrong shape fail in tests rather than in production.
base_uri: user:task/league/give-component-defects-somewhere-to-be-caught.md
created_at: '2026-09-05T03:54:13.603Z'
entity_id: 4d29f33f-ff2a-4e3b-ba96-53bcc1fb4636
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
priority: Medium
public_read: true
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-09-16T22:53:58.263Z'
visibility_analyzed_at: '2026-11-05T23:49:33.163Z'
---

# Give SPA component defects somewhere to be caught

## Overview

A component that reads its props in the wrong shape currently has nowhere in this repo to be caught. On 2026-09-05 `selected-player-content` shipped calling `item.get('url')` on members of a `new List(...)`, which are plain objects, and threw during render in production. Review, `yarn lint`, `yarn check:types`, the full suite, a webpack build and a deploy all passed it.

## Background

Two independent gaps, and BOTH have to be true for the class to be invisible:

- **There is no React render harness.** `grep -rln "react-dom/server\|@testing-library\|renderToString\|enzyme" test/ package.json` returns nothing. No spec renders any component, so no spec can execute JSX.
- **The reducer that owns the shape cannot be imported under mocha.** A spec importing `@core/players/reducer` dies with `ReferenceError: window is not defined` at `app/core/store.js:12`, because the reducer's barrel imports (`@core/stats`, `@core/rosters`, `@core/auction`) pull the store in transitively. So even the cheaper option — pinning the shape contract at the reducer without rendering anything — is closed today. A spec for exactly this was written on 2026-09-05 and withdrawn rather than left broken.

Note what DID exist and did not help: `test/app.connected-component-prop-contract.spec.mjs` and `app.connected-component-props.spec.mjs` are source scans over the connect() layer, and the emitted-CSS oracle proves rules were emitted. Neither can execute a render.

## Design

**Do not assume the answer is a full render harness.** Two candidate levels, cheapest first, and the choice is the first real decision:

- **Make reducers importable, then pin shapes.** Narrower: catches "the slice holds plain objects and the component calls `.get()`" without rendering. Needs the barrel imports untangled or the reducer split from its action-type imports. Catches shape drift in both directions; catches no JSX error.
- **Add a render harness** (jsdom or happy-dom plus a renderer) and render components with fixture props. Catches this class outright, including throws that have nothing to do with shape. Costs a dependency, a mocha environment split, and a decision about how much of the redux/router context a component needs.

Whichever is chosen, the acceptance test is the same and it is not negotiable: **check out the pre-fix revision of `selected-player-content.js` and show the new mechanism goes RED on it**, then green at HEAD. A mechanism that has not been shown to fail on the known defect is not evidence.

## Notes

- Scope this to the defect class that actually shipped. Rendering every component is not the goal and would not have caught more.
- `docs/guides/spa.md` records the trap and this gap; update it when the gap closes.
- The two `app.connected-component-*` specs are the existing source-scan precedent if a scan turns out to be enough.

## Plan

The level is the first decision and it is deliberately not pre-made here — read Design, then expand this into a full implementation plan with [[user:workflow/write/write-software-implementation-plan.md]] once the level is chosen.

- [ ] **Pick the level, cheapest sufficient one**
  - Decide between making reducers importable and pinning shapes, or adding a render harness. Weigh against the defect class that actually shipped, not against total coverage.
  - Verify: the choice is recorded here with what it does and does not catch.

- [ ] **Build the mechanism and prove it goes RED on the known defect**
  - Verify: `git show ca6260d6f^:app/views/components/selected-player-content/selected-player-content.js` is the pre-fix file. The new mechanism must FAIL against it and pass at HEAD. Run both and require the readings to differ — a mechanism that has not been shown to fail is not evidence.

- [ ] **Close the loop in the guide**
  - File: `repository/active/league/docs/guides/spa.md`, which currently records this gap as open.
  - Verify: the paragraph naming the missing harness and the un-importable reducer is corrected or removed, and does not outlive the gap.
- [ ] Make the league SPA's reducer and saga tiers loadable from a spec. The resolver hook landed 2026-08-18 and took `app/core` from 52 to 132 of 234 importable; this is the next tier and it is blocked by something other than resolution, per [[████████████████████████-██-██████████████-██████-███-███████-████-███████]]
