Files
msd-core/tests/msd-statusline-state.property.test.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00

172 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/**
* Property tests for the compact MSD-state status normalizer (#2162).
*
* shortMsdStatus() collapses free-text STATE.md statuses to a canonical
* keyword (or a capped first-word fallback). As a parsing/normalization
* contract it gets property coverage per the repo testing rules, alongside
* the example-based cases in msd-statusline.test.cjs.
*/
const { test, describe } = require('node:test');
const fc = require('./helpers/fast-check-setup.cjs');
const { shortMsdStatus } = require('../hooks/msd-statusline.js');
const CANONICAL = ['discussing', 'planning', 'executing', 'verifying', 'completed', 'paused'];
describe('shortMsdStatus properties (#2162)', () => {
test('totality: any string input yields null or a short non-empty string', () => {
fc.assert(
fc.property(fc.string(), (s) => {
const out = shortMsdStatus(s);
if (out === null) return true;
return typeof out === 'string' && out.length > 0 && out.length <= 16;
}),
);
});
test('canonical statuses are fixed points (paused shouts as PAUSED)', () => {
fc.assert(
fc.property(fc.constantFrom(...CANONICAL), (canonical) => {
const out = shortMsdStatus(canonical);
return canonical === 'paused' ? out === 'PAUSED' : out === canonical;
}),
);
});
test('output never contains whitespace or separator characters', () => {
// The compact line joins segments with ' · ' — a status containing
// whitespace or the separator would corrupt the segment structure.
fc.assert(
fc.property(fc.string(), (s) => {
const out = shortMsdStatus(s);
return out === null || !/[\s·—–]/.test(out);
}),
);
});
test('unrecognized free text falls back to its first word, capped at 16', () => {
// Alphabetic words that are not canonical and don't contain canonical
// trigger substrings exercise the fallback path deterministically.
const word = fc.stringMatching(/^[A-Za-z]{1,32}$/).filter((w) => {
const lower = w.toLowerCase();
return !CANONICAL.some((c) => lower.includes(c.slice(0, 4)));
});
fc.assert(
fc.property(word, word, (first, second) => {
const out = shortMsdStatus(`${first} ${second}`);
// The normalizer may still map some phrasings to a canonical keyword
// (e.g. synonym tables); otherwise the first word survives, capped.
return out === null || CANONICAL.concat('PAUSED').includes(out) || out === first.slice(0, 16);
}),
);
});
});
/**
* Property tests for STATE.md freshness marker derivation (#2734).
*
* These properties are asserted against `deriveStateFreshness`,
* `isValidStateHeadStamp`, `formatStateFreshness`, `formatMsdState`, and
* `formatMsdStateCompact` per `.msd/phase/feat-2734-statusline-state-freshness/50-test-matrix.md`
* rows P1-P5. None of these symbols exist on `hooks/msd-statusline.js` yet —
* this block is deliberately failing-first TDD.
*/
const {
STATE_HEAD_ADVISORY_COMMITS,
isValidStateHeadStamp,
deriveStateFreshness,
formatStateFreshness,
formatMsdState: formatMsdStateFull,
formatMsdStateCompact: formatMsdStateCompactFn,
} = require('../hooks/msd-statusline.js');
describe('state-head freshness properties (#2734)', () => {
test('derivationIsTotalOverArbitraryStamps', () => {
const root = require('node:os').tmpdir();
fc.assert(
fc.property(fc.string(), (stamp) => {
const ir = deriveStateFreshness(root, stamp);
if (!ir || typeof ir !== 'object') return false;
const keys = Object.keys(ir).sort().join(',');
if (keys !== ['commit_stale', 'commits_behind', 'state_head'].sort().join(',')) return false;
const cb = ir.commits_behind;
return cb === null || (Number.isInteger(cb) && cb >= 0);
}),
);
});
test('fenceAcceptsOnlyHexInRange', () => {
fc.assert(
fc.property(fc.string(), (s) => {
const expected = /^[0-9a-f]{4,40}$/i.test(String(s).trim());
return isValidStateHeadStamp(s) === expected;
}),
);
});
test('markerVisibilityIsMonotonicInCount', () => {
fc.assert(
fc.property(fc.nat({ max: 1000000 }), (n) => {
const out = formatStateFreshness({ commits_behind: n });
const shouldShow = n >= STATE_HEAD_ADVISORY_COMMITS;
return shouldShow ? out !== '' : out === '';
}),
);
});
test('unknownNeverDegradesToFresh', () => {
const root = require('node:os').tmpdir();
fc.assert(
fc.property(fc.string(), (stamp) => {
const ir = deriveStateFreshness(root, stamp);
return ir.commit_stale === null;
}),
);
});
test('renderersAreTotalOverArbitraryIr', () => {
const optionalString = fc.oneof(fc.constant(undefined), fc.string());
const optionalCount = fc.oneof(fc.constant(undefined), fc.nat({ max: 999 }));
const hasBadSeparator = (str) => {
const trimmed = str.trim();
if (trimmed.startsWith('·') || trimmed.endsWith('·')) return true;
return str.includes('··') || str.includes('· ·');
};
fc.assert(
fc.property(
fc.record({
status: optionalString,
phaseNum: optionalCount,
phaseTotal: optionalCount,
phaseName: optionalString,
milestone: optionalString,
milestoneName: optionalString,
percent: fc.oneof(fc.constant(undefined), fc.nat({ max: 100 })),
activePhase: optionalString,
nextAction: optionalString,
nextPhases: fc.oneof(fc.constant(undefined), fc.array(fc.string(), { maxLength: 3 })),
completedPhases: optionalCount,
totalPhases: optionalCount,
noActiveWorkstream: fc.boolean(),
freshness: fc.record({
state_head: fc.oneof(fc.constant(null), fc.string()),
commits_behind: fc.oneof(fc.constant(null), fc.nat({ max: 1000000 })),
commit_stale: fc.oneof(fc.constant(null), fc.boolean()),
}),
}),
(s) => {
const full = formatMsdStateFull(s);
const compact = formatMsdStateCompactFn(s);
if (typeof full !== 'string' || typeof compact !== 'string') return false;
return !hasBadSeparator(full) && !hasBadSeparator(compact);
},
),
);
});
});