From 3b8afe0eff987793fe7d9e1c495b186f3b1e978e Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 22 Sep 2026 08:51:38 -0400 Subject: [PATCH] test: catch projects that lose their test files Two gaps let #288 delete all three `the-typeinator` test files without any check going red, leaving the project broken on `main` for months (#322, #323, #324). The `Test`, `Solutions`, and `Tsc` workflows pick which projects to run from `changed-files`'s `all_changed_files` output, which is ACMR and so excludes deletions. The typeinator's only change in that PR was three deletions, so the project never ran. Switches all three workflows to `all_modified_files` (ACMRD), which includes them. `files.test.ts` runs on every PR with no changed-files filtering, but only validated `_category_.json`, `README.md`, `tsconfig.json`, and `package.json` -- a step directory with no test file passed. Adds an assertion that every step (or `src`) of a project contains a test file. A project's `test` script is the discriminator: `tsc` projects are type-checking exercises and legitimately ship no tests, while all 18 `jest` projects currently have a test file in every step, so the new assertion adds no false positives. Verified by re-deleting the three typeinator test files, which now fails `files.test.ts` with one error per step. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/solutions.yml | 6 +++--- .github/workflows/test.yml | 6 +++--- .github/workflows/tsc.yml | 6 +++--- test/files.test.ts | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/.github/workflows/solutions.yml b/.github/workflows/solutions.yml index 6eb097e3..c6c6ba66 100644 --- a/.github/workflows/solutions.yml +++ b/.github/workflows/solutions.yml @@ -25,10 +25,10 @@ jobs: run: ./scripts/copy-solutions.sh shell: bash - name: Run changed solutions in project directories - if: ${{ steps.changed-files.outputs.all_changed_files != '' }} - run: npx zx ./scripts/changed-solutions.mjs "test:solutions" ${{ steps.changed-files.outputs.all_changed_files }} + if: ${{ steps.changed-files.outputs.all_modified_files != '' }} + run: npx zx ./scripts/changed-solutions.mjs "test:solutions" ${{ steps.changed-files.outputs.all_modified_files }} shell: bash - name: Run all solutions tests - if: ${{ steps.changed-files.outputs.all_changed_files == '' }} + if: ${{ steps.changed-files.outputs.all_modified_files == '' }} run: ./scripts/check-solutions.sh "test:solutions" shell: bash diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 089e2022..a7f3a6fb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,10 +25,10 @@ jobs: run: ./scripts/copy-solutions.sh shell: bash - name: Run test in project directories - if: ${{ steps.changed-files.outputs.all_changed_files != '' }} - run: npx zx ./scripts/changed-solutions.mjs test ${{ steps.changed-files.outputs.all_changed_files }} + if: ${{ steps.changed-files.outputs.all_modified_files != '' }} + run: npx zx ./scripts/changed-solutions.mjs test ${{ steps.changed-files.outputs.all_modified_files }} shell: bash - name: Run test in all project directories - if: ${{ steps.changed-files.outputs.all_changed_files == '' }} + if: ${{ steps.changed-files.outputs.all_modified_files == '' }} run: ./scripts/check-solutions.sh "test" shell: bash diff --git a/.github/workflows/tsc.yml b/.github/workflows/tsc.yml index 961a05a1..dc0b4d70 100644 --- a/.github/workflows/tsc.yml +++ b/.github/workflows/tsc.yml @@ -25,10 +25,10 @@ jobs: run: ./scripts/copy-solutions.sh shell: bash - name: Run tsc in project directories - if: ${{ steps.changed-files.outputs.all_changed_files != '' }} - run: npx zx ./scripts/changed-solutions.mjs tsc ${{ steps.changed-files.outputs.all_changed_files }} + if: ${{ steps.changed-files.outputs.all_modified_files != '' }} + run: npx zx ./scripts/changed-solutions.mjs tsc ${{ steps.changed-files.outputs.all_modified_files }} shell: bash - name: Run tsc in all project directories - if: ${{ steps.changed-files.outputs.all_changed_files == '' }} + if: ${{ steps.changed-files.outputs.all_modified_files == '' }} run: ./scripts/check-solutions.sh "tsc" shell: bash diff --git a/test/files.test.ts b/test/files.test.ts index 07af94aa..cd71e312 100644 --- a/test/files.test.ts +++ b/test/files.test.ts @@ -54,6 +54,28 @@ for (const chapterSlug of fs.readdirSync("projects")) { testAppetizerProject(); } + function projectUsesJest() { + const packageData = readFileAsJSON( + `${chapterDirectory}/${projectSlug}/package.json`, + ) as PackageData; + + return packageData.scripts.test === "jest"; + } + + function testContainsTestFile(directorySlug: string) { + test("test file", () => { + const directoryContents = fs.readdirSync( + `${chapterDirectory}/${projectSlug}/${directorySlug}`, + ); + + expect( + directoryContents.filter((fileName) => + fileName.includes(".test."), + ), + ).not.toEqual([]); + }); + } + function testAppetizerProject() { const stepSlugs = fs .readdirSync(`${chapterDirectory}/${projectSlug}`) @@ -115,8 +137,14 @@ npm run test -- 1 --watch }); } + const usesJest = projectUsesJest(); + for (const stepSlug of stepSlugs) { describe(stepSlug, () => { + if (usesJest) { + testContainsTestFile(stepSlug); + } + test("tsconfig.json", () => { const tsconfigData = readFileAsJSON( `${chapterDirectory}/${projectSlug}/${stepSlug}/tsconfig.json`, @@ -145,6 +173,10 @@ npm run test -- 1 --watch testPackageJson(); + if (projectUsesJest()) { + testContainsTestFile("src"); + } + test("README.md", () => { const contents = fs .readFileSync(`${chapterDirectory}/${projectSlug}/README.md`)