capdag 1.194.499 → 1.198.510
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/capdag.test.js +480 -354
- package/package.json +2 -2
package/capdag.test.js
CHANGED
|
@@ -105,7 +105,8 @@ function runTest(name, fn) {
|
|
|
105
105
|
* Uses MEDIA_VOID for in and MEDIA_OBJECT for out, matching the
|
|
106
106
|
* Rust reference test_urn helper: test_urn(tags) => cap:in="media:void";{tags};out="media:record;textable"
|
|
107
107
|
*/
|
|
108
|
-
|
|
108
|
+
// TEST0051: Urn
|
|
109
|
+
function test0051_Urn(tags) {
|
|
109
110
|
if (!tags || tags === '') {
|
|
110
111
|
return `cap:in="${MEDIA_VOID}";out="${MEDIA_OBJECT}"`;
|
|
111
112
|
}
|
|
@@ -131,7 +132,7 @@ function makeGraphCap(inUrn, outUrn, title) {
|
|
|
131
132
|
|
|
132
133
|
// TEST001: Test that cap URN is created with tags parsed correctly and direction specs accessible
|
|
133
134
|
function test001_capUrnCreation() {
|
|
134
|
-
const cap = CapUrn.fromString(
|
|
135
|
+
const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf;target=thumbnail'));
|
|
135
136
|
assert(cap.hasMarkerTag('generate'), 'Should get op tag');
|
|
136
137
|
assertEqual(cap.getTag('target'), 'thumbnail', 'Should get target tag');
|
|
137
138
|
assertEqual(cap.getTag('ext'), 'pdf', 'Should get ext tag');
|
|
@@ -152,8 +153,8 @@ function test002_directionSpecsRequired() {
|
|
|
152
153
|
|
|
153
154
|
// TEST003: Test that direction specs must match exactly, different in/out types don't match, wildcard matches any
|
|
154
155
|
function test003_directionMatching() {
|
|
155
|
-
const cap = CapUrn.fromString(
|
|
156
|
-
const request = CapUrn.fromString(
|
|
156
|
+
const cap = CapUrn.fromString(test0051_Urn('generate'));
|
|
157
|
+
const request = CapUrn.fromString(test0051_Urn('generate'));
|
|
157
158
|
assert(cap.accepts(request), 'Same direction specs should match');
|
|
158
159
|
|
|
159
160
|
// Different direction should not match
|
|
@@ -239,7 +240,7 @@ function test011_serializationSmartQuoting() {
|
|
|
239
240
|
|
|
240
241
|
// TEST012: Test that simple cap URN round-trips (parse -> serialize -> parse equals original)
|
|
241
242
|
function test012_roundTripSimple() {
|
|
242
|
-
const original = CapUrn.fromString(
|
|
243
|
+
const original = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
|
|
243
244
|
const serialized = original.toString();
|
|
244
245
|
const reparsed = CapUrn.fromString(serialized);
|
|
245
246
|
assert(original.equals(reparsed), 'Simple round-trip should produce equal URN');
|
|
@@ -272,14 +273,14 @@ function test015_capPrefixRequired() {
|
|
|
272
273
|
'Should require cap: prefix'
|
|
273
274
|
);
|
|
274
275
|
// Valid cap: prefix should work
|
|
275
|
-
const cap = CapUrn.fromString(
|
|
276
|
+
const cap = CapUrn.fromString(test0051_Urn('generate'));
|
|
276
277
|
assert(cap.hasMarkerTag('generate'), 'Should parse with valid cap: prefix');
|
|
277
278
|
}
|
|
278
279
|
|
|
279
280
|
// TEST016: Test that trailing semicolon is equivalent (same hash, same string, matches)
|
|
280
281
|
function test016_trailingSemicolonEquivalence() {
|
|
281
|
-
const cap1 = CapUrn.fromString(
|
|
282
|
-
const cap2 = CapUrn.fromString(
|
|
282
|
+
const cap1 = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
|
|
283
|
+
const cap2 = CapUrn.fromString(test0051_Urn('generate;ext=pdf') + ';');
|
|
283
284
|
assert(cap1.equals(cap2), 'With/without trailing semicolon should be equal');
|
|
284
285
|
assertEqual(cap1.toString(), cap2.toString(), 'Canonical forms should match');
|
|
285
286
|
}
|
|
@@ -323,24 +324,24 @@ function test939_capUrnCanonicalFormDropsWildcardInOut() {
|
|
|
323
324
|
|
|
324
325
|
// TEST017: Test tag matching: exact match, subset match, wildcard match, value mismatch
|
|
325
326
|
function test017_tagMatching() {
|
|
326
|
-
const cap = CapUrn.fromString(
|
|
327
|
+
const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf;target=thumbnail'));
|
|
327
328
|
|
|
328
329
|
// Exact match — both directions accept
|
|
329
|
-
const exact = CapUrn.fromString(
|
|
330
|
+
const exact = CapUrn.fromString(test0051_Urn('generate;ext=pdf;target=thumbnail'));
|
|
330
331
|
assert(cap.accepts(exact), 'Should accept exact match');
|
|
331
332
|
assert(exact.accepts(cap), 'Exact match should accept in reverse too');
|
|
332
333
|
|
|
333
334
|
// Routing direction: request(generate) accepts cap(op,ext,target)
|
|
334
|
-
const subset = CapUrn.fromString(
|
|
335
|
+
const subset = CapUrn.fromString(test0051_Urn('generate'));
|
|
335
336
|
assert(subset.accepts(cap), 'General request should accept more specific instance');
|
|
336
337
|
assert(!cap.accepts(subset), 'Specific pattern should reject subset instance');
|
|
337
338
|
|
|
338
339
|
// Routing direction: request(ext=*) accepts cap(ext=pdf)
|
|
339
|
-
const wildcard = CapUrn.fromString(
|
|
340
|
+
const wildcard = CapUrn.fromString(test0051_Urn('ext=*'));
|
|
340
341
|
assert(wildcard.accepts(cap), 'Wildcard request should accept specific instance');
|
|
341
342
|
|
|
342
343
|
// Conflicting value — neither direction accepts
|
|
343
|
-
const mismatch = CapUrn.fromString(
|
|
344
|
+
const mismatch = CapUrn.fromString(test0051_Urn('extract'));
|
|
344
345
|
assert(!cap.accepts(mismatch), 'Should not accept value mismatch');
|
|
345
346
|
assert(!mismatch.accepts(cap), 'Reverse mismatch should also reject');
|
|
346
347
|
}
|
|
@@ -354,13 +355,13 @@ function test018_matchingCaseSensitiveValues() {
|
|
|
354
355
|
|
|
355
356
|
// TEST019: Missing tag in instance causes rejection — pattern's tags are constraints
|
|
356
357
|
function test019_missingTagHandling() {
|
|
357
|
-
const cap = CapUrn.fromString(
|
|
358
|
-
const request = CapUrn.fromString(
|
|
358
|
+
const cap = CapUrn.fromString(test0051_Urn('generate'));
|
|
359
|
+
const request = CapUrn.fromString(test0051_Urn('ext=pdf'));
|
|
359
360
|
assert(!cap.accepts(request), 'Pattern requiring op should reject instance missing op');
|
|
360
361
|
assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
|
|
361
362
|
|
|
362
|
-
const cap2 = CapUrn.fromString(
|
|
363
|
-
const request2 = CapUrn.fromString(
|
|
363
|
+
const cap2 = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
|
|
364
|
+
const request2 = CapUrn.fromString(test0051_Urn('generate'));
|
|
364
365
|
assert(!cap2.accepts(request2), 'Specific pattern should reject instance missing ext');
|
|
365
366
|
assert(request2.accepts(cap2), 'General request should accept more specific instance');
|
|
366
367
|
}
|
|
@@ -370,27 +371,27 @@ function test019_missingTagHandling() {
|
|
|
370
371
|
// (must-have-any), exact `key=value` tags score 3, missing/`?` score
|
|
371
372
|
// 0, `!` scores 1.
|
|
372
373
|
//
|
|
373
|
-
//
|
|
374
|
+
// test0051_Urn() builds "cap:in=media:void;out=media:record;<tags>" so
|
|
374
375
|
// the directional baseline is:
|
|
375
376
|
// in: media:void -> {void=*} -> 2
|
|
376
377
|
// out: media:record -> {record=*} -> 2
|
|
377
378
|
// Total directional baseline: 4.
|
|
378
379
|
function test020_specificity() {
|
|
379
|
-
//
|
|
380
|
+
// test0051_Urn() prepends in="media:void" (1 marker, score 2) and
|
|
380
381
|
// out="media:record" (1 marker, score 2). Cap-URN spec is
|
|
381
382
|
// 10000*spec_U(out) + 100*spec_U(in) + spec_U(y).
|
|
382
383
|
|
|
383
|
-
const cap1 = CapUrn.fromString(
|
|
384
|
+
const cap1 = CapUrn.fromString(test0051_Urn('type=general'));
|
|
384
385
|
// out=2, in=2, y=4 (type=general exact)
|
|
385
386
|
assertEqual(cap1.specificity(), 10000*2 + 100*2 + 4,
|
|
386
387
|
'out=2, in=2, y=type=general exact=4 -> 20204');
|
|
387
388
|
|
|
388
|
-
const cap2 = CapUrn.fromString(
|
|
389
|
+
const cap2 = CapUrn.fromString(test0051_Urn('generate'));
|
|
389
390
|
// out=2, in=2, y=2 (generate marker = must-have-any)
|
|
390
391
|
assertEqual(cap2.specificity(), 10000*2 + 100*2 + 2,
|
|
391
392
|
'out=2, in=2, y=generate marker=2 -> 20202');
|
|
392
393
|
|
|
393
|
-
const cap3 = CapUrn.fromString(
|
|
394
|
+
const cap3 = CapUrn.fromString(test0051_Urn('op;ext=pdf'));
|
|
394
395
|
// out=2, in=2, y=2+4 (op marker, ext=pdf exact)
|
|
395
396
|
assertEqual(cap3.specificity(), 10000*2 + 100*2 + 6,
|
|
396
397
|
'out=2, in=2, y=op marker(2)+ext=pdf exact(4) -> 20206');
|
|
@@ -444,16 +445,16 @@ function test023_builderPreservesCase() {
|
|
|
444
445
|
|
|
445
446
|
// TEST024: Directional accepts — pattern's tags are constraints, instance must satisfy
|
|
446
447
|
function test024_compatibility() {
|
|
447
|
-
const cap1 = CapUrn.fromString(
|
|
448
|
-
const cap2 = CapUrn.fromString(
|
|
449
|
-
const cap3 = CapUrn.fromString(
|
|
448
|
+
const cap1 = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
|
|
449
|
+
const cap2 = CapUrn.fromString(test0051_Urn('generate;format=*'));
|
|
450
|
+
const cap3 = CapUrn.fromString(test0051_Urn('type=image;extract'));
|
|
450
451
|
|
|
451
452
|
assert(!cap1.accepts(cap2), 'Pattern requiring ext should reject instance missing ext');
|
|
452
453
|
assert(!cap2.accepts(cap1), 'Pattern requiring format should reject instance missing format');
|
|
453
454
|
assert(!cap1.accepts(cap3), 'Different op should not accept');
|
|
454
455
|
assert(!cap3.accepts(cap1), 'Different op should not accept in reverse');
|
|
455
456
|
|
|
456
|
-
const general = CapUrn.fromString(
|
|
457
|
+
const general = CapUrn.fromString(test0051_Urn('generate'));
|
|
457
458
|
assert(general.accepts(cap1), 'General request should accept more specific instance');
|
|
458
459
|
assert(!cap1.accepts(general), 'Specific pattern should reject general instance');
|
|
459
460
|
|
|
@@ -466,10 +467,10 @@ function test024_compatibility() {
|
|
|
466
467
|
function test025_bestMatch() {
|
|
467
468
|
const caps = [
|
|
468
469
|
CapUrn.fromString('cap:in=*;out=*;op'),
|
|
469
|
-
CapUrn.fromString(
|
|
470
|
-
CapUrn.fromString(
|
|
470
|
+
CapUrn.fromString(test0051_Urn('generate')),
|
|
471
|
+
CapUrn.fromString(test0051_Urn('generate;ext=pdf'))
|
|
471
472
|
];
|
|
472
|
-
const request = CapUrn.fromString(
|
|
473
|
+
const request = CapUrn.fromString(test0051_Urn('generate'));
|
|
473
474
|
const best = CapMatcher.findBestMatch(caps, request);
|
|
474
475
|
assert(best !== null, 'Should find a best match');
|
|
475
476
|
assertEqual(best.getTag('ext'), 'pdf', 'Best match should be the most specific (ext=pdf)');
|
|
@@ -477,7 +478,7 @@ function test025_bestMatch() {
|
|
|
477
478
|
|
|
478
479
|
// TEST026: Test merge combines tags from both caps, subset keeps only specified tags
|
|
479
480
|
function test026_mergeAndSubset() {
|
|
480
|
-
const cap1 = CapUrn.fromString(
|
|
481
|
+
const cap1 = CapUrn.fromString(test0051_Urn('generate'));
|
|
481
482
|
const cap2 = CapUrn.fromString('cap:in="media:textable";ext=pdf;format=binary;out="media:"');
|
|
482
483
|
|
|
483
484
|
// Merge (other takes precedence)
|
|
@@ -496,7 +497,7 @@ function test026_mergeAndSubset() {
|
|
|
496
497
|
|
|
497
498
|
// TEST027: Test with_wildcard_tag sets tag to wildcard, including in/out
|
|
498
499
|
function test027_wildcardTag() {
|
|
499
|
-
const cap = CapUrn.fromString(
|
|
500
|
+
const cap = CapUrn.fromString(test0051_Urn('ext=pdf'));
|
|
500
501
|
const wildcardExt = cap.withWildcardTag('ext');
|
|
501
502
|
assertEqual(wildcardExt.getTag('ext'), '*', 'Should set ext to wildcard');
|
|
502
503
|
|
|
@@ -526,7 +527,7 @@ function test029_minimalCapUrn() {
|
|
|
526
527
|
|
|
527
528
|
// TEST030: Test extended characters (forward slashes, colons) in tag values
|
|
528
529
|
function test030_extendedCharacterSupport() {
|
|
529
|
-
const cap = CapUrn.fromString(
|
|
530
|
+
const cap = CapUrn.fromString(test0051_Urn('url=https://example_org/api;path=/some/file'));
|
|
530
531
|
assertEqual(cap.getTag('url'), 'https://example_org/api', 'Should support colons and slashes');
|
|
531
532
|
assertEqual(cap.getTag('path'), '/some/file', 'Should support forward slashes');
|
|
532
533
|
}
|
|
@@ -534,13 +535,13 @@ function test030_extendedCharacterSupport() {
|
|
|
534
535
|
// TEST031: Test wildcard rejected in keys but accepted in values
|
|
535
536
|
function test031_wildcardRestrictions() {
|
|
536
537
|
assertThrows(
|
|
537
|
-
() => CapUrn.fromString(
|
|
538
|
+
() => CapUrn.fromString(test0051_Urn('*=value')),
|
|
538
539
|
ErrorCodes.INVALID_CHARACTER,
|
|
539
540
|
'Should reject wildcard in key'
|
|
540
541
|
);
|
|
541
542
|
|
|
542
543
|
// Wildcard accepted in values
|
|
543
|
-
const cap = CapUrn.fromString(
|
|
544
|
+
const cap = CapUrn.fromString(test0051_Urn('key=*'));
|
|
544
545
|
assertEqual(cap.getTag('key'), '*', 'Should accept wildcard in value');
|
|
545
546
|
|
|
546
547
|
// Wildcard in in/out normalizes to media:
|
|
@@ -552,7 +553,7 @@ function test031_wildcardRestrictions() {
|
|
|
552
553
|
// TEST032: Test duplicate keys are rejected with DuplicateKey error
|
|
553
554
|
function test032_duplicateKeyRejection() {
|
|
554
555
|
assertThrows(
|
|
555
|
-
() => CapUrn.fromString(
|
|
556
|
+
() => CapUrn.fromString(test0051_Urn('key=value1;key=value2')),
|
|
556
557
|
ErrorCodes.DUPLICATE_KEY,
|
|
557
558
|
'Should reject duplicate keys'
|
|
558
559
|
);
|
|
@@ -561,14 +562,14 @@ function test032_duplicateKeyRejection() {
|
|
|
561
562
|
// TEST033: Test pure numeric keys rejected, mixed alphanumeric allowed, numeric values allowed
|
|
562
563
|
function test033_numericKeyRestriction() {
|
|
563
564
|
assertThrows(
|
|
564
|
-
() => CapUrn.fromString(
|
|
565
|
+
() => CapUrn.fromString(test0051_Urn('123=value')),
|
|
565
566
|
ErrorCodes.NUMERIC_KEY,
|
|
566
567
|
'Should reject pure numeric keys'
|
|
567
568
|
);
|
|
568
569
|
// Mixed alphanumeric allowed
|
|
569
|
-
const cap1 = CapUrn.fromString(
|
|
570
|
+
const cap1 = CapUrn.fromString(test0051_Urn('key123=value'));
|
|
570
571
|
assertEqual(cap1.getTag('key123'), 'value', 'Mixed alphanumeric key should be allowed');
|
|
571
|
-
const cap2 = CapUrn.fromString(
|
|
572
|
+
const cap2 = CapUrn.fromString(test0051_Urn('x123key=value'));
|
|
572
573
|
assertEqual(cap2.getTag('x123key'), 'value', 'Mixed alphanumeric key should be allowed');
|
|
573
574
|
}
|
|
574
575
|
|
|
@@ -633,45 +634,45 @@ function test039_getTagReturnsDirectionSpecs() {
|
|
|
633
634
|
|
|
634
635
|
// TEST040: Matching semantics - exact match succeeds
|
|
635
636
|
function test040_matchingSemanticsExactMatch() {
|
|
636
|
-
const cap = CapUrn.fromString(
|
|
637
|
-
const request = CapUrn.fromString(
|
|
637
|
+
const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
|
|
638
|
+
const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
|
|
638
639
|
assert(cap.accepts(request), 'Exact match should accept');
|
|
639
640
|
}
|
|
640
641
|
|
|
641
642
|
// TEST041: Matching semantics - cap missing tag matches (implicit wildcard)
|
|
642
643
|
function test041_matchingSemanticsCapMissingTag() {
|
|
643
|
-
const cap = CapUrn.fromString(
|
|
644
|
-
const request = CapUrn.fromString(
|
|
644
|
+
const cap = CapUrn.fromString(test0051_Urn('generate'));
|
|
645
|
+
const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
|
|
645
646
|
assert(cap.accepts(request), 'General pattern with only op should accept specific instance');
|
|
646
647
|
assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
|
|
647
648
|
}
|
|
648
649
|
|
|
649
650
|
// TEST042: Pattern rejects instance missing required tags
|
|
650
651
|
function test042_matchingSemanticsCapHasExtraTag() {
|
|
651
|
-
const cap = CapUrn.fromString(
|
|
652
|
-
const request = CapUrn.fromString(
|
|
652
|
+
const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf;version=2'));
|
|
653
|
+
const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
|
|
653
654
|
assert(!cap.accepts(request), 'Pattern requiring version should reject instance missing version');
|
|
654
655
|
assert(request.accepts(cap), 'General request should accept refined instance');
|
|
655
656
|
}
|
|
656
657
|
|
|
657
658
|
// TEST043: Matching semantics - request wildcard matches specific cap value
|
|
658
659
|
function test043_matchingSemanticsRequestHasWildcard() {
|
|
659
|
-
const cap = CapUrn.fromString(
|
|
660
|
-
const request = CapUrn.fromString(
|
|
660
|
+
const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
|
|
661
|
+
const request = CapUrn.fromString(test0051_Urn('generate;ext=*'));
|
|
661
662
|
assert(cap.accepts(request), 'Request wildcard should match specific cap value');
|
|
662
663
|
}
|
|
663
664
|
|
|
664
665
|
// TEST044: Matching semantics - cap wildcard matches specific request value
|
|
665
666
|
function test044_matchingSemanticsCapHasWildcard() {
|
|
666
|
-
const cap = CapUrn.fromString(
|
|
667
|
-
const request = CapUrn.fromString(
|
|
667
|
+
const cap = CapUrn.fromString(test0051_Urn('generate;ext=*'));
|
|
668
|
+
const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
|
|
668
669
|
assert(cap.accepts(request), 'Cap wildcard should match specific request value');
|
|
669
670
|
}
|
|
670
671
|
|
|
671
672
|
// TEST045: Matching semantics - value mismatch does not match
|
|
672
673
|
function test045_matchingSemanticsValueMismatch() {
|
|
673
|
-
const cap = CapUrn.fromString(
|
|
674
|
-
const request = CapUrn.fromString(
|
|
674
|
+
const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
|
|
675
|
+
const request = CapUrn.fromString(test0051_Urn('generate;ext=docx'));
|
|
675
676
|
assert(!cap.accepts(request), 'Value mismatch should not accept');
|
|
676
677
|
}
|
|
677
678
|
|
|
@@ -693,14 +694,14 @@ function test047_matchingSemanticsThumbnailVoidInput() {
|
|
|
693
694
|
// TEST048: Matching semantics - wildcard direction matches anything
|
|
694
695
|
function test048_matchingSemanticsWildcardDirection() {
|
|
695
696
|
const cap = CapUrn.fromString('cap:generate');
|
|
696
|
-
const request = CapUrn.fromString(
|
|
697
|
+
const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
|
|
697
698
|
assert(cap.accepts(request), 'Generic declared directions should accept a more specific matching request');
|
|
698
699
|
}
|
|
699
700
|
|
|
700
701
|
// TEST049: Non-overlapping tags — neither direction accepts
|
|
701
702
|
function test049_matchingSemanticsCrossDimension() {
|
|
702
|
-
const cap = CapUrn.fromString(
|
|
703
|
-
const request = CapUrn.fromString(
|
|
703
|
+
const cap = CapUrn.fromString(test0051_Urn('generate'));
|
|
704
|
+
const request = CapUrn.fromString(test0051_Urn('ext=pdf'));
|
|
704
705
|
assert(!cap.accepts(request), 'Pattern requiring op should reject instance missing op');
|
|
705
706
|
assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
|
|
706
707
|
}
|
|
@@ -1281,7 +1282,7 @@ function test116_capArgConstructors() {
|
|
|
1281
1282
|
|
|
1282
1283
|
// TEST150: JSON roundtrip
|
|
1283
1284
|
function test150_capManifestJsonSerialization() {
|
|
1284
|
-
const capUrn = CapUrn.fromString(
|
|
1285
|
+
const capUrn = CapUrn.fromString(test0051_Urn('extract;target=metadata'));
|
|
1285
1286
|
const cap = new Cap(capUrn, 'Extract Metadata', 'extract-metadata');
|
|
1286
1287
|
cap.addArg(new CapArg('media:pdf', true, [new ArgSource({ stdin: 'media:pdf' })]));
|
|
1287
1288
|
cap.addArg(new CapArg(
|
|
@@ -1379,7 +1380,7 @@ function test597_capArgWithFullDefinition() {
|
|
|
1379
1380
|
|
|
1380
1381
|
// Add a cap and check it becomes an edge with from/to nodes and carries the
|
|
1381
1382
|
// registry name we passed. This is exactly the shape the renderer depends on.
|
|
1382
|
-
function
|
|
1383
|
+
function test0052_CapFabAddCapPopulatesEdgesAndNodes() {
|
|
1383
1384
|
const graph = new CapFab();
|
|
1384
1385
|
const cap = makeGraphCap('media:pdf', 'media:textable', 'PDF to Text');
|
|
1385
1386
|
graph.addCap(cap, 'registry');
|
|
@@ -1397,7 +1398,7 @@ function testCapFabAddCapPopulatesEdgesAndNodes() {
|
|
|
1397
1398
|
|
|
1398
1399
|
// getOutgoing takes a concrete source URN and returns edges whose from_spec
|
|
1399
1400
|
// the source conforms to. It must NOT be a plain string lookup.
|
|
1400
|
-
function
|
|
1401
|
+
function test0053_CapFabGetOutgoingConformsToMatching() {
|
|
1401
1402
|
const graph = new CapFab();
|
|
1402
1403
|
graph.addCap(makeGraphCap('media:textable', 'media:embedding-vector', 'Embed text'), 'registry');
|
|
1403
1404
|
|
|
@@ -1418,7 +1419,7 @@ function testCapFabGetOutgoingConformsToMatching() {
|
|
|
1418
1419
|
|
|
1419
1420
|
// Each edge must carry the registry name it was added with. This is how
|
|
1420
1421
|
// the renderer colours/groups edges by provenance in browse mode.
|
|
1421
|
-
function
|
|
1422
|
+
function test0057_CapFabDistinctRegistryNames() {
|
|
1422
1423
|
const graph = new CapFab();
|
|
1423
1424
|
graph.addCap(makeGraphCap('media:pdf', 'media:textable', 'PDF to Text'), 'providers');
|
|
1424
1425
|
graph.addCap(makeGraphCap('media:textable', 'media:embedding-vector', 'Embed'), 'cartridges');
|
|
@@ -1629,7 +1630,7 @@ function test310_llmGenerateTextUrn() {
|
|
|
1629
1630
|
}
|
|
1630
1631
|
|
|
1631
1632
|
// Mirror-specific coverage: llm_generate_text_urn input/output specs conform to MEDIA_STRING
|
|
1632
|
-
function
|
|
1633
|
+
function test0058_LlmGenerateTextUrnSpecs() {
|
|
1633
1634
|
const urn = llmGenerateTextUrn();
|
|
1634
1635
|
const inSpec = TaggedUrn.fromString(urn.getInSpec());
|
|
1635
1636
|
const expectedIn = TaggedUrn.fromString(MEDIA_STRING);
|
|
@@ -1660,7 +1661,7 @@ function test312_allUrnBuildersProduceValidUrns() {
|
|
|
1660
1661
|
// These tests cover JS-specific functionality not in the Rust numbering scheme
|
|
1661
1662
|
// but are important for capdag-js correctness.
|
|
1662
1663
|
|
|
1663
|
-
function
|
|
1664
|
+
function test0059_JS_buildExtensionIndex() {
|
|
1664
1665
|
const mediaDefs = [
|
|
1665
1666
|
{ urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1666
1667
|
{ urn: 'media:image;jpeg', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] },
|
|
@@ -1676,7 +1677,8 @@ function testJS_buildExtensionIndex() {
|
|
|
1676
1677
|
assertEqual(index.get('pdf')[0], 'media:pdf', 'pdf should map correctly');
|
|
1677
1678
|
}
|
|
1678
1679
|
|
|
1679
|
-
|
|
1680
|
+
// TEST0069: J s media urns for extension
|
|
1681
|
+
function test0069_JS_mediaUrnsForExtension() {
|
|
1680
1682
|
const mediaDefs = [
|
|
1681
1683
|
{ urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1682
1684
|
{ urn: 'media:json;textable;record', media_type: 'application/json', extensions: ['json'] },
|
|
@@ -1704,7 +1706,8 @@ function testJS_mediaUrnsForExtension() {
|
|
|
1704
1706
|
assert(thrownError instanceof MediaDefError, 'Should throw MediaDefError for unknown ext');
|
|
1705
1707
|
}
|
|
1706
1708
|
|
|
1707
|
-
|
|
1709
|
+
// TEST0070: J s get extension mappings
|
|
1710
|
+
function test0070_JS_getExtensionMappings() {
|
|
1708
1711
|
const mediaDefs = [
|
|
1709
1712
|
{ urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1710
1713
|
{ urn: 'media:image;jpeg', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] }
|
|
@@ -1714,7 +1717,8 @@ function testJS_getExtensionMappings() {
|
|
|
1714
1717
|
assertEqual(mappings.length, 3, 'Should have 3 mappings');
|
|
1715
1718
|
}
|
|
1716
1719
|
|
|
1717
|
-
|
|
1720
|
+
// TEST0073: J s resolve media urn from specs
|
|
1721
|
+
function test0073_JS_resolveMediaUrnFromSpecs() {
|
|
1718
1722
|
const mediaDefs = [
|
|
1719
1723
|
{ urn: MEDIA_STRING, media_type: 'text/plain', title: 'String', profile_uri: 'https://capdag.com/schema/str' },
|
|
1720
1724
|
{ urn: 'media:custom', media_type: 'application/json', title: 'Custom Output', schema: { type: 'object' } }
|
|
@@ -1726,8 +1730,9 @@ function testJS_resolveMediaUrnFromSpecs() {
|
|
|
1726
1730
|
assert(outputSpec.schema !== null, 'Should have schema');
|
|
1727
1731
|
}
|
|
1728
1732
|
|
|
1729
|
-
|
|
1730
|
-
|
|
1733
|
+
// TEST0079: J s cap j s o n serialization
|
|
1734
|
+
function test0079_JS_capJSONSerialization() {
|
|
1735
|
+
const urn = CapUrn.fromString(test0051_Urn('test'));
|
|
1731
1736
|
const cap = new Cap(urn, 'Test Cap', 'test_command');
|
|
1732
1737
|
cap.arguments = {
|
|
1733
1738
|
required: [{ name: 'input', media_urn: MEDIA_STRING }],
|
|
@@ -1749,8 +1754,8 @@ function testJS_capJSONSerialization() {
|
|
|
1749
1754
|
// backticks, embedded quotes, Unicode) so escaping mismatches between
|
|
1750
1755
|
// JSON.stringify on this side and the Rust serializer on the other side
|
|
1751
1756
|
// surface as failures here.
|
|
1752
|
-
function
|
|
1753
|
-
const urn = CapUrn.fromString(
|
|
1757
|
+
function test0080_JS_capDocumentationRoundTrip() {
|
|
1758
|
+
const urn = CapUrn.fromString(test0051_Urn('documented'));
|
|
1754
1759
|
const cap = new Cap(urn, 'Documented Cap', 'documented');
|
|
1755
1760
|
const body = '# Documented Cap\r\n\nDoes the thing.\n\n```bash\necho "hi"\n```\n\nSee also: \u2605\n';
|
|
1756
1761
|
cap.setDocumentation(body);
|
|
@@ -1771,8 +1776,8 @@ function testJS_capDocumentationRoundTrip() {
|
|
|
1771
1776
|
// toDictionary behaviour. A regression where null is emitted as
|
|
1772
1777
|
// `documentation: null` would break the symmetric round-trip with Rust
|
|
1773
1778
|
// (which has no null sentinel) and pollute generated JSON.
|
|
1774
|
-
function
|
|
1775
|
-
const urn = CapUrn.fromString(
|
|
1779
|
+
function test0081_JS_capDocumentationOmittedWhenNull() {
|
|
1780
|
+
const urn = CapUrn.fromString(test0051_Urn('undocumented'));
|
|
1776
1781
|
const cap = new Cap(urn, 'Undocumented Cap', 'undocumented');
|
|
1777
1782
|
assertEqual(cap.getDocumentation(), null, 'Default documentation must be null');
|
|
1778
1783
|
|
|
@@ -1794,7 +1799,7 @@ function testJS_capDocumentationOmittedWhenNull() {
|
|
|
1794
1799
|
// resolveMediaUrn into the resolved MediaDef. Mirrors TEST924 on the Rust
|
|
1795
1800
|
// side. This is the path every UI consumer uses, so a break here makes the
|
|
1796
1801
|
// new field invisible everywhere downstream.
|
|
1797
|
-
function
|
|
1802
|
+
function test0082_JS_mediaDefDocumentationPropagatesThroughResolve() {
|
|
1798
1803
|
const body = '## Markdown body\n\nWith `code` and a [link](https://example.com).';
|
|
1799
1804
|
const mediaDefs = [
|
|
1800
1805
|
{
|
|
@@ -1824,20 +1829,23 @@ function testJS_mediaDefDocumentationPropagatesThroughResolve() {
|
|
|
1824
1829
|
assertEqual(emptyDoc.documentation, null, 'Empty documentation string must collapse to null');
|
|
1825
1830
|
}
|
|
1826
1831
|
|
|
1827
|
-
|
|
1832
|
+
// TEST0083: J s stdin source kind constants
|
|
1833
|
+
function test0083_JS_stdinSourceKindConstants() {
|
|
1828
1834
|
assert(StdinSourceKind.DATA !== undefined, 'DATA kind should be defined');
|
|
1829
1835
|
assert(StdinSourceKind.FILE_REFERENCE !== undefined, 'FILE_REFERENCE kind should be defined');
|
|
1830
1836
|
assert(StdinSourceKind.DATA !== StdinSourceKind.FILE_REFERENCE, 'Kind values should be distinct');
|
|
1831
1837
|
}
|
|
1832
1838
|
|
|
1833
|
-
|
|
1839
|
+
// TEST0084: J s stdin source null data
|
|
1840
|
+
function test0084_JS_stdinSourceNullData() {
|
|
1834
1841
|
const source = StdinSource.fromData(null);
|
|
1835
1842
|
assert(source !== null, 'Should create source');
|
|
1836
1843
|
assert(source.isData(), 'Should be data source');
|
|
1837
1844
|
assertEqual(source.data, null, 'Data should be null');
|
|
1838
1845
|
}
|
|
1839
1846
|
|
|
1840
|
-
|
|
1847
|
+
// TEST0085: J s media def construction
|
|
1848
|
+
function test0085_JS_mediaDefConstruction() {
|
|
1841
1849
|
const spec1 = new MediaDef('text/plain', 'https://capdag.com/schema/str', null, 'String', null, 'media:string');
|
|
1842
1850
|
assertEqual(spec1.contentType, 'text/plain', 'Should have content type');
|
|
1843
1851
|
assertEqual(spec1.profile, 'https://capdag.com/schema/str', 'Should have profile');
|
|
@@ -2501,7 +2509,7 @@ function test1299_isFilePath() {
|
|
|
2501
2509
|
|
|
2502
2510
|
// Mirror-specific coverage: isCollection returns true when collection marker tag is present
|
|
2503
2511
|
// Mirror-specific coverage: N/A for JS (MEDIA_COLLECTION constants removed - no longer exists)
|
|
2504
|
-
function
|
|
2512
|
+
function test0086_isCollection() {
|
|
2505
2513
|
// Skip - collection types removed from capdag
|
|
2506
2514
|
}
|
|
2507
2515
|
|
|
@@ -2924,22 +2932,25 @@ function test657_effectDispatchRequiresExplicitWildcard() {
|
|
|
2924
2932
|
|
|
2925
2933
|
// --- Machine parser tests (mirrors parser.rs tests) ---
|
|
2926
2934
|
|
|
2927
|
-
function
|
|
2935
|
+
function test0087_Machine_emptyInput() {
|
|
2928
2936
|
assertThrowsWithCode(() => parseMachine(''), MachineSyntaxErrorCodes.EMPTY);
|
|
2929
2937
|
}
|
|
2930
2938
|
|
|
2931
|
-
|
|
2939
|
+
// TEST0088: Machine whitespace only
|
|
2940
|
+
function test0088_Machine_whitespaceOnly() {
|
|
2932
2941
|
assertThrowsWithCode(() => parseMachine(' \n \t '), MachineSyntaxErrorCodes.EMPTY);
|
|
2933
2942
|
}
|
|
2934
2943
|
|
|
2935
|
-
|
|
2944
|
+
// TEST0089: Machine header only no wirings
|
|
2945
|
+
function test0089_Machine_headerOnlyNoWirings() {
|
|
2936
2946
|
assertThrowsWithCode(
|
|
2937
2947
|
() => Machine.fromString('[extract cap:in="media:pdf";extract;out="media:txt;textable"]'),
|
|
2938
2948
|
MachineSyntaxErrorCodes.NO_EDGES
|
|
2939
2949
|
);
|
|
2940
2950
|
}
|
|
2941
2951
|
|
|
2942
|
-
|
|
2952
|
+
// TEST0090: Machine duplicate alias
|
|
2953
|
+
function test0090_Machine_duplicateAlias() {
|
|
2943
2954
|
assertThrowsWithCode(
|
|
2944
2955
|
() => Machine.fromString(
|
|
2945
2956
|
'[ex cap:in="media:pdf";extract;out="media:txt;textable"]' +
|
|
@@ -2950,7 +2961,8 @@ function testMachine_duplicateAlias() {
|
|
|
2950
2961
|
);
|
|
2951
2962
|
}
|
|
2952
2963
|
|
|
2953
|
-
|
|
2964
|
+
// TEST0094: Machine simple linear chain
|
|
2965
|
+
function test0094_Machine_simpleLinearChain() {
|
|
2954
2966
|
const g = Machine.fromString(
|
|
2955
2967
|
'[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
|
|
2956
2968
|
'[doc -> extract -> text]'
|
|
@@ -2965,7 +2977,8 @@ function testMachine_simpleLinearChain() {
|
|
|
2965
2977
|
assertEqual(edge.isLoop, false);
|
|
2966
2978
|
}
|
|
2967
2979
|
|
|
2968
|
-
|
|
2980
|
+
// TEST0095: Machine two step chain
|
|
2981
|
+
function test0095_Machine_twoStepChain() {
|
|
2969
2982
|
const g = Machine.fromString(
|
|
2970
2983
|
'[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
|
|
2971
2984
|
'[embed cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"]' +
|
|
@@ -2979,7 +2992,8 @@ function testMachine_twoStepChain() {
|
|
|
2979
2992
|
'Second edge target should be media:embedding-vector;record;textable');
|
|
2980
2993
|
}
|
|
2981
2994
|
|
|
2982
|
-
|
|
2995
|
+
// TEST0096: Machine fan out
|
|
2996
|
+
function test0096_Machine_fanOut() {
|
|
2983
2997
|
const g = Machine.fromString(
|
|
2984
2998
|
'[meta cap:in="media:pdf";extract-metadata;out="media:file-metadata;record;textable"]' +
|
|
2985
2999
|
'[outline cap:in="media:pdf";extract-outline;out="media:document-outline;record;textable"]' +
|
|
@@ -2996,7 +3010,8 @@ function testMachine_fanOut() {
|
|
|
2996
3010
|
}
|
|
2997
3011
|
}
|
|
2998
3012
|
|
|
2999
|
-
|
|
3013
|
+
// TEST0097: Machine fan in secondary assigned by prior wiring
|
|
3014
|
+
function test0097_Machine_fanInSecondaryAssignedByPriorWiring() {
|
|
3000
3015
|
const g = Machine.fromString(
|
|
3001
3016
|
'[thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"]' +
|
|
3002
3017
|
'[model_dl cap:in="media:model-spec;textable";download;out="media:model-spec;textable"]' +
|
|
@@ -3009,7 +3024,8 @@ function testMachine_fanInSecondaryAssignedByPriorWiring() {
|
|
|
3009
3024
|
assertEqual(g.edges()[2].sources.length, 2);
|
|
3010
3025
|
}
|
|
3011
3026
|
|
|
3012
|
-
|
|
3027
|
+
// TEST0098: Machine fan in secondary unassigned gets wildcard
|
|
3028
|
+
function test0098_Machine_fanInSecondaryUnassignedGetsWildcard() {
|
|
3013
3029
|
const g = Machine.fromString(
|
|
3014
3030
|
'[describe cap:in="media:image;png";describe-image;out="media:image-description;textable"]\n' +
|
|
3015
3031
|
'[(thumbnail, model_spec) -> describe -> description]'
|
|
@@ -3020,7 +3036,8 @@ function testMachine_fanInSecondaryUnassignedGetsWildcard() {
|
|
|
3020
3036
|
assertEqual(g.edges()[0].sources[1].toString(), 'media:');
|
|
3021
3037
|
}
|
|
3022
3038
|
|
|
3023
|
-
|
|
3039
|
+
// TEST0111: Machine loop edge
|
|
3040
|
+
function test0111_Machine_loopEdge() {
|
|
3024
3041
|
const g = Machine.fromString(
|
|
3025
3042
|
'[p2t cap:in="media:disbound-page;textable";page-to-text;out="media:txt;textable"]' +
|
|
3026
3043
|
'[pages -> LOOP p2t -> texts]'
|
|
@@ -3029,14 +3046,16 @@ function testMachine_loopEdge() {
|
|
|
3029
3046
|
assertEqual(g.edges()[0].isLoop, true);
|
|
3030
3047
|
}
|
|
3031
3048
|
|
|
3032
|
-
|
|
3049
|
+
// TEST0112: Machine undefined alias fails
|
|
3050
|
+
function test0112_Machine_undefinedAliasFails() {
|
|
3033
3051
|
assertThrowsWithCode(
|
|
3034
3052
|
() => Machine.fromString('[doc -> nonexistent -> text]'),
|
|
3035
3053
|
MachineSyntaxErrorCodes.UNDEFINED_ALIAS
|
|
3036
3054
|
);
|
|
3037
3055
|
}
|
|
3038
3056
|
|
|
3039
|
-
|
|
3057
|
+
// TEST0113: Machine node alias collision
|
|
3058
|
+
function test0113_Machine_nodeAliasCollision() {
|
|
3040
3059
|
assertThrowsWithCode(
|
|
3041
3060
|
() => Machine.fromString(
|
|
3042
3061
|
'[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
|
|
@@ -3046,7 +3065,8 @@ function testMachine_nodeAliasCollision() {
|
|
|
3046
3065
|
);
|
|
3047
3066
|
}
|
|
3048
3067
|
|
|
3049
|
-
|
|
3068
|
+
// TEST0114: Machine conflicting media types fail
|
|
3069
|
+
function test0114_Machine_conflictingMediaTypesFail() {
|
|
3050
3070
|
assertThrowsWithCode(
|
|
3051
3071
|
() => Machine.fromString(
|
|
3052
3072
|
'[cap1 cap:in="media:txt;textable";a;out="media:pdf"]' +
|
|
@@ -3058,7 +3078,8 @@ function testMachine_conflictingMediaTypesFail() {
|
|
|
3058
3078
|
);
|
|
3059
3079
|
}
|
|
3060
3080
|
|
|
3061
|
-
|
|
3081
|
+
// TEST0117: Machine multiline format
|
|
3082
|
+
function test0117_Machine_multilineFormat() {
|
|
3062
3083
|
const g = Machine.fromString(
|
|
3063
3084
|
'[extract cap:in="media:pdf";extract;out="media:txt;textable"]\n' +
|
|
3064
3085
|
'[embed cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"]\n' +
|
|
@@ -3068,7 +3089,8 @@ function testMachine_multilineFormat() {
|
|
|
3068
3089
|
assertEqual(g.edgeCount(), 2);
|
|
3069
3090
|
}
|
|
3070
3091
|
|
|
3071
|
-
|
|
3092
|
+
// TEST0118: Machine different aliases same graph
|
|
3093
|
+
function test0118_Machine_differentAliasesSameGraph() {
|
|
3072
3094
|
const g1 = Machine.fromString(
|
|
3073
3095
|
'[ex cap:in="media:pdf";extract;out="media:txt;textable"]' +
|
|
3074
3096
|
'[a -> ex -> b]'
|
|
@@ -3080,14 +3102,16 @@ function testMachine_differentAliasesSameGraph() {
|
|
|
3080
3102
|
assert(g1.isEquivalent(g2), 'Different aliases should produce equivalent graphs');
|
|
3081
3103
|
}
|
|
3082
3104
|
|
|
3083
|
-
|
|
3105
|
+
// TEST0119: Machine malformed input fails
|
|
3106
|
+
function test0119_Machine_malformedInputFails() {
|
|
3084
3107
|
assertThrowsWithCode(
|
|
3085
3108
|
() => parseMachine('not valid machine notation'),
|
|
3086
3109
|
MachineSyntaxErrorCodes.PARSE_ERROR
|
|
3087
3110
|
);
|
|
3088
3111
|
}
|
|
3089
3112
|
|
|
3090
|
-
|
|
3113
|
+
// TEST0120: Machine unterminated bracket fails
|
|
3114
|
+
function test0120_Machine_unterminatedBracketFails() {
|
|
3091
3115
|
assertThrowsWithCode(
|
|
3092
3116
|
() => parseMachine('[extract cap:in=media:pdf'),
|
|
3093
3117
|
MachineSyntaxErrorCodes.PARSE_ERROR
|
|
@@ -3096,7 +3120,7 @@ function testMachine_unterminatedBracketFails() {
|
|
|
3096
3120
|
|
|
3097
3121
|
// --- Machine parser line-based mode tests ---
|
|
3098
3122
|
|
|
3099
|
-
function
|
|
3123
|
+
function test0121_Machine_lineBasedSimpleChain() {
|
|
3100
3124
|
const g = Machine.fromString(
|
|
3101
3125
|
'extract cap:in="media:pdf";extract;out="media:txt;textable"\n' +
|
|
3102
3126
|
'doc -> extract -> text'
|
|
@@ -3109,7 +3133,8 @@ function testMachine_lineBasedSimpleChain() {
|
|
|
3109
3133
|
'Target should be media:txt;textable');
|
|
3110
3134
|
}
|
|
3111
3135
|
|
|
3112
|
-
|
|
3136
|
+
// TEST0122: Machine line based two step chain
|
|
3137
|
+
function test0122_Machine_lineBasedTwoStepChain() {
|
|
3113
3138
|
const g = Machine.fromString(
|
|
3114
3139
|
'extract cap:in="media:pdf";extract;out="media:txt;textable"\n' +
|
|
3115
3140
|
'embed cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"\n' +
|
|
@@ -3119,7 +3144,8 @@ function testMachine_lineBasedTwoStepChain() {
|
|
|
3119
3144
|
assertEqual(g.edgeCount(), 2);
|
|
3120
3145
|
}
|
|
3121
3146
|
|
|
3122
|
-
|
|
3147
|
+
// TEST0123: Machine line based loop
|
|
3148
|
+
function test0123_Machine_lineBasedLoop() {
|
|
3123
3149
|
const g = Machine.fromString(
|
|
3124
3150
|
'p2t cap:in="media:disbound-page;textable";page-to-text;out="media:txt;textable"\n' +
|
|
3125
3151
|
'pages -> LOOP p2t -> texts'
|
|
@@ -3128,7 +3154,8 @@ function testMachine_lineBasedLoop() {
|
|
|
3128
3154
|
assertEqual(g.edges()[0].isLoop, true);
|
|
3129
3155
|
}
|
|
3130
3156
|
|
|
3131
|
-
|
|
3157
|
+
// TEST0124: Machine line based fan in
|
|
3158
|
+
function test0124_Machine_lineBasedFanIn() {
|
|
3132
3159
|
const g = Machine.fromString(
|
|
3133
3160
|
'thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"\n' +
|
|
3134
3161
|
'model_dl cap:in="media:model-spec;textable";download;out="media:model-spec;textable"\n' +
|
|
@@ -3141,7 +3168,8 @@ function testMachine_lineBasedFanIn() {
|
|
|
3141
3168
|
assertEqual(g.edges()[2].sources.length, 2);
|
|
3142
3169
|
}
|
|
3143
3170
|
|
|
3144
|
-
|
|
3171
|
+
// TEST0125: Machine mixed bracketed and line based
|
|
3172
|
+
function test0125_Machine_mixedBracketedAndLineBased() {
|
|
3145
3173
|
const g = Machine.fromString(
|
|
3146
3174
|
'[extract cap:in="media:pdf";extract;out="media:txt;textable"]\n' +
|
|
3147
3175
|
'doc -> extract -> text'
|
|
@@ -3149,7 +3177,8 @@ function testMachine_mixedBracketedAndLineBased() {
|
|
|
3149
3177
|
assertEqual(g.edgeCount(), 1);
|
|
3150
3178
|
}
|
|
3151
3179
|
|
|
3152
|
-
|
|
3180
|
+
// TEST0126: Machine line based equivalent to bracketed
|
|
3181
|
+
function test0126_Machine_lineBasedEquivalentToBracketed() {
|
|
3153
3182
|
const g1 = Machine.fromString(
|
|
3154
3183
|
'[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
|
|
3155
3184
|
'[doc -> extract -> text]'
|
|
@@ -3161,7 +3190,8 @@ function testMachine_lineBasedEquivalentToBracketed() {
|
|
|
3161
3190
|
assert(g1.isEquivalent(g2), 'Line-based and bracketed must produce equivalent graphs');
|
|
3162
3191
|
}
|
|
3163
3192
|
|
|
3164
|
-
|
|
3193
|
+
// TEST0127: Machine line based format serialization
|
|
3194
|
+
function test0127_Machine_lineBasedFormatSerialization() {
|
|
3165
3195
|
const g = new Machine([
|
|
3166
3196
|
new MachineEdge(
|
|
3167
3197
|
[MediaUrn.fromString('media:pdf')],
|
|
@@ -3182,7 +3212,8 @@ function testMachine_lineBasedFormatSerialization() {
|
|
|
3182
3212
|
assert(g.isEquivalent(reparsed), 'Line-based round-trip must produce equivalent graph');
|
|
3183
3213
|
}
|
|
3184
3214
|
|
|
3185
|
-
|
|
3215
|
+
// TEST0128: Machine line based and bracketed parse same graph
|
|
3216
|
+
function test0128_Machine_lineBasedAndBracketedParseSameGraph() {
|
|
3186
3217
|
const g = new Machine([
|
|
3187
3218
|
new MachineEdge(
|
|
3188
3219
|
[MediaUrn.fromString('media:pdf')],
|
|
@@ -3208,7 +3239,7 @@ function testMachine_lineBasedAndBracketedParseSameGraph() {
|
|
|
3208
3239
|
|
|
3209
3240
|
// --- Machine graph tests (mirrors graph.rs tests) ---
|
|
3210
3241
|
|
|
3211
|
-
function
|
|
3242
|
+
function test0129_Machine_edgeEquivalenceSameUrns() {
|
|
3212
3243
|
const e1 = new MachineEdge(
|
|
3213
3244
|
[MediaUrn.fromString('media:pdf')],
|
|
3214
3245
|
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
|
|
@@ -3224,7 +3255,8 @@ function testMachine_edgeEquivalenceSameUrns() {
|
|
|
3224
3255
|
assert(e1.isEquivalent(e2), 'Same URNs should be equivalent');
|
|
3225
3256
|
}
|
|
3226
3257
|
|
|
3227
|
-
|
|
3258
|
+
// TEST0130: Machine edge equivalence different cap urns
|
|
3259
|
+
function test0130_Machine_edgeEquivalenceDifferentCapUrns() {
|
|
3228
3260
|
const e1 = new MachineEdge(
|
|
3229
3261
|
[MediaUrn.fromString('media:pdf')],
|
|
3230
3262
|
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
|
|
@@ -3240,7 +3272,8 @@ function testMachine_edgeEquivalenceDifferentCapUrns() {
|
|
|
3240
3272
|
assert(!e1.isEquivalent(e2), 'Different cap URNs should not be equivalent');
|
|
3241
3273
|
}
|
|
3242
3274
|
|
|
3243
|
-
|
|
3275
|
+
// TEST0131: Machine edge equivalence different targets
|
|
3276
|
+
function test0131_Machine_edgeEquivalenceDifferentTargets() {
|
|
3244
3277
|
const e1 = new MachineEdge(
|
|
3245
3278
|
[MediaUrn.fromString('media:pdf')],
|
|
3246
3279
|
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
|
|
@@ -3256,7 +3289,8 @@ function testMachine_edgeEquivalenceDifferentTargets() {
|
|
|
3256
3289
|
assert(!e1.isEquivalent(e2), 'Different targets should not be equivalent');
|
|
3257
3290
|
}
|
|
3258
3291
|
|
|
3259
|
-
|
|
3292
|
+
// TEST0132: Machine edge equivalence different loop flag
|
|
3293
|
+
function test0132_Machine_edgeEquivalenceDifferentLoopFlag() {
|
|
3260
3294
|
const e1 = new MachineEdge(
|
|
3261
3295
|
[MediaUrn.fromString('media:pdf')],
|
|
3262
3296
|
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
|
|
@@ -3272,7 +3306,8 @@ function testMachine_edgeEquivalenceDifferentLoopFlag() {
|
|
|
3272
3306
|
assert(!e1.isEquivalent(e2), 'Different loop flags should not be equivalent');
|
|
3273
3307
|
}
|
|
3274
3308
|
|
|
3275
|
-
|
|
3309
|
+
// TEST0133: Machine edge equivalence source order independent
|
|
3310
|
+
function test0133_Machine_edgeEquivalenceSourceOrderIndependent() {
|
|
3276
3311
|
const e1 = new MachineEdge(
|
|
3277
3312
|
[MediaUrn.fromString('media:txt;textable'), MediaUrn.fromString('media:model-spec;textable')],
|
|
3278
3313
|
CapUrn.fromString('cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"'),
|
|
@@ -3288,7 +3323,8 @@ function testMachine_edgeEquivalenceSourceOrderIndependent() {
|
|
|
3288
3323
|
assert(e1.isEquivalent(e2), 'Source order should not matter for equivalence');
|
|
3289
3324
|
}
|
|
3290
3325
|
|
|
3291
|
-
|
|
3326
|
+
// TEST0134: Machine edge equivalence different source count
|
|
3327
|
+
function test0134_Machine_edgeEquivalenceDifferentSourceCount() {
|
|
3292
3328
|
const e1 = new MachineEdge(
|
|
3293
3329
|
[MediaUrn.fromString('media:txt;textable')],
|
|
3294
3330
|
CapUrn.fromString('cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"'),
|
|
@@ -3304,7 +3340,8 @@ function testMachine_edgeEquivalenceDifferentSourceCount() {
|
|
|
3304
3340
|
assert(!e1.isEquivalent(e2), 'Different source counts should not be equivalent');
|
|
3305
3341
|
}
|
|
3306
3342
|
|
|
3307
|
-
|
|
3343
|
+
// TEST0135: Machine graph equivalence same edges
|
|
3344
|
+
function test0135_Machine_graphEquivalenceSameEdges() {
|
|
3308
3345
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3309
3346
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3310
3347
|
);
|
|
@@ -3319,7 +3356,8 @@ function testMachine_graphEquivalenceSameEdges() {
|
|
|
3319
3356
|
assert(g1.isEquivalent(g2), 'Same edges should be equivalent');
|
|
3320
3357
|
}
|
|
3321
3358
|
|
|
3322
|
-
|
|
3359
|
+
// TEST0136: Machine graph equivalence reordered edges
|
|
3360
|
+
function test0136_Machine_graphEquivalenceReorderedEdges() {
|
|
3323
3361
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3324
3362
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3325
3363
|
);
|
|
@@ -3334,7 +3372,8 @@ function testMachine_graphEquivalenceReorderedEdges() {
|
|
|
3334
3372
|
assert(g1.isEquivalent(g2), 'Reordered edges should still be equivalent');
|
|
3335
3373
|
}
|
|
3336
3374
|
|
|
3337
|
-
|
|
3375
|
+
// TEST0137: Machine graph not equivalent different edge count
|
|
3376
|
+
function test0137_Machine_graphNotEquivalentDifferentEdgeCount() {
|
|
3338
3377
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3339
3378
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3340
3379
|
);
|
|
@@ -3348,7 +3387,8 @@ function testMachine_graphNotEquivalentDifferentEdgeCount() {
|
|
|
3348
3387
|
assert(!g1.isEquivalent(g2), 'Different edge counts should not be equivalent');
|
|
3349
3388
|
}
|
|
3350
3389
|
|
|
3351
|
-
|
|
3390
|
+
// TEST0138: Machine graph not equivalent different cap
|
|
3391
|
+
function test0138_Machine_graphNotEquivalentDifferentCap() {
|
|
3352
3392
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3353
3393
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3354
3394
|
);
|
|
@@ -3361,19 +3401,22 @@ function testMachine_graphNotEquivalentDifferentCap() {
|
|
|
3361
3401
|
assert(!g1.isEquivalent(g2), 'Different caps should not be equivalent');
|
|
3362
3402
|
}
|
|
3363
3403
|
|
|
3364
|
-
|
|
3404
|
+
// TEST0139: Machine graph empty
|
|
3405
|
+
function test0139_Machine_graphEmpty() {
|
|
3365
3406
|
const g = Machine.empty();
|
|
3366
3407
|
assert(g.isEmpty(), 'Empty graph should be empty');
|
|
3367
3408
|
assertEqual(g.edgeCount(), 0);
|
|
3368
3409
|
}
|
|
3369
3410
|
|
|
3370
|
-
|
|
3411
|
+
// TEST0140: Machine graph empty equivalence
|
|
3412
|
+
function test0140_Machine_graphEmptyEquivalence() {
|
|
3371
3413
|
const g1 = Machine.empty();
|
|
3372
3414
|
const g2 = Machine.empty();
|
|
3373
3415
|
assert(g1.isEquivalent(g2), 'Two empty graphs should be equivalent');
|
|
3374
3416
|
}
|
|
3375
3417
|
|
|
3376
|
-
|
|
3418
|
+
// TEST0141: Machine root sources linear chain
|
|
3419
|
+
function test0141_Machine_rootSourcesLinearChain() {
|
|
3377
3420
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3378
3421
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3379
3422
|
);
|
|
@@ -3387,7 +3430,8 @@ function testMachine_rootSourcesLinearChain() {
|
|
|
3387
3430
|
'Root source should be media:pdf');
|
|
3388
3431
|
}
|
|
3389
3432
|
|
|
3390
|
-
|
|
3433
|
+
// TEST0142: Machine leaf targets linear chain
|
|
3434
|
+
function test0142_Machine_leafTargetsLinearChain() {
|
|
3391
3435
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3392
3436
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3393
3437
|
);
|
|
@@ -3401,7 +3445,8 @@ function testMachine_leafTargetsLinearChain() {
|
|
|
3401
3445
|
'Leaf target should be media:embedding-vector;record;textable');
|
|
3402
3446
|
}
|
|
3403
3447
|
|
|
3404
|
-
|
|
3448
|
+
// TEST0143: Machine root sources fan in
|
|
3449
|
+
function test0143_Machine_rootSourcesFanIn() {
|
|
3405
3450
|
const e = new MachineEdge(
|
|
3406
3451
|
[MediaUrn.fromString('media:txt;textable'), MediaUrn.fromString('media:model-spec;textable')],
|
|
3407
3452
|
CapUrn.fromString('cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"'),
|
|
@@ -3413,7 +3458,8 @@ function testMachine_rootSourcesFanIn() {
|
|
|
3413
3458
|
assertEqual(roots.length, 2);
|
|
3414
3459
|
}
|
|
3415
3460
|
|
|
3416
|
-
|
|
3461
|
+
// TEST0144: Machine display edge
|
|
3462
|
+
function test0144_Machine_displayEdge() {
|
|
3417
3463
|
const e = new MachineEdge(
|
|
3418
3464
|
[MediaUrn.fromString('media:pdf')],
|
|
3419
3465
|
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
|
|
@@ -3424,7 +3470,8 @@ function testMachine_displayEdge() {
|
|
|
3424
3470
|
assert(display.includes('media:pdf'), 'Display should contain media:pdf');
|
|
3425
3471
|
}
|
|
3426
3472
|
|
|
3427
|
-
|
|
3473
|
+
// TEST0145: Machine display graph
|
|
3474
|
+
function test0145_Machine_displayGraph() {
|
|
3428
3475
|
const e = new MachineEdge(
|
|
3429
3476
|
[MediaUrn.fromString('media:pdf')],
|
|
3430
3477
|
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
|
|
@@ -3437,7 +3484,7 @@ function testMachine_displayGraph() {
|
|
|
3437
3484
|
|
|
3438
3485
|
// --- Machine serializer tests (mirrors serializer.rs tests) ---
|
|
3439
3486
|
|
|
3440
|
-
function
|
|
3487
|
+
function test0146_Machine_serializeSingleEdge() {
|
|
3441
3488
|
const g = new Machine([new MachineEdge(
|
|
3442
3489
|
[MediaUrn.fromString('media:pdf')],
|
|
3443
3490
|
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
|
|
@@ -3452,7 +3499,8 @@ function testMachine_serializeSingleEdge() {
|
|
|
3452
3499
|
assert(notation.includes('-> n1]'), 'Should use n1 for target: ' + notation);
|
|
3453
3500
|
}
|
|
3454
3501
|
|
|
3455
|
-
|
|
3502
|
+
// TEST0147: Machine serialize two edge chain
|
|
3503
|
+
function test0147_Machine_serializeTwoEdgeChain() {
|
|
3456
3504
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3457
3505
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3458
3506
|
);
|
|
@@ -3465,11 +3513,13 @@ function testMachine_serializeTwoEdgeChain() {
|
|
|
3465
3513
|
assertEqual(bracketCount, 4, 'Should have 4 brackets (2 headers + 2 wirings)');
|
|
3466
3514
|
}
|
|
3467
3515
|
|
|
3468
|
-
|
|
3516
|
+
// TEST0148: Machine serialize empty graph
|
|
3517
|
+
function test0148_Machine_serializeEmptyGraph() {
|
|
3469
3518
|
assertEqual(Machine.empty().toMachineNotation(), '');
|
|
3470
3519
|
}
|
|
3471
3520
|
|
|
3472
|
-
|
|
3521
|
+
// TEST0149: Machine roundtrip single edge
|
|
3522
|
+
function test0149_Machine_roundtripSingleEdge() {
|
|
3473
3523
|
const original = new Machine([new MachineEdge(
|
|
3474
3524
|
[MediaUrn.fromString('media:pdf')],
|
|
3475
3525
|
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
|
|
@@ -3482,7 +3532,8 @@ function testMachine_roundtripSingleEdge() {
|
|
|
3482
3532
|
'Single edge round-trip failed: ' + notation);
|
|
3483
3533
|
}
|
|
3484
3534
|
|
|
3485
|
-
|
|
3535
|
+
// TEST0151: Machine roundtrip two edge chain
|
|
3536
|
+
function test0151_Machine_roundtripTwoEdgeChain() {
|
|
3486
3537
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3487
3538
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3488
3539
|
);
|
|
@@ -3496,7 +3547,8 @@ function testMachine_roundtripTwoEdgeChain() {
|
|
|
3496
3547
|
'Two-edge chain round-trip failed: ' + notation);
|
|
3497
3548
|
}
|
|
3498
3549
|
|
|
3499
|
-
|
|
3550
|
+
// TEST0152: Machine roundtrip fan out
|
|
3551
|
+
function test0152_Machine_roundtripFanOut() {
|
|
3500
3552
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3501
3553
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3502
3554
|
);
|
|
@@ -3511,7 +3563,8 @@ function testMachine_roundtripFanOut() {
|
|
|
3511
3563
|
'Fan-out round-trip failed: ' + notation);
|
|
3512
3564
|
}
|
|
3513
3565
|
|
|
3514
|
-
|
|
3566
|
+
// TEST0153: Machine roundtrip loop edge
|
|
3567
|
+
function test0153_Machine_roundtripLoopEdge() {
|
|
3515
3568
|
const original = new Machine([new MachineEdge(
|
|
3516
3569
|
[MediaUrn.fromString('media:disbound-page;textable')],
|
|
3517
3570
|
CapUrn.fromString('cap:in="media:disbound-page;textable";page-to-text;out="media:txt;textable"'),
|
|
@@ -3524,7 +3577,8 @@ function testMachine_roundtripLoopEdge() {
|
|
|
3524
3577
|
assertEqual(reparsed.edges()[0].isLoop, true, 'isLoop must be preserved');
|
|
3525
3578
|
}
|
|
3526
3579
|
|
|
3527
|
-
|
|
3580
|
+
// TEST0154: Machine serialization is deterministic
|
|
3581
|
+
function test0154_Machine_serializationIsDeterministic() {
|
|
3528
3582
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3529
3583
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3530
3584
|
);
|
|
@@ -3537,7 +3591,8 @@ function testMachine_serializationIsDeterministic() {
|
|
|
3537
3591
|
assertEqual(n1, n2, 'Serialization must be deterministic');
|
|
3538
3592
|
}
|
|
3539
3593
|
|
|
3540
|
-
|
|
3594
|
+
// TEST0155: Machine reordered edges produce same notation
|
|
3595
|
+
function test0155_Machine_reorderedEdgesProduceSameNotation() {
|
|
3541
3596
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3542
3597
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3543
3598
|
);
|
|
@@ -3553,7 +3608,8 @@ function testMachine_reorderedEdgesProduceSameNotation() {
|
|
|
3553
3608
|
'Same graph with reordered edges must produce identical notation');
|
|
3554
3609
|
}
|
|
3555
3610
|
|
|
3556
|
-
|
|
3611
|
+
// TEST0160: Machine multiline serialize format
|
|
3612
|
+
function test0160_Machine_multilineSerializeFormat() {
|
|
3557
3613
|
const g = new Machine([new MachineEdge(
|
|
3558
3614
|
[MediaUrn.fromString('media:pdf')],
|
|
3559
3615
|
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
|
|
@@ -3569,7 +3625,7 @@ function testMachine_multilineSerializeFormat() {
|
|
|
3569
3625
|
|
|
3570
3626
|
// Aliases are pure-index `edge_<N>` regardless of the cap's tags; there is
|
|
3571
3627
|
// no privileged `op` tag to derive a friendlier name from.
|
|
3572
|
-
function
|
|
3628
|
+
function test0161_Machine_aliasFromOpTag() {
|
|
3573
3629
|
const g = new Machine([new MachineEdge(
|
|
3574
3630
|
[MediaUrn.fromString('media:pdf')],
|
|
3575
3631
|
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
|
|
@@ -3580,7 +3636,8 @@ function testMachine_aliasFromOpTag() {
|
|
|
3580
3636
|
assert(notation.includes('[edge_0 '), 'Expected edge_0 alias, got: ' + notation);
|
|
3581
3637
|
}
|
|
3582
3638
|
|
|
3583
|
-
|
|
3639
|
+
// TEST0162: Machine alias fallback without op tag
|
|
3640
|
+
function test0162_Machine_aliasFallbackWithoutOpTag() {
|
|
3584
3641
|
const g = new Machine([new MachineEdge(
|
|
3585
3642
|
[MediaUrn.fromString('media:pdf')],
|
|
3586
3643
|
CapUrn.fromString('cap:in="media:pdf";out="media:txt;textable"'),
|
|
@@ -3592,7 +3649,7 @@ function testMachine_aliasFallbackWithoutOpTag() {
|
|
|
3592
3649
|
}
|
|
3593
3650
|
|
|
3594
3651
|
// Pure-index aliases inherently disambiguate edges that share a marker tag.
|
|
3595
|
-
function
|
|
3652
|
+
function test0163_Machine_duplicateOpTagsDisambiguated() {
|
|
3596
3653
|
const g = new Machine([
|
|
3597
3654
|
new MachineEdge(
|
|
3598
3655
|
[MediaUrn.fromString('media:pdf')],
|
|
@@ -3614,7 +3671,7 @@ function testMachine_duplicateOpTagsDisambiguated() {
|
|
|
3614
3671
|
|
|
3615
3672
|
// --- Machine builder tests ---
|
|
3616
3673
|
|
|
3617
|
-
function
|
|
3674
|
+
function test0164_Machine_builderSingleEdge() {
|
|
3618
3675
|
const builder = new MachineBuilder();
|
|
3619
3676
|
builder.addEdge(
|
|
3620
3677
|
['media:pdf'],
|
|
@@ -3626,7 +3683,8 @@ function testMachine_builderSingleEdge() {
|
|
|
3626
3683
|
assertEqual(g.edges()[0].isLoop, false);
|
|
3627
3684
|
}
|
|
3628
3685
|
|
|
3629
|
-
|
|
3686
|
+
// TEST0165: Machine builder with loop
|
|
3687
|
+
function test0165_Machine_builderWithLoop() {
|
|
3630
3688
|
const builder = new MachineBuilder();
|
|
3631
3689
|
builder.addEdge(
|
|
3632
3690
|
['media:disbound-page;textable'],
|
|
@@ -3638,7 +3696,8 @@ function testMachine_builderWithLoop() {
|
|
|
3638
3696
|
assertEqual(g.edges()[0].isLoop, true);
|
|
3639
3697
|
}
|
|
3640
3698
|
|
|
3641
|
-
|
|
3699
|
+
// TEST0166: Machine builder chaining
|
|
3700
|
+
function test0166_Machine_builderChaining() {
|
|
3642
3701
|
const g = new MachineBuilder()
|
|
3643
3702
|
.addEdge(['media:pdf'], 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable')
|
|
3644
3703
|
.addEdge(['media:txt;textable'], 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable')
|
|
@@ -3646,7 +3705,8 @@ function testMachine_builderChaining() {
|
|
|
3646
3705
|
assertEqual(g.edgeCount(), 2);
|
|
3647
3706
|
}
|
|
3648
3707
|
|
|
3649
|
-
|
|
3708
|
+
// TEST0167: Machine builder equivalent to parsed
|
|
3709
|
+
function test0167_Machine_builderEquivalentToParsed() {
|
|
3650
3710
|
const parsed = Machine.fromString(
|
|
3651
3711
|
'[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
|
|
3652
3712
|
'[doc -> extract -> text]'
|
|
@@ -3658,7 +3718,8 @@ function testMachine_builderEquivalentToParsed() {
|
|
|
3658
3718
|
'Builder-constructed graph should be equivalent to parsed graph');
|
|
3659
3719
|
}
|
|
3660
3720
|
|
|
3661
|
-
|
|
3721
|
+
// TEST0168: Machine builder round trip
|
|
3722
|
+
function test0168_Machine_builderRoundTrip() {
|
|
3662
3723
|
const built = new MachineBuilder()
|
|
3663
3724
|
.addEdge(['media:pdf'], 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable')
|
|
3664
3725
|
.addEdge(['media:txt;textable'], 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable')
|
|
@@ -3670,7 +3731,7 @@ function testMachine_builderRoundTrip() {
|
|
|
3670
3731
|
|
|
3671
3732
|
// --- CapUrn.isEquivalent/isComparable tests ---
|
|
3672
3733
|
|
|
3673
|
-
function
|
|
3734
|
+
function test0169_Machine_capUrnIsEquivalent() {
|
|
3674
3735
|
const a = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
|
|
3675
3736
|
const b = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
|
|
3676
3737
|
assert(a.isEquivalent(b), 'Same cap URNs should be equivalent');
|
|
@@ -3678,21 +3739,24 @@ function testMachine_capUrnIsEquivalent() {
|
|
|
3678
3739
|
assert(!a.isEquivalent(c), 'Different cap URNs should not be equivalent');
|
|
3679
3740
|
}
|
|
3680
3741
|
|
|
3681
|
-
|
|
3742
|
+
// TEST0170: Machine cap urn is comparable
|
|
3743
|
+
function test0170_Machine_capUrnIsComparable() {
|
|
3682
3744
|
const general = CapUrn.fromString('cap:in="media:pdf";out="media:txt;textable"');
|
|
3683
3745
|
const specific = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
|
|
3684
3746
|
assert(general.isComparable(specific), 'General should be comparable to specific');
|
|
3685
3747
|
assert(specific.isComparable(general), 'isComparable should be symmetric');
|
|
3686
3748
|
}
|
|
3687
3749
|
|
|
3688
|
-
|
|
3750
|
+
// TEST0171: Machine cap urn in media urn
|
|
3751
|
+
function test0171_Machine_capUrnInMediaUrn() {
|
|
3689
3752
|
const cap = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
|
|
3690
3753
|
const inUrn = cap.inMediaUrn();
|
|
3691
3754
|
assert(inUrn instanceof MediaUrn, 'inMediaUrn should return MediaUrn');
|
|
3692
3755
|
assert(inUrn.isEquivalent(MediaUrn.fromString('media:pdf')), 'inMediaUrn should be media:pdf');
|
|
3693
3756
|
}
|
|
3694
3757
|
|
|
3695
|
-
|
|
3758
|
+
// TEST0172: Machine cap urn out media urn
|
|
3759
|
+
function test0172_Machine_capUrnOutMediaUrn() {
|
|
3696
3760
|
const cap = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
|
|
3697
3761
|
const outUrn = cap.outMediaUrn();
|
|
3698
3762
|
assert(outUrn instanceof MediaUrn, 'outMediaUrn should return MediaUrn');
|
|
@@ -3701,7 +3765,7 @@ function testMachine_capUrnOutMediaUrn() {
|
|
|
3701
3765
|
|
|
3702
3766
|
// --- MediaUrn.isEquivalent/isComparable tests ---
|
|
3703
3767
|
|
|
3704
|
-
function
|
|
3768
|
+
function test0173_Machine_mediaUrnIsEquivalent() {
|
|
3705
3769
|
const a = MediaUrn.fromString('media:pdf');
|
|
3706
3770
|
const b = MediaUrn.fromString('media:pdf');
|
|
3707
3771
|
assert(a.isEquivalent(b), 'Same media URNs should be equivalent');
|
|
@@ -3709,7 +3773,8 @@ function testMachine_mediaUrnIsEquivalent() {
|
|
|
3709
3773
|
assert(!a.isEquivalent(c), 'Different media URNs should not be equivalent');
|
|
3710
3774
|
}
|
|
3711
3775
|
|
|
3712
|
-
|
|
3776
|
+
// TEST0174: Machine media urn is comparable
|
|
3777
|
+
function test0174_Machine_mediaUrnIsComparable() {
|
|
3713
3778
|
const general = MediaUrn.fromString('media:textable');
|
|
3714
3779
|
const specific = MediaUrn.fromString('media:txt;textable');
|
|
3715
3780
|
assert(general.isComparable(specific), 'General should be comparable to specific');
|
|
@@ -3722,7 +3787,7 @@ function testMachine_mediaUrnIsComparable() {
|
|
|
3722
3787
|
// Phase 0A: Position tracking tests
|
|
3723
3788
|
// ============================================================================
|
|
3724
3789
|
|
|
3725
|
-
function
|
|
3790
|
+
function test0175_Machine_parseMachineWithAST_headerLocation() {
|
|
3726
3791
|
const input = '[extract cap:in="media:pdf";extract;out="media:txt;textable"][doc -> extract -> text]';
|
|
3727
3792
|
const result = parseMachineWithAST(input);
|
|
3728
3793
|
assert(result.statements.length === 2, 'Should have 2 statements');
|
|
@@ -3738,7 +3803,8 @@ function testMachine_parseMachineWithAST_headerLocation() {
|
|
|
3738
3803
|
assertEqual(stmt.alias, 'extract', 'Alias should be extract');
|
|
3739
3804
|
}
|
|
3740
3805
|
|
|
3741
|
-
|
|
3806
|
+
// TEST0176: Machine parse machine with a s t wiring location
|
|
3807
|
+
function test0176_Machine_parseMachineWithAST_wiringLocation() {
|
|
3742
3808
|
const input = '[extract cap:in="media:pdf";extract;out="media:txt;textable"]\n[doc -> extract -> text]';
|
|
3743
3809
|
const result = parseMachineWithAST(input);
|
|
3744
3810
|
assert(result.statements.length === 2, 'Should have 2 statements');
|
|
@@ -3752,7 +3818,8 @@ function testMachine_parseMachineWithAST_wiringLocation() {
|
|
|
3752
3818
|
assertEqual(wiring.target, 'text', 'Target should be text');
|
|
3753
3819
|
}
|
|
3754
3820
|
|
|
3755
|
-
|
|
3821
|
+
// TEST0177: Machine parse machine with a s t multiline positions
|
|
3822
|
+
function test0177_Machine_parseMachineWithAST_multilinePositions() {
|
|
3756
3823
|
const input = '[extract cap:in="media:pdf";extract;out="media:txt;textable"]\n[doc -> extract -> text]';
|
|
3757
3824
|
const result = parseMachineWithAST(input);
|
|
3758
3825
|
const headerLoc = result.statements[0].location;
|
|
@@ -3761,7 +3828,8 @@ function testMachine_parseMachineWithAST_multilinePositions() {
|
|
|
3761
3828
|
assertEqual(wiringLoc.start.line, 2, 'Wiring should be on line 2');
|
|
3762
3829
|
}
|
|
3763
3830
|
|
|
3764
|
-
|
|
3831
|
+
// TEST0178: Machine parse machine with a s t fan in source locations
|
|
3832
|
+
function test0178_Machine_parseMachineWithAST_fanInSourceLocations() {
|
|
3765
3833
|
const input = [
|
|
3766
3834
|
'[describe cap:in="media:image;png";describe-image;out="media:image-description;textable"]',
|
|
3767
3835
|
'[(thumbnail, model_spec) -> describe -> description]'
|
|
@@ -3772,7 +3840,8 @@ function testMachine_parseMachineWithAST_fanInSourceLocations() {
|
|
|
3772
3840
|
assert(wiring.sourceLocations.length === 2, 'Should have 2 source locations');
|
|
3773
3841
|
}
|
|
3774
3842
|
|
|
3775
|
-
|
|
3843
|
+
// TEST0179: Machine parse machine with a s t alias map
|
|
3844
|
+
function test0179_Machine_parseMachineWithAST_aliasMap() {
|
|
3776
3845
|
const input = [
|
|
3777
3846
|
'[extract cap:in="media:pdf";extract;out="media:txt;textable"]',
|
|
3778
3847
|
'[embed cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"]',
|
|
@@ -3790,7 +3859,8 @@ function testMachine_parseMachineWithAST_aliasMap() {
|
|
|
3790
3859
|
assert(extractEntry.capUrnLocation !== undefined, 'Alias entry should have capUrnLocation');
|
|
3791
3860
|
}
|
|
3792
3861
|
|
|
3793
|
-
|
|
3862
|
+
// TEST0180: Machine parse machine with a s t node media
|
|
3863
|
+
function test0180_Machine_parseMachineWithAST_nodeMedia() {
|
|
3794
3864
|
const input = [
|
|
3795
3865
|
'[extract cap:in="media:pdf";extract;out="media:txt;textable"]',
|
|
3796
3866
|
'[doc -> extract -> text]',
|
|
@@ -3802,7 +3872,8 @@ function testMachine_parseMachineWithAST_nodeMedia() {
|
|
|
3802
3872
|
assertEqual(result.nodeMedia.get('text').toString(), 'media:textable;txt', 'text should be media:textable;txt');
|
|
3803
3873
|
}
|
|
3804
3874
|
|
|
3805
|
-
|
|
3875
|
+
// TEST0181: Machine error location parse error
|
|
3876
|
+
function test0181_Machine_errorLocation_parseError() {
|
|
3806
3877
|
try {
|
|
3807
3878
|
parseMachine('[this is not valid');
|
|
3808
3879
|
throw new Error('Expected MachineSyntaxError');
|
|
@@ -3812,7 +3883,8 @@ function testMachine_errorLocation_parseError() {
|
|
|
3812
3883
|
}
|
|
3813
3884
|
}
|
|
3814
3885
|
|
|
3815
|
-
|
|
3886
|
+
// TEST0182: Machine error location duplicate alias
|
|
3887
|
+
function test0182_Machine_errorLocation_duplicateAlias() {
|
|
3816
3888
|
try {
|
|
3817
3889
|
parseMachine(
|
|
3818
3890
|
'[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
|
|
@@ -3826,7 +3898,8 @@ function testMachine_errorLocation_duplicateAlias() {
|
|
|
3826
3898
|
}
|
|
3827
3899
|
}
|
|
3828
3900
|
|
|
3829
|
-
|
|
3901
|
+
// TEST0183: Machine error location undefined alias
|
|
3902
|
+
function test0183_Machine_errorLocation_undefinedAlias() {
|
|
3830
3903
|
try {
|
|
3831
3904
|
parseMachine('[doc -> nonexistent -> text]');
|
|
3832
3905
|
throw new Error('Expected MachineSyntaxError');
|
|
@@ -3840,7 +3913,7 @@ function testMachine_errorLocation_undefinedAlias() {
|
|
|
3840
3913
|
// Phase 0C: Machine.toMermaid() tests
|
|
3841
3914
|
// ============================================================================
|
|
3842
3915
|
|
|
3843
|
-
function
|
|
3916
|
+
function test0184_Machine_toMermaid_linearChain() {
|
|
3844
3917
|
const machine = Machine.fromString(
|
|
3845
3918
|
'[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
|
|
3846
3919
|
'[doc -> extract -> text]'
|
|
@@ -3858,7 +3931,8 @@ function testMachine_toMermaid_linearChain() {
|
|
|
3858
3931
|
assert(mermaid.includes('(['), 'Should have stadium shape nodes');
|
|
3859
3932
|
}
|
|
3860
3933
|
|
|
3861
|
-
|
|
3934
|
+
// TEST0185: Machine to mermaid loop edge
|
|
3935
|
+
function test0185_Machine_toMermaid_loopEdge() {
|
|
3862
3936
|
const machine = Machine.fromString(
|
|
3863
3937
|
'[p2t cap:in="media:disbound-page;textable";page-to-text;out="media:txt;textable"]' +
|
|
3864
3938
|
'[pages -> LOOP p2t -> texts]'
|
|
@@ -3869,13 +3943,15 @@ function testMachine_toMermaid_loopEdge() {
|
|
|
3869
3943
|
assert(mermaid.includes('.->'), 'Should use dotted arrow for LOOP');
|
|
3870
3944
|
}
|
|
3871
3945
|
|
|
3872
|
-
|
|
3946
|
+
// TEST0186: Machine to mermaid empty graph
|
|
3947
|
+
function test0186_Machine_toMermaid_emptyGraph() {
|
|
3873
3948
|
const machine = Machine.empty();
|
|
3874
3949
|
const mermaid = machine.toMermaid();
|
|
3875
3950
|
assert(mermaid.includes('empty graph'), 'Should indicate empty graph');
|
|
3876
3951
|
}
|
|
3877
3952
|
|
|
3878
|
-
|
|
3953
|
+
// TEST0187: Machine to mermaid fan in
|
|
3954
|
+
function test0187_Machine_toMermaid_fanIn() {
|
|
3879
3955
|
const machine = Machine.fromString(
|
|
3880
3956
|
'[describe cap:in="media:image;png";describe-image;out="media:image-description;textable"]' +
|
|
3881
3957
|
'[(thumbnail, model_spec) -> describe -> description]'
|
|
@@ -3886,7 +3962,8 @@ function testMachine_toMermaid_fanIn() {
|
|
|
3886
3962
|
assertEqual(arrowCount, 2, 'Fan-in should produce 2 arrows');
|
|
3887
3963
|
}
|
|
3888
3964
|
|
|
3889
|
-
|
|
3965
|
+
// TEST0188: Machine to mermaid fan out
|
|
3966
|
+
function test0188_Machine_toMermaid_fanOut() {
|
|
3890
3967
|
const input = [
|
|
3891
3968
|
'[meta cap:in="media:pdf";extract-metadata;out="media:file-metadata;record;textable"]',
|
|
3892
3969
|
'[thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"]',
|
|
@@ -3906,7 +3983,7 @@ function testMachine_toMermaid_fanOut() {
|
|
|
3906
3983
|
// Phase 0B: FabricRegistryClient tests
|
|
3907
3984
|
// ============================================================================
|
|
3908
3985
|
|
|
3909
|
-
function
|
|
3986
|
+
function test0189_Machine_capRegistryEntry_construction() {
|
|
3910
3987
|
const entry = new FabricRegistryEntry({
|
|
3911
3988
|
urn: 'cap:in="media:pdf";extract;out="media:txt;textable"',
|
|
3912
3989
|
title: 'PDF Extractor',
|
|
@@ -3929,7 +4006,8 @@ function testMachine_capRegistryEntry_construction() {
|
|
|
3929
4006
|
assertEqual(entry.urnTags.op, 'extract', 'op tag should match');
|
|
3930
4007
|
}
|
|
3931
4008
|
|
|
3932
|
-
|
|
4009
|
+
// TEST0190: Machine media registry entry construction
|
|
4010
|
+
function test0190_Machine_mediaRegistryEntry_construction() {
|
|
3933
4011
|
const entry = new MediaRegistryEntry({
|
|
3934
4012
|
urn: 'media:pdf',
|
|
3935
4013
|
title: 'PDF Document',
|
|
@@ -3942,14 +4020,16 @@ function testMachine_mediaRegistryEntry_construction() {
|
|
|
3942
4020
|
assertEqual(entry.description, 'Portable Document Format', 'Description should match');
|
|
3943
4021
|
}
|
|
3944
4022
|
|
|
3945
|
-
|
|
4023
|
+
// TEST0191: Machine cap registry client construction
|
|
4024
|
+
function test0191_Machine_capRegistryClient_construction() {
|
|
3946
4025
|
const client = new FabricRegistryClient('https://example.com', 600);
|
|
3947
4026
|
assert(client !== null, 'Client should be constructed');
|
|
3948
4027
|
// Invalidate should not throw
|
|
3949
4028
|
client.invalidate();
|
|
3950
4029
|
}
|
|
3951
4030
|
|
|
3952
|
-
|
|
4031
|
+
// TEST0192: Machine cap registry entry defaults
|
|
4032
|
+
function test0192_Machine_capRegistryEntry_defaults() {
|
|
3953
4033
|
// Verify that missing fields default gracefully
|
|
3954
4034
|
const entry = new FabricRegistryEntry({ urn: 'cap:in=media:;test;out=media:' });
|
|
3955
4035
|
assertEqual(entry.urn, 'cap:in=media:;test;out=media:', 'URN should match');
|
|
@@ -4014,14 +4094,16 @@ if (typeof global.createCap === 'undefined') {
|
|
|
4014
4094
|
global.createCap = require('./capdag.js').createCap;
|
|
4015
4095
|
}
|
|
4016
4096
|
|
|
4017
|
-
|
|
4097
|
+
// TEST0193: Renderer cardinality label all four cases
|
|
4098
|
+
function test0193_Renderer_cardinalityLabel_allFourCases() {
|
|
4018
4099
|
assertEqual(rendererCardinalityLabel(false, false), '1\u21921', 'scalar-to-scalar');
|
|
4019
4100
|
assertEqual(rendererCardinalityLabel(true, false), 'n\u21921', 'sequence-to-scalar');
|
|
4020
4101
|
assertEqual(rendererCardinalityLabel(false, true), '1\u2192n', 'scalar-to-sequence');
|
|
4021
4102
|
assertEqual(rendererCardinalityLabel(true, true), 'n\u2192n', 'sequence-to-sequence');
|
|
4022
4103
|
}
|
|
4023
4104
|
|
|
4024
|
-
|
|
4105
|
+
// TEST0194: Renderer cardinality label uses unicode arrow
|
|
4106
|
+
function test0194_Renderer_cardinalityLabel_usesUnicodeArrow() {
|
|
4025
4107
|
// The label must use the real rightwards arrow (U+2192), not ASCII "->".
|
|
4026
4108
|
// Downstream styling and tests depend on this glyph.
|
|
4027
4109
|
const label = rendererCardinalityLabel(false, true);
|
|
@@ -4029,7 +4111,8 @@ function testRenderer_cardinalityLabel_usesUnicodeArrow() {
|
|
|
4029
4111
|
assert(!label.includes('->'), 'label must not contain the ASCII replacement "->"');
|
|
4030
4112
|
}
|
|
4031
4113
|
|
|
4032
|
-
|
|
4114
|
+
// TEST0195: Renderer cardinality from cap finds stdin arg not first arg
|
|
4115
|
+
function test0195_Renderer_cardinalityFromCap_findsStdinArgNotFirstArg() {
|
|
4033
4116
|
// The main input arg is the one whose sources include a stdin source.
|
|
4034
4117
|
// A naive implementation that reads args[0] would see `cli-only` (not a
|
|
4035
4118
|
// sequence) and report 1→1 even though the stdin arg is a sequence.
|
|
@@ -4053,7 +4136,8 @@ function testRenderer_cardinalityFromCap_findsStdinArgNotFirstArg() {
|
|
|
4053
4136
|
'must pick the arg that has a stdin source, not args[0]');
|
|
4054
4137
|
}
|
|
4055
4138
|
|
|
4056
|
-
|
|
4139
|
+
// TEST0196: Renderer cardinality from cap scalar defaults when fields missing
|
|
4140
|
+
function test0196_Renderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing() {
|
|
4057
4141
|
// No args and no output: both sides collapse to 1 (scalar default).
|
|
4058
4142
|
// If a bug makes the function return "n" for missing data, this fails.
|
|
4059
4143
|
const cap = { urn: 'cap:in="media:";noop;out="media:"' };
|
|
@@ -4061,7 +4145,8 @@ function testRenderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing() {
|
|
|
4061
4145
|
'missing args/output must default to scalar on both sides');
|
|
4062
4146
|
}
|
|
4063
4147
|
|
|
4064
|
-
|
|
4148
|
+
// TEST0197: Renderer cardinality from cap output only sequence
|
|
4149
|
+
function test0197_Renderer_cardinalityFromCap_outputOnlySequence() {
|
|
4065
4150
|
// One scalar stdin arg, output is a sequence: expects 1→n.
|
|
4066
4151
|
const cap = {
|
|
4067
4152
|
urn: 'cap:in="media:textable";generate;out="media:textable;list"',
|
|
@@ -4072,7 +4157,8 @@ function testRenderer_cardinalityFromCap_outputOnlySequence() {
|
|
|
4072
4157
|
'scalar stdin with sequence output must yield 1→n');
|
|
4073
4158
|
}
|
|
4074
4159
|
|
|
4075
|
-
|
|
4160
|
+
// TEST0198: Renderer cardinality from cap rejects string is sequence
|
|
4161
|
+
function test0198_Renderer_cardinalityFromCap_rejectsStringIsSequence() {
|
|
4076
4162
|
// The function must use strict `=== true` to avoid treating truthy strings
|
|
4077
4163
|
// as booleans. "true" is a string, not a boolean — it must NOT be treated
|
|
4078
4164
|
// as a sequence, because downstream renderers expect boolean semantics.
|
|
@@ -4085,7 +4171,8 @@ function testRenderer_cardinalityFromCap_rejectsStringIsSequence() {
|
|
|
4085
4171
|
'string "true" must not be treated as boolean true');
|
|
4086
4172
|
}
|
|
4087
4173
|
|
|
4088
|
-
|
|
4174
|
+
// TEST0199: Renderer cardinality from cap throws on non object
|
|
4175
|
+
function test0199_Renderer_cardinalityFromCap_throwsOnNonObject() {
|
|
4089
4176
|
// Fail-hard on invalid input; no fallback to a default cardinality.
|
|
4090
4177
|
let threw = false;
|
|
4091
4178
|
try {
|
|
@@ -4104,7 +4191,8 @@ function testRenderer_cardinalityFromCap_throwsOnNonObject() {
|
|
|
4104
4191
|
assert(threw, 'cardinalityFromCap(string) must throw');
|
|
4105
4192
|
}
|
|
4106
4193
|
|
|
4107
|
-
|
|
4194
|
+
// TEST0200: Renderer canonical media urn normalizes tag order
|
|
4195
|
+
function test0200_Renderer_canonicalMediaUrn_normalizesTagOrder() {
|
|
4108
4196
|
// Two media URNs with identical tags in different input orders must
|
|
4109
4197
|
// produce the same canonical string. If canonicalization is bypassed,
|
|
4110
4198
|
// the two strings remain different and this test exposes it.
|
|
@@ -4113,12 +4201,14 @@ function testRenderer_canonicalMediaUrn_normalizesTagOrder() {
|
|
|
4113
4201
|
assertEqual(a, b, 'tag-order differences must not survive canonicalization');
|
|
4114
4202
|
}
|
|
4115
4203
|
|
|
4116
|
-
|
|
4204
|
+
// TEST0201: Renderer canonical media urn preserves value tags
|
|
4205
|
+
function test0201_Renderer_canonicalMediaUrn_preservesValueTags() {
|
|
4117
4206
|
const c = rendererCanonicalMediaUrn('media:model;quant=q4');
|
|
4118
4207
|
assert(c.includes('quant=q4'), 'value tag must be preserved');
|
|
4119
4208
|
}
|
|
4120
4209
|
|
|
4121
|
-
|
|
4210
|
+
// TEST0202: Renderer canonical media urn rejects cap urn
|
|
4211
|
+
function test0202_Renderer_canonicalMediaUrn_rejectsCapUrn() {
|
|
4122
4212
|
// MediaUrn.fromString enforces the media: prefix. Feeding a cap URN to
|
|
4123
4213
|
// canonicalMediaUrn must fail hard.
|
|
4124
4214
|
let threw = false;
|
|
@@ -4130,7 +4220,8 @@ function testRenderer_canonicalMediaUrn_rejectsCapUrn() {
|
|
|
4130
4220
|
assert(threw, 'canonicalMediaUrn must reject non-media URNs');
|
|
4131
4221
|
}
|
|
4132
4222
|
|
|
4133
|
-
|
|
4223
|
+
// TEST0203: Renderer media node label rejects urn derived labels
|
|
4224
|
+
function test0203_Renderer_mediaNodeLabel_rejectsUrnDerivedLabels() {
|
|
4134
4225
|
let threw = false;
|
|
4135
4226
|
let message = '';
|
|
4136
4227
|
try {
|
|
@@ -4144,7 +4235,8 @@ function testRenderer_mediaNodeLabel_rejectsUrnDerivedLabels() {
|
|
|
4144
4235
|
'error must explain that explicit titles are required');
|
|
4145
4236
|
}
|
|
4146
4237
|
|
|
4147
|
-
|
|
4238
|
+
// TEST0204: Renderer build browse graph data rejects missing media titles
|
|
4239
|
+
function test0204_Renderer_buildBrowseGraphData_rejectsMissingMediaTitles() {
|
|
4148
4240
|
let threw = false;
|
|
4149
4241
|
let message = '';
|
|
4150
4242
|
try {
|
|
@@ -4201,7 +4293,8 @@ function makeCollectStep(mediaDef) {
|
|
|
4201
4293
|
};
|
|
4202
4294
|
}
|
|
4203
4295
|
|
|
4204
|
-
|
|
4296
|
+
// TEST0205: Renderer validate strand step rejects unknown variant
|
|
4297
|
+
function test0205_Renderer_validateStrandStep_rejectsUnknownVariant() {
|
|
4205
4298
|
// A step with an unknown variant must fail hard at validation; no
|
|
4206
4299
|
// silent coercion.
|
|
4207
4300
|
let threw = false;
|
|
@@ -4218,7 +4311,8 @@ function testRenderer_validateStrandStep_rejectsUnknownVariant() {
|
|
|
4218
4311
|
assert(threw, 'unknown variant must throw');
|
|
4219
4312
|
}
|
|
4220
4313
|
|
|
4221
|
-
|
|
4314
|
+
// TEST0206: Renderer validate strand step requires boolean is sequence
|
|
4315
|
+
function test0206_Renderer_validateStrandStep_requiresBooleanIsSequence() {
|
|
4222
4316
|
// A Cap variant must have boolean is_sequence fields; number or string
|
|
4223
4317
|
// must reject.
|
|
4224
4318
|
let threw = false;
|
|
@@ -4240,7 +4334,8 @@ function testRenderer_validateStrandStep_requiresBooleanIsSequence() {
|
|
|
4240
4334
|
assert(threw, 'non-boolean is_sequence must throw');
|
|
4241
4335
|
}
|
|
4242
4336
|
|
|
4243
|
-
|
|
4337
|
+
// TEST0207: Renderer classify strand cap steps cap flags
|
|
4338
|
+
function test0207_Renderer_classifyStrandCapSteps_capFlags() {
|
|
4244
4339
|
// Strand: ForEach → cap1 → cap2 → cap3 → Collect. cap1 should have
|
|
4245
4340
|
// prevForEach=true; cap3 should have nextCollect=true; cap2 should
|
|
4246
4341
|
// have neither.
|
|
@@ -4261,7 +4356,8 @@ function testRenderer_classifyStrandCapSteps_capFlags() {
|
|
|
4261
4356
|
assert(capFlags.get(3).nextCollect, 'cap3 has nextCollect');
|
|
4262
4357
|
}
|
|
4263
4358
|
|
|
4264
|
-
|
|
4359
|
+
// TEST0208: Renderer classify strand cap steps nested forks
|
|
4360
|
+
function test0208_Renderer_classifyStrandCapSteps_nestedForks() {
|
|
4265
4361
|
// Nested strand: ForEach → cap1 → ForEach → cap2 → Collect → cap3 → Collect.
|
|
4266
4362
|
// cap1 has prevForEach (outer), cap2 has prevForEach (inner) and
|
|
4267
4363
|
// nextCollect (inner), cap3 has nextCollect (outer).
|
|
@@ -4291,7 +4387,8 @@ function withMediaDisplayNames(payload, mediaDisplayNames) {
|
|
|
4291
4387
|
});
|
|
4292
4388
|
}
|
|
4293
4389
|
|
|
4294
|
-
|
|
4390
|
+
// TEST0209: Renderer build strand graph data single cap plain
|
|
4391
|
+
function test0209_Renderer_buildStrandGraphData_singleCapPlain() {
|
|
4295
4392
|
// Minimal strand with one plain 1→1 cap. Plan builder produces:
|
|
4296
4393
|
// input_slot → step_0 (cap) → output
|
|
4297
4394
|
// (two edges, three nodes). No cardinality marker in the cap label
|
|
@@ -4318,7 +4415,8 @@ function testRenderer_buildStrandGraphData_singleCapPlain() {
|
|
|
4318
4415
|
assert(outEdge !== undefined, 'output edge from step_0 to output exists');
|
|
4319
4416
|
}
|
|
4320
4417
|
|
|
4321
|
-
|
|
4418
|
+
// TEST0210: Renderer build strand graph data sequence shows cardinality
|
|
4419
|
+
function test0210_Renderer_buildStrandGraphData_sequenceShowsCardinality() {
|
|
4322
4420
|
// A cap with input_is_sequence=true MUST emit "(n→1)" on its edge
|
|
4323
4421
|
// label.
|
|
4324
4422
|
const payload = withMediaDisplayNames({
|
|
@@ -4338,7 +4436,8 @@ function testRenderer_buildStrandGraphData_sequenceShowsCardinality() {
|
|
|
4338
4436
|
`cap edge label must include (n\u21921) marker; got: ${capEdge.label}`);
|
|
4339
4437
|
}
|
|
4340
4438
|
|
|
4341
|
-
|
|
4439
|
+
// TEST0211: Renderer build strand graph data foreach collect span
|
|
4440
|
+
function test0211_Renderer_buildStrandGraphData_foreachCollectSpan() {
|
|
4342
4441
|
// Strand: [ForEach, Cap, Collect]. Plan builder produces:
|
|
4343
4442
|
// input_slot (source) →direct→ step_1 (cap) — cap emits its own
|
|
4344
4443
|
// direct edge from prev
|
|
@@ -4389,7 +4488,8 @@ function testRenderer_buildStrandGraphData_foreachCollectSpan() {
|
|
|
4389
4488
|
assertEqual(collectNode.label, 'collect', 'Collect node labeled "collect"');
|
|
4390
4489
|
}
|
|
4391
4490
|
|
|
4392
|
-
|
|
4491
|
+
// TEST0212: Renderer build strand graph data standalone collect
|
|
4492
|
+
function test0212_Renderer_buildStrandGraphData_standaloneCollect() {
|
|
4393
4493
|
// Strand with a standalone Collect (no enclosing ForEach). Plan
|
|
4394
4494
|
// builder creates a Collect node consuming prev directly — plain
|
|
4395
4495
|
// direct edge, no iteration/collection semantics.
|
|
@@ -4416,7 +4516,8 @@ function testRenderer_buildStrandGraphData_standaloneCollect() {
|
|
|
4416
4516
|
assertEqual(collectNode.label, 'collect', 'Collect node labeled "collect"');
|
|
4417
4517
|
}
|
|
4418
4518
|
|
|
4419
|
-
|
|
4519
|
+
// TEST0213: Renderer build strand graph data unclosed for each body
|
|
4520
|
+
function test0213_Renderer_buildStrandGraphData_unclosedForEachBody() {
|
|
4420
4521
|
// Strand: [Cap_a, ForEach, Cap_b] with no closing Collect. The plan
|
|
4421
4522
|
// builder's "unclosed ForEach" branch creates a ForEach node
|
|
4422
4523
|
// connecting Cap_a to Cap_b via iteration, with prev becoming the
|
|
@@ -4453,7 +4554,8 @@ function testRenderer_buildStrandGraphData_unclosedForEachBody() {
|
|
|
4453
4554
|
'output edge step_2 → output');
|
|
4454
4555
|
}
|
|
4455
4556
|
|
|
4456
|
-
|
|
4557
|
+
// TEST0214: Renderer build strand graph data nested for each throws
|
|
4558
|
+
function test0214_Renderer_buildStrandGraphData_nestedForEachThrows() {
|
|
4457
4559
|
// Nested ForEach without an intervening body cap in the outer
|
|
4458
4560
|
// ForEach is an illegal nesting per plan_builder. The renderer
|
|
4459
4561
|
// must throw the same error to surface the issue rather than
|
|
@@ -4481,7 +4583,8 @@ function testRenderer_buildStrandGraphData_nestedForEachThrows() {
|
|
|
4481
4583
|
assert(threw, 'nested ForEach without outer body cap must throw');
|
|
4482
4584
|
}
|
|
4483
4585
|
|
|
4484
|
-
|
|
4586
|
+
// TEST0215: Renderer collapse strand single cap body keeps cap own label
|
|
4587
|
+
function test0215_Renderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
|
|
4485
4588
|
// User spec: ForEach/Collect are NOT rendered as nodes, and
|
|
4486
4589
|
// the collapse does NOT relabel cap edges. Each cap edge
|
|
4487
4590
|
// carries whatever label the strand builder emitted — the
|
|
@@ -4533,7 +4636,8 @@ function testRenderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
|
|
|
4533
4636
|
'collect bridge is unlabeled');
|
|
4534
4637
|
}
|
|
4535
4638
|
|
|
4536
|
-
|
|
4639
|
+
// TEST0216: Renderer collapse strand unclosed for each body collapses
|
|
4640
|
+
function test0216_Renderer_collapseStrand_unclosedForEachBodyCollapses() {
|
|
4537
4641
|
// [Cap_a(1→1), ForEach, Cap_b(1→1)] with no Collect,
|
|
4538
4642
|
// source=media:a, target=media:c. Cap_b's to_spec is media:c
|
|
4539
4643
|
// which is equivalent to target_media_urn, so the output node is
|
|
@@ -4589,7 +4693,8 @@ function testRenderer_collapseStrand_unclosedForEachBodyCollapses() {
|
|
|
4589
4693
|
'merged step_2 takes on the strand-target role');
|
|
4590
4694
|
}
|
|
4591
4695
|
|
|
4592
|
-
|
|
4696
|
+
// TEST0217: Renderer collapse strand standalone collect collapses
|
|
4697
|
+
function test0217_Renderer_collapseStrand_standaloneCollectCollapses() {
|
|
4593
4698
|
// [Cap, Collect] with no enclosing ForEach, source=media:a,
|
|
4594
4699
|
// target=media:b;list (NOT equivalent to cap's to_spec media:b,
|
|
4595
4700
|
// so the output node is retained after collapse).
|
|
@@ -4632,7 +4737,8 @@ function testRenderer_collapseStrand_standaloneCollectCollapses() {
|
|
|
4632
4737
|
'the synthesized bridging edge for a standalone Collect is an unlabeled connector (cap labels carry all cardinality info)');
|
|
4633
4738
|
}
|
|
4634
4739
|
|
|
4635
|
-
|
|
4740
|
+
// TEST0218: Renderer collapse strand sequence producing cap before foreach
|
|
4741
|
+
function test0218_Renderer_collapseStrand_sequenceProducingCapBeforeForeach() {
|
|
4636
4742
|
// Regression test mirroring the user's real strand:
|
|
4637
4743
|
// [Cap_disbind (output_is_sequence=true), ForEach, Cap_make_decision],
|
|
4638
4744
|
// source = media:pdf, target = media:decision (equivalent to
|
|
@@ -4696,7 +4802,8 @@ function testRenderer_collapseStrand_sequenceProducingCapBeforeForeach() {
|
|
|
4696
4802
|
'output node merged into step_2 because they represent the same URN');
|
|
4697
4803
|
}
|
|
4698
4804
|
|
|
4699
|
-
|
|
4805
|
+
// TEST0219: Renderer collapse strand plain cap merges trailing output
|
|
4806
|
+
function test0219_Renderer_collapseStrand_plainCapMergesTrailingOutput() {
|
|
4700
4807
|
// A strand with a single plain 1→1 cap whose to_spec equals
|
|
4701
4808
|
// target_media_urn. The plan-builder topology produces:
|
|
4702
4809
|
// input_slot → step_0 (cap) → output
|
|
@@ -4732,7 +4839,8 @@ function testRenderer_collapseStrand_plainCapMergesTrailingOutput() {
|
|
|
4732
4839
|
assertEqual(collapsed.edges[0].label, 'x', 'cap title preserved as edge label');
|
|
4733
4840
|
}
|
|
4734
4841
|
|
|
4735
|
-
|
|
4842
|
+
// TEST0220: Renderer collapse strand plain cap distinct target no merge
|
|
4843
|
+
function test0220_Renderer_collapseStrand_plainCapDistinctTargetNoMerge() {
|
|
4736
4844
|
// A strand with a single plain cap whose to_spec is NOT
|
|
4737
4845
|
// equivalent to target_media_urn. The output node must be retained
|
|
4738
4846
|
// and the trailing connector edge preserved.
|
|
@@ -4758,7 +4866,8 @@ function testRenderer_collapseStrand_plainCapDistinctTargetNoMerge() {
|
|
|
4758
4866
|
'step_0 retained');
|
|
4759
4867
|
}
|
|
4760
4868
|
|
|
4761
|
-
|
|
4869
|
+
// TEST0221: Renderer validate strand payload missing source media urn
|
|
4870
|
+
function test0221_Renderer_validateStrandPayload_missingSourceMediaUrn() {
|
|
4762
4871
|
let threw = false;
|
|
4763
4872
|
try {
|
|
4764
4873
|
rendererValidateStrandPayload({ target_media_urn: 'media:b', steps: [] });
|
|
@@ -4771,7 +4880,7 @@ function testRenderer_validateStrandPayload_missingSourceMediaUrn() {
|
|
|
4771
4880
|
|
|
4772
4881
|
// ---------------- run builder ----------------
|
|
4773
4882
|
|
|
4774
|
-
function
|
|
4883
|
+
function test0222_Renderer_validateBodyOutcome_rejectsNegativeIndex() {
|
|
4775
4884
|
let threw = false;
|
|
4776
4885
|
try {
|
|
4777
4886
|
rendererValidateBodyOutcome({ body_index: -1, success: true, cap_urns: [] }, 'test');
|
|
@@ -4781,7 +4890,8 @@ function testRenderer_validateBodyOutcome_rejectsNegativeIndex() {
|
|
|
4781
4890
|
assert(threw, 'negative body_index must throw');
|
|
4782
4891
|
}
|
|
4783
4892
|
|
|
4784
|
-
|
|
4893
|
+
// TEST0223: Renderer build run graph data pages successes and failures
|
|
4894
|
+
function test0223_Renderer_buildRunGraphData_pagesSuccessesAndFailures() {
|
|
4785
4895
|
// 6 successes, 4 failures. visible=3+2, total=10. Body has 2
|
|
4786
4896
|
// caps (a, b). Each body replica is a chain of:
|
|
4787
4897
|
// entry node + body_step_0 (cap a) + body_step_1 (cap b)
|
|
@@ -4851,7 +4961,8 @@ function testRenderer_buildRunGraphData_pagesSuccessesAndFailures() {
|
|
|
4851
4961
|
assertEqual(failureShowMore.data.hiddenCount, 2, 'failure hidden count = 2');
|
|
4852
4962
|
}
|
|
4853
4963
|
|
|
4854
|
-
|
|
4964
|
+
// TEST0224: Renderer build run graph data failure without failed cap renders full trace
|
|
4965
|
+
function test0224_Renderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace() {
|
|
4855
4966
|
// A failure without failed_cap (infrastructure failure) must
|
|
4856
4967
|
// still render the full body trace — the builder must not
|
|
4857
4968
|
// crash or produce zero replicas.
|
|
@@ -4889,7 +5000,8 @@ function testRenderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace(
|
|
|
4889
5000
|
assertEqual(failureNodes, 2, 'entry + body cap = 2 failure replica nodes');
|
|
4890
5001
|
}
|
|
4891
5002
|
|
|
4892
|
-
|
|
5003
|
+
// TEST0225: Renderer build run graph data uses cap urn is equivalent for failed cap
|
|
5004
|
+
function test0225_Renderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap() {
|
|
4893
5005
|
// The renderer matches failed_cap against step cap URNs via
|
|
4894
5006
|
// CapUrn.isEquivalent, NOT string equality. Feed a payload where
|
|
4895
5007
|
// failed_cap and the step's cap_urn differ only in tag order — they
|
|
@@ -4948,7 +5060,8 @@ function testRenderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap() {
|
|
|
4948
5060
|
assertEqual(failureNodes, 3, 'trace truncates at cap y via isEquivalent, yielding entry + 2 cap nodes');
|
|
4949
5061
|
}
|
|
4950
5062
|
|
|
4951
|
-
|
|
5063
|
+
// TEST0226: Renderer build run graph data backbone has no foreach node
|
|
5064
|
+
function test0226_Renderer_buildRunGraphData_backboneHasNoForeachNode() {
|
|
4952
5065
|
// Regression test for the run-mode rendering fix: the backbone
|
|
4953
5066
|
// delivered to cytoscape must NOT contain any strand-foreach or
|
|
4954
5067
|
// strand-collect nodes. Run mode inherits the same cosmetic
|
|
@@ -5002,7 +5115,8 @@ function testRenderer_buildRunGraphData_backboneHasNoForeachNode() {
|
|
|
5002
5115
|
assertEqual(built.showMoreNodes.length, 0, 'no show-more nodes when no hidden outcomes');
|
|
5003
5116
|
}
|
|
5004
5117
|
|
|
5005
|
-
|
|
5118
|
+
// TEST0227: Renderer build run graph data all failed drops target placeholder
|
|
5119
|
+
function test0227_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
|
|
5006
5120
|
// When every body fails, the strand target node was never
|
|
5007
5121
|
// reached by any execution. The render drops BOTH the backbone
|
|
5008
5122
|
// foreach-entry edge AND the orphaned target node so the user
|
|
@@ -5067,7 +5181,8 @@ function testRenderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
|
|
|
5067
5181
|
assertEqual(hasInputSlot, true, 'input_slot survives as the shared source');
|
|
5068
5182
|
}
|
|
5069
5183
|
|
|
5070
|
-
|
|
5184
|
+
// TEST0228: Renderer build run graph data unclosed foreach success no merge
|
|
5185
|
+
function test0228_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
|
|
5071
5186
|
// Strand without a Collect: [Disbind, ForEach, make_decision].
|
|
5072
5187
|
// Under the machfab realize_strand semantics there's no Collect,
|
|
5073
5188
|
// so each body produces its OWN terminal output. Successful
|
|
@@ -5128,7 +5243,8 @@ function testRenderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
|
|
|
5128
5243
|
assertEqual(forkEdges.length, 1, 'fork edge input_slot → body-0-entry exists');
|
|
5129
5244
|
}
|
|
5130
5245
|
|
|
5131
|
-
|
|
5246
|
+
// TEST0229: Renderer build run graph data closed foreach success merges at collect target
|
|
5247
|
+
function test0229_Renderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarget() {
|
|
5132
5248
|
// With a Collect closing the body, successful replicas DO merge
|
|
5133
5249
|
// into the post-collect target so the flow converges.
|
|
5134
5250
|
// Strand: [Disbind, ForEach, Cap_a, Cap_b, Collect] with a
|
|
@@ -5183,7 +5299,7 @@ function testRenderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarge
|
|
|
5183
5299
|
|
|
5184
5300
|
// ---------------- editor-graph builder ----------------
|
|
5185
5301
|
|
|
5186
|
-
function
|
|
5302
|
+
function test0230_Renderer_validateEditorGraphPayload_rejectsUnknownKind() {
|
|
5187
5303
|
let threw = false;
|
|
5188
5304
|
try {
|
|
5189
5305
|
rendererValidateEditorGraphPayload({
|
|
@@ -5197,7 +5313,8 @@ function testRenderer_validateEditorGraphPayload_rejectsUnknownKind() {
|
|
|
5197
5313
|
assert(threw, 'unknown element kind must throw');
|
|
5198
5314
|
}
|
|
5199
5315
|
|
|
5200
|
-
|
|
5316
|
+
// TEST0231: Renderer build editor graph data collapses caps into labeled edges
|
|
5317
|
+
function test0231_Renderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges() {
|
|
5201
5318
|
// The notation analyzer emits a bipartite chain per cap
|
|
5202
5319
|
// application: data_node → arg_edge → cap_node → arg_edge →
|
|
5203
5320
|
// data_node. The machine builder collapses each cap into a
|
|
@@ -5235,7 +5352,8 @@ function testRenderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges() {
|
|
|
5235
5352
|
'edge carries the cap node tokenId so editor cross-highlight points to the cap in source text');
|
|
5236
5353
|
}
|
|
5237
5354
|
|
|
5238
|
-
|
|
5355
|
+
// TEST0232: Renderer build editor graph data loop marked edge gets loop class
|
|
5356
|
+
function test0232_Renderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass() {
|
|
5239
5357
|
// A cap marked `is_loop: true` must produce a `machine-loop`
|
|
5240
5358
|
// edge so the stylesheet's dashed amber rule applies.
|
|
5241
5359
|
const data = {
|
|
@@ -5253,7 +5371,8 @@ function testRenderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass() {
|
|
|
5253
5371
|
'loop-marked cap emits machine-loop class on the collapsed edge');
|
|
5254
5372
|
}
|
|
5255
5373
|
|
|
5256
|
-
|
|
5374
|
+
// TEST0233: Renderer build editor graph data cardinality from data slot sequence flags
|
|
5375
|
+
function test0233_Renderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags() {
|
|
5257
5376
|
// Cardinality markers come from the source and target data
|
|
5258
5377
|
// slots' `is_sequence` flags. A cap whose output data slot has
|
|
5259
5378
|
// `is_sequence=true` shows "(1→n)" on its collapsed edge.
|
|
@@ -5272,7 +5391,8 @@ function testRenderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags(
|
|
|
5272
5391
|
'cardinality marker "(1→n)" derived from output data slot is_sequence=true');
|
|
5273
5392
|
}
|
|
5274
5393
|
|
|
5275
|
-
|
|
5394
|
+
// TEST0234: Renderer build editor graph data cap without complete args is dropped
|
|
5395
|
+
function test0234_Renderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped() {
|
|
5276
5396
|
// A cap with no incoming or no outgoing argument edges (e.g.
|
|
5277
5397
|
// the user is mid-typing) contributes nothing to the render.
|
|
5278
5398
|
// The data slots are still emitted.
|
|
@@ -5289,7 +5409,8 @@ function testRenderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped() {
|
|
|
5289
5409
|
'incomplete cap (no outgoing argument) drops out of the render');
|
|
5290
5410
|
}
|
|
5291
5411
|
|
|
5292
|
-
|
|
5412
|
+
// TEST0235: Renderer build editor graph data rejects edge with missing source
|
|
5413
|
+
function test0235_Renderer_buildEditorGraphData_rejectsEdgeWithMissingSource() {
|
|
5293
5414
|
let threw = false;
|
|
5294
5415
|
try {
|
|
5295
5416
|
rendererBuildEditorGraphData({
|
|
@@ -5305,7 +5426,7 @@ function testRenderer_buildEditorGraphData_rejectsEdgeWithMissingSource() {
|
|
|
5305
5426
|
|
|
5306
5427
|
// ---------------- resolved-machine builder ----------------
|
|
5307
5428
|
|
|
5308
|
-
function
|
|
5429
|
+
function test0236_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
|
|
5309
5430
|
// A single-strand machine: media:pdf → extract → media:txt
|
|
5310
5431
|
// → embed → media:embedding. Two edges, three nodes, no
|
|
5311
5432
|
// loops, no fan-in. Tests the basic shape — nodes and
|
|
@@ -5363,7 +5484,8 @@ function testRenderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
|
|
|
5363
5484
|
'output anchor node carries strand-target class');
|
|
5364
5485
|
}
|
|
5365
5486
|
|
|
5366
|
-
|
|
5487
|
+
// TEST0237: Renderer build resolved machine graph data loop edge gets loop class
|
|
5488
|
+
function test0237_Renderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass() {
|
|
5367
5489
|
// An is_loop edge corresponds to a strand step inside a
|
|
5368
5490
|
// ForEach body. The renderer must mark it with the
|
|
5369
5491
|
// `machine-loop` class so the dashed amber rule applies.
|
|
@@ -5397,7 +5519,8 @@ function testRenderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass() {
|
|
|
5397
5519
|
'is_loop=true must produce a machine-loop class on the cap edge');
|
|
5398
5520
|
}
|
|
5399
5521
|
|
|
5400
|
-
|
|
5522
|
+
// TEST0238: Renderer build resolved machine graph data fan in produces edge per assignment
|
|
5523
|
+
function test0238_Renderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignment() {
|
|
5401
5524
|
// A cap with two input args (a fan-in) gets one rendered
|
|
5402
5525
|
// edge per (source_node, target_node) pair so cytoscape can
|
|
5403
5526
|
// draw both incoming wires. Both edges share the cap title
|
|
@@ -5437,7 +5560,8 @@ function testRenderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignme
|
|
|
5437
5560
|
assertEqual(built.edges[1].data.target, 'n2', 'second edge targets n2');
|
|
5438
5561
|
}
|
|
5439
5562
|
|
|
5440
|
-
|
|
5563
|
+
// TEST0239: Renderer build resolved machine graph data multi strand keeps strands disjoint
|
|
5564
|
+
function test0239_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjoint() {
|
|
5441
5565
|
// Two strands inside one machine. Each strand has its own
|
|
5442
5566
|
// nodes and edges. Node ids are globally unique across
|
|
5443
5567
|
// strands (Rust assigns them via a single counter), so no
|
|
@@ -5499,7 +5623,8 @@ function testRenderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjo
|
|
|
5499
5623
|
assertEqual(idToStrand['n3'], 1, 'n3 belongs to strand 1');
|
|
5500
5624
|
}
|
|
5501
5625
|
|
|
5502
|
-
|
|
5626
|
+
// TEST0240: Renderer build resolved machine graph data duplicate node id across strands fails hard
|
|
5627
|
+
function test0240_Renderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrandsFailsHard() {
|
|
5503
5628
|
// Node ids must be globally unique across strands. The
|
|
5504
5629
|
// Rust serializer guarantees this via a single global
|
|
5505
5630
|
// counter. If the host ever feeds a payload that violates
|
|
@@ -5534,7 +5659,8 @@ function testRenderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrands
|
|
|
5534
5659
|
'error must name the colliding node id');
|
|
5535
5660
|
}
|
|
5536
5661
|
|
|
5537
|
-
|
|
5662
|
+
// TEST0241: Renderer validate resolved machine payload rejects missing fields
|
|
5663
|
+
function test0241_Renderer_validateResolvedMachinePayload_rejectsMissingFields() {
|
|
5538
5664
|
// The validator must reject any payload missing a required
|
|
5539
5665
|
// field on a strand, edge, node, or assignment binding.
|
|
5540
5666
|
// We exercise the most-likely-to-be-missed field on each
|
|
@@ -6065,9 +6191,9 @@ async function runTests() {
|
|
|
6065
6191
|
// /api/capabilities). These tests guard the minimal API the renderer relies
|
|
6066
6192
|
// on: new CapFab(), addCap(cap, registryName), getEdges(), getOutgoing().
|
|
6067
6193
|
console.log('\n--- cap_fab (browse-mode API used by cap-fab-renderer) ---');
|
|
6068
|
-
runTest('cap_fab: add_cap_populates_edges_and_nodes',
|
|
6069
|
-
runTest('cap_fab: get_outgoing_conforms_to_matching',
|
|
6070
|
-
runTest('cap_fab: distinct_registry_names_recorded_per_edge',
|
|
6194
|
+
runTest('cap_fab: add_cap_populates_edges_and_nodes', test0052_CapFabAddCapPopulatesEdgesAndNodes);
|
|
6195
|
+
runTest('cap_fab: get_outgoing_conforms_to_matching', test0053_CapFabGetOutgoingConformsToMatching);
|
|
6196
|
+
runTest('cap_fab: distinct_registry_names_recorded_per_edge', test0057_CapFabDistinctRegistryNames);
|
|
6071
6197
|
|
|
6072
6198
|
// caller.rs: TEST156-TEST159
|
|
6073
6199
|
console.log('\n--- caller.rs (StdinSource) ---');
|
|
@@ -6096,22 +6222,22 @@ async function runTests() {
|
|
|
6096
6222
|
runTest('TEST308: model_path_urn', test308_modelPathUrn);
|
|
6097
6223
|
runTest('TEST309: model_availability_and_path_are_distinct', test309_modelAvailabilityAndPathAreDistinct);
|
|
6098
6224
|
runTest('TEST310: llm_generate_text_urn', test310_llmGenerateTextUrn);
|
|
6099
|
-
runTest('llm_generate_text_urn_specs',
|
|
6225
|
+
runTest('llm_generate_text_urn_specs', test0058_LlmGenerateTextUrnSpecs);
|
|
6100
6226
|
runTest('TEST312: all_urn_builders_produce_valid_urns', test312_allUrnBuildersProduceValidUrns);
|
|
6101
6227
|
|
|
6102
6228
|
// JS-specific tests (no Rust number)
|
|
6103
6229
|
console.log('\n--- JS-specific ---');
|
|
6104
|
-
runTest('JS: build_extension_index',
|
|
6105
|
-
runTest('JS: media_urns_for_extension',
|
|
6106
|
-
runTest('JS: get_extension_mappings',
|
|
6107
|
-
runTest('JS: resolve_media_urn_from_specs',
|
|
6108
|
-
runTest('JS: cap_json_serialization',
|
|
6109
|
-
runTest('JS: cap_documentation_round_trip',
|
|
6110
|
-
runTest('JS: cap_documentation_omitted_when_null',
|
|
6111
|
-
runTest('JS: media_def_documentation_propagates_through_resolve',
|
|
6112
|
-
runTest('JS: stdin_source_kind_constants',
|
|
6113
|
-
runTest('JS: stdin_source_null_data',
|
|
6114
|
-
runTest('JS: media_def_construction',
|
|
6230
|
+
runTest('JS: build_extension_index', test0059_JS_buildExtensionIndex);
|
|
6231
|
+
runTest('JS: media_urns_for_extension', test0069_JS_mediaUrnsForExtension);
|
|
6232
|
+
runTest('JS: get_extension_mappings', test0070_JS_getExtensionMappings);
|
|
6233
|
+
runTest('JS: resolve_media_urn_from_specs', test0073_JS_resolveMediaUrnFromSpecs);
|
|
6234
|
+
runTest('JS: cap_json_serialization', test0079_JS_capJSONSerialization);
|
|
6235
|
+
runTest('JS: cap_documentation_round_trip', test0080_JS_capDocumentationRoundTrip);
|
|
6236
|
+
runTest('JS: cap_documentation_omitted_when_null', test0081_JS_capDocumentationOmittedWhenNull);
|
|
6237
|
+
runTest('JS: media_def_documentation_propagates_through_resolve', test0082_JS_mediaDefDocumentationPropagatesThroughResolve);
|
|
6238
|
+
runTest('JS: stdin_source_kind_constants', test0083_JS_stdinSourceKindConstants);
|
|
6239
|
+
runTest('JS: stdin_source_null_data', test0084_JS_stdinSourceNullData);
|
|
6240
|
+
runTest('JS: media_def_construction', test0085_JS_mediaDefConstruction);
|
|
6115
6241
|
|
|
6116
6242
|
// cartridge_repo: CartridgeRepoServer and CartridgeRepoClient tests
|
|
6117
6243
|
console.log('\n--- cartridge_repo ---');
|
|
@@ -6179,174 +6305,174 @@ async function runTests() {
|
|
|
6179
6305
|
|
|
6180
6306
|
// machine module: parser tests (mirrors parser.rs)
|
|
6181
6307
|
console.log('\n--- machine/parser.rs ---');
|
|
6182
|
-
runTest('MACHINE:empty_input',
|
|
6183
|
-
runTest('MACHINE:whitespace_only',
|
|
6184
|
-
runTest('MACHINE:header_only_no_wirings',
|
|
6185
|
-
runTest('MACHINE:duplicate_alias',
|
|
6186
|
-
runTest('MACHINE:simple_linear_chain',
|
|
6187
|
-
runTest('MACHINE:two_step_chain',
|
|
6188
|
-
runTest('MACHINE:fan_out',
|
|
6189
|
-
runTest('MACHINE:fan_in_secondary_assigned_by_prior_wiring',
|
|
6190
|
-
runTest('MACHINE:fan_in_secondary_unassigned_gets_wildcard',
|
|
6191
|
-
runTest('MACHINE:loop_edge',
|
|
6192
|
-
runTest('MACHINE:undefined_alias_fails',
|
|
6193
|
-
runTest('MACHINE:node_alias_collision',
|
|
6194
|
-
runTest('MACHINE:conflicting_media_types_fail',
|
|
6195
|
-
runTest('MACHINE:multiline_format',
|
|
6196
|
-
runTest('MACHINE:different_aliases_same_graph',
|
|
6197
|
-
runTest('MACHINE:malformed_input_fails',
|
|
6198
|
-
runTest('MACHINE:unterminated_bracket_fails',
|
|
6308
|
+
runTest('MACHINE:empty_input', test0087_Machine_emptyInput);
|
|
6309
|
+
runTest('MACHINE:whitespace_only', test0088_Machine_whitespaceOnly);
|
|
6310
|
+
runTest('MACHINE:header_only_no_wirings', test0089_Machine_headerOnlyNoWirings);
|
|
6311
|
+
runTest('MACHINE:duplicate_alias', test0090_Machine_duplicateAlias);
|
|
6312
|
+
runTest('MACHINE:simple_linear_chain', test0094_Machine_simpleLinearChain);
|
|
6313
|
+
runTest('MACHINE:two_step_chain', test0095_Machine_twoStepChain);
|
|
6314
|
+
runTest('MACHINE:fan_out', test0096_Machine_fanOut);
|
|
6315
|
+
runTest('MACHINE:fan_in_secondary_assigned_by_prior_wiring', test0097_Machine_fanInSecondaryAssignedByPriorWiring);
|
|
6316
|
+
runTest('MACHINE:fan_in_secondary_unassigned_gets_wildcard', test0098_Machine_fanInSecondaryUnassignedGetsWildcard);
|
|
6317
|
+
runTest('MACHINE:loop_edge', test0111_Machine_loopEdge);
|
|
6318
|
+
runTest('MACHINE:undefined_alias_fails', test0112_Machine_undefinedAliasFails);
|
|
6319
|
+
runTest('MACHINE:node_alias_collision', test0113_Machine_nodeAliasCollision);
|
|
6320
|
+
runTest('MACHINE:conflicting_media_types_fail', test0114_Machine_conflictingMediaTypesFail);
|
|
6321
|
+
runTest('MACHINE:multiline_format', test0117_Machine_multilineFormat);
|
|
6322
|
+
runTest('MACHINE:different_aliases_same_graph', test0118_Machine_differentAliasesSameGraph);
|
|
6323
|
+
runTest('MACHINE:malformed_input_fails', test0119_Machine_malformedInputFails);
|
|
6324
|
+
runTest('MACHINE:unterminated_bracket_fails', test0120_Machine_unterminatedBracketFails);
|
|
6199
6325
|
|
|
6200
6326
|
// machine module: line-based mode tests
|
|
6201
6327
|
console.log('\n--- machine/parser.rs (line-based) ---');
|
|
6202
|
-
runTest('MACHINE:line_based_simple_chain',
|
|
6203
|
-
runTest('MACHINE:line_based_two_step_chain',
|
|
6204
|
-
runTest('MACHINE:line_based_loop',
|
|
6205
|
-
runTest('MACHINE:line_based_fan_in',
|
|
6206
|
-
runTest('MACHINE:mixed_bracketed_and_line_based',
|
|
6207
|
-
runTest('MACHINE:line_based_equivalent_to_bracketed',
|
|
6208
|
-
runTest('MACHINE:line_based_format_serialization',
|
|
6209
|
-
runTest('MACHINE:line_based_and_bracketed_parse_same_graph',
|
|
6328
|
+
runTest('MACHINE:line_based_simple_chain', test0121_Machine_lineBasedSimpleChain);
|
|
6329
|
+
runTest('MACHINE:line_based_two_step_chain', test0122_Machine_lineBasedTwoStepChain);
|
|
6330
|
+
runTest('MACHINE:line_based_loop', test0123_Machine_lineBasedLoop);
|
|
6331
|
+
runTest('MACHINE:line_based_fan_in', test0124_Machine_lineBasedFanIn);
|
|
6332
|
+
runTest('MACHINE:mixed_bracketed_and_line_based', test0125_Machine_mixedBracketedAndLineBased);
|
|
6333
|
+
runTest('MACHINE:line_based_equivalent_to_bracketed', test0126_Machine_lineBasedEquivalentToBracketed);
|
|
6334
|
+
runTest('MACHINE:line_based_format_serialization', test0127_Machine_lineBasedFormatSerialization);
|
|
6335
|
+
runTest('MACHINE:line_based_and_bracketed_parse_same_graph', test0128_Machine_lineBasedAndBracketedParseSameGraph);
|
|
6210
6336
|
|
|
6211
6337
|
// machine module: graph tests (mirrors graph.rs)
|
|
6212
6338
|
console.log('\n--- machine/graph.rs ---');
|
|
6213
|
-
runTest('MACHINE:edge_equivalence_same_urns',
|
|
6214
|
-
runTest('MACHINE:edge_equivalence_different_cap_urns',
|
|
6215
|
-
runTest('MACHINE:edge_equivalence_different_targets',
|
|
6216
|
-
runTest('MACHINE:edge_equivalence_different_loop_flag',
|
|
6217
|
-
runTest('MACHINE:edge_equivalence_source_order_independent',
|
|
6218
|
-
runTest('MACHINE:edge_equivalence_different_source_count',
|
|
6219
|
-
runTest('MACHINE:graph_equivalence_same_edges',
|
|
6220
|
-
runTest('MACHINE:graph_equivalence_reordered_edges',
|
|
6221
|
-
runTest('MACHINE:graph_not_equivalent_different_edge_count',
|
|
6222
|
-
runTest('MACHINE:graph_not_equivalent_different_cap',
|
|
6223
|
-
runTest('MACHINE:graph_empty',
|
|
6224
|
-
runTest('MACHINE:graph_empty_equivalence',
|
|
6225
|
-
runTest('MACHINE:root_sources_linear_chain',
|
|
6226
|
-
runTest('MACHINE:leaf_targets_linear_chain',
|
|
6227
|
-
runTest('MACHINE:root_sources_fan_in',
|
|
6228
|
-
runTest('MACHINE:display_edge',
|
|
6229
|
-
runTest('MACHINE:display_graph',
|
|
6339
|
+
runTest('MACHINE:edge_equivalence_same_urns', test0129_Machine_edgeEquivalenceSameUrns);
|
|
6340
|
+
runTest('MACHINE:edge_equivalence_different_cap_urns', test0130_Machine_edgeEquivalenceDifferentCapUrns);
|
|
6341
|
+
runTest('MACHINE:edge_equivalence_different_targets', test0131_Machine_edgeEquivalenceDifferentTargets);
|
|
6342
|
+
runTest('MACHINE:edge_equivalence_different_loop_flag', test0132_Machine_edgeEquivalenceDifferentLoopFlag);
|
|
6343
|
+
runTest('MACHINE:edge_equivalence_source_order_independent', test0133_Machine_edgeEquivalenceSourceOrderIndependent);
|
|
6344
|
+
runTest('MACHINE:edge_equivalence_different_source_count', test0134_Machine_edgeEquivalenceDifferentSourceCount);
|
|
6345
|
+
runTest('MACHINE:graph_equivalence_same_edges', test0135_Machine_graphEquivalenceSameEdges);
|
|
6346
|
+
runTest('MACHINE:graph_equivalence_reordered_edges', test0136_Machine_graphEquivalenceReorderedEdges);
|
|
6347
|
+
runTest('MACHINE:graph_not_equivalent_different_edge_count', test0137_Machine_graphNotEquivalentDifferentEdgeCount);
|
|
6348
|
+
runTest('MACHINE:graph_not_equivalent_different_cap', test0138_Machine_graphNotEquivalentDifferentCap);
|
|
6349
|
+
runTest('MACHINE:graph_empty', test0139_Machine_graphEmpty);
|
|
6350
|
+
runTest('MACHINE:graph_empty_equivalence', test0140_Machine_graphEmptyEquivalence);
|
|
6351
|
+
runTest('MACHINE:root_sources_linear_chain', test0141_Machine_rootSourcesLinearChain);
|
|
6352
|
+
runTest('MACHINE:leaf_targets_linear_chain', test0142_Machine_leafTargetsLinearChain);
|
|
6353
|
+
runTest('MACHINE:root_sources_fan_in', test0143_Machine_rootSourcesFanIn);
|
|
6354
|
+
runTest('MACHINE:display_edge', test0144_Machine_displayEdge);
|
|
6355
|
+
runTest('MACHINE:display_graph', test0145_Machine_displayGraph);
|
|
6230
6356
|
|
|
6231
6357
|
// machine module: serializer tests (mirrors serializer.rs)
|
|
6232
6358
|
console.log('\n--- machine/serializer.rs ---');
|
|
6233
|
-
runTest('MACHINE:serialize_single_edge',
|
|
6234
|
-
runTest('MACHINE:serialize_two_edge_chain',
|
|
6235
|
-
runTest('MACHINE:serialize_empty_graph',
|
|
6236
|
-
runTest('MACHINE:roundtrip_single_edge',
|
|
6237
|
-
runTest('MACHINE:roundtrip_two_edge_chain',
|
|
6238
|
-
runTest('MACHINE:roundtrip_fan_out',
|
|
6239
|
-
runTest('MACHINE:roundtrip_loop_edge',
|
|
6240
|
-
runTest('MACHINE:serialization_is_deterministic',
|
|
6241
|
-
runTest('MACHINE:reordered_edges_produce_same_notation',
|
|
6242
|
-
runTest('MACHINE:multiline_serialize_format',
|
|
6243
|
-
runTest('MACHINE:alias_from_op_tag',
|
|
6244
|
-
runTest('MACHINE:alias_fallback_without_op_tag',
|
|
6245
|
-
runTest('MACHINE:duplicate_op_tags_disambiguated',
|
|
6359
|
+
runTest('MACHINE:serialize_single_edge', test0146_Machine_serializeSingleEdge);
|
|
6360
|
+
runTest('MACHINE:serialize_two_edge_chain', test0147_Machine_serializeTwoEdgeChain);
|
|
6361
|
+
runTest('MACHINE:serialize_empty_graph', test0148_Machine_serializeEmptyGraph);
|
|
6362
|
+
runTest('MACHINE:roundtrip_single_edge', test0149_Machine_roundtripSingleEdge);
|
|
6363
|
+
runTest('MACHINE:roundtrip_two_edge_chain', test0151_Machine_roundtripTwoEdgeChain);
|
|
6364
|
+
runTest('MACHINE:roundtrip_fan_out', test0152_Machine_roundtripFanOut);
|
|
6365
|
+
runTest('MACHINE:roundtrip_loop_edge', test0153_Machine_roundtripLoopEdge);
|
|
6366
|
+
runTest('MACHINE:serialization_is_deterministic', test0154_Machine_serializationIsDeterministic);
|
|
6367
|
+
runTest('MACHINE:reordered_edges_produce_same_notation', test0155_Machine_reorderedEdgesProduceSameNotation);
|
|
6368
|
+
runTest('MACHINE:multiline_serialize_format', test0160_Machine_multilineSerializeFormat);
|
|
6369
|
+
runTest('MACHINE:alias_from_op_tag', test0161_Machine_aliasFromOpTag);
|
|
6370
|
+
runTest('MACHINE:alias_fallback_without_op_tag', test0162_Machine_aliasFallbackWithoutOpTag);
|
|
6371
|
+
runTest('MACHINE:duplicate_op_tags_disambiguated', test0163_Machine_duplicateOpTagsDisambiguated);
|
|
6246
6372
|
|
|
6247
6373
|
// machine module: builder tests
|
|
6248
6374
|
console.log('\n--- machine/builder ---');
|
|
6249
|
-
runTest('MACHINE:builder_single_edge',
|
|
6250
|
-
runTest('MACHINE:builder_with_loop',
|
|
6251
|
-
runTest('MACHINE:builder_chaining',
|
|
6252
|
-
runTest('MACHINE:builder_equivalent_to_parsed',
|
|
6253
|
-
runTest('MACHINE:builder_round_trip',
|
|
6375
|
+
runTest('MACHINE:builder_single_edge', test0164_Machine_builderSingleEdge);
|
|
6376
|
+
runTest('MACHINE:builder_with_loop', test0165_Machine_builderWithLoop);
|
|
6377
|
+
runTest('MACHINE:builder_chaining', test0166_Machine_builderChaining);
|
|
6378
|
+
runTest('MACHINE:builder_equivalent_to_parsed', test0167_Machine_builderEquivalentToParsed);
|
|
6379
|
+
runTest('MACHINE:builder_round_trip', test0168_Machine_builderRoundTrip);
|
|
6254
6380
|
|
|
6255
6381
|
// machine module: CapUrn.isEquivalent/isComparable
|
|
6256
6382
|
console.log('\n--- machine/urn_predicates ---');
|
|
6257
|
-
runTest('MACHINE:cap_urn_is_equivalent',
|
|
6258
|
-
runTest('MACHINE:cap_urn_is_comparable',
|
|
6259
|
-
runTest('MACHINE:cap_urn_in_media_urn',
|
|
6260
|
-
runTest('MACHINE:cap_urn_out_media_urn',
|
|
6261
|
-
runTest('MACHINE:media_urn_is_equivalent',
|
|
6262
|
-
runTest('MACHINE:media_urn_is_comparable',
|
|
6383
|
+
runTest('MACHINE:cap_urn_is_equivalent', test0169_Machine_capUrnIsEquivalent);
|
|
6384
|
+
runTest('MACHINE:cap_urn_is_comparable', test0170_Machine_capUrnIsComparable);
|
|
6385
|
+
runTest('MACHINE:cap_urn_in_media_urn', test0171_Machine_capUrnInMediaUrn);
|
|
6386
|
+
runTest('MACHINE:cap_urn_out_media_urn', test0172_Machine_capUrnOutMediaUrn);
|
|
6387
|
+
runTest('MACHINE:media_urn_is_equivalent', test0173_Machine_mediaUrnIsEquivalent);
|
|
6388
|
+
runTest('MACHINE:media_urn_is_comparable', test0174_Machine_mediaUrnIsComparable);
|
|
6263
6389
|
|
|
6264
6390
|
// Phase 0A: Position tracking
|
|
6265
6391
|
console.log('\n--- machine/position_tracking ---');
|
|
6266
|
-
runTest('MACHINE:parseMachineWithAST_headerLocation',
|
|
6267
|
-
runTest('MACHINE:parseMachineWithAST_wiringLocation',
|
|
6268
|
-
runTest('MACHINE:parseMachineWithAST_multilinePositions',
|
|
6269
|
-
runTest('MACHINE:parseMachineWithAST_fanInSourceLocations',
|
|
6270
|
-
runTest('MACHINE:parseMachineWithAST_aliasMap',
|
|
6271
|
-
runTest('MACHINE:parseMachineWithAST_nodeMedia',
|
|
6272
|
-
runTest('MACHINE:errorLocation_parseError',
|
|
6273
|
-
runTest('MACHINE:errorLocation_duplicateAlias',
|
|
6274
|
-
runTest('MACHINE:errorLocation_undefinedAlias',
|
|
6392
|
+
runTest('MACHINE:parseMachineWithAST_headerLocation', test0175_Machine_parseMachineWithAST_headerLocation);
|
|
6393
|
+
runTest('MACHINE:parseMachineWithAST_wiringLocation', test0176_Machine_parseMachineWithAST_wiringLocation);
|
|
6394
|
+
runTest('MACHINE:parseMachineWithAST_multilinePositions', test0177_Machine_parseMachineWithAST_multilinePositions);
|
|
6395
|
+
runTest('MACHINE:parseMachineWithAST_fanInSourceLocations', test0178_Machine_parseMachineWithAST_fanInSourceLocations);
|
|
6396
|
+
runTest('MACHINE:parseMachineWithAST_aliasMap', test0179_Machine_parseMachineWithAST_aliasMap);
|
|
6397
|
+
runTest('MACHINE:parseMachineWithAST_nodeMedia', test0180_Machine_parseMachineWithAST_nodeMedia);
|
|
6398
|
+
runTest('MACHINE:errorLocation_parseError', test0181_Machine_errorLocation_parseError);
|
|
6399
|
+
runTest('MACHINE:errorLocation_duplicateAlias', test0182_Machine_errorLocation_duplicateAlias);
|
|
6400
|
+
runTest('MACHINE:errorLocation_undefinedAlias', test0183_Machine_errorLocation_undefinedAlias);
|
|
6275
6401
|
|
|
6276
6402
|
// Phase 0C: Machine.toMermaid()
|
|
6277
6403
|
console.log('\n--- machine/mermaid ---');
|
|
6278
|
-
runTest('MACHINE:toMermaid_linearChain',
|
|
6279
|
-
runTest('MACHINE:toMermaid_loopEdge',
|
|
6280
|
-
runTest('MACHINE:toMermaid_emptyGraph',
|
|
6281
|
-
runTest('MACHINE:toMermaid_fanIn',
|
|
6282
|
-
runTest('MACHINE:toMermaid_fanOut',
|
|
6404
|
+
runTest('MACHINE:toMermaid_linearChain', test0184_Machine_toMermaid_linearChain);
|
|
6405
|
+
runTest('MACHINE:toMermaid_loopEdge', test0185_Machine_toMermaid_loopEdge);
|
|
6406
|
+
runTest('MACHINE:toMermaid_emptyGraph', test0186_Machine_toMermaid_emptyGraph);
|
|
6407
|
+
runTest('MACHINE:toMermaid_fanIn', test0187_Machine_toMermaid_fanIn);
|
|
6408
|
+
runTest('MACHINE:toMermaid_fanOut', test0188_Machine_toMermaid_fanOut);
|
|
6283
6409
|
|
|
6284
6410
|
// Phase 0B: FabricRegistryClient
|
|
6285
6411
|
console.log('\n--- registry/client ---');
|
|
6286
|
-
runTest('REGISTRY: capRegistryEntry_construction',
|
|
6287
|
-
runTest('REGISTRY: mediaRegistryEntry_construction',
|
|
6288
|
-
runTest('REGISTRY: capRegistryClient_construction',
|
|
6289
|
-
runTest('REGISTRY: capRegistryEntry_defaults',
|
|
6412
|
+
runTest('REGISTRY: capRegistryEntry_construction', test0189_Machine_capRegistryEntry_construction);
|
|
6413
|
+
runTest('REGISTRY: mediaRegistryEntry_construction', test0190_Machine_mediaRegistryEntry_construction);
|
|
6414
|
+
runTest('REGISTRY: capRegistryClient_construction', test0191_Machine_capRegistryClient_construction);
|
|
6415
|
+
runTest('REGISTRY: capRegistryEntry_defaults', test0192_Machine_capRegistryEntry_defaults);
|
|
6290
6416
|
|
|
6291
6417
|
// cap-fab-renderer pure helpers (no DOM dependency)
|
|
6292
6418
|
console.log('\n--- cap-fab-renderer helpers ---');
|
|
6293
|
-
runTest('RENDERER: cardinalityLabel_allFourCases',
|
|
6294
|
-
runTest('RENDERER: cardinalityLabel_usesUnicodeArrow',
|
|
6295
|
-
runTest('RENDERER: cardinalityFromCap_findsStdinArg',
|
|
6296
|
-
runTest('RENDERER: cardinalityFromCap_scalarDefaults',
|
|
6297
|
-
runTest('RENDERER: cardinalityFromCap_outputOnlySequence',
|
|
6298
|
-
runTest('RENDERER: cardinalityFromCap_rejectsStringBool',
|
|
6299
|
-
runTest('RENDERER: cardinalityFromCap_throwsOnNonObject',
|
|
6300
|
-
runTest('RENDERER: canonicalMediaUrn_normalizesTagOrder',
|
|
6301
|
-
runTest('RENDERER: canonicalMediaUrn_preservesValueTags',
|
|
6302
|
-
runTest('RENDERER: canonicalMediaUrn_rejectsCapUrn',
|
|
6303
|
-
runTest('RENDERER: mediaNodeLabel_rejectsUrnDerived',
|
|
6304
|
-
runTest('RENDERER: buildBrowse_rejectsMissingMediaTitles',
|
|
6419
|
+
runTest('RENDERER: cardinalityLabel_allFourCases', test0193_Renderer_cardinalityLabel_allFourCases);
|
|
6420
|
+
runTest('RENDERER: cardinalityLabel_usesUnicodeArrow', test0194_Renderer_cardinalityLabel_usesUnicodeArrow);
|
|
6421
|
+
runTest('RENDERER: cardinalityFromCap_findsStdinArg', test0195_Renderer_cardinalityFromCap_findsStdinArgNotFirstArg);
|
|
6422
|
+
runTest('RENDERER: cardinalityFromCap_scalarDefaults', test0196_Renderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing);
|
|
6423
|
+
runTest('RENDERER: cardinalityFromCap_outputOnlySequence', test0197_Renderer_cardinalityFromCap_outputOnlySequence);
|
|
6424
|
+
runTest('RENDERER: cardinalityFromCap_rejectsStringBool', test0198_Renderer_cardinalityFromCap_rejectsStringIsSequence);
|
|
6425
|
+
runTest('RENDERER: cardinalityFromCap_throwsOnNonObject', test0199_Renderer_cardinalityFromCap_throwsOnNonObject);
|
|
6426
|
+
runTest('RENDERER: canonicalMediaUrn_normalizesTagOrder', test0200_Renderer_canonicalMediaUrn_normalizesTagOrder);
|
|
6427
|
+
runTest('RENDERER: canonicalMediaUrn_preservesValueTags', test0201_Renderer_canonicalMediaUrn_preservesValueTags);
|
|
6428
|
+
runTest('RENDERER: canonicalMediaUrn_rejectsCapUrn', test0202_Renderer_canonicalMediaUrn_rejectsCapUrn);
|
|
6429
|
+
runTest('RENDERER: mediaNodeLabel_rejectsUrnDerived', test0203_Renderer_mediaNodeLabel_rejectsUrnDerivedLabels);
|
|
6430
|
+
runTest('RENDERER: buildBrowse_rejectsMissingMediaTitles', test0204_Renderer_buildBrowseGraphData_rejectsMissingMediaTitles);
|
|
6305
6431
|
|
|
6306
6432
|
console.log('\n--- cap-fab-renderer strand builder ---');
|
|
6307
|
-
runTest('RENDERER: validateStrandStep_unknownVariant',
|
|
6308
|
-
runTest('RENDERER: validateStrandStep_booleanIsSequence',
|
|
6309
|
-
runTest('RENDERER: classifyStrandCapSteps_simple',
|
|
6310
|
-
runTest('RENDERER: classifyStrandCapSteps_nested',
|
|
6311
|
-
runTest('RENDERER: buildStrand_singleCapPlain',
|
|
6312
|
-
runTest('RENDERER: buildStrand_sequenceShowsCardinality',
|
|
6313
|
-
runTest('RENDERER: buildStrand_foreachCollectSpan',
|
|
6314
|
-
runTest('RENDERER: buildStrand_standaloneCollect',
|
|
6315
|
-
runTest('RENDERER: buildStrand_unclosedForEachBody',
|
|
6316
|
-
runTest('RENDERER: buildStrand_nestedForEachThrows',
|
|
6317
|
-
runTest('RENDERER: collapseStrand_singleCapBody',
|
|
6318
|
-
runTest('RENDERER: collapseStrand_unclosedForEachBody',
|
|
6319
|
-
runTest('RENDERER: collapseStrand_standaloneCollect',
|
|
6320
|
-
runTest('RENDERER: collapseStrand_seqCapBeforeForeach',
|
|
6321
|
-
runTest('RENDERER: collapseStrand_plainCapMergesOutput',
|
|
6322
|
-
runTest('RENDERER: collapseStrand_plainCapDistinctTarget',
|
|
6323
|
-
runTest('RENDERER: validateStrand_missingSourceMediaUrn',
|
|
6433
|
+
runTest('RENDERER: validateStrandStep_unknownVariant', test0205_Renderer_validateStrandStep_rejectsUnknownVariant);
|
|
6434
|
+
runTest('RENDERER: validateStrandStep_booleanIsSequence', test0206_Renderer_validateStrandStep_requiresBooleanIsSequence);
|
|
6435
|
+
runTest('RENDERER: classifyStrandCapSteps_simple', test0207_Renderer_classifyStrandCapSteps_capFlags);
|
|
6436
|
+
runTest('RENDERER: classifyStrandCapSteps_nested', test0208_Renderer_classifyStrandCapSteps_nestedForks);
|
|
6437
|
+
runTest('RENDERER: buildStrand_singleCapPlain', test0209_Renderer_buildStrandGraphData_singleCapPlain);
|
|
6438
|
+
runTest('RENDERER: buildStrand_sequenceShowsCardinality', test0210_Renderer_buildStrandGraphData_sequenceShowsCardinality);
|
|
6439
|
+
runTest('RENDERER: buildStrand_foreachCollectSpan', test0211_Renderer_buildStrandGraphData_foreachCollectSpan);
|
|
6440
|
+
runTest('RENDERER: buildStrand_standaloneCollect', test0212_Renderer_buildStrandGraphData_standaloneCollect);
|
|
6441
|
+
runTest('RENDERER: buildStrand_unclosedForEachBody', test0213_Renderer_buildStrandGraphData_unclosedForEachBody);
|
|
6442
|
+
runTest('RENDERER: buildStrand_nestedForEachThrows', test0214_Renderer_buildStrandGraphData_nestedForEachThrows);
|
|
6443
|
+
runTest('RENDERER: collapseStrand_singleCapBody', test0215_Renderer_collapseStrand_singleCapBodyKeepsCapOwnLabel);
|
|
6444
|
+
runTest('RENDERER: collapseStrand_unclosedForEachBody', test0216_Renderer_collapseStrand_unclosedForEachBodyCollapses);
|
|
6445
|
+
runTest('RENDERER: collapseStrand_standaloneCollect', test0217_Renderer_collapseStrand_standaloneCollectCollapses);
|
|
6446
|
+
runTest('RENDERER: collapseStrand_seqCapBeforeForeach', test0218_Renderer_collapseStrand_sequenceProducingCapBeforeForeach);
|
|
6447
|
+
runTest('RENDERER: collapseStrand_plainCapMergesOutput', test0219_Renderer_collapseStrand_plainCapMergesTrailingOutput);
|
|
6448
|
+
runTest('RENDERER: collapseStrand_plainCapDistinctTarget', test0220_Renderer_collapseStrand_plainCapDistinctTargetNoMerge);
|
|
6449
|
+
runTest('RENDERER: validateStrand_missingSourceMediaUrn', test0221_Renderer_validateStrandPayload_missingSourceMediaUrn);
|
|
6324
6450
|
|
|
6325
6451
|
console.log('\n--- cap-fab-renderer run builder ---');
|
|
6326
|
-
runTest('RENDERER: validateBodyOutcome_negativeIndex',
|
|
6327
|
-
runTest('RENDERER: buildRun_pagesSuccessesAndFailures',
|
|
6328
|
-
runTest('RENDERER: buildRun_failureWithoutFailedCap',
|
|
6329
|
-
runTest('RENDERER: buildRun_usesIsEquivalentForFailedCap',
|
|
6330
|
-
runTest('RENDERER: buildRun_backboneHasNoForeachNode',
|
|
6331
|
-
runTest('RENDERER: buildRun_allFailedDropsPlaceholder',
|
|
6332
|
-
runTest('RENDERER: buildRun_unclosedForeachNoMerge',
|
|
6333
|
-
runTest('RENDERER: buildRun_closedForeachMerges',
|
|
6452
|
+
runTest('RENDERER: validateBodyOutcome_negativeIndex', test0222_Renderer_validateBodyOutcome_rejectsNegativeIndex);
|
|
6453
|
+
runTest('RENDERER: buildRun_pagesSuccessesAndFailures', test0223_Renderer_buildRunGraphData_pagesSuccessesAndFailures);
|
|
6454
|
+
runTest('RENDERER: buildRun_failureWithoutFailedCap', test0224_Renderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace);
|
|
6455
|
+
runTest('RENDERER: buildRun_usesIsEquivalentForFailedCap', test0225_Renderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap);
|
|
6456
|
+
runTest('RENDERER: buildRun_backboneHasNoForeachNode', test0226_Renderer_buildRunGraphData_backboneHasNoForeachNode);
|
|
6457
|
+
runTest('RENDERER: buildRun_allFailedDropsPlaceholder', test0227_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder);
|
|
6458
|
+
runTest('RENDERER: buildRun_unclosedForeachNoMerge', test0228_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge);
|
|
6459
|
+
runTest('RENDERER: buildRun_closedForeachMerges', test0229_Renderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarget);
|
|
6334
6460
|
|
|
6335
6461
|
console.log('\n--- cap-fab-renderer editor-graph builder ---');
|
|
6336
|
-
runTest('RENDERER: validateEditorGraph_unknownKind',
|
|
6337
|
-
runTest('RENDERER: buildEditorGraph_collapsesCapsIntoEdges',
|
|
6338
|
-
runTest('RENDERER: buildEditorGraph_loopEdgeGetsClass',
|
|
6339
|
-
runTest('RENDERER: buildEditorGraph_cardinalityFromIsSeq',
|
|
6340
|
-
runTest('RENDERER: buildEditorGraph_incompleteCapDropped',
|
|
6341
|
-
runTest('RENDERER: buildEditorGraph_rejectsEdgeMissingSrc',
|
|
6462
|
+
runTest('RENDERER: validateEditorGraph_unknownKind', test0230_Renderer_validateEditorGraphPayload_rejectsUnknownKind);
|
|
6463
|
+
runTest('RENDERER: buildEditorGraph_collapsesCapsIntoEdges', test0231_Renderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges);
|
|
6464
|
+
runTest('RENDERER: buildEditorGraph_loopEdgeGetsClass', test0232_Renderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass);
|
|
6465
|
+
runTest('RENDERER: buildEditorGraph_cardinalityFromIsSeq', test0233_Renderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags);
|
|
6466
|
+
runTest('RENDERER: buildEditorGraph_incompleteCapDropped', test0234_Renderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped);
|
|
6467
|
+
runTest('RENDERER: buildEditorGraph_rejectsEdgeMissingSrc', test0235_Renderer_buildEditorGraphData_rejectsEdgeWithMissingSource);
|
|
6342
6468
|
|
|
6343
6469
|
console.log('\n--- cap-fab-renderer resolved-machine builder ---');
|
|
6344
|
-
runTest('RENDERER: buildResolvedMachine_singleStrandLinear',
|
|
6345
|
-
runTest('RENDERER: buildResolvedMachine_loopGetsLoopClass',
|
|
6346
|
-
runTest('RENDERER: buildResolvedMachine_fanInOneEdgePerSrc',
|
|
6347
|
-
runTest('RENDERER: buildResolvedMachine_multiStrandDisjoint',
|
|
6348
|
-
runTest('RENDERER: buildResolvedMachine_dupNodeIdFails',
|
|
6349
|
-
runTest('RENDERER: validateResolvedMachine_rejectsMissingFields',
|
|
6470
|
+
runTest('RENDERER: buildResolvedMachine_singleStrandLinear', test0236_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain);
|
|
6471
|
+
runTest('RENDERER: buildResolvedMachine_loopGetsLoopClass', test0237_Renderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass);
|
|
6472
|
+
runTest('RENDERER: buildResolvedMachine_fanInOneEdgePerSrc', test0238_Renderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignment);
|
|
6473
|
+
runTest('RENDERER: buildResolvedMachine_multiStrandDisjoint', test0239_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjoint);
|
|
6474
|
+
runTest('RENDERER: buildResolvedMachine_dupNodeIdFails', test0240_Renderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrandsFailsHard);
|
|
6475
|
+
runTest('RENDERER: validateResolvedMachine_rejectsMissingFields', test0241_Renderer_validateResolvedMachinePayload_rejectsMissingFields);
|
|
6350
6476
|
|
|
6351
6477
|
console.log('\n--- CapKind classifier (test1800–test1805) ---');
|
|
6352
6478
|
runTest('TEST1800: kind_identity_requires_effect_none', test1800_kindIdentityOnlyForBareCap);
|