@hyperfixi/testing-framework 3.3.0 → 4.0.1
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/CHANGELOG.md +199 -1
- package/package.json +9 -10
- package/src/agent-bench/README.md +48 -1
- package/src/agent-bench/agent-bench.test.ts +7 -0
- package/src/agent-bench/harness.ts +39 -50
- package/src/multilingual/README.md +30 -11
- package/src/multilingual/bare-render-fidelity.ts +1 -1
- package/src/multilingual/cli.ts +5 -3
- package/src/multilingual/direct-path-shapes-gate.ts +56 -0
- package/src/multilingual/direct-path-shapes.1.test.ts +12 -0
- package/src/multilingual/direct-path-shapes.2.test.ts +12 -0
- package/src/multilingual/direct-path-shapes.3.test.ts +12 -0
- package/src/multilingual/direct-path-shapes.cases.json +2621 -0
- package/src/multilingual/direct-path-shapes.ts +459 -0
- package/src/multilingual/orchestrator.ts +2 -1
- package/src/multilingual/render-fidelity.ts +1 -1
- package/src/multilingual/shipped-examples-execution.test.ts +32 -96
- package/src/multilingual/shipped-examples-execution.ts +31 -121
- package/src/multilingual/shipped-sources-engine.test.ts +19 -19
- package/src/multilingual/shipped-sources-localized.test.ts +87 -0
- package/src/multilingual/shipped-sources-validity.ts +87 -99
- package/src/multilingual/validators/execution-validator.test.ts +81 -23
- package/src/multilingual/validators/execution-validator.ts +135 -91
- package/src/multilingual/validators/parse-validator.ts +7 -20
- package/src/multilingual/value-matrix.accepted.test.ts +8 -14
- package/src/multilingual/value-matrix.isolation.test.ts +7 -6
- package/src/multilingual/value-matrix.ts +32 -84
- package/src/multilingual/shipped-sources-validity.test.ts +0 -95
|
@@ -42,14 +42,12 @@
|
|
|
42
42
|
* Each cell runs its English source on the real `hyperscript.org` engine: that
|
|
43
43
|
* result is the ORACLE. Then, on the same fixture:
|
|
44
44
|
*
|
|
45
|
-
* - `en` hyperfixi's English path (core's parser and runtime);
|
|
46
45
|
* - `en-rt` semantic's English parse rendered back to English, on upstream:
|
|
47
46
|
* where it fails, every translation inherits the loss;
|
|
48
|
-
* - `<L
|
|
49
|
-
* `
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* product for original _hyperscript users;
|
|
47
|
+
* - `<L>/up` each of 23 languages — `render(parse_en(src), L)` — through
|
|
48
|
+
* `@lokascript/hyperscript-adapter` (`preprocess`, back to
|
|
49
|
+
* English) on upstream: the multilingual product for original
|
|
50
|
+
* _hyperscript users;
|
|
53
51
|
* - `eng` the English source on `@hyperfixi/engine`, the engine that
|
|
54
52
|
* replaces core's;
|
|
55
53
|
* - `<L>/eng` the adapter's English (the string the `/up` lane runs) on
|
|
@@ -58,6 +56,15 @@
|
|
|
58
56
|
*
|
|
59
57
|
* A (cell, lane) pair FAILS when its result differs from the oracle's.
|
|
60
58
|
*
|
|
59
|
+
* A `<L>` lane ran each translation on core's direct path (compiled with
|
|
60
|
+
* `{ language: L }`) until Phase C2 of the engine cutover, which made text the
|
|
61
|
+
* multilingual interchange. It retired after the measurement the plan asked
|
|
62
|
+
* for: on the committed baseline no cell failed `<L>/eng` while passing `<L>`
|
|
63
|
+
* (the only foreign failures, three Italian `di` cells, failed all three).
|
|
64
|
+
* An `en` lane ran the English source on `@hyperfixi/core`'s parser and runtime
|
|
65
|
+
* until Phase C4, when it left with core's engine; its only failures were the
|
|
66
|
+
* eight accepted cells of core's `the X of Y as T` reading, which `eng` passes.
|
|
67
|
+
*
|
|
61
68
|
* ## The ratchet
|
|
62
69
|
* `baselines/value-matrix.json` lists every failing pair, per cell. The gate
|
|
63
70
|
* fails on a failing pair it does not list AND on a listed pair that now
|
|
@@ -69,14 +76,13 @@
|
|
|
69
76
|
* One jsdom window for the whole run; each lane resets `<body>` and the
|
|
70
77
|
* globals, installs the handler on a fresh button, clicks it, and reads
|
|
71
78
|
* `#out`. Variables are window globals (see GLOBALS), so no source needs a
|
|
72
|
-
* `set` prefix.
|
|
73
|
-
* within one macrotask.
|
|
79
|
+
* `set` prefix. Both engines run synchronously unless the source waits.
|
|
74
80
|
*
|
|
75
|
-
* A translation that loses a loop's condition can loop forever.
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
81
|
+
* A translation that loses a loop's condition can loop forever. Upstream has
|
|
82
|
+
* no cap and blocks the thread, so every upstream run gets an evaluation
|
|
83
|
+
* budget (see EVAL_BUDGET). The new engine has no cap and no hook for one: a
|
|
84
|
+
* string that spent the budget on upstream is not run on it, and its `/eng`
|
|
85
|
+
* lane reads `✗budget` too.
|
|
80
86
|
*
|
|
81
87
|
* Node-only: imports the real `hyperscript.org` build off disk and needs the
|
|
82
88
|
* node vitest environment (see the shipped-examples gate for why).
|
|
@@ -738,12 +744,14 @@ export const FOREIGN_LANGUAGES = [
|
|
|
738
744
|
|
|
739
745
|
/** Every lane, in report order. */
|
|
740
746
|
export const LANES: readonly string[] = [
|
|
741
|
-
'en',
|
|
742
747
|
'en-rt',
|
|
743
748
|
'eng',
|
|
744
|
-
...FOREIGN_LANGUAGES.flatMap(language => [
|
|
749
|
+
...FOREIGN_LANGUAGES.flatMap(language => [`${language}/up`, `${language}/eng`]),
|
|
745
750
|
];
|
|
746
751
|
|
|
752
|
+
/** The lanes a cell runs per language it does not skip. */
|
|
753
|
+
export const LANES_PER_LANGUAGE = 2;
|
|
754
|
+
|
|
747
755
|
/**
|
|
748
756
|
* Upstream evaluations allowed per run. A cell uses well under a hundred; a
|
|
749
757
|
* loop whose condition a translation lost uses them all and stops, where it
|
|
@@ -787,8 +795,8 @@ export interface MatrixEngines {
|
|
|
787
795
|
*
|
|
788
796
|
* Until `close()`, the console is silenced and unhandled rejections are
|
|
789
797
|
* trapped: thousands of lanes fail by design, both engines report a failure
|
|
790
|
-
* on the console, and
|
|
791
|
-
* in it rejects a promise nobody holds. The process's own rejection
|
|
798
|
+
* on the console, and a handler that waits on something runs on after its
|
|
799
|
+
* click, so an error in it rejects a promise nobody holds. The process's own rejection
|
|
792
800
|
* listeners (vitest's, under test) are set aside for the run and restored.
|
|
793
801
|
*/
|
|
794
802
|
export async function initMatrixEngines(): Promise<MatrixEngines> {
|
|
@@ -815,7 +823,6 @@ export async function initMatrixEngines(): Promise<MatrixEngines> {
|
|
|
815
823
|
};
|
|
816
824
|
process.on('unhandledRejection', trap);
|
|
817
825
|
|
|
818
|
-
const { hyperscript } = await import('@hyperfixi/core');
|
|
819
826
|
const { parseSemantic, render } = await import('@lokascript/semantic');
|
|
820
827
|
const { preprocess } = await import('@lokascript/hyperscript-adapter');
|
|
821
828
|
const require = createRequire(import.meta.url);
|
|
@@ -836,13 +843,6 @@ export async function initMatrixEngines(): Promise<MatrixEngines> {
|
|
|
836
843
|
|
|
837
844
|
const window = dom.window;
|
|
838
845
|
const document = window.document;
|
|
839
|
-
type Ast = Parameters<typeof hyperscript.execute>[0];
|
|
840
|
-
|
|
841
|
-
// hyperfixi keeps its global variables in one Map that every context shares,
|
|
842
|
-
// and writes a window global there (`increment n`). It outlives the run, and
|
|
843
|
-
// it shadows window's copy, so every later lane would read the write.
|
|
844
|
-
const coreGlobals = hyperscript.createContext().globals;
|
|
845
|
-
const coreGlobalsAtStart = new Map(coreGlobals);
|
|
846
846
|
const headAtStart = document.head.innerHTML;
|
|
847
847
|
const names = collidingNames();
|
|
848
848
|
|
|
@@ -859,8 +859,6 @@ export async function initMatrixEngines(): Promise<MatrixEngines> {
|
|
|
859
859
|
html.innerHTML = `<head>${headAtStart}</head><body></body>`;
|
|
860
860
|
}
|
|
861
861
|
document.body.innerHTML = FIXTURE;
|
|
862
|
-
coreGlobals.clear();
|
|
863
|
-
for (const [name, value] of coreGlobalsAtStart) coreGlobals.set(name, value);
|
|
864
862
|
for (const [name, make] of Object.entries(GLOBALS)) {
|
|
865
863
|
const value = make();
|
|
866
864
|
Reflect.set(window, name, value);
|
|
@@ -893,15 +891,6 @@ export async function initMatrixEngines(): Promise<MatrixEngines> {
|
|
|
893
891
|
};
|
|
894
892
|
const onUpstream = (source: string): string => onHost(upstream, source);
|
|
895
893
|
|
|
896
|
-
/** Install a compiled handler on hyperfixi, click, and settle. */
|
|
897
|
-
const onHyperfixi = async (ast: Ast): Promise<string> => {
|
|
898
|
-
const button = reset();
|
|
899
|
-
await hyperscript.execute(ast, hyperscript.createContext(button));
|
|
900
|
-
click(button);
|
|
901
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
902
|
-
return read();
|
|
903
|
-
};
|
|
904
|
-
|
|
905
894
|
const guard = async (run: () => Promise<string> | string): Promise<string> => {
|
|
906
895
|
try {
|
|
907
896
|
return await run();
|
|
@@ -920,12 +909,6 @@ export async function initMatrixEngines(): Promise<MatrixEngines> {
|
|
|
920
909
|
}
|
|
921
910
|
const lanes = result.lanes;
|
|
922
911
|
|
|
923
|
-
lanes.en = await guard(async () => {
|
|
924
|
-
const compiled = hyperscript.compileSync(cell.source);
|
|
925
|
-
if (!compiled.ok || !compiled.ast) return '✗compile';
|
|
926
|
-
return onHyperfixi(compiled.ast);
|
|
927
|
-
});
|
|
928
|
-
|
|
929
912
|
const english = parseSemantic(cell.source, 'en').node;
|
|
930
913
|
lanes['en-rt'] = await guard(() =>
|
|
931
914
|
english ? onUpstream(render(english, 'en')) : '✗untranslatable'
|
|
@@ -941,17 +924,11 @@ export async function initMatrixEngines(): Promise<MatrixEngines> {
|
|
|
941
924
|
code = null;
|
|
942
925
|
}
|
|
943
926
|
if (code === null) {
|
|
944
|
-
lanes[language] = '✗untranslatable';
|
|
945
927
|
lanes[`${language}/up`] = '✗untranslatable';
|
|
946
928
|
lanes[`${language}/eng`] = '✗untranslatable';
|
|
947
929
|
continue;
|
|
948
930
|
}
|
|
949
931
|
const translated = code;
|
|
950
|
-
lanes[language] = await guard(async () => {
|
|
951
|
-
const compiled = await hyperscript.compile(translated, { language });
|
|
952
|
-
if (!compiled.ok || !compiled.ast) return '✗compile';
|
|
953
|
-
return onHyperfixi(compiled.ast);
|
|
954
|
-
});
|
|
955
932
|
// The adapter's English, once, for both hosts.
|
|
956
933
|
const adapted = await guard(() => preprocess(translated, language));
|
|
957
934
|
const up = (lanes[`${language}/up`] = adapted.startsWith('✗')
|
|
@@ -992,9 +969,8 @@ export async function runValueMatrix(cells: readonly MatrixCell[]): Promise<Cell
|
|
|
992
969
|
|
|
993
970
|
export interface BaselineEntry {
|
|
994
971
|
/**
|
|
995
|
-
* The failing lanes, space-separated, in LANES order, with
|
|
996
|
-
* `*
|
|
997
|
-
* `*eng` for all 23 on the new engine.
|
|
972
|
+
* The failing lanes, space-separated, in LANES order, with two shorthands:
|
|
973
|
+
* `*up` for all 23 languages on upstream, `*eng` for all 23 on the new engine.
|
|
998
974
|
*/
|
|
999
975
|
lanes: string;
|
|
1000
976
|
/** Where the loss sits, for reading the burn-down (see familyOf); not asserted. */
|
|
@@ -1014,7 +990,6 @@ export interface ValueMatrixBaseline {
|
|
|
1014
990
|
entries: Record<string, BaselineEntry>;
|
|
1015
991
|
}
|
|
1016
992
|
|
|
1017
|
-
const DIRECT_LANES: readonly string[] = FOREIGN_LANGUAGES;
|
|
1018
993
|
const ADAPTER_LANES: readonly string[] = FOREIGN_LANGUAGES.map(language => `${language}/up`);
|
|
1019
994
|
const ENGINE_LANES: readonly string[] = FOREIGN_LANGUAGES.map(language => `${language}/eng`);
|
|
1020
995
|
|
|
@@ -1022,17 +997,14 @@ const ENGINE_LANES: readonly string[] = FOREIGN_LANGUAGES.map(language => `${lan
|
|
|
1022
997
|
export function compressLanes(lanes: readonly string[]): string {
|
|
1023
998
|
const set = new Set(lanes);
|
|
1024
999
|
const out: string[] = [];
|
|
1025
|
-
const direct = DIRECT_LANES.every(l => set.has(l));
|
|
1026
1000
|
const adapter = ADAPTER_LANES.every(l => set.has(l));
|
|
1027
1001
|
const engine = ENGINE_LANES.every(l => set.has(l));
|
|
1028
1002
|
for (const lane of LANES) {
|
|
1029
1003
|
if (!set.has(lane)) continue;
|
|
1030
|
-
if (direct && DIRECT_LANES.includes(lane)) continue;
|
|
1031
1004
|
if (adapter && ADAPTER_LANES.includes(lane)) continue;
|
|
1032
1005
|
if (engine && ENGINE_LANES.includes(lane)) continue;
|
|
1033
1006
|
out.push(lane);
|
|
1034
1007
|
}
|
|
1035
|
-
if (direct) out.push('*direct');
|
|
1036
1008
|
if (adapter) out.push('*up');
|
|
1037
1009
|
if (engine) out.push('*eng');
|
|
1038
1010
|
return out.join(' ');
|
|
@@ -1042,8 +1014,7 @@ export function compressLanes(lanes: readonly string[]): string {
|
|
|
1042
1014
|
export function expandLanes(text: string): string[] {
|
|
1043
1015
|
const out: string[] = [];
|
|
1044
1016
|
for (const token of text.split(' ').filter(Boolean)) {
|
|
1045
|
-
if (token === '*
|
|
1046
|
-
else if (token === '*up') out.push(...ADAPTER_LANES);
|
|
1017
|
+
if (token === '*up') out.push(...ADAPTER_LANES);
|
|
1047
1018
|
else if (token === '*eng') out.push(...ENGINE_LANES);
|
|
1048
1019
|
else out.push(token);
|
|
1049
1020
|
}
|
|
@@ -1058,12 +1029,10 @@ export function failingLanes(result: CellResult): string[] {
|
|
|
1058
1029
|
/**
|
|
1059
1030
|
* Where a cell's failure sits, from which lanes fail:
|
|
1060
1031
|
*
|
|
1061
|
-
* - `core` core's English run differs from upstream's;
|
|
1062
1032
|
* - `semantic-en` semantic's English parse loses it (`en-rt`), so every
|
|
1063
1033
|
* translation inherits the loss;
|
|
1064
|
-
* - `translation` some foreign lanes
|
|
1065
|
-
*
|
|
1066
|
-
* - `adapter` upstream's foreign lanes only;
|
|
1034
|
+
* - `translation` some foreign lanes on upstream: the adapter's English
|
|
1035
|
+
* for that language says something else;
|
|
1067
1036
|
* - `engine` the new engine differs from upstream on the same text:
|
|
1068
1037
|
* its English run, or a language's `/eng` lane without
|
|
1069
1038
|
* its `/up` lane.
|
|
@@ -1073,16 +1042,8 @@ export function failingLanes(result: CellResult): string[] {
|
|
|
1073
1042
|
export function familyOf(lanes: readonly string[]): string {
|
|
1074
1043
|
const set = new Set(lanes);
|
|
1075
1044
|
const parts: string[] = [];
|
|
1076
|
-
if (set.has('en')) parts.push('core');
|
|
1077
1045
|
if (set.has('en-rt')) parts.push('semantic-en');
|
|
1078
|
-
else {
|
|
1079
|
-
const direct = FOREIGN_LANGUAGES.filter(l => set.has(l));
|
|
1080
|
-
const adapter = FOREIGN_LANGUAGES.filter(l => set.has(`${l}/up`));
|
|
1081
|
-
const both = direct.filter(l => adapter.includes(l));
|
|
1082
|
-
if (both.length) parts.push('translation');
|
|
1083
|
-
if (direct.length > both.length && !set.has('en')) parts.push('direct-path');
|
|
1084
|
-
if (adapter.length > both.length) parts.push('adapter');
|
|
1085
|
-
}
|
|
1046
|
+
else if (FOREIGN_LANGUAGES.some(l => set.has(`${l}/up`))) parts.push('translation');
|
|
1086
1047
|
if (set.has('eng') || FOREIGN_LANGUAGES.some(l => set.has(`${l}/eng`) !== set.has(`${l}/up`))) {
|
|
1087
1048
|
parts.push('engine');
|
|
1088
1049
|
}
|
|
@@ -1101,26 +1062,13 @@ export const ACCEPTED: ReadonlyArray<{
|
|
|
1101
1062
|
lanes: string;
|
|
1102
1063
|
reason: string;
|
|
1103
1064
|
}> = [
|
|
1104
|
-
{
|
|
1105
|
-
cells: [
|
|
1106
|
-
...['put', 'set', 'while', 'times', 'increment'].map(
|
|
1107
|
-
p => `${p}|the textContent of #a as Int`
|
|
1108
|
-
),
|
|
1109
|
-
// The same difference with a reference owner (PR 108); upstream's
|
|
1110
|
-
// `window as Int` is null, and in a loop bound both read 0 iterations.
|
|
1111
|
-
...['put', 'set', 'increment'].map(p => `${p}|the scrollY of window as Int`),
|
|
1112
|
-
],
|
|
1113
|
-
lanes: 'en *direct',
|
|
1114
|
-
reason:
|
|
1115
|
-
'known difference: core converts the property, upstream the target (core/docs/UPSTREAM-KNOWN-DIFFS.md)',
|
|
1116
|
-
},
|
|
1117
1065
|
{
|
|
1118
1066
|
cells: [
|
|
1119
1067
|
'increment|#a.textContent',
|
|
1120
1068
|
'increment|#a.textContent + 2',
|
|
1121
1069
|
'increment|#a.textContent as Int',
|
|
1122
1070
|
],
|
|
1123
|
-
lanes: 'it
|
|
1071
|
+
lanes: 'it/up it/eng',
|
|
1124
1072
|
reason:
|
|
1125
1073
|
'ambiguity: it `di` is both `by` and `of`, so `incrementare i di #a.textContent` also says `increment i of #a.textContent`',
|
|
1126
1074
|
},
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shipped-sources validity gate (see shipped-sources-validity.ts for the why).
|
|
3
|
-
*
|
|
4
|
-
* Compiles every hyperscript source we ship in `examples/` and the doc trees,
|
|
5
|
-
* and ratchets on the recovers-with-errors state (`ok: true` with a non-empty
|
|
6
|
-
* `errors`). Three assertions, matching the canonical-validity gates:
|
|
7
|
-
* 1. sanity — sources were actually found and mostly compile clean;
|
|
8
|
-
* 2. no NEW recovering source appears outside the committed allowlist;
|
|
9
|
-
* 3. no allowlisted key has silently become clean (stale entries must be
|
|
10
|
-
* removed so the list only ever shrinks).
|
|
11
|
-
*
|
|
12
|
-
* To update after an intentional fix: re-run and rewrite
|
|
13
|
-
* `baselines/shipped-sources-validity.json`. Note the allowlist key embeds a
|
|
14
|
-
* hash of the source, so FIXING a source changes its key — the entry goes
|
|
15
|
-
* stale and assertion 3 makes removing it mandatory.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { readFileSync } from 'node:fs';
|
|
19
|
-
import { fileURLToPath } from 'node:url';
|
|
20
|
-
import path from 'node:path';
|
|
21
|
-
import { JSDOM } from 'jsdom';
|
|
22
|
-
import { describe, it, expect, beforeAll } from 'vitest';
|
|
23
|
-
import {
|
|
24
|
-
checkShippedSourcesValidity,
|
|
25
|
-
type ShippedSourcesResult,
|
|
26
|
-
type CompileForValidity,
|
|
27
|
-
} from './shipped-sources-validity';
|
|
28
|
-
|
|
29
|
-
interface AllowlistDoc {
|
|
30
|
-
allowedRecovered: Array<{
|
|
31
|
-
key: string;
|
|
32
|
-
file: string;
|
|
33
|
-
error: string;
|
|
34
|
-
upstream: string;
|
|
35
|
-
reason: string;
|
|
36
|
-
}>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const baselinePath = path.resolve(
|
|
40
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
|
41
|
-
'../../baselines/shipped-sources-validity.json'
|
|
42
|
-
);
|
|
43
|
-
const allowlist = JSON.parse(readFileSync(baselinePath, 'utf8')) as AllowlistDoc;
|
|
44
|
-
const allowed = new Set(allowlist.allowedRecovered.map(e => e.key));
|
|
45
|
-
|
|
46
|
-
describe('shipped-sources validity gate', () => {
|
|
47
|
-
let result: ShippedSourcesResult;
|
|
48
|
-
|
|
49
|
-
beforeAll(async () => {
|
|
50
|
-
// Import core through its built entry, the same surface a consumer gets.
|
|
51
|
-
const core = (await import('@hyperfixi/core')) as unknown as {
|
|
52
|
-
hyperscript: { compileSync: CompileForValidity };
|
|
53
|
-
};
|
|
54
|
-
const dom = new JSDOM('<!doctype html><html><body></body></html>');
|
|
55
|
-
result = checkShippedSourcesValidity(
|
|
56
|
-
code => core.hyperscript.compileSync(code),
|
|
57
|
-
dom.window.document as unknown as Parameters<typeof checkShippedSourcesValidity>[1]
|
|
58
|
-
);
|
|
59
|
-
}, 120_000);
|
|
60
|
-
|
|
61
|
-
it('finds and compiles the shipped sources (sanity: trees walked, extraction working)', () => {
|
|
62
|
-
// Guards the silent-zero failure mode: a broken walk or extractor would
|
|
63
|
-
// make every other assertion vacuously pass.
|
|
64
|
-
expect(result.checked).toBeGreaterThan(100);
|
|
65
|
-
expect(result.clean).toBeGreaterThan(100);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('has no NEW source that parses with recovered errors outside the allowlist', () => {
|
|
69
|
-
const unexpected = result.findings.filter(f => !allowed.has(f.key));
|
|
70
|
-
expect(
|
|
71
|
-
unexpected,
|
|
72
|
-
unexpected.length
|
|
73
|
-
? `\nNew shipped sources that parse ok:true WITH errors (fix the source, or allowlist with a reason):\n` +
|
|
74
|
-
unexpected
|
|
75
|
-
.map(f => ` [${f.key}]\n "${f.excerpt}"\n -> ${f.error}`)
|
|
76
|
-
.join('\n') +
|
|
77
|
-
`\n\nAsk the real hyperscript.org engine for a second opinion before deciding:\n` +
|
|
78
|
-
`upstream REJECTING means the source is malformed; upstream ACCEPTING means this is a hyperfixi parser defect.\n` +
|
|
79
|
-
`See loadCanonicalParser() in canonical-validity.ts.`
|
|
80
|
-
: ''
|
|
81
|
-
).toEqual([]);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('has no stale allowlist entries (a now-clean source must be removed so the list ratchets down)', () => {
|
|
85
|
-
const stillFailing = new Set(result.findings.map(f => f.key));
|
|
86
|
-
const stale = allowlist.allowedRecovered.map(e => e.key).filter(key => !stillFailing.has(key));
|
|
87
|
-
expect(
|
|
88
|
-
stale,
|
|
89
|
-
stale.length
|
|
90
|
-
? `\nThese allowlisted sources no longer recover-with-errors (fixed, or edited — the key embeds a source hash).\n` +
|
|
91
|
-
`Remove them from baselines/shipped-sources-validity.json:\n ${stale.join('\n ')}`
|
|
92
|
-
: ''
|
|
93
|
-
).toEqual([]);
|
|
94
|
-
});
|
|
95
|
-
});
|