@ricsam/isolate-test-environment 0.1.11 → 0.1.12
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/dist/cjs/index.cjs +458 -3
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/index.mjs +458 -3
- package/dist/mjs/index.mjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -55,6 +55,26 @@ var testEnvironmentCode = `
|
|
|
55
55
|
// Internal State
|
|
56
56
|
// ============================================================
|
|
57
57
|
|
|
58
|
+
// Mock registry and call counter
|
|
59
|
+
let __mockCallOrder = 0;
|
|
60
|
+
const __mockRegistry = [];
|
|
61
|
+
|
|
62
|
+
// Assertion counting state
|
|
63
|
+
let __expectedAssertions = null;
|
|
64
|
+
let __assertionCount = 0;
|
|
65
|
+
let __hasAssertionsFlag = false;
|
|
66
|
+
|
|
67
|
+
function createMockState() {
|
|
68
|
+
return {
|
|
69
|
+
calls: [],
|
|
70
|
+
results: [],
|
|
71
|
+
contexts: [],
|
|
72
|
+
instances: [],
|
|
73
|
+
invocationCallOrder: [],
|
|
74
|
+
lastCall: undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
58
78
|
function createSuite(name, skip = false, only = false) {
|
|
59
79
|
return {
|
|
60
80
|
name,
|
|
@@ -100,6 +120,34 @@ var testEnvironmentCode = `
|
|
|
100
120
|
}
|
|
101
121
|
}
|
|
102
122
|
|
|
123
|
+
// ============================================================
|
|
124
|
+
// Asymmetric Matcher Infrastructure
|
|
125
|
+
// ============================================================
|
|
126
|
+
|
|
127
|
+
const ASYMMETRIC_MATCHER = Symbol('asymmetricMatcher');
|
|
128
|
+
|
|
129
|
+
function isAsymmetricMatcher(obj) {
|
|
130
|
+
return obj && obj[ASYMMETRIC_MATCHER] === true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Deep equality with asymmetric matcher support
|
|
134
|
+
function asymmetricDeepEqual(a, b) {
|
|
135
|
+
if (isAsymmetricMatcher(b)) return b.asymmetricMatch(a);
|
|
136
|
+
if (isAsymmetricMatcher(a)) return a.asymmetricMatch(b);
|
|
137
|
+
if (a === b) return true;
|
|
138
|
+
if (typeof a !== typeof b) return false;
|
|
139
|
+
if (typeof a !== 'object' || a === null || b === null) return false;
|
|
140
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
141
|
+
const keysA = Object.keys(a);
|
|
142
|
+
const keysB = Object.keys(b);
|
|
143
|
+
if (keysA.length !== keysB.length) return false;
|
|
144
|
+
for (const key of keysA) {
|
|
145
|
+
if (!keysB.includes(key)) return false;
|
|
146
|
+
if (!asymmetricDeepEqual(a[key], b[key])) return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
|
|
103
151
|
// ============================================================
|
|
104
152
|
// Deep Equality Helper
|
|
105
153
|
// ============================================================
|
|
@@ -200,6 +248,7 @@ var testEnvironmentCode = `
|
|
|
200
248
|
function expect(actual) {
|
|
201
249
|
function createMatchers(negated = false) {
|
|
202
250
|
const assert = (condition, message, matcherName, expected) => {
|
|
251
|
+
__assertionCount++;
|
|
203
252
|
const pass = negated ? !condition : condition;
|
|
204
253
|
if (!pass) {
|
|
205
254
|
throw new TestError(message, matcherName, expected, actual);
|
|
@@ -220,7 +269,7 @@ var testEnvironmentCode = `
|
|
|
220
269
|
|
|
221
270
|
toEqual(expected) {
|
|
222
271
|
assert(
|
|
223
|
-
|
|
272
|
+
asymmetricDeepEqual(actual, expected),
|
|
224
273
|
negated
|
|
225
274
|
? \`Expected \${formatValue(actual)} not to equal \${formatValue(expected)}\`
|
|
226
275
|
: \`Expected \${formatValue(actual)} to equal \${formatValue(expected)}\`,
|
|
@@ -395,7 +444,7 @@ var testEnvironmentCode = `
|
|
|
395
444
|
toHaveProperty(path, value) {
|
|
396
445
|
const prop = getNestedProperty(actual, path);
|
|
397
446
|
const hasProperty = prop.exists;
|
|
398
|
-
const valueMatches = arguments.length < 2 ||
|
|
447
|
+
const valueMatches = arguments.length < 2 || asymmetricDeepEqual(prop.value, value);
|
|
399
448
|
|
|
400
449
|
assert(
|
|
401
450
|
hasProperty && valueMatches,
|
|
@@ -450,6 +499,163 @@ var testEnvironmentCode = `
|
|
|
450
499
|
expected
|
|
451
500
|
);
|
|
452
501
|
},
|
|
502
|
+
|
|
503
|
+
toBeCloseTo(expected, numDigits = 2) {
|
|
504
|
+
const precision = Math.pow(10, -numDigits) / 2;
|
|
505
|
+
const pass = Math.abs(actual - expected) < precision;
|
|
506
|
+
assert(
|
|
507
|
+
pass,
|
|
508
|
+
negated
|
|
509
|
+
? \`Expected \${formatValue(actual)} not to be close to \${formatValue(expected)} (precision: \${numDigits} digits)\`
|
|
510
|
+
: \`Expected \${formatValue(actual)} to be close to \${formatValue(expected)} (precision: \${numDigits} digits)\`,
|
|
511
|
+
'toBeCloseTo',
|
|
512
|
+
expected
|
|
513
|
+
);
|
|
514
|
+
},
|
|
515
|
+
|
|
516
|
+
toBeNaN() {
|
|
517
|
+
assert(
|
|
518
|
+
Number.isNaN(actual),
|
|
519
|
+
negated
|
|
520
|
+
? \`Expected \${formatValue(actual)} not to be NaN\`
|
|
521
|
+
: \`Expected \${formatValue(actual)} to be NaN\`,
|
|
522
|
+
'toBeNaN',
|
|
523
|
+
NaN
|
|
524
|
+
);
|
|
525
|
+
},
|
|
526
|
+
|
|
527
|
+
toMatchObject(expected) {
|
|
528
|
+
function matchesObject(obj, pattern) {
|
|
529
|
+
if (isAsymmetricMatcher(pattern)) return pattern.asymmetricMatch(obj);
|
|
530
|
+
if (typeof pattern !== 'object' || pattern === null) return obj === pattern;
|
|
531
|
+
if (typeof obj !== 'object' || obj === null) return false;
|
|
532
|
+
for (const key of Object.keys(pattern)) {
|
|
533
|
+
if (!(key in obj)) return false;
|
|
534
|
+
if (!matchesObject(obj[key], pattern[key])) return false;
|
|
535
|
+
}
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
assert(
|
|
539
|
+
matchesObject(actual, expected),
|
|
540
|
+
negated
|
|
541
|
+
? \`Expected \${formatValue(actual)} not to match object \${formatValue(expected)}\`
|
|
542
|
+
: \`Expected \${formatValue(actual)} to match object \${formatValue(expected)}\`,
|
|
543
|
+
'toMatchObject',
|
|
544
|
+
expected
|
|
545
|
+
);
|
|
546
|
+
},
|
|
547
|
+
|
|
548
|
+
toContainEqual(item) {
|
|
549
|
+
const contains = Array.isArray(actual) && actual.some(el => asymmetricDeepEqual(el, item));
|
|
550
|
+
assert(
|
|
551
|
+
contains,
|
|
552
|
+
negated
|
|
553
|
+
? \`Expected array not to contain equal \${formatValue(item)}\`
|
|
554
|
+
: \`Expected array to contain equal \${formatValue(item)}\`,
|
|
555
|
+
'toContainEqual',
|
|
556
|
+
item
|
|
557
|
+
);
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
toBeTypeOf(expectedType) {
|
|
561
|
+
const actualType = typeof actual;
|
|
562
|
+
assert(
|
|
563
|
+
actualType === expectedType,
|
|
564
|
+
negated
|
|
565
|
+
? \`Expected typeof \${formatValue(actual)} not to be "\${expectedType}"\`
|
|
566
|
+
: \`Expected typeof \${formatValue(actual)} to be "\${expectedType}", got "\${actualType}"\`,
|
|
567
|
+
'toBeTypeOf',
|
|
568
|
+
expectedType
|
|
569
|
+
);
|
|
570
|
+
},
|
|
571
|
+
|
|
572
|
+
// Mock matchers
|
|
573
|
+
toHaveBeenCalled() {
|
|
574
|
+
if (!actual.__isMockFunction) throw new Error('toHaveBeenCalled requires a mock function');
|
|
575
|
+
assert(actual.mock.calls.length > 0,
|
|
576
|
+
negated ? \`Expected mock not to have been called\` : \`Expected mock to have been called\`,
|
|
577
|
+
'toHaveBeenCalled', 'called');
|
|
578
|
+
},
|
|
579
|
+
|
|
580
|
+
toHaveBeenCalledTimes(n) {
|
|
581
|
+
if (!actual.__isMockFunction) throw new Error('toHaveBeenCalledTimes requires a mock function');
|
|
582
|
+
assert(actual.mock.calls.length === n,
|
|
583
|
+
negated ? \`Expected mock not to have been called \${n} times\`
|
|
584
|
+
: \`Expected mock to be called \${n} times, got \${actual.mock.calls.length}\`,
|
|
585
|
+
'toHaveBeenCalledTimes', n);
|
|
586
|
+
},
|
|
587
|
+
|
|
588
|
+
toHaveBeenCalledWith(...expectedArgs) {
|
|
589
|
+
if (!actual.__isMockFunction) throw new Error('toHaveBeenCalledWith requires a mock function');
|
|
590
|
+
const match = actual.mock.calls.some(args => asymmetricDeepEqual(args, expectedArgs));
|
|
591
|
+
assert(match,
|
|
592
|
+
negated ? \`Expected mock not to have been called with \${formatValue(expectedArgs)}\`
|
|
593
|
+
: \`Expected mock to have been called with \${formatValue(expectedArgs)}\`,
|
|
594
|
+
'toHaveBeenCalledWith', expectedArgs);
|
|
595
|
+
},
|
|
596
|
+
|
|
597
|
+
toHaveBeenLastCalledWith(...expectedArgs) {
|
|
598
|
+
if (!actual.__isMockFunction) throw new Error('toHaveBeenLastCalledWith requires a mock function');
|
|
599
|
+
assert(actual.mock.lastCall && asymmetricDeepEqual(actual.mock.lastCall, expectedArgs),
|
|
600
|
+
negated ? \`Expected last call not to be \${formatValue(expectedArgs)}\`
|
|
601
|
+
: \`Expected last call to be \${formatValue(expectedArgs)}, got \${formatValue(actual.mock.lastCall)}\`,
|
|
602
|
+
'toHaveBeenLastCalledWith', expectedArgs);
|
|
603
|
+
},
|
|
604
|
+
|
|
605
|
+
toHaveBeenNthCalledWith(n, ...expectedArgs) {
|
|
606
|
+
if (!actual.__isMockFunction) throw new Error('toHaveBeenNthCalledWith requires a mock function');
|
|
607
|
+
const nthCall = actual.mock.calls[n - 1];
|
|
608
|
+
assert(nthCall && asymmetricDeepEqual(nthCall, expectedArgs),
|
|
609
|
+
negated ? \`Expected call \${n} not to be \${formatValue(expectedArgs)}\`
|
|
610
|
+
: \`Expected call \${n} to be \${formatValue(expectedArgs)}, got \${formatValue(nthCall)}\`,
|
|
611
|
+
'toHaveBeenNthCalledWith', expectedArgs);
|
|
612
|
+
},
|
|
613
|
+
|
|
614
|
+
toHaveReturned() {
|
|
615
|
+
if (!actual.__isMockFunction) throw new Error('toHaveReturned requires a mock function');
|
|
616
|
+
const hasReturned = actual.mock.results.some(r => r.type === 'return');
|
|
617
|
+
assert(hasReturned,
|
|
618
|
+
negated ? \`Expected mock not to have returned\` : \`Expected mock to have returned\`,
|
|
619
|
+
'toHaveReturned', 'returned');
|
|
620
|
+
},
|
|
621
|
+
|
|
622
|
+
toHaveReturnedWith(value) {
|
|
623
|
+
if (!actual.__isMockFunction) throw new Error('toHaveReturnedWith requires a mock function');
|
|
624
|
+
const match = actual.mock.results.some(r => r.type === 'return' && asymmetricDeepEqual(r.value, value));
|
|
625
|
+
assert(match,
|
|
626
|
+
negated ? \`Expected mock not to have returned \${formatValue(value)}\`
|
|
627
|
+
: \`Expected mock to have returned \${formatValue(value)}\`,
|
|
628
|
+
'toHaveReturnedWith', value);
|
|
629
|
+
},
|
|
630
|
+
|
|
631
|
+
toHaveLastReturnedWith(value) {
|
|
632
|
+
if (!actual.__isMockFunction) throw new Error('toHaveLastReturnedWith requires a mock function');
|
|
633
|
+
const returns = actual.mock.results.filter(r => r.type === 'return');
|
|
634
|
+
const last = returns[returns.length - 1];
|
|
635
|
+
assert(last && asymmetricDeepEqual(last.value, value),
|
|
636
|
+
negated ? \`Expected last return not to be \${formatValue(value)}\`
|
|
637
|
+
: \`Expected last return to be \${formatValue(value)}, got \${formatValue(last?.value)}\`,
|
|
638
|
+
'toHaveLastReturnedWith', value);
|
|
639
|
+
},
|
|
640
|
+
|
|
641
|
+
toHaveReturnedTimes(n) {
|
|
642
|
+
if (!actual.__isMockFunction) throw new Error('toHaveReturnedTimes requires a mock function');
|
|
643
|
+
const returnCount = actual.mock.results.filter(r => r.type === 'return').length;
|
|
644
|
+
assert(returnCount === n,
|
|
645
|
+
negated ? \`Expected mock not to have returned \${n} times\`
|
|
646
|
+
: \`Expected mock to have returned \${n} times, got \${returnCount}\`,
|
|
647
|
+
'toHaveReturnedTimes', n);
|
|
648
|
+
},
|
|
649
|
+
|
|
650
|
+
toHaveNthReturnedWith(n, value) {
|
|
651
|
+
if (!actual.__isMockFunction) throw new Error('toHaveNthReturnedWith requires a mock function');
|
|
652
|
+
const returns = actual.mock.results.filter(r => r.type === 'return');
|
|
653
|
+
const nthReturn = returns[n - 1];
|
|
654
|
+
assert(nthReturn && asymmetricDeepEqual(nthReturn.value, value),
|
|
655
|
+
negated ? \`Expected return \${n} not to be \${formatValue(value)}\`
|
|
656
|
+
: \`Expected return \${n} to be \${formatValue(value)}, got \${formatValue(nthReturn?.value)}\`,
|
|
657
|
+
'toHaveNthReturnedWith', value);
|
|
658
|
+
},
|
|
453
659
|
};
|
|
454
660
|
|
|
455
661
|
return matchers;
|
|
@@ -458,9 +664,129 @@ var testEnvironmentCode = `
|
|
|
458
664
|
const matchers = createMatchers(false);
|
|
459
665
|
matchers.not = createMatchers(true);
|
|
460
666
|
|
|
667
|
+
// Promise matchers using Proxy
|
|
668
|
+
matchers.resolves = new Proxy({}, {
|
|
669
|
+
get(_, matcherName) {
|
|
670
|
+
if (matcherName === 'not') {
|
|
671
|
+
return new Proxy({}, {
|
|
672
|
+
get(_, negatedMatcherName) {
|
|
673
|
+
return async (...args) => {
|
|
674
|
+
const result = await actual;
|
|
675
|
+
return expect(result).not[negatedMatcherName](...args);
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
return async (...args) => {
|
|
681
|
+
const result = await actual;
|
|
682
|
+
return expect(result)[matcherName](...args);
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
matchers.rejects = new Proxy({}, {
|
|
688
|
+
get(_, matcherName) {
|
|
689
|
+
if (matcherName === 'not') {
|
|
690
|
+
return new Proxy({}, {
|
|
691
|
+
get(_, negatedMatcherName) {
|
|
692
|
+
return async (...args) => {
|
|
693
|
+
let error;
|
|
694
|
+
try {
|
|
695
|
+
await actual;
|
|
696
|
+
throw new TestError('Expected promise to reject', 'rejects', 'rejection', undefined);
|
|
697
|
+
} catch (e) {
|
|
698
|
+
if (e instanceof TestError && e.matcherName === 'rejects') throw e;
|
|
699
|
+
error = e;
|
|
700
|
+
}
|
|
701
|
+
return expect(error).not[negatedMatcherName](...args);
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
return async (...args) => {
|
|
707
|
+
let error;
|
|
708
|
+
try {
|
|
709
|
+
await actual;
|
|
710
|
+
throw new TestError('Expected promise to reject', 'rejects', 'rejection', undefined);
|
|
711
|
+
} catch (e) {
|
|
712
|
+
if (e instanceof TestError && e.matcherName === 'rejects') throw e;
|
|
713
|
+
error = e;
|
|
714
|
+
}
|
|
715
|
+
return expect(error)[matcherName](...args);
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
|
|
461
720
|
return matchers;
|
|
462
721
|
}
|
|
463
722
|
|
|
723
|
+
// Asymmetric matcher implementations
|
|
724
|
+
expect.anything = () => ({
|
|
725
|
+
[ASYMMETRIC_MATCHER]: true,
|
|
726
|
+
asymmetricMatch: (other) => other !== null && other !== undefined,
|
|
727
|
+
toString: () => 'anything()',
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
expect.any = (constructor) => ({
|
|
731
|
+
[ASYMMETRIC_MATCHER]: true,
|
|
732
|
+
asymmetricMatch: (other) => {
|
|
733
|
+
if (constructor === String) return typeof other === 'string' || other instanceof String;
|
|
734
|
+
if (constructor === Number) return typeof other === 'number' || other instanceof Number;
|
|
735
|
+
if (constructor === Boolean) return typeof other === 'boolean' || other instanceof Boolean;
|
|
736
|
+
if (constructor === Function) return typeof other === 'function';
|
|
737
|
+
if (constructor === Object) return typeof other === 'object' && other !== null;
|
|
738
|
+
if (constructor === Array) return Array.isArray(other);
|
|
739
|
+
return other instanceof constructor;
|
|
740
|
+
},
|
|
741
|
+
toString: () => \`any(\${constructor.name || constructor})\`,
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
expect.stringContaining = (str) => ({
|
|
745
|
+
[ASYMMETRIC_MATCHER]: true,
|
|
746
|
+
asymmetricMatch: (other) => typeof other === 'string' && other.includes(str),
|
|
747
|
+
toString: () => \`stringContaining("\${str}")\`,
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
expect.stringMatching = (pattern) => ({
|
|
751
|
+
[ASYMMETRIC_MATCHER]: true,
|
|
752
|
+
asymmetricMatch: (other) => {
|
|
753
|
+
if (typeof other !== 'string') return false;
|
|
754
|
+
return typeof pattern === 'string' ? other.includes(pattern) : pattern.test(other);
|
|
755
|
+
},
|
|
756
|
+
toString: () => \`stringMatching(\${pattern})\`,
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
expect.arrayContaining = (expected) => ({
|
|
760
|
+
[ASYMMETRIC_MATCHER]: true,
|
|
761
|
+
asymmetricMatch: (other) => {
|
|
762
|
+
if (!Array.isArray(other)) return false;
|
|
763
|
+
return expected.every(exp => other.some(item => asymmetricDeepEqual(item, exp)));
|
|
764
|
+
},
|
|
765
|
+
toString: () => \`arrayContaining(\${formatValue(expected)})\`,
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
expect.objectContaining = (expected) => ({
|
|
769
|
+
[ASYMMETRIC_MATCHER]: true,
|
|
770
|
+
asymmetricMatch: (other) => {
|
|
771
|
+
if (typeof other !== 'object' || other === null) return false;
|
|
772
|
+
for (const key of Object.keys(expected)) {
|
|
773
|
+
if (!(key in other)) return false;
|
|
774
|
+
if (!asymmetricDeepEqual(other[key], expected[key])) return false;
|
|
775
|
+
}
|
|
776
|
+
return true;
|
|
777
|
+
},
|
|
778
|
+
toString: () => \`objectContaining(\${formatValue(expected)})\`,
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
// Assertion counting
|
|
782
|
+
expect.assertions = (n) => {
|
|
783
|
+
__expectedAssertions = n;
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
expect.hasAssertions = () => {
|
|
787
|
+
__hasAssertionsFlag = true;
|
|
788
|
+
};
|
|
789
|
+
|
|
464
790
|
// ============================================================
|
|
465
791
|
// Test Registration Functions
|
|
466
792
|
// ============================================================
|
|
@@ -569,6 +895,113 @@ var testEnvironmentCode = `
|
|
|
569
895
|
currentSuite.afterAll.push(fn);
|
|
570
896
|
}
|
|
571
897
|
|
|
898
|
+
// ============================================================
|
|
899
|
+
// Mock Implementation
|
|
900
|
+
// ============================================================
|
|
901
|
+
|
|
902
|
+
const mock = {
|
|
903
|
+
fn(implementation) {
|
|
904
|
+
const mockState = createMockState();
|
|
905
|
+
let defaultImpl = implementation;
|
|
906
|
+
const onceImpls = [];
|
|
907
|
+
const onceReturns = [];
|
|
908
|
+
let returnVal, resolvedVal, rejectedVal;
|
|
909
|
+
let returnSet = false, resolvedSet = false, rejectedSet = false;
|
|
910
|
+
|
|
911
|
+
function mockFn(...args) {
|
|
912
|
+
mockState.calls.push(args);
|
|
913
|
+
mockState.contexts.push(this);
|
|
914
|
+
mockState.lastCall = args;
|
|
915
|
+
mockState.invocationCallOrder.push(++__mockCallOrder);
|
|
916
|
+
|
|
917
|
+
let result;
|
|
918
|
+
try {
|
|
919
|
+
if (onceImpls.length > 0) {
|
|
920
|
+
result = onceImpls.shift().apply(this, args);
|
|
921
|
+
} else if (onceReturns.length > 0) {
|
|
922
|
+
result = onceReturns.shift();
|
|
923
|
+
} else if (returnSet) {
|
|
924
|
+
result = returnVal;
|
|
925
|
+
} else if (resolvedSet) {
|
|
926
|
+
result = Promise.resolve(resolvedVal);
|
|
927
|
+
} else if (rejectedSet) {
|
|
928
|
+
result = Promise.reject(rejectedVal);
|
|
929
|
+
} else if (defaultImpl) {
|
|
930
|
+
result = defaultImpl.apply(this, args);
|
|
931
|
+
}
|
|
932
|
+
mockState.results.push({ type: 'return', value: result });
|
|
933
|
+
return result;
|
|
934
|
+
} catch (e) {
|
|
935
|
+
mockState.results.push({ type: 'throw', value: e });
|
|
936
|
+
throw e;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
mockFn.__isMockFunction = true;
|
|
941
|
+
mockFn.mock = mockState;
|
|
942
|
+
|
|
943
|
+
// Configuration methods
|
|
944
|
+
mockFn.mockReturnValue = (v) => { returnVal = v; returnSet = true; return mockFn; };
|
|
945
|
+
mockFn.mockReturnValueOnce = (v) => { onceReturns.push(v); return mockFn; };
|
|
946
|
+
mockFn.mockResolvedValue = (v) => { resolvedVal = v; resolvedSet = true; return mockFn; };
|
|
947
|
+
mockFn.mockRejectedValue = (v) => { rejectedVal = v; rejectedSet = true; return mockFn; };
|
|
948
|
+
mockFn.mockImplementation = (fn) => { defaultImpl = fn; return mockFn; };
|
|
949
|
+
mockFn.mockImplementationOnce = (fn) => { onceImpls.push(fn); return mockFn; };
|
|
950
|
+
|
|
951
|
+
// Clearing methods
|
|
952
|
+
mockFn.mockClear = () => {
|
|
953
|
+
mockState.calls = []; mockState.results = []; mockState.contexts = [];
|
|
954
|
+
mockState.instances = []; mockState.invocationCallOrder = []; mockState.lastCall = undefined;
|
|
955
|
+
return mockFn;
|
|
956
|
+
};
|
|
957
|
+
mockFn.mockReset = () => {
|
|
958
|
+
mockFn.mockClear();
|
|
959
|
+
defaultImpl = undefined; returnVal = resolvedVal = rejectedVal = undefined;
|
|
960
|
+
returnSet = resolvedSet = rejectedSet = false;
|
|
961
|
+
onceImpls.length = 0; onceReturns.length = 0;
|
|
962
|
+
return mockFn;
|
|
963
|
+
};
|
|
964
|
+
mockFn.mockRestore = () => mockFn.mockReset();
|
|
965
|
+
|
|
966
|
+
__mockRegistry.push(mockFn);
|
|
967
|
+
return mockFn;
|
|
968
|
+
},
|
|
969
|
+
|
|
970
|
+
spyOn(object, methodName) {
|
|
971
|
+
const original = object[methodName];
|
|
972
|
+
if (typeof original !== 'function') {
|
|
973
|
+
throw new Error(\`Cannot spy on \${methodName}: not a function\`);
|
|
974
|
+
}
|
|
975
|
+
const spy = mock.fn(original);
|
|
976
|
+
spy.__originalMethod = original;
|
|
977
|
+
spy.__spyTarget = object;
|
|
978
|
+
spy.__spyMethodName = methodName;
|
|
979
|
+
spy.__isSpyFunction = true;
|
|
980
|
+
spy.mockRestore = () => {
|
|
981
|
+
object[methodName] = original;
|
|
982
|
+
const idx = __mockRegistry.indexOf(spy);
|
|
983
|
+
if (idx !== -1) __mockRegistry.splice(idx, 1);
|
|
984
|
+
return spy;
|
|
985
|
+
};
|
|
986
|
+
object[methodName] = spy;
|
|
987
|
+
return spy;
|
|
988
|
+
},
|
|
989
|
+
|
|
990
|
+
clearAllMocks() {
|
|
991
|
+
for (const fn of __mockRegistry) fn.mockClear();
|
|
992
|
+
},
|
|
993
|
+
|
|
994
|
+
resetAllMocks() {
|
|
995
|
+
for (const fn of __mockRegistry) fn.mockReset();
|
|
996
|
+
},
|
|
997
|
+
|
|
998
|
+
restoreAllMocks() {
|
|
999
|
+
for (let i = __mockRegistry.length - 1; i >= 0; i--) {
|
|
1000
|
+
if (__mockRegistry[i].__isSpyFunction) __mockRegistry[i].mockRestore();
|
|
1001
|
+
}
|
|
1002
|
+
},
|
|
1003
|
+
};
|
|
1004
|
+
|
|
572
1005
|
// ============================================================
|
|
573
1006
|
// Test Runner Helpers
|
|
574
1007
|
// ============================================================
|
|
@@ -735,6 +1168,11 @@ var testEnvironmentCode = `
|
|
|
735
1168
|
}
|
|
736
1169
|
|
|
737
1170
|
const testStart = Date.now();
|
|
1171
|
+
// Reset assertion counting state before each test
|
|
1172
|
+
__expectedAssertions = null;
|
|
1173
|
+
__assertionCount = 0;
|
|
1174
|
+
__hasAssertionsFlag = false;
|
|
1175
|
+
|
|
738
1176
|
try {
|
|
739
1177
|
// Run all beforeEach hooks (parent first, then current)
|
|
740
1178
|
for (const hook of [...parentHooks.beforeEach, ...suite.beforeEach]) {
|
|
@@ -749,6 +1187,17 @@ var testEnvironmentCode = `
|
|
|
749
1187
|
await hook();
|
|
750
1188
|
}
|
|
751
1189
|
|
|
1190
|
+
// Verify assertion counts after test passes
|
|
1191
|
+
if (__hasAssertionsFlag && __assertionCount === 0) {
|
|
1192
|
+
throw new TestError('Expected at least one assertion', 'hasAssertions', '>0', 0);
|
|
1193
|
+
}
|
|
1194
|
+
if (__expectedAssertions !== null && __assertionCount !== __expectedAssertions) {
|
|
1195
|
+
throw new TestError(
|
|
1196
|
+
\`Expected \${__expectedAssertions} assertions, got \${__assertionCount}\`,
|
|
1197
|
+
'assertions', __expectedAssertions, __assertionCount
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
752
1201
|
const testResult = {
|
|
753
1202
|
name: t.name,
|
|
754
1203
|
suitePath: suitePath,
|
|
@@ -875,6 +1324,10 @@ var testEnvironmentCode = `
|
|
|
875
1324
|
currentSuite = rootSuite;
|
|
876
1325
|
suiteStack.length = 0;
|
|
877
1326
|
suiteStack.push(rootSuite);
|
|
1327
|
+
// Clear mocks
|
|
1328
|
+
__mockCallOrder = 0;
|
|
1329
|
+
mock.restoreAllMocks();
|
|
1330
|
+
__mockRegistry.length = 0;
|
|
878
1331
|
}
|
|
879
1332
|
|
|
880
1333
|
function __setEventCallback(callback) {
|
|
@@ -893,6 +1346,8 @@ var testEnvironmentCode = `
|
|
|
893
1346
|
globalThis.afterEach = afterEach;
|
|
894
1347
|
globalThis.beforeAll = beforeAll;
|
|
895
1348
|
globalThis.afterAll = afterAll;
|
|
1349
|
+
globalThis.mock = mock;
|
|
1350
|
+
globalThis.jest = mock; // Jest compatibility alias
|
|
896
1351
|
globalThis.__runAllTests = __runAllTests;
|
|
897
1352
|
globalThis.__resetTestEnvironment = __resetTestEnvironment;
|
|
898
1353
|
globalThis.__hasTests = __hasTests;
|
|
@@ -936,4 +1391,4 @@ function getTestCount(context) {
|
|
|
936
1391
|
return context.evalSync("__getTestCount()");
|
|
937
1392
|
}
|
|
938
1393
|
|
|
939
|
-
//# debugId=
|
|
1394
|
+
//# debugId=4F21D3A88A45D56164756E2164756E21
|