test-helper.js (10213B)
1 let log = []; 2 3 function expect_log(test, expected_log) { 4 test.step_func_done(() => { 5 const actual_log = log; 6 log = []; 7 assert_array_equals(actual_log, expected_log, 'fallback log'); 8 })(); 9 } 10 11 // Results of resolving a specifier using import maps. 12 const Result = { 13 // A failure considered as a fetch error in a module script tree. 14 // <script>'s error event is fired. 15 FETCH_ERROR: "fetch_error", 16 17 // A failure considered as a parse error in a module script tree. 18 // Window's error event is fired. 19 PARSE_ERROR: "parse_error", 20 21 // The specifier is considered as a relative or absolute URL. 22 // Specifier Expected log 23 // ------------------------- ---------------------- 24 // ...?name=foo log:foo 25 // data:...log('foo') foo 26 // Others, e.g. bare/bare relative:bare/bare 27 // ------------------------- ---------------------- 28 // (The last case assumes a file `bare/bare` that logs `relative:bare/bare` 29 // exists) 30 URL: "URL", 31 }; 32 33 const Handler = { 34 // Handlers for <script> element cases. 35 // Note that on a parse error both WindowErrorEvent and ScriptLoadEvent are 36 // called. 37 ScriptLoadEvent: "<script> element's load event handler", 38 ScriptErrorEvent: "<script> element's error event handler", 39 WindowErrorEvent: "window's error event handler", 40 41 // Handlers for dynamic imports. 42 DynamicImportResolve: "dynamic import resolve", 43 DynamicImportReject: "dynamic import reject", 44 }; 45 46 // Returns a map with Handler.* as the keys. 47 function getHandlers(t, specifier, expected) { 48 let handlers = {}; 49 handlers[Handler.ScriptLoadEvent] = t.unreached_func("Shouldn't load"); 50 handlers[Handler.ScriptErrorEvent] = 51 t.unreached_func("script's error event shouldn't be fired"); 52 handlers[Handler.WindowErrorEvent] = 53 t.unreached_func("window's error event shouldn't be fired"); 54 handlers[Handler.DynamicImportResolve] = 55 t.unreached_func("dynamic import promise shouldn't be resolved"); 56 handlers[Handler.DynamicImportReject] = 57 t.unreached_func("dynamic import promise shouldn't be rejected"); 58 59 if (expected === Result.FETCH_ERROR) { 60 handlers[Handler.ScriptErrorEvent] = () => expect_log(t, []); 61 handlers[Handler.DynamicImportReject] = () => expect_log(t, []); 62 } else if (expected === Result.PARSE_ERROR) { 63 let error_occurred = false; 64 handlers[Handler.WindowErrorEvent] = () => { error_occurred = true; }; 65 handlers[Handler.ScriptLoadEvent] = t.step_func(() => { 66 // Even if a parse error occurs, load event is fired (after 67 // window.onerror is called), so trigger the load handler only if 68 // there was no previous window.onerror call. 69 assert_true(error_occurred, "window.onerror should be fired"); 70 expect_log(t, []); 71 }); 72 handlers[Handler.DynamicImportReject] = t.step_func(() => { 73 assert_false(error_occurred, 74 "window.onerror shouldn't be fired for dynamic imports"); 75 expect_log(t, []); 76 }); 77 } else { 78 let expected_log; 79 if (expected === Result.URL) { 80 const match_data_url = specifier.match(/data:.*log\.push\('(.*)'\)/); 81 const match_log_js = specifier.match(/name=(.*)/); 82 if (match_data_url) { 83 expected_log = [match_data_url[1]]; 84 } else if (match_log_js) { 85 expected_log = ["log:" + match_log_js[1]]; 86 } else { 87 expected_log = ["relative:" + specifier]; 88 } 89 } else { 90 expected_log = [expected]; 91 } 92 handlers[Handler.ScriptLoadEvent] = () => expect_log(t, expected_log); 93 handlers[Handler.DynamicImportResolve] = () => expect_log(t, expected_log); 94 } 95 return handlers; 96 } 97 98 // Creates an <iframe> and run a test inside the <iframe> 99 // to separate the module maps and import maps in each test. 100 function testInIframe(importMapString, importMapBaseURL, testScript) { 101 const iframe = document.createElement('iframe'); 102 document.body.appendChild(iframe); 103 if (!importMapBaseURL) { 104 importMapBaseURL = document.baseURI; 105 } 106 let content = ` 107 <script src="/resources/testharness.js"></script> 108 <script src="/import-maps/resources/test-helper.js"></script> 109 <base href="${importMapBaseURL}"> 110 <script type="importmap">${importMapString}</script> 111 <body> 112 <script> 113 setup({ allow_uncaught_exception: true }); 114 ${testScript} 115 </sc` + `ript> 116 `; 117 iframe.contentDocument.write(content); 118 iframe.contentDocument.close(); 119 return fetch_tests_from_window(iframe.contentWindow); 120 } 121 122 function testScriptElement(importMapString, importMapBaseURL, specifier, expected, type) { 123 return testInIframe(importMapString, importMapBaseURL, ` 124 const t = async_test("${specifier}: <script src type=${type}>"); 125 const handlers = getHandlers(t, "${specifier}", "${expected}"); 126 const script = document.createElement("script"); 127 script.setAttribute("type", "${type}"); 128 script.setAttribute("src", "${specifier}"); 129 script.addEventListener("load", handlers[Handler.ScriptLoadEvent]); 130 script.addEventListener("error", handlers[Handler.ScriptErrorEvent]); 131 window.addEventListener("error", handlers[Handler.WindowErrorEvent]); 132 document.body.appendChild(script); 133 `); 134 } 135 136 function testStaticImport(importMapString, importMapBaseURL, specifier, expected) { 137 return testInIframe(importMapString, importMapBaseURL, ` 138 const t = async_test("${specifier}: static import"); 139 const handlers = getHandlers(t, "${specifier}", "${expected}"); 140 const script = document.createElement("script"); 141 script.setAttribute("type", "module"); 142 script.setAttribute("src", 143 "/import-maps/static-import.py?url=" + 144 encodeURIComponent("${specifier}")); 145 script.addEventListener("load", handlers[Handler.ScriptLoadEvent]); 146 script.addEventListener("error", handlers[Handler.ScriptErrorEvent]); 147 window.addEventListener("error", handlers[Handler.WindowErrorEvent]); 148 document.body.appendChild(script); 149 `); 150 } 151 152 function testDynamicImport(importMapString, importMapBaseURL, specifier, expected, type) { 153 return testInIframe(importMapString, importMapBaseURL, ` 154 const t = async_test("${specifier}: dynamic import (from ${type})"); 155 const handlers = getHandlers(t, "${specifier}", "${expected}"); 156 const script = document.createElement("script"); 157 script.setAttribute("type", "${type}"); 158 script.innerText = 159 "import(\\"${specifier}\\")" + 160 ".then(handlers[Handler.DynamicImportResolve], " + 161 "handlers[Handler.DynamicImportReject]);"; 162 script.addEventListener("error", 163 t.unreached_func("top-level inline script shouldn't error")); 164 document.body.appendChild(script); 165 `); 166 } 167 168 function testInIframeInjectBase(importMapString, importMapBaseURL, testScript) { 169 const iframe = document.createElement('iframe'); 170 document.body.appendChild(iframe); 171 let content = ` 172 <script src="/resources/testharness.js"></script> 173 <script src="/import-maps/resources/test-helper.js"></script> 174 <script src="/import-maps/resources/inject-base.js?pipe=sub&baseurl=${importMapBaseURL}"></script> 175 <script type="importmap"> 176 ${importMapString} 177 </script> 178 <body> 179 <script> 180 setup({ allow_uncaught_exception: true }); 181 ${testScript} 182 </sc` + `ript> 183 `; 184 iframe.contentDocument.write(content); 185 iframe.contentDocument.close(); 186 return fetch_tests_from_window(iframe.contentWindow); 187 } 188 189 function testStaticImportInjectBase(importMapString, importMapBaseURL, specifier, expected) { 190 return testInIframeInjectBase(importMapString, importMapBaseURL, ` 191 const t = async_test("${specifier}: static import with inject <base>"); 192 const handlers = getHandlers(t, "${specifier}", "${expected}"); 193 const script = document.createElement("script"); 194 script.setAttribute("type", "module"); 195 script.setAttribute("src", 196 "/import-maps/static-import.py?url=" + 197 encodeURIComponent("${specifier}")); 198 script.addEventListener("load", handlers[Handler.ScriptLoadEvent]); 199 script.addEventListener("error", handlers[Handler.ScriptErrorEvent]); 200 window.addEventListener("error", handlers[Handler.WindowErrorEvent]); 201 document.body.appendChild(script); 202 `); 203 } 204 205 function testDynamicImportInjectBase(importMapString, importMapBaseURL, specifier, expected, type) { 206 return testInIframeInjectBase(importMapString, importMapBaseURL, ` 207 const t = async_test("${specifier}: dynamic import (from ${type}) with inject <base>"); 208 const handlers = getHandlers(t, "${specifier}", "${expected}"); 209 const script = document.createElement("script"); 210 script.setAttribute("type", "${type}"); 211 script.innerText = 212 "import(\\"${specifier}\\")" + 213 ".then(handlers[Handler.DynamicImportResolve], " + 214 "handlers[Handler.DynamicImportReject]);"; 215 script.addEventListener("error", 216 t.unreached_func("top-level inline script shouldn't error")); 217 document.body.appendChild(script); 218 `); 219 } 220 221 function doTests(importMapString, importMapBaseURL, tests) { 222 promise_setup(function () { 223 return new Promise((resolve) => { 224 window.addEventListener("load", async () => { 225 for (const specifier in tests) { 226 // <script src> (module scripts) 227 await testScriptElement(importMapString, importMapBaseURL, specifier, tests[specifier][0], "module"); 228 229 // <script src> (classic scripts) 230 await testScriptElement(importMapString, importMapBaseURL, specifier, tests[specifier][1], "text/javascript"); 231 232 // static imports. 233 await testStaticImport(importMapString, importMapBaseURL, specifier, tests[specifier][2]); 234 235 // dynamic imports from a module script. 236 await testDynamicImport(importMapString, importMapBaseURL, specifier, tests[specifier][3], "module"); 237 238 // dynamic imports from a classic script. 239 await testDynamicImport(importMapString, importMapBaseURL, specifier, tests[specifier][3], "text/javascript"); 240 } 241 done(); 242 resolve(); 243 }); 244 }); 245 }, { explicit_done: true }); 246 } 247 248 function test_loaded(specifier, expected_log, description) { 249 promise_test(async t => { 250 log = []; 251 await import(specifier); 252 assert_array_equals(log, expected_log); 253 }, description); 254 };