* test(#4657): add failing-first coverage for the ui probe's text_en channel Mirrors the #3717/#4156 test shape onto the UI adapter: a failing-first proposeConsiderations regression (Danish text + English text_en must classify as its English equivalent, not land in the #1110 unclassified sentinel), proposeElements/analyzeCoverage/CLI end-to-end pairs, fail-closed text_en validation cases (empty/whitespace/non-string, unconditional under an elements override), a ui-phase.md Step 9.5 workflow-prose contract test, a reference-doc Inputs parity test, and a fast-check property proving any cue-matching prose classifies identically under a cue-free Danish rendering plus text_en. All new assertions are RED until src/ui-consideration-probe.cts and the workflow/reference docs are updated. * fix(#4657): give the ui consideration probe a text_en language channel Element gains an optional text_en; classifyElement's own signature stays untouched (a locked, directly-tested export) and the text_en ?? text selection is pushed to the two classification call sites (proposeConsiderations, proposeElements) instead. text_en is validated fail-closed: an empty or whitespace-only value throws rather than silently winning the ?? fallback and degrading classification to zero kinds. Mirrors #3717/#4156 onto the UI adapter: ui-phase.md Step 9.5 gains the Non-English projects section (mirroring spec-phase Step 5.5) and the ELEMENTS_JSON shape comment documents the field with both zero-applicable guard arms named; the reference doc's Inputs section, the PROBE.ui CONTEXT predicate (with both derived indexes regenerated), and the nav-override test expectation stay in sync. The ui-phase contract test carries the site-scoped allow-test-rule marker and its cluster is registered in the test-file-count allowlist ratchet. Emitted-Drift-Ack-Growth: ui-phase.md — Non-English text_en section, ELEMENTS_JSON shape comment, and two-arm guard wording (#4657) * docs(#4657): backfill changeset PR number --------- Co-authored-by: sim <sim@local>
105 lines
4.8 KiB
JavaScript
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.GSD_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, '..', 'gsd-core', 'workflows', 'ui-phase.md');
|
|
const { proposeConsiderations } = require(
|
|
path.join(__dirname, '..', 'gsd-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 },
|
|
);
|
|
});
|
|
});
|