solarite 0.2.1 → 0.2.2

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 (38) hide show
  1. package/package.json +6 -5
  2. package/benchmarks/naive/Solarite.min.js +0 -4
  3. package/benchmarks/naive/index.html +0 -14
  4. package/benchmarks/naive/main.js +0 -339
  5. package/benchmarks/naive/package-lock.json +0 -13
  6. package/benchmarks/naive/package.json +0 -23
  7. package/benchmarks/readme.md +0 -48
  8. package/build/build.bat +0 -4
  9. package/build/build.js +0 -139
  10. package/build/lib/rollup.min.js +0 -11
  11. package/build/lib/source-map.min.js +0 -1
  12. package/build/lib/terser.min.js +0 -1
  13. package/docs/index.md +0 -883
  14. package/docs/js/Playground.js +0 -184
  15. package/docs/js/codemirror/codemirror6.js +0 -32513
  16. package/docs/js/codemirror/themeSolarIce.js +0 -312
  17. package/docs/js/documentation.js +0 -32
  18. package/docs/js/ui/CodeEditor.js +0 -978
  19. package/docs/js/ui/DarkToggle.js +0 -52
  20. package/docs/js/ui/FlexResizer.js +0 -152
  21. package/docs/js/util/Draggable2.js +0 -151
  22. package/docs/js/util/Errors.js +0 -20
  23. package/docs/js/util/Html.js +0 -147
  24. package/docs/js/util/Icons.js +0 -623
  25. package/docs/js/util/Input.js +0 -253
  26. package/docs/js/util/Util.js +0 -88
  27. package/docs/js/util/delve.js +0 -43
  28. package/docs/media/FiraCode400.woff2 +0 -0
  29. package/docs/media/cabin-latin-700.woff2 +0 -0
  30. package/docs/media/documentation.css +0 -96
  31. package/docs/media/eternium.css +0 -1123
  32. package/docs/media/solarite-machine.webp +0 -0
  33. package/index.html +0 -761
  34. package/tests/Benchmark.test.js +0 -319
  35. package/tests/Solarite.test.js +0 -3838
  36. package/tests/Testimony.js +0 -809
  37. package/tests/index.html +0 -46
  38. package/tests/run.bat +0 -2
@@ -1,809 +0,0 @@
1
- /**
2
- * Provide functionality for running Deno tests in a web browser.
3
- * Has no external dependencies.
4
- *
5
- * TODO:
6
- * Make a Test interface and a Test web component.
7
- * That way we can render and run the same test object separately.
8
- *
9
- *
10
- * 4. Integrate with IntelliJ file watcher so we run cmd line tests when files change.
11
- * 5. Run tests from @expect doc tags.
12
- * 6. Documentation - Web tests, deno tests, intellij integration
13
- * 7. Add to github.
14
- * 8. Command line via node
15
- * 9. Support other Deno options.
16
- * 11. URLs only mark which tests to include or exclude, to make url shorter
17
- * 12. Auto-expand to failed tests.
18
- */
19
-
20
- class AssertError extends Error {
21
- constructor(expected, actual, op) {
22
- super('Assertion Failed');
23
- this.name = "AssertError";
24
- this.expected = expected;
25
- this.actual = actual;
26
- this.op = op;
27
- }
28
- }
29
-
30
- function assert(val) {
31
- if (!val) {
32
- if (Testimony.debugOnAssertFail)
33
- debugger;
34
- throw new AssertError(val, true);
35
- }
36
- }
37
-
38
- Object.assign(assert, {
39
- eq(expected, actual) {
40
- if (!isSame(expected, actual)) { // JUnit, PhpUnit, and mocha all use the order: expected, actual.
41
- if (Testimony.debugOnAssertFail)
42
- debugger;
43
- throw new AssertError(expected, actual, '==');
44
- }
45
- },
46
-
47
- eqJson(expected, actual) {
48
- if (JSON.stringify(actual) !== JSON.stringify(expected)) {
49
- if (Testimony.debugOnAssertFail)
50
- debugger;
51
- throw new AssertError(expected, actual);
52
- }
53
- },
54
-
55
- neq(val1, val2) {
56
- if (val1 !== val2) {
57
- if (Testimony.debugOnAssertFail)
58
- debugger;
59
- throw new AssertError(val1 + ' === ' + val2);
60
- }
61
- },
62
-
63
- lte(val1, val2) {
64
- if (val1 > val2) {
65
- if (Testimony.debugOnAssertFail)
66
- debugger;
67
- throw new AssertError(val1 + ' > ' + val2);
68
- }
69
- }
70
- });
71
-
72
-
73
-
74
- /**
75
- * https://stackoverflow.com/a/6713782/
76
- * @param x
77
- * @param y
78
- * @return {boolean} */
79
- function isSame( x, y ) {
80
- if (x === y)
81
- return true; // if both x and y are null or undefined and exactly the same
82
-
83
- // if they are not strictly equal, they both need to be Objects
84
- // they must have the exact same prototype chain, the closest we can do is
85
- // test their constructor.
86
- if (!(x instanceof Object) || !(y instanceof Object) || x.constructor !== y.constructor)
87
- return false;
88
-
89
- for (var p in x) {
90
- if (!x.hasOwnProperty(p))
91
- continue; // other properties were tested using x.constructor === y.constructor
92
-
93
- if (!y.hasOwnProperty(p))
94
- return false; // allows to compare x[ p ] and y[ p ] when set to undefined
95
-
96
- if (x[p] === y[p])
97
- continue; // if they have the same strict value or identity then they are equal
98
-
99
- if (typeof x[p] !== "object" || !isSame(x[p], y[p])) // Numbers, Strings, Functions, Booleans must be strictly equal
100
- return false; // Objects and Arrays must be tested recursively
101
- }
102
-
103
- for (p in y) // allows x[ p ] to be set to undefined
104
- if (y.hasOwnProperty(p) && !x.hasOwnProperty(p))
105
- return false;
106
-
107
- return true;
108
- }
109
-
110
- let styleId = 1;
111
-
112
- /**
113
- * Create a single html element, node, or comment from the html string.
114
- * The string will be trimmed so that an element with space before it doesn't create a text node with spaces.
115
- * TODO: Allow specifying an existing div as props to bind a new set of html to it.
116
- * This could be used for manually adding children.
117
- * TODO: Get values and setValues() functions for form fields? Can make it work with any html element.
118
- * TODO: Bind this to render() so I can render ${this.someField}
119
- * TODO: Make properties, ids, events, and scoped styles only happen if a props argument is passed in, or is not false?
120
- *
121
- * I could use name="items[].name" to specify json structure of the result?
122
- * OR:
123
- *
124
- * <div name="item[]">
125
- * <input name="description">
126
- * </div>
127
- *
128
- * Will give me item[0].description
129
- *
130
- *
131
- * @param html {string|function} Must be a function that returns html if you want to call render() again later.
132
- * @param doc {Document}
133
- * @param props {Object|boolean} */
134
- function createEl(html, props=false, doc=document) {
135
- let template = doc.createElement('template');
136
- let result = doc.createElement((html+'').match(/<(\w+[\w-]*)/)[1]);
137
- let ids; // Track them so they can be removed later.
138
-
139
- // Properties
140
- if (typeof props === 'object')
141
- for (let name in props) {
142
- if (name in result)
143
- throw new Error(`Property ${name} already exists.`);
144
- result[name] = props[name];
145
- }
146
-
147
- // Render
148
- result.render = () => {
149
- template.innerHTML = (typeof html === 'function' ? html() : html).trim();
150
-
151
- // Attributes and Children
152
- if (props) {
153
- result.innerHTML = '';
154
- [...result.attributes].map(attr => result.removeAttribute(attr.name));
155
- }
156
- [...template.content.firstElementChild.attributes].map(attr => result.setAttribute(attr.name, attr.value));
157
- [...template.content.firstElementChild.childNodes].map(child => result.append(child));
158
-
159
- if (props) {
160
- // Assign ids
161
- (ids || []).map(id => delete result[id]);
162
- ids = [...result.querySelectorAll('[id],[data-id]')].map(el => {
163
- if (el.dataset.id in result && !(el.dataset.id in props)) // allow id's to override our custom props.
164
- throw new Error(`Property ${el.dataset.id} already exists.`);
165
- result[el.dataset.id] = el;
166
- return [el.dataset.id];
167
- });
168
-
169
- // Bind events
170
- [result, ...result.querySelectorAll('*')].map(el =>
171
- [...el.attributes].filter(attr => attr.name.startsWith('on')).map(attr => {
172
- el[attr.name] = e => // e.g. el.onclick = ...
173
- (new Function('event', 'el', attr.value)).bind(result)(e, el) // put "event", "el", and "this" in scope for the event code.
174
- })
175
- );
176
-
177
- // Scoped Styles
178
- let styles = result.querySelectorAll('style');
179
- if (styles.length) {
180
- result.setAttribute('data-style-scope', styleId++); // TODO: re-use style id on re-render.
181
- [...styles].map(style => style.textContent = style.textContent.replace(/:host(?=[^a-z\d_])/gi,
182
- '[data-style-scope="' + result.getAttribute('data-style-scope') + '"]'));
183
- }
184
- }
185
- }
186
-
187
- if (typeof result.init === 'function')
188
- result.init();
189
- if (!ids) // if not called by init()
190
- result.render();
191
-
192
- return result;
193
- }
194
-
195
- // Html.encode()
196
- function h(text, quotes='"') {
197
- text = (text === null || text === undefined ? '' : text+'')
198
- .replace(/&/g, '&amp;')
199
- .replace(/</g, '&lt;')
200
- .replace(/>/g, '&gt;')
201
- .replace(/\a0/g, '&nbsp;')
202
- if (quotes.includes("'"))
203
- text = text.replace(/'/g, '&apos;');
204
- if (quotes.includes('"'))
205
- text = text.replace(/"/g, '&quot;');
206
- return text;
207
- }
208
-
209
- var HtmlRenderer = {
210
-
211
- /**
212
- * @param test {Test}
213
- * @return {HTMLDivElement} */
214
- render(test) {
215
-
216
- // If updating
217
- if (test.el) {
218
- test.el.render();
219
- for (let t2 of Object.values(test.children || {}))
220
- test.el.childTests.append(HtmlRenderer.render(t2));
221
- return test.el;
222
- }
223
- else {
224
-
225
- let result = createEl(() => `
226
- <div class="test">
227
- <style>
228
- :host input[type=checkbox] { width: 7.7px; appearance: none; margin: 0; color: inherit }
229
- :host [data-id=expandCB]:after { content: '+'; user-select: none; cursor: pointer }
230
- :host [data-id=expandCB]:checked:after { content: '-' }
231
- :host [data-id=enableCB]:after { content: '\xa0'; color: #55f; font-weight: bold; text-shadow: 1px 0 0 #55f }
232
- :host [data-id=enableCB]:checked:after { content: 'x' }
233
- :host [data-id=status] { line-height: 1; display: inline-block; width: 7.7px }
234
- :host [data-id=childTests] { padding-left: ${test.name.length ? '38.5' : '15.4'}px }
235
- </style>
236
- <div>
237
- ${test.name.length ?
238
- Object.values(test.children || {}).length
239
- ? `<input data-id="expandCB" type="checkbox" name="x" ${test.expanded ? 'checked' : ''}
240
- value="${h(test.name) || ''}" onchange="this.expandClick()">`
241
- : `&nbsp;`
242
- : ''
243
- }
244
- <label>
245
- [<input data-id="enableCB" type="checkbox" name="r" value="${h(test.name) || ''}"
246
- ${(test.getShortName()[0] === '_') ? 'data-disabled' : ''}
247
- ${test.enabled ? 'checked' : ''}
248
- onchange="this.enableClick()">]
249
- <span data-id="status">${
250
- test.status === true ? `<span style="color: #0f0">✓</span>` :
251
- test.status === null ? `&nbsp;` :
252
- `<span style="color: red">✗</span>`
253
- }</span>
254
- ${h(test.getShortName())}
255
- </label>
256
- <span style="opacity: .5">${h(test.desc) || ''}</span>
257
- <div style="color: red; padding-left: 61.6px">${test.status instanceof Error ? Testimony.shortenError(test.status) : ''}</div>
258
- </div>
259
- <div data-id="childTests" ${test.expanded ? '' : 'style="display: none"'}></div>
260
- </div>`, {
261
-
262
- test,
263
-
264
- init() {
265
- test.el = this;
266
- },
267
-
268
- enableClick() {
269
- // Check all children if this is checked.
270
- [...this.childTests.querySelectorAll('[name=r]:not([data-disabled])')].map(cb => cb.checked = this.enableCB.checked);
271
-
272
- // Make every parent checked if all its children are checked.
273
- let p = this;
274
- while (p = p.parentNode)
275
- if (p.nodeType === 1 && p.matches('.test'))
276
- p.querySelector('[name=r]').checked = ![...p.childTests.querySelectorAll('[name=r]:not([data-disabled])')].find(cb => !cb.checked);
277
- },
278
-
279
- expandClick() {
280
- this.childTests.style.display = this.expandCB.checked ? '': 'none';
281
- }
282
- });
283
-
284
- // Recursively add child tests
285
- if (test.children)
286
- for (let test2 of Object.values(test.children))
287
- result.childTests.append(HtmlRenderer.render(test2));
288
-
289
- return result;
290
- }
291
- }
292
- }
293
-
294
- // Not used.
295
- var TextRenderer = {
296
-
297
- render(test) {
298
- let result = [];
299
-
300
- // If not the root node:
301
- if (test.name) {
302
-
303
- // Color codes: https://stackoverflow.com/a/41407246
304
- let status = ' ';
305
- if (test.status === null)
306
- status = ' ';
307
- else if (test.status === true)
308
- status = '\x1b[1;32m' + '✓' + '\x1b[0m'; // green, 1; for bold
309
- else
310
- status = '\x1b[1;31m' + '✗' + '\x1b[0m'; // red
311
-
312
- let result2 = ' '.repeat(test.getDepth()) + `[${status}] ${test.getShortName() || ''}`;
313
- if (test.desc)
314
- result2 += ` \x1b[90m${test.desc}\x1b[0m`; // gray
315
- if (test.status instanceof Error)
316
- result2 += ' \x1b[31m' + Testimony.shortenError(test.status, '\n ') + '\x1b[0m';
317
-
318
- result.push(result2);
319
- }
320
-
321
- // Recurse through children.
322
- if (test.children)
323
- for (let test2 of Object.values(test.children))
324
- result.push(TextRenderer.render(test2));
325
-
326
- return result.join('\n');
327
- }
328
- }
329
-
330
-
331
- class Test {
332
- name;
333
- desc;
334
- expanded;
335
- enabled;
336
-
337
- /**
338
- * @type {boolean|Error|null}
339
- * true: Test passed or all non-disabled child tests passed
340
- * false: One or more child tests failed.
341
- * Error: Test failed.
342
- * null: Hasn't been run yet. */
343
- status = null;
344
- /**
345
- * Every test will have either a fn OR children.
346
- * @type {?function} */
347
- fn = null;
348
-
349
- /**
350
- * @type {?Object<name:string, Test>} */
351
- children = null;
352
-
353
- constructor(name='', desc='', fn) {
354
- this.name = name;
355
- this.desc = desc;
356
- this.fn = fn;
357
-
358
- if (window.location) {
359
- let url = new URL(window.location);
360
- this.expanded = url.searchParams.getAll('x').includes(name);
361
-
362
- // Enabled if this or a parent is checked
363
- if (this.enabled === undefined && !this.getShortName().startsWith('_')) { // if not otherwise set, set it from url:
364
- if (url.searchParams.has('allTests'))
365
- this.enabled = true;
366
-
367
- else {
368
-
369
- let r = url.searchParams.getAll('r');
370
- let parent = name;
371
- do {
372
-
373
- // Enable test if a parent is enabled.
374
- if (r.includes(parent)) {
375
- this.enabled = true;
376
- break;
377
- }
378
- parent = parent.split('.').slice(0, -1).join('.')
379
- } while (parent);
380
- }
381
- }
382
- }
383
- else // TODO: Get enabled tests from the command line enable arguments.
384
- this.enabled = true;
385
- }
386
-
387
- /**
388
- * Run this test or its children. */
389
- async run() {
390
-
391
- // A test to run.
392
- if (this.fn && this.enabled) {
393
- let result = true;
394
- if (Testimony.throwOnError) {
395
- result = this.fn();
396
- if (result instanceof Promise)
397
- result = await result;
398
- if (result !== false)
399
- this.status = true;
400
- } else {
401
- try {
402
- result = await this.fn();
403
- if (result instanceof Promise)
404
- await result;
405
- if (result !== false)
406
- this.status = true;
407
- } catch (e) {
408
- console.log(e)
409
- Testimony.failedTests.push([this.name, Testimony.shortenError(e, '\n')]);
410
- this.status = e;
411
- }
412
- }
413
- }
414
-
415
- // A node containing other tests.
416
- if (!this.fn) {
417
- // TODO: Run in parallel?
418
- this.status = true;
419
- let hasPassingChild = false;
420
- for (let child of Object.values(this.children)) {
421
- await child.run();
422
- if (child.enabled) {
423
- if (child.status === false || child.status instanceof Error)
424
- this.status = false;
425
-
426
- // else if (child.status === null && this.status !== false)
427
- // this.status = null;
428
-
429
- else if (child.status === true)
430
- hasPassingChild = true;
431
- }
432
- }
433
-
434
- // Must have at least one child green checkmark to have a green checkmark.
435
- if (this.status === true && !hasPassingChild)
436
- this.status = null;
437
- }
438
-
439
- return this.status;
440
- }
441
-
442
- /**
443
- * Set the enabled status of this test and its children, checking their checkbox.
444
- * @param tests {string[]} Names of tests.
445
- * @param enabled {boolean} */
446
- setEnabled(tests, enabled) {
447
- if (!tests || tests.includes(this.name))
448
- this.enabled = enabled;
449
-
450
- for (let childName in this.children || {}) {
451
- let child = this.children[childName];
452
- if (!childName.startsWith('_'))
453
- child.setEnabled(tests, enabled);
454
- }
455
- }
456
-
457
- /**
458
- * The top level test returns a depth of 0.
459
- * @returns {int} */
460
- getDepth() {
461
- return ((this.name || '').match(/\./g) || []).length;
462
- }
463
-
464
- /**
465
- * Get the name after the last dot.
466
- * @returns {string} */
467
- getShortName() {
468
- return /[^.]*$/.exec(this.name)[0];
469
- }
470
- }
471
-
472
- var Testimony = {
473
-
474
- debugOnAssertFail: false,
475
- throwOnError: false, // throw from original location on assert fail or error.
476
- expandLevel: 1,
477
-
478
- /** @type {Test} */
479
- rootTest: new Test(),
480
-
481
-
482
-
483
-
484
- finished: false,
485
-
486
- /**
487
- * A map from the test name to the error.
488
- * @type {[string, string][]} */
489
- failedTests: [],
490
-
491
-
492
-
493
- /**
494
- *
495
- * @param tests {?string[]} Test names. E.g. ['Main.one', 'Main.two']. If null, apply to all tests that are not disabled.
496
- * @param enabled {boolean} */
497
- setTestsEnabled(tests, enabled) {
498
- this.rootTest.setEnabled(tests, enabled);
499
- },
500
-
501
- /**
502
- * Run the root test and any of the root tests children.
503
- * TODO: Separate rendering from running.
504
- * @param parent {?HTMLElement}
505
- * @returns {Promise<[string, Error][]>}
506
- */
507
- async run(parent) {
508
- this.failedTests = []; // resets
509
- let renderer = parent ? HtmlRenderer : TextRenderer;
510
-
511
- if (parent) {
512
- // Expand recursively
513
- function doExpand(test, expand) {
514
- if (expand) {
515
- test.expanded = true;
516
- for (let child of Object.values(test.children || {}))
517
- doExpand(child, expand - 1);
518
- }
519
- }
520
-
521
- let hasXParam = new URL(location).searchParams.getAll('x').length;
522
- doExpand(Testimony.rootTest, hasXParam ? 1 : this.expandLevel);
523
-
524
- // Render empty tests
525
- parent.append(renderer.render(Testimony.rootTest));
526
- await new Promise(r => setTimeout(r, 1)); // allow browser to render.
527
-
528
- // Run tests
529
- await Testimony.rootTest.run();
530
-
531
- // Update the status.
532
- renderer.render(Testimony.rootTest);
533
- }
534
-
535
- // Command line
536
- else {
537
- await Testimony.rootTest.run();
538
- console.log(renderer.render(Testimony.rootTest));
539
- }
540
-
541
-
542
- this.finished = true;
543
- return this.failedTests;
544
- },
545
-
546
- /**
547
- * Requires Deno and a regular Chrome installation.
548
- * @param page {string}
549
- * @param webRoot {?string}
550
- * @param tests {?string[]}
551
- * @param headless {boolean}
552
- * @param port {int} Defaults to 8004 to not conflict with commonly used development ports like 8000 or 8080.
553
- * @returns {Promise<void>} */
554
- async runPage(page, webRoot=null, tests=null, headless=false, port=8004) {
555
-
556
- /*
557
- import puppeteer from 'https://esm.sh/puppeteer@13.0.0';
558
- import { serve } from 'https://deno.land/std/http/server.ts';
559
- import { serveFile } from 'https://deno.land/std@0.102.0/http/file_server.ts';
560
- import { Launcher } from 'https://esm.sh/chrome-launcher@0.15.0';
561
- */
562
-
563
- // Dynamically import so we only pull them in if necessary.
564
- const [
565
- {default: puppeteer},
566
- {Launcher},
567
- {serve},
568
- {serveFile},
569
- ] = await Promise.all([
570
- import('https://deno.land/x/puppeteer@16.2.0/mod.ts'),
571
- import('https://esm.sh/chrome-launcher@0.15.0'),
572
- import('https://deno.land/std@0.102.0/http/server.ts'),
573
- import('https://deno.land/std@0.102.0/http/file_server.ts')
574
- ]);
575
-
576
- const startServer = () => {
577
-
578
- const absWebRoot = Deno.realPathSync(webRoot);
579
- const server = serve({port: 8004});
580
- //console.log("HTTP web server running. Access it at: http://localhost:8000/");
581
-
582
- (async () => {
583
- for await (const request of server) {
584
- const url = new URL(request.url, `http://${request.headers.get("host")}`);
585
- const filepath = `${absWebRoot}${url.pathname}`;
586
- //console.log(filepath)
587
- try {
588
- const content = await serveFile(request, filepath);
589
- request.respond(content);
590
- } catch {
591
- request.respond({status: 404, body: "File not found"});
592
- }
593
- }
594
- })();
595
-
596
- return server;
597
- };
598
-
599
- const stopServer = (server) => {
600
- server.close();
601
- };
602
-
603
- const server = startServer();
604
-
605
- const installations = await Launcher.getInstallations();
606
- if (installations.length === 0)
607
- throw new Error("No Chrome installations found.");
608
-
609
- const executablePath = installations[0]; // Use the first found installation
610
-
611
-
612
- const browser = await puppeteer.launch({headless, executablePath});
613
- const browserPage = await browser.newPage();
614
- let args = [];
615
-
616
- if (!tests)
617
- args.push('allTests');
618
- else
619
- for (let test of tests)
620
- args.push(`&r=${test}`);
621
-
622
-
623
- const url = `http://localhost:${port}/${page}?${args.join('&')}`;
624
- //console.log(url)
625
- await browserPage.goto(url);
626
-
627
- // Wait for the tests to finish
628
- await browserPage.waitForFunction(() => window.Testimony?.finished === true);
629
-
630
- const failedTests = await browserPage.evaluate(() => window.Testimony?.failedTests);
631
- this.printTestResult(failedTests);
632
-
633
- await browser.close();
634
- stopServer(server);
635
-
636
- Deno.exit(failedTests.length ? 1 : 0);
637
- },
638
-
639
- printTestResult(failedTests) {
640
- if (!failedTests.length)
641
- console.log(`%cAll tests passed.`, 'color: #0c0');
642
- else {
643
- console.log(`These tests failed:`);
644
- for (const [testName, testError] of failedTests) {
645
- console.error(`${testName} - %c${testError}`, 'color: red');
646
- }
647
- }
648
- },
649
-
650
-
651
- /**
652
- * Add a test.
653
- *
654
- * Arguments can be given in any order, except that name must occur before desc.
655
- * @param name {string}
656
- * @param desc {string|function()=}
657
- * @param html {string|function()=}
658
- * @param func {function()=} */
659
- test(name, desc, html=null, func) {
660
- let name2, desc2='', html2, func2;
661
- for (let arg of arguments) {
662
- if (typeof arg === 'function')
663
- func2 = arg;
664
- else if ((arg+'').trim().match(/^<[!a-z]/i)) // an open tag.
665
- html2 = arg;
666
- else if (!name2)
667
- name2 = arg;
668
- else
669
- desc2 = arg || '';
670
- }
671
-
672
- // update func to create and destroy html before and after test.
673
- if (html2) {
674
- let oldFunc = func2;
675
-
676
- // As an iframe.
677
- if (html2.startsWith('<html') || html2.startsWith('<!')) {
678
- func2 = async () => {
679
- var iframe = document.createElement('iframe');
680
- iframe.style.display = 'none';
681
- document.body.append(iframe);
682
-
683
- var doc = iframe.contentDocument || iframe.contentWindow.document;
684
- doc.open();
685
- doc.write(html2);
686
- doc.close();
687
-
688
- let result = await oldFunc(doc);
689
- iframe.parentNode.removeChild(iframe);
690
- return result;
691
- };
692
- }
693
-
694
- // As part of the regular document
695
- else {
696
- func2 = async () => {
697
- let el = createEl(html2);
698
- document.body.append(el);
699
- let result = await oldFunc(el);
700
- document.body.removeChild(el);
701
- return result;
702
- }
703
- }
704
- }
705
-
706
-
707
- // Add to rootTest tree.
708
- let path = name.split(/\./g);
709
- let pathSoFar = [];
710
- let test = this.rootTest;
711
- for (let item of path) {
712
- pathSoFar.push(item);
713
-
714
- if (!test.children)
715
- test.children = {};
716
-
717
- // If at leaf
718
- if (pathSoFar.length === path.length)
719
- test.children[item] = new Test(name2, desc2, func2);
720
-
721
- // Create test if it doesn't exist.
722
- else {
723
- test.children[item] = test.children[item] || new Test(pathSoFar.join('.'));
724
- test = test.children[item];
725
- }
726
- }
727
- },
728
-
729
- /**
730
- * @param test {?Test} */
731
- getAllTestNames(test=null) {
732
- test = test || this.rootTest;
733
- let result = [];
734
- if (test.name.length)
735
- result.push(test.name);
736
- for (let name in test.children)
737
- result.push(...this.getAllTestNames(test.children[name]));
738
- return result;
739
- },
740
-
741
- // Internal functions:
742
-
743
- /**
744
- * @param error {Error}
745
- * @param br {string}
746
- * @returns {string} */
747
- shortenError(error, br='<br>&nbsp;&nbsp;') {
748
- // slice(0, -3) to remove the 3 stacktrace lines inside Testimony.js that calls runtests.
749
- let errorStack = error.stack.split(/\n/g).slice(0, -3).join('\r\n');
750
-
751
- errorStack = errorStack.replace(/\r?\n/g, br);
752
- return errorStack.replace(new RegExp(window.location.origin, 'g'), ''); // Remove server name to shorten error stack.
753
- }
754
- }
755
- window.Testimony = Testimony; // used by command line test runner.
756
-
757
- export default Testimony;
758
- export {assert, Testimony, TextRenderer, HtmlRenderer};
759
-
760
-
761
- // If Testimony.js is run directly from the command line
762
- if (import.meta.main) {
763
- let pages = null;
764
- let imports = null;
765
- let tests = null;
766
- let webroot = null;
767
- let headless = false;
768
- for (let arg of Deno.args) {
769
-
770
- if (arg.startsWith('--page=')) {
771
- if (!pages)
772
- pages = [];
773
- pages.push(arg.slice('--page='.length));
774
- }
775
-
776
- else if (arg.startsWith('--import=')) {
777
- if (!imports)
778
- imports = [];
779
- imports.push(arg.slice('--import='.length));
780
- }
781
-
782
- else if (arg.startsWith('--webroot='))
783
- webroot = arg.slice('--webroot='.length);
784
-
785
-
786
- else if (arg == '--headless')
787
- headless = true;
788
-
789
- else if (arg.startsWith('--')) {
790
- console.error(`Unsupported arg ${arg}`);
791
- Deno.exit(1);
792
- }
793
-
794
- // Capture test names to run.
795
- else {
796
- if (!tests)
797
- tests = [];
798
- tests.push(arg);
799
- }
800
- }
801
-
802
- if (webroot && !pages)
803
- pages = ['index.html'];
804
-
805
- if (pages) {
806
- for (let page of pages)
807
- Testimony.runPage(page, webroot, tests, headless);
808
- }
809
- }