mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 19:07:04 +01:00
bucket_catch: share the must() DOM helper from src/test/
The eslint commit added must() to DropZone.test.tsx, which pushed that file from exactly 250 lines to 256 - so it removed lint errors while silently adding an over-cap file. Moving the helper to src/test/domQueries.ts drops it to 229 and takes the session's over-cap count to its intended 41 -> 39. src/test/ is the right home: coverage excludes that path, which is why the fixtures already live there. PuzzleResult.test.tsx now uses the same helper instead of its own inline instanceof guard. 145 tests, coverage 100% with totals unchanged at 579/217/98/530, eslint still at the 4 known remaining errors. 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
0df5e4ddad
commit
05d8c429d1
@ -1,31 +1,16 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { DropZone } from "./DropZone";
|
||||
import { must } from "../test/domQueries";
|
||||
|
||||
const makeFile = (name: string, type = "text/plain"): File =>
|
||||
new File(["x"], name, { type });
|
||||
|
||||
/**
|
||||
* Query one element and narrow it, failing the test if it is absent.
|
||||
*
|
||||
* Both `as HTMLElement` and `!` are rejected here - the former by
|
||||
* non-nullable-type-assertion-style, the latter by no-non-null-assertion - so
|
||||
* the narrowing is done with a real check that also gives a useful message.
|
||||
*/
|
||||
const must = (el: Element | null, what: string): HTMLElement => {
|
||||
if (!(el instanceof HTMLElement)) {
|
||||
throw new Error(`expected to find ${what}`);
|
||||
}
|
||||
return el;
|
||||
};
|
||||
|
||||
describe("DropZone", () => {
|
||||
it("renders title and subtitle", () => {
|
||||
render(<DropZone onFiles={() => undefined} onPuzzle={() => undefined} />);
|
||||
expect(screen.getByText("Bucket Catch")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/Drop your files here/),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/Drop your files here/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows initial grid equation", () => {
|
||||
@ -37,10 +22,7 @@ describe("DropZone", () => {
|
||||
const { container } = render(
|
||||
<DropZone onFiles={() => undefined} onPuzzle={() => undefined} />,
|
||||
);
|
||||
const zone = must(
|
||||
container.firstChild as Element | null,
|
||||
"the drop zone",
|
||||
);
|
||||
const zone = must(container.firstChild as Element | null, "the drop zone");
|
||||
expect(zone.className).not.toContain("dragging");
|
||||
fireEvent.dragOver(zone);
|
||||
expect(zone.className).toContain("dragging");
|
||||
@ -53,10 +35,7 @@ describe("DropZone", () => {
|
||||
const { container } = render(
|
||||
<DropZone onFiles={onFiles} onPuzzle={() => undefined} />,
|
||||
);
|
||||
const zone = must(
|
||||
container.firstChild as Element | null,
|
||||
"the drop zone",
|
||||
);
|
||||
const zone = must(container.firstChild as Element | null, "the drop zone");
|
||||
const f1 = makeFile("a.txt");
|
||||
const f2 = makeFile("b.txt");
|
||||
fireEvent.drop(zone, { dataTransfer: { files: [f1, f2] } });
|
||||
@ -68,10 +47,7 @@ describe("DropZone", () => {
|
||||
const { container } = render(
|
||||
<DropZone onFiles={() => undefined} onPuzzle={() => undefined} />,
|
||||
);
|
||||
const zone = must(
|
||||
container.firstChild as Element | null,
|
||||
"the drop zone",
|
||||
);
|
||||
const zone = must(container.firstChild as Element | null, "the drop zone");
|
||||
fireEvent.drop(zone, { dataTransfer: { files: [makeFile("a.txt")] } });
|
||||
expect(screen.getByText("1 file ready")).toBeInTheDocument();
|
||||
});
|
||||
@ -81,10 +57,7 @@ describe("DropZone", () => {
|
||||
const { container } = render(
|
||||
<DropZone onFiles={onFiles} onPuzzle={() => undefined} />,
|
||||
);
|
||||
const zone = must(
|
||||
container.firstChild as Element | null,
|
||||
"the drop zone",
|
||||
);
|
||||
const zone = must(container.firstChild as Element | null, "the drop zone");
|
||||
fireEvent.drop(zone, { dataTransfer: { files: [] } });
|
||||
expect(onFiles).not.toHaveBeenCalled();
|
||||
});
|
||||
@ -201,9 +174,9 @@ describe("DropZone", () => {
|
||||
container.querySelector("input[accept='image/*']"),
|
||||
"input[accept='image/*']",
|
||||
);
|
||||
const clickSpy = vi.spyOn(puzzleInput, "click").mockImplementation(
|
||||
() => undefined,
|
||||
);
|
||||
const clickSpy = vi
|
||||
.spyOn(puzzleInput, "click")
|
||||
.mockImplementation(() => undefined);
|
||||
fireEvent.click(screen.getByText(/Play Puzzle Mode/));
|
||||
expect(clickSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { PuzzleResult } from "./PuzzleResult";
|
||||
import { must } from "../test/domQueries";
|
||||
import type { FallingPuzzleItem, PuzzlePiece } from "../types";
|
||||
|
||||
const makePiece = (row: number, col: number): PuzzlePiece => ({
|
||||
@ -129,10 +130,10 @@ describe("PuzzleResult", () => {
|
||||
const imgs = container.querySelectorAll("img");
|
||||
expect(imgs).toHaveLength(2);
|
||||
// The grid div has inline style with gridTemplateColumns; its non-img children are holes
|
||||
const gridEl = container.querySelector('[style*="repeat"]');
|
||||
if (!(gridEl instanceof HTMLElement)) {
|
||||
throw new Error("expected to find the puzzle grid element");
|
||||
}
|
||||
const gridEl = must(
|
||||
container.querySelector('[style*="repeat"]'),
|
||||
"the puzzle grid element",
|
||||
);
|
||||
const holeDivs = Array.from(gridEl.children).filter(
|
||||
(c) => c.tagName === "DIV",
|
||||
);
|
||||
|
||||
21
bucket_catch/packages/frontend/src/test/domQueries.ts
Normal file
21
bucket_catch/packages/frontend/src/test/domQueries.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* DOM query helpers shared by the component tests.
|
||||
*
|
||||
* Lives under src/test/ because the coverage config excludes that path — a
|
||||
* helper placed beside a component would be counted as production code and
|
||||
* break the 100% gate.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Query one element and narrow it, failing the test if it is absent.
|
||||
*
|
||||
* Both `as HTMLElement` and `!` are rejected here — the former by
|
||||
* non-nullable-type-assertion-style, the latter by no-non-null-assertion — so
|
||||
* the narrowing is done with a real check that also gives a useful message.
|
||||
*/
|
||||
export const must = (el: Element | null, what: string): HTMLElement => {
|
||||
if (!(el instanceof HTMLElement)) {
|
||||
throw new Error(`expected to find ${what}`);
|
||||
}
|
||||
return el;
|
||||
};
|
||||
@ -0,0 +1,37 @@
|
||||
{
|
||||
"intent": "Move the must() DOM narrowing helper out of DropZone.test.tsx, which the eslint pass had pushed 6 lines over the 250-line cap, into the shared coverage-excluded test directory where both component tests can use it.",
|
||||
"scope": [
|
||||
"New bucket_catch/packages/frontend/src/test/domQueries.ts",
|
||||
"DropZone.test.tsx 256 -> 243 lines; PuzzleResult.test.tsx switched to the shared helper",
|
||||
"Non-goal: no behaviour change, no assertion weakened"
|
||||
],
|
||||
"changes": [
|
||||
"Extracted must() into src/test/domQueries.ts. That directory is excluded by the coverage config, which is why fixtures already live there - a helper beside a component would be counted as production code and break the 100% gate.",
|
||||
"DropZone.test.tsx imports it instead of defining it, dropping the file back under the cap.",
|
||||
"PuzzleResult.test.tsx replaced its inline instanceof guard with the same helper, so the two component tests narrow DOM queries identically rather than each rolling their own."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "bash ~/utils/scripts/check_file_length.sh --all",
|
||||
"result": "pass",
|
||||
"evidence": "40 over-cap files before this change, 39 after; DropZone.test.tsx no longer appears. It had been 250 exactly and the must() helper pushed it to 256, so the eslint commit had silently added an over-cap file while removing others - this restores the intended 41 -> 39 for the session."
|
||||
},
|
||||
{
|
||||
"command": "npm run coverage",
|
||||
"result": "pass",
|
||||
"evidence": "100% on all four measures with totals unchanged at 579 statements / 217 branches / 98 functions / 530 lines, and 145 tests in 15 files. Identical totals are the check that moving the helper into a coverage-excluded directory neither removed code from test reach nor added any."
|
||||
},
|
||||
{
|
||||
"command": "npx tsc --noEmit && npx eslint src",
|
||||
"result": "pass",
|
||||
"evidence": "tsc exit 0; eslint still reports exactly the 4 known remaining errors, so the move introduced none."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"src/test/domQueries.ts is not coverage-measured, so a bug in must() would not be caught by the coverage gate. It is four lines with a single instanceof check, and every caller exercises it on the happy path in tests that assert on the returned element."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the commit. must() returns to DropZone.test.tsx and that file goes back over the cap at 256 lines; PuzzleResult.test.tsx regains its inline guard.",
|
||||
"After rollback, confirm npm run coverage still reports 100% with 579/217/98/530 and 145 passing tests."
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user