meadow 2.0.23 → 2.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +110 -141
  2. package/docs/README.md +34 -230
  3. package/docs/_cover.md +14 -0
  4. package/docs/_sidebar.md +44 -12
  5. package/docs/_topbar.md +5 -0
  6. package/docs/api/doCount.md +109 -0
  7. package/docs/api/doCreate.md +132 -0
  8. package/docs/api/doDelete.md +101 -0
  9. package/docs/api/doRead.md +122 -0
  10. package/docs/api/doReads.md +136 -0
  11. package/docs/api/doUndelete.md +98 -0
  12. package/docs/api/doUpdate.md +129 -0
  13. package/docs/api/getRoleName.md +84 -0
  14. package/docs/api/loadFromPackage.md +153 -0
  15. package/docs/api/marshalRecordFromSourceToObject.md +92 -0
  16. package/docs/api/query.md +133 -0
  17. package/docs/api/rawQueries.md +197 -0
  18. package/docs/api/reference.md +117 -0
  19. package/docs/api/setAuthorizer.md +103 -0
  20. package/docs/api/setDefault.md +90 -0
  21. package/docs/api/setDefaultIdentifier.md +84 -0
  22. package/docs/api/setDomain.md +56 -0
  23. package/docs/api/setIDUser.md +91 -0
  24. package/docs/api/setJsonSchema.md +92 -0
  25. package/docs/api/setProvider.md +87 -0
  26. package/docs/api/setSchema.md +107 -0
  27. package/docs/api/setScope.md +68 -0
  28. package/docs/api/validateObject.md +119 -0
  29. package/docs/architecture.md +316 -0
  30. package/docs/audit-tracking.md +226 -0
  31. package/docs/configuration.md +317 -0
  32. package/docs/providers/meadow-endpoints.md +306 -0
  33. package/docs/providers/mongodb.md +319 -0
  34. package/docs/providers/postgresql.md +312 -0
  35. package/docs/providers/rocksdb.md +297 -0
  36. package/docs/query-dsl.md +269 -0
  37. package/docs/quick-start.md +384 -0
  38. package/docs/raw-queries.md +193 -0
  39. package/docs/retold-catalog.json +61 -1
  40. package/docs/retold-keyword-index.json +15860 -4839
  41. package/docs/soft-deletes.md +224 -0
  42. package/package.json +44 -13
  43. package/scripts/bookstore-seed-postgresql.sql +135 -0
  44. package/scripts/dgraph-test-db.sh +144 -0
  45. package/scripts/meadow-test-cleanup.sh +5 -1
  46. package/scripts/mongodb-test-db.sh +98 -0
  47. package/scripts/postgresql-test-db.sh +124 -0
  48. package/scripts/solr-test-db.sh +135 -0
  49. package/source/Meadow.js +5 -0
  50. package/source/providers/Meadow-Provider-DGraph.js +679 -0
  51. package/source/providers/Meadow-Provider-MeadowEndpoints.js +1 -1
  52. package/source/providers/Meadow-Provider-MongoDB.js +527 -0
  53. package/source/providers/Meadow-Provider-PostgreSQL.js +361 -0
  54. package/source/providers/Meadow-Provider-RocksDB.js +1300 -0
  55. package/source/providers/Meadow-Provider-Solr.js +726 -0
  56. package/test/Meadow-Provider-DGraph_tests.js +741 -0
  57. package/test/Meadow-Provider-MongoDB_tests.js +661 -0
  58. package/test/Meadow-Provider-PostgreSQL_tests.js +787 -0
  59. package/test/Meadow-Provider-RocksDB_tests.js +887 -0
  60. package/test/Meadow-Provider-SQLiteBrowser-Headless_tests.js +657 -0
  61. package/test/Meadow-Provider-SQLiteBrowser_tests.js +895 -0
  62. package/test/Meadow-Provider-Solr_tests.js +679 -0
@@ -0,0 +1,657 @@
1
+ /**
2
+ * Headless browser integration tests for the Meadow SQLite provider
3
+ * using meadow-connection-sqlite-browser (sql.js / WASM).
4
+ *
5
+ * Verifies the full Meadow ORM CRUD pipeline works in a real browser:
6
+ * Fable → MeadowConnectionSQLiteBrowser → Meadow DAL → doCreate/doRead/doUpdate/doDelete
7
+ *
8
+ * Loads four bundles in headless Chrome:
9
+ * 1) sql-wasm.js (global initSqlJs)
10
+ * 2) fable.min.js from CDN (global Fable)
11
+ * 3) meadow.js from dist/ (global Meadow)
12
+ * 4) meadow-connection-sqlite-browser.js from connection package dist/ (global MeadowConnectionSqliteBrowser)
13
+ *
14
+ * Requires:
15
+ * - npm run build (so meadow dist/ exists)
16
+ * - meadow-connection-sqlite-browser built (its dist/ must exist)
17
+ * - puppeteer installed
18
+ *
19
+ * @license MIT
20
+ * @author Steven Velozo <steven@velozo.com>
21
+ */
22
+
23
+ var Chai = require('chai');
24
+ var Expect = Chai.expect;
25
+
26
+ var libHTTP = require('http');
27
+ var libFS = require('fs');
28
+ var libPath = require('path');
29
+
30
+ var _MeadowRoot = libPath.resolve(__dirname, '..');
31
+ var _MeadowDistDir = libPath.join(_MeadowRoot, 'dist');
32
+
33
+ // Resolve connection package paths
34
+ var _ConnectionPackageRoot = libPath.resolve(_MeadowRoot, '..', 'meadow-connection-sqlite-browser');
35
+ var _ConnectionDistDir = libPath.join(_ConnectionPackageRoot, 'dist');
36
+ var _SqlJsDistDir = libPath.join(_ConnectionPackageRoot, 'node_modules', 'sql.js', 'dist');
37
+
38
+ // Fable loaded from CDN
39
+ var _FABLE_CDN_URL = 'https://cdn.jsdelivr.net/npm/fable@3/dist/fable.min.js';
40
+
41
+ /**
42
+ * Create a simple HTTP server that serves the static files needed
43
+ * for the browser test page.
44
+ *
45
+ * @param {function} fCallback - Callback with (pError, pServer, pPort)
46
+ */
47
+ function startTestServer(fCallback)
48
+ {
49
+ var tmpMimeTypes =
50
+ {
51
+ '.html': 'text/html',
52
+ '.js': 'application/javascript',
53
+ '.wasm': 'application/wasm',
54
+ '.map': 'application/json'
55
+ };
56
+
57
+ var tmpServer = libHTTP.createServer(
58
+ function(pRequest, pResponse)
59
+ {
60
+ var tmpUrl = pRequest.url;
61
+
62
+ // Route: / → test page
63
+ if (tmpUrl === '/' || tmpUrl === '/index.html')
64
+ {
65
+ pResponse.writeHead(200, { 'Content-Type': 'text/html' });
66
+ pResponse.end(generateTestHTML());
67
+ return;
68
+ }
69
+
70
+ // Route: /sql-wasm.js, /sql-wasm.wasm → from sql.js dist
71
+ if (tmpUrl === '/sql-wasm.js' || tmpUrl === '/sql-wasm.wasm')
72
+ {
73
+ serveFile(libPath.join(_SqlJsDistDir, tmpUrl.slice(1)), pResponse, tmpMimeTypes);
74
+ return;
75
+ }
76
+
77
+ // Route: /meadow.js → from meadow dist/
78
+ if (tmpUrl === '/meadow.js')
79
+ {
80
+ serveFile(libPath.join(_MeadowDistDir, 'meadow.js'), pResponse, tmpMimeTypes);
81
+ return;
82
+ }
83
+
84
+ // Route: /meadow-connection-sqlite-browser.js → from connection dist/
85
+ if (tmpUrl.startsWith('/meadow-connection-sqlite-browser'))
86
+ {
87
+ serveFile(libPath.join(_ConnectionDistDir, tmpUrl.slice(1)), pResponse, tmpMimeTypes);
88
+ return;
89
+ }
90
+
91
+ pResponse.writeHead(404);
92
+ pResponse.end('Not Found');
93
+ });
94
+
95
+ tmpServer.listen(0, '127.0.0.1',
96
+ function()
97
+ {
98
+ var tmpPort = tmpServer.address().port;
99
+ return fCallback(null, tmpServer, tmpPort);
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Serve a static file.
105
+ *
106
+ * @param {string} pFilePath - Absolute path
107
+ * @param {object} pResponse - HTTP response
108
+ * @param {object} pMimeTypes - Extension → MIME type map
109
+ */
110
+ function serveFile(pFilePath, pResponse, pMimeTypes)
111
+ {
112
+ if (!libFS.existsSync(pFilePath))
113
+ {
114
+ pResponse.writeHead(404);
115
+ pResponse.end('File not found: ' + pFilePath);
116
+ return;
117
+ }
118
+
119
+ var tmpExt = libPath.extname(pFilePath);
120
+ var tmpContentType = pMimeTypes[tmpExt] || 'application/octet-stream';
121
+
122
+ var tmpContent = libFS.readFileSync(pFilePath);
123
+ pResponse.writeHead(200, { 'Content-Type': tmpContentType });
124
+ pResponse.end(tmpContent);
125
+ }
126
+
127
+ /**
128
+ * Generate the test HTML page.
129
+ *
130
+ * Exercises the full Meadow CRUD pipeline in the browser:
131
+ * Fable → register MeadowSQLiteProvider → connectAsync → create table →
132
+ * seed data → Meadow.new() → setProvider('SQLite') → doCreate/doRead/
133
+ * doReads/doUpdate/doDelete/doCount
134
+ *
135
+ * @returns {string} HTML content
136
+ */
137
+ function generateTestHTML()
138
+ {
139
+ return `<!DOCTYPE html>
140
+ <html>
141
+ <head><title>Meadow SQLiteBrowser Headless CRUD Tests</title></head>
142
+ <body>
143
+ <h1>Meadow CRUD — Headless Browser</h1>
144
+ <pre id="output">Running tests...</pre>
145
+
146
+ <!-- 1) sql.js WASM loader -->
147
+ <script src="/sql-wasm.js"></script>
148
+
149
+ <!-- 2) Fable from CDN -->
150
+ <script src="${_FABLE_CDN_URL}"></script>
151
+
152
+ <!-- 3) Meadow ORM (includes foxhound + providers) -->
153
+ <script src="/meadow.js"></script>
154
+
155
+ <!-- 4) Browser SQLite connection provider -->
156
+ <script src="/meadow-connection-sqlite-browser.js"></script>
157
+
158
+ <script>
159
+ (async function runTests()
160
+ {
161
+ var results = [];
162
+ var output = document.getElementById('output');
163
+
164
+ function addResult(pName, pPassed, pError)
165
+ {
166
+ results.push({ name: pName, passed: pPassed, error: pError || null });
167
+ output.textContent += '\\n' + (pPassed ? 'PASS' : 'FAIL') + ': ' + pName;
168
+ if (pError) { output.textContent += ' (' + pError + ')'; }
169
+ }
170
+
171
+ // Wrap Meadow callback-style CRUD in promises for sequential await
172
+ function promisify(pFn)
173
+ {
174
+ return function()
175
+ {
176
+ var args = Array.prototype.slice.call(arguments);
177
+ return new Promise(function(resolve, reject)
178
+ {
179
+ args.push(function()
180
+ {
181
+ // Meadow callbacks vary: (err, query, record) or (err, query, queryRead, record)
182
+ var cbArgs = Array.prototype.slice.call(arguments);
183
+ var err = cbArgs[0];
184
+ if (err) { return reject(err); }
185
+ resolve(cbArgs);
186
+ });
187
+ pFn.apply(null, args);
188
+ });
189
+ };
190
+ }
191
+
192
+ try
193
+ {
194
+ // ---- Test 1: Globals available ----
195
+ addResult('globals available',
196
+ typeof Fable === 'function'
197
+ && typeof Meadow === 'object'
198
+ && typeof Meadow.new === 'function'
199
+ && typeof MeadowConnectionSqliteBrowser === 'function'
200
+ && typeof initSqlJs === 'function'
201
+ );
202
+
203
+ // ---- Test 2: Create Fable and register connection ----
204
+ var fable = new Fable({
205
+ Product: 'MeadowBrowserCRUD',
206
+ ProductVersion: '1.0.0',
207
+ LogStreams: [{ streamtype: 'console' }]
208
+ });
209
+ fable.serviceManager.addServiceType('MeadowSQLiteProvider', MeadowConnectionSqliteBrowser);
210
+ var sqliteConn = fable.serviceManager.instantiateServiceProvider('MeadowSQLiteProvider');
211
+ addResult('fable + connection registered',
212
+ fable.isFable === true
213
+ && typeof fable.MeadowSQLiteProvider === 'object'
214
+ );
215
+
216
+ // ---- Test 3: connectAsync ----
217
+ await new Promise(function(resolve, reject)
218
+ {
219
+ sqliteConn.connectAsync(function(pError)
220
+ {
221
+ if (pError) { return reject(pError); }
222
+ addResult('connectAsync succeeded', sqliteConn.connected === true);
223
+ resolve();
224
+ });
225
+ });
226
+
227
+ // ---- Test 4: Create table and seed data ----
228
+ var db = sqliteConn.db;
229
+ db.exec(
230
+ "CREATE TABLE IF NOT EXISTS FableTest (" +
231
+ " IDAnimal INTEGER PRIMARY KEY AUTOINCREMENT," +
232
+ " GUIDAnimal TEXT NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'," +
233
+ " CreateDate TEXT," +
234
+ " CreatingIDUser INTEGER NOT NULL DEFAULT 0," +
235
+ " UpdateDate TEXT," +
236
+ " UpdatingIDUser INTEGER NOT NULL DEFAULT 0," +
237
+ " Deleted INTEGER NOT NULL DEFAULT 0," +
238
+ " DeleteDate TEXT," +
239
+ " DeletingIDUser INTEGER NOT NULL DEFAULT 0," +
240
+ " Name TEXT NOT NULL DEFAULT ''," +
241
+ " Type TEXT NOT NULL DEFAULT ''" +
242
+ ");"
243
+ );
244
+ var ins = db.prepare(
245
+ "INSERT INTO FableTest (GUIDAnimal, CreateDate, CreatingIDUser, UpdateDate, UpdatingIDUser, Deleted, Name, Type) " +
246
+ "VALUES ('00000000-0000-0000-0000-000000000000', datetime('now'), 1, datetime('now'), 1, 0, ?, ?)"
247
+ );
248
+ ins.run('Foo Foo', 'Bunny');
249
+ ins.run('Red Riding Hood', 'Girl');
250
+ ins.run('Red', 'Dog');
251
+ ins.run('Spot', 'Dog');
252
+ ins.run('Gertrude', 'Frog');
253
+
254
+ var seedCheck = db.prepare('SELECT COUNT(*) AS cnt FROM FableTest').get();
255
+ addResult('table created and seeded', seedCheck.cnt === 5);
256
+
257
+ // ---- Set up Meadow DAL ----
258
+ var animalSchema = [
259
+ { Column: 'IDAnimal', Type: 'AutoIdentity' },
260
+ { Column: 'GUIDAnimal', Type: 'AutoGUID' },
261
+ { Column: 'CreateDate', Type: 'CreateDate' },
262
+ { Column: 'CreatingIDUser', Type: 'CreateIDUser' },
263
+ { Column: 'UpdateDate', Type: 'UpdateDate' },
264
+ { Column: 'UpdatingIDUser', Type: 'UpdateIDUser' },
265
+ { Column: 'Deleted', Type: 'Deleted' },
266
+ { Column: 'DeletingIDUser', Type: 'DeleteIDUser' },
267
+ { Column: 'DeleteDate', Type: 'DeleteDate' }
268
+ ];
269
+ var animalJsonSchema = {
270
+ title: 'Animal',
271
+ description: 'A creature that lives in a meadow.',
272
+ type: 'object',
273
+ properties: {
274
+ IDAnimal: { description: 'The unique identifier for an animal', type: 'integer' },
275
+ Name: { description: "The animal's name", type: 'string' },
276
+ Type: { description: 'The type of the animal', type: 'string' }
277
+ },
278
+ required: ['IDAnimal', 'Name', 'CreatingIDUser']
279
+ };
280
+ var animalDefault = {
281
+ IDAnimal: null, GUIDAnimal: '',
282
+ CreateDate: false, CreatingIDUser: 0,
283
+ UpdateDate: false, UpdatingIDUser: 0,
284
+ Deleted: 0, DeleteDate: false, DeletingIDUser: 0,
285
+ Name: 'Unknown', Type: 'Unclassified'
286
+ };
287
+
288
+ function newMeadow()
289
+ {
290
+ return Meadow.new(fable, 'FableTest')
291
+ .setProvider('SQLite')
292
+ .setSchema(animalSchema)
293
+ .setJsonSchema(animalJsonSchema)
294
+ .setDefaultIdentifier('IDAnimal')
295
+ .setDefault(animalDefault);
296
+ }
297
+
298
+ // ---- Test 5: doCreate ----
299
+ var dal = newMeadow().setIDUser(90210);
300
+ var createResult = await new Promise(function(resolve, reject)
301
+ {
302
+ var q = dal.query.clone().addRecord({ Name: 'Blastoise', Type: 'Pokemon' });
303
+ dal.doCreate(q, function(pError, pQuery, pQueryRead, pRecord)
304
+ {
305
+ if (pError) { return reject(pError); }
306
+ resolve(pRecord);
307
+ });
308
+ });
309
+ addResult('doCreate',
310
+ createResult.Name === 'Blastoise'
311
+ && createResult.CreatingIDUser === 90210
312
+ && typeof createResult.IDAnimal === 'number'
313
+ );
314
+
315
+ // ---- Test 6: doRead ----
316
+ dal = newMeadow();
317
+ var readResult = await new Promise(function(resolve, reject)
318
+ {
319
+ var q = dal.query.addFilter('IDAnimal', 1);
320
+ dal.doRead(q, function(pError, pQuery, pRecord)
321
+ {
322
+ if (pError) { return reject(pError); }
323
+ resolve(pRecord);
324
+ });
325
+ });
326
+ addResult('doRead',
327
+ readResult.IDAnimal === 1
328
+ && readResult.Name === 'Foo Foo'
329
+ );
330
+
331
+ // ---- Test 7: doReads ----
332
+ dal = newMeadow();
333
+ var readsResult = await new Promise(function(resolve, reject)
334
+ {
335
+ dal.doReads(dal.query, function(pError, pQuery, pRecords)
336
+ {
337
+ if (pError) { return reject(pError); }
338
+ resolve(pRecords);
339
+ });
340
+ });
341
+ addResult('doReads',
342
+ Array.isArray(readsResult)
343
+ && readsResult.length >= 5
344
+ && readsResult[0].Name === 'Foo Foo'
345
+ && readsResult[1].Name === 'Red Riding Hood'
346
+ );
347
+
348
+ // ---- Test 8: doUpdate ----
349
+ dal = newMeadow();
350
+ var updateResult = await new Promise(function(resolve, reject)
351
+ {
352
+ var q = dal.query.addRecord({ IDAnimal: 2, Type: 'Human' });
353
+ dal.doUpdate(q, function(pError, pQuery, pQueryRead, pRecord)
354
+ {
355
+ if (pError) { return reject(pError); }
356
+ resolve(pRecord);
357
+ });
358
+ });
359
+ addResult('doUpdate',
360
+ updateResult.Type === 'Human'
361
+ && updateResult.IDAnimal === 2
362
+ );
363
+
364
+ // ---- Test 9: doDelete ----
365
+ dal = newMeadow();
366
+ var deleteResult = await new Promise(function(resolve, reject)
367
+ {
368
+ var q = dal.query.addFilter('IDAnimal', 3);
369
+ dal.doDelete(q, function(pError, pQuery, pRecord)
370
+ {
371
+ if (pError) { return reject(pError); }
372
+ resolve(pRecord);
373
+ });
374
+ });
375
+ addResult('doDelete', deleteResult === 1);
376
+
377
+ // ---- Test 10: doUndelete ----
378
+ dal = newMeadow();
379
+ // First delete #5
380
+ await new Promise(function(resolve, reject)
381
+ {
382
+ var q = dal.query.addFilter('IDAnimal', 5);
383
+ dal.doDelete(q, function(pError) { pError ? reject(pError) : resolve(); });
384
+ });
385
+ dal = newMeadow();
386
+ var undeleteResult = await new Promise(function(resolve, reject)
387
+ {
388
+ var q = dal.query.addFilter('IDAnimal', 5);
389
+ dal.doUndelete(q, function(pError, pQuery, pRecord)
390
+ {
391
+ if (pError) { return reject(pError); }
392
+ resolve(pRecord);
393
+ });
394
+ });
395
+ addResult('doUndelete', undeleteResult === 1);
396
+
397
+ // ---- Test 11: doCount ----
398
+ dal = newMeadow();
399
+ var countResult = await new Promise(function(resolve, reject)
400
+ {
401
+ dal.doCount(dal.query, function(pError, pQuery, pRecord)
402
+ {
403
+ if (pError) { return reject(pError); }
404
+ resolve(pRecord);
405
+ });
406
+ });
407
+ addResult('doCount',
408
+ typeof countResult === 'number'
409
+ && countResult >= 4
410
+ );
411
+
412
+ // ---- Test 12: doCreate with predefined GUID ----
413
+ dal = newMeadow();
414
+ var guidResult = await new Promise(function(resolve, reject)
415
+ {
416
+ var q = dal.query.clone().addRecord({
417
+ Name: 'MewThree', GUIDAnimal: '0xBROWSER123', Type: 'Pokemon'
418
+ });
419
+ dal.doCreate(q, function(pError, pQuery, pQueryRead, pRecord)
420
+ {
421
+ if (pError) { return reject(pError); }
422
+ resolve(pRecord);
423
+ });
424
+ });
425
+ addResult('doCreate with predefined GUID',
426
+ guidResult.Name === 'MewThree'
427
+ && guidResult.GUIDAnimal === '0xBROWSER123'
428
+ );
429
+
430
+ // ---- Test 13: doRead by GUID ----
431
+ dal = newMeadow();
432
+ var guidReadResult = await new Promise(function(resolve, reject)
433
+ {
434
+ var q = dal.query.addFilter('GUIDAnimal', '0xBROWSER123');
435
+ dal.doRead(q, function(pError, pQuery, pRecord)
436
+ {
437
+ if (pError) { return reject(pError); }
438
+ resolve(pRecord);
439
+ });
440
+ });
441
+ addResult('doRead by GUID',
442
+ guidReadResult.Name === 'MewThree'
443
+ );
444
+
445
+ // ---- Test 14: doRead with no match returns false ----
446
+ dal = newMeadow();
447
+ var noMatchResult = await new Promise(function(resolve, reject)
448
+ {
449
+ var q = dal.query.addFilter('IDAnimal', 99999);
450
+ dal.doRead(q, function(pError, pQuery, pRecord)
451
+ {
452
+ if (pError) { return reject(pError); }
453
+ resolve(pRecord);
454
+ });
455
+ });
456
+ addResult('doRead no match returns false', noMatchResult === false);
457
+ }
458
+ catch (pError)
459
+ {
460
+ addResult('unexpected error', false, pError.message || String(pError));
461
+ }
462
+
463
+ window.__TEST_RESULTS__ = results;
464
+ window.__TESTS_COMPLETE__ = true;
465
+ output.textContent += '\\n\\nDone: '
466
+ + results.filter(function(r) { return r.passed; }).length + '/'
467
+ + results.length + ' passed';
468
+ })();
469
+ </script>
470
+ </body>
471
+ </html>`;
472
+ }
473
+
474
+ suite
475
+ (
476
+ 'Meadow-Provider-SQLiteBrowser-Headless',
477
+ function()
478
+ {
479
+ this.timeout(60000);
480
+
481
+ var _Server;
482
+ var _Port;
483
+ var _Browser;
484
+ var _Puppeteer;
485
+
486
+ suiteSetup
487
+ (
488
+ function(fDone)
489
+ {
490
+ // Verify meadow dist exists
491
+ if (!libFS.existsSync(libPath.join(_MeadowDistDir, 'meadow.js')))
492
+ {
493
+ return fDone(new Error(
494
+ 'dist/meadow.js not found. Run "npm run build" in the meadow package first.'
495
+ ));
496
+ }
497
+
498
+ // Verify connection package dist exists
499
+ if (!libFS.existsSync(libPath.join(_ConnectionDistDir, 'meadow-connection-sqlite-browser.js')))
500
+ {
501
+ return fDone(new Error(
502
+ 'meadow-connection-sqlite-browser dist not found. Run "npm run build" in that package first.'
503
+ ));
504
+ }
505
+
506
+ // Verify sql.js dist exists
507
+ if (!libFS.existsSync(libPath.join(_SqlJsDistDir, 'sql-wasm.js')))
508
+ {
509
+ return fDone(new Error(
510
+ 'sql.js dist files not found. Run "npm install" in meadow-connection-sqlite-browser first.'
511
+ ));
512
+ }
513
+
514
+ // Start the test server
515
+ startTestServer(
516
+ function(pError, pServer, pPort)
517
+ {
518
+ if (pError)
519
+ {
520
+ return fDone(pError);
521
+ }
522
+ _Server = pServer;
523
+ _Port = pPort;
524
+
525
+ try
526
+ {
527
+ _Puppeteer = require('puppeteer');
528
+ }
529
+ catch (pRequireError)
530
+ {
531
+ _Server.close();
532
+ return fDone(new Error(
533
+ 'puppeteer is not installed. Add it as a devDependency and run npm install.'
534
+ ));
535
+ }
536
+
537
+ return fDone();
538
+ });
539
+ }
540
+ );
541
+
542
+ suiteTeardown
543
+ (
544
+ function(fDone)
545
+ {
546
+ var tmpSteps = [];
547
+
548
+ if (_Browser)
549
+ {
550
+ tmpSteps.push(_Browser.close().catch(function() {}));
551
+ }
552
+
553
+ Promise.all(tmpSteps).then(
554
+ function()
555
+ {
556
+ if (_Server)
557
+ {
558
+ _Server.close(fDone);
559
+ }
560
+ else
561
+ {
562
+ fDone();
563
+ }
564
+ });
565
+ }
566
+ );
567
+
568
+ test
569
+ (
570
+ 'Full Meadow CRUD pipeline works in headless Chrome',
571
+ function(fDone)
572
+ {
573
+ _Puppeteer.launch(
574
+ {
575
+ headless: true,
576
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
577
+ })
578
+ .then(
579
+ function(pBrowser)
580
+ {
581
+ _Browser = pBrowser;
582
+ return _Browser.newPage();
583
+ })
584
+ .then(
585
+ function(pPage)
586
+ {
587
+ pPage.on('console',
588
+ function(pMessage)
589
+ {
590
+ if (pMessage.type() === 'error')
591
+ {
592
+ console.log(' [browser error]', pMessage.text());
593
+ }
594
+ });
595
+
596
+ pPage.on('pageerror',
597
+ function(pError)
598
+ {
599
+ console.log(' [browser page error]', pError.message);
600
+ });
601
+
602
+ return pPage.goto('http://127.0.0.1:' + _Port + '/', { waitUntil: 'networkidle0', timeout: 30000 })
603
+ .then(function() { return pPage; });
604
+ })
605
+ .then(
606
+ function(pPage)
607
+ {
608
+ return pPage.waitForFunction(
609
+ 'window.__TESTS_COMPLETE__ === true',
610
+ { timeout: 30000 }
611
+ ).then(function() { return pPage; });
612
+ })
613
+ .then(
614
+ function(pPage)
615
+ {
616
+ return pPage.evaluate(function()
617
+ {
618
+ return window.__TEST_RESULTS__;
619
+ });
620
+ })
621
+ .then(
622
+ function(pResults)
623
+ {
624
+ Expect(pResults).to.be.an('array');
625
+ Expect(pResults.length).to.be.above(0);
626
+
627
+ var tmpFailures = [];
628
+
629
+ for (var i = 0; i < pResults.length; i++)
630
+ {
631
+ if (!pResults[i].passed)
632
+ {
633
+ tmpFailures.push(
634
+ pResults[i].name + (pResults[i].error ? ': ' + pResults[i].error : '')
635
+ );
636
+ }
637
+ }
638
+
639
+ if (tmpFailures.length > 0)
640
+ {
641
+ Expect.fail(
642
+ 'Browser CRUD tests failed:\n - ' + tmpFailures.join('\n - ')
643
+ );
644
+ }
645
+
646
+ console.log(' ' + pResults.length + ' browser CRUD sub-tests passed');
647
+ fDone();
648
+ })
649
+ .catch(
650
+ function(pError)
651
+ {
652
+ fDone(pError);
653
+ });
654
+ }
655
+ );
656
+ }
657
+ );