1/* 2 2022-05-22 3 4 The author disclaims copyright to this source code. In place of a 5 legal notice, here is a blessing: 6 7 * May you do good and not evil. 8 * May you find forgiveness for yourself and forgive others. 9 * May you share freely, never taking more than you give. 10 11 *********************************************************************** 12 13 This file contains bootstrapping code used by various test scripts 14 which live in this file's directory. 15*/ 16'use strict'; 17(function(self){ 18 /* querySelectorAll() proxy */ 19 const EAll = function(/*[element=document,] cssSelector*/){ 20 return (arguments.length>1 ? arguments[0] : document) 21 .querySelectorAll(arguments[arguments.length-1]); 22 }; 23 /* querySelector() proxy */ 24 const E = function(/*[element=document,] cssSelector*/){ 25 return (arguments.length>1 ? arguments[0] : document) 26 .querySelector(arguments[arguments.length-1]); 27 }; 28 29 /** 30 Helpers for writing sqlite3-specific tests. 31 */ 32 self.SqliteTestUtil = { 33 /** Running total of the number of tests run via 34 this API. */ 35 counter: 0, 36 /** 37 If expr is a function, it is called and its result 38 is returned, coerced to a bool, else expr, coerced to 39 a bool, is returned. 40 */ 41 toBool: function(expr){ 42 return (expr instanceof Function) ? !!expr() : !!expr; 43 }, 44 /** abort() if expr is false. If expr is a function, it 45 is called and its result is evaluated. 46 */ 47 assert: function f(expr, msg){ 48 if(!f._){ 49 f._ = ('undefined'===typeof abort 50 ? (msg)=>{throw new Error(msg)} 51 : abort); 52 } 53 ++this.counter; 54 if(!this.toBool(expr)){ 55 f._(msg || "Assertion failed."); 56 } 57 return this; 58 }, 59 /** Identical to assert() but throws instead of calling 60 abort(). */ 61 affirm: function(expr, msg){ 62 ++this.counter; 63 if(!this.toBool(expr)) throw new Error(msg || "Affirmation failed."); 64 return this; 65 }, 66 /** Calls f() and squelches any exception it throws. If it 67 does not throw, this function throws. */ 68 mustThrow: function(f, msg){ 69 ++this.counter; 70 let err; 71 try{ f(); } catch(e){err=e;} 72 if(!err) throw new Error(msg || "Expected exception."); 73 return this; 74 }, 75 /** 76 Works like mustThrow() but expects filter to be a regex, 77 function, or string to match/filter the resulting exception 78 against. If f() does not throw, this test fails and an Error is 79 thrown. If filter is a regex, the test passes if 80 filter.test(error.message) passes. If it's a function, the test 81 passes if filter(error) returns truthy. If it's a string, the 82 test passes if the filter matches the exception message 83 precisely. In all other cases the test fails, throwing an 84 Error. 85 86 If it throws, msg is used as the error report unless it's falsy, 87 in which case a default is used. 88 */ 89 mustThrowMatching: function(f, filter, msg){ 90 ++this.counter; 91 let err; 92 try{ f(); } catch(e){err=e;} 93 if(!err) throw new Error(msg || "Expected exception."); 94 let pass = false; 95 if(filter instanceof RegExp) pass = filter.test(err.message); 96 else if(filter instanceof Function) pass = filter(err); 97 else if('string' === typeof filter) pass = (err.message === filter); 98 if(!pass){ 99 throw new Error(msg || ("Filter rejected this exception: "+err.message)); 100 } 101 return this; 102 }, 103 /** Throws if expr is truthy or expr is a function and expr() 104 returns truthy. */ 105 throwIf: function(expr, msg){ 106 ++this.counter; 107 if(this.toBool(expr)) throw new Error(msg || "throwIf() failed"); 108 return this; 109 }, 110 /** Throws if expr is falsy or expr is a function and expr() 111 returns falsy. */ 112 throwUnless: function(expr, msg){ 113 ++this.counter; 114 if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed"); 115 return this; 116 }, 117 118 /** 119 Parses window.location.search-style string into an object 120 containing key/value pairs of URL arguments (already 121 urldecoded). The object is created using Object.create(null), 122 so contains only parsed-out properties and has no prototype 123 (and thus no inherited properties). 124 125 If the str argument is not passed (arguments.length==0) then 126 window.location.search.substring(1) is used by default. If 127 neither str is passed in nor window exists then false is returned. 128 129 On success it returns an Object containing the key/value pairs 130 parsed from the string. Keys which have no value are treated 131 has having the boolean true value. 132 133 Pedantic licensing note: this code has appeared in other source 134 trees, but was originally written by the same person who pasted 135 it into those trees. 136 */ 137 processUrlArgs: function(str) { 138 if( 0 === arguments.length ) { 139 if( ('undefined' === typeof window) || 140 !window.location || 141 !window.location.search ) return false; 142 else str = (''+window.location.search).substring(1); 143 } 144 if( ! str ) return false; 145 str = (''+str).split(/#/,2)[0]; // remove #... to avoid it being added as part of the last value. 146 const args = Object.create(null); 147 const sp = str.split(/&+/); 148 const rx = /^([^=]+)(=(.+))?/; 149 var i, m; 150 for( i in sp ) { 151 m = rx.exec( sp[i] ); 152 if( ! m ) continue; 153 args[decodeURIComponent(m[1])] = (m[3] ? decodeURIComponent(m[3]) : true); 154 } 155 return args; 156 } 157 }; 158 159 160 /** 161 This is a module object for use with the emscripten-installed 162 sqlite3InitModule() factory function. 163 */ 164 self.sqlite3TestModule = { 165 /** 166 Array of functions to call after Emscripten has initialized the 167 wasm module. Each gets passed the Emscripten module object 168 (which is _this_ object). 169 */ 170 postRun: [ 171 /* function(theModule){...} */ 172 ], 173 //onRuntimeInitialized: function(){}, 174 /* Proxy for C-side stdout output. */ 175 print: (...args)=>{console.log(...args)}, 176 /* Proxy for C-side stderr output. */ 177 printErr: (...args)=>{console.error(...args)}, 178 /** 179 Called by the Emscripten module init bits to report loading 180 progress. It gets passed an empty argument when loading is done 181 (after onRuntimeInitialized() and any this.postRun callbacks 182 have been run). 183 */ 184 setStatus: function f(text){ 185 if(!f.last){ 186 f.last = { text: '', step: 0 }; 187 f.ui = { 188 status: E('#module-status'), 189 progress: E('#module-progress'), 190 spinner: E('#module-spinner') 191 }; 192 } 193 if(text === f.last.text) return; 194 f.last.text = text; 195 if(f.ui.progress){ 196 f.ui.progress.value = f.last.step; 197 f.ui.progress.max = f.last.step + 1; 198 } 199 ++f.last.step; 200 if(text) { 201 f.ui.status.classList.remove('hidden'); 202 f.ui.status.innerText = text; 203 }else{ 204 if(f.ui.progress){ 205 f.ui.progress.remove(); 206 f.ui.spinner.remove(); 207 delete f.ui.progress; 208 delete f.ui.spinner; 209 } 210 f.ui.status.classList.add('hidden'); 211 } 212 }, 213 /** 214 Config options used by the Emscripten-dependent initialization 215 which happens via this.initSqlite3(). This object gets 216 (indirectly) passed to sqlite3ApiBootstrap() to configure the 217 sqlite3 API. 218 */ 219 sqlite3ApiConfig: { 220 wasmfsOpfsDir: "/opfs" 221 }, 222 /** 223 Intended to be called by apps which need to call the 224 Emscripten-installed sqlite3InitModule() routine. This function 225 temporarily installs this.sqlite3ApiConfig into the self 226 object, calls it sqlite3InitModule(), and removes 227 self.sqlite3ApiConfig after initialization is done. Returns the 228 promise from sqlite3InitModule(), and the next then() handler 229 will get the sqlite3 API object as its argument. 230 */ 231 initSqlite3: function(){ 232 self.sqlite3ApiConfig = this.sqlite3ApiConfig; 233 return self.sqlite3InitModule(this).finally(()=>delete self.sqlite3ApiConfig); 234 } 235 }; 236})(self/*window or worker*/); 237