solarite 0.1.0 → 0.2.0

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 (48) hide show
  1. package/benchmarks/naive/Solarite.min.js +4 -0
  2. package/benchmarks/naive/index.html +14 -0
  3. package/benchmarks/naive/main.js +339 -0
  4. package/benchmarks/naive/package-lock.json +13 -0
  5. package/benchmarks/naive/package.json +23 -0
  6. package/benchmarks/readme.md +48 -0
  7. package/build/build.bat +3 -2
  8. package/build/build.js +1 -1
  9. package/dist/Solarite-debug.js +1941 -2791
  10. package/dist/Solarite.js +1697 -2511
  11. package/dist/Solarite.min.js +3 -3
  12. package/dist/udomdiff-license.txt +18 -0
  13. package/docs/index.md +695 -235
  14. package/docs/js/Playground.js +1 -1
  15. package/docs/js/codemirror/codemirror6.js +3683 -3206
  16. package/docs/js/codemirror/themeSolarIce.js +2 -2
  17. package/docs/js/documentation.js +2 -2
  18. package/docs/js/ui/CodeEditor.js +183 -45
  19. package/docs/js/ui/FlexResizer.js +15 -5
  20. package/docs/js/util/Errors.js +11 -0
  21. package/docs/media/documentation.css +6 -3
  22. package/index.html +462 -26
  23. package/package.json +1 -1
  24. package/readme.md +11 -1
  25. package/src/solarite/ExprPath.js +422 -135
  26. package/src/solarite/Globals.js +53 -0
  27. package/src/solarite/NodeGroup.js +300 -388
  28. package/src/solarite/Shell.js +31 -33
  29. package/src/solarite/Solarite.js +17 -2
  30. package/src/solarite/Template.js +75 -25
  31. package/src/solarite/Util.js +131 -7
  32. package/src/solarite/createSolarite.js +32 -25
  33. package/src/solarite/getArg.js +12 -12
  34. package/src/solarite/hash.js +18 -35
  35. package/src/solarite/r.js +128 -118
  36. package/src/solarite/watch3.js +98 -0
  37. package/src/{solarite → unused}/NodeGroupManager.js +86 -224
  38. package/src/unused/onConnect.js +79 -0
  39. package/src/{solarite → unused}/watch.js +2 -2
  40. package/src/{solarite → unused}/watch2.js +4 -4
  41. package/src/{solarite → util}/MultiValueMap.js +23 -12
  42. package/src/util/WeakArray.js +33 -0
  43. package/tests/Solarite.test.js +1108 -166
  44. package/tests/Testimony.js +274 -67
  45. package/tests/index.html +6 -35
  46. package/tests/run.bat +2 -0
  47. package/tests/NodeGroup.test.js +0 -115
  48. package/tests/Shell.test.js +0 -75
@@ -1,17 +1,222 @@
1
1
  // noinspection DuplicatedCode
2
2
 
3
+ // Broken tests will be annotated with FIXME
4
+
3
5
  import {camelToDashes, htmlContext} from "../src/solarite/Util.js";
4
- import {watchGet, watchSet} from "../src/solarite/watch.js";
6
+ //import {watchGet, watchSet} from "../src/solarite/watch.js";
7
+
8
+ import {Solarite, r, getArg} from '../src/solarite/Solarite.js';
9
+ //import {Solarite, r, getArg} from '../dist/Solarite.min.js'; // This will help the Benchmark test warm up.
10
+ //import {watch} from "../src/solarite/watch2.js";
11
+ import watch3 from "../src/solarite/watch3.js";
12
+ //import NodeGroupManager from "../src/solarite/NodeGroupManager.js";
13
+ import NodeGroup from "../src/solarite/NodeGroup.js";
14
+ import Template from "../src/solarite/Template.js";
15
+ import Shell from "../src/solarite/Shell.js";
16
+
5
17
  import Testimony, {assert} from './Testimony.js';
6
- Testimony.enableJsDom();
7
18
 
8
- import {Solarite, r} from '../src/solarite/Solarite.js';
9
- //import {Red, r, Perf} from '../dist/Solarite.min.js'; // This will help the Benchmark test warm up.
10
- import {watch} from "../src/solarite/watch2.js";
19
+ // This function is used by the various tests.
20
+ window.getHtml = (item, includeComments=false) => {
21
+ if (!item)
22
+ return item;
23
+
24
+ if (item.fragment)
25
+ item = item.fragment; // Shell
26
+ if (item instanceof DocumentFragment)
27
+ item = [...item.childNodes]
28
+
29
+ else if (item.getNodes)
30
+ item = item.getNodes()
31
+
32
+ let result;
33
+ if (Array.isArray(item)) {
34
+ if (!includeComments)
35
+ item = item.filter(n => n.nodeType !==8)
36
+
37
+ result = item.map(n => n.nodeType === 8 ? `<!--${n.textContent}-->` : n.outerHTML || n.textContent).join('|');
38
+ }
39
+ else
40
+ result = item.outerHTML || item.textContent
41
+
42
+ if (!includeComments)
43
+ result = result.replace(/(<)!--(.*?)-->/g, '')
44
+
45
+ // Remove whitespace between tags, so we can write simpler tests.
46
+ return result.replace(/^\s+</g, '<').replace(/>\s+</g, '><').replace(/>\s+$/g, '>');
47
+ }
48
+
49
+
50
+
51
+ //<editor-fold desc="shell">
52
+ /* ┌─────────────────╮
53
+ * | Shell |
54
+ * └─────────────────╯*/
55
+ Testimony.test('Solarite.Shell.empty', () => {
56
+ let shell = new Shell(['', ''])
57
+ assert.eq(getHtml(shell, true), '<!--ExprPath:0-->|<!--ExprPathEnd:0-->')
58
+ });
59
+
60
+ Testimony.test('Solarite.Shell.paragraph', () => {
61
+ let shell = new Shell(['<p>', '</p>'])
62
+ assert.eq(getHtml(shell, true), '<p><!--ExprPath:0--><!--ExprPathEnd:0--></p>')
63
+ });
64
+
65
+ Testimony.test('Solarite.Shell.nodeBefore', () => {
66
+ let shell = new Shell(['a', ''])
67
+ assert.eq(getHtml(shell, true), 'a|<!--ExprPathEnd:0-->')
68
+ });
69
+
70
+ Testimony.test('Solarite.Shell.nodeAfter', () => {
71
+ let shell = new Shell(['', 'b'])
72
+ assert.eq(getHtml(shell, true), '<!--ExprPath:0-->|b')
73
+ });
74
+
75
+ Testimony.test('Solarite.Shell.nodeBeforeAfter', () => {
76
+ let shell = new Shell(['a', 'b'])
77
+ assert.eq(getHtml(shell, true), 'a|b')
78
+ });
79
+
80
+ Testimony.test('Solarite.Shell.emptyTwoPaths', () => {
81
+ let shell = new Shell(['', '', ''])
82
+ assert.eq(getHtml(shell, true), '<!--ExprPath:0-->|<!--ExprPathEnd:0-->|<!--ExprPathEnd:1-->')
83
+ });
84
+
85
+ Testimony.test('Solarite.Shell.nodeBetweenPaths', () => {
86
+ let shell = new Shell(['', 'a', ''])
87
+ assert.eq(getHtml(shell, true), '<!--ExprPath:0-->|a|<!--ExprPathEnd:1-->')
88
+ });
89
+
90
+ Testimony.test('Solarite.Shell.nodesAroundPaths', () => {
91
+ let shell = new Shell(['a', 'b', 'c'])
92
+ assert.eq(getHtml(shell, true), 'a|b|c')
93
+ });
94
+
95
+ Testimony.test('Solarite.Shell.emptyTwoPathsNested', () => {
96
+ let shell = new Shell(['<p>', '', '</p>'])
97
+ assert.eq(getHtml(shell, true), '<p><!--ExprPath:0--><!--ExprPathEnd:0--><!--ExprPathEnd:1--></p>')
98
+ });
99
+
100
+ Testimony.test('Solarite.Shell.nodesAroundPathsNested', () => {
101
+ let shell = new Shell(['<p>a', 'b', 'c</p>'])
102
+ assert.eq(getHtml(shell, true), '<p>abc</p>')
103
+ });
104
+
105
+
106
+ // Testimony.test('Shell.emptySpacer', () => {
107
+ // let expr = 1;
108
+ // let shell = new Shell(['<input ', '', 'onclick=','>'])
109
+ // console.log(getHtml(shell, true))
110
+ // });
111
+
112
+
113
+
114
+ //</editor-fold>
115
+
116
+
117
+
118
+ //<editor-fold desc="nodegroup">
119
+ /* ┌─────────────────╮
120
+ * | NodeGroup |
121
+ * └─────────────────╯*/
122
+ Testimony.test('Solarite.NodeGroup.empty', () => {
123
+
124
+ let ng = new NodeGroup(new Template([``], []))
125
+ assert.eq(getHtml(ng), '')
126
+
127
+ ng.applyExprs([])
128
+ assert.eq(getHtml(ng), '')
129
+ });
130
+
131
+
132
+ Testimony.test('Solarite.NodeGroup.oneExpr', () => {
133
+
134
+ let ng = new NodeGroup(new Template([``, ``], ['1']))
135
+ assert.eq(getHtml(ng), '1')
136
+
137
+ ng.applyExprs([2])
138
+ assert.eq(getHtml(ng), '2')
139
+
140
+ ng.applyExprs([[3, 4, 5]])
141
+ assert.eq(getHtml(ng), '3|4|5')
142
+ });
143
+
144
+ Testimony.test('Solarite.NodeGroup.emptyAdjacent', () => {
145
+ let ng = new NodeGroup(new Template([``, ``, ``], ['1', '2']))
146
+ ng.verify();
147
+
148
+ assert.eq(getHtml(ng), '1|2')
149
+
150
+ ng.applyExprs([3, 4])
151
+ assert.eq(getHtml(ng), '3|4')
152
+
153
+ ng.applyExprs([[1, 2, 3], [4, 5, 6]])
154
+ assert.eq(getHtml(ng), '1|2|3|4|5|6')
155
+ });
156
+
157
+
158
+ Testimony.test('Solarite.NodeGroup.paragraph', () => {
159
+ let ng = new NodeGroup(new Template(['<p>', '</p>'], ['1']))
160
+ assert.eq(getHtml(ng), '<p>1</p>')
161
+
162
+ ng.applyExprs([2])
163
+ assert.eq(getHtml(ng), '<p>2</p>')
164
+
165
+ ng.applyExprs([[3, 4, 5]])
166
+ assert.eq(getHtml(ng), '<p>345</p>')
167
+ });
168
+
169
+ Testimony.test('Solarite.NodeGroup.node', () => {
170
+ let a = r('<p>a</p>')
171
+ let b = r('<p>b</p>')
172
+ let ng = new NodeGroup(new Template(['<div>', '</div>'], [a]))
173
+
174
+ assert.eq(getHtml(ng), '<div><p>a</p></div>')
175
+
176
+ ng.applyExprs([b]);
177
+ assert.eq(getHtml(ng), '<div><p>b</p></div>')
178
+
179
+ ng.applyExprs([1]);
180
+ assert.eq(getHtml(ng), '<div>1</div>')
181
+
182
+ ng.applyExprs([a]);
183
+ assert.eq(getHtml(ng), '<div><p>a</p></div>')
184
+ });
185
+
186
+ Testimony.test('Solarite.NodeGroup._nodeSwap', () => {
187
+ let a = r('<p>a</p>')
188
+ let b = r('<p>b</p>')
189
+ let ng = new NodeGroup(new Template(['<div>', '', '</div>'], [a, b]))
190
+ document.body.append(ng.startNode)
191
+
192
+ assert.eq(getHtml(ng), '<div><p>a</p><p>b</p></div>')
193
+
194
+ ng.applyExprs([b, a]);
195
+ assert.eq(getHtml(ng), '<div><p>b</p><p>a</p></div>');
196
+ });
197
+
198
+
199
+ Testimony.test('Solarite.NodeGroup.arrayReverse', () => {
200
+ let list = [r('a'), r('b')];
201
+
202
+ let ng = new NodeGroup(new Template(['<div>', '</div>'], [list]))
203
+ ng.verify();
204
+ assert(getHtml(ng), '<div>ab</div')
205
+
206
+ list.reverse();
207
+ ng.applyExprs([list])
208
+ assert(getHtml(ng), '<div>ba</div')
209
+ });
210
+ //</editor-fold>
11
211
 
12
212
 
13
213
 
14
- Testimony.test('Util.htmlContext', () => {
214
+ //<editor-fold desc="util">
215
+ /* ┌─────────────────╮
216
+ * | Util |
217
+ * └─────────────────╯*/
218
+
219
+ Testimony.test('Solarite.Util.htmlContext', () => {
15
220
  assert.eq(htmlContext('<div class="test'), htmlContext.Attribute)
16
221
  assert.eq(htmlContext('">hello '), htmlContext.Text);
17
222
  assert.eq(htmlContext('<span data-attr="hi > there"'), htmlContext.Tag);
@@ -24,14 +229,21 @@ Testimony.test('Util.htmlContext', () => {
24
229
  assert.eq(htmlContext('>'), htmlContext.Text);
25
230
  });
26
231
 
27
- Testimony.test('Util.camelToDashes', () => {
232
+ Testimony.test('Solarite.Util.camelToDashes', () => {
28
233
  assert.eq(camelToDashes('ProperName'), 'proper-name');
29
234
  assert.eq(camelToDashes('HTMLElement'), 'html-element');
30
235
  assert.eq(camelToDashes('BigUI'), 'big-ui');
31
236
  assert.eq(camelToDashes('UIForm'), 'ui-form');
32
237
  assert.eq(camelToDashes('A100'), 'a-100');
33
238
  });
239
+ //</editor-fold>
240
+
34
241
 
242
+
243
+ //<editor-fold desc="basic">
244
+ /* ┌─────────────────╮
245
+ * | Basic |
246
+ * └─────────────────╯*/
35
247
  Testimony.test('Solarite.basic.empty', () => {
36
248
  class A extends HTMLElement {
37
249
  constructor() {
@@ -153,20 +365,27 @@ Testimony.test('Solarite.basic.createElement', () => {
153
365
  a.render();
154
366
  assert.eq(getHtml(a), `<r-35><div>Hello!</div></r-35>`);
155
367
  });
368
+ //</editor-fold>
369
+
156
370
 
157
371
 
158
- // Expr
372
+ //<editor-fold desc="expr">
373
+ /* ┌─────────────────╮
374
+ * | Expr |
375
+ * └─────────────────╯*/
159
376
  Testimony.test('Solarite.expr.staticString', () => {
160
- class R40 extends Solarite {
377
+ class R40 extends HTMLElement {
161
378
  render() {
162
- r(this)`Solarite ${'🟥'} Component`
379
+ r(this)`Solarite ${'Test'} Component`
163
380
  }
164
381
  }
382
+ customElements.define('r-40', R40);
165
383
 
166
384
  let a = new R40();
385
+ a.render();
167
386
  document.body.append(a);
168
387
 
169
- assert.eq(getHtml(a), '<r-40>Solarite 🟥 Component</r-40>');
388
+ assert.eq(getHtml(a), '<r-40>Solarite Test Component</r-40>');
170
389
 
171
390
  a.remove();
172
391
  });
@@ -190,7 +409,6 @@ Testimony.test('Solarite.expr.htmlString', () => {
190
409
  assert.eq(getHtml(a), '<r-42>This text is <b>Bold</b>!</r-42>');
191
410
  });
192
411
 
193
-
194
412
  Testimony.test('Solarite.expr.table', () => {
195
413
 
196
414
  class R43 extends Solarite {
@@ -217,7 +435,13 @@ Testimony.test('Solarite.expr.documentFragment', () => {
217
435
  }
218
436
  }
219
437
 
220
- let a = new R44({render:true}); // auto render on construct.
438
+ let a = new R44(); // auto render on construct.
439
+
440
+ a.render();
441
+ assert.eq(getHtml(a), '<r-44>This text is <b>Bold</b><i>Italic</i>!</r-44>');
442
+
443
+
444
+ a.render();
221
445
  assert.eq(getHtml(a), '<r-44>This text is <b>Bold</b><i>Italic</i>!</r-44>');
222
446
  });
223
447
 
@@ -446,7 +670,6 @@ Testimony.test('Solarite.expr.staticElement', () => {
446
670
  assert.eq(getHtml(a), '<r-74>Field: <input></r-74>');
447
671
  });
448
672
 
449
-
450
673
  Testimony.test('Solarite.expr.varText', () => {
451
674
  class A extends Solarite {
452
675
  value = 'Apple';
@@ -472,8 +695,6 @@ Testimony.test('Solarite.expr.varText', () => {
472
695
  assert.eq(a.childNodes.length, 3);
473
696
  });
474
697
 
475
-
476
-
477
698
  Testimony.test('Solarite.expr.cyclicRef', () => {
478
699
 
479
700
 
@@ -499,15 +720,14 @@ Testimony.test('Solarite.expr.cyclicRef', () => {
499
720
  a.render();
500
721
  assert.eq(getHtml(a), '<r-100>The fruit is Cherry!</r-100>');
501
722
  });
723
+ //</editor-fold>
502
724
 
503
725
 
504
726
 
505
-
506
-
507
-
508
-
509
-
510
- // Loop:
727
+ //<editor-fold desc="loop">
728
+ /* ┌─────────────────╮
729
+ * | Loop |
730
+ * └─────────────────╯*/
511
731
  Testimony.test('Solarite.loop.strings', () => {
512
732
  class A extends Solarite {
513
733
  fruits = ['Apple', 'Banana'];
@@ -543,7 +763,6 @@ Testimony.test('Solarite.loop.strings', () => {
543
763
  assert.eq(getHtml(a), '<r-200>Apple</r-200>');
544
764
  });
545
765
 
546
-
547
766
  Testimony.test('Solarite.loop.paragraphs', () => {
548
767
  class A extends Solarite {
549
768
  fruits = ['Apple', 'Banana'];
@@ -554,7 +773,8 @@ Testimony.test('Solarite.loop.paragraphs', () => {
554
773
  }
555
774
  customElements.define('r-210', A);
556
775
  let a = new A();
557
- document.body.append(a);
776
+
777
+ a.render();
558
778
 
559
779
  assert.eq(getHtml(a), '<r-210><p>Apple</p><p>Banana</p></r-210>');
560
780
 
@@ -563,6 +783,7 @@ Testimony.test('Solarite.loop.paragraphs', () => {
563
783
  assert.eq(getHtml(a), '<r-210><p>Apple</p><p>Banana</p><p>Cherry</p></r-210>');
564
784
 
565
785
  a.fruits.pop();
786
+ window.debug = true;
566
787
  a.render();
567
788
  assert.eq(getHtml(a), '<r-210><p>Apple</p><p>Banana</p></r-210>');
568
789
 
@@ -581,8 +802,7 @@ Testimony.test('Solarite.loop.paragraphs', () => {
581
802
  a.remove();
582
803
  });
583
804
 
584
-
585
- Testimony.test('Solarite.loop.paragraphsBefore', () => {
805
+ Testimony.test('Solarite.loop.paragraphsBefore', `Same as above, but with another element afterward.`, () => {
586
806
  class A extends Solarite {
587
807
  fruits = ['Apple', 'Banana'];
588
808
 
@@ -619,6 +839,54 @@ Testimony.test('Solarite.loop.paragraphsBefore', () => {
619
839
  a.remove();
620
840
  });
621
841
 
842
+ Testimony.test('Solarite.loop.continuity', `Make sure elements are reused in a consistent way.`, () => {
843
+ class A extends Solarite {
844
+ fruits = ['Apple', 'Banana'];
845
+
846
+ render() {
847
+ r(this)`${this.fruits.map(fruit => r`<p>${fruit}</p>`)}`
848
+ }
849
+ }
850
+ customElements.define('a-213', A);
851
+ let a = new A();
852
+ document.body.append(a);
853
+ a.render();
854
+ assert.eq(getHtml(a), '<a-213><p>Apple</p><p>Banana</p></a-213>');
855
+
856
+ let apple = a.children[0];
857
+ let banana = a.children[1];
858
+ a.fruits.shift();
859
+ a.render();
860
+ assert.eq(getHtml(a), '<a-213><p>Banana</p></a-213>');
861
+ assert.eq(a.children[0], banana);
862
+
863
+ a.fruits.pop();
864
+ a.render();
865
+ assert.eq(getHtml(a), '<a-213></a-213>');
866
+
867
+ // We get back the same element.
868
+ a.fruits.push('Apple');
869
+ a.render();
870
+ assert.eq(getHtml(a), '<a-213><p>Apple</p></a-213>');
871
+ assert.eq(a.children[0], apple);
872
+
873
+
874
+ a.fruits.push('Banana');
875
+ a.render();
876
+ assert.eq(getHtml(a), '<a-213><p>Apple</p><p>Banana</p></a-213>');
877
+ assert.eq(a.children[0], apple);
878
+ assert.eq(a.children[1], banana);
879
+
880
+ // Make sure we maintain continuity after adding to the beginning.
881
+ a.fruits.unshift('Cherry');
882
+ a.render();
883
+ assert.eq(getHtml(a), '<a-213><p>Cherry</p><p>Apple</p><p>Banana</p></a-213>');
884
+ assert.eq(a.children[1], apple);
885
+ assert.eq(a.children[2], banana);
886
+
887
+ a.remove();
888
+ });
889
+
622
890
  Testimony.test('Solarite.loop.eventBindings', async () => {
623
891
  await new Promise((resolve, reject) => {
624
892
  let callCount = 0;
@@ -653,7 +921,7 @@ Testimony.test('Solarite.loop.eventBindings', async () => {
653
921
 
654
922
  });
655
923
 
656
-
924
+ // TODO: Why is this called pathCache? What does it test?
657
925
  Testimony.test('Solarite.loop.pathCache', () => {
658
926
 
659
927
  class R216 extends Solarite {
@@ -670,15 +938,13 @@ Testimony.test('Solarite.loop.pathCache', () => {
670
938
  }
671
939
 
672
940
  let a = new R216();
673
- document.body.append(a);
674
941
  a.render();
675
942
  assert.eq(getHtml(a), `<r-216><p>Item</p><p>Item</p></r-216>`);
676
943
 
677
944
  a.fruits.shift();
945
+ window.debug = true;
678
946
  a.render();
679
947
  assert.eq(getHtml(a), `<r-216><p>Item</p></r-216>`);
680
-
681
- a.remove();
682
948
  });
683
949
 
684
950
  Testimony.test('Solarite.loop.nested', () => {
@@ -728,6 +994,7 @@ Testimony.test('Solarite.loop.nested', () => {
728
994
 
729
995
  a.pets.reverse();
730
996
  a.fruits.reverse();
997
+ window.debug = true;
731
998
  a.render();
732
999
  assert.eq(getHtml(a), `<r-220><p>Bird eats Apricot</p><p>Bird eats Banana</p><p>Cat eats Apricot</p><p>Cat eats Banana</p></r-220>`);
733
1000
 
@@ -803,7 +1070,6 @@ Testimony.test('Solarite.loop.nested2', () => {
803
1070
  a.remove();
804
1071
  });
805
1072
 
806
-
807
1073
  Testimony.test('Solarite.loop.nested3', `Move items from one sublist to another.`, () => {
808
1074
 
809
1075
  class A225 extends Solarite {
@@ -825,6 +1091,7 @@ Testimony.test('Solarite.loop.nested3', `Move items from one sublist to another.
825
1091
 
826
1092
  let a = new A225();
827
1093
  document.body.append(a);
1094
+ window.debug = true;
828
1095
  a.render();
829
1096
 
830
1097
  let cherry = a.fruitGroups[1].pop();
@@ -834,15 +1101,12 @@ Testimony.test('Solarite.loop.nested3', `Move items from one sublist to another.
834
1101
 
835
1102
  let banana = a.fruitGroups[1].pop();
836
1103
  a.fruitGroups[0].push(banana);
837
- //window.debug = true;
838
1104
  a.render();
839
-
1105
+
840
1106
  window.verify = false;
841
1107
  a.remove();
842
1108
  });
843
1109
 
844
-
845
-
846
1110
  // Tried to make a simpler version of nested3, but it works fine:
847
1111
  Testimony.test('Solarite.loop.nested4', () => {
848
1112
 
@@ -877,14 +1141,6 @@ Testimony.test('Solarite.loop.nested4', () => {
877
1141
  a.remove();
878
1142
  });
879
1143
 
880
-
881
-
882
-
883
-
884
-
885
-
886
-
887
-
888
1144
  Testimony.test('Solarite.loop.nested5', () => {
889
1145
 
890
1146
  // This test was originally created by reducing a failure in production code
@@ -904,7 +1160,7 @@ Testimony.test('Solarite.loop.nested5', () => {
904
1160
  ]
905
1161
 
906
1162
  render() {
907
- this.html = r`
1163
+ r(this)`
908
1164
  <a-227>${this.boxes.map(item =>
909
1165
  r`${item.map(item2 =>
910
1166
  r`<div title=${v}>${item2}</div>`
@@ -929,28 +1185,12 @@ Testimony.test('Solarite.loop.nested5', () => {
929
1185
  ]
930
1186
  a.render();
931
1187
 
932
- assert.eq(a.outerHTML, `<a-227><!--PathStart:0--><!--PathStart:0--><div title="F2"><!--PathStart:1-->A<!--PathEnd:1--></div><div title="F2"><!--PathStart:1-->B<!--PathEnd:1--></div><!--PathEnd:0--><!--PathStart:0--><div title="F2"><!--PathStart:1-->A<!--PathEnd:1--></div><!--PathEnd:0--><!--PathEnd:0--></a-227>`);
1188
+ assert.eq(a.outerHTML, `<a-227><!--ExprPath:0--><!--ExprPath:0--><div title="F2"><!--ExprPath:1-->A<!--ExprPathEnd:1--></div><div title="F2"><!--ExprPath:1-->B<!--ExprPathEnd:1--></div><!--ExprPathEnd:0--><!--ExprPath:0--><div title="F2"><!--ExprPath:1-->A<!--ExprPathEnd:1--></div><!--ExprPathEnd:0--><!--ExprPathEnd:0--></a-227>`);
933
1189
 
934
1190
  window.verify = false;
935
1191
  a.remove();
936
1192
  });
937
1193
 
938
-
939
-
940
-
941
-
942
-
943
-
944
-
945
-
946
-
947
-
948
-
949
-
950
-
951
-
952
-
953
-
954
1194
  Testimony.test('Solarite.loop.nestedConditional', () => {
955
1195
 
956
1196
  let isGoodBoy = true;
@@ -1035,7 +1275,6 @@ Testimony.test('Solarite.loop.conditionalNested', () => {
1035
1275
  a.remove();
1036
1276
  });
1037
1277
 
1038
-
1039
1278
  Testimony.test('Solarite.loop.tripleNested', 'Triple nested grid', () => {
1040
1279
 
1041
1280
  class R250 extends Solarite {
@@ -1101,12 +1340,15 @@ Testimony.test('Solarite.loop.tripleNested', 'Triple nested grid', () => {
1101
1340
  a.remove();
1102
1341
  });
1103
1342
 
1343
+ //</editor-fold>
1104
1344
 
1105
1345
 
1106
1346
 
1107
1347
 
1108
-
1109
-
1348
+ //<editor-fold desc="embed">
1349
+ /* ┌─────────────────╮
1350
+ * | Embed |
1351
+ * └─────────────────╯*/
1110
1352
 
1111
1353
  Testimony.test('Solarite.embed.styleStatic', () => {
1112
1354
  let count = 0;
@@ -1159,7 +1401,6 @@ Testimony.test('Solarite.embed.optionsNoStyles', () => {
1159
1401
  a.remove();
1160
1402
  });
1161
1403
 
1162
-
1163
1404
  Testimony.test('Solarite.embed.styleDynamic', () => {
1164
1405
  let style1 = `:host { color: red }`
1165
1406
  let style2 = `:host { font-weight: bold }`
@@ -1233,10 +1474,6 @@ Testimony.test('Solarite.embed.styleDynamicTag', () => {
1233
1474
  a.remove();
1234
1475
  });
1235
1476
 
1236
-
1237
-
1238
-
1239
-
1240
1477
  Testimony.test('Solarite.embed.svg', () => {
1241
1478
  class R330 extends Solarite {
1242
1479
  render() {
@@ -1314,8 +1551,16 @@ Testimony.test('Solarite.embed._scriptDynamic', () => {
1314
1551
  delete window.scriptStaticCount;
1315
1552
  });
1316
1553
 
1554
+ //</editor-fold>
1555
+
1317
1556
 
1318
1557
 
1558
+
1559
+ //<editor-fold desc="attrib">
1560
+ /* ┌─────────────────╮
1561
+ * | Attrib |
1562
+ * └─────────────────╯*/
1563
+
1319
1564
  Testimony.test('Solarite.attrib.single', () => {
1320
1565
 
1321
1566
  let val = 'one';
@@ -1385,7 +1630,6 @@ Testimony.test('Solarite.attrib.double', () => {
1385
1630
  assert.eq(getHtml(a), `<r-420><div class="oneBtwoB">oneB</div></r-420>`)
1386
1631
  });
1387
1632
 
1388
-
1389
1633
  Testimony.test('Solarite.attrib.doubleAndText', () => {
1390
1634
 
1391
1635
  let val1 = 'one';
@@ -1499,12 +1743,11 @@ Testimony.test('Solarite.attrib.toggle', () => {
1499
1743
  assert.eq(getHtml(a), `<r-460><div></div></r-460>`)
1500
1744
  });
1501
1745
 
1502
-
1503
1746
  Testimony.test('Solarite.attrib.pseudoRoot', () => {
1504
1747
  let title = 'Hello'
1505
1748
  class R470 extends Solarite {
1506
1749
  render() {
1507
- this.html = r`<r-470 title="${title}">World</r-470>`
1750
+ r(this)`<r-470 title="${title}">World</r-470>`
1508
1751
  }
1509
1752
  }
1510
1753
 
@@ -1522,7 +1765,6 @@ Testimony.test('Solarite.attrib.pseudoRoot', () => {
1522
1765
  assert.eq(a.outerHTML, `<r-470>World</r-470>`)
1523
1766
  });
1524
1767
 
1525
-
1526
1768
  Testimony.test('Solarite.attrib.pseudoRoot2', 'Static attribute overrides.', () => {
1527
1769
  let title = 'Hello'
1528
1770
  class R472 extends Solarite {
@@ -1584,9 +1826,59 @@ Testimony.test('Solarite.attrib.multiple', '', () => {
1584
1826
  assert.eq(getHtml(a), `<r-474><button onclick="" class="primary">Hello</button></r-474>`);
1585
1827
  });
1586
1828
 
1829
+ Testimony.test('Solarite.attrib.ids1', () => {
1830
+ class R500 extends Solarite {
1831
+ one;
1832
+ render() {
1833
+ r(this)`<div data-id="one"></div>`;
1834
+ }
1835
+ }
1836
+
1837
+ let a = new R500();
1838
+ a.render();
1839
+
1840
+ assert(a.one.tagName === 'DIV')
1841
+ });
1842
+
1843
+ Testimony.test('Solarite.attrib.ids2', () => {
1844
+ class R510 extends Solarite {
1845
+ one;
1846
+ render() {
1847
+ r(this)`<div data-id="one"><p id="two"></p></div>`;
1848
+ }
1849
+ }
1850
+
1851
+ let a = new R510();
1852
+ a.render();
1853
+
1854
+ assert(a.one.tagName === 'DIV')
1855
+ assert(a.two.tagName === 'P')
1856
+ });
1857
+
1858
+ Testimony.test('Solarite.attrib.idsDelve', () => {
1859
+ class R520 extends Solarite {
1860
+ one;
1861
+ render() {
1862
+ r(this)`<div data-id="one"><p id="path.to.p"></p></div>`;
1863
+ }
1864
+ }
1865
+
1866
+ let a = new R520();
1867
+ a.render();
1868
+
1869
+ assert(a.one.tagName === 'DIV')
1870
+ assert(a.path.to.p.tagName === 'P')
1871
+ });
1872
+ //</editor-fold>
1873
+
1874
+
1587
1875
 
1588
1876
 
1589
- Testimony.test('Solarite.comments', () => {
1877
+ //<editor-fold desc="comments">
1878
+ /* ┌─────────────────╮
1879
+ * | comments |
1880
+ * └─────────────────╯*/
1881
+ Testimony.test('Solarite.comments.one', () => {
1590
1882
 
1591
1883
  class A480 extends Solarite {
1592
1884
  render() {
@@ -1601,7 +1893,7 @@ Testimony.test('Solarite.comments', () => {
1601
1893
  a.remove();
1602
1894
  });
1603
1895
 
1604
- Testimony.test('Solarite.comments2', () => {
1896
+ Testimony.test('Solarite.comments.two', () => {
1605
1897
 
1606
1898
  class A482 extends Solarite {
1607
1899
  render() {
@@ -1614,32 +1906,72 @@ Testimony.test('Solarite.comments2', () => {
1614
1906
  assert.eq(getHtml(a), `<a-482><div>3</div></a-482>`)
1615
1907
  a.remove();
1616
1908
  });
1909
+ //</editor-fold>
1617
1910
 
1618
1911
 
1619
1912
 
1620
1913
 
1914
+ //<editor-fold desc="r">
1915
+ /* ┌─────────────────╮
1916
+ * | r |
1917
+ * └─────────────────╯*/
1918
+ Testimony.test('Solarite.r.staticElement', () => {
1919
+ let button = r(`<button>hi</button>`);
1920
+ assert(button instanceof HTMLElement); // Not a DocumentFragment
1921
+ assert.eq(getHtml(button), `<button>hi</button>`)
1922
+ })
1621
1923
 
1622
- Testimony.test('Solarite.ids', () => {
1623
- class R500 extends Solarite {
1624
- one;
1625
- render() {
1626
- this.html = r`<div data-id="one"></div>`;
1627
- }
1628
- }
1924
+ Testimony.test('Solarite.r.staticElement2', () => {
1925
+ let button = r(`
1926
+ <button>hi</button>`);
1927
+ assert(button instanceof HTMLElement); // Not a DocumentFragment
1928
+ assert.eq(getHtml(button), `<button>hi</button>`)
1929
+ })
1629
1930
 
1630
- let a = new R500();
1631
- a.render();
1931
+ Testimony.test('Solarite.r.staticElement3', () => {
1932
+ let button = r(` <!-- comment -->
1933
+ <button>hi</button>`);
1934
+ assert(button instanceof HTMLElement); // Not a DocumentFragment
1935
+ assert.eq(getHtml(button), `<button>hi</button>`)
1936
+ })
1632
1937
 
1633
- assert(a.one.tagName === 'DIV')
1938
+ Testimony.test('Solarite.r.staticElement4', () => {
1939
+ let button = r(` Hello`);
1940
+ assert(button instanceof Text);
1941
+ assert.eq(getHtml(button), ` Hello`)
1942
+ })
1943
+
1944
+ Testimony.test('Solarite.r.staticElement5', () => {
1945
+ let button = r(` <!--comment--> Hello`);
1946
+ assert(button instanceof Text);
1947
+ assert.eq(getHtml(button), ` Hello`)
1948
+ })
1949
+
1950
+ Testimony.test('Solarite.r.fragment', () => {
1951
+ let fragment = r(`Hello <button>hi</button>`);
1952
+ assert(fragment instanceof DocumentFragment);
1953
+ assert.eq(getHtml(fragment), `Hello |<button>hi</button>`)
1634
1954
  });
1635
1955
 
1636
- Testimony.test('Solarite.r._createElement', () => {
1956
+ Testimony.test('Solarite.r.fragment2', () => {
1957
+ let fragment = r()`Hello <button>hi</button>`;
1958
+ assert(fragment instanceof DocumentFragment);
1959
+ assert.eq(getHtml(fragment), `Hello |<button>hi</button>`)
1960
+ });
1637
1961
 
1638
- let button = r()`<button>hi</button>`
1639
- console.log(button.outerHTML)
1640
- document.body.append(button);
1641
- })
1962
+ Testimony.test('Solarite.r.staticElement3', () => {
1963
+ let button = r()`<button>hi</button>`;
1964
+ assert(button instanceof HTMLElement);
1965
+ assert.eq(getHtml(button), `<button>hi</button>`)
1966
+ });
1642
1967
 
1968
+ Testimony.test('Solarite.r.staticElement4', () => {
1969
+ // with line return
1970
+ let button = r()`
1971
+ <button>hi</button>`;
1972
+ assert(button instanceof HTMLElement);
1973
+ assert.eq(getHtml(button), `<button>hi</button>`)
1974
+ })
1643
1975
 
1644
1976
 
1645
1977
 
@@ -1650,70 +1982,213 @@ Testimony.test('Solarite.r.element', () => {
1650
1982
  assert.eq(getHtml(button), `<button>I'm a <b>better</b> button</button>`)
1651
1983
  })
1652
1984
 
1985
+ Testimony.test('Solarite.r.standalone1', () => {
1986
+ let button = r({
1987
+ count: 0,
1653
1988
 
1989
+ inc() {
1990
+ this.count++;
1991
+ this.render();
1992
+ },
1654
1993
 
1655
- Testimony.test('Solarite.r.lightweightComponent', () => {
1656
- function betterButton(count=0) {
1657
- let result = r(
1658
- () => r`<button onclick=${(ev, self)=>{count++; self.render()}}>I'm a ${count}X better button</button>`
1659
- );
1660
- result.inc = () => {
1661
- count++;
1662
- result.render();
1663
- }
1664
- return result;
1665
- }
1666
-
1667
- let button = betterButton(3);
1668
- assert.eq(getHtml(button), `<button onclick="">I'm a 3X better button</button>`)
1669
-
1670
- button.inc();
1671
- assert.eq(getHtml(button), `<button onclick="">I'm a 4X better button</button>`)
1672
- })
1673
-
1674
- Testimony.test('Solarite.r.lightweightComponent2', () => {
1675
- function betterButton(count=0) {
1676
- let result = r(
1677
- () => r`<button onclick=${(ev, self)=>{count++; self.render()}}>I'm a ${count}X better button</button>`
1678
- );
1679
- result.inc = () => {
1680
- count++;
1681
- result.render();
1994
+ render() {
1995
+ r(this)`<button onclick=${this.inc}>I've been clicked ${this.count} times.</button>`
1682
1996
  }
1683
- return result;
1684
- }
1997
+ });
1998
+ //document.body.append(button);
1685
1999
 
1686
- let button = betterButton(3);
1687
- assert.eq(getHtml(button), `<button onclick="">I'm a 3X better button</button>`)
2000
+ assert.eq(getHtml(button), `<button onclick="">I've been clicked 0 times.</button>`)
1688
2001
 
1689
2002
  button.inc();
1690
- assert.eq(getHtml(button), `<button onclick="">I'm a 4X better button</button>`)
1691
- })
2003
+ assert.eq(getHtml(button), `<button onclick="">I've been clicked 1 times.</button>`);
1692
2004
 
2005
+ button.dispatchEvent(new MouseEvent('click'));
2006
+ assert.eq(getHtml(button), `<button onclick="">I've been clicked 2 times.</button>`);
1693
2007
 
2008
+ //button.remove();
2009
+ });
1694
2010
 
2011
+ Testimony.test('Solarite.r.standalone2', () => {
2012
+ let list = r({
2013
+ items: [],
1695
2014
 
1696
- Testimony.test('Solarite.component.tr', () => {
2015
+ add() {
2016
+ this.items.push('Item ' + this.items.length);
2017
+ this.render();
2018
+ },
1697
2019
 
1698
- class TR510 extends Solarite('tr') {
1699
2020
  render() {
1700
- this.html = r`<td>hello</td>`
2021
+ r(this)`
2022
+ <div>
2023
+ <button onclick=${this.add}>Add Item</button>
2024
+ <hr>
2025
+ ${this.items.map(item => r`
2026
+ <p>${item}</p>
2027
+ `)}
2028
+ </div>`
1701
2029
  }
1702
- }
2030
+ });
1703
2031
 
1704
- let table = document.createElement('table')
2032
+ //document.body.append(list);
1705
2033
 
1706
- table.append(new TR510())
1707
- document.body.append(table)
2034
+ assert.eq(getHtml(list), `<div><button onclick="">Add Item</button><hr></div>`);
1708
2035
 
1709
- assert.eq(table.outerHTML, `<table><tr is="tr-510"><td>hello</td></tr></table>`)
2036
+ // At one point, rendering a standalone component the first time would re-render everything.
2037
+ // Here we make sure the hr element doesn't come back.
2038
+ list.querySelector('hr').remove();
2039
+ list.render();
1710
2040
 
1711
- table.remove();
2041
+ assert.eq(getHtml(list), `<div><button onclick="">Add Item</button></div>`);
2042
+
2043
+ list.add();
2044
+ assert.eq(getHtml(list), `<div><button onclick="">Add Item</button><p>Item 0</p></div>`);
1712
2045
 
2046
+ list.querySelector('button').dispatchEvent(new MouseEvent('click'));
2047
+ assert.eq(getHtml(list), `<div><button onclick="">Add Item</button><p>Item 0</p><p>Item 1</p></div>`);
2048
+
2049
+ //button.remove();
1713
2050
  });
1714
2051
 
2052
+ Testimony.test('Solarite.r.standaloneId', "Test id's on standalone elmenets.", () => {
2053
+ let list = r({
2054
+ items: [],
1715
2055
 
1716
- Testimony.test('Solarite.component.staticAttribs', () => {
2056
+ add() {
2057
+ this.items.push('Item ' + this.items.length);
2058
+ this.render();
2059
+ },
2060
+
2061
+ render() {
2062
+ r(this)`
2063
+ <div>
2064
+ <button data-id="button" onclick=${this.add}>Add Item</button>
2065
+ </div>`
2066
+ }
2067
+ });
2068
+
2069
+ assert.eq(list.button.tagName, 'BUTTON');
2070
+ });
2071
+
2072
+ Testimony.test('Solarite.r.standaloneStyle', "Test id's on standalone elmenets.", () => {
2073
+ let box = r({
2074
+ render() {
2075
+ r(this)`
2076
+ <div>
2077
+ <style>:host { display: block; background: red; width: 20px; height: 20px }</style>
2078
+ </div>`
2079
+ }
2080
+ });
2081
+
2082
+ let styleId = box.getAttribute('data-style');
2083
+ assert.eq(box.firstElementChild.textContent, `div[data-style="${styleId}"] { display: block; background: red; width: 20px; height: 20px }`);
2084
+ });
2085
+
2086
+ Testimony.test('Solarite.r.standalone3', () => {
2087
+
2088
+ // Make sure a div inside a div doesn't replace the parent div.
2089
+ let list = r({
2090
+ items: [],
2091
+
2092
+ render() {
2093
+ r(this)`
2094
+ <div>
2095
+ ${this.items.map(item => r`
2096
+ <div>
2097
+ <input placeholder="Name" value=${item.name}>
2098
+ <input type="number" value=${item.qty}>
2099
+ <button onclick=${()=>this.removeItem(item)}>x</button>
2100
+ </div>
2101
+ `)}
2102
+ </div>`
2103
+ }
2104
+ });
2105
+
2106
+ list.items.push({name: 'name', qty: 2});
2107
+ list.render();
2108
+
2109
+ assert.eq(getHtml(list), `<div><div><input placeholder="Name" value="name"><input type="number" value="2"><button onclick="">x</button></div></div>`);
2110
+ });
2111
+
2112
+ Testimony.test('Solarite.r.standaloneChild', () => {
2113
+
2114
+ function createItem(item) {
2115
+ return r({
2116
+ item: item,
2117
+ render(attribs = null) {
2118
+ // If attributes passed to constructor have changed.
2119
+ if (attribs)
2120
+ this.item = attribs.item;
2121
+ r(this)`
2122
+ <div>
2123
+ <b>${this.item.name}</b> - ${this.item.description}<br>
2124
+ </div>`
2125
+ }
2126
+ });
2127
+ }
2128
+
2129
+ function createList(items) {
2130
+ return r({
2131
+ items: items,
2132
+ render() {
2133
+ r(this)`
2134
+ <div>
2135
+ ${this.items.map(item =>
2136
+ createItem(item)
2137
+ )}
2138
+ </div>`
2139
+ }
2140
+ });
2141
+ }
2142
+
2143
+ let list = createList([
2144
+ {
2145
+ name: 'English',
2146
+ description: 'See spot run.'
2147
+ },
2148
+
2149
+ {
2150
+ name: 'Science',
2151
+ description: 'Snails are mollusks.'
2152
+ }
2153
+ ]);
2154
+ document.body.append(list);
2155
+
2156
+ list.items[0].name = 'PhysEd';
2157
+ list.render(); // calls NotesItem.render() with the new item.
2158
+
2159
+ assert.eq(getHtml(list), `<div><div><b>PhysEd</b> - See spot run.<br></div><div><b>Science</b> - Snails are mollusks.<br></div></div>`);
2160
+
2161
+ list.remove();
2162
+ });
2163
+ //</editor-fold>
2164
+
2165
+
2166
+
2167
+
2168
+ //<editor-fold desc="component">
2169
+ /* ┌─────────────────╮
2170
+ * | Component |
2171
+ * └─────────────────╯*/
2172
+ Testimony.test('Solarite.component.tr', () => {
2173
+
2174
+ class TR510 extends Solarite('tr') {
2175
+ render() {
2176
+ this.html = r`<td>hello</td>`
2177
+ }
2178
+ }
2179
+
2180
+ let table = document.createElement('table')
2181
+
2182
+ table.append(new TR510())
2183
+ document.body.append(table)
2184
+
2185
+ assert.eq(table.outerHTML, `<table><tr is="tr-510"><td>hello</td></tr></table>`)
2186
+
2187
+ table.remove();
2188
+
2189
+ });
2190
+
2191
+ Testimony.test('Solarite.component.staticAttribs', () => {
1717
2192
  // Note that all attribs become lowercase.
1718
2193
  // Not sure how to prevent this w/o using an xml doctype.
1719
2194
  class B512 extends Solarite {
@@ -1739,11 +2214,9 @@ Testimony.test('Solarite.component.staticAttribs', () => {
1739
2214
  let a = new A512();
1740
2215
  a.render();
1741
2216
 
1742
- assert.eq(a.outerHTML, `<a-512><div><b-512 name="User" userid="2"><!--PathStart:0-->User | 2<!--PathEnd:1--></b-512></div></a-512>`);
2217
+ assert.eq(a.outerHTML, `<a-512><div><b-512 name="User" userid="2"><!--ExprPath:0-->User | 2<!--ExprPathEnd:1--></b-512></div></a-512>`);
1743
2218
  });
1744
2219
 
1745
-
1746
-
1747
2220
  Testimony.test('Solarite.component.dynamicAttribs', 'Attribs specified via ${...}', () => {
1748
2221
 
1749
2222
  class B513 extends Solarite {
@@ -1769,21 +2242,17 @@ Testimony.test('Solarite.component.dynamicAttribs', 'Attribs specified via ${...
1769
2242
  let a = new A513();
1770
2243
  a.render();
1771
2244
 
1772
- assert.eq(a.outerHTML, `<a-513><div><b-513 name="User" userid="2"><!--PathStart:0-->User | 2<!--PathEnd:1--></b-513></div></a-513>`);
2245
+ assert.eq(a.outerHTML, `<a-513><div><b-513 name="User" userid="2"><!--ExprPath:0-->User | 2<!--ExprPathEnd:1--></b-513></div></a-513>`);
1773
2246
  });
1774
2247
 
1775
2248
  Testimony.test('Solarite.component.getArg', 'Attribs specified html when not nested in another Solarite component.', () => {
1776
-
1777
- Solarite.getArgs = function(el) {
1778
- debugger;
1779
- return {};
1780
- }
1781
-
2249
+
1782
2250
  class B514 extends Solarite {
1783
2251
  constructor({name, userid}={}) {
1784
2252
  super();
1785
- this.name = this.getArg('name', name);
1786
- this.userId = this.getArg('userid', userid);
2253
+
2254
+ this.name = getArg(this, 'name', name);
2255
+ this.userId = getArg(this, 'userid', userid);
1787
2256
  this.render();
1788
2257
  }
1789
2258
 
@@ -1796,14 +2265,11 @@ Testimony.test('Solarite.component.getArg', 'Attribs specified html when not nes
1796
2265
 
1797
2266
  let div = document.createElement('div');
1798
2267
  div.innerHTML = `<b-514 name="User" userid="2"></b-514>`
1799
-
1800
- assert.eq(div.outerHTML, `<div><b-514 name="User" userid="2"><!--PathStart:0-->User | 2<!--PathEnd:1--></b-514></div>`)
2268
+
2269
+ assert.eq(div.outerHTML, `<div><b-514 name="User" userid="2"><!--ExprPath:0-->User | 2<!--ExprPathEnd:1--></b-514></div>`)
1801
2270
 
1802
2271
  });
1803
2272
 
1804
-
1805
-
1806
-
1807
2273
  Testimony.test('Solarite.component.nested', () => {
1808
2274
 
1809
2275
  let bRenderCount = 0;
@@ -1828,11 +2294,10 @@ Testimony.test('Solarite.component.nested', () => {
1828
2294
  assert(!(a.firstChild instanceof B515))
1829
2295
 
1830
2296
  a.render();
1831
- assert(a.firstChild instanceof B515);
2297
+ assert(a.firstChild instanceof B515);
1832
2298
  assert.eq(getHtml(a), `<a-515><b-515><div>B</div></b-515></a-515>`);
1833
2299
  })
1834
2300
 
1835
-
1836
2301
  // Pass an object to the child.
1837
2302
  Testimony.test('Solarite.component.nested2', () => {
1838
2303
 
@@ -1883,19 +2348,78 @@ Testimony.test('Solarite.component.nested2', () => {
1883
2348
  a.title = 'Users2'
1884
2349
  a.render();
1885
2350
  assert.eq(getHtml(a), `<a-520>Users2<b-520 user=""><div>Name:</div><div>Barry</div><div>Email:</div><div>fred@example.com</div></b-520></a-520>`)
1886
- assert.eq(bRenderCount, 0) // Value passed to b didn't change, so it shouldn't re-render.
2351
+ assert.eq(bRenderCount, 0); // Value passed to b didn't change, so it shouldn't re-render.
1887
2352
 
1888
2353
  a.remove();
1889
2354
  });
1890
2355
 
2356
+ // Pass an object to the child.
2357
+ Testimony.test('Solarite.component.nestedNonSolarite', () => {
1891
2358
 
2359
+ let bRenderCount = 0;
2360
+ class B530 extends HTMLElement {
2361
+
2362
+ constructor({user}={}) {
2363
+ super();
2364
+ this.user = user;
2365
+ this.render();
2366
+ }
2367
+
2368
+ render(props={}) {
2369
+ if (props.user)
2370
+ this.user = props.user;
2371
+ r(this)`<div>Name:</div><div>${this.user.name}</div><div>Email:</div><div>${this.user.email}</div>`;
2372
+ bRenderCount++;
2373
+ }
2374
+ }
2375
+ customElements.define('b-530', B530);
2376
+
2377
+ class A530 extends HTMLElement {
2378
+ title = 'Users'
2379
+ user = {name: 'John', email: 'john@example.com'};
2380
+ render() {
2381
+ r(this)`${this.title}<b-530 user="${this.user}"></b-530>`
2382
+ }
2383
+ }
2384
+ customElements.define('a-530', A530);
2385
+
2386
+ let a = new A530();
2387
+ a.render();
2388
+ document.body.append(a);
2389
+ assert.eq(getHtml(a), `<a-530>Users<b-530 user=""><div>Name:</div><div>John</div><div>Email:</div><div>john@example.com</div></b-530></a-530>`)
2390
+
2391
+
2392
+ a.user = {name: 'Fred', email: 'fred@example.com'};
2393
+ a.render();
2394
+ assert.eq(getHtml(a), `<a-530>Users<b-530 user=""><div>Name:</div><div>Fred</div><div>Email:</div><div>fred@example.com</div></b-530></a-530>`)
2395
+
2396
+
2397
+ a.user.name = 'Barry'
2398
+ a.render();
2399
+ assert.eq(getHtml(a), `<a-530>Users<b-530 user=""><div>Name:</div><div>Barry</div><div>Email:</div><div>fred@example.com</div></b-530></a-530>`)
2400
+
2401
+ bRenderCount = 0
2402
+ a.render();
2403
+ assert.eq(bRenderCount, 0)
2404
+
2405
+ a.title = 'Users2'
2406
+ a.render();
2407
+ assert.eq(getHtml(a), `<a-530>Users2<b-530 user=""><div>Name:</div><div>Barry</div><div>Email:</div><div>fred@example.com</div></b-530></a-530>`)
2408
+ assert.eq(bRenderCount, 0) // Value passed to b didn't change, so it shouldn't re-render.
2409
+
2410
+ a.remove();
2411
+ });
1892
2412
 
1893
2413
  // TODO: This redraws every tr on every update.
1894
2414
  // Maybe that can be fixed when keying is supported?
1895
2415
  Testimony.test('Solarite.component.nestedTrLoop', () => {
1896
2416
 
1897
2417
  function tableRow(user) {
1898
- let tr = r(() => r`<tr><td>${user.name}</td><td>${user.email}</td></tr>`)
2418
+ let tr = r({
2419
+ render() {
2420
+ r(this)`<tr><td>${user.name}</td><td>${user.email}</td></tr>`
2421
+ }
2422
+ })
1899
2423
  return tr;
1900
2424
  }
1901
2425
 
@@ -1929,7 +2453,6 @@ Testimony.test('Solarite.component.nestedTrLoop', () => {
1929
2453
  table.remove();
1930
2454
  });
1931
2455
 
1932
-
1933
2456
  Testimony.test('Solarite.component.nestedComponentTrLoop', () => {
1934
2457
 
1935
2458
  class TR540 extends Solarite('tr') {
@@ -1973,16 +2496,20 @@ Testimony.test('Solarite.component.nestedComponentTrLoop', () => {
1973
2496
 
1974
2497
  table.remove();
1975
2498
  });
2499
+ //</editor-fold>
1976
2500
 
1977
2501
 
1978
2502
 
1979
- // Slots
1980
2503
 
2504
+ //<editor-fold desc="slots">
2505
+ /* ┌─────────────────╮
2506
+ * | Slots |
2507
+ * └─────────────────╯*/
1981
2508
  Testimony.test('Solarite.slots.basic', () => {
1982
2509
 
1983
2510
  class S10 extends Solarite {
1984
2511
  render() {
1985
- this.html = r`<div>slot content:<slot></slot></div>`
2512
+ r(this)`<div>slot content:<slot></slot></div>`
1986
2513
  }
1987
2514
  }
1988
2515
  S10.define();
@@ -2013,9 +2540,31 @@ Testimony.test('Solarite.slots.named', () => {
2013
2540
  });
2014
2541
 
2015
2542
 
2016
- // Events
2543
+ Testimony.test('Solarite.slots.slotless', `Add children even when no slots present.`, () => {
2544
+
2545
+ class S30 extends Solarite {
2546
+ render() {
2547
+ r(this)`<div>child1</div>`
2548
+ }
2549
+ }
2550
+ S30.define();
2551
+
2552
+ let div = r('<div><s-30>child2<br>child4</s-30></div>')
2553
+ document.body.append(div);
2554
+
2555
+ assert.eq(div.outerHTML, `<div><s-30><div>child1</div>child2<br>child4</s-30></div>`)
2556
+
2557
+ div.remove();
2558
+ });
2559
+ //</editor-fold>
2560
+
2561
+
2017
2562
 
2018
- // TODO:
2563
+
2564
+ //<editor-fold desc="events">
2565
+ /* ┌─────────────────╮
2566
+ * | Events |
2567
+ * └─────────────────╯*/
2019
2568
  Testimony.test('Solarite.events.classic', () => {
2020
2569
 
2021
2570
  class Ev10 extends Solarite {
@@ -2113,10 +2662,110 @@ Testimony.test('Solarite.events.onComponent', 'Event attrib on root component',
2113
2662
  assert.eq(a.count, '2');
2114
2663
  });
2115
2664
 
2665
+ Testimony.test('Solarite.events.onChild', () => {
2666
+
2667
+ class E50 extends HTMLElement {
2668
+ constructor() {
2669
+ super();
2670
+ this.items = [];
2671
+ this.render();
2672
+ }
2673
+
2674
+ addItem() {
2675
+ this.items.push(1);
2676
+ this.render();
2677
+ }
2678
+
2679
+ render() {
2680
+ r(this)`
2681
+ <e-50>
2682
+ <button onclick=${this.addItem}>Add Item</button>
2683
+ </e-50>`
2684
+ }
2685
+ }
2686
+ customElements.define('e-50', E50);
2687
+
2688
+ let e = new E50();
2689
+ e.querySelector('button').dispatchEvent(new MouseEvent('click'));
2690
+ assert.eq(e.items.length, 1);
2691
+ });
2692
+
2693
+ Testimony.test('Solarite.events.onExprChild', () => {
2694
+
2695
+ class E60 extends HTMLElement {
2696
+ constructor() {
2697
+ super();
2698
+ this.items = [];
2699
+ this.render();
2700
+ }
2701
+
2702
+ addItem() {
2703
+ this.items.push(1);
2704
+ this.render();
2705
+ }
2706
+
2707
+ render() {
2708
+ r(this)`
2709
+ <e-60>
2710
+ ${r`<button onclick=${this.addItem}>Add Item</button>`}
2711
+ </e-60>`
2712
+ }
2713
+ }
2714
+ customElements.define('e-60', E60);
2715
+
2716
+ let e = new E60();
2717
+ e.querySelector('button').dispatchEvent(new MouseEvent('click'));
2718
+ assert.eq(e.items.length, 1);
2719
+ });
2720
+
2721
+ Testimony.test('Solarite.events.loop', () => {
2722
+
2723
+ let lastButtonId = null;
2724
+
2725
+ class V70 extends Solarite {
2726
+ items = [{id: 1}, {id: 2}];
2727
+
2728
+ log(id) {
2729
+ lastButtonId = id;
2730
+ //console.log(id);
2731
+ }
2732
+
2733
+ render() {
2734
+ r(this)`
2735
+ <v-70>
2736
+ ${this.items.map(item => r`
2737
+ <button onclick=${[this.log, item.id]}>${item.id}</button>
2738
+ `)}
2739
+ </v-70>`
2740
+ }
2741
+ }
2742
+
2743
+ let v = new V70();
2744
+ document.body.append(v);
2745
+
2746
+ v.children[1].dispatchEvent(new MouseEvent('click'));
2747
+ assert.eq(lastButtonId, 2);
2748
+
2749
+ // This doesn't update the bound function to use 3.
2750
+ v.items.splice(1, 1); // remove 2.
2751
+ v.items.push({id: 3});
2752
+ window.debug = true;
2753
+ v.render();
2754
+ v.children[1].dispatchEvent(new MouseEvent('click'));
2755
+ assert.eq(lastButtonId, 3);
2756
+
2757
+
2758
+ //v.remove();
2759
+ });
2760
+ //</editor-fold>
2761
+
2116
2762
 
2117
2763
 
2118
- // Binding
2119
2764
 
2765
+ //<editor-fold desc="binding">
2766
+ /* ┌─────────────────╮
2767
+ * | Binding |
2768
+ * └─────────────────╯*/
2120
2769
  Testimony.test('Solarite.binding.input', () => {
2121
2770
 
2122
2771
  class B10 extends Solarite {
@@ -2265,8 +2914,6 @@ Testimony.test('Solarite.binding.selectDynamic', () => {
2265
2914
  b.remove();
2266
2915
  });
2267
2916
 
2268
-
2269
-
2270
2917
  Testimony.test('Solarite.binding.number', () => {
2271
2918
 
2272
2919
  class B40 extends Solarite {
@@ -2295,8 +2942,45 @@ Testimony.test('Solarite.binding.number', () => {
2295
2942
  b.remove();
2296
2943
  });
2297
2944
 
2945
+ // FIXME
2946
+ Testimony.test('Solarite.binding.number2', () => {
2947
+
2948
+ class B50 extends Solarite {
2949
+ count = 1
2950
+
2951
+ render() {
2952
+ r(this)`<input type="number" data-id="input" value=${[this, 'count']}>`
2953
+ }
2954
+ }
2955
+
2956
+ let b = new B50();
2957
+ document.body.append(b);
2958
+ assert.eq(b.input.value, '1')
2959
+
2960
+ b.count = 2;
2961
+ b.render();
2962
+ assert.eq(b.input.value, '2')
2963
+
2964
+ b.input.value = 3;
2965
+ b.input.dispatchEvent(new Event('input', {
2966
+ bubbles: true,
2967
+ cancelable: true,
2968
+ }));
2969
+ assert.eq(b.count, 3)
2970
+
2971
+ b.remove();
2972
+ });
2973
+
2974
+
2975
+ //</editor-fold>
2976
+
2977
+
2298
2978
 
2299
2979
 
2980
+ //<editor-fold desc="watch">
2981
+ /* ┌─────────────────╮
2982
+ * | Watch |
2983
+ * └─────────────────╯*/
2300
2984
  /*
2301
2985
  Testimony.test('Solarite.watched.watchSet', () => {
2302
2986
 
@@ -2837,19 +3521,121 @@ Testimony.test('Solarite.watch2._forEachSpliceInsert', () => {
2837
3521
 
2838
3522
 
2839
3523
 
3524
+ Testimony.test('Solarite.watch3.primitive', () => {
3525
+
3526
+ class W100 extends HTMLElement {
3527
+
3528
+ constructor() {
3529
+ super();
3530
+ watch3(this, 'name', 'Fred');
3531
+
3532
+
3533
+ this.render();
3534
+ }
3535
+
3536
+ render() {
3537
+ r(this)`<w-100>${this.name + '!'}</w-100>`;
3538
+ }
3539
+ }
3540
+ customElements.define('w-100', W100);
3541
+
3542
+ let a = new W100();
3543
+ document.body.append(a);
3544
+ assert(a.outerHTML, `<w-100>Fred!</w-100>`);
3545
+
3546
+ a.name = 'Jim';
3547
+ assert(a.outerHTML, `<w-100>Jim!</w-100>`);
3548
+
3549
+ a.remove();
3550
+ });
3551
+
2840
3552
 
3553
+ Testimony.test('Solarite.watch3._nodes', () => {
2841
3554
 
3555
+ class W110 extends HTMLElement {
3556
+ name = 'Fred';
3557
+ name2 = 'White';
2842
3558
 
3559
+ constructor() {
3560
+ super();
3561
+ watch3(this, 'name');
3562
+ this.render();
3563
+ }
2843
3564
 
3565
+ render() {
3566
+ r(this)`<w-110>${() => r`<div>${this.name + '!'}</div>`} ${this.name2}</w-110>`;
3567
+ }
3568
+ }
3569
+ customElements.define('w-110', W110);
2844
3570
 
3571
+ let a = new W110();
3572
+ document.body.append(a);
3573
+ assert(a.outerHTML, `<w-110><div>Fred!</div> White</w-110>`);
2845
3574
 
3575
+ a.name = 'Jim';
3576
+ assert(a.outerHTML, `<w-110><div>Jim!</div> White</w-110>`);
2846
3577
 
3578
+ a.name2 = 'Brown'
3579
+ a.render();
3580
+ assert(a.outerHTML, `<w-110><div>Jim!</div> Brown</w-110>`);
2847
3581
 
3582
+ a.remove();
3583
+ });
3584
+ //</editor-fold>
2848
3585
 
2849
3586
 
2850
- Testimony.test('Solarite.full.treeItems', () => {
2851
3587
 
2852
- //import '../src/RedComponent.js'
3588
+
3589
+ //<editor-fold desc="full">
3590
+ /* ┌─────────────────╮
3591
+ * | Full |
3592
+ * └─────────────────╯*/
3593
+
3594
+ Testimony.test('Solarite.full._todoList', () => {
3595
+ class ShoppingList extends HTMLElement {
3596
+ constructor(items=[]) {
3597
+ super();
3598
+ this.items = items;
3599
+ this.render();
3600
+ }
3601
+
3602
+ addItem() {
3603
+ this.items.push({name: '', qty: 0});
3604
+ this.render();
3605
+ }
3606
+
3607
+ removeItem(item) {
3608
+ this.items.splice(this.items.indexOf(item), 1);
3609
+ this.render();
3610
+ }
3611
+
3612
+ render() {
3613
+ r(this)`
3614
+ <shopping-list>
3615
+ <style>:host input { width: 80px }</style>
3616
+
3617
+ <button onclick=${this.addItem}>Add Item</button>
3618
+
3619
+ ${this.items.map(item => r`
3620
+ <div>
3621
+ <input placeholder="Item" value=${item.name}
3622
+ oninput=${e => { // two-way binding
3623
+ item.name = e.target.value;
3624
+ this.render()
3625
+ }}>
3626
+ </div>
3627
+ `)}
3628
+ </shopping-list>`
3629
+ }
3630
+ }
3631
+
3632
+ customElements.define('shopping-list', ShoppingList);
3633
+ document.body.append(new ShoppingList()); // add <shopping-list> element
3634
+ });
3635
+
3636
+ Testimony.test('Solarite.full._treeItems', () => {
3637
+
3638
+ //import '../src/Solarite.js'
2853
3639
 
2854
3640
  class TreeItem extends Solarite {
2855
3641
 
@@ -2866,13 +3652,13 @@ Testimony.test('Solarite.full.treeItems', () => {
2866
3652
  }
2867
3653
 
2868
3654
  render() {
2869
- this.html = r`
3655
+ r(this)`
2870
3656
  <tree-item>
2871
3657
  <style>
2872
3658
  :host #childItems { padding-left: 20px }
2873
3659
  </style>
2874
3660
  <div onclick="${this.toggleChildren}">${this.titleText}</div>
2875
- <div id="childItems" hidden="${!this.showChildren}">
3661
+ <div hidden="${!this.showChildren}">
2876
3662
  ${this.childItems}
2877
3663
  </div>
2878
3664
  </tree-item>`;
@@ -2894,3 +3680,159 @@ Testimony.test('Solarite.full.treeItems', () => {
2894
3680
  });
2895
3681
 
2896
3682
 
3683
+
3684
+
3685
+
3686
+ // An attempt to create a simpler version that reproduces the same bug as full.misc.
3687
+ // but this version always works, and doesn't reproduce the issue.
3688
+ Testimony.test('Solarite.full._isc2', () => {
3689
+ let items = [
3690
+ {name: 'Apples', qty: 1},
3691
+ {name: 'Banans', qty: 2},
3692
+ {name: 'Cherries', qty: 3}
3693
+ ]
3694
+ let toggle = false;
3695
+
3696
+ class Misc2 extends HTMLElement {
3697
+ render() {
3698
+ r(this)`
3699
+ <misc-2>
3700
+ ${items.map(item => r`
3701
+ <div>
3702
+ ${r`<span>${item.name}</span>`}
3703
+ ${r`<span>${item.qty}</span>`}
3704
+ </div>`
3705
+ )}
3706
+
3707
+ </misc-2>`;
3708
+ }
3709
+ }
3710
+
3711
+ customElements.define('misc-2', Misc2);
3712
+
3713
+ let m = new Misc2();
3714
+ document.body.append(m);
3715
+ m.render();
3716
+
3717
+
3718
+ // let temp = items[0];
3719
+ // items[0] = items[1];
3720
+ // items[1] = temp;
3721
+
3722
+ // items[0].name = 'Bananas';
3723
+ // items[0].qty = 3;
3724
+
3725
+ //items[0] = items[1];
3726
+
3727
+ toggle = !toggle;
3728
+
3729
+ m.render();
3730
+
3731
+ toggle = !toggle;
3732
+
3733
+ m.render();
3734
+
3735
+ toggle = !toggle;
3736
+
3737
+ m.render();
3738
+
3739
+ });
3740
+
3741
+
3742
+
3743
+
3744
+ Testimony.test('Solarite.full._misc', () => {
3745
+
3746
+ class Misc1 extends HTMLElement {
3747
+
3748
+ items = [];
3749
+ options = [];
3750
+
3751
+
3752
+
3753
+ constructor(options) {
3754
+ super();
3755
+ this.options = options;
3756
+ this.items = items;
3757
+
3758
+ for (let product of this.items)
3759
+ for (let option of this.options)
3760
+ option[product.id + '_showOther'] = !!(product[option.name] && option.name !== product[option.name]);
3761
+
3762
+ this.render();
3763
+ }
3764
+
3765
+ async setValue(product, option, field, value) {
3766
+ product[field] = value;
3767
+ option[product.id + '_showOther'] = !!(value && option.name !== value);
3768
+ this.render();
3769
+ }
3770
+
3771
+ render() {
3772
+ r(this)`
3773
+ <misc-1>
3774
+ ${this.items.map(item => r`
3775
+ <p>
3776
+ ${this.options.map(option => r`
3777
+ <div style="display: flex">
3778
+ ${r`<button onclick=${() => this.setValue(item, option, option.name, '')}>${option.name}</button>`}
3779
+
3780
+ ${option[item.id + '_showOther'] && r`<input>`}
3781
+ </div>`
3782
+ )}
3783
+ </p>
3784
+ <br><br>
3785
+ `)}
3786
+ </misc-1>`;
3787
+ }
3788
+ }
3789
+ customElements.define('misc-1', Misc1);
3790
+
3791
+ let options = [
3792
+ {
3793
+ name: "a"
3794
+ },
3795
+ {
3796
+ name: "b"
3797
+ },
3798
+ {
3799
+ name: "c"
3800
+ }
3801
+ ];
3802
+ let items = [
3803
+ {
3804
+ "id": 1,
3805
+ "a": 1,
3806
+ "b": 2,
3807
+ "c": 3
3808
+ },
3809
+ {
3810
+ "id": 2,
3811
+ "a": 1,
3812
+ "b": 2,
3813
+ "c": null
3814
+ }
3815
+ ];
3816
+ let sp = new Misc1(options, items);
3817
+ document.body.append(sp);
3818
+
3819
+ sp.children[0].setAttribute('style', 'border: 1px solid red');
3820
+
3821
+ sp.children[0].children[0].querySelector('input').setAttribute('style', 'border: 3px solid #f80');
3822
+ sp.children[0].children[1].querySelector('input').setAttribute('style', 'border: 3px solid #fc0');
3823
+ sp.children[0].children[2].querySelector('input').setAttribute('style', 'border: 3px solid #660');
3824
+
3825
+ sp.children[1].setAttribute('style', 'border: 1px solid blue');
3826
+
3827
+ sp.children[3].children[0].setAttribute('style', 'border: 3px solid #0f8');
3828
+ sp.children[3].children[1].setAttribute('style', 'border: 3px solid #0fc');
3829
+ sp.children[3].children[2].setAttribute('style', 'border: 3px solid #0cf');
3830
+
3831
+ // Trigger bug:
3832
+ //sp.setValue(products[0], 'b', '');
3833
+ //sp.setValue(products[0], 'c', '');
3834
+ });
3835
+
3836
+
3837
+ //<editor-fold desc="full">
3838
+