1
+ const { create } = require ( "domain" ) ;
1
2
const { promises : fs } = require ( "fs" ) ;
2
3
const { default : diff } = require ( "jest-diff" ) ;
3
4
const { toMatchInlineSnapshot, toMatchSnapshot } = require ( "jest-snapshot" ) ;
4
5
const { extractSpeechLines } = require ( "./logParser" ) ;
5
6
7
+ const speechSnapshotBrand = Symbol . for (
8
+ "screen-reader-testing-library.speechSnapshot"
9
+ ) ;
10
+
6
11
/**
7
12
* @param {number } timeoutMS
8
13
* @returns {Promise<void> }
@@ -46,7 +51,7 @@ function createSpeechRecorder(logFilePath) {
46
51
* @param {() => Promise<void> } fn
47
52
* @returns {Promise<string[][]> }
48
53
*/
49
- async function recordLines ( fn ) {
54
+ async function record ( fn ) {
50
55
// move to end
51
56
await start ( ) ;
52
57
await fn ( ) ;
@@ -57,31 +62,49 @@ function createSpeechRecorder(logFilePath) {
57
62
return fs . access ( logFilePath ) ;
58
63
}
59
64
60
- return { readable, recordLines , start, stop } ;
65
+ return { readable, record , start, stop } ;
61
66
}
62
67
63
68
/**
69
+ * Must return `any` or `expect.extend(createMatchers(logFilePath))` does not typecheck.
70
+ * `toMatchInlineSnapshot` will be unassignable for unknown reasons.
64
71
* @param {string } logFilePath
72
+ * @returns {any }
65
73
*/
66
74
function createMatchers ( logFilePath ) {
67
- const recorder = createSpeechRecorder ( logFilePath ) ;
75
+ const speechRecorder = createSpeechRecorder ( logFilePath ) ;
68
76
69
77
/**
70
78
*
71
- * @param {() => Promise<void> } fn
72
- * @param {string[][] } _expectedLines
73
- * @returns {Promise< ReturnType<typeof toMatchInlineSnapshot> > }
79
+ * @param {string[][] } recordedSpeech
80
+ * @param {string } [expectedSpeechSnapshot]
81
+ * @returns {ReturnType<typeof toMatchInlineSnapshot> }
74
82
* @this {import('jest-snapshot/build/types').Context}
75
83
*/
76
- async function toMatchSpeechInlineSnapshot ( fn , _expectedLines ) {
77
- // throws with "Jest: Multiple inline snapshots for the same call are not supported."
78
- throw new Error ( "Not implemented" ) ;
79
- // // move to end
80
- // await recorder.start();
81
- // await fn();
82
- // const actualLines = await recorder.stop();
83
-
84
- // return toMatchInlineSnapshot.call(this, actualLines);
84
+ function toMatchSpeechInlineSnapshot ( recordedSpeech , expectedSpeechSnapshot ) {
85
+ // Abort test on first mismatch.
86
+ // Subsequent actions will be based on an incorrect state otherwise and almost always fail as well.
87
+ this . dontThrow = ( ) => { } ;
88
+ if ( typeof recordedSpeech === "function" ) {
89
+ throw new Error (
90
+ "Recording lines is not implemented by the matcher. Use `expect(recordLines(async () => {})).resolves.toMatchInlineSnapshot()` instead"
91
+ ) ;
92
+ }
93
+
94
+ const actualSpeechSnapshot = {
95
+ [ speechSnapshotBrand ] : true ,
96
+ speech : recordedSpeech ,
97
+ } ;
98
+
99
+ // jest's `toMatchInlineSnapshot` relies on arity.
100
+ if ( expectedSpeechSnapshot === undefined ) {
101
+ return toMatchInlineSnapshot . call ( this , actualSpeechSnapshot ) ;
102
+ }
103
+ return toMatchInlineSnapshot . call (
104
+ this ,
105
+ actualSpeechSnapshot ,
106
+ expectedSpeechSnapshot
107
+ ) ;
85
108
}
86
109
87
110
/**
@@ -92,51 +115,51 @@ function createMatchers(logFilePath) {
92
115
* @this {import('jest-snapshot/build/types').Context}
93
116
*/
94
117
async function toMatchSpeechSnapshot ( fn , snapshotName ) {
95
- const actualLines = await recorder . recordLines ( fn ) ;
118
+ const speech = await speechRecorder . record ( fn ) ;
96
119
97
- return toMatchSnapshot . call ( this , actualLines , snapshotName ) ;
120
+ return toMatchSnapshot . call ( this , speech , snapshotName ) ;
98
121
}
99
122
100
123
/**
101
124
* @param {() => Promise<void> } fn
102
- * @param {string[][] } expectedLines
125
+ * @param {string[][] } expectedSpeech
103
126
* @returns {Promise<{actual: unknown, message: () => string, pass: boolean}> }
104
127
* @this {import('jest-snapshot/build/types').Context}
105
128
*/
106
- async function toAnnounceNVDA ( fn , expectedLines ) {
107
- const actualLines = await recorder . recordLines ( fn ) ;
129
+ async function toAnnounceNVDA ( fn , expectedSpeech ) {
130
+ const actualSpeech = await speechRecorder . record ( fn ) ;
108
131
109
132
const options = {
110
133
comment : "deep equality" ,
111
134
isNot : this . isNot ,
112
135
promise : this . promise ,
113
136
} ;
114
137
115
- const pass = this . equals ( actualLines , expectedLines ) ;
138
+ const pass = this . equals ( actualSpeech , expectedSpeech ) ;
116
139
const message = pass
117
140
? ( ) =>
118
141
this . utils . matcherHint ( "toBe" , undefined , undefined , options ) +
119
142
"\n\n" +
120
- `Expected: not ${ this . utils . printExpected ( expectedLines ) } \n` +
121
- `Received: ${ this . utils . printReceived ( actualLines ) } `
143
+ `Expected: not ${ this . utils . printExpected ( expectedSpeech ) } \n` +
144
+ `Received: ${ this . utils . printReceived ( actualSpeech ) } `
122
145
: ( ) => {
123
- const diffString = diff ( expectedLines , actualLines , {
146
+ const diffString = diff ( expectedSpeech , actualSpeech , {
124
147
expand : this . expand ,
125
148
} ) ;
126
149
return (
127
150
this . utils . matcherHint ( "toBe" , undefined , undefined , options ) +
128
151
"\n\n" +
129
152
( diffString && diffString . includes ( "- Expect" )
130
153
? `Difference:\n\n${ diffString } `
131
- : `Expected: ${ this . utils . printExpected ( expectedLines ) } \n` +
132
- `Received: ${ this . utils . printReceived ( actualLines ) } ` )
154
+ : `Expected: ${ this . utils . printExpected ( expectedSpeech ) } \n` +
155
+ `Received: ${ this . utils . printReceived ( actualSpeech ) } ` )
133
156
) ;
134
157
} ;
135
158
136
- return { actual : actualLines , message, pass } ;
159
+ return { actual : actualSpeech , message, pass } ;
137
160
}
138
161
139
- return { toAnnounceNVDA, toMatchSpeechInlineSnapshot , toMatchSpeechSnapshot } ;
162
+ return { toAnnounceNVDA, toMatchSpeechSnapshot , toMatchSpeechInlineSnapshot } ;
140
163
}
141
164
142
165
/**
@@ -156,9 +179,40 @@ function createJestSpeechRecorder(logFilePath) {
156
179
return recorder ;
157
180
}
158
181
182
+ /**
183
+ *
184
+ * @param {jest.Expect } expect
185
+ * @param {* } logFilePath
186
+ */
187
+ function extendExpect ( expect , logFilePath ) {
188
+ expect . extend ( createMatchers ( logFilePath ) ) ;
189
+
190
+ expect . addSnapshotSerializer ( {
191
+ /**
192
+ * @param {any } val
193
+ */
194
+ print ( val ) {
195
+ /**
196
+ * @type {{ speech: string[][] } }
197
+ */
198
+ const snapshot = val ;
199
+ const { speech } = snapshot ;
200
+
201
+ return speech
202
+ . map ( ( line ) => {
203
+ return `"${ line . join ( ", " ) } "` ;
204
+ } )
205
+ . join ( "\n" ) ;
206
+ } ,
207
+ test ( value ) {
208
+ return value != null && value [ speechSnapshotBrand ] === true ;
209
+ } ,
210
+ } ) ;
211
+ }
212
+
159
213
module . exports = {
160
214
awaitNvdaRecording,
161
215
createSpeechRecorder,
162
- createMatchers,
163
216
createJestSpeechRecorder,
217
+ extendExpect,
164
218
} ;
0 commit comments