test(ultraplan-local): failing tests for /ultracontinue Bug 1 (SC-1, SC-2)
Step 2 of v3.4.1 hot-fix plan. Establishes tests/commands/
directory and adds Pattern D structure tests against
commands/ultracontinue-local.md prose:
(a) Phase 1 must document Date.parse(updated_at) numeric sort
(b) Phase 0 must NOT use substring "contains --help" dispatch;
must reference parsed flags or positional[0]
(c) Phase 1 must reference auto-discovery as empty-args fallback
(d) Phase 1 must emit SC-2 diagnostic strings for .md positional arg
Tests (a), (b), (d) fail intentionally on current prose; Step 3
fix turns them green. Test (c) passes already (current Phase 1
prose says "non-empty" which matches the regex assertion).
This commit is contained in:
parent
7cdbcb7425
commit
06c0a0a86b
1 changed files with 156 additions and 0 deletions
156
plugins/ultraplan-local/tests/commands/ultracontinue.test.mjs
Normal file
156
plugins/ultraplan-local/tests/commands/ultracontinue.test.mjs
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
// tests/commands/ultracontinue.test.mjs
|
||||
// Regression tests for /ultracontinue-local (commands/ultracontinue-local.md).
|
||||
//
|
||||
// Steps 2 + 4 of the v3.4.1 hot-fix plan
|
||||
// (project 2026-05-04-v3.3.1-ultracontinue-fixes).
|
||||
//
|
||||
// Pattern mix:
|
||||
// - Pattern B (tmp-dir, mkdtempSync + try/finally) — fixture builds
|
||||
// - Pattern D (markdown structure) — assertions against command prose
|
||||
// - Hook integration via runHook + pre-bash-executor (Pattern C, Step 4)
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(HERE, '..', '..');
|
||||
const COMMAND_FILE = join(ROOT, 'commands', 'ultracontinue-local.md');
|
||||
|
||||
function readCommand() {
|
||||
return readFileSync(COMMAND_FILE, 'utf8');
|
||||
}
|
||||
|
||||
function extractPhase(commandText, phaseHeader) {
|
||||
// phaseHeader e.g. "## Phase 0 ", "## Phase 1 ", "## Phase 2 "
|
||||
const startIdx = commandText.indexOf(phaseHeader);
|
||||
if (startIdx === -1) return '';
|
||||
const rest = commandText.slice(startIdx);
|
||||
// Stop at the next "## Phase " (or "## Hard rules" — also a top-level break)
|
||||
const nextPhase = rest.search(/\n## (?:Phase |Hard )/);
|
||||
if (nextPhase === -1) return rest;
|
||||
return rest.slice(0, nextPhase);
|
||||
}
|
||||
|
||||
function inProgressState(updatedAtIso) {
|
||||
return {
|
||||
schema_version: 1,
|
||||
project: '.claude/projects/2026-05-04-fixture-a',
|
||||
next_session_brief_path: '.claude/projects/2026-05-04-fixture-a/brief.md',
|
||||
next_session_label: 'Session 2: in progress fixture',
|
||||
status: 'in_progress',
|
||||
updated_at: updatedAtIso,
|
||||
};
|
||||
}
|
||||
|
||||
function completedState(updatedAtIso) {
|
||||
return {
|
||||
schema_version: 1,
|
||||
project: '.claude/projects/2026-05-04-fixture-b',
|
||||
next_session_brief_path: '.claude/projects/2026-05-04-fixture-b/brief.md',
|
||||
next_session_label: 'Session N: completed fixture',
|
||||
status: 'completed',
|
||||
updated_at: updatedAtIso,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Step 2 — Bug 1 regression tests (SC-1, SC-2)
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
test('ultracontinue Bug 1 — Phase 1 documents auto-discovery sort by Date.parse(updated_at) DESC', () => {
|
||||
// Fixture-builds two project dirs and verifies our chosen sort key
|
||||
// matches what Phase 1 prose documents.
|
||||
const root = mkdtempSync(join(tmpdir(), 'ultracontinue-disc-'));
|
||||
try {
|
||||
const projectsRoot = join(root, '.claude', 'projects');
|
||||
mkdirSync(join(projectsRoot, '2026-05-04-fixture-a'), { recursive: true });
|
||||
mkdirSync(join(projectsRoot, '2026-05-04-fixture-b'), { recursive: true });
|
||||
|
||||
const inProgress = inProgressState('2026-05-04T18:00:00.000Z');
|
||||
const completed = completedState('2026-05-03T09:00:00.000Z');
|
||||
|
||||
writeFileSync(
|
||||
join(projectsRoot, '2026-05-04-fixture-a', '.session-state.local.json'),
|
||||
JSON.stringify(inProgress, null, 2),
|
||||
);
|
||||
writeFileSync(
|
||||
join(projectsRoot, '2026-05-04-fixture-b', '.session-state.local.json'),
|
||||
JSON.stringify(completed, null, 2),
|
||||
);
|
||||
|
||||
// Numeric sort by Date.parse — newest first.
|
||||
const candidates = [
|
||||
{ ...completed, _path: 'b' },
|
||||
{ ...inProgress, _path: 'a' },
|
||||
].sort((x, y) => Date.parse(y.updated_at) - Date.parse(x.updated_at));
|
||||
assert.equal(candidates[0]._path, 'a', 'newest in_progress fixture must win the sort');
|
||||
|
||||
const phase1 = extractPhase(readCommand(), '## Phase 1 ');
|
||||
assert.match(
|
||||
phase1,
|
||||
/Date\.parse/,
|
||||
'Phase 1 prose must document Date.parse-based sort (numeric, not lexicographic)',
|
||||
);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('ultracontinue Bug 1 — Phase 0 dispatches via parsed flags, not substring contains', () => {
|
||||
const phase0 = extractPhase(readCommand(), '## Phase 0 ');
|
||||
// Must NOT use the legacy "contains --help or -h" substring dispatch.
|
||||
assert.doesNotMatch(
|
||||
phase0,
|
||||
/contains\s+`?--help`?\s+or\s+`?-h`?/i,
|
||||
'Phase 0 must not dispatch via substring `contains` — use parsed flags / positional',
|
||||
);
|
||||
// Must reference parseArgs / flags['--help'] / positional[0] (parsed-arg dispatch).
|
||||
const referencesParsedDispatch =
|
||||
/flags\[\s*['"]--help['"]\s*\]/.test(phase0) ||
|
||||
/positional\[\s*0\s*\]/.test(phase0);
|
||||
assert.ok(
|
||||
referencesParsedDispatch,
|
||||
'Phase 0 must dispatch via parsed flags["--help"] or positional[0] === "-h"',
|
||||
);
|
||||
});
|
||||
|
||||
test('ultracontinue Bug 1 — Phase 1 documents empty-args path explicitly to auto-discovery', () => {
|
||||
const phase1 = extractPhase(readCommand(), '## Phase 1 ');
|
||||
// Some explicit text mentioning the empty / whitespace path so a future reader
|
||||
// can't misread Phase 0 as "fall through to usage on empty".
|
||||
assert.match(
|
||||
phase1,
|
||||
/\b(empty|whitespace)\b/i,
|
||||
'Phase 1 must explicitly handle the empty-args case (auto-discovery)',
|
||||
);
|
||||
assert.match(
|
||||
phase1,
|
||||
/auto-discover/i,
|
||||
'Phase 1 must reference auto-discovery as the empty-args fallback',
|
||||
);
|
||||
});
|
||||
|
||||
test('ultracontinue Bug 1 sub — Phase 1 emits SC-2 diagnostic for .md positional arg', () => {
|
||||
const phase1 = extractPhase(readCommand(), '## Phase 1 ');
|
||||
// SC-2 verbatim diagnostic strings.
|
||||
assert.match(
|
||||
phase1,
|
||||
/expected.*<project-dir>/i,
|
||||
'Phase 1 must mention "expected <project-dir>" in the .md-arg diagnostic',
|
||||
);
|
||||
assert.match(
|
||||
phase1,
|
||||
/did you mean to paste/i,
|
||||
'Phase 1 must mention "did you mean to paste" in the .md-arg diagnostic',
|
||||
);
|
||||
// Detection condition must reference .md.
|
||||
assert.match(
|
||||
phase1,
|
||||
/\.md\b/,
|
||||
'Phase 1 must detect .md positional arg (case for SC-2)',
|
||||
);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue