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: function(){
176      console.log.apply(console, Array.prototype.slice.call(arguments));
177    },
178    /* Proxy for C-side stderr output. */
179    printErr: function(){
180      console.error.apply(console, Array.prototype.slice.call(arguments));
181    },
182    /**
183       Called by the Emscripten module init bits to report loading
184       progress. It gets passed an empty argument when loading is done
185       (after onRuntimeInitialized() and any this.postRun callbacks
186       have been run).
187    */
188    setStatus: function f(text){
189      if(!f.last){
190        f.last = { text: '', step: 0 };
191        f.ui = {
192          status: E('#module-status'),
193          progress: E('#module-progress'),
194          spinner: E('#module-spinner')
195        };
196      }
197      if(text === f.last.text) return;
198      f.last.text = text;
199      if(f.ui.progress){
200        f.ui.progress.value = f.last.step;
201        f.ui.progress.max = f.last.step + 1;
202      }
203      ++f.last.step;
204      if(text) {
205        f.ui.status.classList.remove('hidden');
206        f.ui.status.innerText = text;
207      }else{
208        if(f.ui.progress){
209          f.ui.progress.remove();
210          f.ui.spinner.remove();
211          delete f.ui.progress;
212          delete f.ui.spinner;
213        }
214        f.ui.status.classList.add('hidden');
215      }
216    },
217    /**
218       Config options used by the Emscripten-dependent initialization
219       which happens via this.initSqlite3(). This object gets
220       (indirectly) passed to sqlite3ApiBootstrap() to configure the
221       sqlite3 API.
222    */
223    sqlite3ApiConfig: {
224      wasmfsOpfsDir: "/opfs"
225    },
226    /**
227       Intended to be called by apps which need to call the
228       Emscripten-installed sqlite3InitModule() routine. This function
229       temporarily installs this.sqlite3ApiConfig into the self
230       object, calls it sqlite3InitModule(), and removes
231       self.sqlite3ApiConfig after initialization is done. Returns the
232       promise from sqlite3InitModule(), and the next then() handler
233       will get the sqlite3 API object as its argument.
234    */
235    initSqlite3: function(){
236      self.sqlite3ApiConfig = this.sqlite3ApiConfig;
237      return self.sqlite3InitModule(this).finally(()=>delete self.sqlite3ApiConfig);
238    }
239  };
240})(self/*window or worker*/);
241