// tests/lib/files-reader-regressions.test.mjs // 6.4 / regressions A, B and C of the `Files:` reader (fieldPaths in // lib/parsers/plan-schema.mjs): three inputs 6.3.0 read right and 6.3.1 read // wrong. Each case goes through stepTestsOnly on a 1.8-shaped step and through // the validator's PLAN_STEP_TESTS_ONLY on a 1.8 plan. The controls are the // round 7-10 neighbours of each case and the plain path right after a // backticked one: they pin 6.3.1's answers and stay as they are. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { buildPlan } from '../helpers/plan-v18-builder.mjs'; import { validatePlanContent } from '../../lib/validators/plan-validator.mjs'; import { stepTestsOnly } from '../../lib/parsers/plan-schema.mjs'; const step = (files) => `- **Files:** ${files}\n- **Changes:** x\n`; // A 1.8 plan whose step 2 carries `files` as its `Files:` field. function planWith(files) { const plan = buildPlan({ steps: [{}, { file: 'src/mod2.mjs', endToEnd: 'bash tests/e2e.sh' }] }); const line = '- **Files:** `src/mod2.mjs`'; assert.ok(plan.includes(line), 'the builder writes step 2\'s Files: line'); return plan.replace(line, `- **Files:** ${files}`); } const testsOnlyWarned = (files) => validatePlanContent(planWith(files)).warnings .some((w) => w.code === 'PLAN_STEP_TESTS_ONLY' && /Step 2\b/.test(w.message)); // [case, Files: text, 6.3.0's paths] const CASES = [ ['A: a comma chunk that opens with a plain file ends the description', '`tests/a.test.mjs` - new test, src/a.mjs - the fix', ['tests/a.test.mjs', 'src/a.mjs']], ['B: a plain slash path inside a description, with no comma, is no file', '`tests/a.test.mjs` covers the parser in src/parse.mjs', ['tests/a.test.mjs']], ['C: a backticked bare name alone on a new line is a file', '`tests/a.test.mjs` - new test\n `LICENSE`', ['tests/a.test.mjs', 'LICENSE']], ]; for (const [name, files, paths] of CASES) { test(`6.4 / regression ${name}`, () => { const testsOnly = paths.every((p) => p.startsWith('tests/')); assert.deepEqual(stepTestsOnly(step(files)), { testsOnly, files: paths }, files); assert.equal(testsOnlyWarned(files), testsOnly, `PLAN_STEP_TESTS_ONLY ${testsOnly ? 'fires' : 'stays quiet'} on a 1.8 plan: ${files}`); }); } // [control, Files: text, 6.3.1's paths] const CONTROLS = [ ['r7 (comma form of B): a plain slash path in its own comma entry is read', '`tests/x.test.mjs`, the CLI entry src/cli.mjs', ['tests/x.test.mjs', 'src/cli.mjs']], ['r9 (A\'s neighbour): a description runs on past a comma', '`tests/a.test.mjs` — mocks fs, child_process.spawnSync and path.join', ['tests/a.test.mjs']], ['r10 (C\'s neighbour): a backticked capitalised name inside a description is no file', '`tests/a.test.mjs` — covers `parse`, `Parser`, `ParseError`', ['tests/a.test.mjs']], ['B\'s neighbour: a plain path right after the backticked one is read', '`tests/a.test.mjs` src/b.mjs', ['tests/a.test.mjs', 'src/b.mjs']], ]; for (const [name, files, paths] of CONTROLS) { test(`6.4 / control ${name}`, () => { const testsOnly = paths.every((p) => p.startsWith('tests/')); assert.deepEqual(stepTestsOnly(step(files)), { testsOnly, files: paths }, files); assert.equal(testsOnlyWarned(files), testsOnly, `PLAN_STEP_TESTS_ONLY ${testsOnly ? 'fires' : 'stays quiet'} on a 1.8 plan: ${files}`); }); }