@pmelab/gtd 45.2.0 → 46.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +9 -24
- package/dist/chunks/{reserved-FWrjy7zo.mjs → reserved-BENPy6IJ.mjs} +3 -28
- package/dist/chunks/{review-Dhl4vvDe.mjs → review-DcbgUwr7.mjs} +6 -17
- package/dist/chunks/{tail-B-Es6Hwj.mjs → tail-CDNMZ-2E.mjs} +1 -1
- package/dist/gtd.bundle.mjs +5 -7
- package/dist/testkit.mjs +5 -7
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +3 -3
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +14 -4
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +17 -228
- package/package.json +1 -6
- package/src/install/ConfigScaffold.ts +1 -2
- package/src/install/Install.ts +4 -5
- package/src/workflows/index.ts +1 -2
- package/src/workflows/review/review.ts +15 -3
- package/src/workflows/shared/lenses.ts +1 -5
- package/src/workflows/shared/options.ts +1 -21
- package/src/workflows/shared/review.ts +2 -19
- package/src/workflows/shared/skills.test.ts +0 -2
- package/src/workflows/shared/skills.ts +0 -3
- package/src/workflows/shared/text.ts +2 -5
- package/src/workflows/shared/vars.ts +0 -8
- package/dist/workflows/tests.mjs +0 -193
- package/src/workflows/tests/index.ts +0 -6
- package/src/workflows/tests/tests-options.feature +0 -52
- package/src/workflows/tests/tests.feature +0 -266
- package/src/workflows/tests/tests.test.ts +0 -51
- package/src/workflows/tests/tests.ts +0 -270
package/dist/workflows/tests.mjs
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { changes, changesSince, glob, read, refuse, removeScript, run, scope, sectionBodies, start, vars } from "../flows.mjs";
|
|
2
|
-
import { E as fixCheck, Et as envDefaults, J as finisherSystem, Ot as testsDefaults, P as agentWithSkills, Tt as defaults, c as testsShipped, d as gate, i as skills, n as summary, p as healthy, r as access, t as steering, w as coder, y as REQUIREMENTS } from "../chunks/reserved-FWrjy7zo.mjs";
|
|
3
|
-
import { a as refusePendingHandOff, o as tail } from "../chunks/tail-B-Es6Hwj.mjs";
|
|
4
|
-
import { a as reUnwind, i as isCode, n as fixFeedback, r as humanReview } from "../chunks/review-Dhl4vvDe.mjs";
|
|
5
|
-
//#region src/workflows/tests/tests.ts
|
|
6
|
-
const TESTS = ".gtd/TESTS.md";
|
|
7
|
-
const RETIRE = ".gtd/RETIRE.md";
|
|
8
|
-
/** The first two answer boxes of a deletion proposal; the third stays the human's own answer. */
|
|
9
|
-
const RETIRE_ANSWERS = ["delete it", "keep it skipped"];
|
|
10
|
-
testsDefaults.skipPattern;
|
|
11
|
-
/** `skipPattern` as a per-line expression; refuses one that is blank, invalid, or matches every line. */
|
|
12
|
-
const skipExpression = (pattern) => {
|
|
13
|
-
if (pattern.trim() === "") return refuse("gtd: skipPattern must not be blank");
|
|
14
|
-
let expression;
|
|
15
|
-
try {
|
|
16
|
-
expression = new RegExp(pattern);
|
|
17
|
-
} catch (error) {
|
|
18
|
-
return refuse(`gtd: skipPattern is not a valid regular expression: ${String(error)}`);
|
|
19
|
-
}
|
|
20
|
-
if (expression.test("")) return refuse("gtd: skipPattern must not match the empty string");
|
|
21
|
-
return expression;
|
|
22
|
-
};
|
|
23
|
-
/** Every line of `files` the expression calls skipped, in file then line order. */
|
|
24
|
-
const skipsIn = (files, expression) => files.flatMap(({ path, text }) => text.split("\n").flatMap((line, i) => expression.test(line) ? [{
|
|
25
|
-
path,
|
|
26
|
-
line: i + 1,
|
|
27
|
-
text: line.trim()
|
|
28
|
-
}] : []));
|
|
29
|
-
const testPaths = () => {
|
|
30
|
-
const globs = (vars.testFiles ?? testsDefaults.testFiles).split(",").map((g) => g.trim()).filter((g) => g !== "");
|
|
31
|
-
return [...new Set(globs.flatMap((g) => glob(g)))].filter(isCode).sort();
|
|
32
|
-
};
|
|
33
|
-
const scan = (paths, expression) => skipsIn(paths.map((path) => ({
|
|
34
|
-
path,
|
|
35
|
-
text: read(path) ?? ""
|
|
36
|
-
})), expression);
|
|
37
|
-
/** A section title as a repository path: backticks and a leading `./` dropped. */
|
|
38
|
-
const pathOf = (title) => title.replace(/`/g, "").trim().replace(/^\.\//, "");
|
|
39
|
-
const below = (section) => section.split("\n").slice(1).join("\n").trim();
|
|
40
|
-
const sweepingPrompt = (skips) => `Sweep this repository's tests for ones to revive.
|
|
41
|
-
|
|
42
|
-
Skipped tests gtd found:
|
|
43
|
-
|
|
44
|
-
${skips.length === 0 ? "- none" : skips.map((s) => `- ${s.path}:${s.line} — ${s.text}`).join("\n")}
|
|
45
|
-
|
|
46
|
-
- Audit every test in the repository with the test-audit skill — the whole
|
|
47
|
-
suite, not a recent change
|
|
48
|
-
- Write \`${TESTS}\`: \`# Tests\`, then one \`## <path>\` section per test file
|
|
49
|
-
that needs work — every file listed above, and each file the audit found
|
|
50
|
-
worth rewriting — saying which tests are skipped and what the audit found
|
|
51
|
-
- Change nothing else; write nothing when there is nothing to do
|
|
52
|
-
- Leave everything uncommitted and finish your turn`;
|
|
53
|
-
const rewritingPrompt = (path, found) => `Rewrite the tests in \`${path}\` until none is skipped and the suite is green.
|
|
54
|
-
|
|
55
|
-
What the sweep found:
|
|
56
|
-
|
|
57
|
-
${found || "_Nothing more than the skipped tests._"}
|
|
58
|
-
|
|
59
|
-
- Unskip every skipped test in the file and make it pass, rewriting it where
|
|
60
|
-
it rotted, and fix what the audit found
|
|
61
|
-
- A test that is obsolete — what it tests is gone, or another test already
|
|
62
|
-
covers it — stays skipped: never delete it. Propose its deletion in
|
|
63
|
-
\`${RETIRE}\` instead: a \`## <title>\` section naming \`${path}\`, the test,
|
|
64
|
-
and why it should go. A person decides. Propose a line gtd misread as
|
|
65
|
-
skipped (fixture text, a comment) the same way, saying so
|
|
66
|
-
- Delete no test file
|
|
67
|
-
- Leave everything uncommitted and finish your turn`;
|
|
68
|
-
const gateMessage = () => `The test baseline is red — gtd will not revive skipped tests on a broken suite.
|
|
69
|
-
\`.gtd/FEEDBACK.md\` holds the failing output.
|
|
70
|
-
|
|
71
|
-
What each change does next (then run \`gtd land\`):
|
|
72
|
-
- **Retry check** — edit the code and/or \`.gtd/FEEDBACK.md\` to fix the failing tests (**tests-gate.check**).
|
|
73
|
-
`;
|
|
74
|
-
const awaitMessage = (base) => `\`.gtd/REVIEW.md\` holds one chunk per test the sweep would delete. Each ends in
|
|
75
|
-
three boxes — tick exactly one: \`delete it\` (the test is deleted, then the
|
|
76
|
-
suite runs), \`keep it skipped\` (dropped, recorded in the commit), or write your
|
|
77
|
-
own answer over \`_your answer_\` and tick it (judged like a note). Every
|
|
78
|
-
landing here is refused until each proposal holds exactly one tick. An agent
|
|
79
|
-
turn that deleted a test file was refused; check the diff for tests removed
|
|
80
|
-
inside a file.
|
|
81
|
-
|
|
82
|
-
Review the revived tests yourself:
|
|
83
|
-
|
|
84
|
-
git diff ${base}
|
|
85
|
-
|
|
86
|
-
Then run \`gtd land\`. A note or code edit of your own is read as feedback and
|
|
87
|
-
built before you review again.
|
|
88
|
-
`;
|
|
89
|
-
/** A proposal's own text, made a request the nit fixer acts on once the human ticks `delete it`. */
|
|
90
|
-
const proposals = (text) => sectionBodies(text).map((s, i) => ({
|
|
91
|
-
id: `R${i + 1}`,
|
|
92
|
-
title: s.title,
|
|
93
|
-
body: `Delete this skipped test.\n\n${below(s.body)}`,
|
|
94
|
-
answers: RETIRE_ANSWERS
|
|
95
|
-
}));
|
|
96
|
-
/** Sections of `text` that name `path` as a whole token — a trailing full stop still counts. */
|
|
97
|
-
const naming = (text, path) => {
|
|
98
|
-
const token = new RegExp(`(?<![\\w./-])${path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w/-])`);
|
|
99
|
-
return sectionBodies(text).filter((s) => token.test(s.body.replace(/(^|[^\w.])\.\//g, "$1"))).length;
|
|
100
|
-
};
|
|
101
|
-
/** Refuses a test file a turn deleted, and a skip left in the file being rewritten that `.gtd/RETIRE.md` does not name. */
|
|
102
|
-
const guardFor = (known, expression, path) => () => {
|
|
103
|
-
const deleted = changes().filter((c) => c.status === "deleted" && known.has(c.path)).map((c) => c.path);
|
|
104
|
-
if (deleted.length > 0) refuse(`gtd land: tests: ${deleted.join(", ")} deleted — a test goes only once a person ticks "delete it" at the review`);
|
|
105
|
-
if (path === void 0) return;
|
|
106
|
-
const left = scan([path], expression).slice(naming(read(RETIRE) ?? "", path));
|
|
107
|
-
if (left.length > 0) refuse(`gtd land: tests: ${left.map((s) => `${s.path}:${s.line}`).join(", ")} still skipped — revive it, or propose its deletion in ${RETIRE}`);
|
|
108
|
-
};
|
|
109
|
-
/** The sweep's packages, refused when it changed more than its plan or the plan misses a file. */
|
|
110
|
-
const planned = (skips, known) => {
|
|
111
|
-
const strays = changes().filter((c) => c.path !== TESTS);
|
|
112
|
-
if (strays.length > 0) refuse(`gtd land: tests: the sweep changed ${strays.map((c) => c.path).join(", ")} — it writes only ${TESTS}`);
|
|
113
|
-
const packages = sectionBodies(read(TESTS) ?? "").map((s) => ({
|
|
114
|
-
path: pathOf(s.title),
|
|
115
|
-
found: below(s.body)
|
|
116
|
-
}));
|
|
117
|
-
const missing = [...packages.filter((p) => !known.has(p.path)).map((p) => `"${p.path}" is no test file`), ...[...new Set(skips.map((s) => s.path))].filter((path) => !packages.some((p) => p.path === path)).map((path) => `${path} has no section`)];
|
|
118
|
-
if (missing.length > 0) refuse(`gtd land: tests: in ${TESTS}, ${missing.join("; ")}`);
|
|
119
|
-
return packages;
|
|
120
|
-
};
|
|
121
|
-
/**
|
|
122
|
-
* Revive or retire skipped tests: a sweep plans one package per test file, each
|
|
123
|
-
* is rewritten until unskipped and green, and what an agent calls obsolete
|
|
124
|
-
* reaches the human review as a deletion proposal only a person can tick.
|
|
125
|
-
*/
|
|
126
|
-
const testsFlow = async () => {
|
|
127
|
-
refusePendingHandOff();
|
|
128
|
-
for (const file of [
|
|
129
|
-
REQUIREMENTS,
|
|
130
|
-
TESTS,
|
|
131
|
-
RETIRE
|
|
132
|
-
]) if (read(file) !== void 0) refuse(`gtd: ${file} is left over — finish or remove it first.`);
|
|
133
|
-
const expression = skipExpression(vars.skipPattern ?? testsDefaults.skipPattern);
|
|
134
|
-
await gate("tests-gate", gateMessage());
|
|
135
|
-
const base = start();
|
|
136
|
-
const known = new Set(testPaths());
|
|
137
|
-
const guard = guardFor(known, expression);
|
|
138
|
-
const report = await scope("tests", async () => {
|
|
139
|
-
const skips = scan([...known], expression);
|
|
140
|
-
await agentWithSkills("sweeping", sweepingPrompt(skips), {
|
|
141
|
-
label: "Sweeping the tests",
|
|
142
|
-
file: TESTS,
|
|
143
|
-
model: coder(),
|
|
144
|
-
system: finisherSystem(),
|
|
145
|
-
allowEmpty: true
|
|
146
|
-
});
|
|
147
|
-
guard();
|
|
148
|
-
const packages = planned(skips, known);
|
|
149
|
-
for (const { path, found } of packages) {
|
|
150
|
-
const guarded = guardFor(known, expression, path);
|
|
151
|
-
await agentWithSkills("rewriting", rewritingPrompt(path, found), {
|
|
152
|
-
label: `Rewriting ${path}`,
|
|
153
|
-
file: RETIRE,
|
|
154
|
-
model: coder(),
|
|
155
|
-
system: finisherSystem(),
|
|
156
|
-
allowEmpty: true
|
|
157
|
-
});
|
|
158
|
-
guarded();
|
|
159
|
-
if (!changes().some((c) => isCode(c.path))) continue;
|
|
160
|
-
await healthy(() => fixCheck(), {
|
|
161
|
-
guard: guarded,
|
|
162
|
-
guardRests: false
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
const retired = proposals(read(RETIRE) ?? "");
|
|
166
|
-
if (read(TESTS) !== void 0 || read(RETIRE) !== void 0) await run("closing", removeScript([TESTS, RETIRE]), { label: "Closing the sweep" });
|
|
167
|
-
const changed = changesSince(base).paths.some(isCode);
|
|
168
|
-
return retired.length === 0 && !changed ? void 0 : {
|
|
169
|
-
notes: retired,
|
|
170
|
-
fixed: [],
|
|
171
|
-
noise: []
|
|
172
|
-
};
|
|
173
|
-
});
|
|
174
|
-
if (report === void 0) return;
|
|
175
|
-
let outcome = await tail((escalations) => humanReview(base, escalations, {
|
|
176
|
-
report,
|
|
177
|
-
awaitMessage: report.notes.length > 0 ? awaitMessage : void 0
|
|
178
|
-
}), {
|
|
179
|
-
lenses: [],
|
|
180
|
-
green: true
|
|
181
|
-
});
|
|
182
|
-
while (outcome.verdict === "feedback") {
|
|
183
|
-
await reUnwind(outcome);
|
|
184
|
-
await fixFeedback();
|
|
185
|
-
outcome = await tail((escalations) => humanReview(base, escalations), { lenses: [] });
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
const tests = testsShipped(testsFlow);
|
|
189
|
-
//#endregion
|
|
190
|
-
//#region src/workflows/tests/index.ts
|
|
191
|
-
var tests_default = tests;
|
|
192
|
-
//#endregion
|
|
193
|
-
export { access, tests_default as default, defaults, envDefaults, skills, steering, summary, tests, testsFlow };
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
@inmem
|
|
2
|
-
Feature: the tests workflow's skip detection is a workflow option
|
|
3
|
-
|
|
4
|
-
`testFiles` (globs) and `skipPattern` (a per-line regular expression) default
|
|
5
|
-
to JavaScript/TypeScript runners and cucumber tags; another stack sets its
|
|
6
|
-
own on the factory.
|
|
7
|
-
|
|
8
|
-
Background:
|
|
9
|
-
Given a gtd config with:
|
|
10
|
-
"""
|
|
11
|
-
export {
|
|
12
|
-
feature as default,
|
|
13
|
-
feature,
|
|
14
|
-
defaults,
|
|
15
|
-
envDefaults,
|
|
16
|
-
summary,
|
|
17
|
-
steering,
|
|
18
|
-
skills,
|
|
19
|
-
access,
|
|
20
|
-
} from "@pmelab/gtd/workflows/feature"
|
|
21
|
-
import { tests as shippedTests } from "@pmelab/gtd/workflows/tests"
|
|
22
|
-
export const tests = shippedTests({
|
|
23
|
-
testFiles: "tests/test_*.py",
|
|
24
|
-
skipPattern: "@pytest\\.mark\\.skip\\b",
|
|
25
|
-
})
|
|
26
|
-
"""
|
|
27
|
-
And a file "tests/test_calc.py" with:
|
|
28
|
-
"""
|
|
29
|
-
@pytest.mark.skip
|
|
30
|
-
def test_add(): pass
|
|
31
|
-
"""
|
|
32
|
-
And a file "src/calc.test.ts" with:
|
|
33
|
-
"""
|
|
34
|
-
it.skip("adds", () => {})
|
|
35
|
-
"""
|
|
36
|
-
And the working tree is committed as "test: python"
|
|
37
|
-
And step "tests-gate.check" changes nothing
|
|
38
|
-
|
|
39
|
-
Scenario: only the configured files and pattern count as skipped
|
|
40
|
-
Given step "tests.sweeping" is prompted with "tests/test_calc.py:1 — @pytest.mark.skip"
|
|
41
|
-
And step "tests.sweeping" changes nothing
|
|
42
|
-
When gtd starts workflow "tests"
|
|
43
|
-
Then driving the process is refused with "tests/test_calc.py has no section"
|
|
44
|
-
And driving the process is refused without "src/calc.test.ts"
|
|
45
|
-
|
|
46
|
-
Scenario Outline: an unusable skipPattern is refused before any step
|
|
47
|
-
Then starting workflow "tests" with "--var skipPattern=<pattern>" is refused with "<refusal>"
|
|
48
|
-
|
|
49
|
-
Examples:
|
|
50
|
-
| pattern | refusal |
|
|
51
|
-
| .* | skipPattern must not match the empty string |
|
|
52
|
-
| ( | skipPattern is not a valid regular expression |
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
@inmem
|
|
2
|
-
Feature: the tests workflow revives skipped tests or proposes their deletion
|
|
3
|
-
|
|
4
|
-
`gtd --workflow tests` sweeps the repository for skipped tests and test-audit
|
|
5
|
-
findings into `.gtd/TESTS.md`, one section per test file. Each file is
|
|
6
|
-
rewritten in its own turn until nothing in it is skipped and the suite is
|
|
7
|
-
green. A test the agent calls obsolete stays skipped and becomes a proposal in
|
|
8
|
-
`.gtd/RETIRE.md`; the human review asks for one tick on each — delete it, keep
|
|
9
|
-
it skipped, or an own answer. Only a person's tick deletes a test.
|
|
10
|
-
|
|
11
|
-
Background:
|
|
12
|
-
Given a gtd config with:
|
|
13
|
-
"""
|
|
14
|
-
export {
|
|
15
|
-
feature as default,
|
|
16
|
-
feature,
|
|
17
|
-
defaults,
|
|
18
|
-
envDefaults,
|
|
19
|
-
summary,
|
|
20
|
-
steering,
|
|
21
|
-
skills,
|
|
22
|
-
access,
|
|
23
|
-
} from "@pmelab/gtd/workflows/feature"
|
|
24
|
-
export { tests } from "@pmelab/gtd/workflows/tests"
|
|
25
|
-
"""
|
|
26
|
-
And a file "src/calc.ts" with:
|
|
27
|
-
"""
|
|
28
|
-
export const add = (a: number, b: number) => a + b
|
|
29
|
-
"""
|
|
30
|
-
And a file "src/calc.test.ts" with:
|
|
31
|
-
"""
|
|
32
|
-
it.skip("adds", () => expect(add(1, 2)).toBe(3))
|
|
33
|
-
"""
|
|
34
|
-
And a file "src/legacy.test.ts" with:
|
|
35
|
-
"""
|
|
36
|
-
it.skip("calls the v1 api", () => {})
|
|
37
|
-
"""
|
|
38
|
-
And the working tree is committed as "feat: calculator"
|
|
39
|
-
And step "tests-gate.check" changes nothing
|
|
40
|
-
And step "tests.sweeping" writes ".gtd/TESTS.md" with:
|
|
41
|
-
"""
|
|
42
|
-
# Tests
|
|
43
|
-
|
|
44
|
-
## src/calc.test.ts
|
|
45
|
-
|
|
46
|
-
"adds" is skipped.
|
|
47
|
-
|
|
48
|
-
## src/legacy.test.ts
|
|
49
|
-
|
|
50
|
-
"calls the v1 api" is skipped.
|
|
51
|
-
"""
|
|
52
|
-
And step "tests.rewriting", when prompted with "\"adds\" is skipped", writes "src/calc.test.ts" with:
|
|
53
|
-
"""
|
|
54
|
-
it("adds", () => expect(add(1, 2)).toBe(3))
|
|
55
|
-
"""
|
|
56
|
-
And step "tests.rewriting", when prompted with "\"calls the v1 api\" is skipped", writes ".gtd/RETIRE.md" with:
|
|
57
|
-
"""
|
|
58
|
-
# Retire
|
|
59
|
-
|
|
60
|
-
## calls the v1 api
|
|
61
|
-
|
|
62
|
-
src/legacy.test.ts tests the v1 api, which is gone.
|
|
63
|
-
"""
|
|
64
|
-
And step "tests.health.check" changes nothing
|
|
65
|
-
And step "tests.closing" runs its script
|
|
66
|
-
And step "build.review.composing" runs its script
|
|
67
|
-
And step "build.review.closing" runs its script
|
|
68
|
-
|
|
69
|
-
Scenario: the revivable test is unskipped and the obsolete one is proposed for deletion
|
|
70
|
-
When gtd starts workflow "tests"
|
|
71
|
-
And the process is driven
|
|
72
|
-
Then the process rests at "build.review.await-review"
|
|
73
|
-
And the commit subjects end with:
|
|
74
|
-
"""
|
|
75
|
-
gtd(check): tests-gate.check → tests.sweeping
|
|
76
|
-
gtd(agent): tests.sweeping → tests.rewriting
|
|
77
|
-
gtd(agent): tests.rewriting → tests.health.check
|
|
78
|
-
gtd(check): tests.health.check → tests.rewriting
|
|
79
|
-
gtd(agent): tests.rewriting → tests.closing
|
|
80
|
-
gtd(check): tests.closing → build.review.composing
|
|
81
|
-
gtd(check): build.review.composing → build.review.await-review
|
|
82
|
-
"""
|
|
83
|
-
And "src/calc.test.ts" contains "it(\"adds\""
|
|
84
|
-
And "src/legacy.test.ts" contains "it.skip"
|
|
85
|
-
And ".gtd/TESTS.md" does not exist
|
|
86
|
-
And ".gtd/RETIRE.md" does not exist
|
|
87
|
-
And ".gtd/REVIEW.md" contains:
|
|
88
|
-
"""
|
|
89
|
-
## calls the v1 api
|
|
90
|
-
|
|
91
|
-
Delete this skipped test.
|
|
92
|
-
|
|
93
|
-
src/legacy.test.ts tests the v1 api, which is gone.
|
|
94
|
-
|
|
95
|
-
Tick exactly one:
|
|
96
|
-
|
|
97
|
-
- [ ] delete it
|
|
98
|
-
- [ ] keep it skipped
|
|
99
|
-
- [ ] _your answer_
|
|
100
|
-
"""
|
|
101
|
-
And the resting step's content contains "tests removed"
|
|
102
|
-
|
|
103
|
-
Scenario: signing off with an unticked proposal is refused
|
|
104
|
-
When gtd starts workflow "tests"
|
|
105
|
-
And the process is driven
|
|
106
|
-
Then landing is refused with "tick exactly one box"
|
|
107
|
-
|
|
108
|
-
Scenario: a ticked delete it has the test deleted, then the review comes back
|
|
109
|
-
Given step "build.review.fix.nits.fixing" deletes "src/legacy.test.ts"
|
|
110
|
-
And step "build.health.check" changes nothing
|
|
111
|
-
When gtd starts workflow "tests"
|
|
112
|
-
And the process is driven
|
|
113
|
-
And the text "- [ ] delete it" in ".gtd/REVIEW.md" is replaced with "- [x] delete it"
|
|
114
|
-
And gtd lands
|
|
115
|
-
And the process is driven
|
|
116
|
-
Then the process rests at "build.review.await-review"
|
|
117
|
-
And "src/legacy.test.ts" does not exist
|
|
118
|
-
When gtd lands
|
|
119
|
-
And the process is driven
|
|
120
|
-
Then the process rests at "idle"
|
|
121
|
-
|
|
122
|
-
Scenario: a ticked keep it skipped leaves the test skipped and ends the process
|
|
123
|
-
Given judge step "build.review.standards.judge" answers:
|
|
124
|
-
"""
|
|
125
|
-
[{"id":"standard-1","answer":"no","p":0.9}]
|
|
126
|
-
"""
|
|
127
|
-
When gtd starts workflow "tests"
|
|
128
|
-
And the process is driven
|
|
129
|
-
And the text "- [ ] keep it skipped" in ".gtd/REVIEW.md" is replaced with "- [x] keep it skipped"
|
|
130
|
-
And gtd lands
|
|
131
|
-
And the process is driven
|
|
132
|
-
Then the process rests at "idle"
|
|
133
|
-
And "src/legacy.test.ts" contains "it.skip"
|
|
134
|
-
And the commit "gtd(human): build.review.await-review → build.review.standards.judge" has a body containing "Gtd-Mark: wontfix=calls the v1 api"
|
|
135
|
-
|
|
136
|
-
Scenario: a rewrite leaving a test skipped without a proposal is refused
|
|
137
|
-
Given step "tests.rewriting", when prompted with "\"calls the v1 api\" is skipped", writes ".gtd/RETIRE.md" with:
|
|
138
|
-
"""
|
|
139
|
-
# Retire
|
|
140
|
-
"""
|
|
141
|
-
When gtd starts workflow "tests"
|
|
142
|
-
Then driving the process is refused with "src/legacy.test.ts:1 still skipped"
|
|
143
|
-
|
|
144
|
-
Scenario: a rewrite deleting a test file is refused
|
|
145
|
-
Given step "tests.rewriting" deletes "src/legacy.test.ts"
|
|
146
|
-
When gtd starts workflow "tests"
|
|
147
|
-
Then driving the process is refused with "src/legacy.test.ts deleted"
|
|
148
|
-
|
|
149
|
-
Scenario: a sweep deleting a test file is refused
|
|
150
|
-
Given step "tests.sweeping" deletes "src/legacy.test.ts"
|
|
151
|
-
When gtd starts workflow "tests"
|
|
152
|
-
Then driving the process is refused with "src/legacy.test.ts deleted"
|
|
153
|
-
|
|
154
|
-
Scenario: a sweep changing anything but its plan is refused
|
|
155
|
-
Given step "tests.sweeping" writes "src/calc.ts" with:
|
|
156
|
-
"""
|
|
157
|
-
export const add = (a: number, b: number) => b + a
|
|
158
|
-
"""
|
|
159
|
-
When gtd starts workflow "tests"
|
|
160
|
-
Then driving the process is refused with "the sweep changed src/calc.ts"
|
|
161
|
-
|
|
162
|
-
Scenario: a proposal naming its file at the end of a sentence counts
|
|
163
|
-
Given step "tests.rewriting", when prompted with "\"calls the v1 api\" is skipped", writes ".gtd/RETIRE.md" with:
|
|
164
|
-
"""
|
|
165
|
-
# Retire
|
|
166
|
-
|
|
167
|
-
## calls the v1 api
|
|
168
|
-
|
|
169
|
-
The v1 api is gone, so drop it from src/legacy.test.ts.
|
|
170
|
-
"""
|
|
171
|
-
When gtd starts workflow "tests"
|
|
172
|
-
And the process is driven
|
|
173
|
-
Then the process rests at "build.review.await-review"
|
|
174
|
-
|
|
175
|
-
Scenario: a sweep planning a file that is no test file is refused
|
|
176
|
-
Given step "tests.sweeping" writes ".gtd/TESTS.md" with:
|
|
177
|
-
"""
|
|
178
|
-
# Tests
|
|
179
|
-
|
|
180
|
-
## src/calc.ts
|
|
181
|
-
|
|
182
|
-
Nothing is skipped.
|
|
183
|
-
|
|
184
|
-
## src/calc.test.ts
|
|
185
|
-
|
|
186
|
-
"adds" is skipped.
|
|
187
|
-
|
|
188
|
-
## src/legacy.test.ts
|
|
189
|
-
|
|
190
|
-
"calls the v1 api" is skipped.
|
|
191
|
-
"""
|
|
192
|
-
When gtd starts workflow "tests"
|
|
193
|
-
Then driving the process is refused with "is no test file"
|
|
194
|
-
|
|
195
|
-
Scenario: a proposal naming its file as ./path counts
|
|
196
|
-
Given step "tests.rewriting", when prompted with "\"calls the v1 api\" is skipped", writes ".gtd/RETIRE.md" with:
|
|
197
|
-
"""
|
|
198
|
-
# Retire
|
|
199
|
-
|
|
200
|
-
## calls the v1 api
|
|
201
|
-
|
|
202
|
-
./src/legacy.test.ts tests the v1 api, which is gone.
|
|
203
|
-
"""
|
|
204
|
-
When gtd starts workflow "tests"
|
|
205
|
-
And the process is driven
|
|
206
|
-
Then the process rests at "build.review.await-review"
|
|
207
|
-
|
|
208
|
-
Scenario: rewrites that change no code and propose nothing end the process
|
|
209
|
-
Given a file "src/legacy.test.ts" with:
|
|
210
|
-
"""
|
|
211
|
-
it("calls the v2 api", () => {})
|
|
212
|
-
"""
|
|
213
|
-
And a file "src/calc.test.ts" with:
|
|
214
|
-
"""
|
|
215
|
-
it("adds", () => expect(add(1, 2)).toBe(3))
|
|
216
|
-
"""
|
|
217
|
-
And the working tree is committed as "test: nothing skipped"
|
|
218
|
-
And step "tests.rewriting" from now on changes nothing
|
|
219
|
-
When gtd starts workflow "tests"
|
|
220
|
-
And the process is driven
|
|
221
|
-
Then the process rests at "idle"
|
|
222
|
-
|
|
223
|
-
Scenario: one proposal covers one skip, not every skip in its file
|
|
224
|
-
Given a file "src/legacy.test.ts" with:
|
|
225
|
-
"""
|
|
226
|
-
it.skip("calls the v1 api", () => {})
|
|
227
|
-
it.skip("calls the v1 batch api", () => {})
|
|
228
|
-
"""
|
|
229
|
-
And the working tree is committed as "test: another legacy test"
|
|
230
|
-
When gtd starts workflow "tests"
|
|
231
|
-
Then driving the process is refused with "src/legacy.test.ts:2 still skipped"
|
|
232
|
-
|
|
233
|
-
Scenario: a sweep leaving out a file with a skipped test is refused
|
|
234
|
-
Given step "tests.sweeping" writes ".gtd/TESTS.md" with:
|
|
235
|
-
"""
|
|
236
|
-
# Tests
|
|
237
|
-
|
|
238
|
-
## src/calc.test.ts
|
|
239
|
-
|
|
240
|
-
"adds" is skipped.
|
|
241
|
-
"""
|
|
242
|
-
When gtd starts workflow "tests"
|
|
243
|
-
Then driving the process is refused with "src/legacy.test.ts has no section"
|
|
244
|
-
|
|
245
|
-
Scenario: the sweep is told every skipped test, path and line
|
|
246
|
-
Given step "tests.sweeping" is prompted with "src/legacy.test.ts:1 — it.skip(\"calls the v1 api\", () => {})"
|
|
247
|
-
When gtd starts workflow "tests"
|
|
248
|
-
And the process is driven
|
|
249
|
-
Then the process rests at "build.review.await-review"
|
|
250
|
-
|
|
251
|
-
Scenario: a sweep with nothing to do ends the process
|
|
252
|
-
Given a file "src/calc.test.ts" with:
|
|
253
|
-
"""
|
|
254
|
-
it("adds", () => expect(add(1, 2)).toBe(3))
|
|
255
|
-
"""
|
|
256
|
-
And the file "src/legacy.test.ts" is deleted
|
|
257
|
-
And the working tree is committed as "test: revive by hand"
|
|
258
|
-
And step "tests.sweeping" from now on changes nothing
|
|
259
|
-
When gtd starts workflow "tests"
|
|
260
|
-
And the process is driven
|
|
261
|
-
Then the process rests at "idle"
|
|
262
|
-
|
|
263
|
-
Scenario: gtd door tests starts the workflow
|
|
264
|
-
When gtd opens door "tests"
|
|
265
|
-
And the process is driven
|
|
266
|
-
Then the process rests at "build.review.await-review"
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest"
|
|
2
|
-
import { DEFAULT_SKIP_PATTERN, skipsIn } from "./tests.js"
|
|
3
|
-
|
|
4
|
-
const skipped = (text: string): readonly number[] =>
|
|
5
|
-
skipsIn([{ path: "a", text }], new RegExp(DEFAULT_SKIP_PATTERN)).map((s) => s.line)
|
|
6
|
-
|
|
7
|
-
describe("the default skipPattern", () => {
|
|
8
|
-
it.each([
|
|
9
|
-
'it.skip("x", () => {})',
|
|
10
|
-
'test.skip("x", () => {})',
|
|
11
|
-
'describe.skip("x", () => {})',
|
|
12
|
-
'it.todo("x")',
|
|
13
|
-
'xit("x", () => {})',
|
|
14
|
-
'xdescribe("x", () => {})',
|
|
15
|
-
" @inmem @skip",
|
|
16
|
-
"@ignore",
|
|
17
|
-
])("reads %s as skipped", (line) => {
|
|
18
|
-
expect(skipped(line)).toEqual([1])
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it.each([
|
|
22
|
-
'it("x", () => {})',
|
|
23
|
-
'it.skipIf(windows)("x", () => {})',
|
|
24
|
-
'it.only("x", () => {})',
|
|
25
|
-
"@skipped-by-nobody",
|
|
26
|
-
"fixit()",
|
|
27
|
-
"user@skip.example",
|
|
28
|
-
' "it.skip(x)",',
|
|
29
|
-
"// xit( is how jasmine skips",
|
|
30
|
-
"@skip-ci",
|
|
31
|
-
])("does not read %s as skipped", (line) => {
|
|
32
|
-
expect(skipped(line)).toEqual([])
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
describe("skipsIn", () => {
|
|
37
|
-
it("names every skipped line by path and 1-based line, trimmed", () => {
|
|
38
|
-
expect(
|
|
39
|
-
skipsIn(
|
|
40
|
-
[
|
|
41
|
-
{ path: "a.test.ts", text: 'it("a", f)\n it.skip("b", f)\n' },
|
|
42
|
-
{ path: "b.feature", text: "@skip\nScenario: c" },
|
|
43
|
-
],
|
|
44
|
-
new RegExp(DEFAULT_SKIP_PATTERN),
|
|
45
|
-
),
|
|
46
|
-
).toEqual([
|
|
47
|
-
{ path: "a.test.ts", line: 2, text: 'it.skip("b", f)' },
|
|
48
|
-
{ path: "b.feature", line: 1, text: "@skip" },
|
|
49
|
-
])
|
|
50
|
-
})
|
|
51
|
-
})
|