@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 +16 -11
- package/dist/browser.d.ts +1 -2
- package/dist/browser.js +2 -3
- package/dist/compile.d.ts +1 -1
- package/dist/compile.js +79 -4
- package/dist/deployment.d.ts +1 -1
- package/dist/deployment.js +2 -2
- package/dist/discover.d.ts +1 -1
- package/dist/discover.js +1 -1
- package/dist/logic.d.ts +0 -1
- package/dist/logic.js +14 -4
- package/dist/protocol.d.ts +2 -3
- package/dist/protocol.js +2 -3
- package/dist/resume.d.ts +1 -1
- package/dist/resume.js +2 -2
- package/dist/ssr.d.ts +1 -1
- package/dist/ssr.js +2 -2
- package/package.json +3 -4
- package/src/browser.ts +0 -602
- package/src/compile.ts +0 -509
- package/src/deployment.ts +0 -204
- package/src/discover.ts +0 -74
- package/src/index.ts +0 -37
- package/src/logic.ts +0 -427
- package/src/protocol.ts +0 -128
- package/src/resume.ts +0 -275
- package/src/run.ts +0 -124
- package/src/ssr.ts +0 -265
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
|
|
30
|
-
|
|
31
|
-
| Compile
|
|
32
|
-
| Logic
|
|
33
|
-
| Browser
|
|
34
|
-
| Router
|
|
35
|
-
| SSR / Resume | HTML, serialized state, event entries, and replay avoidance
|
|
36
|
-
| Deployment
|
|
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
|
|
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
|
|
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,
|
|
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` (
|
|
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` (
|
|
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
package/dist/compile.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Compile-mode host for `vmz test` / `@vmz/test` (
|
|
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
|
|
252
|
-
|
|
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 (
|
|
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 ||
|
package/dist/deployment.d.ts
CHANGED
package/dist/deployment.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Deployment host for `vmz test --mode deployment`
|
|
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)}`);
|
package/dist/discover.d.ts
CHANGED
package/dist/discover.js
CHANGED
package/dist/logic.d.ts
CHANGED
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
|
-
|
|
211
|
-
const
|
|
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') {
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* VMZ native test protocol
|
|
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
|
|
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
|
|
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
|
|
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
package/dist/resume.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Resume host for `vmz test --mode resume` (
|
|
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
package/dist/ssr.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SSR / hydrate / stream host for `vmz test --mode ssr`
|
|
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.
|
|
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.
|
|
32
|
-
"@vmz/protocol": "0.0.
|
|
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
|
},
|