@vmz/test 0.0.2 → 0.0.4

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/README.md CHANGED
@@ -26,24 +26,29 @@ safe server boundaries, SSR that does not needlessly replay, and application beh
26
26
 
27
27
  ## One scenario, several kinds of evidence
28
28
 
29
- | Surface | Evidence VMZ can collect |
30
- |---|---|
31
- | Compile | Diagnostics, graph identities, and generated boundaries |
32
- | Logic | State transitions, derived work, and cancellation |
33
- | Browser | Real input, semantic locators, DOM, accessibility, and screenshots |
34
- | Router | Matched RouteId, layout retention, loading, errors, and disposal |
35
- | SSR / Resume | HTML, serialized state, event entries, and replay avoidance |
36
- | Deployment | Client/server reachability, capabilities, artifacts, and traces |
29
+ | Surface | Evidence VMZ can collect |
30
+ |--------------|--------------------------------------------------------------------|
31
+ | Compile | Diagnostics, graph identities, and generated boundaries |
32
+ | Logic | State transitions, derived work, and cancellation |
33
+ | Browser | Real input, semantic locators, DOM, accessibility, and screenshots |
34
+ | Router | Matched RouteId, layout retention, loading, errors, and disposal |
35
+ | SSR / Resume | HTML, serialized state, event entries, and replay avoidance |
36
+ | Deployment | Client/server reachability, capabilities, artifacts, and traces |
37
37
 
38
38
  ### UI automation without a borrowed worldview
39
39
 
40
- VMZ can use a real browser protocol as transport while owning locator, action, waiting, and expectation semantics. Tests can find elements by role, label, text, RouteId, or stable test identity; CSS remains an escape hatch rather than the default contract.
40
+ VMZ can use a real browser protocol as transport while owning locator, action, waiting, and expectation semantics. Tests
41
+ can find elements by role, label, text, RouteId, or stable test identity; CSS remains an escape hatch rather than the
42
+ default contract.
41
43
 
42
- Auto-waiting can observe navigation, pending work, region commits, and server activity instead of guessing with arbitrary delays. A failure can connect the visible symptom to the application work that produced it.
44
+ Auto-waiting can observe navigation, pending work, region commits, and server activity instead of guessing with
45
+ arbitrary delays. A failure can connect the visible symptom to the application work that produced it.
43
46
 
44
47
  ### Precision is testable
45
48
 
46
- A fine-grained compiler should prove that nothing unrelated ran. VMZ tests can check that the intended binding changed, unrelated computations stayed idle, an obsolete generation was cancelled, and a disposed owner received no late write. That turns testing into a competitive feature rather than a bundled convenience.
49
+ A fine-grained compiler should prove that nothing unrelated ran. VMZ tests can check that the intended binding changed,
50
+ unrelated computations stayed idle, an obsolete generation was cancelled, and a disposed owner received no late write.
51
+ That turns testing into a competitive feature rather than a bundled convenience.
47
52
 
48
53
  ## License
49
54
 
package/dist/browser.d.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  /**
2
- * Browser Host for `vmz test --mode browser` (T2 close slice).
2
+ * Browser Host for `vmz test --mode browser` ( close slice).
3
3
  *
4
4
  * Real Chromium/Chrome via CDP. Transport may use puppeteer-core as a CDP
5
5
  * client — that is NOT the Playwright/Puppeteer *test model*. Manifest actions
6
6
  * and assertions remain the VMZ Browser Host protocol; same Direct schedule as
7
7
  * production (`__vmzCreate` in a real document).
8
8
  *
9
- * Design: 规划设计/vmz/16 §T2 · §5 浏览器连接
10
9
  */
11
10
  type Diag = {
12
11
  severity: string;
package/dist/browser.js CHANGED
@@ -1,12 +1,11 @@
1
1
  /**
2
- * Browser Host for `vmz test --mode browser` (T2 close slice).
2
+ * Browser Host for `vmz test --mode browser` ( close slice).
3
3
  *
4
4
  * Real Chromium/Chrome via CDP. Transport may use puppeteer-core as a CDP
5
5
  * client — that is NOT the Playwright/Puppeteer *test model*. Manifest actions
6
6
  * and assertions remain the VMZ Browser Host protocol; same Direct schedule as
7
7
  * production (`__vmzCreate` in a real document).
8
8
  *
9
- * Design: 规划设计/vmz/16 §T2 · §5 浏览器连接
10
9
  */
11
10
  import { spawn } from 'node:child_process';
12
11
  import fs from 'node:fs';
@@ -527,7 +526,7 @@ export async function runBrowserManifest(manifest, ctx) {
527
526
  }
528
527
  continue;
529
528
  }
530
- if (kind === 'graph' || kind === 'plan' || kind === 'diagnostic' || kind === 'view') {
529
+ if (kind === 'graph' || kind === 'plan' || kind === 'diagnostic' || kind === 'view' || kind === 'motion') {
531
530
  continue;
532
531
  }
533
532
  fail(`unknown browser assertion ${JSON.stringify(kind)}`);
package/dist/compile.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Compile-mode host for `vmz test` / `@vmz/test` (T1+).
2
+ * Compile-mode host for `vmz test` / `@vmz/test` (+).
3
3
  * Builds the project and checks graph/plan/view assertions against dist artifacts.
4
4
  */
5
5
  export type CreateWorkspaceFn = (opts: {
package/dist/compile.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Compile-mode host for `vmz test` / `@vmz/test` (T1+).
2
+ * Compile-mode host for `vmz test` / `@vmz/test` (+).
3
3
  * Builds the project and checks graph/plan/view assertions against dist artifacts.
4
4
  */
5
5
  import fs from 'node:fs';
@@ -248,15 +248,20 @@ export function runCompileManifest(manifest, ctx) {
248
248
  if (expect.create === true && !clientJs.includes('__vmzCreate')) {
249
249
  fail('missing __vmzCreate');
250
250
  }
251
- if (expect.plan === true && !clientJs.includes('__vmzPlan')) {
252
- fail('missing __vmzPlan');
251
+ if (expect.plan === true) {
252
+ // Plan identity is *.program.json; client `__vmzPlan` embed is opt-in (VMZ_EMIT_PLAN).
253
+ const hasClientPlan = clientJs.includes('__vmzPlan');
254
+ const hasProgramPlan = typeof prog.schema === 'string' || Boolean(arts.programPath) || Boolean(plan);
255
+ if (!hasClientPlan && !hasProgramPlan) {
256
+ fail('missing plan (client __vmzPlan or *.program.json)');
257
+ }
253
258
  }
254
259
  if (expect.serialize === true && !clientJs.includes('__vmzSerialize')) {
255
260
  fail('missing __vmzSerialize');
256
261
  }
257
262
  if (expect.noRender === true || expect.noRenderFallback === true) {
258
263
  if (clientJs.includes('prototype.render')) {
259
- fail('production client must not emit prototype.render (Gate 3)');
264
+ fail('production client must not emit prototype.render (production Direct emit)');
260
265
  }
261
266
  }
262
267
  if (typeof expect.includes === 'string' && !clientJs.includes(expect.includes)) {
@@ -400,6 +405,76 @@ export function runCompileManifest(manifest, ctx) {
400
405
  }
401
406
  continue;
402
407
  }
408
+ if (kind === 'motion') {
409
+ const motion = unit.motion || null;
410
+ const transitions = motion?.transitions || [];
411
+ const edges = unit.graph?.edges || [];
412
+ if (expect.status != null) {
413
+ if (!motion || motion.status !== expect.status) {
414
+ fail(`motion.status want ${expect.status}, got ${motion?.status}`);
415
+ }
416
+ }
417
+ if (expect.nonEmpty === true && transitions.length === 0) {
418
+ fail('motion.transitions empty');
419
+ }
420
+ if (Array.isArray(expect.kinds)) {
421
+ const have = new Set(transitions.map((t) => String(t.kind || '')));
422
+ for (const k of expect.kinds) {
423
+ if (!have.has(String(k)))
424
+ fail(`motion missing kind=${k}: ${[...have]}`);
425
+ }
426
+ }
427
+ if (typeof expect.token === 'string') {
428
+ if (!transitions.some((t) => t.token === expect.token)) {
429
+ fail(`motion missing token=${expect.token}: ${JSON.stringify(transitions)}`);
430
+ }
431
+ }
432
+ if (Array.isArray(expect.tokens)) {
433
+ for (const tok of expect.tokens) {
434
+ if (!transitions.some((t) => t.token === tok)) {
435
+ fail(`motion missing token=${tok}: ${JSON.stringify(transitions)}`);
436
+ }
437
+ }
438
+ }
439
+ if (expect.cancelable === true) {
440
+ if (!transitions.some((t) => t.cancelable === true)) {
441
+ fail(`motion missing cancelable transition: ${JSON.stringify(transitions)}`);
442
+ }
443
+ }
444
+ if (expect.generation === true) {
445
+ if (!transitions.some((t) => t.generation === true)) {
446
+ fail(`motion missing generation transition: ${JSON.stringify(transitions)}`);
447
+ }
448
+ }
449
+ if (typeof expect.reducedMotion === 'string') {
450
+ if (!transitions.every((t) => t.reduced_motion === expect.reducedMotion)) {
451
+ fail(`motion.reduced_motion want ${expect.reducedMotion}: ${JSON.stringify(transitions)}`);
452
+ }
453
+ }
454
+ if (expect.hasRegion === true) {
455
+ if (!transitions.some((t) => t.region != null)) {
456
+ fail(`motion missing region on transitions: ${JSON.stringify(transitions)}`);
457
+ }
458
+ }
459
+ if (expect.affectsRegion === true) {
460
+ const hit = edges.some((e) => e.kind === 'affects' && String(e.from).startsWith('motion:') && String(e.to).startsWith('region:'));
461
+ if (!hit)
462
+ fail(`missing motion→region affects edges: ${JSON.stringify(edges.filter((e) => String(e.from).startsWith('motion:')))}`);
463
+ }
464
+ if (Array.isArray(expect.cancelsFrom)) {
465
+ for (const from of expect.cancelsFrom) {
466
+ const hit = edges.some((e) => e.kind === 'cancels' && e.from === from && String(e.to).startsWith('motion:'));
467
+ if (!hit)
468
+ fail(`missing cancels from ${from}: ${JSON.stringify(edges.filter((e) => e.kind === 'cancels'))}`);
469
+ }
470
+ }
471
+ if (expect.planMotionTransition === true) {
472
+ const nodes = (plan?.nodes || []).filter((n) => n.kind === 'motion_transition');
473
+ if (!nodes.length)
474
+ fail('plan missing motion_transition nodes');
475
+ }
476
+ continue;
477
+ }
403
478
  if (kind === 'deployment') {
404
479
  const deployment = unit.deployment || null;
405
480
  const entries = deployment?.resume_entries ||
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Deployment host for `vmz test --mode deployment` (T3).
2
+ * Deployment host for `vmz test --mode deployment` .
3
3
  * Proves deployment IR + server capability isolation (client stubs vs #server body).
4
4
  */
5
5
  type Diag = {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Deployment host for `vmz test --mode deployment` (T3).
2
+ * Deployment host for `vmz test --mode deployment` .
3
3
  * Proves deployment IR + server capability isolation (client stubs vs #server body).
4
4
  */
5
5
  import fs from 'node:fs';
@@ -157,7 +157,7 @@ export function runDeploymentManifest(manifest, ctx) {
157
157
  }
158
158
  continue;
159
159
  }
160
- if (kind === 'graph' || kind === 'plan' || kind === 'diagnostic') {
160
+ if (kind === 'graph' || kind === 'plan' || kind === 'diagnostic' || kind === 'motion') {
161
161
  continue;
162
162
  }
163
163
  fail(`unknown deployment assertion ${JSON.stringify(kind)}`);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Discover VMZ native test manifests (T0).
2
+ * Discover VMZ native test manifests .
3
3
  */
4
4
  export declare function listManifestFiles(root: string): string[];
5
5
  export type DiscoveredManifest = Record<string, unknown> & {
package/dist/discover.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Discover VMZ native test manifests (T0).
2
+ * Discover VMZ native test manifests .
3
3
  */
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
package/dist/logic.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Logic-mode host — headless document (linkedom), same Direct __vmzCreate as production.
3
- * Design: 规划设计/vmz/16 — not Browser Host / Playwright.
4
3
  */
5
4
  export declare function installHeadlessDocument(): Document;
6
5
  export type LogicHost = {
package/dist/logic.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Logic-mode host — headless document (linkedom), same Direct __vmzCreate as production.
3
- * Design: 规划设计/vmz/16 — not Browser Host / Playwright.
4
3
  */
5
4
  import fs from 'node:fs';
6
5
  import path from 'node:path';
@@ -207,8 +206,19 @@ function runOneAssertion(a, host, fail) {
207
206
  return;
208
207
  }
209
208
  if (kind === 'domKeys') {
210
- const attr = String(expect.attr || 'data-vmz-key');
211
- const keys = [...host.app.querySelectorAll(`[${attr}]`)].map((el) => el.getAttribute(attr));
209
+ // Client Direct: `__vmzKey` expando. SSR HTML: `data-vmz-key` attr (expect.attr).
210
+ const attr = expect.attr != null ? String(expect.attr) : null;
211
+ const expando = expect.expando != null ? String(expect.expando) : attr ? null : '__vmzKey';
212
+ let keys = [];
213
+ if (attr) {
214
+ keys = [...host.app.querySelectorAll(`[${attr}]`)].map((el) => String(el.getAttribute(attr)));
215
+ }
216
+ else if (expando) {
217
+ keys = [...host.app.querySelectorAll('*')]
218
+ .map((el) => el[expando])
219
+ .filter((k) => k != null)
220
+ .map((k) => String(k));
221
+ }
212
222
  if (Array.isArray(expect.includes)) {
213
223
  for (const k of expect.includes) {
214
224
  if (!keys.includes(String(k)))
@@ -235,7 +245,7 @@ function runOneAssertion(a, host, fail) {
235
245
  }
236
246
  return;
237
247
  }
238
- if (kind === 'graph' || kind === 'plan' || kind === 'diagnostic' || kind === 'view') {
248
+ if (kind === 'graph' || kind === 'plan' || kind === 'diagnostic' || kind === 'view' || kind === 'motion') {
239
249
  return;
240
250
  }
241
251
  if (kind === 'assert') {
@@ -1,7 +1,6 @@
1
1
  /**
2
- * VMZ native test protocol (T0) — validators + report builders.
2
+ * VMZ native test protocol — validators + report builders.
3
3
  * Schema ids live in `@vmz/protocol` (mirrors Rust `vmz-protocol`).
4
- * Design: 规划设计/vmz/16 — not a Test IR; references Program Graph + Execution Plan.
5
4
  */
6
5
  export { EXECUTION_PLAN_REF_SCHEMA, PLAN_SCHEMA, TEST_ACTION_SCHEMA, TEST_ASSERTION_SCHEMA, TEST_MANIFEST_SCHEMA, TEST_PROTOCOL, TEST_REPORT_SCHEMA, testCatalog, } from '@vmz/protocol';
7
6
  export type TestMode = 'compile' | 'logic' | 'browser' | 'ssr' | 'resume' | 'deployment' | 'all';
@@ -16,7 +15,7 @@ export type ManifestValidation = {
16
15
  ok: false;
17
16
  error: string;
18
17
  };
19
- /** Validate a discovered manifest object (narrow T0 checks). */
18
+ /** Validate a discovered manifest object (narrow checks). */
20
19
  export declare function validateManifest(raw: unknown, file: string): ManifestValidation;
21
20
  export type TestReportEntryInput = {
22
21
  testId: string;
package/dist/protocol.js CHANGED
@@ -1,7 +1,6 @@
1
1
  /**
2
- * VMZ native test protocol (T0) — validators + report builders.
2
+ * VMZ native test protocol — validators + report builders.
3
3
  * Schema ids live in `@vmz/protocol` (mirrors Rust `vmz-protocol`).
4
- * Design: 规划设计/vmz/16 — not a Test IR; references Program Graph + Execution Plan.
5
4
  */
6
5
  import { EXECUTION_PLAN_REF_SCHEMA, PLAN_SCHEMA, TEST_MANIFEST_SCHEMA, TEST_REPORT_SCHEMA } from '@vmz/protocol';
7
6
  export { EXECUTION_PLAN_REF_SCHEMA, PLAN_SCHEMA, TEST_ACTION_SCHEMA, TEST_ASSERTION_SCHEMA, TEST_MANIFEST_SCHEMA, TEST_PROTOCOL, TEST_REPORT_SCHEMA, testCatalog, } from '@vmz/protocol';
@@ -26,7 +25,7 @@ export function parseModes(raw) {
26
25
  }
27
26
  return parts;
28
27
  }
29
- /** Validate a discovered manifest object (narrow T0 checks). */
28
+ /** Validate a discovered manifest object (narrow checks). */
30
29
  export function validateManifest(raw, file) {
31
30
  if (!raw || typeof raw !== 'object') {
32
31
  return { ok: false, error: `${file}: manifest must be an object` };
package/dist/resume.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Resume host for `vmz test --mode resume` (T2/T3 first slice).
2
+ * Resume host for `vmz test --mode resume` (/ first slice).
3
3
  * SSR shell → resume adopt → event patch; onMount must not run.
4
4
  */
5
5
  type Diag = {
package/dist/resume.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Resume host for `vmz test --mode resume` (T2/T3 first slice).
2
+ * Resume host for `vmz test --mode resume` (/ first slice).
3
3
  * SSR shell → resume adopt → event patch; onMount must not run.
4
4
  */
5
5
  import path from 'node:path';
@@ -241,7 +241,7 @@ export async function runResumeManifest(manifest, ctx) {
241
241
  }
242
242
  continue;
243
243
  }
244
- if (kind === 'graph' || kind === 'plan' || kind === 'deployment' || kind === 'diagnostic') {
244
+ if (kind === 'graph' || kind === 'plan' || kind === 'deployment' || kind === 'diagnostic' || kind === 'motion') {
245
245
  continue;
246
246
  }
247
247
  fail(`unknown resume assertion ${JSON.stringify(kind)}`);
package/dist/ssr.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SSR / hydrate / stream host for `vmz test --mode ssr` (T2).
2
+ * SSR / hydrate / stream host for `vmz test --mode ssr` .
3
3
  * Same Direct schedule as production via linkedom + renderToString / renderToStream / hydrate.
4
4
  */
5
5
  type Diag = {
package/dist/ssr.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SSR / hydrate / stream host for `vmz test --mode ssr` (T2).
2
+ * SSR / hydrate / stream host for `vmz test --mode ssr` .
3
3
  * Same Direct schedule as production via linkedom + renderToString / renderToStream / hydrate.
4
4
  */
5
5
  import path from 'node:path';
@@ -234,7 +234,7 @@ export async function runSsrManifest(manifest, ctx) {
234
234
  }
235
235
  continue;
236
236
  }
237
- if (kind === 'graph' || kind === 'plan' || kind === 'view' || kind === 'diagnostic') {
237
+ if (kind === 'graph' || kind === 'plan' || kind === 'view' || kind === 'diagnostic' || kind === 'motion') {
238
238
  continue;
239
239
  }
240
240
  fail(`unknown ssr assertion ${JSON.stringify(kind)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vmz/test",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "description": "VMZ native test protocol + Compile/Logic/Browser/SSR/Resume/Deployment hosts",
6
6
  "main": "./dist/index.js",
@@ -21,15 +21,14 @@
21
21
  },
22
22
  "files": [
23
23
  "dist",
24
- "src",
25
24
  "README.md"
26
25
  ],
27
26
  "scripts": {
28
27
  "build": "tsc -p tsconfig.json"
29
28
  },
30
29
  "dependencies": {
31
- "@vmz/core": "0.0.2",
32
- "@vmz/protocol": "0.0.2",
30
+ "@vmz/core": "0.0.4",
31
+ "@vmz/protocol": "0.0.4",
33
32
  "linkedom": "^0.18.13",
34
33
  "puppeteer-core": "^24.11.2"
35
34
  },