Skip to content

Commit a6e4931

Browse files
committed
Add cashscript/jest export and make TestExtensions work with jest and vitest
1 parent b734f27 commit a6e4931

9 files changed

Lines changed: 129 additions & 140 deletions

File tree

packages/cashscript/.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
*
22
!dist/**
33
!vitest/package.json
4+
!jest/package.json
45
*.map
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "module",
3+
"types": "../dist/test/TestExtensions.d.ts",
4+
"main": "../dist/test/TestExtensions.js"
5+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { DebugResults } from '../debugging.js';
2+
3+
declare global {
4+
namespace jest {
5+
// eslint-disable-next-line
6+
interface Matchers<R> {
7+
toLog(value?: RegExp | string): Promise<void>;
8+
toFailRequireWith(value: RegExp | string): Promise<void>;
9+
toFailRequire(): Promise<void>;
10+
}
11+
}
12+
}
13+
14+
interface Debuggable {
15+
debug(): DebugResults;
16+
}
17+
18+
type TestFramework = typeof vi;
19+
const testFramework: TestFramework = (globalThis as any).vi ?? (globalThis as any).jest;
20+
21+
// Extend Vitest with the custom matchers, this file needs to be imported in the vitest.setup.ts file or the test file
22+
expect.extend({
23+
toLog(
24+
transaction: Debuggable,
25+
match?: RegExp | string,
26+
) {
27+
const loggerSpy = testFramework.spyOn(console, 'log');
28+
29+
// Clear any previous calls (if spy reused accidentally)
30+
loggerSpy.mockClear();
31+
32+
// silence actual stdout output
33+
loggerSpy.mockImplementation(() => { });
34+
35+
// Run debug, ignoring any errors because we only care about the logs, even if the transaction fails
36+
try {
37+
transaction.debug();
38+
} catch (error) { }
39+
40+
// We concatenate all the logs into a single string - if no logs are present, we set received to undefined
41+
const receivedBase = loggerSpy.mock.calls.reduce((acc, [log]) => `${acc}\n${log}`, '').trim();
42+
const received = receivedBase === '' ? undefined : receivedBase;
43+
44+
const matcherHint = this.utils.matcherHint('toLog', 'received', 'expected', { isNot: this.isNot });
45+
const expectedText = `Expected: ${this.isNot ? 'not ' : ''}${this.utils.printExpected(match)}`;
46+
const receivedText = `Received: ${this.utils.printReceived(received)}`;
47+
const message = (): string => `${matcherHint}\n\n${expectedText}\n${receivedText}`;
48+
49+
try {
50+
// We first check if the expected string is present in any of the individual console.log calls
51+
expect(loggerSpy).toHaveBeenCalledWith(match ? expect.stringMatching(match) : expect.anything());
52+
} catch {
53+
try {
54+
// We add this extra check to allow expect().toLog() to check multiple console.log calls in a single test
55+
// (e.g. for log ordering), which would fail the first check because that compares the individual console.log calls
56+
expect(receivedBase).toMatch(match ? match : expect.anything());
57+
} catch {
58+
return { message, pass: false };
59+
}
60+
} finally {
61+
// Restore the original console.log implementation
62+
loggerSpy.mockRestore();
63+
}
64+
65+
return { message, pass: true };
66+
},
67+
toFailRequire(
68+
transaction: Debuggable,
69+
) {
70+
try {
71+
transaction.debug();
72+
const message = (): string => 'Contract function did not fail a require statement.';
73+
return { message, pass: false };
74+
} catch (transactionError: any) {
75+
const receivedText = `Received string: ${this.utils.printReceived(transactionError?.message ?? '')}`;
76+
const message = (): string => `Contract function failed a require statement.\n${receivedText}`;
77+
return { message, pass: true };
78+
}
79+
},
80+
toFailRequireWith(
81+
transaction: Debuggable,
82+
match: RegExp | string,
83+
) {
84+
try {
85+
transaction.debug();
86+
const matcherHint = this.utils.matcherHint('.toFailRequireWith', undefined, match.toString(), { isNot: this.isNot });
87+
const message = (): string => `${matcherHint}\n\nContract function did not fail a require statement.`;
88+
return { message, pass: false };
89+
} catch (transactionError: any) {
90+
const matcherHint = this.utils.matcherHint('toFailRequireWith', 'received', 'expected', { isNot: this.isNot });
91+
const expectedText = `Expected pattern: ${this.isNot ? 'not ' : ''}${this.utils.printExpected(match)}`;
92+
const receivedText = `Received string: ${this.utils.printReceived(transactionError?.message ?? '')}`;
93+
const message = (): string => `${matcherHint}\n\n${expectedText}\n${receivedText}`;
94+
95+
try {
96+
expect(transactionError?.message ?? '').toMatch(match);
97+
return { message, pass: true };
98+
} catch {
99+
return { message, pass: false };
100+
}
101+
}
102+
},
103+
});
104+
105+
export { };

packages/cashscript/src/test/VitestExtensions.ts

Lines changed: 0 additions & 111 deletions
This file was deleted.

packages/cashscript/test/debugging.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -523,15 +523,15 @@ describe('Debugging tests', () => {
523523
});
524524
});
525525

526-
describe('VitestExtensions', () => {
526+
describe('TestExtensions', () => {
527527
const provider = new MockNetworkProvider();
528528
const contractTestRequires = new Contract(artifactTestRequires, [], { provider });
529529
const contractTestRequiresUtxo = randomUtxo();
530530
provider.addUtxo(contractTestRequires.address, contractTestRequiresUtxo);
531531

532-
// Note: happy cases are implicitly tested by the "regular" debugging tests, since the use VitestExtensions
532+
// Note: happy cases are implicitly tested by the "regular" debugging tests, since the use TestExtensions
533533

534-
it('should fail the VitestExtensions test if an incorrect log is expected', async () => {
534+
it('should fail the TestExtensions test if an incorrect log is expected', async () => {
535535
const transaction = new TransactionBuilder({ provider })
536536
.addInput(
537537
contractTestRequiresUtxo,
@@ -544,7 +544,7 @@ describe('Debugging tests', () => {
544544
).toThrow(/Expected: .*This is definitely not the log.*\nReceived: (.|\n)*?\[Input #0] Test.cash:4 Hello World/);
545545
});
546546

547-
it('should fail the VitestExtensions test if a log is logged that is NOT expected', async () => {
547+
it('should fail the TestExtensions test if a log is logged that is NOT expected', async () => {
548548
const transaction = new TransactionBuilder({ provider })
549549
.addInput(
550550
contractTestRequiresUtxo,
@@ -563,7 +563,7 @@ describe('Debugging tests', () => {
563563
).toThrow(/Expected: not .*undefined.*\nReceived: (.|\n)*?\[Input #0] Test.cash:4 Hello World/);
564564
});
565565

566-
it('should fail the VitestExtensions test if a log is expected where no log is logged', async () => {
566+
it('should fail the TestExtensions test if a log is expected where no log is logged', async () => {
567567
const transaction = new TransactionBuilder({ provider })
568568
.addInput(
569569
contractTestRequiresUtxo,
@@ -576,7 +576,7 @@ describe('Debugging tests', () => {
576576
).toThrow(/Expected: .*Hello World.*\nReceived: (.|\n)*?undefined/);
577577
});
578578

579-
it('should fail the VitestExtensions test if an incorrect require error message is expected', async () => {
579+
it('should fail the TestExtensions test if an incorrect require error message is expected', async () => {
580580
const transaction = new TransactionBuilder({ provider })
581581
.addInput(
582582
contractTestRequiresUtxo,
@@ -589,7 +589,7 @@ describe('Debugging tests', () => {
589589
).toThrow(/Expected pattern: .*1 should equal 3.*\nReceived string: (.|\n)*?1 should equal 2/);
590590
});
591591

592-
it('should fail the VitestExtensions test if a require error message is expected where no error is thrown', async () => {
592+
it('should fail the TestExtensions test if a require error message is expected where no error is thrown', async () => {
593593
const transaction = new TransactionBuilder({ provider })
594594
.addInput(
595595
contractTestRequiresUtxo,
@@ -602,7 +602,7 @@ describe('Debugging tests', () => {
602602
).toThrow(/Contract function did not fail a require statement/);
603603
});
604604

605-
it('should fail the VitestExtensions test if an error is thrown where it is NOT expected', async () => {
605+
it('should fail the TestExtensions test if an error is thrown where it is NOT expected', async () => {
606606
const transaction = new TransactionBuilder({ provider })
607607
.addInput(
608608
contractTestRequiresUtxo,

packages/cashscript/tsconfig.build.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
"extends": "../../tsconfig.build.json",
33
"compilerOptions": {
44
"outDir": "./dist",
5-
"types": ["vitest/globals"]
5+
"types": [
6+
"vitest/globals"
7+
]
68
},
79
"include": [
810
"src/**/*",
9-
"vitest.d.ts",
1011
],
11-
"exclude": ["test/fixture"],
12-
}
12+
"exclude": [
13+
"test/fixture"
14+
],
15+
}

packages/cashscript/vitest.d.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import { inspect } from 'util';
2-
import '../src/test/VitestExtensions.js';
2+
import '../src/test/TestExtensions.js';
33

44
inspect.defaultOptions.depth = 10;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"type": "module",
3-
"types": "../dist/test/VitestExtensions.d.ts",
4-
"main": "../dist/test/VitestExtensions.js"
3+
"types": "../dist/test/TestExtensions.d.ts",
4+
"main": "../dist/test/TestExtensions.js"
55
}

0 commit comments

Comments
 (0)