@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
|
@@ -5,33 +5,46 @@
|
|
|
5
5
|
* parse that EXECUTES wrongly: the session-5 probe found translations whose
|
|
6
6
|
* parse looks plausible but whose runtime behavior diverges from the English
|
|
7
7
|
* reference — a dropped destination role lands the effect on `me` instead of
|
|
8
|
-
* `#menu`, a
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* the
|
|
8
|
+
* `#menu`, a quoted literal keeps its quotes in the DOM. This validator
|
|
9
|
+
* executes a CURATED subset of corpus patterns in jsdom and compares the
|
|
10
|
+
* resulting DOM effects against the English reference's effects.
|
|
11
|
+
*
|
|
12
|
+
* What runs where (since Phase C2 of the engine cutover): the en reference is
|
|
13
|
+
* the English source on upstream `_hyperscript` (the oracle, as for the value
|
|
14
|
+
* matrix and the shipped-examples gate); a translation is its foreign text
|
|
15
|
+
* AS WRITTEN on `@hyperfixi/engine` with `@lokascript/hyperscript-adapter`'s
|
|
16
|
+
* plugin installed and `lang` on the element — the product's multilingual
|
|
17
|
+
* path, where the engine translates the script as it reads it. Until C2 both
|
|
18
|
+
* ran on core's runtime through `parseSemantic → buildAST` (the direct path,
|
|
19
|
+
* which retires with core's parser). Measured before the switch, on a fresh
|
|
20
|
+
* populate: the engine matched upstream on all 49 en references, and all
|
|
21
|
+
* 1,127 translations matched on the engine; core's R2 path differed from
|
|
22
|
+
* upstream on 13 en references (core-only `show`/`toggle @attr`/`with
|
|
23
|
+
* *opacity`/`swap` semantics), which is why the oracle is upstream.
|
|
13
24
|
*
|
|
14
25
|
* Effect signature: a before/after diff of every element's classes, attributes,
|
|
15
|
-
* inline style, and leaf text
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* (
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* behavior diverges in its effects regardless.
|
|
26
|
+
* inline style, and leaf text, plus (here only) the document order of the
|
|
27
|
+
* id'd elements when it changed — upstream's `swap #a with #b` exchanges the
|
|
28
|
+
* two elements and changes nothing else. Selectors/classes/attribute names are
|
|
29
|
+
* code (not translated), so signatures are directly comparable across
|
|
30
|
+
* languages. A pattern scores 1 when its signature EXACTLY matches the en
|
|
31
|
+
* reference's (binary — extra effects are as wrong as missing ones). Errors
|
|
32
|
+
* the host reports are recorded for diagnostics but are NOT part of the match;
|
|
33
|
+
* a script that damages behavior diverges in its effects regardless.
|
|
23
34
|
*
|
|
24
|
-
* Determinism: a fresh JSDOM document
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* effects are excluded (no usable reference — same semantics as R0/R1).
|
|
35
|
+
* Determinism: a fresh JSDOM document per execution, no network, no timers
|
|
36
|
+
* beyond a fixed settle window. Patterns whose en reference errors or produces
|
|
37
|
+
* no effects are excluded (no usable reference — same semantics as R0/R1).
|
|
28
38
|
*/
|
|
29
39
|
|
|
40
|
+
import { createRequire } from 'node:module';
|
|
41
|
+
import path from 'node:path';
|
|
42
|
+
import { pathToFileURL } from 'node:url';
|
|
30
43
|
import { JSDOM } from 'jsdom';
|
|
31
|
-
import { parseSemantic, buildAST } from '@lokascript/semantic';
|
|
32
44
|
import { getAllPatterns, getTranslationsByLanguage } from '@hyperfixi/patterns-reference';
|
|
33
45
|
import type { LanguageCode } from '../types';
|
|
34
46
|
import { snapshot, diffSnapshots } from '../effect-signature';
|
|
47
|
+
import { installGlobals } from '../shipped-examples-execution';
|
|
35
48
|
|
|
36
49
|
/**
|
|
37
50
|
* The curated execution subset: simple, deterministic, fixture-friendly
|
|
@@ -230,7 +243,7 @@ export const EXECUTION_SUBSET: readonly string[] = [
|
|
|
230
243
|
* The SAME fixture (plus the same per-pattern setup) is used for the en
|
|
231
244
|
* reference and every translation, so signatures are comparable.
|
|
232
245
|
*/
|
|
233
|
-
const FIXTURE_HTML = `<!DOCTYPE html><html><body>
|
|
246
|
+
export const FIXTURE_HTML = `<!DOCTYPE html><html><body>
|
|
234
247
|
<div class="card"><button id="btn">Click</button></div>
|
|
235
248
|
<div id="item"></div>
|
|
236
249
|
<div id="menu"></div>
|
|
@@ -254,7 +267,7 @@ const FIXTURE_HTML = `<!DOCTYPE html><html><body>
|
|
|
254
267
|
</body></html>`;
|
|
255
268
|
|
|
256
269
|
/** Per-pattern fixture preconditions (applied identically for every language). */
|
|
257
|
-
const PATTERN_SETUP: Record<string, (doc: Document) => void> = {
|
|
270
|
+
export const PATTERN_SETUP: Record<string, (doc: Document) => void> = {
|
|
258
271
|
// `remove .highlight from me` needs the class present to have an effect.
|
|
259
272
|
'remove-class-basic': doc => doc.getElementById('btn')!.classList.add('highlight'),
|
|
260
273
|
// `show #modal` needs the modal hidden.
|
|
@@ -282,6 +295,19 @@ const PATTERN_SETUP: Record<string, (doc: Document) => void> = {
|
|
|
282
295
|
out.textContent = '5';
|
|
283
296
|
doc.getElementById('btn')!.before(out);
|
|
284
297
|
},
|
|
298
|
+
// `if #modal exists show #modal …` changes nothing on upstream while #modal
|
|
299
|
+
// is visible (core's `show` added a `show` class, which is what the
|
|
300
|
+
// reference used to record). Hidden, the `show` is observable.
|
|
301
|
+
'if-exists': doc => {
|
|
302
|
+
(doc.getElementById('modal') as HTMLElement).style.display = 'none';
|
|
303
|
+
},
|
|
304
|
+
// `toggle .open on closest .accordion-item` throws on upstream when there is
|
|
305
|
+
// no such ancestor (core fell back to `me`), and the second command never
|
|
306
|
+
// runs. #btn inside an item, as on a real accordion header.
|
|
307
|
+
'accordion-toggle': doc => doc.querySelector('.card')!.classList.add('accordion-item'),
|
|
308
|
+
// `toggle .open on next .panel` throws on upstream when nothing follows
|
|
309
|
+
// (core fell back to `me`), so the reference errored and was excluded.
|
|
310
|
+
'toggle-aria-expanded': doc => doc.getElementById('panel')!.classList.add('panel'),
|
|
285
311
|
};
|
|
286
312
|
|
|
287
313
|
/**
|
|
@@ -294,11 +320,11 @@ const PATTERN_SETUP: Record<string, (doc: Document) => void> = {
|
|
|
294
320
|
* reference and every translation (it is harness-supplied, not translated), so
|
|
295
321
|
* effect signatures stay comparable across languages.
|
|
296
322
|
*/
|
|
297
|
-
interface PatternTrigger {
|
|
323
|
+
export interface PatternTrigger {
|
|
298
324
|
readonly event: string;
|
|
299
325
|
readonly detail?: unknown;
|
|
300
326
|
}
|
|
301
|
-
const PATTERN_TRIGGER: Record<string, PatternTrigger> = {
|
|
327
|
+
export const PATTERN_TRIGGER: Record<string, PatternTrigger> = {
|
|
302
328
|
// `on success put event.detail.message into #sr-announce set @role to "alert"
|
|
303
329
|
// on #sr-announce` — a custom event carrying the announcement text. The fixed
|
|
304
330
|
// message lands in #sr-announce's text; role=alert is set on the same node.
|
|
@@ -311,18 +337,27 @@ export interface ExecutionResult {
|
|
|
311
337
|
language: LanguageCode;
|
|
312
338
|
/** Sorted effect lines (see snapshot/diff). Empty on error or no effect. */
|
|
313
339
|
effects: string[];
|
|
314
|
-
/**
|
|
340
|
+
/** Errors the host reported (parse or runtime), when any. */
|
|
315
341
|
error?: string;
|
|
316
342
|
/** 1 = exact signature match vs the en reference, 0 = mismatch. Undefined for en itself or when no usable reference exists. */
|
|
317
343
|
executionFidelity?: number;
|
|
318
344
|
}
|
|
319
345
|
|
|
320
|
-
// Effect-signature machinery (serializeElement/snapshot/diffSnapshots)
|
|
321
|
-
// ../effect-signature.ts
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
346
|
+
// Effect-signature machinery (serializeElement/snapshot/diffSnapshots) lives in
|
|
347
|
+
// ../effect-signature.ts, shared with the shipped-examples execution gate so the
|
|
348
|
+
// two can never disagree about what a DOM effect is.
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Where an execution runs: `engine` is `@hyperfixi/engine` with the
|
|
352
|
+
* multilingual plugin (English or any language); `upstream` is
|
|
353
|
+
* `hyperscript.org`, the oracle, which runs English only.
|
|
354
|
+
*/
|
|
355
|
+
export type ExecutionHost = 'engine' | 'upstream';
|
|
356
|
+
|
|
357
|
+
/** What this needs of a host that reads scripts off attributes. */
|
|
358
|
+
interface ScriptHost {
|
|
359
|
+
processNode(element: Element): void;
|
|
360
|
+
}
|
|
326
361
|
|
|
327
362
|
/** How long to let the dispatched handler settle (ms). The subset contains no
|
|
328
363
|
* waits/transitions/fetches, so this only needs to drain micro/macrotasks. */
|
|
@@ -331,42 +366,53 @@ const SETTLE_MS = 20;
|
|
|
331
366
|
/** Per-execution hard timeout — a hung execution must not hang the sweep. */
|
|
332
367
|
const EXECUTION_TIMEOUT_MS = 5000;
|
|
333
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Both hosts, loaded once per process: the engine's grammar is a module
|
|
371
|
+
* singleton, and `register` re-runs every module against it.
|
|
372
|
+
*/
|
|
373
|
+
let hostsLoading: Promise<Record<ExecutionHost, ScriptHost>> | null = null;
|
|
374
|
+
function loadHosts(): Promise<Record<ExecutionHost, ScriptHost>> {
|
|
375
|
+
hostsLoading ??= (async () => {
|
|
376
|
+
const engine = await import('@hyperfixi/engine');
|
|
377
|
+
engine.register(...engine.everything);
|
|
378
|
+
const { hyperscriptI18n } = await import('@lokascript/hyperscript-adapter');
|
|
379
|
+
engine.api.use(hyperscriptI18n());
|
|
380
|
+
const require = createRequire(import.meta.url);
|
|
381
|
+
const esm = path.join(path.dirname(require.resolve('hyperscript.org')), '_hyperscript.esm.js');
|
|
382
|
+
const upstream = (await import(pathToFileURL(esm).href)).default as ScriptHost;
|
|
383
|
+
return { engine: engine.api, upstream };
|
|
384
|
+
})();
|
|
385
|
+
return hostsLoading;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** The ids of id'd elements in document order — what `swap` changes. */
|
|
389
|
+
function idOrder(document: Document): string {
|
|
390
|
+
return Array.from(document.body.querySelectorAll('[id]'), el => el.id).join(',');
|
|
391
|
+
}
|
|
392
|
+
|
|
334
393
|
/**
|
|
335
394
|
* R2 execution validator. `initialize()` MUST complete before `execute()`:
|
|
336
|
-
* it bootstraps jsdom globals and only then loads
|
|
337
|
-
*
|
|
395
|
+
* it bootstraps jsdom globals and only then loads the hosts, whose module
|
|
396
|
+
* evaluation touches DOM constructors.
|
|
338
397
|
*/
|
|
339
398
|
export class ExecutionValidator {
|
|
340
|
-
private
|
|
341
|
-
Runtime: new () => { execute(ast: unknown, ctx: unknown): Promise<unknown> };
|
|
342
|
-
createContext: (el: HTMLElement) => unknown;
|
|
343
|
-
} | null = null;
|
|
399
|
+
private hosts: Record<ExecutionHost, ScriptHost> | null = null;
|
|
344
400
|
|
|
345
|
-
/** Errors
|
|
346
|
-
*
|
|
347
|
-
|
|
401
|
+
/** Errors the hosts report during the current execution: parse and runtime
|
|
402
|
+
* errors go to console.error, and an async handler's rejection to the
|
|
403
|
+
* process. */
|
|
404
|
+
private hostErrors: string[] = [];
|
|
348
405
|
private trapInstalled = false;
|
|
349
406
|
|
|
350
407
|
async initialize(): Promise<void> {
|
|
351
|
-
if (this.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
// evaluates `Element` at module load). The per-execution fresh document
|
|
355
|
-
// replaces these for each run.
|
|
356
|
-
this.installGlobals(new JSDOM(FIXTURE_HTML));
|
|
357
|
-
|
|
358
|
-
const core = await import('@hyperfixi/core');
|
|
359
|
-
this.core = {
|
|
360
|
-
Runtime: core.Runtime as unknown as new () => {
|
|
361
|
-
execute(ast: unknown, ctx: unknown): Promise<unknown>;
|
|
362
|
-
},
|
|
363
|
-
createContext: core.createContext as unknown as (el: HTMLElement) => unknown,
|
|
364
|
-
};
|
|
408
|
+
if (this.hosts) return;
|
|
409
|
+
installGlobals(new JSDOM(FIXTURE_HTML));
|
|
410
|
+
this.hosts = await loadHosts();
|
|
365
411
|
|
|
366
412
|
if (!this.trapInstalled) {
|
|
367
413
|
process.on('unhandledRejection', (reason: unknown) => {
|
|
368
414
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
369
|
-
this.
|
|
415
|
+
this.hostErrors.push(msg.split('\n')[0] ?? msg);
|
|
370
416
|
});
|
|
371
417
|
this.trapInstalled = true;
|
|
372
418
|
}
|
|
@@ -376,25 +422,17 @@ export class ExecutionValidator {
|
|
|
376
422
|
return 'ExecutionValidator';
|
|
377
423
|
}
|
|
378
424
|
|
|
379
|
-
private installGlobals(dom: JSDOM): void {
|
|
380
|
-
const g = globalThis as Record<string, unknown>;
|
|
381
|
-
g.window = dom.window;
|
|
382
|
-
g.document = dom.window.document;
|
|
383
|
-
g.Event = dom.window.Event;
|
|
384
|
-
g.CustomEvent = dom.window.CustomEvent;
|
|
385
|
-
g.HTMLElement = dom.window.HTMLElement;
|
|
386
|
-
g.Element = dom.window.Element;
|
|
387
|
-
g.Node = dom.window.Node;
|
|
388
|
-
g.MutationObserver = dom.window.MutationObserver;
|
|
389
|
-
g.getComputedStyle = dom.window.getComputedStyle;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
425
|
/**
|
|
393
|
-
* Execute one pattern
|
|
426
|
+
* Execute one pattern's source in a fresh fixture and return its effect
|
|
394
427
|
* signature. Never throws; failures come back as `error`.
|
|
395
428
|
*/
|
|
396
|
-
async execute(
|
|
397
|
-
|
|
429
|
+
async execute(
|
|
430
|
+
codeExampleId: string,
|
|
431
|
+
code: string,
|
|
432
|
+
lang: string,
|
|
433
|
+
host: ExecutionHost = 'engine'
|
|
434
|
+
): Promise<ExecutionResult> {
|
|
435
|
+
const run = this.executeInner(codeExampleId, code, lang, host);
|
|
398
436
|
const timeout = new Promise<ExecutionResult>(resolve =>
|
|
399
437
|
setTimeout(
|
|
400
438
|
() =>
|
|
@@ -413,40 +451,44 @@ export class ExecutionValidator {
|
|
|
413
451
|
private async executeInner(
|
|
414
452
|
codeExampleId: string,
|
|
415
453
|
code: string,
|
|
416
|
-
lang: string
|
|
454
|
+
lang: string,
|
|
455
|
+
host: ExecutionHost
|
|
417
456
|
): Promise<ExecutionResult> {
|
|
418
|
-
if (!this.
|
|
457
|
+
if (!this.hosts) await this.initialize();
|
|
419
458
|
const base: ExecutionResult = {
|
|
420
459
|
codeExampleId,
|
|
421
460
|
language: lang as LanguageCode,
|
|
422
461
|
effects: [],
|
|
423
462
|
};
|
|
463
|
+
if (host === 'upstream' && lang !== 'en') {
|
|
464
|
+
return { ...base, error: 'upstream runs English only' };
|
|
465
|
+
}
|
|
424
466
|
|
|
425
467
|
const dom = new JSDOM(FIXTURE_HTML);
|
|
426
|
-
|
|
468
|
+
installGlobals(dom);
|
|
427
469
|
const document = dom.window.document;
|
|
428
470
|
PATTERN_SETUP[codeExampleId]?.(document);
|
|
429
471
|
const btn = document.getElementById('btn')!;
|
|
430
|
-
|
|
472
|
+
// The plugin reads the language off the closest `lang`, as on a page; on
|
|
473
|
+
// <html>, outside the snapshot, so it is no part of any signature.
|
|
474
|
+
if (lang !== 'en') document.documentElement.setAttribute('lang', lang);
|
|
475
|
+
btn.setAttribute('_', code);
|
|
476
|
+
this.hostErrors = [];
|
|
431
477
|
|
|
432
|
-
//
|
|
433
|
-
// multi-language sweep that is
|
|
478
|
+
// Both hosts log every failing script via console — across a full
|
|
479
|
+
// multi-language sweep that is noise; keep only the errors, as diagnostics.
|
|
434
480
|
const saved = { log: console.log, warn: console.warn, error: console.error };
|
|
435
|
-
console.log = console.warn =
|
|
481
|
+
console.log = console.warn = () => {};
|
|
482
|
+
console.error = (...args: unknown[]) => {
|
|
483
|
+
const first = args.map(String).join(' ').split('\n')[0] ?? '';
|
|
484
|
+
this.hostErrors.push(first);
|
|
485
|
+
};
|
|
436
486
|
try {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
return { ...base, error: `parse failed (confidence ${parsed.confidence?.toFixed(2)})` };
|
|
440
|
-
}
|
|
441
|
-
const built = buildAST(parsed.node);
|
|
442
|
-
if (!built.ast) return { ...base, error: 'buildAST returned no AST' };
|
|
443
|
-
|
|
444
|
-
const runtime = new this.core!.Runtime();
|
|
445
|
-
const ctx = this.core!.createContext(btn as unknown as HTMLElement);
|
|
446
|
-
// Installs the event handler (or runs bare commands immediately).
|
|
447
|
-
await runtime.execute(built.ast, ctx);
|
|
487
|
+
this.hosts![host].processNode(btn);
|
|
488
|
+
await new Promise(r => setTimeout(r, 0));
|
|
448
489
|
|
|
449
490
|
const before = snapshot(document);
|
|
491
|
+
const orderBefore = idOrder(document);
|
|
450
492
|
const trigger = PATTERN_TRIGGER[codeExampleId];
|
|
451
493
|
const triggerEvent =
|
|
452
494
|
trigger?.detail !== undefined
|
|
@@ -454,11 +496,13 @@ export class ExecutionValidator {
|
|
|
454
496
|
: new dom.window.Event(trigger?.event ?? 'click', { bubbles: true });
|
|
455
497
|
btn.dispatchEvent(triggerEvent);
|
|
456
498
|
await new Promise(r => setTimeout(r, SETTLE_MS));
|
|
457
|
-
const after = snapshot(document);
|
|
458
499
|
|
|
459
|
-
const effects = diffSnapshots(before,
|
|
460
|
-
|
|
461
|
-
|
|
500
|
+
const effects = diffSnapshots(before, snapshot(document));
|
|
501
|
+
const orderAfter = idOrder(document);
|
|
502
|
+
if (orderAfter !== orderBefore) effects.push(`order[${orderAfter}]`);
|
|
503
|
+
effects.sort();
|
|
504
|
+
if (this.hostErrors.length > 0) {
|
|
505
|
+
return { ...base, effects, error: this.hostErrors.join('; ') };
|
|
462
506
|
}
|
|
463
507
|
return { ...base, effects };
|
|
464
508
|
} catch (e: unknown) {
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Parse Validator - Validates hyperscript parsing across languages
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { MultilingualHyperscript } from '@hyperfixi/core/multilingual';
|
|
6
5
|
import type { SemanticNode } from '@lokascript/semantic';
|
|
7
|
-
import { fillSchemaDefaults } from '@lokascript/semantic';
|
|
6
|
+
import { fillSchemaDefaults, parseSemantic } from '@lokascript/semantic';
|
|
8
7
|
import type { PatternTranslation, ParseResult, Validator } from '../types';
|
|
9
8
|
import {
|
|
10
9
|
collectActions,
|
|
@@ -19,21 +18,12 @@ import {
|
|
|
19
18
|
* Validates that hyperscript patterns parse correctly using the semantic parser.
|
|
20
19
|
*/
|
|
21
20
|
export class ParseValidator implements Validator<ParseResult[]> {
|
|
22
|
-
private ml: MultilingualHyperscript;
|
|
23
|
-
private initialized = false;
|
|
24
|
-
|
|
25
|
-
constructor() {
|
|
26
|
-
this.ml = new MultilingualHyperscript();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
21
|
/**
|
|
30
|
-
* Initialize the validator
|
|
22
|
+
* Initialize the validator. Nothing to load: the semantic parser is a
|
|
23
|
+
* static import (it was reached through core's MultilingualHyperscript,
|
|
24
|
+
* whose `parse` was this same call, until Phase C2).
|
|
31
25
|
*/
|
|
32
|
-
async initialize(): Promise<void> {
|
|
33
|
-
if (this.initialized) return;
|
|
34
|
-
await this.ml.initialize();
|
|
35
|
-
this.initialized = true;
|
|
36
|
-
}
|
|
26
|
+
async initialize(): Promise<void> {}
|
|
37
27
|
|
|
38
28
|
/**
|
|
39
29
|
* Get validator name
|
|
@@ -65,11 +55,8 @@ export class ParseValidator implements Validator<ParseResult[]> {
|
|
|
65
55
|
const startTime = performance.now();
|
|
66
56
|
|
|
67
57
|
try {
|
|
68
|
-
// Parse the hyperscript
|
|
69
|
-
const semanticNode = (
|
|
70
|
-
pattern.hyperscript,
|
|
71
|
-
pattern.language
|
|
72
|
-
)) as SemanticNode;
|
|
58
|
+
// Parse the hyperscript. Confidence is not filtered here.
|
|
59
|
+
const semanticNode = parseSemantic(pattern.hyperscript, pattern.language).node;
|
|
73
60
|
|
|
74
61
|
if (!semanticNode) {
|
|
75
62
|
return {
|
|
@@ -16,39 +16,33 @@ import {
|
|
|
16
16
|
import { loadBaseline } from './value-matrix-gate';
|
|
17
17
|
|
|
18
18
|
describe('value matrix: accepted pairs', () => {
|
|
19
|
-
const [
|
|
19
|
+
const [ambiguity] = ACCEPTED;
|
|
20
20
|
|
|
21
21
|
it('tags a cell whose failing lanes are all accepted', () => {
|
|
22
|
-
expect(acceptedReason('
|
|
23
|
-
|
|
24
|
-
);
|
|
25
|
-
expect(acceptedReason('increment|#a.textContent', ['it', 'it/up'])).toBe(ambiguity!.reason);
|
|
26
|
-
expect(acceptedReason('increment|#a.textContent', ['it', 'it/up', 'it/eng'])).toBe(
|
|
27
|
-
ambiguity!.reason
|
|
28
|
-
);
|
|
22
|
+
expect(acceptedReason('increment|#a.textContent', ['it/up'])).toBe(ambiguity!.reason);
|
|
23
|
+
expect(acceptedReason('increment|#a.textContent', ['it/up', 'it/eng'])).toBe(ambiguity!.reason);
|
|
29
24
|
});
|
|
30
25
|
|
|
31
26
|
it('leaves it open when one failing lane is not', () => {
|
|
32
|
-
expect(acceptedReason('increment|#a.textContent', ['it', 'es'])).toBeUndefined();
|
|
33
|
-
expect(acceptedReason('put|the textContent of #a as Int', ['en', 'es/up'])).toBeUndefined();
|
|
27
|
+
expect(acceptedReason('increment|#a.textContent', ['it/up', 'es/up'])).toBeUndefined();
|
|
34
28
|
});
|
|
35
29
|
|
|
36
30
|
it('a regenerated baseline carries the reason, and counts the pairs', () => {
|
|
37
31
|
const result = (lanes: Record<string, string>): CellResult => ({
|
|
38
32
|
id: 'increment|#a.textContent',
|
|
39
33
|
want: '7',
|
|
40
|
-
lanes: {
|
|
34
|
+
lanes: { eng: '7', 'es/up': '7', ...lanes },
|
|
41
35
|
});
|
|
42
|
-
const kept = baselineFrom([result({ it: '1', 'it/
|
|
36
|
+
const kept = baselineFrom([result({ 'it/up': '1', 'it/eng': '1' })], '');
|
|
43
37
|
expect(kept.entries['increment|#a.textContent']?.accepted).toBe(ambiguity!.reason);
|
|
44
38
|
expect(kept.accepted).toBe(2);
|
|
45
|
-
const open = baselineFrom([result({ it: '1', es: '1' })], '');
|
|
39
|
+
const open = baselineFrom([result({ 'it/up': '1', 'es/up': '1' })], '');
|
|
46
40
|
expect(open.entries['increment|#a.textContent']?.accepted).toBeUndefined();
|
|
47
41
|
expect(open.accepted).toBe(0);
|
|
48
42
|
});
|
|
49
43
|
|
|
50
44
|
it('accepts only the cells it names', () => {
|
|
51
|
-
expect(acceptedReason('put
|
|
45
|
+
expect(acceptedReason('put|#a.textContent', ['it/up'])).toBeUndefined();
|
|
52
46
|
});
|
|
53
47
|
|
|
54
48
|
it('names cells the generator makes, and the baseline lists them as accepted', () => {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The value matrix runs every (cell, lane) pair in one process, so no run may
|
|
3
3
|
* see another's writes. Two ways one did:
|
|
4
|
-
* -
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* - a run writes a window global (`increment n`); `@hyperfixi/core` also kept
|
|
5
|
+
* global variables in a Map every context shared, so one lane's write made
|
|
6
|
+
* every later run read `n` as 7 (that lane left with core's engine in C4,
|
|
7
|
+
* and the reset still restores every global);
|
|
7
8
|
* - a lane can remove the body (tr read `increment i by 2 * 2` as
|
|
8
9
|
* `increment *`), and the next run's reset then threw.
|
|
9
10
|
*
|
|
@@ -41,9 +42,9 @@ describe('value matrix: runs are isolated', () => {
|
|
|
41
42
|
cell('writes', 'on click increment n then put n into #out')
|
|
42
43
|
);
|
|
43
44
|
const after = await engines.runCell(read);
|
|
44
|
-
expect(writes.lanes.
|
|
45
|
+
expect(writes.lanes.eng).toBe('7');
|
|
45
46
|
expect(before.want).toBe('6');
|
|
46
|
-
expect(before.lanes.
|
|
47
|
+
expect(before.lanes.eng).toBe('6');
|
|
47
48
|
expect(after.lanes).toEqual(before.lanes);
|
|
48
49
|
}, 120_000);
|
|
49
50
|
|
|
@@ -51,7 +52,7 @@ describe('value matrix: runs are isolated', () => {
|
|
|
51
52
|
const before = await engines.runCell(read);
|
|
52
53
|
await engines.runCell(cell('removes', 'on click remove document.body'));
|
|
53
54
|
const after = await engines.runCell(read);
|
|
54
|
-
expect(before.lanes.
|
|
55
|
+
expect(before.lanes.eng).toBe('6');
|
|
55
56
|
expect(after.lanes).toEqual(before.lanes);
|
|
56
57
|
}, 120_000);
|
|
57
58
|
});
|