@knowark/componarkjs 1.13.4 → 1.14.1
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.
- package/README.md +57 -45
- package/lib/base/component/component.js +142 -20
- package/lib/base/component/component.test.js +753 -374
- package/lib/base/component/index.js +3 -0
- package/lib/base/styles/index.js +4 -1
- package/lib/base/utils/define.js +30 -7
- package/lib/base/utils/define.test.js +129 -42
- package/lib/base/utils/format.js +12 -6
- package/lib/base/utils/format.test.js +16 -16
- package/lib/base/utils/helpers.js +42 -9
- package/lib/base/utils/helpers.test.js +134 -115
- package/lib/base/utils/index.js +1 -0
- package/lib/base/utils/slots.js +3 -2
- package/lib/base/utils/slots.test.js +38 -38
- package/lib/base/utils/uuid.js +1 -1
- package/lib/base/utils/uuid.test.js +13 -13
- package/lib/components/audio/components/audio.js +36 -3
- package/lib/components/audio/components/audio.test.js +120 -90
- package/lib/components/audio/index.js +1 -0
- package/lib/components/audio/styles/index.js +5 -1
- package/lib/components/camera/components/camera.js +15 -0
- package/lib/components/camera/components/camera.test.js +96 -91
- package/lib/components/camera/index.js +1 -0
- package/lib/components/camera/styles/index.js +5 -1
- package/lib/components/capture/components/capture.js +48 -4
- package/lib/components/capture/components/capture.test.js +165 -97
- package/lib/components/capture/index.js +1 -0
- package/lib/components/droparea/components/droparea-preview.js +114 -19
- package/lib/components/droparea/components/droparea-preview.test.js +344 -80
- package/lib/components/droparea/components/droparea.js +82 -6
- package/lib/components/droparea/components/droparea.test.js +309 -299
- package/lib/components/droparea/index.js +1 -0
- package/lib/components/droparea/styles/index.js +5 -1
- package/lib/components/emit/components/emit.js +34 -4
- package/lib/components/emit/components/emit.test.js +192 -134
- package/lib/components/emit/index.js +1 -0
- package/lib/components/index.js +2 -1
- package/lib/components/list/components/item.js +6 -0
- package/lib/components/list/components/item.test.js +69 -68
- package/lib/components/list/components/list.js +51 -7
- package/lib/components/list/components/list.test.js +358 -227
- package/lib/components/list/index.js +1 -0
- package/lib/components/paginator/components/paginator.js +34 -8
- package/lib/components/paginator/components/paginator.test.js +146 -143
- package/lib/components/paginator/index.js +1 -0
- package/lib/components/paginator/styles/index.js +5 -1
- package/lib/components/spinner/components/spinner.js +10 -0
- package/lib/components/spinner/components/spinner.test.js +36 -41
- package/lib/components/spinner/index.js +1 -0
- package/lib/components/spinner/styles/index.js +5 -1
- package/lib/components/splitview/components/splitview.detail.js +10 -1
- package/lib/components/splitview/components/splitview.detail.test.js +75 -73
- package/lib/components/splitview/components/splitview.js +54 -11
- package/lib/components/splitview/components/splitview.master.js +37 -2
- package/lib/components/splitview/components/splitview.master.test.js +52 -52
- package/lib/components/splitview/components/splitview.test.js +135 -31
- package/lib/components/splitview/index.js +1 -0
- package/lib/components/translate/components/translate.js +65 -14
- package/lib/components/translate/components/translate.test.js +658 -131
- package/lib/components/translate/index.js +1 -0
- package/lib/index.js +3 -0
- package/package.json +4 -27
- package/scripts/node-test-setup.js +94 -0
- package/tsconfig.json +1 -1
- package/types/base/component/component.d.ts +43 -8
- package/types/base/component/component.d.ts.map +1 -1
- package/types/base/component/index.d.ts +4 -6
- package/types/base/component/index.d.ts.map +1 -1
- package/types/base/styles/index.d.ts +3 -2
- package/types/base/styles/index.d.ts.map +1 -1
- package/types/base/utils/define.d.ts +3 -2
- package/types/base/utils/define.d.ts.map +1 -1
- package/types/base/utils/format.d.ts +12 -6
- package/types/base/utils/format.d.ts.map +1 -1
- package/types/base/utils/helpers.d.ts +27 -7
- package/types/base/utils/helpers.d.ts.map +1 -1
- package/types/base/utils/slots.d.ts +8 -10
- package/types/base/utils/slots.d.ts.map +1 -1
- package/types/base/utils/uuid.d.ts +1 -1
- package/types/base/utils/uuid.d.ts.map +1 -1
- package/types/components/audio/components/audio.d.ts +23 -9
- package/types/components/audio/components/audio.d.ts.map +1 -1
- package/types/components/audio/styles/index.d.ts +3 -2
- package/types/components/audio/styles/index.d.ts.map +1 -1
- package/types/components/camera/components/camera.d.ts +11 -3
- package/types/components/camera/components/camera.d.ts.map +1 -1
- package/types/components/camera/styles/index.d.ts +3 -2
- package/types/components/camera/styles/index.d.ts.map +1 -1
- package/types/components/capture/components/capture.d.ts +23 -3
- package/types/components/capture/components/capture.d.ts.map +1 -1
- package/types/components/droparea/components/droparea-preview.d.ts +64 -11
- package/types/components/droparea/components/droparea-preview.d.ts.map +1 -1
- package/types/components/droparea/components/droparea.d.ts +58 -13
- package/types/components/droparea/components/droparea.d.ts.map +1 -1
- package/types/components/droparea/styles/index.d.ts +3 -2
- package/types/components/droparea/styles/index.d.ts.map +1 -1
- package/types/components/emit/components/emit.d.ts +15 -3
- package/types/components/emit/components/emit.d.ts.map +1 -1
- package/types/components/list/components/item.d.ts +8 -1
- package/types/components/list/components/item.d.ts.map +1 -1
- package/types/components/list/components/list.d.ts +23 -5
- package/types/components/list/components/list.d.ts.map +1 -1
- package/types/components/paginator/components/paginator.d.ts +32 -8
- package/types/components/paginator/components/paginator.d.ts.map +1 -1
- package/types/components/paginator/styles/index.d.ts +3 -2
- package/types/components/paginator/styles/index.d.ts.map +1 -1
- package/types/components/spinner/components/spinner.d.ts +14 -3
- package/types/components/spinner/components/spinner.d.ts.map +1 -1
- package/types/components/spinner/styles/index.d.ts +3 -2
- package/types/components/spinner/styles/index.d.ts.map +1 -1
- package/types/components/splitview/components/splitview.d.ts +22 -4
- package/types/components/splitview/components/splitview.d.ts.map +1 -1
- package/types/components/splitview/components/splitview.detail.d.ts +12 -2
- package/types/components/splitview/components/splitview.detail.d.ts.map +1 -1
- package/types/components/splitview/components/splitview.master.d.ts +12 -1
- package/types/components/splitview/components/splitview.master.d.ts.map +1 -1
- package/types/components/translate/components/translate.d.ts +44 -10
- package/types/components/translate/components/translate.d.ts.map +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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,864 @@ class MockContentComponent extends Component {
|
|
|
42
43
|
}
|
|
43
44
|
Component.define('mock-content-component', MockContentComponent)
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
component = container.querySelector('mock-component')
|
|
52
|
-
document.body.append(container)
|
|
53
|
-
})
|
|
46
|
+
class AsyncLoadComponent extends Component {
|
|
47
|
+
async load () {
|
|
48
|
+
throw new Error('Async Load Error!')
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
Component.define('async-load-component', AsyncLoadComponent)
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
class SyncLoadComponent extends Component {
|
|
54
|
+
load () {
|
|
55
|
+
throw new Error('Sync Load Error!')
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
Component.define('sync-load-component', SyncLoadComponent)
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
class PlainLoadComponent extends Component {
|
|
61
|
+
load () {
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
Component.define('plain-load-component', PlainLoadComponent)
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
})
|
|
67
|
+
class StringLoadComponent extends Component {
|
|
68
|
+
load () {
|
|
69
|
+
throw 'String Load Error!'
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
Component.define('string-load-component', StringLoadComponent)
|
|
77
73
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
74
|
+
class CleanupComponent extends Component {
|
|
75
|
+
init (context = {}) {
|
|
76
|
+
this.cleanupCalls = 0
|
|
77
|
+
return super.init(context)
|
|
78
|
+
}
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
})
|
|
80
|
+
render () {
|
|
81
|
+
return super.render()
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
Component.define('cleanup-component', CleanupComponent)
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
})
|
|
86
|
+
class OptimizedListenerComponent extends Component {
|
|
87
|
+
init (context = {}) {
|
|
88
|
+
this.clicks = 0
|
|
89
|
+
return super.init(context)
|
|
90
|
+
}
|
|
96
91
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
render () {
|
|
93
|
+
if (!this.querySelector('button')) {
|
|
94
|
+
this.content = '<button listen on-click="onClick">Click me</button>'
|
|
95
|
+
}
|
|
96
|
+
return super.render()
|
|
97
|
+
}
|
|
101
98
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
expect(component.className).toEqual(
|
|
108
|
-
'mock-component custom-class special-class')
|
|
109
|
-
})
|
|
99
|
+
onClick () {
|
|
100
|
+
this.clicks += 1
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
Component.define('optimized-listener-component', OptimizedListenerComponent)
|
|
110
104
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const handler = (event) => { detail = event.detail }
|
|
114
|
-
component.addEventListener('fire', handler)
|
|
105
|
+
let container = null
|
|
106
|
+
let component = null
|
|
115
107
|
|
|
116
|
-
|
|
108
|
+
const setup = () => {
|
|
109
|
+
document.body.innerHTML = ''
|
|
110
|
+
container = document.createElement('div')
|
|
111
|
+
container.innerHTML = '<mock-component code="XYZ123"></mock-component>'
|
|
112
|
+
component = container.querySelector('mock-component')
|
|
113
|
+
document.body.append(container)
|
|
114
|
+
}
|
|
117
115
|
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
it('can be instantiated', () => {
|
|
117
|
+
setup()
|
|
118
|
+
assert.ok(component)
|
|
119
|
+
})
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
it('has an stable public api', () => {
|
|
122
|
+
setup()
|
|
123
|
+
assert.deepStrictEqual(typeof component.constructor.define, 'function')
|
|
124
|
+
assert.deepStrictEqual(typeof component.init, 'function')
|
|
125
|
+
assert.deepStrictEqual(typeof component.reflectedProperties, 'function')
|
|
126
|
+
assert.deepStrictEqual(typeof component.connectedCallback, 'function')
|
|
127
|
+
assert.deepStrictEqual(typeof component.render, 'function')
|
|
128
|
+
assert.deepStrictEqual(typeof component.load, 'function')
|
|
129
|
+
assert.deepStrictEqual(typeof component.select, 'function')
|
|
130
|
+
assert.deepStrictEqual(typeof component.selectAll, 'function')
|
|
131
|
+
assert.deepStrictEqual(typeof component.emit, 'function')
|
|
132
|
+
assert.notStrictEqual(typeof component.content, undefined)
|
|
133
|
+
})
|
|
127
134
|
|
|
128
|
-
|
|
135
|
+
it('has an init method through which state is set', () => {
|
|
136
|
+
setup()
|
|
137
|
+
const response = component.init({ attribute: 'value' })
|
|
138
|
+
assert.strictEqual(response, component)
|
|
139
|
+
})
|
|
129
140
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
141
|
+
it('can set its content via a property', () => {
|
|
142
|
+
setup()
|
|
143
|
+
component.content = '<p>Hello World</p>'
|
|
144
|
+
const paragraph = component.querySelector('p')
|
|
145
|
+
assert.strictEqual(component.content, '<p>Hello World</p>')
|
|
146
|
+
assert.strictEqual(paragraph.outerHTML, '<p>Hello World</p>')
|
|
147
|
+
})
|
|
134
148
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
149
|
+
it('can have some of its attributes reflected as properties', () => {
|
|
150
|
+
setup()
|
|
151
|
+
assert.strictEqual(component.code, 'XYZ123')
|
|
152
|
+
})
|
|
153
|
+
it('has an asynchronous load method which is empty by default', async () => {
|
|
154
|
+
setup()
|
|
155
|
+
assert.strictEqual(await component.load(), undefined)
|
|
156
|
+
})
|
|
144
157
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
158
|
+
it('sets its tag name as class when rendered', () => {
|
|
159
|
+
setup()
|
|
160
|
+
component.render()
|
|
161
|
+
assert.deepStrictEqual(component.className, 'mock-component')
|
|
162
|
+
})
|
|
150
163
|
|
|
151
|
-
|
|
152
|
-
|
|
164
|
+
it('keeps its previous classes after rendering', () => {
|
|
165
|
+
setup()
|
|
166
|
+
component.classList.add('custom-class')
|
|
167
|
+
component.classList.add('custom-class')
|
|
168
|
+
component.classList.add('special-class')
|
|
169
|
+
component.render()
|
|
170
|
+
assert.deepStrictEqual(component.className, 'mock-component custom-class special-class')
|
|
171
|
+
})
|
|
153
172
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
<div class="red"></div>
|
|
160
|
-
</mock-component>
|
|
161
|
-
`
|
|
162
|
-
const component = container.querySelector('mock-component')
|
|
173
|
+
it('emits custom events', () => {
|
|
174
|
+
setup()
|
|
175
|
+
let detail = null
|
|
176
|
+
const handler = (event) => { detail = event.detail }
|
|
177
|
+
component.addEventListener('fire', handler)
|
|
163
178
|
|
|
164
|
-
|
|
165
|
-
expect(blue.tagName).toEqual('DIV')
|
|
179
|
+
component.emit('fire', { location: 'indoors' })
|
|
166
180
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
})
|
|
181
|
+
assert.deepStrictEqual(detail, { location: 'indoors' })
|
|
182
|
+
})
|
|
170
183
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
it('calls the load method on connectedCallback', async () => {
|
|
185
|
+
setup()
|
|
186
|
+
const component = /** @type {Component} */ (
|
|
187
|
+
document.createElement('mock-component'))
|
|
188
|
+
const initSpy = mock.method(component, 'init')
|
|
189
|
+
const renderSpy = mock.method(component, 'render')
|
|
190
|
+
const loadSpy = mock.method(component, 'load')
|
|
191
|
+
|
|
192
|
+
document.body.append(component)
|
|
193
|
+
|
|
194
|
+
assert.strictEqual(initSpy.mock.calls.length, 1)
|
|
195
|
+
assert.strictEqual(renderSpy.mock.calls.length, 1)
|
|
196
|
+
assert.strictEqual(loadSpy.mock.calls.length, 1)
|
|
197
|
+
initSpy.mock.restore()
|
|
198
|
+
renderSpy.mock.restore()
|
|
199
|
+
loadSpy.mock.restore()
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('catches and re-raises connectedCallback errors', async () => {
|
|
203
|
+
setup()
|
|
204
|
+
const component = /** @type {Component} */ (
|
|
205
|
+
document.createElement('mock-component'))
|
|
206
|
+
const consoleErrorMock = mock.method(console, 'error', () => {})
|
|
207
|
+
let errorEvent = null
|
|
208
|
+
component.addEventListener('error', (event) => {
|
|
209
|
+
errorEvent = event
|
|
210
|
+
})
|
|
211
|
+
component.render = () => {
|
|
212
|
+
throw new Error('Render Error!')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
component.connectedCallback()
|
|
217
|
+
} catch (error) {
|
|
218
|
+
assert.deepStrictEqual(error.message, 'Render Error!')
|
|
219
|
+
assert.deepStrictEqual(errorEvent.detail.phase, 'init-render')
|
|
220
|
+
assert.deepStrictEqual(errorEvent.detail.component, 'MOCK-COMPONENT')
|
|
221
|
+
} finally {
|
|
222
|
+
consoleErrorMock.mock.restore()
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('creates events without CustomEvent and keeps event details', () => {
|
|
227
|
+
setup()
|
|
180
228
|
const component = container.querySelector('mock-component')
|
|
229
|
+
const previousCustomEvent = globalThis.CustomEvent
|
|
181
230
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
231
|
+
try {
|
|
232
|
+
globalThis.CustomEvent = undefined
|
|
233
|
+
let detail = null
|
|
234
|
+
component.addEventListener('fallback-emit', (event) => {
|
|
235
|
+
detail = event.detail
|
|
186
236
|
})
|
|
237
|
+
|
|
238
|
+
component.emit('fallback-emit', { ready: true })
|
|
239
|
+
|
|
240
|
+
assert.deepStrictEqual(detail, { ready: true })
|
|
241
|
+
} finally {
|
|
242
|
+
globalThis.CustomEvent = previousCustomEvent
|
|
243
|
+
}
|
|
187
244
|
})
|
|
188
245
|
|
|
189
|
-
it('
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
</mock-component>
|
|
194
|
-
`
|
|
246
|
+
it('creates events from the component global window Event fallback', () => {
|
|
247
|
+
setup()
|
|
248
|
+
const component = document.createElement('mock-component')
|
|
249
|
+
const previousGlobal = component.global
|
|
195
250
|
|
|
196
|
-
|
|
197
|
-
|
|
251
|
+
try {
|
|
252
|
+
component.global = {
|
|
253
|
+
document: {
|
|
254
|
+
defaultView: {
|
|
255
|
+
Event: globalThis.Event
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
198
259
|
|
|
199
|
-
|
|
200
|
-
new globalThis.InputEvent('input', { bubbles: true, data: 'E' })
|
|
201
|
-
)
|
|
260
|
+
const event = component._createEvent('context-window-event', { ready: true })
|
|
202
261
|
|
|
203
|
-
|
|
262
|
+
assert.deepStrictEqual(event.type, 'context-window-event')
|
|
263
|
+
assert.deepStrictEqual(event.detail.ready, true)
|
|
264
|
+
assert.deepStrictEqual(typeof event.stopPropagation, 'function')
|
|
265
|
+
} finally {
|
|
266
|
+
component.global = previousGlobal
|
|
267
|
+
}
|
|
204
268
|
})
|
|
205
269
|
|
|
206
|
-
it('
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
`
|
|
270
|
+
it('creates events from globalThis CustomEvent when component global has no window objects', () => {
|
|
271
|
+
setup()
|
|
272
|
+
const component = document.createElement('mock-component')
|
|
218
273
|
|
|
219
|
-
|
|
220
|
-
const
|
|
274
|
+
component.global = {}
|
|
275
|
+
const event = component._createEvent('global-custom-event', { ready: true })
|
|
221
276
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
)
|
|
277
|
+
assert.deepStrictEqual(event.type, 'global-custom-event')
|
|
278
|
+
assert.deepStrictEqual(event.detail.ready, true)
|
|
279
|
+
assert.deepStrictEqual(event instanceof globalThis.CustomEvent, true)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('emits an error event when async load fails', async () => {
|
|
283
|
+
setup()
|
|
284
|
+
const component = document.createElement('async-load-component')
|
|
285
|
+
let errorEvent = null
|
|
225
286
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
expect(component.data.value2).toEqual('E')
|
|
229
|
-
expect(component.data.value3).toEqual('E')
|
|
287
|
+
component.addEventListener('error', (event) => {
|
|
288
|
+
errorEvent = event
|
|
230
289
|
})
|
|
231
290
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const target = { name: 'input', value: 'X' }
|
|
242
|
-
Object.defineProperty(
|
|
243
|
-
inputEvent, 'target', { writable: false, value: target })
|
|
291
|
+
document.body.appendChild(component)
|
|
292
|
+
|
|
293
|
+
await new Promise(resolve => setTimeout(resolve, 0))
|
|
294
|
+
|
|
295
|
+
assert.ok(errorEvent)
|
|
296
|
+
assert.deepStrictEqual(errorEvent.detail.message, 'Async Load Error!')
|
|
297
|
+
assert.deepStrictEqual(errorEvent.detail.phase, 'load')
|
|
298
|
+
assert.deepStrictEqual(errorEvent.detail.component, 'ASYNC-LOAD-COMPONENT')
|
|
299
|
+
})
|
|
244
300
|
|
|
245
|
-
|
|
301
|
+
it('emits an error event and throws when sync load fails', () => {
|
|
302
|
+
setup()
|
|
303
|
+
const component = document.createElement('sync-load-component')
|
|
304
|
+
let errorEvent = null
|
|
246
305
|
|
|
247
|
-
|
|
306
|
+
component.addEventListener('error', (event) => {
|
|
307
|
+
errorEvent = event
|
|
248
308
|
})
|
|
249
309
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
<input type="text" listen on-alter="{{ data.value }}"></input>
|
|
254
|
-
</mock-component>
|
|
255
|
-
`
|
|
310
|
+
assert.throws(() => {
|
|
311
|
+
component.connectedCallback()
|
|
312
|
+
}, /Sync Load Error!/)
|
|
256
313
|
|
|
257
|
-
|
|
258
|
-
|
|
314
|
+
assert.ok(errorEvent)
|
|
315
|
+
assert.deepStrictEqual(errorEvent.detail.message, 'Sync Load Error!')
|
|
316
|
+
assert.deepStrictEqual(errorEvent.detail.phase, 'load')
|
|
317
|
+
assert.deepStrictEqual(errorEvent.detail.component, 'SYNC-LOAD-COMPONENT')
|
|
318
|
+
})
|
|
259
319
|
|
|
260
|
-
|
|
261
|
-
|
|
320
|
+
it('coerces non-error load failures into errors with metadata', () => {
|
|
321
|
+
setup()
|
|
322
|
+
const component = document.createElement('string-load-component')
|
|
323
|
+
let errorEvent = null
|
|
262
324
|
|
|
263
|
-
|
|
325
|
+
component.addEventListener('error', (event) => {
|
|
326
|
+
errorEvent = event
|
|
264
327
|
})
|
|
265
328
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
<input type="text" listen on-alter="{{ data.value | number }}"></input>
|
|
270
|
-
</mock-component>
|
|
271
|
-
`
|
|
329
|
+
assert.throws(() => {
|
|
330
|
+
component.connectedCallback()
|
|
331
|
+
}, /String Load Error!/)
|
|
272
332
|
|
|
273
|
-
|
|
274
|
-
|
|
333
|
+
assert.ok(errorEvent)
|
|
334
|
+
assert.deepStrictEqual(errorEvent.detail.message, 'String Load Error!')
|
|
335
|
+
assert.deepStrictEqual(errorEvent.detail.phase, 'load')
|
|
336
|
+
assert.deepStrictEqual(errorEvent.detail.component, 'STRING-LOAD-COMPONENT')
|
|
337
|
+
})
|
|
275
338
|
|
|
276
|
-
|
|
277
|
-
|
|
339
|
+
it('creates resolve events without CustomEvent and still resolves', () => {
|
|
340
|
+
setup()
|
|
341
|
+
const component = document.createElement('mock-component')
|
|
342
|
+
const previousCustomEvent = globalThis.CustomEvent
|
|
278
343
|
|
|
279
|
-
|
|
280
|
-
|
|
344
|
+
try {
|
|
345
|
+
globalThis.CustomEvent = undefined
|
|
346
|
+
let resource = null
|
|
347
|
+
component.addEventListener('resolve', (event) => {
|
|
348
|
+
resource = event.detail.resource
|
|
349
|
+
})
|
|
281
350
|
|
|
282
|
-
|
|
283
|
-
container.innerHTML = `
|
|
284
|
-
<mock-component>
|
|
285
|
-
<input type="text" listen on-alter="{{ local | object }}"></input>
|
|
286
|
-
</mock-component>
|
|
287
|
-
`
|
|
351
|
+
const result = component.resolve('dependency')
|
|
288
352
|
|
|
289
|
-
|
|
290
|
-
|
|
353
|
+
assert.deepStrictEqual(resource, 'dependency')
|
|
354
|
+
assert.strictEqual(result, undefined)
|
|
355
|
+
} finally {
|
|
356
|
+
globalThis.CustomEvent = previousCustomEvent
|
|
357
|
+
}
|
|
358
|
+
})
|
|
291
359
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
360
|
+
it('creates fallback events when neither CustomEvent nor Event are available', () => {
|
|
361
|
+
setup()
|
|
362
|
+
const component = document.createElement('mock-component')
|
|
363
|
+
const previousCustomEvent = globalThis.CustomEvent
|
|
364
|
+
const previousEvent = globalThis.Event
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
globalThis.CustomEvent = undefined
|
|
368
|
+
globalThis.Event = undefined
|
|
369
|
+
|
|
370
|
+
component.global = {}
|
|
371
|
+
const event = component._createEvent('fallback', { ready: true })
|
|
372
|
+
|
|
373
|
+
assert.deepStrictEqual(event.type, 'fallback')
|
|
374
|
+
assert.deepStrictEqual(event.detail, { ready: true })
|
|
375
|
+
assert.deepStrictEqual(typeof event.stopPropagation, 'function')
|
|
376
|
+
assert.deepStrictEqual(typeof event.preventDefault, 'function')
|
|
377
|
+
assert.doesNotThrow(() => event.stopPropagation())
|
|
378
|
+
assert.doesNotThrow(() => event.preventDefault())
|
|
379
|
+
} finally {
|
|
380
|
+
globalThis.CustomEvent = previousCustomEvent
|
|
381
|
+
globalThis.Event = previousEvent
|
|
382
|
+
}
|
|
383
|
+
})
|
|
295
384
|
|
|
296
|
-
|
|
297
|
-
|
|
385
|
+
it('falls back to globalThis when component global is missing', () => {
|
|
386
|
+
setup()
|
|
387
|
+
const component = document.createElement('mock-component')
|
|
388
|
+
const previousCustomEvent = globalThis.CustomEvent
|
|
298
389
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
<input type="text" listen on-alter="{{ local.zone | object }}"></input>
|
|
303
|
-
</mock-component>
|
|
304
|
-
`
|
|
390
|
+
try {
|
|
391
|
+
globalThis.CustomEvent = undefined
|
|
392
|
+
component.global = null
|
|
305
393
|
|
|
306
|
-
const
|
|
307
|
-
component.local.zone = {}
|
|
308
|
-
const input = component.select('input')
|
|
394
|
+
const event = component._createEvent('global-fallback', { ready: true })
|
|
309
395
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
396
|
+
assert.deepStrictEqual(event.type, 'global-fallback')
|
|
397
|
+
assert.deepStrictEqual(event.detail.ready, true)
|
|
398
|
+
} finally {
|
|
399
|
+
globalThis.CustomEvent = previousCustomEvent
|
|
400
|
+
}
|
|
401
|
+
})
|
|
313
402
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
403
|
+
it('registers and runs cleanup callbacks on disconnect', () => {
|
|
404
|
+
setup()
|
|
405
|
+
const component = document.createElement('cleanup-component')
|
|
406
|
+
container.appendChild(component)
|
|
407
|
+
|
|
408
|
+
let cleanupCalls = 0
|
|
409
|
+
component.registerCleanup(() => {
|
|
410
|
+
cleanupCalls += 1
|
|
411
|
+
})
|
|
412
|
+
component.registerCleanup(() => {
|
|
413
|
+
cleanupCalls += 1
|
|
320
414
|
})
|
|
321
415
|
|
|
322
|
-
|
|
323
|
-
container.innerHTML = `
|
|
324
|
-
<mock-component code="ABC123">
|
|
325
|
-
<input type="text" listen on-alter="customHandler"></input>
|
|
326
|
-
</mock-component>
|
|
327
|
-
`
|
|
416
|
+
component.disconnectedCallback()
|
|
328
417
|
|
|
329
|
-
|
|
330
|
-
|
|
418
|
+
assert.deepStrictEqual(cleanupCalls, 2)
|
|
419
|
+
})
|
|
331
420
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
421
|
+
it('supports unregistering cleanup callbacks', () => {
|
|
422
|
+
setup()
|
|
423
|
+
const component = document.createElement('cleanup-component')
|
|
424
|
+
container.appendChild(component)
|
|
335
425
|
|
|
336
|
-
|
|
426
|
+
let cleanupCalls = 0
|
|
427
|
+
const unregister = component.registerCleanup(() => {
|
|
428
|
+
cleanupCalls += 1
|
|
337
429
|
})
|
|
430
|
+
unregister()
|
|
431
|
+
unregister()
|
|
338
432
|
|
|
339
|
-
|
|
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
|
-
`
|
|
433
|
+
component.disconnectedCallback()
|
|
346
434
|
|
|
347
|
-
|
|
348
|
-
|
|
435
|
+
assert.deepStrictEqual(cleanupCalls, 0)
|
|
436
|
+
})
|
|
349
437
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
438
|
+
it('supports disconnected init fast-path when local state is already set', () => {
|
|
439
|
+
setup()
|
|
440
|
+
const component = document.createElement('mock-component')
|
|
441
|
+
component.local = { preset: true }
|
|
442
|
+
const initSpy = mock.method(component, 'init')
|
|
443
|
+
container.appendChild(component)
|
|
353
444
|
|
|
354
|
-
|
|
355
|
-
|
|
445
|
+
assert.deepStrictEqual(initSpy.mock.calls.length, 0)
|
|
446
|
+
initSpy.mock.restore()
|
|
447
|
+
})
|
|
356
448
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
<input type="text" listen on-alter="customHandler@#missing"></input>
|
|
361
|
-
<mock-component id="child" code="XYZ890"></mock-component>
|
|
362
|
-
</mock-component>
|
|
363
|
-
`
|
|
449
|
+
it('returns unchanged values when enhanced error details are absent', () => {
|
|
450
|
+
setup()
|
|
451
|
+
const component = document.createElement('cleanup-component')
|
|
364
452
|
|
|
365
|
-
|
|
366
|
-
|
|
453
|
+
const result = component._enhanceError(undefined, 'init-render')
|
|
454
|
+
|
|
455
|
+
assert.strictEqual(result, undefined)
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
it('accepts non-function cleanup callbacks as no-op registrations', () => {
|
|
459
|
+
setup()
|
|
460
|
+
const component = document.createElement('cleanup-component')
|
|
367
461
|
|
|
368
|
-
|
|
369
|
-
'alter', { bubbles: true, detail: { code: [] } })
|
|
370
|
-
input.dispatchEvent(event)
|
|
462
|
+
const unregister = component.registerCleanup()
|
|
371
463
|
|
|
372
|
-
|
|
464
|
+
assert.doesNotThrow(() => unregister())
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
it('emits an error event when a cleanup callback throws', () => {
|
|
468
|
+
setup()
|
|
469
|
+
const component = document.createElement('cleanup-component')
|
|
470
|
+
container.appendChild(component)
|
|
471
|
+
let errorEvent = null
|
|
472
|
+
|
|
473
|
+
component.addEventListener('error', (event) => {
|
|
474
|
+
errorEvent = event
|
|
475
|
+
})
|
|
476
|
+
component.registerCleanup(() => {
|
|
477
|
+
throw new Error('Cleanup failed')
|
|
373
478
|
})
|
|
374
479
|
|
|
480
|
+
component.disconnectedCallback()
|
|
375
481
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
</div>
|
|
382
|
-
<mock-component id="child" code="XYZ890"></mock-component>
|
|
383
|
-
</mock-component>
|
|
384
|
-
`
|
|
482
|
+
assert.ok(errorEvent)
|
|
483
|
+
assert.deepStrictEqual(errorEvent.detail.message, 'Cleanup failed')
|
|
484
|
+
assert.deepStrictEqual(errorEvent.detail.phase, 'cleanup')
|
|
485
|
+
assert.deepStrictEqual(errorEvent.detail.component, 'CLEANUP-COMPONENT')
|
|
486
|
+
})
|
|
385
487
|
|
|
386
|
-
|
|
387
|
-
|
|
488
|
+
it('supports load implementations that do not return promises', () => {
|
|
489
|
+
setup()
|
|
490
|
+
const component = document.createElement('plain-load-component')
|
|
491
|
+
|
|
492
|
+
assert.doesNotThrow(() => {
|
|
493
|
+
component.connectedCallback()
|
|
494
|
+
})
|
|
495
|
+
})
|
|
388
496
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
497
|
+
it('selects the children matching a selector', () => {
|
|
498
|
+
setup();
|
|
499
|
+
container.innerHTML = `
|
|
500
|
+
<mock-component>
|
|
501
|
+
<div class="blue"></div>
|
|
502
|
+
<div class="red"></div>
|
|
503
|
+
<div class="red"></div>
|
|
504
|
+
</mock-component>
|
|
505
|
+
`
|
|
506
|
+
const component = container.querySelector('mock-component')
|
|
507
|
+
|
|
508
|
+
const blue = component.select('.blue')
|
|
509
|
+
assert.deepStrictEqual(blue.tagName, 'DIV')
|
|
510
|
+
|
|
511
|
+
const red = component.selectAll('.red')
|
|
512
|
+
assert.deepStrictEqual(red.length, 2)
|
|
513
|
+
})
|
|
392
514
|
|
|
393
|
-
|
|
515
|
+
it('retrieves its slots through the slots method', () => {
|
|
516
|
+
setup();
|
|
517
|
+
container.innerHTML = `
|
|
518
|
+
<mock-component>
|
|
519
|
+
<div slot="header" class="header"></div>
|
|
520
|
+
<div class="body"></div>
|
|
521
|
+
<div class="aside"></div>
|
|
522
|
+
<div slot="footer" class="footer"></div>
|
|
523
|
+
</mock-component>
|
|
524
|
+
`
|
|
525
|
+
const component = container.querySelector('mock-component')
|
|
526
|
+
|
|
527
|
+
assert.deepStrictEqual(component.slots, {
|
|
528
|
+
header: [component.select('.header')],
|
|
529
|
+
general: [component.select('.body'), component.select('.aside')],
|
|
530
|
+
footer: [component.select('.footer')]
|
|
394
531
|
})
|
|
532
|
+
})
|
|
395
533
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
`
|
|
407
|
-
const component = container.querySelector('mock-component')
|
|
408
|
-
const button = component.select('#action')
|
|
534
|
+
it('binds its properties to children events', async () => {
|
|
535
|
+
setup();
|
|
536
|
+
container.innerHTML = `
|
|
537
|
+
<mock-component>
|
|
538
|
+
<input type="text" listen on-input="{{ data.value = data }}"></input>
|
|
539
|
+
</mock-component>
|
|
540
|
+
`
|
|
541
|
+
|
|
542
|
+
const component = container.querySelector('mock-component')
|
|
543
|
+
const input = component.select('input')
|
|
409
544
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
545
|
+
input.dispatchEvent(
|
|
546
|
+
new globalThis.InputEvent('input', { bubbles: true, data: 'E' })
|
|
547
|
+
)
|
|
413
548
|
|
|
414
|
-
|
|
415
|
-
|
|
549
|
+
assert.deepStrictEqual(component.data.value, 'E')
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
it('binds multiple handlers to the same event', async () => {
|
|
553
|
+
setup();
|
|
554
|
+
container.innerHTML = `
|
|
555
|
+
<mock-component>
|
|
556
|
+
<input type="text"
|
|
557
|
+
listen
|
|
558
|
+
on-input="{{ data.value = data }}"
|
|
559
|
+
on-input-1="{{ data.value1 = data }}"
|
|
560
|
+
on-input-2="{{ data.value2 = data }}"
|
|
561
|
+
on-input-3="{{ data.value3 = data }}"
|
|
562
|
+
></input>
|
|
563
|
+
</mock-component>
|
|
564
|
+
`
|
|
565
|
+
|
|
566
|
+
const component = container.querySelector('mock-component')
|
|
567
|
+
const input = component.select('input')
|
|
568
|
+
|
|
569
|
+
input.dispatchEvent(
|
|
570
|
+
new globalThis.InputEvent('input', { bubbles: true, data: 'E' })
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
assert.deepStrictEqual(component.data.value, 'E')
|
|
574
|
+
assert.deepStrictEqual(component.data.value1, 'E')
|
|
575
|
+
assert.deepStrictEqual(component.data.value2, 'E')
|
|
576
|
+
assert.deepStrictEqual(component.data.value3, 'E')
|
|
577
|
+
})
|
|
578
|
+
|
|
579
|
+
it('binds to the target.value event property by default', async () => {
|
|
580
|
+
setup()
|
|
581
|
+
container.innerHTML = `
|
|
582
|
+
<mock-component>
|
|
583
|
+
<input type="text" listen on-input="{{ data.value }}"></input>
|
|
584
|
+
</mock-component>
|
|
585
|
+
`
|
|
586
|
+
const component = container.querySelector('mock-component')
|
|
587
|
+
const input = component.select('input')
|
|
588
|
+
input.value = 'X'
|
|
589
|
+
const inputEvent = new globalThis.InputEvent('input', { bubbles: true })
|
|
590
|
+
|
|
591
|
+
input.dispatchEvent(inputEvent)
|
|
592
|
+
|
|
593
|
+
assert.deepStrictEqual(component.data.value, 'X')
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
it('binds to the detail custom event property', async () => {
|
|
597
|
+
setup();
|
|
598
|
+
container.innerHTML = `
|
|
599
|
+
<mock-component>
|
|
600
|
+
<input type="text" listen on-alter="{{ data.value }}"></input>
|
|
601
|
+
</mock-component>
|
|
602
|
+
`
|
|
603
|
+
|
|
604
|
+
const component = container.querySelector('mock-component')
|
|
605
|
+
const input = component.select('input')
|
|
606
|
+
|
|
607
|
+
input.dispatchEvent(
|
|
608
|
+
new globalThis.CustomEvent('alter', { bubbles: true, detail: 'A' }))
|
|
609
|
+
|
|
610
|
+
assert.deepStrictEqual(component.data.value, 'A')
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
it('performs basic transformations to event properties', async () => {
|
|
614
|
+
setup();
|
|
615
|
+
container.innerHTML = `
|
|
616
|
+
<mock-component>
|
|
617
|
+
<input type="text" listen on-alter="{{ data.value | number }}"></input>
|
|
618
|
+
</mock-component>
|
|
619
|
+
`
|
|
620
|
+
|
|
621
|
+
const component = container.querySelector('mock-component')
|
|
622
|
+
const input = component.select('input')
|
|
623
|
+
|
|
624
|
+
input.dispatchEvent(
|
|
625
|
+
new globalThis.CustomEvent('alter', { bubbles: true, detail: '777' }))
|
|
626
|
+
|
|
627
|
+
assert.deepStrictEqual(component.data.value, 777)
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
it('performs object assignment of event details', async () => {
|
|
631
|
+
setup();
|
|
632
|
+
container.innerHTML = `
|
|
633
|
+
<mock-component>
|
|
634
|
+
<input type="text" listen on-alter="{{ local | object }}"></input>
|
|
635
|
+
</mock-component>
|
|
636
|
+
`
|
|
637
|
+
|
|
638
|
+
const component = container.querySelector('mock-component')
|
|
639
|
+
const input = component.select('input')
|
|
640
|
+
|
|
641
|
+
const detail = { name: 'Sprite', brand: 'Coca-Cola' }
|
|
642
|
+
input.dispatchEvent(
|
|
643
|
+
new globalThis.CustomEvent('alter', { bubbles: true, detail }))
|
|
644
|
+
|
|
645
|
+
assert.deepStrictEqual(component.local, { name: 'Sprite', brand: 'Coca-Cola' })
|
|
646
|
+
})
|
|
647
|
+
|
|
648
|
+
it('performs nested object assignment of event details', async () => {
|
|
649
|
+
setup();
|
|
650
|
+
container.innerHTML = `
|
|
651
|
+
<mock-component>
|
|
652
|
+
<input type="text" listen on-alter="{{ local.zone | object }}"></input>
|
|
653
|
+
</mock-component>
|
|
654
|
+
`
|
|
655
|
+
|
|
656
|
+
const component = container.querySelector('mock-component')
|
|
657
|
+
component.local.zone = {}
|
|
658
|
+
const input = component.select('input')
|
|
659
|
+
|
|
660
|
+
const detail = { country: 'USA', city: 'Atlanta' }
|
|
661
|
+
input.dispatchEvent(
|
|
662
|
+
new globalThis.CustomEvent('alter', { bubbles: true, detail }))
|
|
663
|
+
|
|
664
|
+
assert.deepStrictEqual(component.local, {
|
|
665
|
+
zone: {
|
|
666
|
+
country: 'USA',
|
|
667
|
+
city: 'Atlanta'
|
|
668
|
+
}
|
|
416
669
|
})
|
|
670
|
+
})
|
|
417
671
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
672
|
+
it('listens to events and handles them with its own methods', async () => {
|
|
673
|
+
setup();
|
|
674
|
+
container.innerHTML = `
|
|
675
|
+
<mock-component code="ABC123">
|
|
676
|
+
<input type="text" listen on-alter="customHandler"></input>
|
|
422
677
|
</mock-component>
|
|
423
|
-
|
|
678
|
+
`
|
|
424
679
|
|
|
425
|
-
|
|
680
|
+
const component = container.querySelector('mock-component')
|
|
681
|
+
const input = component.select('input')
|
|
682
|
+
|
|
683
|
+
const event = new globalThis.CustomEvent(
|
|
684
|
+
'alter', { bubbles: true, detail: { code: [] } })
|
|
685
|
+
input.dispatchEvent(event)
|
|
426
686
|
|
|
427
|
-
|
|
428
|
-
|
|
687
|
+
assert.deepStrictEqual(event.detail.code, ['ABC123'])
|
|
688
|
+
})
|
|
429
689
|
|
|
430
|
-
|
|
690
|
+
it('listens to events and redirects them to target components', async () => {
|
|
691
|
+
setup();
|
|
692
|
+
container.innerHTML = `
|
|
693
|
+
<mock-component code="ABC123">
|
|
694
|
+
<input type="text" listen on-alter="customHandler@#child"></input>
|
|
695
|
+
<mock-component id="child" code="XYZ890"></mock-component>
|
|
696
|
+
</mock-component>
|
|
697
|
+
`
|
|
431
698
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
'alter', { bubbles: true, detail: 'I will error!' }
|
|
435
|
-
))
|
|
699
|
+
const component = container.querySelector('mock-component')
|
|
700
|
+
const input = component.select('input')
|
|
436
701
|
|
|
437
|
-
|
|
438
|
-
|
|
702
|
+
const event = new globalThis.CustomEvent(
|
|
703
|
+
'alter', { bubbles: true, detail: { code: [] } })
|
|
704
|
+
input.dispatchEvent(event)
|
|
439
705
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
706
|
+
assert.deepStrictEqual(event.detail.code, ['XYZ890'])
|
|
707
|
+
})
|
|
708
|
+
|
|
709
|
+
it('does not launch and event if the target is not found', async () => {
|
|
710
|
+
setup();
|
|
711
|
+
container.innerHTML = `
|
|
712
|
+
<mock-component code="ABC123">
|
|
713
|
+
<input type="text" listen on-alter="customHandler@#missing"></input>
|
|
714
|
+
<mock-component id="child" code="XYZ890"></mock-component>
|
|
444
715
|
</mock-component>
|
|
445
|
-
|
|
716
|
+
`
|
|
446
717
|
|
|
447
|
-
|
|
718
|
+
const component = container.querySelector('mock-component')
|
|
719
|
+
const input = component.select('input')
|
|
448
720
|
|
|
449
|
-
|
|
450
|
-
|
|
721
|
+
const event = new globalThis.CustomEvent(
|
|
722
|
+
'alter', { bubbles: true, detail: { code: [] } })
|
|
723
|
+
input.dispatchEvent(event)
|
|
451
724
|
|
|
452
|
-
|
|
725
|
+
assert.deepStrictEqual(event.detail.code, [])
|
|
726
|
+
})
|
|
453
727
|
|
|
454
|
-
input.dispatchEvent(
|
|
455
|
-
new globalThis.CustomEvent(
|
|
456
|
-
'alter', { bubbles: true, detail: 'I will error!' }
|
|
457
|
-
))
|
|
458
728
|
|
|
459
|
-
|
|
460
|
-
|
|
729
|
+
it('listens to clicks and redirects them to target components', async () => {
|
|
730
|
+
setup();
|
|
731
|
+
container.innerHTML = `
|
|
732
|
+
<mock-component code="ABC123">
|
|
733
|
+
<div type="text" listen on-click="customHandler@#child">
|
|
734
|
+
<button id="action">Action</button>
|
|
735
|
+
</div>
|
|
736
|
+
<mock-component id="child" code="XYZ890"></mock-component>
|
|
737
|
+
</mock-component>
|
|
738
|
+
`
|
|
461
739
|
|
|
462
|
-
|
|
463
|
-
|
|
740
|
+
const component = container.querySelector('mock-component')
|
|
741
|
+
const button = component.select('#action')
|
|
464
742
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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')
|
|
743
|
+
const event = new globalThis.CustomEvent(
|
|
744
|
+
'click', { bubbles: true, detail: { code: [] } })
|
|
745
|
+
button.dispatchEvent(event)
|
|
475
746
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
})
|
|
747
|
+
assert.deepStrictEqual(event.detail.code, ['XYZ890'])
|
|
748
|
+
})
|
|
479
749
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
750
|
+
it('listens to events and pipes them to target components', async () => {
|
|
751
|
+
setup();
|
|
752
|
+
container.innerHTML = `
|
|
753
|
+
<mock-component code="ABC123">
|
|
754
|
+
<div type="text" listen on-change="replaceChildren%detail.name@#child">
|
|
755
|
+
<button id="action">Action</button>
|
|
756
|
+
</div>
|
|
757
|
+
<div id="child">
|
|
758
|
+
Hello
|
|
759
|
+
</div>
|
|
760
|
+
</mock-component>
|
|
761
|
+
`
|
|
762
|
+
const component = container.querySelector('mock-component')
|
|
763
|
+
const button = component.select('#action')
|
|
764
|
+
|
|
765
|
+
const event = new globalThis.CustomEvent(
|
|
766
|
+
'change', { bubbles: true, detail: { name: 'World' } })
|
|
767
|
+
button.dispatchEvent(event)
|
|
768
|
+
|
|
769
|
+
const child = container.querySelector('#child')
|
|
770
|
+
assert.deepStrictEqual(child.textContent.trim(), 'World')
|
|
771
|
+
})
|
|
772
|
+
|
|
773
|
+
it('emits an error event on declared listeners', async () => {
|
|
774
|
+
setup();
|
|
775
|
+
container.innerHTML = `
|
|
776
|
+
<mock-component>
|
|
777
|
+
<input type="text" listen on-alter="erroringHandler"></input>
|
|
778
|
+
</mock-component>
|
|
779
|
+
`
|
|
780
|
+
|
|
781
|
+
const component = container.querySelector('mock-component')
|
|
782
|
+
|
|
783
|
+
let errorEvent = {}
|
|
784
|
+
component.addEventListener('error', (event) => { errorEvent = event })
|
|
785
|
+
|
|
786
|
+
const input = component.select('input')
|
|
787
|
+
|
|
788
|
+
input.dispatchEvent(
|
|
789
|
+
new globalThis.CustomEvent(
|
|
790
|
+
'alter', { bubbles: true, detail: 'I will error!' }
|
|
791
|
+
))
|
|
792
|
+
|
|
793
|
+
assert.deepStrictEqual(errorEvent.detail.message, 'Something went wrong!')
|
|
794
|
+
})
|
|
795
|
+
|
|
796
|
+
it('emits an error event on declared async listeners', async () => {
|
|
797
|
+
setup();
|
|
798
|
+
container.innerHTML = `
|
|
799
|
+
<mock-component>
|
|
800
|
+
<input type="text" listen on-alter="asyncErroringHandler"></input>
|
|
801
|
+
</mock-component>
|
|
802
|
+
`
|
|
803
|
+
|
|
804
|
+
const component = container.querySelector('mock-component')
|
|
805
|
+
|
|
806
|
+
let errorEvent = {}
|
|
807
|
+
component.addEventListener('error', (event) => { errorEvent = event })
|
|
808
|
+
|
|
809
|
+
const input = component.select('input')
|
|
810
|
+
|
|
811
|
+
input.dispatchEvent(
|
|
812
|
+
new globalThis.CustomEvent(
|
|
813
|
+
'alter', { bubbles: true, detail: 'I will error!' }
|
|
814
|
+
))
|
|
815
|
+
|
|
816
|
+
// Sleep
|
|
817
|
+
await new Promise(resolve => setTimeout(resolve, 0))
|
|
818
|
+
|
|
819
|
+
assert.deepStrictEqual(errorEvent.detail.message, 'Something went async wrong!')
|
|
820
|
+
})
|
|
821
|
+
|
|
822
|
+
it('resolves its resource dependencies using events propagation', () => {
|
|
823
|
+
setup();
|
|
824
|
+
const listener = (event) => {
|
|
825
|
+
const resource = event.detail.resource
|
|
826
|
+
event.detail[resource] = 'RESOLVED_DEPENDENCY'
|
|
827
|
+
}
|
|
828
|
+
document.addEventListener('resolve', listener)
|
|
829
|
+
container.innerHTML = `
|
|
830
|
+
<mock-component></mock-component>
|
|
831
|
+
`
|
|
832
|
+
const component = container.querySelector('mock-component')
|
|
833
|
+
|
|
834
|
+
assert.deepStrictEqual(component.dependency, 'RESOLVED_DEPENDENCY')
|
|
835
|
+
document.removeEventListener('resolve', listener)
|
|
836
|
+
})
|
|
837
|
+
|
|
838
|
+
it('provides the dependencies requested to it by child components', () => {
|
|
839
|
+
setup();
|
|
840
|
+
class ParentComponent extends MockComponent {
|
|
841
|
+
provide (resource) {
|
|
842
|
+
if (resource === 'Dependency') {
|
|
843
|
+
return `RESOURCE: ${resource} PROVIDED BY: ${this.id}`
|
|
487
844
|
}
|
|
845
|
+
if (resource === 'state') return { key: 'value' }
|
|
488
846
|
}
|
|
489
|
-
|
|
847
|
+
}
|
|
848
|
+
Component.define('parent-component', ParentComponent)
|
|
490
849
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
850
|
+
container.innerHTML = `
|
|
851
|
+
<parent-component id="parent">
|
|
852
|
+
<mock-component id="child"></mock-component>
|
|
853
|
+
</parent-component>
|
|
854
|
+
`
|
|
496
855
|
|
|
497
|
-
|
|
856
|
+
const child = container.querySelector('#child')
|
|
498
857
|
|
|
499
|
-
|
|
500
|
-
'RESOURCE: Dependency PROVIDED BY: parent')
|
|
858
|
+
assert.deepStrictEqual(child.dependency, 'RESOURCE: Dependency PROVIDED BY: parent')
|
|
501
859
|
|
|
502
|
-
|
|
503
|
-
|
|
860
|
+
const state = child.resolve('state')
|
|
861
|
+
assert.deepStrictEqual(state, { key: 'value' })
|
|
504
862
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
863
|
+
const unknown = child.resolve('unknown')
|
|
864
|
+
assert.strictEqual(unknown, undefined)
|
|
865
|
+
})
|
|
508
866
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
867
|
+
it('provides a styleNames utility function for setting styles', () => {
|
|
868
|
+
setup();
|
|
869
|
+
container.innerHTML = `
|
|
870
|
+
<mock-component class></mock-component>
|
|
871
|
+
`
|
|
872
|
+
const component = container.querySelector('mock-component')
|
|
873
|
+
const background = 'primary'
|
|
874
|
+
const shadow = 'small'
|
|
875
|
+
const color = ''
|
|
876
|
+
const styleMap = {
|
|
877
|
+
[`background-${background}`]: background,
|
|
878
|
+
[`color-${color}`]: color,
|
|
879
|
+
[`shadow-${shadow}`]: shadow
|
|
880
|
+
}
|
|
522
881
|
|
|
523
|
-
|
|
882
|
+
const result = component.styleNames(styleMap)
|
|
524
883
|
|
|
525
|
-
|
|
526
|
-
|
|
884
|
+
assert.deepStrictEqual(result, 'background-primary shadow-small')
|
|
885
|
+
})
|
|
886
|
+
|
|
887
|
+
it('reuses listener bindings when content does not change', () => {
|
|
888
|
+
setup()
|
|
889
|
+
container.innerHTML = `
|
|
890
|
+
<optimized-listener-component></optimized-listener-component>
|
|
891
|
+
`
|
|
892
|
+
const component = container.querySelector('optimized-listener-component')
|
|
893
|
+
|
|
894
|
+
component.render()
|
|
895
|
+
component.querySelector('button').click()
|
|
896
|
+
|
|
897
|
+
component.render()
|
|
898
|
+
component.querySelector('button').click()
|
|
899
|
+
|
|
900
|
+
component.content = '<button listen on-click="onClick">Click me</button>'
|
|
901
|
+
component.render()
|
|
902
|
+
component.querySelector('button').click()
|
|
903
|
+
|
|
904
|
+
assert.deepStrictEqual(component.clicks, 3)
|
|
905
|
+
assert.deepStrictEqual(component._needsBinding, false)
|
|
527
906
|
})
|