@@ -6,21 +6,31 @@ const { specify } = require("mocha-sugar-free");
6
6
const { inBrowserContext } = require ( "./util.js" ) ;
7
7
const { JSDOM , VirtualConsole } = require ( "jsdom" ) ;
8
8
const ResourceLoader = require ( "jsdom/lib/jsdom/browser/resources/resource-loader" ) ;
9
+ const { resolveReason } = require ( "./utils.js" ) ;
9
10
const {
10
11
computeAccessibleName,
11
12
computeAccessibleDescription,
13
+ getRole,
12
14
} = require ( "../../dist/" ) ;
13
15
14
16
const reporterPathname = "/resources/testharnessreport.js" ;
17
+ const testdriverPathname = "/resources/testdriver.js" ;
18
+ const ATTAcommPathname = "/wai-aria/scripts/ATTAcomm.js" ;
19
+
20
+ const unexpectedPassingTestMessage = `
21
+ Hey, did you fix a bug? This test used to be failing, but during
22
+ this run there were no errors. If you have fixed the issue covered
23
+ by this test, you can edit the "to-run.yaml" file and remove the line
24
+ containing this test. Thanks!
25
+ ` ;
15
26
16
27
module . exports = ( urlPrefixFactory ) => {
17
28
if ( inBrowserContext ( ) ) {
18
29
return ( ) => {
19
30
// TODO: browser support for running WPT
20
31
} ;
21
32
}
22
-
23
- return ( testPath , title = testPath , expectFail ) => {
33
+ return ( testPath , title = testPath , expectFail = false ) => {
24
34
specify ( {
25
35
title,
26
36
expectPromise : true ,
@@ -42,13 +52,23 @@ class CustomResourceLoader extends ResourceLoader {
42
52
fetch ( urlString , options ) {
43
53
const url = new URL ( urlString ) ;
44
54
45
- if ( url . pathname === reporterPathname ) {
55
+ if ( url . pathname === ATTAcommPathname ) {
56
+ const filePath = path . resolve ( __dirname , "./ATTAcomm.js" ) ;
57
+
58
+ return super . fetch ( `file://${ filePath } ` , options ) ;
59
+ } else if ( url . pathname === testdriverPathname ) {
60
+ const filePath = path . resolve ( __dirname , "./testdriver.js" ) ;
61
+
62
+ return super . fetch ( `file://${ filePath } ` , options ) ;
63
+ } else if ( url . pathname . startsWith ( "/resources/testdriver" ) ) {
64
+ return Promise . resolve ( Buffer . from ( "" , "utf-8" ) ) ;
65
+ } else if ( url . pathname === reporterPathname ) {
46
66
return Promise . resolve ( Buffer . from ( "window.shimTest();" , "utf-8" ) ) ;
47
67
} else if ( url . pathname . startsWith ( "/resources/" ) ) {
48
68
// When running to-upstream tests, the server doesn't have a /resources/ directory.
49
- // So, always go to the one in ./tests .
69
+ // So, always go to the one in ../wpt .
50
70
// The path replacement accounts for a rewrite performed by the WPT server:
51
- // https://github.com/w3c/ web-platform-tests/blob/master/tools/serve/serve.py#L271
71
+ // https://github.com/web-platform-tests/wpt /blob/master/tools/serve/serve.py#L271
52
72
const filePath = path
53
73
. resolve ( __dirname , "../wpt" + url . pathname )
54
74
. replace (
@@ -57,18 +77,42 @@ class CustomResourceLoader extends ResourceLoader {
57
77
) ;
58
78
59
79
return super . fetch ( `file://${ filePath } ` , options ) ;
60
- } else if ( url . pathname === "/wai-aria/scripts/ATTAcomm.js" ) {
61
- const filePath = path . resolve ( __dirname , "./ATTAcomm.js" ) ;
62
- return super . fetch ( `file://${ filePath } ` , options ) ;
63
80
}
64
81
65
82
return super . fetch ( urlString , options ) ;
66
83
}
67
84
}
68
85
86
+ function formatFailedTest ( test ) {
87
+ switch ( test . status ) {
88
+ case test . PASS :
89
+ return `Unexpected passing test: ${ JSON . stringify (
90
+ test . name ,
91
+ ) } ${ unexpectedPassingTestMessage } `;
92
+ case test . FAIL :
93
+ case test . PRECONDITION_FAILED :
94
+ return `Failed in ${ JSON . stringify ( test . name ) } :\n${ test . message } \n\n${
95
+ test . stack
96
+ } `;
97
+ case test . TIMEOUT :
98
+ return `Timeout in ${ JSON . stringify ( test . name ) } :\n${ test . message } \n\n${
99
+ test . stack
100
+ } `;
101
+ case test . NOTRUN :
102
+ return `Uncompleted test ${ JSON . stringify ( test . name ) } :\n${
103
+ test . message
104
+ } \n\n${ test . stack } `;
105
+ default :
106
+ throw new RangeError (
107
+ `Unexpected test status: ${ test . status } (test: ${ JSON . stringify (
108
+ test . name ,
109
+ ) } )`,
110
+ ) ;
111
+ }
112
+ }
113
+
69
114
function createJSDOM ( urlPrefix , testPath , expectFail ) {
70
115
const unhandledExceptions = [ ] ;
71
- const doneErrors = [ ] ;
72
116
73
117
let allowUnhandledExceptions = false ;
74
118
@@ -102,27 +146,108 @@ function createJSDOM(urlPrefix, testPath, expectFail) {
102
146
103
147
window . computeAccessibleName = computeAccessibleName ;
104
148
window . computeAccessibleDescription = computeAccessibleDescription ;
149
+ window . getRole = getRole ;
105
150
106
151
window . shimTest = ( ) => {
107
152
const oldSetup = window . setup ;
108
- window . setup = ( ) => {
109
- // noop, otherwise failing tests just slowly timeout
153
+ window . setup = ( options ) => {
154
+ if ( options . allow_uncaught_exception ) {
155
+ allowUnhandledExceptions = true ;
156
+ }
157
+ oldSetup ( options ) ;
110
158
} ;
111
159
112
- window . add_result_callback ( ( test ) => {
113
- if ( test . status === 1 ) {
114
- errors . push (
115
- `Failed in "${ test . name } ": \n${ test . message } \n\n${ test . stack } ` ,
160
+ // Overriding assert_throws_js and friends in order to allow us to throw exceptions from another realm. See
161
+ // https://github.com/jsdom/jsdom/issues/2727 for more information.
162
+
163
+ function assertThrowsJSImpl (
164
+ constructor ,
165
+ func ,
166
+ description ,
167
+ assertionType ,
168
+ ) {
169
+ try {
170
+ func . call ( this ) ;
171
+ window . assert_true (
172
+ false ,
173
+ `${ assertionType } : ${ description } : ${ func } did not throw` ,
116
174
) ;
117
- } else if ( test . status === 2 ) {
118
- errors . push (
119
- `Timeout in "${ test . name } ": \n${ test . message } \n\n${ test . stack } ` ,
175
+ } catch ( e ) {
176
+ if ( e instanceof window . AssertionError ) {
177
+ throw e ;
178
+ }
179
+
180
+ // Basic sanity-checks on the thrown exception.
181
+ window . assert_true (
182
+ typeof e === "object" ,
183
+ `${ assertionType } : ${ description } : ${ func } threw ${ e } with type ${ typeof e } , not an object` ,
120
184
) ;
121
- } else if ( test . status === 3 ) {
122
- errors . push (
123
- `Uncompleted test "${ test . name } ": \n${ test . message } \n\n${ test . stack } ` ,
185
+
186
+ window . assert_true (
187
+ e !== null ,
188
+ `${ assertionType } : ${ description } : ${ func } threw null, not an object` ,
189
+ ) ;
190
+
191
+ // Basic sanity-check on the passed-in constructor
192
+ window . assert_true (
193
+ typeof constructor === "function" ,
194
+ `${ assertionType } : ${ description } : ${ constructor } is not a constructor` ,
195
+ ) ;
196
+ let obj = constructor ;
197
+ while ( obj ) {
198
+ if ( typeof obj === "function" && obj . name === "Error" ) {
199
+ break ;
200
+ }
201
+ obj = Object . getPrototypeOf ( obj ) ;
202
+ }
203
+ window . assert_true (
204
+ obj !== null && obj !== undefined ,
205
+ `${ assertionType } : ${ description } : ${ constructor } is not an Error subtype` ,
206
+ ) ;
207
+
208
+ // And checking that our exception is reasonable
209
+ window . assert_equals (
210
+ e . name ,
211
+ constructor . name ,
212
+ `${ assertionType } : ${ description } : ${ func } threw ${ e } (${ e . name } ) ` +
213
+ `expected instance of ${ constructor . name } ` ,
124
214
) ;
125
215
}
216
+ }
217
+
218
+ // eslint-disable-next-line camelcase
219
+ window . assert_throws_js = ( constructor , func , description ) => {
220
+ assertThrowsJSImpl (
221
+ constructor ,
222
+ func ,
223
+ description ,
224
+ "assert_throws_js" ,
225
+ ) ;
226
+ } ;
227
+ // eslint-disable-next-line camelcase
228
+ window . promise_rejects_js = ( test , expected , promise , description ) => {
229
+ return promise
230
+ . then ( test . unreached_func ( "Should have rejected: " + description ) )
231
+ . catch ( ( e ) => {
232
+ assertThrowsJSImpl (
233
+ expected ,
234
+ ( ) => {
235
+ throw e ;
236
+ } ,
237
+ description ,
238
+ "promise_reject_js" ,
239
+ ) ;
240
+ } ) ;
241
+ } ;
242
+
243
+ window . add_result_callback ( ( test ) => {
244
+ if (
245
+ test . status === test . FAIL ||
246
+ test . status === test . TIMEOUT ||
247
+ test . status === test . NOTRUN
248
+ ) {
249
+ errors . push ( formatFailedTest ( test ) ) ;
250
+ }
126
251
} ) ;
127
252
128
253
window . add_completion_callback ( ( tests , harnessStatus ) => {
@@ -131,34 +256,75 @@ function createJSDOM(urlPrefix, testPath, expectFail) {
131
256
window . close ( ) ;
132
257
} ) ;
133
258
134
- if ( harnessStatus . status === 2 ) {
259
+ let harnessFail = false ;
260
+ if ( harnessStatus . status === harnessStatus . ERROR ) {
261
+ harnessFail = true ;
262
+ errors . push (
263
+ new Error (
264
+ `test harness should not error: ${ testPath } \n${ harnessStatus . message } ` ,
265
+ ) ,
266
+ ) ;
267
+ } else if ( harnessStatus . status === harnessStatus . TIMEOUT ) {
268
+ harnessFail = true ;
135
269
errors . push (
136
270
new Error ( `test harness should not timeout: ${ testPath } ` ) ,
137
271
) ;
138
272
}
139
273
140
- errors . push ( ...doneErrors ) ;
141
274
errors . push ( ...unhandledExceptions ) ;
142
275
276
+ if (
277
+ typeof expectFail === "object" &&
278
+ ( harnessFail || unhandledExceptions . length )
279
+ ) {
280
+ expectFail = false ;
281
+ }
282
+
143
283
if ( errors . length === 0 && expectFail ) {
144
- reject (
145
- new Error ( `
146
- Hey, did you fix a bug? This test used to be failing, but during
147
- this run there were no errors. If you have fixed the issue covered
148
- by this test, you can edit the "to-run.yaml" file and remove the line
149
- containing this test. Thanks!
150
- ` ) ,
151
- ) ;
152
- } else if ( errors . length === 1 && ! expectFail ) {
284
+ reject ( new Error ( unexpectedPassingTestMessage ) ) ;
285
+ } else if (
286
+ errors . length === 1 &&
287
+ ( tests . length === 1 || harnessFail ) &&
288
+ ! expectFail
289
+ ) {
153
290
reject ( new Error ( errors [ 0 ] ) ) ;
154
291
} else if ( errors . length && ! expectFail ) {
155
292
reject (
156
293
new Error (
157
- `${ errors . length } errors in test:\n\n${ errors . join ( "\n" ) } ` ,
294
+ `${ errors . length } /${
295
+ tests . length
296
+ } errors in test:\n\n${ errors . join ( "\n\n" ) } `,
158
297
) ,
159
298
) ;
160
- } else {
299
+ } else if ( typeof expectFail !== "object" ) {
161
300
resolve ( ) ;
301
+ } else {
302
+ const unexpectedErrors = [ ] ;
303
+ for ( const test of tests ) {
304
+ const data = expectFail [ test . name ] ;
305
+ const reason = data && data [ 0 ] ;
306
+
307
+ const innerExpectFail = resolveReason ( reason ) === "expect-fail" ;
308
+ if (
309
+ innerExpectFail
310
+ ? test . status === test . PASS
311
+ : test . status !== test . PASS
312
+ ) {
313
+ unexpectedErrors . push ( formatFailedTest ( test ) ) ;
314
+ }
315
+ }
316
+
317
+ if ( unexpectedErrors . length ) {
318
+ reject (
319
+ new Error (
320
+ `${ unexpectedErrors . length } /${
321
+ tests . length
322
+ } errors in test:\n\n${ unexpectedErrors . join ( "\n\n" ) } `,
323
+ ) ,
324
+ ) ;
325
+ } else {
326
+ resolve ( ) ;
327
+ }
162
328
}
163
329
} ) ;
164
330
} ;
0 commit comments