@knowark/componarkjs 1.13.3 → 1.14.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 (177) hide show
  1. package/lib/base/component/component.js +17 -1
  2. package/lib/base/component/component.test.js +475 -389
  3. package/lib/base/utils/define.js +28 -6
  4. package/lib/base/utils/define.test.js +129 -42
  5. package/lib/base/utils/format.test.js +16 -16
  6. package/lib/base/utils/helpers.js +11 -4
  7. package/lib/base/utils/helpers.test.js +134 -115
  8. package/lib/base/utils/slots.test.js +38 -38
  9. package/lib/base/utils/uuid.test.js +13 -13
  10. package/lib/components/audio/components/audio.js +22 -3
  11. package/lib/components/audio/components/audio.test.js +120 -90
  12. package/lib/components/camera/components/camera.js +8 -3
  13. package/lib/components/camera/components/camera.test.js +96 -91
  14. package/lib/components/capture/components/capture.js +33 -5
  15. package/lib/components/capture/components/capture.test.js +165 -97
  16. package/lib/components/droparea/components/droparea-preview.js +66 -15
  17. package/lib/components/droparea/components/droparea-preview.test.js +262 -78
  18. package/lib/components/droparea/components/droparea.js +47 -8
  19. package/lib/components/droparea/components/droparea.test.js +309 -298
  20. package/lib/components/emit/components/emit.js +24 -4
  21. package/lib/components/emit/components/emit.test.js +192 -134
  22. package/lib/components/index.js +1 -1
  23. package/lib/components/list/components/{list.item.js → item.js} +1 -1
  24. package/lib/components/list/components/item.test.js +70 -69
  25. package/lib/components/list/components/list.js +35 -5
  26. package/lib/components/list/components/list.test.js +358 -227
  27. package/lib/components/list/index.js +1 -1
  28. package/lib/components/paginator/components/paginator.js +3 -2
  29. package/lib/components/paginator/components/paginator.test.js +146 -143
  30. package/lib/components/spinner/components/spinner.js +1 -1
  31. package/lib/components/spinner/components/spinner.test.js +36 -41
  32. package/lib/components/splitview/components/splitview.detail.js +1 -1
  33. package/lib/components/splitview/components/splitview.detail.test.js +78 -74
  34. package/lib/components/splitview/components/splitview.js +40 -10
  35. package/lib/components/splitview/components/splitview.master.js +29 -3
  36. package/lib/components/splitview/components/splitview.master.test.js +52 -52
  37. package/lib/components/splitview/components/splitview.test.js +136 -32
  38. package/lib/components/translate/components/translate.js +32 -10
  39. package/lib/components/translate/components/translate.test.js +492 -133
  40. package/package.json +7 -27
  41. package/scripts/node-test-setup.js +94 -0
  42. package/showcase/components/index.html +1 -1
  43. package/{jsconfig.json → tsconfig.json} +6 -4
  44. package/types/base/component/component.d.ts +48 -0
  45. package/types/base/component/component.d.ts.map +1 -0
  46. package/types/base/component/component.test.d.ts +2 -0
  47. package/types/base/component/component.test.d.ts.map +1 -0
  48. package/types/base/component/index.d.ts +8 -0
  49. package/types/base/component/index.d.ts.map +1 -0
  50. package/types/base/index.d.ts +2 -0
  51. package/types/base/index.d.ts.map +1 -0
  52. package/types/base/styles/index.d.ts +3 -0
  53. package/types/base/styles/index.d.ts.map +1 -0
  54. package/types/base/styles/styles.d.ts +3 -0
  55. package/types/base/styles/styles.d.ts.map +1 -0
  56. package/types/base/utils/define.d.ts +5 -0
  57. package/types/base/utils/define.d.ts.map +1 -0
  58. package/types/base/utils/define.test.d.ts +2 -0
  59. package/types/base/utils/define.test.d.ts.map +1 -0
  60. package/types/base/utils/format.d.ts +13 -0
  61. package/types/base/utils/format.d.ts.map +1 -0
  62. package/types/base/utils/format.test.d.ts +2 -0
  63. package/types/base/utils/format.test.d.ts.map +1 -0
  64. package/types/base/utils/helpers.d.ts +11 -0
  65. package/types/base/utils/helpers.d.ts.map +1 -0
  66. package/types/base/utils/helpers.test.d.ts +2 -0
  67. package/types/base/utils/helpers.test.d.ts.map +1 -0
  68. package/types/base/utils/index.d.ts +6 -0
  69. package/types/base/utils/index.d.ts.map +1 -0
  70. package/types/base/utils/slots.d.ts +15 -0
  71. package/types/base/utils/slots.d.ts.map +1 -0
  72. package/types/base/utils/slots.test.d.ts +2 -0
  73. package/types/base/utils/slots.test.d.ts.map +1 -0
  74. package/types/base/utils/uuid.d.ts +3 -0
  75. package/types/base/utils/uuid.d.ts.map +1 -0
  76. package/types/base/utils/uuid.test.d.ts +2 -0
  77. package/types/base/utils/uuid.test.d.ts.map +1 -0
  78. package/types/components/audio/components/audio.d.ts +18 -0
  79. package/types/components/audio/components/audio.d.ts.map +1 -0
  80. package/types/components/audio/components/audio.test.d.ts +2 -0
  81. package/types/components/audio/components/audio.test.d.ts.map +1 -0
  82. package/types/components/audio/index.d.ts +2 -0
  83. package/types/components/audio/index.d.ts.map +1 -0
  84. package/types/components/audio/styles/ark.css.d.ts +3 -0
  85. package/types/components/audio/styles/ark.css.d.ts.map +1 -0
  86. package/types/components/audio/styles/index.d.ts +3 -0
  87. package/types/components/audio/styles/index.d.ts.map +1 -0
  88. package/types/components/camera/components/camera.d.ts +18 -0
  89. package/types/components/camera/components/camera.d.ts.map +1 -0
  90. package/types/components/camera/components/camera.test.d.ts +2 -0
  91. package/types/components/camera/components/camera.test.d.ts.map +1 -0
  92. package/types/components/camera/index.d.ts +2 -0
  93. package/types/components/camera/index.d.ts.map +1 -0
  94. package/types/components/camera/styles/ark.css.d.ts +3 -0
  95. package/types/components/camera/styles/ark.css.d.ts.map +1 -0
  96. package/types/components/camera/styles/index.d.ts +3 -0
  97. package/types/components/camera/styles/index.d.ts.map +1 -0
  98. package/types/components/capture/components/capture.d.ts +10 -0
  99. package/types/components/capture/components/capture.d.ts.map +1 -0
  100. package/types/components/capture/components/capture.test.d.ts +2 -0
  101. package/types/components/capture/components/capture.test.d.ts.map +1 -0
  102. package/types/components/capture/index.d.ts +2 -0
  103. package/types/components/capture/index.d.ts.map +1 -0
  104. package/types/components/droparea/components/droparea-preview.d.ts +21 -0
  105. package/types/components/droparea/components/droparea-preview.d.ts.map +1 -0
  106. package/types/components/droparea/components/droparea-preview.test.d.ts +2 -0
  107. package/types/components/droparea/components/droparea-preview.test.d.ts.map +1 -0
  108. package/types/components/droparea/components/droparea.d.ts +32 -0
  109. package/types/components/droparea/components/droparea.d.ts.map +1 -0
  110. package/types/components/droparea/components/droparea.test.d.ts +2 -0
  111. package/types/components/droparea/components/droparea.test.d.ts.map +1 -0
  112. package/types/components/droparea/index.d.ts +2 -0
  113. package/types/components/droparea/index.d.ts.map +1 -0
  114. package/types/components/droparea/styles/ark.css.d.ts +3 -0
  115. package/types/components/droparea/styles/ark.css.d.ts.map +1 -0
  116. package/types/components/droparea/styles/index.d.ts +3 -0
  117. package/types/components/droparea/styles/index.d.ts.map +1 -0
  118. package/types/components/emit/components/emit.d.ts +10 -0
  119. package/types/components/emit/components/emit.d.ts.map +1 -0
  120. package/types/components/emit/components/emit.test.d.ts +2 -0
  121. package/types/components/emit/components/emit.test.d.ts.map +1 -0
  122. package/types/components/emit/index.d.ts +2 -0
  123. package/types/components/emit/index.d.ts.map +1 -0
  124. package/types/components/index.d.ts +10 -0
  125. package/types/components/index.d.ts.map +1 -0
  126. package/types/components/list/components/item.d.ts +8 -0
  127. package/types/components/list/components/item.d.ts.map +1 -0
  128. package/types/components/list/components/item.test.d.ts +2 -0
  129. package/types/components/list/components/item.test.d.ts.map +1 -0
  130. package/types/components/list/components/list.d.ts +13 -0
  131. package/types/components/list/components/list.d.ts.map +1 -0
  132. package/types/components/list/components/list.test.d.ts +2 -0
  133. package/types/components/list/components/list.test.d.ts.map +1 -0
  134. package/types/components/list/index.d.ts +3 -0
  135. package/types/components/list/index.d.ts.map +1 -0
  136. package/types/components/paginator/components/paginator.d.ts +32 -0
  137. package/types/components/paginator/components/paginator.d.ts.map +1 -0
  138. package/types/components/paginator/components/paginator.test.d.ts +2 -0
  139. package/types/components/paginator/components/paginator.test.d.ts.map +1 -0
  140. package/types/components/paginator/index.d.ts +2 -0
  141. package/types/components/paginator/index.d.ts.map +1 -0
  142. package/types/components/paginator/styles/ark.css.d.ts +3 -0
  143. package/types/components/paginator/styles/ark.css.d.ts.map +1 -0
  144. package/types/components/paginator/styles/index.d.ts +3 -0
  145. package/types/components/paginator/styles/index.d.ts.map +1 -0
  146. package/types/components/spinner/components/spinner.d.ts +11 -0
  147. package/types/components/spinner/components/spinner.d.ts.map +1 -0
  148. package/types/components/spinner/components/spinner.test.d.ts +2 -0
  149. package/types/components/spinner/components/spinner.test.d.ts.map +1 -0
  150. package/types/components/spinner/index.d.ts +2 -0
  151. package/types/components/spinner/index.d.ts.map +1 -0
  152. package/types/components/spinner/styles/ark.css.d.ts +3 -0
  153. package/types/components/spinner/styles/ark.css.d.ts.map +1 -0
  154. package/types/components/spinner/styles/index.d.ts +3 -0
  155. package/types/components/spinner/styles/index.d.ts.map +1 -0
  156. package/types/components/splitview/components/splitview.d.ts +12 -0
  157. package/types/components/splitview/components/splitview.d.ts.map +1 -0
  158. package/types/components/splitview/components/splitview.detail.d.ts +10 -0
  159. package/types/components/splitview/components/splitview.detail.d.ts.map +1 -0
  160. package/types/components/splitview/components/splitview.detail.test.d.ts +2 -0
  161. package/types/components/splitview/components/splitview.detail.test.d.ts.map +1 -0
  162. package/types/components/splitview/components/splitview.master.d.ts +8 -0
  163. package/types/components/splitview/components/splitview.master.d.ts.map +1 -0
  164. package/types/components/splitview/components/splitview.master.test.d.ts +2 -0
  165. package/types/components/splitview/components/splitview.master.test.d.ts.map +1 -0
  166. package/types/components/splitview/components/splitview.test.d.ts +2 -0
  167. package/types/components/splitview/components/splitview.test.d.ts.map +1 -0
  168. package/types/components/splitview/index.d.ts +4 -0
  169. package/types/components/splitview/index.d.ts.map +1 -0
  170. package/types/components/translate/components/translate.d.ts +18 -0
  171. package/types/components/translate/components/translate.d.ts.map +1 -0
  172. package/types/components/translate/components/translate.test.d.ts +2 -0
  173. package/types/components/translate/components/translate.test.d.ts.map +1 -0
  174. package/types/components/translate/index.d.ts +2 -0
  175. package/types/components/translate/index.d.ts.map +1 -0
  176. package/types/index.d.ts +3 -0
  177. package/types/index.d.ts.map +1 -0
@@ -1,4 +1,5 @@
1
- import { jest } from '@jest/globals'
1
+ import { it, mock } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
  import { Component } from './component.js'
3
4
 
4
5
  class MockComponent extends Component {
@@ -42,486 +43,571 @@ class MockContentComponent extends Component {
42
43
  }
43
44
  Component.define('mock-content-component', MockContentComponent)
44
45
 
45
- describe('Component', () => {
46
- let container = null
47
- let component = null
48
- beforeEach(() => {
49
- container = document.createElement('div')
50
- container.innerHTML = '<mock-component code="XYZ123"></mock-component>'
51
- component = container.querySelector('mock-component')
52
- document.body.append(container)
53
- })
54
-
55
- afterEach(() => {
56
- container.remove()
57
- container = null
58
- component = null
59
- })
46
+ class AsyncLoadComponent extends Component {
47
+ async load () {
48
+ throw new Error('Async Load Error!')
49
+ }
50
+ }
51
+ Component.define('async-load-component', AsyncLoadComponent)
60
52
 
61
- it('can be instantiated', () => {
62
- expect(component).toBeTruthy()
63
- })
53
+ class SyncLoadComponent extends Component {
54
+ load () {
55
+ throw new Error('Sync Load Error!')
56
+ }
57
+ }
58
+ Component.define('sync-load-component', SyncLoadComponent)
64
59
 
65
- it('has an stable public api', () => {
66
- expect(typeof component.constructor.define).toEqual('function')
67
- expect(typeof component.init).toEqual('function')
68
- expect(typeof component.reflectedProperties).toEqual('function')
69
- expect(typeof component.connectedCallback).toEqual('function')
70
- expect(typeof component.render).toEqual('function')
71
- expect(typeof component.load).toEqual('function')
72
- expect(typeof component.select).toEqual('function')
73
- expect(typeof component.selectAll).toEqual('function')
74
- expect(typeof component.emit).toEqual('function')
75
- expect(typeof component.content).toBeDefined()
76
- })
60
+ class PlainLoadComponent extends Component {
61
+ load () {
62
+ return null
63
+ }
64
+ }
65
+ Component.define('plain-load-component', PlainLoadComponent)
77
66
 
78
- it('has an init method through which state is set', () => {
79
- const response = component.init({ attribute: 'value' })
80
- expect(response).toBe(component)
81
- })
67
+ let container = null
68
+ let component = null
82
69
 
83
- it('can set its content via a property', () => {
84
- component.content = '<p>Hello World</p>'
85
- const paragraph = component.querySelector('p')
86
- expect(component.content).toBe('<p>Hello World</p>')
87
- expect(paragraph.outerHTML).toBe('<p>Hello World</p>')
88
- })
70
+ const setup = () => {
71
+ document.body.innerHTML = ''
72
+ container = document.createElement('div')
73
+ container.innerHTML = '<mock-component code="XYZ123"></mock-component>'
74
+ component = container.querySelector('mock-component')
75
+ document.body.append(container)
76
+ }
89
77
 
90
- it('can have some of its attributes reflected as properties', () => {
91
- expect(component.code).toBe('XYZ123')
92
- })
93
- it('has an asynchronous load method which is empty by default', async () => {
94
- expect(await component.load()).toBeUndefined()
95
- })
78
+ it('can be instantiated', () => {
79
+ setup()
80
+ assert.ok(component)
81
+ })
96
82
 
97
- it('sets its tag name as class when rendered', () => {
98
- component.render()
99
- expect(component.className).toEqual('mock-component')
100
- })
83
+ it('has an stable public api', () => {
84
+ setup()
85
+ assert.deepStrictEqual(typeof component.constructor.define, 'function')
86
+ assert.deepStrictEqual(typeof component.init, 'function')
87
+ assert.deepStrictEqual(typeof component.reflectedProperties, 'function')
88
+ assert.deepStrictEqual(typeof component.connectedCallback, 'function')
89
+ assert.deepStrictEqual(typeof component.render, 'function')
90
+ assert.deepStrictEqual(typeof component.load, 'function')
91
+ assert.deepStrictEqual(typeof component.select, 'function')
92
+ assert.deepStrictEqual(typeof component.selectAll, 'function')
93
+ assert.deepStrictEqual(typeof component.emit, 'function')
94
+ assert.notStrictEqual(typeof component.content, undefined)
95
+ })
101
96
 
102
- it('keeps its previous classes after rendering', () => {
103
- component.classList.add('custom-class')
104
- component.classList.add('custom-class')
105
- component.classList.add('special-class')
106
- component.render()
107
- expect(component.className).toEqual(
108
- 'mock-component custom-class special-class')
109
- })
97
+ it('has an init method through which state is set', () => {
98
+ setup()
99
+ const response = component.init({ attribute: 'value' })
100
+ assert.strictEqual(response, component)
101
+ })
110
102
 
111
- it('emits custom events', () => {
112
- let detail = null
113
- const handler = (event) => { detail = event.detail }
114
- component.addEventListener('fire', handler)
103
+ it('can set its content via a property', () => {
104
+ setup()
105
+ component.content = '<p>Hello World</p>'
106
+ const paragraph = component.querySelector('p')
107
+ assert.strictEqual(component.content, '<p>Hello World</p>')
108
+ assert.strictEqual(paragraph.outerHTML, '<p>Hello World</p>')
109
+ })
115
110
 
116
- component.emit('fire', { location: 'indoors' })
111
+ it('can have some of its attributes reflected as properties', () => {
112
+ setup()
113
+ assert.strictEqual(component.code, 'XYZ123')
114
+ })
115
+ it('has an asynchronous load method which is empty by default', async () => {
116
+ setup()
117
+ assert.strictEqual(await component.load(), undefined)
118
+ })
117
119
 
118
- expect(detail).toEqual({ location: 'indoors' })
119
- })
120
+ it('sets its tag name as class when rendered', () => {
121
+ setup()
122
+ component.render()
123
+ assert.deepStrictEqual(component.className, 'mock-component')
124
+ })
120
125
 
121
- it('calls the load method on connectedCallback', async () => {
122
- const component = /** @type {Component} */ (
123
- document.createElement('mock-component'))
124
- const initSpy = jest.spyOn(component, 'init')
125
- const renderSpy = jest.spyOn(component, 'render')
126
- const loadSpy = jest.spyOn(component, 'load')
126
+ it('keeps its previous classes after rendering', () => {
127
+ setup()
128
+ component.classList.add('custom-class')
129
+ component.classList.add('custom-class')
130
+ component.classList.add('special-class')
131
+ component.render()
132
+ assert.deepStrictEqual(component.className, 'mock-component custom-class special-class')
133
+ })
127
134
 
128
- document.body.append(component)
135
+ it('emits custom events', () => {
136
+ setup()
137
+ let detail = null
138
+ const handler = (event) => { detail = event.detail }
139
+ component.addEventListener('fire', handler)
129
140
 
130
- expect(initSpy).toHaveBeenCalledTimes(1)
131
- expect(renderSpy).toHaveBeenCalledTimes(1)
132
- expect(loadSpy).toHaveBeenCalledTimes(1)
133
- })
141
+ component.emit('fire', { location: 'indoors' })
134
142
 
135
- it('catches and re-raises connectedCallback errors', async () => {
136
- expect.assertions(1)
137
- const component = /** @type {Component} */ (
138
- document.createElement('mock-component'))
139
- const consoleError = console.error
140
- console.error = jest.fn()
141
- component.render = () => {
142
- throw new Error('Render Error!')
143
- }
143
+ assert.deepStrictEqual(detail, { location: 'indoors' })
144
+ })
144
145
 
145
- try {
146
- component.connectedCallback()
147
- } catch (error) {
148
- expect(error.message).toEqual('Render Error!')
149
- }
146
+ it('calls the load method on connectedCallback', async () => {
147
+ setup()
148
+ const component = /** @type {Component} */ (
149
+ document.createElement('mock-component'))
150
+ const initSpy = mock.method(component, 'init')
151
+ const renderSpy = mock.method(component, 'render')
152
+ const loadSpy = mock.method(component, 'load')
153
+
154
+ document.body.append(component)
155
+
156
+ assert.strictEqual(initSpy.mock.calls.length, 1)
157
+ assert.strictEqual(renderSpy.mock.calls.length, 1)
158
+ assert.strictEqual(loadSpy.mock.calls.length, 1)
159
+ initSpy.mock.restore()
160
+ renderSpy.mock.restore()
161
+ loadSpy.mock.restore()
162
+ })
150
163
 
151
- console.error = consoleError
152
- })
164
+ it('catches and re-raises connectedCallback errors', async () => {
165
+ setup()
166
+ const component = /** @type {Component} */ (
167
+ document.createElement('mock-component'))
168
+ const consoleErrorMock = mock.method(console, 'error', () => {})
169
+ component.render = () => {
170
+ throw new Error('Render Error!')
171
+ }
153
172
 
154
- it('selects the children matching a selector', () => {
155
- container.innerHTML = `
156
- <mock-component>
157
- <div class="blue"></div>
158
- <div class="red"></div>
159
- <div class="red"></div>
160
- </mock-component>
161
- `
162
- const component = container.querySelector('mock-component')
173
+ try {
174
+ component.connectedCallback()
175
+ } catch (error) {
176
+ assert.deepStrictEqual(error.message, 'Render Error!')
177
+ } finally {
178
+ consoleErrorMock.mock.restore()
179
+ }
180
+ })
163
181
 
164
- const blue = component.select('.blue')
165
- expect(blue.tagName).toEqual('DIV')
182
+ it('emits an error event when async load fails', async () => {
183
+ setup()
184
+ const component = document.createElement('async-load-component')
185
+ let errorEvent = null
166
186
 
167
- const red = component.selectAll('.red')
168
- expect(red.length).toEqual(2)
187
+ component.addEventListener('error', (event) => {
188
+ errorEvent = event
169
189
  })
170
190
 
171
- it('retrieves its slots through the slots method', () => {
172
- container.innerHTML = `
173
- <mock-component>
174
- <div slot="header" class="header"></div>
175
- <div class="body"></div>
176
- <div class="aside"></div>
177
- <div slot="footer" class="footer"></div>
178
- </mock-component>
179
- `
180
- const component = container.querySelector('mock-component')
181
-
182
- expect(component.slots).toEqual({
183
- header: [component.select('.header')],
184
- general: [component.select('.body'), component.select('.aside')],
185
- footer: [component.select('.footer')]
186
- })
187
- })
191
+ document.body.appendChild(component)
188
192
 
189
- it('binds its properties to children events', async () => {
190
- container.innerHTML = `
191
- <mock-component>
192
- <input type="text" listen on-input="{{ data.value = data }}"></input>
193
- </mock-component>
194
- `
193
+ await new Promise(resolve => setTimeout(resolve, 0))
195
194
 
196
- const component = container.querySelector('mock-component')
197
- const input = component.select('input')
195
+ assert.ok(errorEvent)
196
+ assert.deepStrictEqual(errorEvent.detail.message, 'Async Load Error!')
197
+ })
198
198
 
199
- input.dispatchEvent(
200
- new globalThis.InputEvent('input', { bubbles: true, data: 'E' })
201
- )
199
+ it('emits an error event and throws when sync load fails', () => {
200
+ setup()
201
+ const component = document.createElement('sync-load-component')
202
+ let errorEvent = null
202
203
 
203
- expect(component.data.value).toEqual('E')
204
+ component.addEventListener('error', (event) => {
205
+ errorEvent = event
204
206
  })
205
207
 
206
- it('binds multiple handlers to the same event', async () => {
207
- container.innerHTML = `
208
- <mock-component>
209
- <input type="text"
210
- listen
211
- on-input="{{ data.value = data }}"
212
- on-input-1="{{ data.value1 = data }}"
213
- on-input-2="{{ data.value2 = data }}"
214
- on-input-3="{{ data.value3 = data }}"
215
- ></input>
216
- </mock-component>
217
- `
208
+ assert.throws(() => {
209
+ component.connectedCallback()
210
+ }, /Sync Load Error!/)
218
211
 
219
- const component = container.querySelector('mock-component')
220
- const input = component.select('input')
212
+ assert.ok(errorEvent)
213
+ assert.deepStrictEqual(errorEvent.detail.message, 'Sync Load Error!')
214
+ })
221
215
 
222
- input.dispatchEvent(
223
- new globalThis.InputEvent('input', { bubbles: true, data: 'E' })
224
- )
216
+ it('supports load implementations that do not return promises', () => {
217
+ setup()
218
+ const component = document.createElement('plain-load-component')
225
219
 
226
- expect(component.data.value).toEqual('E')
227
- expect(component.data.value1).toEqual('E')
228
- expect(component.data.value2).toEqual('E')
229
- expect(component.data.value3).toEqual('E')
220
+ assert.doesNotThrow(() => {
221
+ component.connectedCallback()
230
222
  })
223
+ })
231
224
 
232
- it('binds to the target.value event property by default', async () => {
233
- container.innerHTML = `
234
- <mock-component>
235
- <input type="text" listen on-input="{{ data.value }}"></input>
236
- </mock-component>
237
- `
238
- const component = container.querySelector('mock-component')
239
- const input = component.select('input')
240
- const inputEvent = new globalThis.InputEvent('input')
241
- const target = { name: 'input', value: 'X' }
242
- Object.defineProperty(
243
- inputEvent, 'target', { writable: false, value: target })
244
-
245
- input.dispatchEvent(inputEvent)
225
+ it('selects the children matching a selector', () => {
226
+ setup();
227
+ container.innerHTML = `
228
+ <mock-component>
229
+ <div class="blue"></div>
230
+ <div class="red"></div>
231
+ <div class="red"></div>
232
+ </mock-component>
233
+ `
234
+ const component = container.querySelector('mock-component')
235
+
236
+ const blue = component.select('.blue')
237
+ assert.deepStrictEqual(blue.tagName, 'DIV')
238
+
239
+ const red = component.selectAll('.red')
240
+ assert.deepStrictEqual(red.length, 2)
241
+ })
246
242
 
247
- expect(component.data.value).toEqual('X')
243
+ it('retrieves its slots through the slots method', () => {
244
+ setup();
245
+ container.innerHTML = `
246
+ <mock-component>
247
+ <div slot="header" class="header"></div>
248
+ <div class="body"></div>
249
+ <div class="aside"></div>
250
+ <div slot="footer" class="footer"></div>
251
+ </mock-component>
252
+ `
253
+ const component = container.querySelector('mock-component')
254
+
255
+ assert.deepStrictEqual(component.slots, {
256
+ header: [component.select('.header')],
257
+ general: [component.select('.body'), component.select('.aside')],
258
+ footer: [component.select('.footer')]
248
259
  })
260
+ })
249
261
 
250
- it('binds to the detail custom event property', async () => {
251
- container.innerHTML = `
252
- <mock-component>
253
- <input type="text" listen on-alter="{{ data.value }}"></input>
254
- </mock-component>
255
- `
256
-
257
- const component = container.querySelector('mock-component')
258
- const input = component.select('input')
262
+ it('binds its properties to children events', async () => {
263
+ setup();
264
+ container.innerHTML = `
265
+ <mock-component>
266
+ <input type="text" listen on-input="{{ data.value = data }}"></input>
267
+ </mock-component>
268
+ `
259
269
 
260
- input.dispatchEvent(
261
- new globalThis.CustomEvent('alter', { bubbles: true, detail: 'A' }))
270
+ const component = container.querySelector('mock-component')
271
+ const input = component.select('input')
262
272
 
263
- expect(component.data.value).toEqual('A')
264
- })
273
+ input.dispatchEvent(
274
+ new globalThis.InputEvent('input', { bubbles: true, data: 'E' })
275
+ )
265
276
 
266
- it('performs basic transformations to event properties', async () => {
267
- container.innerHTML = `
268
- <mock-component>
269
- <input type="text" listen on-alter="{{ data.value | number }}"></input>
270
- </mock-component>
271
- `
277
+ assert.deepStrictEqual(component.data.value, 'E')
278
+ })
272
279
 
273
- const component = container.querySelector('mock-component')
274
- const input = component.select('input')
280
+ it('binds multiple handlers to the same event', async () => {
281
+ setup();
282
+ container.innerHTML = `
283
+ <mock-component>
284
+ <input type="text"
285
+ listen
286
+ on-input="{{ data.value = data }}"
287
+ on-input-1="{{ data.value1 = data }}"
288
+ on-input-2="{{ data.value2 = data }}"
289
+ on-input-3="{{ data.value3 = data }}"
290
+ ></input>
291
+ </mock-component>
292
+ `
293
+
294
+ const component = container.querySelector('mock-component')
295
+ const input = component.select('input')
296
+
297
+ input.dispatchEvent(
298
+ new globalThis.InputEvent('input', { bubbles: true, data: 'E' })
299
+ )
300
+
301
+ assert.deepStrictEqual(component.data.value, 'E')
302
+ assert.deepStrictEqual(component.data.value1, 'E')
303
+ assert.deepStrictEqual(component.data.value2, 'E')
304
+ assert.deepStrictEqual(component.data.value3, 'E')
305
+ })
275
306
 
276
- input.dispatchEvent(
277
- new globalThis.CustomEvent('alter', { bubbles: true, detail: '777' }))
307
+ it('binds to the target.value event property by default', async () => {
308
+ setup()
309
+ container.innerHTML = `
310
+ <mock-component>
311
+ <input type="text" listen on-input="{{ data.value }}"></input>
312
+ </mock-component>
313
+ `
314
+ const component = container.querySelector('mock-component')
315
+ const input = component.select('input')
316
+ input.value = 'X'
317
+ const inputEvent = new globalThis.InputEvent('input', { bubbles: true })
318
+
319
+ input.dispatchEvent(inputEvent)
320
+
321
+ assert.deepStrictEqual(component.data.value, 'X')
322
+ })
278
323
 
279
- expect(component.data.value).toEqual(777)
280
- })
324
+ it('binds to the detail custom event property', async () => {
325
+ setup();
326
+ container.innerHTML = `
327
+ <mock-component>
328
+ <input type="text" listen on-alter="{{ data.value }}"></input>
329
+ </mock-component>
330
+ `
281
331
 
282
- it('performs object assignment of event details', async () => {
283
- container.innerHTML = `
284
- <mock-component>
285
- <input type="text" listen on-alter="{{ local | object }}"></input>
286
- </mock-component>
287
- `
332
+ const component = container.querySelector('mock-component')
333
+ const input = component.select('input')
288
334
 
289
- const component = container.querySelector('mock-component')
290
- const input = component.select('input')
335
+ input.dispatchEvent(
336
+ new globalThis.CustomEvent('alter', { bubbles: true, detail: 'A' }))
291
337
 
292
- const detail = { name: 'Sprite', brand: 'Coca-Cola' }
293
- input.dispatchEvent(
294
- new globalThis.CustomEvent('alter', { bubbles: true, detail }))
338
+ assert.deepStrictEqual(component.data.value, 'A')
339
+ })
295
340
 
296
- expect(component.local).toEqual({ name: 'Sprite', brand: 'Coca-Cola' })
297
- })
341
+ it('performs basic transformations to event properties', async () => {
342
+ setup();
343
+ container.innerHTML = `
344
+ <mock-component>
345
+ <input type="text" listen on-alter="{{ data.value | number }}"></input>
346
+ </mock-component>
347
+ `
298
348
 
299
- it('performs nested object assignment of event details', async () => {
300
- container.innerHTML = `
301
- <mock-component>
302
- <input type="text" listen on-alter="{{ local.zone | object }}"></input>
303
- </mock-component>
304
- `
349
+ const component = container.querySelector('mock-component')
350
+ const input = component.select('input')
305
351
 
306
- const component = container.querySelector('mock-component')
307
- component.local.zone = {}
308
- const input = component.select('input')
352
+ input.dispatchEvent(
353
+ new globalThis.CustomEvent('alter', { bubbles: true, detail: '777' }))
309
354
 
310
- const detail = { country: 'USA', city: 'Atlanta' }
311
- input.dispatchEvent(
312
- new globalThis.CustomEvent('alter', { bubbles: true, detail }))
355
+ assert.deepStrictEqual(component.data.value, 777)
356
+ })
313
357
 
314
- expect(component.local).toEqual({
315
- zone: {
316
- country: 'USA',
317
- city: 'Atlanta'
318
- }
319
- })
320
- })
358
+ it('performs object assignment of event details', async () => {
359
+ setup();
360
+ container.innerHTML = `
361
+ <mock-component>
362
+ <input type="text" listen on-alter="{{ local | object }}"></input>
363
+ </mock-component>
364
+ `
321
365
 
322
- it('listens to events and handles them with its own methods', async () => {
323
- container.innerHTML = `
324
- <mock-component code="ABC123">
325
- <input type="text" listen on-alter="customHandler"></input>
326
- </mock-component>
327
- `
366
+ const component = container.querySelector('mock-component')
367
+ const input = component.select('input')
328
368
 
329
- const component = container.querySelector('mock-component')
330
- const input = component.select('input')
369
+ const detail = { name: 'Sprite', brand: 'Coca-Cola' }
370
+ input.dispatchEvent(
371
+ new globalThis.CustomEvent('alter', { bubbles: true, detail }))
331
372
 
332
- const event = new globalThis.CustomEvent(
333
- 'alter', { bubbles: true, detail: { code: [] } })
334
- input.dispatchEvent(event)
373
+ assert.deepStrictEqual(component.local, { name: 'Sprite', brand: 'Coca-Cola' })
374
+ })
335
375
 
336
- expect(event.detail.code).toEqual(['ABC123'])
376
+ it('performs nested object assignment of event details', async () => {
377
+ setup();
378
+ container.innerHTML = `
379
+ <mock-component>
380
+ <input type="text" listen on-alter="{{ local.zone | object }}"></input>
381
+ </mock-component>
382
+ `
383
+
384
+ const component = container.querySelector('mock-component')
385
+ component.local.zone = {}
386
+ const input = component.select('input')
387
+
388
+ const detail = { country: 'USA', city: 'Atlanta' }
389
+ input.dispatchEvent(
390
+ new globalThis.CustomEvent('alter', { bubbles: true, detail }))
391
+
392
+ assert.deepStrictEqual(component.local, {
393
+ zone: {
394
+ country: 'USA',
395
+ city: 'Atlanta'
396
+ }
337
397
  })
398
+ })
338
399
 
339
- it('listens to events and redirects them to target components', async () => {
340
- container.innerHTML = `
341
- <mock-component code="ABC123">
342
- <input type="text" listen on-alter="customHandler@#child"></input>
343
- <mock-component id="child" code="XYZ890"></mock-component>
344
- </mock-component>
345
- `
400
+ it('listens to events and handles them with its own methods', async () => {
401
+ setup();
402
+ container.innerHTML = `
403
+ <mock-component code="ABC123">
404
+ <input type="text" listen on-alter="customHandler"></input>
405
+ </mock-component>
406
+ `
346
407
 
347
- const component = container.querySelector('mock-component')
348
- const input = component.select('input')
408
+ const component = container.querySelector('mock-component')
409
+ const input = component.select('input')
349
410
 
350
- const event = new globalThis.CustomEvent(
351
- 'alter', { bubbles: true, detail: { code: [] } })
352
- input.dispatchEvent(event)
411
+ const event = new globalThis.CustomEvent(
412
+ 'alter', { bubbles: true, detail: { code: [] } })
413
+ input.dispatchEvent(event)
353
414
 
354
- expect(event.detail.code).toEqual(['XYZ890'])
355
- })
415
+ assert.deepStrictEqual(event.detail.code, ['ABC123'])
416
+ })
356
417
 
357
- it('does not launch and event if the target is not found', async () => {
358
- container.innerHTML = `
359
- <mock-component code="ABC123">
360
- <input type="text" listen on-alter="customHandler@#missing"></input>
361
- <mock-component id="child" code="XYZ890"></mock-component>
362
- </mock-component>
363
- `
418
+ it('listens to events and redirects them to target components', async () => {
419
+ setup();
420
+ container.innerHTML = `
421
+ <mock-component code="ABC123">
422
+ <input type="text" listen on-alter="customHandler@#child"></input>
423
+ <mock-component id="child" code="XYZ890"></mock-component>
424
+ </mock-component>
425
+ `
364
426
 
365
- const component = container.querySelector('mock-component')
366
- const input = component.select('input')
427
+ const component = container.querySelector('mock-component')
428
+ const input = component.select('input')
367
429
 
368
- const event = new globalThis.CustomEvent(
369
- 'alter', { bubbles: true, detail: { code: [] } })
370
- input.dispatchEvent(event)
430
+ const event = new globalThis.CustomEvent(
431
+ 'alter', { bubbles: true, detail: { code: [] } })
432
+ input.dispatchEvent(event)
371
433
 
372
- expect(event.detail.code).toEqual([])
373
- })
434
+ assert.deepStrictEqual(event.detail.code, ['XYZ890'])
435
+ })
374
436
 
437
+ it('does not launch and event if the target is not found', async () => {
438
+ setup();
439
+ container.innerHTML = `
440
+ <mock-component code="ABC123">
441
+ <input type="text" listen on-alter="customHandler@#missing"></input>
442
+ <mock-component id="child" code="XYZ890"></mock-component>
443
+ </mock-component>
444
+ `
375
445
 
376
- it('listens to clicks and redirects them to target components', async () => {
377
- container.innerHTML = `
378
- <mock-component code="ABC123">
379
- <div type="text" listen on-click="customHandler@#child">
380
- <button id="action">Action</button>
381
- </div>
382
- <mock-component id="child" code="XYZ890"></mock-component>
383
- </mock-component>
384
- `
446
+ const component = container.querySelector('mock-component')
447
+ const input = component.select('input')
385
448
 
386
- const component = container.querySelector('mock-component')
387
- const button = component.select('#action')
449
+ const event = new globalThis.CustomEvent(
450
+ 'alter', { bubbles: true, detail: { code: [] } })
451
+ input.dispatchEvent(event)
388
452
 
389
- const event = new globalThis.CustomEvent(
390
- 'click', { bubbles: true, detail: { code: [] } })
391
- button.dispatchEvent(event)
453
+ assert.deepStrictEqual(event.detail.code, [])
454
+ })
392
455
 
393
- expect(event.detail.code).toEqual(['XYZ890'])
394
- })
395
456
 
396
- it('listens to events and pipes them to target components', async () => {
397
- container.innerHTML = `
398
- <mock-component code="ABC123">
399
- <div type="text" listen on-change="replaceChildren%detail.name@#child">
400
- <button id="action">Action</button>
401
- </div>
402
- <div id="child">
403
- Hello
404
- </div>
405
- </mock-component>
406
- `
407
- const component = container.querySelector('mock-component')
408
- const button = component.select('#action')
409
-
410
- const event = new globalThis.CustomEvent(
411
- 'change', { bubbles: true, detail: { name: 'World' } })
412
- button.dispatchEvent(event)
413
-
414
- const child = container.querySelector('#child')
415
- expect(child.textContent.trim()).toEqual('World')
416
- })
457
+ it('listens to clicks and redirects them to target components', async () => {
458
+ setup();
459
+ container.innerHTML = `
460
+ <mock-component code="ABC123">
461
+ <div type="text" listen on-click="customHandler@#child">
462
+ <button id="action">Action</button>
463
+ </div>
464
+ <mock-component id="child" code="XYZ890"></mock-component>
465
+ </mock-component>
466
+ `
467
+
468
+ const component = container.querySelector('mock-component')
469
+ const button = component.select('#action')
470
+
471
+ const event = new globalThis.CustomEvent(
472
+ 'click', { bubbles: true, detail: { code: [] } })
473
+ button.dispatchEvent(event)
417
474
 
418
- it('emits an error event on declared listeners', async () => {
419
- container.innerHTML = `
420
- <mock-component>
421
- <input type="text" listen on-alter="erroringHandler"></input>
475
+ assert.deepStrictEqual(event.detail.code, ['XYZ890'])
476
+ })
477
+
478
+ it('listens to events and pipes them to target components', async () => {
479
+ setup();
480
+ container.innerHTML = `
481
+ <mock-component code="ABC123">
482
+ <div type="text" listen on-change="replaceChildren%detail.name@#child">
483
+ <button id="action">Action</button>
484
+ </div>
485
+ <div id="child">
486
+ Hello
487
+ </div>
422
488
  </mock-component>
423
- `
489
+ `
490
+ const component = container.querySelector('mock-component')
491
+ const button = component.select('#action')
424
492
 
425
- const component = container.querySelector('mock-component')
493
+ const event = new globalThis.CustomEvent(
494
+ 'change', { bubbles: true, detail: { name: 'World' } })
495
+ button.dispatchEvent(event)
426
496
 
427
- let errorEvent = {}
428
- component.addEventListener('error', (event) => { errorEvent = event })
497
+ const child = container.querySelector('#child')
498
+ assert.deepStrictEqual(child.textContent.trim(), 'World')
499
+ })
429
500
 
430
- const input = component.select('input')
501
+ it('emits an error event on declared listeners', async () => {
502
+ setup();
503
+ container.innerHTML = `
504
+ <mock-component>
505
+ <input type="text" listen on-alter="erroringHandler"></input>
506
+ </mock-component>
507
+ `
431
508
 
432
- input.dispatchEvent(
433
- new globalThis.CustomEvent(
434
- 'alter', { bubbles: true, detail: 'I will error!' }
435
- ))
509
+ const component = container.querySelector('mock-component')
436
510
 
437
- expect(errorEvent.detail.message).toEqual('Something went wrong!')
438
- })
511
+ let errorEvent = {}
512
+ component.addEventListener('error', (event) => { errorEvent = event })
439
513
 
440
- it('emits an error event on declared async listeners', async () => {
441
- container.innerHTML = `
442
- <mock-component>
443
- <input type="text" listen on-alter="asyncErroringHandler"></input>
444
- </mock-component>
445
- `
514
+ const input = component.select('input')
446
515
 
447
- const component = container.querySelector('mock-component')
516
+ input.dispatchEvent(
517
+ new globalThis.CustomEvent(
518
+ 'alter', { bubbles: true, detail: 'I will error!' }
519
+ ))
448
520
 
449
- let errorEvent = {}
450
- component.addEventListener('error', (event) => { errorEvent = event })
521
+ assert.deepStrictEqual(errorEvent.detail.message, 'Something went wrong!')
522
+ })
451
523
 
452
- const input = component.select('input')
524
+ it('emits an error event on declared async listeners', async () => {
525
+ setup();
526
+ container.innerHTML = `
527
+ <mock-component>
528
+ <input type="text" listen on-alter="asyncErroringHandler"></input>
529
+ </mock-component>
530
+ `
453
531
 
454
- input.dispatchEvent(
455
- new globalThis.CustomEvent(
456
- 'alter', { bubbles: true, detail: 'I will error!' }
457
- ))
532
+ const component = container.querySelector('mock-component')
458
533
 
459
- // Sleep
460
- await new Promise(resolve => setTimeout(resolve, 0))
534
+ let errorEvent = {}
535
+ component.addEventListener('error', (event) => { errorEvent = event })
461
536
 
462
- expect(errorEvent.detail.message).toEqual('Something went async wrong!')
463
- })
537
+ const input = component.select('input')
464
538
 
465
- it('resolves its resource dependencies using events propagation', () => {
466
- const listener = (event) => {
467
- const resource = event.detail.resource
468
- event.detail[resource] = 'RESOLVED_DEPENDENCY'
469
- }
470
- document.addEventListener('resolve', listener)
471
- container.innerHTML = `
472
- <mock-component></mock-component>
473
- `
474
- const component = container.querySelector('mock-component')
475
-
476
- expect(component.dependency).toEqual('RESOLVED_DEPENDENCY')
477
- document.removeEventListener('resolve', listener)
478
- })
539
+ input.dispatchEvent(
540
+ new globalThis.CustomEvent(
541
+ 'alter', { bubbles: true, detail: 'I will error!' }
542
+ ))
479
543
 
480
- it('provides the dependencies requested to it by child components', () => {
481
- class ParentComponent extends MockComponent {
482
- provide (resource) {
483
- if (resource === 'Dependency') {
484
- return `RESOURCE: ${resource} PROVIDED BY: ${this.id}`
485
- }
486
- if (resource === 'state') return { key: 'value' }
544
+ // Sleep
545
+ await new Promise(resolve => setTimeout(resolve, 0))
546
+
547
+ assert.deepStrictEqual(errorEvent.detail.message, 'Something went async wrong!')
548
+ })
549
+
550
+ it('resolves its resource dependencies using events propagation', () => {
551
+ setup();
552
+ const listener = (event) => {
553
+ const resource = event.detail.resource
554
+ event.detail[resource] = 'RESOLVED_DEPENDENCY'
555
+ }
556
+ document.addEventListener('resolve', listener)
557
+ container.innerHTML = `
558
+ <mock-component></mock-component>
559
+ `
560
+ const component = container.querySelector('mock-component')
561
+
562
+ assert.deepStrictEqual(component.dependency, 'RESOLVED_DEPENDENCY')
563
+ document.removeEventListener('resolve', listener)
564
+ })
565
+
566
+ it('provides the dependencies requested to it by child components', () => {
567
+ setup();
568
+ class ParentComponent extends MockComponent {
569
+ provide (resource) {
570
+ if (resource === 'Dependency') {
571
+ return `RESOURCE: ${resource} PROVIDED BY: ${this.id}`
487
572
  }
573
+ if (resource === 'state') return { key: 'value' }
488
574
  }
489
- Component.define('parent-component', ParentComponent)
575
+ }
576
+ Component.define('parent-component', ParentComponent)
490
577
 
491
- container.innerHTML = `
492
- <parent-component id="parent">
493
- <mock-component id="child"></mock-component>
494
- </parent-component>
495
- `
578
+ container.innerHTML = `
579
+ <parent-component id="parent">
580
+ <mock-component id="child"></mock-component>
581
+ </parent-component>
582
+ `
496
583
 
497
- const child = container.querySelector('#child')
584
+ const child = container.querySelector('#child')
498
585
 
499
- expect(child.dependency).toEqual(
500
- 'RESOURCE: Dependency PROVIDED BY: parent')
586
+ assert.deepStrictEqual(child.dependency, 'RESOURCE: Dependency PROVIDED BY: parent')
501
587
 
502
- const state = child.resolve('state')
503
- expect(state).toEqual({ key: 'value' })
588
+ const state = child.resolve('state')
589
+ assert.deepStrictEqual(state, { key: 'value' })
504
590
 
505
- const unknown = child.resolve('unknown')
506
- expect(unknown).toBe(undefined)
507
- })
591
+ const unknown = child.resolve('unknown')
592
+ assert.strictEqual(unknown, undefined)
593
+ })
508
594
 
509
- it('provides a styleNames utility function for setting styles', () => {
510
- container.innerHTML = `
511
- <mock-component class></mock-component>
512
- `
513
- const component = container.querySelector('mock-component')
514
- const background = 'primary'
515
- const shadow = 'small'
516
- const color = ''
517
- const styleMap = {
518
- [`background-${background}`]: background,
519
- [`color-${color}`]: color,
520
- [`shadow-${shadow}`]: shadow
521
- }
595
+ it('provides a styleNames utility function for setting styles', () => {
596
+ setup();
597
+ container.innerHTML = `
598
+ <mock-component class></mock-component>
599
+ `
600
+ const component = container.querySelector('mock-component')
601
+ const background = 'primary'
602
+ const shadow = 'small'
603
+ const color = ''
604
+ const styleMap = {
605
+ [`background-${background}`]: background,
606
+ [`color-${color}`]: color,
607
+ [`shadow-${shadow}`]: shadow
608
+ }
522
609
 
523
- const result = component.styleNames(styleMap)
610
+ const result = component.styleNames(styleMap)
524
611
 
525
- expect(result).toEqual('background-primary shadow-small')
526
- })
612
+ assert.deepStrictEqual(result, 'background-primary shadow-small')
527
613
  })