Files
msd-core/tests/ui-consideration-probe-ui-phase-contract.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

105 lines
4.8 KiB
JavaScript

// ui-phase.md Step 9.5 is the deployed workflow runtime contract under assertion; its text IS
// what the runtime loads. These checks lock the #4657 text_en wiring so the Non-English
// guidance cannot silently rot the way it did for the edge adapter before #3717 — mirroring
// tests/edge-probe-spec-phase-contract.test.cjs onto the ui-phase probe step.
// Assertions scope to the extracted Step 9.5 block to avoid false positives from incidental
// mentions elsewhere in the file.
'use strict';
process.env.MSD_TEST_MODE = '1';
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const fc = require('./helpers/fast-check-setup.cjs');
const UI_PHASE_PATH = path.join(__dirname, '..', 'msd-core', 'workflows', 'ui-phase.md');
const { proposeConsiderations } = require(
path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'ui-consideration-probe.cjs'),
);
function readUiPhase() {
// allow-test-rule: source-text-is-the-product (#4657)
// The workflow file's text IS the deployed runtime contract; assertions below match on it.
return fs.readFileSync(UI_PHASE_PATH, 'utf8');
}
// Slice the Step 9.5 block: from the "## 9.5." heading to the next "## " heading. Scopes
// assertions to the probe step only.
function extractStep95Block(content) {
const startIdx = content.indexOf('## 9.5.');
if (startIdx === -1) return '';
const rest = content.slice(startIdx + '## 9.5.'.length);
const nextHeading = rest.search(/\n## /);
if (nextHeading === -1) return content.slice(startIdx);
return content.slice(startIdx, startIdx + '## 9.5.'.length + nextHeading);
}
describe('ui-consideration-probe ui-phase contract: text_en (#4657)', () => {
test('Step 9.5 documents the Non-English text_en remedy (mirrors spec-phase Step 5.5, #3717)', () => {
const block = extractStep95Block(readUiPhase());
assert.ok(block.length > 0, 'Step 9.5 block must be extractable from ui-phase.md');
assert.match(block, /Non-English/i, 'Step 9.5 must carry the Non-English projects guidance');
assert.match(block, /text_en/, 'Step 9.5 must name text_en — the classifier-facing translation field');
assert.match(block, /response_language/, 'Step 9.5 must tie the remedy to response_language projects');
assert.match(block, /every element/i, 'Step 9.5 must instruct populating text_en for EVERY element, not only UI-obvious ones');
assert.match(block, /engine input/i, 'text_en is engine input, never user-facing output (ADR-550)');
});
test('Step 9.5 ELEMENTS_JSON shape comment includes the optional text_en key', () => {
const block = extractStep95Block(readUiPhase());
assert.match(block, /"text_en"\?/, 'the element record shape must document text_en? alongside id/text/elements');
});
// Property (deterministic: pinned seed, bounded runs, counterexample printed on failure):
// for English element prose built from the classifier's own cue vocabulary, a cue-free
// Danish rendering as `text` plus the original English as `text_en` must classify
// identically to the English original — and the Danish rendering alone must stay
// unclassified. Document-shaped inputs: the English words ARE cue words UI_CUES matches;
// the Danish translations are verified cue-free by the property itself on every run.
test('property: text_en carries classification for any cue-matching prose under a cue-free Danish rendering', () => {
const enToDa = [
['form', 'formular'],
['input', 'indtastning'],
['list', 'liste'],
['table', 'tabel'],
['tabs', 'faner'],
['pagination', 'sideinddeling'],
['image', 'billede'],
['gallery', 'galleri'],
['button', 'knap'],
['dropdown', 'rullemenu'],
['heading', 'overskrift'],
['title', 'titel'],
];
const enWords = enToDa.map(([en]) => en);
const daOf = (w) => enToDa.find(([en]) => en === w)[1];
fc.assert(
fc.property(
fc.constantFrom(...enWords),
fc.constantFrom(...enWords),
fc.nat(2),
(w1, w2, extra) => {
const english = [w1, w2, ...Array(extra).fill(w2)].join(' ');
const danish = [w1, w2, ...Array(extra).fill(w2)].map(daOf).join(' ');
const cats = (el) => proposeConsiderations(el).map((c) => c.category);
assert.deepEqual(
cats({ id: 'P', text: danish }),
['unclassified'],
`the Danish rendering must be cue-free (counterexample text: ${JSON.stringify(danish)})`,
);
assert.deepEqual(
cats({ id: 'P', text: danish, text_en: english }),
cats({ id: 'P', text: english }),
`translated element must classify as its English original (counterexample text_en: ${JSON.stringify(english)})`,
);
},
),
{ seed: 4657, numRuns: 200 },
);
});
});