@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.
Files changed (28) hide show
  1. package/CHANGELOG.md +199 -1
  2. package/package.json +9 -10
  3. package/src/agent-bench/README.md +48 -1
  4. package/src/agent-bench/agent-bench.test.ts +7 -0
  5. package/src/agent-bench/harness.ts +39 -50
  6. package/src/multilingual/README.md +30 -11
  7. package/src/multilingual/bare-render-fidelity.ts +1 -1
  8. package/src/multilingual/cli.ts +5 -3
  9. package/src/multilingual/direct-path-shapes-gate.ts +56 -0
  10. package/src/multilingual/direct-path-shapes.1.test.ts +12 -0
  11. package/src/multilingual/direct-path-shapes.2.test.ts +12 -0
  12. package/src/multilingual/direct-path-shapes.3.test.ts +12 -0
  13. package/src/multilingual/direct-path-shapes.cases.json +2621 -0
  14. package/src/multilingual/direct-path-shapes.ts +459 -0
  15. package/src/multilingual/orchestrator.ts +2 -1
  16. package/src/multilingual/render-fidelity.ts +1 -1
  17. package/src/multilingual/shipped-examples-execution.test.ts +32 -96
  18. package/src/multilingual/shipped-examples-execution.ts +31 -121
  19. package/src/multilingual/shipped-sources-engine.test.ts +19 -19
  20. package/src/multilingual/shipped-sources-localized.test.ts +87 -0
  21. package/src/multilingual/shipped-sources-validity.ts +87 -99
  22. package/src/multilingual/validators/execution-validator.test.ts +81 -23
  23. package/src/multilingual/validators/execution-validator.ts +135 -91
  24. package/src/multilingual/validators/parse-validator.ts +7 -20
  25. package/src/multilingual/value-matrix.accepted.test.ts +8 -14
  26. package/src/multilingual/value-matrix.isolation.test.ts +7 -6
  27. package/src/multilingual/value-matrix.ts +32 -84
  28. 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 mis-built AST throws `Unknown command: into`, a quoted literal
9
- * keeps its quotes in the DOM. This validator executes a CURATED subset of
10
- * corpus patterns in jsdom (semantic parse → buildAST → runtime.execute →
11
- * dispatch the trigger event) and compares the resulting DOM effects against
12
- * the English reference's effects.
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. Selectors/classes/attribute names are code (not
16
- * translated), so signatures are directly comparable across languages. A
17
- * pattern scores 1 when its signature EXACTLY matches the en reference's
18
- * (binary — extra effects are as wrong as missing ones). Trapped runtime
19
- * errors are recorded for diagnostics but are NOT part of the match: their
20
- * attribution depends on unhandled-rejection timing (racy), while the effect
21
- * snapshot is synchronous and deterministic. A mis-built AST that damages
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 and a fresh Runtime per execution, no
25
- * network, no timers beyond a fixed settle window; the probe measured two full
26
- * sweeps byte-identical. Patterns whose en reference errors or produces no
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
- /** Parse/build/runtime error, when any. */
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) moved to
321
- // ../effect-signature.ts (imported at the top), shared with the
322
- // shipped-examples execution gate so the two can never disagree about what a
323
- // DOM effect is. Notes that used to live on the local copies (leaf-text-only
324
- // rationale; body's own stable key, added in wave 3b when the runtime learned
325
- // to resolve `body`) moved with them.
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 `@hyperfixi/core`, whose
337
- * module evaluation touches `Element`/`document`.
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 core: {
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 thrown inside dispatched listeners surface as unhandled rejections
346
- * (the handler is async); trap them into the current execution's sink. */
347
- private listenerErrors: string[] = [];
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.core) return;
352
-
353
- // Bootstrap DOM globals BEFORE importing @hyperfixi/core (its dist
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.listenerErrors.push(msg);
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 translation in a fresh fixture and return its effect
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(codeExampleId: string, code: string, lang: string): Promise<ExecutionResult> {
397
- const run = this.executeInner(codeExampleId, code, lang);
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.core) await this.initialize();
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
- this.installGlobals(dom);
468
+ installGlobals(dom);
427
469
  const document = dom.window.document;
428
470
  PATTERN_SETUP[codeExampleId]?.(document);
429
471
  const btn = document.getElementById('btn')!;
430
- this.listenerErrors = [];
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
- // The runtime logs every failing command via console — across a full
433
- // multi-language sweep that is pure noise; silence it for the execution.
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 = console.error = () => {};
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
- const parsed = parseSemantic(code, lang);
438
- if (!parsed.node || parsed.confidence < 0.5) {
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, after);
460
- if (this.listenerErrors.length > 0) {
461
- return { ...base, effects, error: `runtime: ${this.listenerErrors.join('; ')}` };
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 = (await this.ml.parse(
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 [known, ambiguity] = ACCEPTED;
19
+ const [ambiguity] = ACCEPTED;
20
20
 
21
21
  it('tags a cell whose failing lanes are all accepted', () => {
22
- expect(acceptedReason('put|the textContent of #a as Int', ['en', 'es', 'ja'])).toBe(
23
- known!.reason
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: { en: '7', es: '7', ...lanes },
34
+ lanes: { eng: '7', 'es/up': '7', ...lanes },
41
35
  });
42
- const kept = baselineFrom([result({ it: '1', 'it/up': '1' })], '');
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|textContent of #a', ['en'])).toBeUndefined();
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
- * - hyperfixi keeps global variables in a Map that every context shares, so
5
- * one lane's `increment n` made every later run read `n` as 7, core's own
6
- * English included;
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.en).toBe('7');
45
+ expect(writes.lanes.eng).toBe('7');
45
46
  expect(before.want).toBe('6');
46
- expect(before.lanes.en).toBe('6');
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.en).toBe('6');
55
+ expect(before.lanes.eng).toBe('6');
55
56
  expect(after.lanes).toEqual(before.lanes);
56
57
  }, 120_000);
57
58
  });