testsAndMisc/docs/DOCS-app-split-recipes.md
Krzysztof kuhy Rudnicki 49d82511c3 Adopt the four markdown namespaces and gate them
Renames this repo's markdown onto README.md / CLAUDE*.md / DOCS*.md /
TODO*.md so a future session can tell a live task from a finished
record without reading the file to find out.

TODO files carry the literal "REMOVE ME AFTER FINISH" and are deleted
when the work lands. Enforced by the md-naming pre-commit hook and CI
workflow, both delegating to the shared gate in kuhyx/utils so this
repo's idea of a valid name cannot drift from every other repo's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A47aX5DJwmA3QFn3pkEWi7
2026-08-24 15:55:46 +02:00

79 lines
3.6 KiB
Markdown

# App split recipes (Dart, Kotlin, TypeScript)
Settled while clearing `focus_owner`, `kcd2_dice_solver` and
`reverse_survivors`. Referenced from `refactor_claude_todo_resume.md`.
## Baselines — measure before editing, re-check after
| Project | Command | Expect |
| ---------------------- | ------------------- | -------------- |
| `focus_owner` (Dart) | `flutter test` | 94 |
| `focus_owner` (Kotlin) | see below | 40, 0 failures |
| `kcd2_dice_solver` | `npm test -- --run` | 288 |
| `reverse_survivors` | `npm test -- --run` | 160 |
Gradle prints `UP-TO-DATE` rather than a count, so read the JUnit XML, and
**pin the JDK** — the default JDK 26 breaks the AGP jlink transform:
```bash
cd focus_owner/android
JAVA_HOME=/usr/lib/jvm/java-21-openjdk ./gradlew testDebugUnitTest --offline
grep -ho 'tests="[0-9]*"' ../build/app/test-results/testDebugUnitTest/*.xml \
| grep -oP '\d+' | paste -sd+ | bc
```
## Dart: privates are library-scoped, so use `part`
A separate file is a separate **library**, so `_Card` and friends are invisible
to it. Splitting `main.dart` with `import` + `export` produced 30 errors of the
form "The name `_Card` isn't a class". `part 'x.dart';` + `part of 'main.dart';`
keeps both files in one library and the analyzer goes straight to zero issues.
The part file carries **no imports of its own** — it inherits the library's.
**A `part` cannot rescue a State class.** `status_page_state.dart` stayed over
the cap: its methods call `setState` and read seven private fields, and a
`mixin on State<...>` cannot see them without declaring the state twice.
**Splitting a widget test off its `testWidgets` group loses the binding.**
Four tests failed with "Binding has not yet been initialized" because the
group that had been initialising it implicitly moved away. Add
`TestWidgetsFlutterBinding.ensureInitialized()` to **both** halves.
## Kotlin: no `part`, so use an internal fixture object
Shared `private val`/`private fun` test fixtures go into an
`internal object XFixtures` in the same package, and each test class delegates
to it. Two compile failures came from missing one (`installed`) — list every
`private` member of the original class before cutting.
`EnforcementRunner.kt` is **not splittable** as it stands: its location block
reads the private constructor property `context` and `prefs()` eleven times,
which no extension function can reach.
## TypeScript: `tsc` does not tell you a split worked
Three separate projects had a seam that compiled clean and failed at runtime:
- an import cycle leaves a constant `undefined` at module-init
(`HEADSTART_POINTS`, twice, in `kcd2`);
- a moved function is simply not there (`survivorStep`, 63 failures);
- a type name that collides with a DOM global resolves to the DOM one — the
error talks about `anchorNode` rather than saying the name is missing.
**Always run the suite.** Prune the imports the split leaves behind by
iterating on `tsc`'s own TS6133/TS6192/TS6196 output rather than guessing, but
note it cannot see a helper another file still needs.
## Coverage: new files must land inside the include glob
`kcd2` and `reverse_survivors` both gate at 100%. New source modules are picked
up automatically; **shared test fixtures must go in `src/test/`**, which the
coverage `exclude` names by path. Beside the source they count as production
code and break the gate — the same trap recorded for `poker-stakes`.
## A split that empties a file must delete it
`App.test.tsx` lost its last `describe` and vitest failed the whole run with
"No test suite found in file". Remove the shell.