solarite 0.1.1 → 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 (45) 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 +2 -2
  8. package/build/build.js +1 -1
  9. package/dist/Solarite-debug.js +1344 -1532
  10. package/dist/Solarite.js +1298 -1334
  11. package/dist/Solarite.min.js +3 -3
  12. package/dist/udomdiff-license.txt +18 -0
  13. package/docs/index.md +440 -130
  14. package/docs/js/Playground.js +1 -1
  15. package/docs/js/codemirror/codemirror6.js +3683 -3206
  16. package/docs/js/codemirror/themeSolarIce.js +1 -1
  17. package/docs/js/documentation.js +1 -1
  18. package/docs/js/ui/CodeEditor.js +183 -45
  19. package/docs/js/ui/FlexResizer.js +14 -3
  20. package/docs/js/util/Errors.js +11 -0
  21. package/docs/media/documentation.css +6 -3
  22. package/index.html +449 -30
  23. package/package.json +1 -1
  24. package/readme.md +1 -1
  25. package/src/solarite/ExprPath.js +349 -151
  26. package/src/solarite/Globals.js +43 -1
  27. package/src/solarite/NodeGroup.js +275 -293
  28. package/src/solarite/Shell.js +26 -28
  29. package/src/solarite/Solarite.js +12 -0
  30. package/src/solarite/Template.js +55 -128
  31. package/src/solarite/Util.js +131 -7
  32. package/src/solarite/createSolarite.js +14 -19
  33. package/src/solarite/getArg.js +1 -1
  34. package/src/solarite/hash.js +18 -35
  35. package/src/solarite/r.js +127 -127
  36. package/src/solarite/watch3.js +18 -11
  37. package/src/{solarite → unused}/NodeGroupManager.js +81 -109
  38. package/src/{solarite → unused}/watch.js +1 -1
  39. package/src/{solarite → unused}/watch2.js +1 -2
  40. package/src/{solarite → util}/MultiValueMap.js +18 -11
  41. package/src/util/WeakArray.js +33 -0
  42. package/tests/Solarite.test.js +862 -167
  43. package/tests/index.html +4 -2
  44. package/tests/run.bat +1 -1
  45. package/deno.lock +0 -176
@@ -1,20 +1,21 @@
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
6
  //import {watchGet, watchSet} from "../src/solarite/watch.js";
5
7
 
6
8
  import {Solarite, r, getArg} from '../src/solarite/Solarite.js';
7
9
  //import {Solarite, r, getArg} from '../dist/Solarite.min.js'; // This will help the Benchmark test warm up.
8
- import {watch} from "../src/solarite/watch2.js";
10
+ //import {watch} from "../src/solarite/watch2.js";
9
11
  import watch3 from "../src/solarite/watch3.js";
10
- import NodeGroupManager from "../src/solarite/NodeGroupManager.js";
12
+ //import NodeGroupManager from "../src/solarite/NodeGroupManager.js";
11
13
  import NodeGroup from "../src/solarite/NodeGroup.js";
12
14
  import Template from "../src/solarite/Template.js";
13
15
  import Shell from "../src/solarite/Shell.js";
14
16
 
15
17
  import Testimony, {assert} from './Testimony.js';
16
18
 
17
-
18
19
  // This function is used by the various tests.
19
20
  window.getHtml = (item, includeComments=false) => {
20
21
  if (!item)
@@ -33,7 +34,7 @@ window.getHtml = (item, includeComments=false) => {
33
34
  if (!includeComments)
34
35
  item = item.filter(n => n.nodeType !==8)
35
36
 
36
- result = item.map(n => n.nodeType === 8 ? `<!--${n.textContent}-->` : n.outerHTML || n.textContent).join('|')
37
+ result = item.map(n => n.nodeType === 8 ? `<!--${n.textContent}-->` : n.outerHTML || n.textContent).join('|');
37
38
  }
38
39
  else
39
40
  result = item.outerHTML || item.textContent
@@ -46,24 +47,29 @@ window.getHtml = (item, includeComments=false) => {
46
47
  }
47
48
 
48
49
 
50
+
51
+ //<editor-fold desc="shell">
52
+ /* ┌─────────────────╮
53
+ * | Shell |
54
+ * └─────────────────╯*/
49
55
  Testimony.test('Solarite.Shell.empty', () => {
50
56
  let shell = new Shell(['', ''])
51
- assert.eq(getHtml(shell, true), '<!--PathStart:0-->|<!--PathEnd:0-->')
57
+ assert.eq(getHtml(shell, true), '<!--ExprPath:0-->|<!--ExprPathEnd:0-->')
52
58
  });
53
59
 
54
60
  Testimony.test('Solarite.Shell.paragraph', () => {
55
61
  let shell = new Shell(['<p>', '</p>'])
56
- assert.eq(getHtml(shell, true), '<p><!--PathStart:0--><!--PathEnd:0--></p>')
62
+ assert.eq(getHtml(shell, true), '<p><!--ExprPath:0--><!--ExprPathEnd:0--></p>')
57
63
  });
58
64
 
59
65
  Testimony.test('Solarite.Shell.nodeBefore', () => {
60
66
  let shell = new Shell(['a', ''])
61
- assert.eq(getHtml(shell, true), 'a|<!--PathEnd:0-->')
67
+ assert.eq(getHtml(shell, true), 'a|<!--ExprPathEnd:0-->')
62
68
  });
63
69
 
64
70
  Testimony.test('Solarite.Shell.nodeAfter', () => {
65
71
  let shell = new Shell(['', 'b'])
66
- assert.eq(getHtml(shell, true), '<!--PathStart:0-->|b')
72
+ assert.eq(getHtml(shell, true), '<!--ExprPath:0-->|b')
67
73
  });
68
74
 
69
75
  Testimony.test('Solarite.Shell.nodeBeforeAfter', () => {
@@ -73,12 +79,12 @@ Testimony.test('Solarite.Shell.nodeBeforeAfter', () => {
73
79
 
74
80
  Testimony.test('Solarite.Shell.emptyTwoPaths', () => {
75
81
  let shell = new Shell(['', '', ''])
76
- assert.eq(getHtml(shell, true), '<!--PathStart:0-->|<!--PathEnd:0-->|<!--PathEnd:1-->')
82
+ assert.eq(getHtml(shell, true), '<!--ExprPath:0-->|<!--ExprPathEnd:0-->|<!--ExprPathEnd:1-->')
77
83
  });
78
84
 
79
85
  Testimony.test('Solarite.Shell.nodeBetweenPaths', () => {
80
86
  let shell = new Shell(['', 'a', ''])
81
- assert.eq(getHtml(shell, true), '<!--PathStart:0-->|a|<!--PathEnd:1-->')
87
+ assert.eq(getHtml(shell, true), '<!--ExprPath:0-->|a|<!--ExprPathEnd:1-->')
82
88
  });
83
89
 
84
90
  Testimony.test('Solarite.Shell.nodesAroundPaths', () => {
@@ -88,7 +94,7 @@ Testimony.test('Solarite.Shell.nodesAroundPaths', () => {
88
94
 
89
95
  Testimony.test('Solarite.Shell.emptyTwoPathsNested', () => {
90
96
  let shell = new Shell(['<p>', '', '</p>'])
91
- assert.eq(getHtml(shell, true), '<p><!--PathStart:0--><!--PathEnd:0--><!--PathEnd:1--></p>')
97
+ assert.eq(getHtml(shell, true), '<p><!--ExprPath:0--><!--ExprPathEnd:0--><!--ExprPathEnd:1--></p>')
92
98
  });
93
99
 
94
100
  Testimony.test('Solarite.Shell.nodesAroundPathsNested', () => {
@@ -105,10 +111,17 @@ Testimony.test('Solarite.Shell.nodesAroundPathsNested', () => {
105
111
 
106
112
 
107
113
 
114
+ //</editor-fold>
115
+
116
+
117
+
118
+ //<editor-fold desc="nodegroup">
119
+ /* ┌─────────────────╮
120
+ * | NodeGroup |
121
+ * └─────────────────╯*/
108
122
  Testimony.test('Solarite.NodeGroup.empty', () => {
109
- let ngm = new NodeGroupManager(document.body);
110
123
 
111
- let ng = new NodeGroup(new Template([``], []), ngm)
124
+ let ng = new NodeGroup(new Template([``], []))
112
125
  assert.eq(getHtml(ng), '')
113
126
 
114
127
  ng.applyExprs([])
@@ -117,9 +130,8 @@ Testimony.test('Solarite.NodeGroup.empty', () => {
117
130
 
118
131
 
119
132
  Testimony.test('Solarite.NodeGroup.oneExpr', () => {
120
- let ngm = new NodeGroupManager(document.body);
121
133
 
122
- let ng = new NodeGroup(new Template([``, ``], ['1']), ngm)
134
+ let ng = new NodeGroup(new Template([``, ``], ['1']))
123
135
  assert.eq(getHtml(ng), '1')
124
136
 
125
137
  ng.applyExprs([2])
@@ -130,8 +142,7 @@ Testimony.test('Solarite.NodeGroup.oneExpr', () => {
130
142
  });
131
143
 
132
144
  Testimony.test('Solarite.NodeGroup.emptyAdjacent', () => {
133
- let ngm = new NodeGroupManager(document.body);
134
- let ng = new NodeGroup(new Template([``, ``, ``], ['1', '2']), ngm)
145
+ let ng = new NodeGroup(new Template([``, ``, ``], ['1', '2']))
135
146
  ng.verify();
136
147
 
137
148
  assert.eq(getHtml(ng), '1|2')
@@ -145,8 +156,7 @@ Testimony.test('Solarite.NodeGroup.emptyAdjacent', () => {
145
156
 
146
157
 
147
158
  Testimony.test('Solarite.NodeGroup.paragraph', () => {
148
- let ngm = new NodeGroupManager(document.body);
149
- let ng = new NodeGroup(new Template(['<p>', '</p>'], ['1']), ngm)
159
+ let ng = new NodeGroup(new Template(['<p>', '</p>'], ['1']))
150
160
  assert.eq(getHtml(ng), '<p>1</p>')
151
161
 
152
162
  ng.applyExprs([2])
@@ -157,10 +167,9 @@ Testimony.test('Solarite.NodeGroup.paragraph', () => {
157
167
  });
158
168
 
159
169
  Testimony.test('Solarite.NodeGroup.node', () => {
160
- let ngm = new NodeGroupManager(document.body);
161
170
  let a = r('<p>a</p>')
162
171
  let b = r('<p>b</p>')
163
- let ng = new NodeGroup(new Template(['<div>', '</div>'], [a]), ngm)
172
+ let ng = new NodeGroup(new Template(['<div>', '</div>'], [a]))
164
173
 
165
174
  assert.eq(getHtml(ng), '<div><p>a</p></div>')
166
175
 
@@ -175,10 +184,9 @@ Testimony.test('Solarite.NodeGroup.node', () => {
175
184
  });
176
185
 
177
186
  Testimony.test('Solarite.NodeGroup._nodeSwap', () => {
178
- let ngm = new NodeGroupManager(document.body);
179
187
  let a = r('<p>a</p>')
180
188
  let b = r('<p>b</p>')
181
- let ng = new NodeGroup(new Template(['<div>', '', '</div>'], [a, b]), ngm)
189
+ let ng = new NodeGroup(new Template(['<div>', '', '</div>'], [a, b]))
182
190
  document.body.append(ng.startNode)
183
191
 
184
192
  assert.eq(getHtml(ng), '<div><p>a</p><p>b</p></div>')
@@ -189,10 +197,9 @@ Testimony.test('Solarite.NodeGroup._nodeSwap', () => {
189
197
 
190
198
 
191
199
  Testimony.test('Solarite.NodeGroup.arrayReverse', () => {
192
- let ngm = new NodeGroupManager(document.body);
193
200
  let list = [r('a'), r('b')];
194
201
 
195
- let ng = new NodeGroup(new Template(['<div>', '</div>'], [list]), ngm)
202
+ let ng = new NodeGroup(new Template(['<div>', '</div>'], [list]))
196
203
  ng.verify();
197
204
  assert(getHtml(ng), '<div>ab</div')
198
205
 
@@ -200,7 +207,14 @@ Testimony.test('Solarite.NodeGroup.arrayReverse', () => {
200
207
  ng.applyExprs([list])
201
208
  assert(getHtml(ng), '<div>ba</div')
202
209
  });
210
+ //</editor-fold>
211
+
212
+
203
213
 
214
+ //<editor-fold desc="util">
215
+ /* ┌─────────────────╮
216
+ * | Util |
217
+ * └─────────────────╯*/
204
218
 
205
219
  Testimony.test('Solarite.Util.htmlContext', () => {
206
220
  assert.eq(htmlContext('<div class="test'), htmlContext.Attribute)
@@ -222,7 +236,14 @@ Testimony.test('Solarite.Util.camelToDashes', () => {
222
236
  assert.eq(camelToDashes('UIForm'), 'ui-form');
223
237
  assert.eq(camelToDashes('A100'), 'a-100');
224
238
  });
239
+ //</editor-fold>
225
240
 
241
+
242
+
243
+ //<editor-fold desc="basic">
244
+ /* ┌─────────────────╮
245
+ * | Basic |
246
+ * └─────────────────╯*/
226
247
  Testimony.test('Solarite.basic.empty', () => {
227
248
  class A extends HTMLElement {
228
249
  constructor() {
@@ -344,20 +365,27 @@ Testimony.test('Solarite.basic.createElement', () => {
344
365
  a.render();
345
366
  assert.eq(getHtml(a), `<r-35><div>Hello!</div></r-35>`);
346
367
  });
368
+ //</editor-fold>
347
369
 
348
370
 
349
- // Expr
371
+
372
+ //<editor-fold desc="expr">
373
+ /* ┌─────────────────╮
374
+ * | Expr |
375
+ * └─────────────────╯*/
350
376
  Testimony.test('Solarite.expr.staticString', () => {
351
- class R40 extends Solarite {
377
+ class R40 extends HTMLElement {
352
378
  render() {
353
- r(this)`Solarite ${'🟥'} Component`
379
+ r(this)`Solarite ${'Test'} Component`
354
380
  }
355
381
  }
382
+ customElements.define('r-40', R40);
356
383
 
357
384
  let a = new R40();
385
+ a.render();
358
386
  document.body.append(a);
359
387
 
360
- assert.eq(getHtml(a), '<r-40>Solarite 🟥 Component</r-40>');
388
+ assert.eq(getHtml(a), '<r-40>Solarite Test Component</r-40>');
361
389
 
362
390
  a.remove();
363
391
  });
@@ -381,7 +409,6 @@ Testimony.test('Solarite.expr.htmlString', () => {
381
409
  assert.eq(getHtml(a), '<r-42>This text is <b>Bold</b>!</r-42>');
382
410
  });
383
411
 
384
-
385
412
  Testimony.test('Solarite.expr.table', () => {
386
413
 
387
414
  class R43 extends Solarite {
@@ -408,7 +435,13 @@ Testimony.test('Solarite.expr.documentFragment', () => {
408
435
  }
409
436
  }
410
437
 
411
- 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();
412
445
  assert.eq(getHtml(a), '<r-44>This text is <b>Bold</b><i>Italic</i>!</r-44>');
413
446
  });
414
447
 
@@ -637,7 +670,6 @@ Testimony.test('Solarite.expr.staticElement', () => {
637
670
  assert.eq(getHtml(a), '<r-74>Field: <input></r-74>');
638
671
  });
639
672
 
640
-
641
673
  Testimony.test('Solarite.expr.varText', () => {
642
674
  class A extends Solarite {
643
675
  value = 'Apple';
@@ -663,8 +695,6 @@ Testimony.test('Solarite.expr.varText', () => {
663
695
  assert.eq(a.childNodes.length, 3);
664
696
  });
665
697
 
666
-
667
-
668
698
  Testimony.test('Solarite.expr.cyclicRef', () => {
669
699
 
670
700
 
@@ -690,15 +720,14 @@ Testimony.test('Solarite.expr.cyclicRef', () => {
690
720
  a.render();
691
721
  assert.eq(getHtml(a), '<r-100>The fruit is Cherry!</r-100>');
692
722
  });
723
+ //</editor-fold>
693
724
 
694
725
 
695
726
 
696
-
697
-
698
-
699
-
700
-
701
- // Loop:
727
+ //<editor-fold desc="loop">
728
+ /* ┌─────────────────╮
729
+ * | Loop |
730
+ * └─────────────────╯*/
702
731
  Testimony.test('Solarite.loop.strings', () => {
703
732
  class A extends Solarite {
704
733
  fruits = ['Apple', 'Banana'];
@@ -734,7 +763,6 @@ Testimony.test('Solarite.loop.strings', () => {
734
763
  assert.eq(getHtml(a), '<r-200>Apple</r-200>');
735
764
  });
736
765
 
737
-
738
766
  Testimony.test('Solarite.loop.paragraphs', () => {
739
767
  class A extends Solarite {
740
768
  fruits = ['Apple', 'Banana'];
@@ -745,7 +773,8 @@ Testimony.test('Solarite.loop.paragraphs', () => {
745
773
  }
746
774
  customElements.define('r-210', A);
747
775
  let a = new A();
748
- document.body.append(a);
776
+
777
+ a.render();
749
778
 
750
779
  assert.eq(getHtml(a), '<r-210><p>Apple</p><p>Banana</p></r-210>');
751
780
 
@@ -754,6 +783,7 @@ Testimony.test('Solarite.loop.paragraphs', () => {
754
783
  assert.eq(getHtml(a), '<r-210><p>Apple</p><p>Banana</p><p>Cherry</p></r-210>');
755
784
 
756
785
  a.fruits.pop();
786
+ window.debug = true;
757
787
  a.render();
758
788
  assert.eq(getHtml(a), '<r-210><p>Apple</p><p>Banana</p></r-210>');
759
789
 
@@ -772,8 +802,7 @@ Testimony.test('Solarite.loop.paragraphs', () => {
772
802
  a.remove();
773
803
  });
774
804
 
775
-
776
- Testimony.test('Solarite.loop.paragraphsBefore', () => {
805
+ Testimony.test('Solarite.loop.paragraphsBefore', `Same as above, but with another element afterward.`, () => {
777
806
  class A extends Solarite {
778
807
  fruits = ['Apple', 'Banana'];
779
808
 
@@ -810,6 +839,54 @@ Testimony.test('Solarite.loop.paragraphsBefore', () => {
810
839
  a.remove();
811
840
  });
812
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
+
813
890
  Testimony.test('Solarite.loop.eventBindings', async () => {
814
891
  await new Promise((resolve, reject) => {
815
892
  let callCount = 0;
@@ -844,7 +921,7 @@ Testimony.test('Solarite.loop.eventBindings', async () => {
844
921
 
845
922
  });
846
923
 
847
-
924
+ // TODO: Why is this called pathCache? What does it test?
848
925
  Testimony.test('Solarite.loop.pathCache', () => {
849
926
 
850
927
  class R216 extends Solarite {
@@ -861,15 +938,13 @@ Testimony.test('Solarite.loop.pathCache', () => {
861
938
  }
862
939
 
863
940
  let a = new R216();
864
- document.body.append(a);
865
941
  a.render();
866
942
  assert.eq(getHtml(a), `<r-216><p>Item</p><p>Item</p></r-216>`);
867
943
 
868
944
  a.fruits.shift();
945
+ window.debug = true;
869
946
  a.render();
870
947
  assert.eq(getHtml(a), `<r-216><p>Item</p></r-216>`);
871
-
872
- a.remove();
873
948
  });
874
949
 
875
950
  Testimony.test('Solarite.loop.nested', () => {
@@ -919,6 +994,7 @@ Testimony.test('Solarite.loop.nested', () => {
919
994
 
920
995
  a.pets.reverse();
921
996
  a.fruits.reverse();
997
+ window.debug = true;
922
998
  a.render();
923
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>`);
924
1000
 
@@ -994,7 +1070,6 @@ Testimony.test('Solarite.loop.nested2', () => {
994
1070
  a.remove();
995
1071
  });
996
1072
 
997
-
998
1073
  Testimony.test('Solarite.loop.nested3', `Move items from one sublist to another.`, () => {
999
1074
 
1000
1075
  class A225 extends Solarite {
@@ -1016,6 +1091,7 @@ Testimony.test('Solarite.loop.nested3', `Move items from one sublist to another.
1016
1091
 
1017
1092
  let a = new A225();
1018
1093
  document.body.append(a);
1094
+ window.debug = true;
1019
1095
  a.render();
1020
1096
 
1021
1097
  let cherry = a.fruitGroups[1].pop();
@@ -1025,15 +1101,12 @@ Testimony.test('Solarite.loop.nested3', `Move items from one sublist to another.
1025
1101
 
1026
1102
  let banana = a.fruitGroups[1].pop();
1027
1103
  a.fruitGroups[0].push(banana);
1028
- //window.debug = true;
1029
1104
  a.render();
1030
-
1105
+
1031
1106
  window.verify = false;
1032
1107
  a.remove();
1033
1108
  });
1034
1109
 
1035
-
1036
-
1037
1110
  // Tried to make a simpler version of nested3, but it works fine:
1038
1111
  Testimony.test('Solarite.loop.nested4', () => {
1039
1112
 
@@ -1068,14 +1141,6 @@ Testimony.test('Solarite.loop.nested4', () => {
1068
1141
  a.remove();
1069
1142
  });
1070
1143
 
1071
-
1072
-
1073
-
1074
-
1075
-
1076
-
1077
-
1078
-
1079
1144
  Testimony.test('Solarite.loop.nested5', () => {
1080
1145
 
1081
1146
  // This test was originally created by reducing a failure in production code
@@ -1095,7 +1160,7 @@ Testimony.test('Solarite.loop.nested5', () => {
1095
1160
  ]
1096
1161
 
1097
1162
  render() {
1098
- this.html = r`
1163
+ r(this)`
1099
1164
  <a-227>${this.boxes.map(item =>
1100
1165
  r`${item.map(item2 =>
1101
1166
  r`<div title=${v}>${item2}</div>`
@@ -1120,28 +1185,12 @@ Testimony.test('Solarite.loop.nested5', () => {
1120
1185
  ]
1121
1186
  a.render();
1122
1187
 
1123
- 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>`);
1124
1189
 
1125
1190
  window.verify = false;
1126
1191
  a.remove();
1127
1192
  });
1128
1193
 
1129
-
1130
-
1131
-
1132
-
1133
-
1134
-
1135
-
1136
-
1137
-
1138
-
1139
-
1140
-
1141
-
1142
-
1143
-
1144
-
1145
1194
  Testimony.test('Solarite.loop.nestedConditional', () => {
1146
1195
 
1147
1196
  let isGoodBoy = true;
@@ -1226,7 +1275,6 @@ Testimony.test('Solarite.loop.conditionalNested', () => {
1226
1275
  a.remove();
1227
1276
  });
1228
1277
 
1229
-
1230
1278
  Testimony.test('Solarite.loop.tripleNested', 'Triple nested grid', () => {
1231
1279
 
1232
1280
  class R250 extends Solarite {
@@ -1292,12 +1340,15 @@ Testimony.test('Solarite.loop.tripleNested', 'Triple nested grid', () => {
1292
1340
  a.remove();
1293
1341
  });
1294
1342
 
1343
+ //</editor-fold>
1295
1344
 
1296
1345
 
1297
1346
 
1298
1347
 
1299
-
1300
-
1348
+ //<editor-fold desc="embed">
1349
+ /* ┌─────────────────╮
1350
+ * | Embed |
1351
+ * └─────────────────╯*/
1301
1352
 
1302
1353
  Testimony.test('Solarite.embed.styleStatic', () => {
1303
1354
  let count = 0;
@@ -1350,7 +1401,6 @@ Testimony.test('Solarite.embed.optionsNoStyles', () => {
1350
1401
  a.remove();
1351
1402
  });
1352
1403
 
1353
-
1354
1404
  Testimony.test('Solarite.embed.styleDynamic', () => {
1355
1405
  let style1 = `:host { color: red }`
1356
1406
  let style2 = `:host { font-weight: bold }`
@@ -1424,10 +1474,6 @@ Testimony.test('Solarite.embed.styleDynamicTag', () => {
1424
1474
  a.remove();
1425
1475
  });
1426
1476
 
1427
-
1428
-
1429
-
1430
-
1431
1477
  Testimony.test('Solarite.embed.svg', () => {
1432
1478
  class R330 extends Solarite {
1433
1479
  render() {
@@ -1505,8 +1551,16 @@ Testimony.test('Solarite.embed._scriptDynamic', () => {
1505
1551
  delete window.scriptStaticCount;
1506
1552
  });
1507
1553
 
1554
+ //</editor-fold>
1555
+
1508
1556
 
1509
1557
 
1558
+
1559
+ //<editor-fold desc="attrib">
1560
+ /* ┌─────────────────╮
1561
+ * | Attrib |
1562
+ * └─────────────────╯*/
1563
+
1510
1564
  Testimony.test('Solarite.attrib.single', () => {
1511
1565
 
1512
1566
  let val = 'one';
@@ -1576,7 +1630,6 @@ Testimony.test('Solarite.attrib.double', () => {
1576
1630
  assert.eq(getHtml(a), `<r-420><div class="oneBtwoB">oneB</div></r-420>`)
1577
1631
  });
1578
1632
 
1579
-
1580
1633
  Testimony.test('Solarite.attrib.doubleAndText', () => {
1581
1634
 
1582
1635
  let val1 = 'one';
@@ -1690,12 +1743,11 @@ Testimony.test('Solarite.attrib.toggle', () => {
1690
1743
  assert.eq(getHtml(a), `<r-460><div></div></r-460>`)
1691
1744
  });
1692
1745
 
1693
-
1694
1746
  Testimony.test('Solarite.attrib.pseudoRoot', () => {
1695
1747
  let title = 'Hello'
1696
1748
  class R470 extends Solarite {
1697
1749
  render() {
1698
- this.html = r`<r-470 title="${title}">World</r-470>`
1750
+ r(this)`<r-470 title="${title}">World</r-470>`
1699
1751
  }
1700
1752
  }
1701
1753
 
@@ -1713,7 +1765,6 @@ Testimony.test('Solarite.attrib.pseudoRoot', () => {
1713
1765
  assert.eq(a.outerHTML, `<r-470>World</r-470>`)
1714
1766
  });
1715
1767
 
1716
-
1717
1768
  Testimony.test('Solarite.attrib.pseudoRoot2', 'Static attribute overrides.', () => {
1718
1769
  let title = 'Hello'
1719
1770
  class R472 extends Solarite {
@@ -1775,9 +1826,59 @@ Testimony.test('Solarite.attrib.multiple', '', () => {
1775
1826
  assert.eq(getHtml(a), `<r-474><button onclick="" class="primary">Hello</button></r-474>`);
1776
1827
  });
1777
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
+
1778
1875
 
1779
1876
 
1780
- Testimony.test('Solarite.comments', () => {
1877
+ //<editor-fold desc="comments">
1878
+ /* ┌─────────────────╮
1879
+ * | comments |
1880
+ * └─────────────────╯*/
1881
+ Testimony.test('Solarite.comments.one', () => {
1781
1882
 
1782
1883
  class A480 extends Solarite {
1783
1884
  render() {
@@ -1792,7 +1893,7 @@ Testimony.test('Solarite.comments', () => {
1792
1893
  a.remove();
1793
1894
  });
1794
1895
 
1795
- Testimony.test('Solarite.comments2', () => {
1896
+ Testimony.test('Solarite.comments.two', () => {
1796
1897
 
1797
1898
  class A482 extends Solarite {
1798
1899
  render() {
@@ -1805,33 +1906,75 @@ Testimony.test('Solarite.comments2', () => {
1805
1906
  assert.eq(getHtml(a), `<a-482><div>3</div></a-482>`)
1806
1907
  a.remove();
1807
1908
  });
1909
+ //</editor-fold>
1808
1910
 
1809
1911
 
1810
1912
 
1811
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
+ })
1812
1923
 
1813
- Testimony.test('Solarite.ids', () => {
1814
- class R500 extends Solarite {
1815
- one;
1816
- render() {
1817
- r(this)`<div data-id="one"></div>`;
1818
- }
1819
- }
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
+ })
1820
1930
 
1821
- let a = new R500();
1822
- 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
+ })
1823
1937
 
1824
- 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>`)
1954
+ });
1955
+
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>`)
1825
1960
  });
1826
1961
 
1827
- Testimony.test('Solarite.r._createElement', () => {
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
+ });
1828
1967
 
1829
- let button = r()`<button>hi</button>`
1830
- console.log(button.outerHTML)
1831
- document.body.append(button);
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>`)
1832
1974
  })
1833
1975
 
1834
1976
 
1977
+
1835
1978
  Testimony.test('Solarite.r.element', () => {
1836
1979
  let adjective = 'better'
1837
1980
  let button = r()`<button>I'm a <b>${adjective}</b> button</button>`;
@@ -1839,56 +1982,193 @@ Testimony.test('Solarite.r.element', () => {
1839
1982
  assert.eq(getHtml(button), `<button>I'm a <b>better</b> button</button>`)
1840
1983
  })
1841
1984
 
1985
+ Testimony.test('Solarite.r.standalone1', () => {
1986
+ let button = r({
1987
+ count: 0,
1842
1988
 
1989
+ inc() {
1990
+ this.count++;
1991
+ this.render();
1992
+ },
1843
1993
 
1844
- Testimony.test('Solarite.r.lightweightComponent', () => {
1845
- function betterButton(count=0) {
1846
- // Pass a function to r() and it becomes the render() function.
1847
- let result = r(
1848
- () => r`<button onclick=${(ev, self)=>{count++; self.render()}}>I'm a ${count}X better button</button>`
1849
- );
1850
- result.inc = () => {
1851
- count++;
1852
- result.render();
1994
+ render() {
1995
+ r(this)`<button onclick=${this.inc}>I've been clicked ${this.count} times.</button>`
1853
1996
  }
1854
- return result;
1855
- }
1997
+ });
1998
+ //document.body.append(button);
1856
1999
 
1857
- let button = betterButton(3);
1858
- 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>`)
1859
2001
 
1860
2002
  button.inc();
1861
- assert.eq(getHtml(button), `<button onclick="">I'm a 4X better button</button>`)
1862
- })
2003
+ assert.eq(getHtml(button), `<button onclick="">I've been clicked 1 times.</button>`);
1863
2004
 
1864
- Testimony.test('Solarite.r.lightweightComponent2', () => {
2005
+ button.dispatchEvent(new MouseEvent('click'));
2006
+ assert.eq(getHtml(button), `<button onclick="">I've been clicked 2 times.</button>`);
1865
2007
 
1866
- // Provide a function plus an object of other methods.
1867
- let count = 0;
1868
- let button = r(
1869
- () => r`
1870
- <button onclick=${(ev, self) => {
1871
- count++;
1872
- self.render();
1873
- }}>I've been clicked ${count} times.</button>`,
1874
- {
1875
- inc() {
1876
- count++;
1877
- this.render();
1878
- }
2008
+ //button.remove();
2009
+ });
2010
+
2011
+ Testimony.test('Solarite.r.standalone2', () => {
2012
+ let list = r({
2013
+ items: [],
2014
+
2015
+ add() {
2016
+ this.items.push('Item ' + this.items.length);
2017
+ this.render();
2018
+ },
2019
+
2020
+ render() {
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>`
1879
2029
  }
1880
- );
2030
+ });
1881
2031
 
1882
- assert.eq(getHtml(button), `<button onclick="">I've been clicked 0 times.</button>`)
2032
+ //document.body.append(list);
1883
2033
 
1884
- button.inc();
1885
- assert.eq(getHtml(button), `<button onclick="">I've been clicked 1 times.</button>`)
2034
+ assert.eq(getHtml(list), `<div><button onclick="">Add Item</button><hr></div>`);
2035
+
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();
2040
+
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>`);
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();
2050
+ });
2051
+
2052
+ Testimony.test('Solarite.r.standaloneId', "Test id's on standalone elmenets.", () => {
2053
+ let list = r({
2054
+ items: [],
2055
+
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 }`);
1886
2084
  });
1887
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: [],
1888
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
+ });
1889
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>
1890
2164
 
1891
2165
 
2166
+
2167
+
2168
+ //<editor-fold desc="component">
2169
+ /* ┌─────────────────╮
2170
+ * | Component |
2171
+ * └─────────────────╯*/
1892
2172
  Testimony.test('Solarite.component.tr', () => {
1893
2173
 
1894
2174
  class TR510 extends Solarite('tr') {
@@ -1908,7 +2188,6 @@ Testimony.test('Solarite.component.tr', () => {
1908
2188
 
1909
2189
  });
1910
2190
 
1911
-
1912
2191
  Testimony.test('Solarite.component.staticAttribs', () => {
1913
2192
  // Note that all attribs become lowercase.
1914
2193
  // Not sure how to prevent this w/o using an xml doctype.
@@ -1935,11 +2214,9 @@ Testimony.test('Solarite.component.staticAttribs', () => {
1935
2214
  let a = new A512();
1936
2215
  a.render();
1937
2216
 
1938
- 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>`);
1939
2218
  });
1940
2219
 
1941
-
1942
-
1943
2220
  Testimony.test('Solarite.component.dynamicAttribs', 'Attribs specified via ${...}', () => {
1944
2221
 
1945
2222
  class B513 extends Solarite {
@@ -1965,7 +2242,7 @@ Testimony.test('Solarite.component.dynamicAttribs', 'Attribs specified via ${...
1965
2242
  let a = new A513();
1966
2243
  a.render();
1967
2244
 
1968
- 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>`);
1969
2246
  });
1970
2247
 
1971
2248
  Testimony.test('Solarite.component.getArg', 'Attribs specified html when not nested in another Solarite component.', () => {
@@ -1989,13 +2266,10 @@ Testimony.test('Solarite.component.getArg', 'Attribs specified html when not nes
1989
2266
  let div = document.createElement('div');
1990
2267
  div.innerHTML = `<b-514 name="User" userid="2"></b-514>`
1991
2268
 
1992
- assert.eq(div.outerHTML, `<div><b-514 name="User" userid="2"><!--PathStart:0-->User | 2<!--PathEnd:1--></b-514></div>`)
2269
+ assert.eq(div.outerHTML, `<div><b-514 name="User" userid="2"><!--ExprPath:0-->User | 2<!--ExprPathEnd:1--></b-514></div>`)
1993
2270
 
1994
2271
  });
1995
2272
 
1996
-
1997
-
1998
-
1999
2273
  Testimony.test('Solarite.component.nested', () => {
2000
2274
 
2001
2275
  let bRenderCount = 0;
@@ -2020,11 +2294,10 @@ Testimony.test('Solarite.component.nested', () => {
2020
2294
  assert(!(a.firstChild instanceof B515))
2021
2295
 
2022
2296
  a.render();
2023
- assert(a.firstChild instanceof B515);
2297
+ assert(a.firstChild instanceof B515);
2024
2298
  assert.eq(getHtml(a), `<a-515><b-515><div>B</div></b-515></a-515>`);
2025
2299
  })
2026
2300
 
2027
-
2028
2301
  // Pass an object to the child.
2029
2302
  Testimony.test('Solarite.component.nested2', () => {
2030
2303
 
@@ -2075,19 +2348,78 @@ Testimony.test('Solarite.component.nested2', () => {
2075
2348
  a.title = 'Users2'
2076
2349
  a.render();
2077
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>`)
2078
- 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.
2079
2352
 
2080
2353
  a.remove();
2081
2354
  });
2082
2355
 
2356
+ // Pass an object to the child.
2357
+ Testimony.test('Solarite.component.nestedNonSolarite', () => {
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>`)
2083
2390
 
2084
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
+ });
2412
+
2085
2413
  // TODO: This redraws every tr on every update.
2086
2414
  // Maybe that can be fixed when keying is supported?
2087
2415
  Testimony.test('Solarite.component.nestedTrLoop', () => {
2088
2416
 
2089
2417
  function tableRow(user) {
2090
- 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
+ })
2091
2423
  return tr;
2092
2424
  }
2093
2425
 
@@ -2121,7 +2453,6 @@ Testimony.test('Solarite.component.nestedTrLoop', () => {
2121
2453
  table.remove();
2122
2454
  });
2123
2455
 
2124
-
2125
2456
  Testimony.test('Solarite.component.nestedComponentTrLoop', () => {
2126
2457
 
2127
2458
  class TR540 extends Solarite('tr') {
@@ -2165,16 +2496,20 @@ Testimony.test('Solarite.component.nestedComponentTrLoop', () => {
2165
2496
 
2166
2497
  table.remove();
2167
2498
  });
2499
+ //</editor-fold>
2168
2500
 
2169
2501
 
2170
2502
 
2171
- // Slots
2172
2503
 
2504
+ //<editor-fold desc="slots">
2505
+ /* ┌─────────────────╮
2506
+ * | Slots |
2507
+ * └─────────────────╯*/
2173
2508
  Testimony.test('Solarite.slots.basic', () => {
2174
2509
 
2175
2510
  class S10 extends Solarite {
2176
2511
  render() {
2177
- this.html = r`<div>slot content:<slot></slot></div>`
2512
+ r(this)`<div>slot content:<slot></slot></div>`
2178
2513
  }
2179
2514
  }
2180
2515
  S10.define();
@@ -2205,9 +2540,31 @@ Testimony.test('Solarite.slots.named', () => {
2205
2540
  });
2206
2541
 
2207
2542
 
2208
- // 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
+
2209
2562
 
2210
- // TODO:
2563
+
2564
+ //<editor-fold desc="events">
2565
+ /* ┌─────────────────╮
2566
+ * | Events |
2567
+ * └─────────────────╯*/
2211
2568
  Testimony.test('Solarite.events.classic', () => {
2212
2569
 
2213
2570
  class Ev10 extends Solarite {
@@ -2305,10 +2662,110 @@ Testimony.test('Solarite.events.onComponent', 'Event attrib on root component',
2305
2662
  assert.eq(a.count, '2');
2306
2663
  });
2307
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>
2308
2761
 
2309
2762
 
2310
- // Binding
2311
2763
 
2764
+
2765
+ //<editor-fold desc="binding">
2766
+ /* ┌─────────────────╮
2767
+ * | Binding |
2768
+ * └─────────────────╯*/
2312
2769
  Testimony.test('Solarite.binding.input', () => {
2313
2770
 
2314
2771
  class B10 extends Solarite {
@@ -2457,8 +2914,6 @@ Testimony.test('Solarite.binding.selectDynamic', () => {
2457
2914
  b.remove();
2458
2915
  });
2459
2916
 
2460
-
2461
-
2462
2917
  Testimony.test('Solarite.binding.number', () => {
2463
2918
 
2464
2919
  class B40 extends Solarite {
@@ -2487,8 +2942,45 @@ Testimony.test('Solarite.binding.number', () => {
2487
2942
  b.remove();
2488
2943
  });
2489
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
+
2490
2978
 
2491
2979
 
2980
+ //<editor-fold desc="watch">
2981
+ /* ┌─────────────────╮
2982
+ * | Watch |
2983
+ * └─────────────────╯*/
2492
2984
  /*
2493
2985
  Testimony.test('Solarite.watched.watchSet', () => {
2494
2986
 
@@ -3032,16 +3524,17 @@ Testimony.test('Solarite.watch2._forEachSpliceInsert', () => {
3032
3524
  Testimony.test('Solarite.watch3.primitive', () => {
3033
3525
 
3034
3526
  class W100 extends HTMLElement {
3035
- name = 'Fred';
3036
3527
 
3037
3528
  constructor() {
3038
3529
  super();
3039
- watch3(this, 'name');
3530
+ watch3(this, 'name', 'Fred');
3531
+
3532
+
3040
3533
  this.render();
3041
3534
  }
3042
3535
 
3043
3536
  render() {
3044
- r(this)`<w-100>${() => this.name + '!'}</w-100>`;
3537
+ r(this)`<w-100>${this.name + '!'}</w-100>`;
3045
3538
  }
3046
3539
  }
3047
3540
  customElements.define('w-100', W100);
@@ -3057,7 +3550,7 @@ Testimony.test('Solarite.watch3.primitive', () => {
3057
3550
  });
3058
3551
 
3059
3552
 
3060
- Testimony.test('Solarite.watch3.nodes', () => {
3553
+ Testimony.test('Solarite.watch3._nodes', () => {
3061
3554
 
3062
3555
  class W110 extends HTMLElement {
3063
3556
  name = 'Fred';
@@ -3088,15 +3581,61 @@ Testimony.test('Solarite.watch3.nodes', () => {
3088
3581
 
3089
3582
  a.remove();
3090
3583
  });
3584
+ //</editor-fold>
3091
3585
 
3092
3586
 
3093
3587
 
3094
3588
 
3589
+ //<editor-fold desc="full">
3590
+ /* ┌─────────────────╮
3591
+ * | Full |
3592
+ * └─────────────────╯*/
3095
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
+ }
3096
3601
 
3097
- Testimony.test('Solarite.full.treeItems', () => {
3602
+ addItem() {
3603
+ this.items.push({name: '', qty: 0});
3604
+ this.render();
3605
+ }
3098
3606
 
3099
- //import '../src/RedComponent.js'
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'
3100
3639
 
3101
3640
  class TreeItem extends Solarite {
3102
3641
 
@@ -3141,3 +3680,159 @@ Testimony.test('Solarite.full.treeItems', () => {
3141
3680
  });
3142
3681
 
3143
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
+