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