mirror of
https://github.com/kuhyx/testsAndMisc-archive.git
synced 2026-11-10 18:07:09 +01:00
bucket_catch: make the done phase carry its result
Removes the last fixable non-null assertion. GamePhase goes from a string union to a tagged one whose "done" arm carries `result: GameResult`, so narrowing on phase.kind yields a non-null result and `fileResult!` disappears. GameResult - previously an untagged union nothing consumed - is now tagged, so the render picks a screen from the discriminant instead of inferring it from which of two nullable states was set. Three earlier attempts at this site were reverted: a null guard returning null, a widened ReactElement | null return, and a tagged GameResult with phase left as a string. Each either added a branch no test could reach - dropping the 100% gate - or left the value nullable in the fall-through. The invariant lived in the phase, so the fix had to as well. Coverage stays 100%. Statements 585 -> 579 and lines 536 -> 530, exactly the two useState declarations and their setters that the collapse removed; branches unchanged at 217, so nothing new went untested. All 10 App tests pass unmodified - they drive the app through mocked onDone callbacks, so both done arms are exercised through the new union. eslint 5 -> 4 (45 -> 4 for the session). The remaining four are reported in the previous commit's evidence. usePuzzleGameLoop's Map.get assertion is not among the fixable ones: `?? Infinity` was tried and measured at 219 branches / 99.54%, since the default side is unreachable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
This commit is contained in:
parent
a4b879b19d
commit
031bff898e
@ -13,55 +13,52 @@ import { ScoreScreen } from "./components/ScoreScreen";
|
||||
import { PuzzleResult } from "./components/PuzzleResult";
|
||||
|
||||
export default function App(): React.ReactElement {
|
||||
const [phase, setPhase] = useState<GamePhase>("drop");
|
||||
const [phase, setPhase] = useState<GamePhase>({ kind: "drop" });
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const [mode, setMode] = useState<TransferMode>("download");
|
||||
const [puzzleGridSize, setPuzzleGridSize] = useState(4);
|
||||
const [fileResult, setFileResult] = useState<FileGameResult | null>(null);
|
||||
const [puzzleResult, setPuzzleResult] = useState<PuzzleGameResult | null>(null);
|
||||
|
||||
const handleFiles = useCallback((incoming: File[]) => {
|
||||
setFiles(incoming);
|
||||
setPhase("mode");
|
||||
setPhase({ kind: "mode" });
|
||||
}, []);
|
||||
|
||||
const handlePuzzleDirect = useCallback((imageFile: File, gridSize: number) => {
|
||||
setFiles([imageFile]);
|
||||
setMode("puzzle");
|
||||
setPuzzleGridSize(gridSize);
|
||||
setPhase("playing");
|
||||
}, []);
|
||||
const handlePuzzleDirect = useCallback(
|
||||
(imageFile: File, gridSize: number) => {
|
||||
setFiles([imageFile]);
|
||||
setMode("puzzle");
|
||||
setPuzzleGridSize(gridSize);
|
||||
setPhase({ kind: "playing" });
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleStart = useCallback((selected: TransferMode, gridSize = 4) => {
|
||||
setMode(selected);
|
||||
if (selected === "puzzle") setPuzzleGridSize(gridSize);
|
||||
setPhase("playing");
|
||||
setPhase({ kind: "playing" });
|
||||
}, []);
|
||||
|
||||
const handleFileDone = useCallback((result: FileGameResult) => {
|
||||
setFileResult(result);
|
||||
setPhase("done");
|
||||
const handleFileDone = useCallback((value: FileGameResult) => {
|
||||
setPhase({ kind: "done", result: { kind: "file", value } });
|
||||
}, []);
|
||||
|
||||
const handlePuzzleDone = useCallback((result: PuzzleGameResult) => {
|
||||
setPuzzleResult(result);
|
||||
setPhase("done");
|
||||
const handlePuzzleDone = useCallback((value: PuzzleGameResult) => {
|
||||
setPhase({ kind: "done", result: { kind: "puzzle", value } });
|
||||
}, []);
|
||||
|
||||
const handleRestart = useCallback(() => {
|
||||
setFiles([]);
|
||||
setFileResult(null);
|
||||
setPuzzleResult(null);
|
||||
setPhase("drop");
|
||||
setPhase({ kind: "drop" });
|
||||
}, []);
|
||||
|
||||
if (phase === "drop") {
|
||||
if (phase.kind === "drop") {
|
||||
return <DropZone onFiles={handleFiles} onPuzzle={handlePuzzleDirect} />;
|
||||
}
|
||||
if (phase === "mode") {
|
||||
if (phase.kind === "mode") {
|
||||
return <ModeSelect files={files} onStart={handleStart} />;
|
||||
}
|
||||
if (phase === "playing") {
|
||||
if (phase.kind === "playing") {
|
||||
if (mode === "puzzle") {
|
||||
return (
|
||||
<PuzzleCanvas
|
||||
@ -73,13 +70,15 @@ export default function App(): React.ReactElement {
|
||||
}
|
||||
return <GameCanvas files={files} onDone={handleFileDone} />;
|
||||
}
|
||||
// done phase — puzzleResult is non-null iff mode was "puzzle"
|
||||
if (puzzleResult !== null) {
|
||||
return <PuzzleResult result={puzzleResult} onRestart={handleRestart} />;
|
||||
// done phase: the phase carries its result, so both arms are non-null.
|
||||
if (phase.result.kind === "puzzle") {
|
||||
return (
|
||||
<PuzzleResult result={phase.result.value} onRestart={handleRestart} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ScoreScreen
|
||||
result={fileResult!}
|
||||
result={phase.result.value}
|
||||
mode={mode}
|
||||
onRestart={handleRestart}
|
||||
/>
|
||||
|
||||
@ -1,4 +1,12 @@
|
||||
export type GamePhase = "drop" | "mode" | "playing" | "done";
|
||||
/**
|
||||
* Where the app is. The done phase carries its result, so "finished with no
|
||||
* result" cannot be represented and the render never has to assert one away.
|
||||
*/
|
||||
export type GamePhase =
|
||||
| { kind: "drop" }
|
||||
| { kind: "mode" }
|
||||
| { kind: "playing" }
|
||||
| { kind: "done"; result: GameResult };
|
||||
|
||||
export type TransferMode = "download" | "upload" | "puzzle";
|
||||
|
||||
@ -52,4 +60,7 @@ export interface PuzzleGameResult {
|
||||
gridSize: number;
|
||||
}
|
||||
|
||||
export type GameResult = FileGameResult | PuzzleGameResult;
|
||||
/** The outcome of a finished game, tagged by which mode produced it. */
|
||||
export type GameResult =
|
||||
| { kind: "file"; value: FileGameResult }
|
||||
| { kind: "puzzle"; value: PuzzleGameResult };
|
||||
|
||||
Loading…
Reference in New Issue
Block a user