1+ const { create } = require ( "domain" ) ;
12const { promises : fs } = require ( "fs" ) ;
23const { default : diff } = require ( "jest-diff" ) ;
34const { toMatchInlineSnapshot, toMatchSnapshot } = require ( "jest-snapshot" ) ;
45const { extractSpeechLines } = require ( "./logParser" ) ;
56
7+ const speechSnapshotBrand = Symbol . for (
8+ "screen-reader-testing-library.speechSnapshot"
9+ ) ;
10+
611/**
712 * @param {number } timeoutMS
813 * @returns {Promise<void> }
@@ -46,7 +51,7 @@ function createSpeechRecorder(logFilePath) {
4651 * @param {() => Promise<void> } fn
4752 * @returns {Promise<string[][]> }
4853 */
49- async function recordLines ( fn ) {
54+ async function record ( fn ) {
5055 // move to end
5156 await start ( ) ;
5257 await fn ( ) ;
@@ -57,31 +62,49 @@ function createSpeechRecorder(logFilePath) {
5762 return fs . access ( logFilePath ) ;
5863 }
5964
60- return { readable, recordLines , start, stop } ;
65+ return { readable, record , start, stop } ;
6166}
6267
6368/**
69+ * Must return `any` or `expect.extend(createMatchers(logFilePath))` does not typecheck.
70+ * `toMatchInlineSnapshot` will be unassignable for unknown reasons.
6471 * @param {string } logFilePath
72+ * @returns {any }
6573 */
6674function createMatchers ( logFilePath ) {
67- const recorder = createSpeechRecorder ( logFilePath ) ;
75+ const speechRecorder = createSpeechRecorder ( logFilePath ) ;
6876
6977 /**
7078 *
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> }
7482 * @this {import('jest-snapshot/build/types').Context}
7583 */
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+ ) ;
85108 }
86109
87110 /**
@@ -92,51 +115,51 @@ function createMatchers(logFilePath) {
92115 * @this {import('jest-snapshot/build/types').Context}
93116 */
94117 async function toMatchSpeechSnapshot ( fn , snapshotName ) {
95- const actualLines = await recorder . recordLines ( fn ) ;
118+ const speech = await speechRecorder . record ( fn ) ;
96119
97- return toMatchSnapshot . call ( this , actualLines , snapshotName ) ;
120+ return toMatchSnapshot . call ( this , speech , snapshotName ) ;
98121 }
99122
100123 /**
101124 * @param {() => Promise<void> } fn
102- * @param {string[][] } expectedLines
125+ * @param {string[][] } expectedSpeech
103126 * @returns {Promise<{actual: unknown, message: () => string, pass: boolean}> }
104127 * @this {import('jest-snapshot/build/types').Context}
105128 */
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 ) ;
108131
109132 const options = {
110133 comment : "deep equality" ,
111134 isNot : this . isNot ,
112135 promise : this . promise ,
113136 } ;
114137
115- const pass = this . equals ( actualLines , expectedLines ) ;
138+ const pass = this . equals ( actualSpeech , expectedSpeech ) ;
116139 const message = pass
117140 ? ( ) =>
118141 this . utils . matcherHint ( "toBe" , undefined , undefined , options ) +
119142 "\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 ) } `
122145 : ( ) => {
123- const diffString = diff ( expectedLines , actualLines , {
146+ const diffString = diff ( expectedSpeech , actualSpeech , {
124147 expand : this . expand ,
125148 } ) ;
126149 return (
127150 this . utils . matcherHint ( "toBe" , undefined , undefined , options ) +
128151 "\n\n" +
129152 ( diffString && diffString . includes ( "- Expect" )
130153 ? `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 ) } ` )
133156 ) ;
134157 } ;
135158
136- return { actual : actualLines , message, pass } ;
159+ return { actual : actualSpeech , message, pass } ;
137160 }
138161
139- return { toAnnounceNVDA, toMatchSpeechInlineSnapshot , toMatchSpeechSnapshot } ;
162+ return { toAnnounceNVDA, toMatchSpeechSnapshot , toMatchSpeechInlineSnapshot } ;
140163}
141164
142165/**
@@ -156,9 +179,40 @@ function createJestSpeechRecorder(logFilePath) {
156179 return recorder ;
157180}
158181
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+
159213module . exports = {
160214 awaitNvdaRecording,
161215 createSpeechRecorder,
162- createMatchers,
163216 createJestSpeechRecorder,
217+ extendExpect,
164218} ;
0 commit comments