glass-easel 0.1.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 (237) hide show
  1. package/README.md +40 -0
  2. package/dist/glass_easel.all.d.ts +1 -0
  3. package/dist/glass_easel.all.js +2 -0
  4. package/dist/glass_easel.all.js.map +1 -0
  5. package/dist/glass_easel.domlike.global.d.ts +1 -0
  6. package/dist/glass_easel.domlike.global.js +2 -0
  7. package/dist/glass_easel.domlike.global.js.map +1 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +1 -0
  10. package/dist/types/src/backend/backend_protocol.d.ts +119 -0
  11. package/dist/types/src/backend/backend_protocol.d.ts.map +1 -0
  12. package/dist/types/src/backend/composed_backend_protocol.d.ts +90 -0
  13. package/dist/types/src/backend/composed_backend_protocol.d.ts.map +1 -0
  14. package/dist/types/src/backend/domlike_backend_protocol.d.ts +76 -0
  15. package/dist/types/src/backend/domlike_backend_protocol.d.ts.map +1 -0
  16. package/dist/types/src/backend/mode.d.ts +46 -0
  17. package/dist/types/src/backend/mode.d.ts.map +1 -0
  18. package/dist/types/src/backend/suggested_backend_protocol.d.ts +30 -0
  19. package/dist/types/src/backend/suggested_backend_protocol.d.ts.map +1 -0
  20. package/dist/types/src/behavior.d.ts +428 -0
  21. package/dist/types/src/behavior.d.ts.map +1 -0
  22. package/dist/types/src/class_list.d.ts +79 -0
  23. package/dist/types/src/class_list.d.ts.map +1 -0
  24. package/dist/types/src/component.d.ts +291 -0
  25. package/dist/types/src/component.d.ts.map +1 -0
  26. package/dist/types/src/component_params.d.ts +239 -0
  27. package/dist/types/src/component_params.d.ts.map +1 -0
  28. package/dist/types/src/component_space.d.ts +164 -0
  29. package/dist/types/src/component_space.d.ts.map +1 -0
  30. package/dist/types/src/data_path.d.ts +5 -0
  31. package/dist/types/src/data_path.d.ts.map +1 -0
  32. package/dist/types/src/data_proxy.d.ts +107 -0
  33. package/dist/types/src/data_proxy.d.ts.map +1 -0
  34. package/dist/types/src/data_utils.d.ts +3 -0
  35. package/dist/types/src/data_utils.d.ts.map +1 -0
  36. package/dist/types/src/element.d.ts +275 -0
  37. package/dist/types/src/element.d.ts.map +1 -0
  38. package/dist/types/src/element_iterator.d.ts +43 -0
  39. package/dist/types/src/element_iterator.d.ts.map +1 -0
  40. package/dist/types/src/event.d.ts +104 -0
  41. package/dist/types/src/event.d.ts.map +1 -0
  42. package/dist/types/src/external_shadow_tree.d.ts +20 -0
  43. package/dist/types/src/external_shadow_tree.d.ts.map +1 -0
  44. package/dist/types/src/func_arr.d.ts +39 -0
  45. package/dist/types/src/func_arr.d.ts.map +1 -0
  46. package/dist/types/src/global_options.d.ts +111 -0
  47. package/dist/types/src/global_options.d.ts.map +1 -0
  48. package/dist/types/src/index.d.ts +43 -0
  49. package/dist/types/src/index.d.ts.map +1 -0
  50. package/dist/types/src/mutation_observer.d.ts +79 -0
  51. package/dist/types/src/mutation_observer.d.ts.map +1 -0
  52. package/dist/types/src/native_node.d.ts +8 -0
  53. package/dist/types/src/native_node.d.ts.map +1 -0
  54. package/dist/types/src/node.d.ts +49 -0
  55. package/dist/types/src/node.d.ts.map +1 -0
  56. package/dist/types/src/relation.d.ts +47 -0
  57. package/dist/types/src/relation.d.ts.map +1 -0
  58. package/dist/types/src/render.d.ts +3 -0
  59. package/dist/types/src/render.d.ts.map +1 -0
  60. package/dist/types/src/selector.d.ts +32 -0
  61. package/dist/types/src/selector.d.ts.map +1 -0
  62. package/dist/types/src/shadow_root.d.ts +136 -0
  63. package/dist/types/src/shadow_root.d.ts.map +1 -0
  64. package/dist/types/src/template_engine.d.ts +18 -0
  65. package/dist/types/src/template_engine.d.ts.map +1 -0
  66. package/dist/types/src/text_node.d.ts +32 -0
  67. package/dist/types/src/text_node.d.ts.map +1 -0
  68. package/dist/types/src/tmpl/index.d.ts +18 -0
  69. package/dist/types/src/tmpl/index.d.ts.map +1 -0
  70. package/dist/types/src/tmpl/native_rendering.d.ts +45 -0
  71. package/dist/types/src/tmpl/native_rendering.d.ts.map +1 -0
  72. package/dist/types/src/tmpl/proc_gen_wrapper.d.ts +80 -0
  73. package/dist/types/src/tmpl/proc_gen_wrapper.d.ts.map +1 -0
  74. package/dist/types/src/tmpl/proc_gen_wrapper_dom.d.ts +50 -0
  75. package/dist/types/src/tmpl/proc_gen_wrapper_dom.d.ts.map +1 -0
  76. package/dist/types/src/tmpl/range_list_diff.d.ts +19 -0
  77. package/dist/types/src/tmpl/range_list_diff.d.ts.map +1 -0
  78. package/dist/types/src/trait_behaviors.d.ts +38 -0
  79. package/dist/types/src/trait_behaviors.d.ts.map +1 -0
  80. package/dist/types/src/virtual_node.d.ts +10 -0
  81. package/dist/types/src/virtual_node.d.ts.map +1 -0
  82. package/dist/types/tests/backend/domlike.test.d.ts +2 -0
  83. package/dist/types/tests/backend/domlike.test.d.ts.map +1 -0
  84. package/dist/types/tests/base/env.d.ts +29 -0
  85. package/dist/types/tests/base/env.d.ts.map +1 -0
  86. package/dist/types/tests/base/match.d.ts +9 -0
  87. package/dist/types/tests/base/match.d.ts.map +1 -0
  88. package/dist/types/tests/core/backend.test.d.ts +2 -0
  89. package/dist/types/tests/core/backend.test.d.ts.map +1 -0
  90. package/dist/types/tests/core/behavior.test.d.ts +2 -0
  91. package/dist/types/tests/core/behavior.test.d.ts.map +1 -0
  92. package/dist/types/tests/core/component_space.test.d.ts +2 -0
  93. package/dist/types/tests/core/component_space.test.d.ts.map +1 -0
  94. package/dist/types/tests/core/data_update.test.d.ts +2 -0
  95. package/dist/types/tests/core/data_update.test.d.ts.map +1 -0
  96. package/dist/types/tests/core/misc.test.d.ts +2 -0
  97. package/dist/types/tests/core/misc.test.d.ts.map +1 -0
  98. package/dist/types/tests/core/placeholder.test.d.ts +2 -0
  99. package/dist/types/tests/core/placeholder.test.d.ts.map +1 -0
  100. package/dist/types/tests/core/slot.test.d.ts +2 -0
  101. package/dist/types/tests/core/slot.test.d.ts.map +1 -0
  102. package/dist/types/tests/core/trait_behaviors.test.d.ts +2 -0
  103. package/dist/types/tests/core/trait_behaviors.test.d.ts.map +1 -0
  104. package/dist/types/tests/tmpl/binding_map.test.d.ts +2 -0
  105. package/dist/types/tests/tmpl/binding_map.test.d.ts.map +1 -0
  106. package/dist/types/tests/tmpl/event.test.d.ts +2 -0
  107. package/dist/types/tests/tmpl/event.test.d.ts.map +1 -0
  108. package/dist/types/tests/tmpl/expression.test.d.ts +2 -0
  109. package/dist/types/tests/tmpl/expression.test.d.ts.map +1 -0
  110. package/dist/types/tests/tmpl/lvalue.test.d.ts +2 -0
  111. package/dist/types/tests/tmpl/lvalue.test.d.ts.map +1 -0
  112. package/dist/types/tests/tmpl/native_rendering.test.d.ts +2 -0
  113. package/dist/types/tests/tmpl/native_rendering.test.d.ts.map +1 -0
  114. package/dist/types/tests/tmpl/structure.test.d.ts +2 -0
  115. package/dist/types/tests/tmpl/structure.test.d.ts.map +1 -0
  116. package/dist/types/tests/types/chaining.test.d.ts +2 -0
  117. package/dist/types/tests/types/chaining.test.d.ts.map +1 -0
  118. package/dist/types/tests/types/createElement.test.d.ts +2 -0
  119. package/dist/types/tests/types/createElement.test.d.ts.map +1 -0
  120. package/dist/types/tests/types/definition.test.d.ts +2 -0
  121. package/dist/types/tests/types/definition.test.d.ts.map +1 -0
  122. package/guide/zh_CN/advanced/binding_map_update.md +32 -0
  123. package/guide/zh_CN/advanced/build_args.md +28 -0
  124. package/guide/zh_CN/advanced/component_filter.md +70 -0
  125. package/guide/zh_CN/advanced/component_space.md +124 -0
  126. package/guide/zh_CN/advanced/custom_backend.md +53 -0
  127. package/guide/zh_CN/advanced/error_listener.md +32 -0
  128. package/guide/zh_CN/advanced/external_component.md +73 -0
  129. package/guide/zh_CN/advanced/template_engine.md +61 -0
  130. package/guide/zh_CN/appendix/backend_protocol.md +501 -0
  131. package/guide/zh_CN/appendix/list_diff_algorithm.md +406 -0
  132. package/guide/zh_CN/basic/beginning.md +94 -0
  133. package/guide/zh_CN/basic/component.md +156 -0
  134. package/guide/zh_CN/basic/event.md +169 -0
  135. package/guide/zh_CN/basic/lifetime.md +66 -0
  136. package/guide/zh_CN/basic/method.md +62 -0
  137. package/guide/zh_CN/basic/template.md +135 -0
  138. package/guide/zh_CN/data_management/advanced_update.md +170 -0
  139. package/guide/zh_CN/data_management/data_deep_copy.md +157 -0
  140. package/guide/zh_CN/data_management/data_observer.md +154 -0
  141. package/guide/zh_CN/data_management/property_early_init.md +31 -0
  142. package/guide/zh_CN/data_management/pure_data_pattern.md +21 -0
  143. package/guide/zh_CN/index.md +93 -0
  144. package/guide/zh_CN/interaction/behavior.md +52 -0
  145. package/guide/zh_CN/interaction/component_path.md +37 -0
  146. package/guide/zh_CN/interaction/generic.md +73 -0
  147. package/guide/zh_CN/interaction/placeholder.md +40 -0
  148. package/guide/zh_CN/interaction/relation.md +151 -0
  149. package/guide/zh_CN/interaction/slot.md +137 -0
  150. package/guide/zh_CN/interaction/template_import.md +94 -0
  151. package/guide/zh_CN/interaction/trait_behavior.md +117 -0
  152. package/guide/zh_CN/styling/external_class.md +46 -0
  153. package/guide/zh_CN/styling/style_isolation.md +54 -0
  154. package/guide/zh_CN/styling/virtual_host.md +52 -0
  155. package/guide/zh_CN/tree/element_iterator.md +54 -0
  156. package/guide/zh_CN/tree/mutation_observer.md +52 -0
  157. package/guide/zh_CN/tree/node_tree.md +142 -0
  158. package/guide/zh_CN/tree/node_tree_modification.md +78 -0
  159. package/guide/zh_CN/tree/selector.md +66 -0
  160. package/jest.config.js +6 -0
  161. package/jest.dts.config.js +9 -0
  162. package/jest.unit.config.js +14 -0
  163. package/package.json +28 -0
  164. package/src/backend/backend_protocol.ts +313 -0
  165. package/src/backend/composed_backend_protocol.ts +252 -0
  166. package/src/backend/domlike_backend_protocol.ts +370 -0
  167. package/src/backend/mode.ts +51 -0
  168. package/src/backend/suggested_backend_protocol.ts +83 -0
  169. package/src/behavior.ts +1655 -0
  170. package/src/bootstrap_dom_dev.js +22 -0
  171. package/src/class_list.ts +376 -0
  172. package/src/component.ts +1309 -0
  173. package/src/component_params.ts +461 -0
  174. package/src/component_space.ts +547 -0
  175. package/src/data_path.ts +225 -0
  176. package/src/data_proxy.ts +670 -0
  177. package/src/data_utils.ts +50 -0
  178. package/src/element.ts +1966 -0
  179. package/src/element_iterator.ts +158 -0
  180. package/src/event.ts +401 -0
  181. package/src/external_shadow_tree.ts +27 -0
  182. package/src/func_arr.ts +198 -0
  183. package/src/global_options.ts +242 -0
  184. package/src/index.ts +187 -0
  185. package/src/mutation_observer.ts +252 -0
  186. package/src/native_node.ts +74 -0
  187. package/src/node.ts +174 -0
  188. package/src/relation.ts +380 -0
  189. package/src/render.ts +25 -0
  190. package/src/selector.ts +218 -0
  191. package/src/shadow_root.ts +766 -0
  192. package/src/template_engine.ts +45 -0
  193. package/src/text_node.ts +149 -0
  194. package/src/tmpl/index.ts +199 -0
  195. package/src/tmpl/native_rendering.ts +175 -0
  196. package/src/tmpl/proc_gen_wrapper.ts +954 -0
  197. package/src/tmpl/proc_gen_wrapper_dom.ts +230 -0
  198. package/src/tmpl/range_list_diff.ts +443 -0
  199. package/src/trait_behaviors.ts +51 -0
  200. package/src/virtual_node.ts +51 -0
  201. package/tests/backend/domlike.test.ts +254 -0
  202. package/tests/base/env.ts +78 -0
  203. package/tests/base/match.ts +185 -0
  204. package/tests/core/backend.test.ts +144 -0
  205. package/tests/core/behavior.test.ts +546 -0
  206. package/tests/core/component_space.test.ts +212 -0
  207. package/tests/core/data_update.test.ts +461 -0
  208. package/tests/core/misc.test.ts +339 -0
  209. package/tests/core/placeholder.test.ts +180 -0
  210. package/tests/core/slot.test.ts +1495 -0
  211. package/tests/core/trait_behaviors.test.ts +153 -0
  212. package/tests/legacy/README.md +3 -0
  213. package/tests/legacy/behavior.test.js +293 -0
  214. package/tests/legacy/component.test.js +1247 -0
  215. package/tests/legacy/data_path.test.js +149 -0
  216. package/tests/legacy/data_proxy.test.js +759 -0
  217. package/tests/legacy/element_iterator.test.js +148 -0
  218. package/tests/legacy/event.test.js +849 -0
  219. package/tests/legacy/external.test.js +510 -0
  220. package/tests/legacy/extra_info.test.js +109 -0
  221. package/tests/legacy/generics.test.js +176 -0
  222. package/tests/legacy/mutation_observer.test.js +210 -0
  223. package/tests/legacy/relation.test.js +517 -0
  224. package/tests/legacy/selector.test.js +263 -0
  225. package/tests/legacy/slot.test.js +915 -0
  226. package/tests/legacy/virtual.test.js +394 -0
  227. package/tests/tmpl/binding_map.test.ts +208 -0
  228. package/tests/tmpl/event.test.ts +206 -0
  229. package/tests/tmpl/expression.test.ts +429 -0
  230. package/tests/tmpl/lvalue.test.ts +160 -0
  231. package/tests/tmpl/native_rendering.test.ts +155 -0
  232. package/tests/tmpl/structure.test.ts +998 -0
  233. package/tests/types/chaining.test.ts +614 -0
  234. package/tests/types/createElement.test.ts +82 -0
  235. package/tests/types/definition.test.ts +442 -0
  236. package/tsconfig.json +11 -0
  237. package/webpack.config.js +270 -0
@@ -0,0 +1,998 @@
1
+ import {
2
+ tmpl,
3
+ multiTmpl,
4
+ domBackend,
5
+ execWithWarn,
6
+ } from '../base/env'
7
+ import * as glassEasel from '../../src'
8
+
9
+ const domHtml = (elem: glassEasel.Element): string => {
10
+ const domElem = elem.getBackendElement() as unknown as Element
11
+ return domElem.innerHTML
12
+ }
13
+
14
+ describe('node tree structure', () => {
15
+ test('basic tree building', () => {
16
+ const def = glassEasel.registerElement({
17
+ template: tmpl(`
18
+ <div style="font-weight: bold">
19
+ <span>Hello world!</span>
20
+ </div>
21
+ `),
22
+ }).general()
23
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
24
+ expect(domHtml(elem)).toBe('<div style="font-weight: bold"><span>Hello world!</span></div>')
25
+ })
26
+
27
+ test('basic data binding', () => {
28
+ const def = glassEasel.registerElement({
29
+ template: tmpl(`
30
+ <div class="{{a}}">
31
+ <span>{{c}}</span>
32
+ </div>
33
+ `),
34
+ data: {
35
+ a: 123,
36
+ c: 'abc',
37
+ },
38
+ }).general()
39
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend).general()
40
+ expect(domHtml(elem)).toBe('<div class="123"><span>abc</span></div>')
41
+ elem.setData({
42
+ a: true,
43
+ c: false,
44
+ })
45
+ expect(domHtml(elem)).toBe('<div class="true"><span>false</span></div>')
46
+ })
47
+
48
+ test('if blocks', () => {
49
+ const def = glassEasel.registerElement({
50
+ template: tmpl(`
51
+ <div wx:if="{{cond1}}">a</div>
52
+ <div wx:if="{{cond2}}">b</div>
53
+ <div wx:else>c</div>
54
+ <div wx:if="{{cond1}}">d</div>
55
+ <div wx:elif="{{cond2}}">e</div>
56
+ <div wx:else>f</div>
57
+ `),
58
+ data: {
59
+ cond1: false,
60
+ cond2: '',
61
+ },
62
+ }).general()
63
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
64
+ expect(domHtml(elem)).toBe('<div>c</div><div>f</div>')
65
+ elem.setData({
66
+ cond1: true,
67
+ })
68
+ expect(domHtml(elem)).toBe('<div>a</div><div>c</div><div>d</div>')
69
+ elem.setData({
70
+ cond2: 1,
71
+ })
72
+ expect(domHtml(elem)).toBe('<div>a</div><div>b</div><div>d</div>')
73
+ elem.setData({
74
+ cond1: null,
75
+ })
76
+ expect(domHtml(elem)).toBe('<div>b</div><div>e</div>')
77
+ })
78
+
79
+ test('for blocks without key', () => {
80
+ const ops: Array<[number, string]> = []
81
+ const itemComp = glassEasel.registerElement({
82
+ properties: { s: { type: String } },
83
+ lifetimes: {
84
+ attached() {
85
+ ops.push([-1, (this.data.s).toString()])
86
+ },
87
+ detached() {
88
+ ops.push([-2, this.data.s.toString()])
89
+ },
90
+ },
91
+ }).general()
92
+ const def = glassEasel.registerElement({
93
+ using: {
94
+ 'x-c': itemComp,
95
+ },
96
+ template: tmpl(`
97
+ <div wx:for="{{list}}" wx:for-item="v" wx:for-index="k">
98
+ <x-c s="{{v}}">{{k}}</x-c>
99
+ </div>
100
+ `),
101
+ data: {
102
+ list: [10, 20],
103
+ },
104
+ }).general()
105
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
106
+ glassEasel.Element.pretendAttached(elem)
107
+ expect(domHtml(elem)).toBe('<div><x-c>0</x-c></div><div><x-c>1</x-c></div>')
108
+ expect(ops).toEqual([[-1, '10'], [-1, '20']])
109
+ ops.length = 0
110
+ elem.setData({
111
+ list: [20, 10],
112
+ })
113
+ expect(domHtml(elem)).toBe('<div><x-c>0</x-c></div><div><x-c>1</x-c></div>')
114
+ expect(ops).toEqual([])
115
+ ops.length = 0
116
+ elem.setData({
117
+ list: [30, 40, 50, 60],
118
+ })
119
+ expect(domHtml(elem)).toBe('<div><x-c>0</x-c></div><div><x-c>1</x-c></div><div><x-c>2</x-c></div><div><x-c>3</x-c></div>')
120
+ expect(ops).toEqual([[-1, '50'], [-1, '60']])
121
+ ops.length = 0
122
+ elem.setData({
123
+ list: [50],
124
+ })
125
+ expect(domHtml(elem)).toBe('<div><x-c>0</x-c></div>')
126
+ expect(ops).toEqual([[-2, '40'], [-2, '50'], [-2, '60']])
127
+ })
128
+
129
+ test('for blocks with key', () => {
130
+ const ops: Array<[number, string]> = []
131
+ const itemComp = glassEasel.registerElement({
132
+ properties: { s: String },
133
+ lifetimes: {
134
+ attached() {
135
+ ops.push([-1, this.data.s.toString()])
136
+ },
137
+ detached() {
138
+ ops.push([-2, this.data.s.toString()])
139
+ },
140
+ moved() {
141
+ ops.push([-3, this.data.s.toString()])
142
+ },
143
+ },
144
+ }).general()
145
+ const def = glassEasel.registerElement({
146
+ using: {
147
+ 'x-c': itemComp,
148
+ },
149
+ template: tmpl(`
150
+ <block wx:for="{{list}}" wx:key="k">
151
+ <x-c data-i="{{index}}" s="{{item.k}}:{{item.v}}">{{item.v}}</x-c>
152
+ </block>
153
+ `),
154
+ data: {
155
+ list: [{ k: 'a', v: 10 }, { k: 'b', v: 20 }],
156
+ },
157
+ }).general()
158
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
159
+ const listBlock = elem.getShadowRoot()!.childNodes[0] as glassEasel.VirtualNode
160
+ const checkIndex = () => {
161
+ for (let i = 0; i < listBlock.childNodes.length; i += 1) {
162
+ const itemBlock = listBlock.childNodes[i] as glassEasel.Element
163
+ expect((itemBlock.childNodes[0] as glassEasel.Element).dataset!.i).toBe(i)
164
+ }
165
+ }
166
+ glassEasel.Element.pretendAttached(elem)
167
+ expect(domHtml(elem)).toBe('<x-c>10</x-c><x-c>20</x-c>')
168
+ checkIndex()
169
+ expect(ops).toEqual([[-1, 'a:10'], [-1, 'b:20']])
170
+ ops.length = 0
171
+ elem.setData({
172
+ list: [{ k: 'c', v: 30 }, { k: 'a', v: 40 }, { k: 'd', v: 50 }, { k: 'b', v: 60 }, { k: 'e', v: 70 }],
173
+ })
174
+ expect(domHtml(elem)).toBe('<x-c>30</x-c><x-c>40</x-c><x-c>50</x-c><x-c>60</x-c><x-c>70</x-c>')
175
+ checkIndex()
176
+ expect(ops).toEqual([[-1, 'c:30'], [-1, 'd:50'], [-1, 'e:70']])
177
+ ops.length = 0
178
+ elem.setData({
179
+ list: [{ k: 'c', v: 30 }, { k: 'b', v: 60 }, { k: 'a', v: 40 }, { k: 'd', v: 50 }, { k: 'e', v: 70 }],
180
+ })
181
+ expect(domHtml(elem)).toBe('<x-c>30</x-c><x-c>60</x-c><x-c>40</x-c><x-c>50</x-c><x-c>70</x-c>')
182
+ checkIndex()
183
+ expect(ops).toEqual([[-3, 'b:60']])
184
+ ops.length = 0
185
+ elem.setData({
186
+ list: [{ k: 'c', v: 30 }, { k: 'b', v: 40 }, { k: 'd', v: 50 }, { k: 'e', v: 60 }, { k: 'a', v: 70 }],
187
+ })
188
+ expect(domHtml(elem)).toBe('<x-c>30</x-c><x-c>40</x-c><x-c>50</x-c><x-c>60</x-c><x-c>70</x-c>')
189
+ checkIndex()
190
+ expect(ops).toEqual([[-3, 'a:40']])
191
+ ops.length = 0
192
+ elem.setData({
193
+ list: [{ k: 'c', v: 30 }, { k: 'b', v: 40 }, { k: 'd', v: 50 }, { k: 'e', v: 60 }, { k: 'a', v: 70 }],
194
+ })
195
+ expect(domHtml(elem)).toBe('<x-c>30</x-c><x-c>40</x-c><x-c>50</x-c><x-c>60</x-c><x-c>70</x-c>')
196
+ checkIndex()
197
+ expect(ops).toEqual([])
198
+ ops.length = 0
199
+ elem.setData({
200
+ list: [{ k: 'b', v: 80 }, { k: 'e', v: 90 }],
201
+ })
202
+ expect(domHtml(elem)).toBe('<x-c>80</x-c><x-c>90</x-c>')
203
+ checkIndex()
204
+ expect(ops).toEqual([[-2, 'c:30'], [-2, 'd:50'], [-2, 'a:70']])
205
+ })
206
+
207
+ test('nested for blocks', () => {
208
+ const def = glassEasel.registerElement({
209
+ template: tmpl(`
210
+ <block wx:for="{{list}}" wx:for-index="i" wx:for-item="item">
211
+ <div id="{{j}}{{i}}" wx:for="{{item}}" wx:for-index="j">{{item}}</div>
212
+ </block>
213
+ `),
214
+ data: {
215
+ list: [
216
+ { a: '1', b: '2' },
217
+ { c: '3', d: '4' },
218
+ ],
219
+ },
220
+ }).general()
221
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
222
+ expect((elem.getShadowRoot()!.getElementById('a0')!.childNodes[0] as glassEasel.TextNode).textContent).toBe('1')
223
+ expect((elem.getShadowRoot()!.getElementById('b0')!.childNodes[0] as glassEasel.TextNode).textContent).toBe('2')
224
+ expect((elem.getShadowRoot()!.getElementById('c1')!.childNodes[0] as glassEasel.TextNode).textContent).toBe('3')
225
+ expect((elem.getShadowRoot()!.getElementById('d1')!.childNodes[0] as glassEasel.TextNode).textContent).toBe('4')
226
+ elem.setData({
227
+ list: [{ a: 'A', b: 'B' }],
228
+ })
229
+ expect((elem.getShadowRoot()!.getElementById('a0')!.childNodes[0] as glassEasel.TextNode).textContent).toBe('A')
230
+ expect((elem.getShadowRoot()!.getElementById('b0')!.childNodes[0] as glassEasel.TextNode).textContent).toBe('B')
231
+ elem.setData({
232
+ list: [{ e: 'A', f: 'B' }],
233
+ })
234
+ expect((elem.getShadowRoot()!.getElementById('e0')!.childNodes[0] as glassEasel.TextNode).textContent).toBe('A')
235
+ expect((elem.getShadowRoot()!.getElementById('f0')!.childNodes[0] as glassEasel.TextNode).textContent).toBe('B')
236
+ })
237
+
238
+ test('for blocks expanding object without key', () => {
239
+ const ops: Array<[number, string]> = []
240
+ const itemComp = glassEasel.registerElement({
241
+ properties: { s: { type: String } },
242
+ lifetimes: {
243
+ attached() {
244
+ ops.push([-1, (this.data.s).toString()])
245
+ },
246
+ detached() {
247
+ ops.push([-2, this.data.s.toString()])
248
+ },
249
+ },
250
+ }).general()
251
+ const def = glassEasel.registerElement({
252
+ using: {
253
+ 'x-c': itemComp,
254
+ },
255
+ template: tmpl(`
256
+ <div wx:for="{{list}}" wx:for-item="v" wx:for-index="k">
257
+ <x-c s="{{k}}:{{v}}">{{k}}</x-c>
258
+ </div>
259
+ `),
260
+ data: {
261
+ list: { a: 10, b: 20 },
262
+ },
263
+ }).general()
264
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
265
+ glassEasel.Element.pretendAttached(elem)
266
+ expect(domHtml(elem)).toBe('<div><x-c>a</x-c></div><div><x-c>b</x-c></div>')
267
+ expect(ops).toEqual([[-1, 'a:10'], [-1, 'b:20']])
268
+ ops.length = 0
269
+ elem.setData({
270
+ list: { a: 20, b: 10 },
271
+ })
272
+ expect(domHtml(elem)).toBe('<div><x-c>a</x-c></div><div><x-c>b</x-c></div>')
273
+ expect(ops).toEqual([])
274
+ ops.length = 0
275
+ elem.setData({
276
+ list: {
277
+ a: 30, b: 40, c: 50, d: 60,
278
+ },
279
+ })
280
+ expect(domHtml(elem)).toBe('<div><x-c>a</x-c></div><div><x-c>b</x-c></div><div><x-c>c</x-c></div><div><x-c>d</x-c></div>')
281
+ expect(ops).toEqual([[-1, 'c:50'], [-1, 'd:60']])
282
+ ops.length = 0
283
+ elem.setData({
284
+ list: { c: 50 },
285
+ })
286
+ expect(domHtml(elem)).toBe('<div><x-c>c</x-c></div>')
287
+ expect(ops).toEqual([[-2, 'b:40'], [-2, 'c:50'], [-2, 'd:60']])
288
+ })
289
+
290
+ test('for blocks expanding object with key', () => {
291
+ const ops: Array<[number, string]> = []
292
+ const itemComp = glassEasel.registerElement({
293
+ properties: { s: String },
294
+ lifetimes: {
295
+ attached() {
296
+ ops.push([-1, this.data.s.toString()])
297
+ },
298
+ detached() {
299
+ ops.push([-2, this.data.s.toString()])
300
+ },
301
+ moved() {
302
+ ops.push([-3, this.data.s.toString()])
303
+ },
304
+ },
305
+ })
306
+ const def = glassEasel.registerElement({
307
+ using: {
308
+ 'x-c': itemComp.general(),
309
+ },
310
+ template: tmpl(`
311
+ <block wx:for="{{list}}" wx:key="k">
312
+ <x-c data-i="{{index}}" s="{{item.k}}:{{item.v}}">{{item.v}}</x-c>
313
+ </block>
314
+ `),
315
+ data: {
316
+ list: { f1: { k: 'a', v: 10 }, f2: { k: 'b', v: 20 } } as Record<string, { k: string, v: number }>,
317
+ },
318
+ })
319
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
320
+ const listBlock = elem.getShadowRoot()!.childNodes[0] as glassEasel.VirtualNode
321
+ const checkIndex = () => {
322
+ const keys = Object.keys(elem.data.list)
323
+ for (let i = 0; i < listBlock.childNodes.length; i += 1) {
324
+ const itemBlock = listBlock.childNodes[i] as glassEasel.Element
325
+ expect((itemBlock.childNodes[0] as glassEasel.Element).dataset!.i).toBe(keys[i])
326
+ }
327
+ }
328
+ glassEasel.Element.pretendAttached(elem)
329
+ expect(domHtml(elem)).toBe('<x-c>10</x-c><x-c>20</x-c>')
330
+ checkIndex()
331
+ expect(ops).toEqual([[-1, 'a:10'], [-1, 'b:20']])
332
+ ops.length = 0
333
+ elem.setData({
334
+ list: {
335
+ f3: { k: 'c', v: 30 },
336
+ f4: { k: 'd', v: 40 },
337
+ f1: { k: 'a', v: 50 },
338
+ f5: { k: 'e', v: 60 },
339
+ f6: { k: 'f', v: 70 },
340
+ f2: { k: 'b', v: 80 },
341
+ },
342
+ })
343
+ expect(domHtml(elem)).toBe('<x-c>30</x-c><x-c>40</x-c><x-c>50</x-c><x-c>60</x-c><x-c>70</x-c><x-c>80</x-c>')
344
+ checkIndex()
345
+ expect(ops).toEqual([[-1, 'c:30'], [-1, 'd:40'], [-1, 'e:60'], [-1, 'f:70']])
346
+ ops.length = 0
347
+ elem.setData({
348
+ list: {
349
+ f1: { k: 'a', v: 50 },
350
+ f5: { k: 'e', v: 60 },
351
+ f6: { k: 'f', v: 70 },
352
+ f2: { k: 'b', v: 80 },
353
+ f3: { k: 'g', v: 30 },
354
+ f4: { k: 'h', v: 40 },
355
+ },
356
+ })
357
+ expect(domHtml(elem)).toBe('<x-c>50</x-c><x-c>60</x-c><x-c>70</x-c><x-c>80</x-c><x-c>30</x-c><x-c>40</x-c>')
358
+ checkIndex()
359
+ expect(ops).toEqual([[-2, 'c:30'], [-2, 'd:40'], [-1, 'g:30'], [-1, 'h:40']])
360
+ ops.length = 0
361
+ elem.setData({
362
+ list: {
363
+ f5: { k: 'e', v: 10 },
364
+ f3: { k: 'g', v: 20 },
365
+ f6: { k: 'f', v: 30 },
366
+ f1: { k: 'a', v: 40 },
367
+ f2: { k: 'b', v: 50 },
368
+ },
369
+ })
370
+ expect(domHtml(elem)).toBe('<x-c>10</x-c><x-c>20</x-c><x-c>30</x-c><x-c>40</x-c><x-c>50</x-c>')
371
+ checkIndex()
372
+ expect(ops).toEqual([[-3, 'g:30'], [-3, 'a:50'], [-2, 'h:40']])
373
+ ops.length = 0
374
+ elem.setData({
375
+ list: {
376
+ f1: { k: 'a', v: 10 },
377
+ f2: { k: 'c', v: 20 },
378
+ f3: { k: 'e', v: 30 },
379
+ f4: { k: 'f', v: 40 },
380
+ f5: { k: 'b', v: 50 },
381
+ f6: { k: 'g', v: 60 },
382
+ f7: { k: 'h', v: 70 },
383
+ },
384
+ })
385
+ expect(domHtml(elem)).toBe('<x-c>10</x-c><x-c>20</x-c><x-c>30</x-c><x-c>40</x-c><x-c>50</x-c><x-c>60</x-c><x-c>70</x-c>')
386
+ checkIndex()
387
+ expect(ops).toEqual([[-3, 'a:40'], [-1, 'c:20'], [-3, 'g:20'], [-1, 'h:70']])
388
+ ops.length = 0
389
+ elem.setData({
390
+ list: {
391
+ f7: { k: 'h', v: 60 },
392
+ f3: { k: 'e', v: 50 },
393
+ f4: { k: 'f', v: 40 },
394
+ f2: { k: 'c', v: 30 },
395
+ f5: { k: 'b', v: 20 },
396
+ f1: { k: 'a', v: 10 },
397
+ },
398
+ })
399
+ expect(domHtml(elem)).toBe('<x-c>60</x-c><x-c>50</x-c><x-c>40</x-c><x-c>30</x-c><x-c>20</x-c><x-c>10</x-c>')
400
+ checkIndex()
401
+ expect(ops).toEqual([[-3, 'h:70'], [-3, 'c:20'], [-2, 'g:60'], [-3, 'a:10']])
402
+ ops.length = 0
403
+ elem.setData({
404
+ list: {
405
+ f8: { k: 'f', v: 80 },
406
+ f7: { k: 'e', v: 70 },
407
+ f6: { k: 'd', v: 60 },
408
+ f5: { k: 'a', v: 50 },
409
+ f4: { k: 'c', v: 40 },
410
+ f3: { k: 'b', v: 30 },
411
+ f2: { k: 'g', v: 20 },
412
+ f1: { k: 'h', v: 10 },
413
+ },
414
+ })
415
+ expect(domHtml(elem)).toBe('<x-c>80</x-c><x-c>70</x-c><x-c>60</x-c><x-c>50</x-c><x-c>40</x-c><x-c>30</x-c><x-c>20</x-c><x-c>10</x-c>')
416
+ checkIndex()
417
+ expect(ops).toEqual([[-3, 'f:40'], [-1, 'd:60'], [-3, 'a:10'], [-1, 'g:20'], [-3, 'h:60']])
418
+ })
419
+
420
+ test('for blocks expending string', () => {
421
+ const ops: Array<[number, string]> = []
422
+ const itemComp = glassEasel.registerElement({
423
+ properties: { s: String },
424
+ lifetimes: {
425
+ attached() {
426
+ ops.push([-1, this.data.s.toString()])
427
+ },
428
+ detached() {
429
+ ops.push([-2, this.data.s.toString()])
430
+ },
431
+ moved() {
432
+ ops.push([-3, this.data.s.toString()])
433
+ },
434
+ },
435
+ }).general()
436
+ const def = glassEasel.registerElement({
437
+ using: {
438
+ 'x-c': itemComp,
439
+ },
440
+ template: tmpl(`
441
+ <block wx:for="{{s}}" wx:key="*this">
442
+ <x-c s="{{item}}">{{item}}</x-c>
443
+ </block>
444
+ `),
445
+ data: {
446
+ s: 'x',
447
+ },
448
+ }).general()
449
+ const elem = execWithWarn(1, () => glassEasel.Component.createWithContext('root', def, domBackend))
450
+ glassEasel.Element.pretendAttached(elem)
451
+ expect(domHtml(elem)).toBe('<x-c>x</x-c>')
452
+ expect(ops).toEqual([[-1, 'x']])
453
+ ops.length = 0
454
+ execWithWarn(1, () => {
455
+ elem.setData({
456
+ s: 'abc',
457
+ })
458
+ })
459
+ expect(domHtml(elem)).toBe('<x-c>a</x-c><x-c>b</x-c><x-c>c</x-c>')
460
+ expect(ops).toEqual([[-2, 'x'], [-1, 'a'], [-1, 'b'], [-1, 'c']])
461
+ ops.length = 0
462
+ execWithWarn(1, () => {
463
+ elem.setData({
464
+ s: 'cab',
465
+ })
466
+ })
467
+ expect(domHtml(elem)).toBe('<x-c>c</x-c><x-c>a</x-c><x-c>b</x-c>')
468
+ ops.length = 0
469
+ execWithWarn(1, () => {
470
+ elem.setData({
471
+ s: '',
472
+ })
473
+ })
474
+ expect(domHtml(elem)).toBe('')
475
+ expect(ops).toEqual([[-2, 'c'], [-2, 'a'], [-2, 'b']])
476
+ })
477
+
478
+ test('for blocks expending number', () => {
479
+ const def = glassEasel.registerElement({
480
+ template: tmpl(`
481
+ <block wx:for="{{n}}">
482
+ <span>{{item}}</span>
483
+ </block>
484
+ `),
485
+ data: { n: 3 },
486
+ }).general()
487
+ const elem = execWithWarn(1, () => glassEasel.Component.createWithContext('root', def, domBackend))
488
+ glassEasel.Element.pretendAttached(elem)
489
+ expect(domHtml(elem)).toBe('<span>0</span><span>1</span><span>2</span>')
490
+ execWithWarn(1, () => elem.setData({ n: 2 }))
491
+ expect(domHtml(elem)).toBe('<span>0</span><span>1</span>')
492
+ })
493
+
494
+ test('template include', () => {
495
+ const def = glassEasel.registerElement({
496
+ template: multiTmpl({
497
+ '': '<include src="./a.wxml" />',
498
+ 'a.wxml': '<div>{{a}}</div>',
499
+ }),
500
+ data: {
501
+ a: 123,
502
+ },
503
+ }).general()
504
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
505
+ expect(domHtml(elem)).toBe('<div>123</div>')
506
+ elem.setData({ a: 456 })
507
+ expect(domHtml(elem)).toBe('<div>456</div>')
508
+ })
509
+
510
+ test('template undefined include', () => {
511
+ const def = glassEasel.registerElement({
512
+ template: multiTmpl({
513
+ '': '<div><include src="./a.wxml" /></div>',
514
+ }),
515
+ data: {
516
+ a: 123,
517
+ },
518
+ }).general()
519
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
520
+ expect(domHtml(elem)).toBe('<div></div>')
521
+ })
522
+
523
+ test('template-name data', () => {
524
+ const def = glassEasel.registerElement({
525
+ template: multiTmpl({
526
+ child: `
527
+ <template name="child">
528
+ <span wx:for="{{list}}" wx:if="{{item.shown}}">{{index}}:{{item.data}}</span>
529
+ </template>
530
+ `,
531
+ '': `
532
+ <import src="./child" />
533
+ <div>
534
+ <template is="child" data="{{ list: arr }}" />
535
+ </div>
536
+ `,
537
+ }),
538
+ data: {
539
+ arr: [
540
+ { shown: true, data: 123 },
541
+ { shown: false, data: 456 },
542
+ { shown: true, data: 789 },
543
+ ],
544
+ },
545
+ }).general()
546
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
547
+ expect(domHtml(elem)).toBe('<div><span>0:123</span><span>2:789</span></div>')
548
+ elem.setData({ 'arr[0].shown': false })
549
+ expect(domHtml(elem)).toBe('<div><span>2:789</span></div>')
550
+ elem.setData({
551
+ arr: [
552
+ { shown: false, data: 789 },
553
+ { shown: true, data: 456 },
554
+ ],
555
+ })
556
+ expect(domHtml(elem)).toBe('<div><span>1:456</span></div>')
557
+ })
558
+
559
+ test('static template-is', () => {
560
+ const def = glassEasel.registerElement({
561
+ template: tmpl(`
562
+ <template name="child">
563
+ <span wx:if="{{!hidden}}">{{a + b.c + d}}</span>
564
+ </template>
565
+ <div>
566
+ <template is="child" data="{{b: {c: 40}, d: 5, ...b}}" />
567
+ </div>
568
+ `),
569
+ data: {
570
+ b: {
571
+ a: 300,
572
+ },
573
+ },
574
+ }).general()
575
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
576
+ expect(domHtml(elem)).toBe('<div><span>345</span></div>')
577
+ elem.setData({ b: { a: 200, d: 7 } })
578
+ expect(domHtml(elem)).toBe('<div><span>247</span></div>')
579
+ elem.setData({ 'b.a': 100 })
580
+ expect(domHtml(elem)).toBe('<div><span>147</span></div>')
581
+ elem.setData({ 'b.hidden': true })
582
+ expect(domHtml(elem)).toBe('<div></div>')
583
+ })
584
+
585
+ test('dynamic template-is', () => {
586
+ const def = glassEasel.registerElement({
587
+ template: tmpl(`
588
+ <template name="childA">
589
+ <span>{{a}}</span>
590
+ </template>
591
+ <template name="childB">
592
+ <span>{{b}}</span>
593
+ </template>
594
+ <div>
595
+ <template is="child{{childType}}" data="{{a, b}}" />
596
+ </div>
597
+ `),
598
+ data: {
599
+ childType: '',
600
+ a: 123,
601
+ b: 456,
602
+ },
603
+ properties: {},
604
+ methods: {},
605
+ }).general()
606
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
607
+ expect(domHtml(elem)).toBe('<div></div>')
608
+ elem.setData({ childType: 'A' })
609
+ expect(domHtml(elem)).toBe('<div><span>123</span></div>')
610
+ elem.setData({ childType: 'B' })
611
+ expect(domHtml(elem)).toBe('<div><span>456</span></div>')
612
+ elem.setData({ a: 0 })
613
+ expect(domHtml(elem)).toBe('<div><span>456</span></div>')
614
+ elem.setData({ b: 789 })
615
+ expect(domHtml(elem)).toBe('<div><span>789</span></div>')
616
+ })
617
+
618
+ test('template-is inside for block', () => {
619
+ const def = glassEasel.registerElement({
620
+ template: tmpl(`
621
+ <template name="child1">
622
+ <span>{{index}}:{{a}}</span>
623
+ </template>
624
+ <template name="child2">
625
+ <span>{{index}}:{{b}}</span>
626
+ </template>
627
+ <div>
628
+ <block wx:for="{{list}}">
629
+ <template is="child{{item}}" data="{{a, b, index}}" />
630
+ </block>
631
+ </div>
632
+ `),
633
+ data: {
634
+ list: [],
635
+ a: 123,
636
+ b: 456,
637
+ },
638
+ properties: {},
639
+ methods: {},
640
+ }).general()
641
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
642
+ expect(domHtml(elem)).toBe('<div></div>')
643
+ elem.setData({ list: [1, 2] })
644
+ expect(domHtml(elem)).toBe('<div><span>0:123</span><span>1:456</span></div>')
645
+ elem.setData({ list: [2, 1, 1] })
646
+ expect(domHtml(elem)).toBe('<div><span>0:456</span><span>1:123</span><span>2:123</span></div>')
647
+ elem.setData({ a: 0 })
648
+ expect(domHtml(elem)).toBe('<div><span>0:456</span><span>1:0</span><span>2:0</span></div>')
649
+ elem.setData({ b: 789 })
650
+ expect(domHtml(elem)).toBe('<div><span>0:789</span><span>1:0</span><span>2:0</span></div>')
651
+ })
652
+
653
+ test('undefined template-is', () => {
654
+ const def = glassEasel.registerElement({
655
+ template: tmpl(`
656
+ <div>
657
+ <template is="child" data="{{ b }}" />
658
+ </div>
659
+ `),
660
+ data: {
661
+ b: 123,
662
+ },
663
+ }).general()
664
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
665
+ expect(domHtml(elem)).toBe('<div></div>')
666
+ })
667
+
668
+ test('cascaded template', () => {
669
+ const def = glassEasel.registerElement({
670
+ template: multiTmpl({
671
+ '': `
672
+ <import src="./template/s" />
673
+ <template is="S" data="{{ {c} }}" />
674
+ `,
675
+ 'template/s': `
676
+ <import src="./i" />
677
+ <template name="S">
678
+ <div>key: {{ c.i }}</div>
679
+ <template is="{{ c.i }}" />
680
+ </template>
681
+ `,
682
+ 'template/i': `
683
+ <template name="A"><div>value: a</div></template>
684
+ <template name="B"><div>value: b</div></template>
685
+ `,
686
+ }),
687
+ data: {
688
+ c: {
689
+ i: 'A',
690
+ },
691
+ },
692
+ }).general()
693
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend).asInstanceOf(def)!
694
+ expect(domHtml(elem)).toBe('<div>key: A</div><div>value: a</div>')
695
+
696
+ elem.setData({ 'c.i': 'B' })
697
+ expect(domHtml(elem)).toBe('<div>key: B</div><div>value: b</div>')
698
+ })
699
+
700
+ test('block slot', () => {
701
+ const childComp = glassEasel.registerElement({
702
+ options: {
703
+ multipleSlots: true,
704
+ },
705
+ template: tmpl(`
706
+ <div>
707
+ <slot name="child-slot" />
708
+ </div>
709
+ `),
710
+ })
711
+ const def = glassEasel.registerElement({
712
+ using: {
713
+ 'child-comp': childComp,
714
+ },
715
+ template: tmpl(`
716
+ <child-comp>
717
+ <block slot="{{d}}">123</block>
718
+ </child-comp>
719
+ `),
720
+ data: {
721
+ d: 'child-slot',
722
+ },
723
+ }).general()
724
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
725
+ glassEasel.Element.pretendAttached(elem)
726
+ expect(domHtml(elem)).toBe('<child-comp><div>123</div></child-comp>')
727
+ elem.setData({
728
+ d: '',
729
+ })
730
+ expect(domHtml(elem)).toBe('<child-comp><div></div></child-comp>')
731
+ })
732
+
733
+ test('setting native node attr', () => {
734
+ const def = glassEasel.registerElement({
735
+ template: tmpl(`
736
+ <a hidden="{{hidden}}" href="{{url}}"></a>
737
+ `),
738
+ data: {
739
+ hidden: false,
740
+ url: 'abc',
741
+ },
742
+ }).general()
743
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
744
+ glassEasel.Element.pretendAttached(elem)
745
+ expect(domHtml(elem)).toBe('<a href="abc"></a>')
746
+ elem.setData({
747
+ hidden: true,
748
+ url: '',
749
+ })
750
+ expect(domHtml(elem)).toBe('<a href="" hidden=""></a>')
751
+ })
752
+
753
+ test('setting element id', () => {
754
+ const def = glassEasel.registerElement({
755
+ template: tmpl(`
756
+ <div id="{{d}}">123</div>
757
+ `),
758
+ data: {
759
+ d: 'abc',
760
+ },
761
+ }).general()
762
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
763
+ glassEasel.Element.pretendAttached(elem)
764
+ expect(domHtml(elem)).toBe('<div>123</div>')
765
+ expect(domHtml(elem.getShadowRoot()!.getElementById('abc')!)).toBe('123')
766
+ elem.setData({
767
+ d: 'def',
768
+ })
769
+ expect(elem.getShadowRoot()!.getElementById('abc')).toBe(undefined)
770
+ expect(domHtml(elem.getShadowRoot()!.getElementById('def')!)).toBe('123')
771
+ })
772
+
773
+ test('setting element slot', () => {
774
+ const subComp = glassEasel.registerElement({
775
+ options: {
776
+ multipleSlots: true,
777
+ },
778
+ template: tmpl(`
779
+ <div><slot name="a" /></div>
780
+ <span><slot name="b" /></span>
781
+ `),
782
+ })
783
+ const def = glassEasel.registerElement({
784
+ using: {
785
+ 'sub-comp': subComp,
786
+ },
787
+ template: tmpl(`
788
+ <sub-comp>
789
+ <a slot="{{s}}"></a>
790
+ </sub-comp>
791
+ `),
792
+ }).general()
793
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
794
+ glassEasel.Element.pretendAttached(elem)
795
+ expect(domHtml(elem)).toBe('<sub-comp><div></div><span></span></sub-comp>')
796
+ elem.setData({
797
+ s: 'a',
798
+ })
799
+ expect(domHtml(elem)).toBe('<sub-comp><div><a></a></div><span></span></sub-comp>')
800
+ elem.setData({
801
+ s: 'b',
802
+ })
803
+ expect(domHtml(elem)).toBe('<sub-comp><div></div><span><a></a></span></sub-comp>')
804
+ })
805
+
806
+ test('setting slot name', () => {
807
+ const subComp = glassEasel.registerElement({
808
+ options: {
809
+ multipleSlots: true,
810
+ },
811
+ template: tmpl(`
812
+ <div><slot name="{{a}}" /></div>
813
+ <span><slot name="{{b}}" /></span>
814
+ `),
815
+ data: {
816
+ a: 's',
817
+ b: undefined as string | undefined,
818
+ },
819
+ })
820
+ const def = glassEasel.registerElement({
821
+ using: {
822
+ 'sub-comp': subComp.general(),
823
+ },
824
+ template: tmpl(`
825
+ <sub-comp id="sub">
826
+ <a slot="s"></a>
827
+ </sub-comp>
828
+ `),
829
+ }).general()
830
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
831
+ const subElem = (elem.$.sub as glassEasel.GeneralComponent).asInstanceOf(subComp)!
832
+ glassEasel.Element.pretendAttached(elem)
833
+ expect(domHtml(elem)).toBe('<sub-comp><div><a></a></div><span></span></sub-comp>')
834
+ subElem.setData({
835
+ a: '',
836
+ })
837
+ expect(domHtml(elem)).toBe('<sub-comp><div></div><span></span></sub-comp>')
838
+ subElem.setData({
839
+ b: 's',
840
+ })
841
+ expect(domHtml(elem)).toBe('<sub-comp><div></div><span><a></a></span></sub-comp>')
842
+ subElem.setData({
843
+ a: 's',
844
+ })
845
+ expect(domHtml(elem)).toBe('<sub-comp><div><a></a></div><span></span></sub-comp>')
846
+ })
847
+
848
+ test('binding event listeners', () => {
849
+ const ops: any[] = []
850
+ const def = glassEasel.registerElement({
851
+ template: tmpl(`
852
+ <div mark:a="123" data:n="wrong">
853
+ <span id="child" mark:b="{{d}}" data:dA="a" data:d-b="b" data-d-c="{{d}}" bind:customEv="ev"/>
854
+ </div>
855
+ `),
856
+ methods: {
857
+ ev(ev: any) {
858
+ // eslint-disable-next-line no-use-before-define
859
+ expect(this).toBe(elem)
860
+ ops.push(ev)
861
+ },
862
+ },
863
+ }).general()
864
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
865
+ glassEasel.Element.pretendAttached(elem)
866
+ expect(domHtml(elem)).toBe('<div><span></span></div>')
867
+ const child = elem.getShadowRoot()!.getElementById('child')!
868
+ child.triggerEvent('customEv')
869
+ const ev = ops.shift() as glassEasel.ShadowedEvent<any>
870
+ expect(ev.mark).toEqual({ a: '123', b: undefined })
871
+ expect(ev.target.dataset).toEqual({ dA: 'a', 'd-b': 'b', dC: undefined })
872
+ elem.setData({ d: true })
873
+ child.triggerEvent('customEv')
874
+ const ev2 = ops.shift() as glassEasel.ShadowedEvent<any>
875
+ expect(ev2.mark).toEqual({ a: '123', b: true })
876
+ expect(ev2.target.dataset).toEqual({ dA: 'a', 'd-b': 'b', dC: true })
877
+ })
878
+
879
+ test('binding dynamic event listeners', () => {
880
+ let ops: number[] = []
881
+ const def = glassEasel.registerElement({
882
+ template: tmpl(`
883
+ <span id="child" bind:customEv="{{ handlerName || '' }}"/>
884
+ `),
885
+ methods: {
886
+ a() {
887
+ ops.push(1)
888
+ },
889
+ b() {
890
+ ops.push(2)
891
+ },
892
+ },
893
+ }).general()
894
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
895
+ glassEasel.Element.pretendAttached(elem)
896
+ const child = elem.getShadowRoot()!.getElementById('child')!
897
+ child.triggerEvent('customEv')
898
+ expect(ops).toStrictEqual([])
899
+ ops = []
900
+ elem.setData({
901
+ handlerName: 'a',
902
+ })
903
+ child.triggerEvent('customEv')
904
+ expect(ops).toStrictEqual([1])
905
+ ops = []
906
+ elem.setData({
907
+ handlerName: 'b',
908
+ })
909
+ child.triggerEvent('customEv')
910
+ expect(ops).toStrictEqual([2])
911
+ ops = []
912
+ })
913
+
914
+ test('setting external classes', () => {
915
+ const cs = new glassEasel.ComponentSpace()
916
+ const ssm = cs.styleScopeManager
917
+ const subComp = cs.defineComponent({
918
+ externalClasses: ['class', 'ext-class'],
919
+ template: tmpl(`
920
+ <div class="class ext-class"></div>
921
+ `),
922
+ data: {
923
+ a: 's',
924
+ },
925
+ })
926
+ const def = cs.defineComponent({
927
+ options: {
928
+ styleScope: ssm.register('p'),
929
+ },
930
+ using: {
931
+ 'sub-comp': subComp.general(),
932
+ },
933
+ template: tmpl(`
934
+ <sub-comp class="static {{ dynamic || '' }}" ext-class="a-class" />
935
+ `),
936
+ }).general()
937
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
938
+ glassEasel.Element.pretendAttached(elem)
939
+ expect(domHtml(elem)).toBe('<sub-comp class="p--static"><div class="p--static p--a-class"></div></sub-comp>')
940
+ elem.setData({
941
+ dynamic: 'dynamic',
942
+ })
943
+ expect(domHtml(elem)).toBe('<sub-comp class="p--static p--dynamic"><div class="p--static p--dynamic p--a-class"></div></sub-comp>')
944
+ elem.setData({
945
+ dynamic: '',
946
+ })
947
+ expect(domHtml(elem)).toBe('<sub-comp class="p--static"><div class="p--static p--a-class"></div></sub-comp>')
948
+ })
949
+
950
+ test('pass object to child components', () => {
951
+ const cs = new glassEasel.ComponentSpace()
952
+ let ops = 0
953
+ const subComp = cs.defineComponent({
954
+ template: tmpl(`
955
+ <div class="{{ prop.a }}"></div>
956
+ `),
957
+ properties: {
958
+ prop: Object,
959
+ },
960
+ observers: {
961
+ prop() {
962
+ ops += 1
963
+ },
964
+ },
965
+ })
966
+ const def = cs.defineComponent({
967
+ using: {
968
+ 'sub-comp': subComp.general(),
969
+ },
970
+ template: tmpl(`
971
+ <sub-comp prop="{{obj}}" />
972
+ `),
973
+ data: {
974
+ obj: {
975
+ a: 'a1',
976
+ },
977
+ num: 0,
978
+ },
979
+ }).general()
980
+ const elem = glassEasel.Component.createWithContext('root', def, domBackend)
981
+ glassEasel.Element.pretendAttached(elem)
982
+ expect(domHtml(elem)).toBe('<sub-comp><div class="a1"></div></sub-comp>')
983
+ expect(ops).toBe(1)
984
+ elem.setData({
985
+ 'obj.b': 'b1',
986
+ })
987
+ expect(ops).toBe(2)
988
+ elem.setData({
989
+ num: 1,
990
+ })
991
+ expect(ops).toBe(2)
992
+ elem.setData({
993
+ obj: { a: 'a2' },
994
+ })
995
+ expect(domHtml(elem)).toBe('<sub-comp><div class="a2"></div></sub-comp>')
996
+ expect(ops).toBe(3)
997
+ })
998
+ })