@knowark/componarkjs 1.13.4 → 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 (35) 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 +19 -1
  11. package/lib/components/audio/components/audio.test.js +120 -90
  12. package/lib/components/camera/components/camera.js +5 -0
  13. package/lib/components/camera/components/camera.test.js +96 -91
  14. package/lib/components/capture/components/capture.js +30 -2
  15. package/lib/components/capture/components/capture.test.js +165 -97
  16. package/lib/components/droparea/components/droparea-preview.js +58 -8
  17. package/lib/components/droparea/components/droparea-preview.test.js +262 -80
  18. package/lib/components/droparea/components/droparea.js +41 -4
  19. package/lib/components/droparea/components/droparea.test.js +309 -299
  20. package/lib/components/emit/components/emit.js +23 -3
  21. package/lib/components/emit/components/emit.test.js +192 -134
  22. package/lib/components/list/components/item.test.js +69 -68
  23. package/lib/components/list/components/list.js +33 -3
  24. package/lib/components/list/components/list.test.js +358 -227
  25. package/lib/components/paginator/components/paginator.test.js +146 -143
  26. package/lib/components/spinner/components/spinner.test.js +36 -41
  27. package/lib/components/splitview/components/splitview.detail.test.js +75 -73
  28. package/lib/components/splitview/components/splitview.js +36 -8
  29. package/lib/components/splitview/components/splitview.master.js +27 -2
  30. package/lib/components/splitview/components/splitview.master.test.js +52 -52
  31. package/lib/components/splitview/components/splitview.test.js +135 -31
  32. package/lib/components/translate/components/translate.js +28 -8
  33. package/lib/components/translate/components/translate.test.js +492 -133
  34. package/package.json +3 -27
  35. package/scripts/node-test-setup.js +94 -0
@@ -1,107 +1,289 @@
1
- import { jest } from '@jest/globals'
1
+ import { it, mock } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
  import './droparea-preview.js'
3
4
 
4
- describe('Droparea', () => {
5
- const createBubbledEvent = (type, props = {}) => {
6
- const event = new Event(type, {
7
- bubbles: true
8
- })
9
- Object.assign(event, props)
10
- return event
11
- }
5
+ const createBubbledEvent = (type, props = {}) => {
6
+ const event = new Event(type, {
7
+ bubbles: true
8
+ })
9
+ Object.assign(event, props)
10
+ return event
11
+ }
12
12
 
13
- global.URL.createObjectURL = (
14
- /** @type {(obj: Blob | MediaSource) => string} */ (jest.fn()))
15
- global.document.elementFromPoint = (
16
- /** @type {(x: number, y: number) => Element} */ (jest.fn()))
13
+ let objectURLCount = 0
14
+ global.URL.createObjectURL = (
15
+ /** @type {(obj: Blob | MediaSource) => string} */ (
16
+ mock.fn(() => `mock://data/url/${objectURLCount++}`)))
17
+ global.URL.revokeObjectURL = (
18
+ /** @type {(url: string) => void} */ (mock.fn()))
19
+ global.document.elementFromPoint = (
20
+ /** @type {(x: number, y: number) => Element} */ (mock.fn()))
17
21
 
18
- let container = null
22
+ let container = null
19
23
 
20
- beforeEach(() => {
21
- container = document.createElement('div')
22
- document.body.appendChild(container)
23
- })
24
+ const setup = () => {
25
+ document.body.innerHTML = ''
26
+ objectURLCount = 0
27
+ global.URL.createObjectURL.mock.resetCalls()
28
+ global.URL.revokeObjectURL.mock.resetCalls()
29
+ container = document.createElement('div')
30
+ document.body.appendChild(container)
31
+ }
24
32
 
25
- afterEach(() => {
26
- container.remove()
27
- container = null
33
+ it('can be instantiated', () => {
34
+ setup()
35
+ container.innerHTML = /* html */ `
36
+ <ark-droparea></ark-droparea>
37
+ `
38
+ const droparea = container.querySelector('ark-droparea')
39
+ const preview = droparea.querySelector('ark-droparea-preview')
40
+ assert.strictEqual(preview, preview.init())
41
+ })
42
+
43
+ it('Item can be removed', () => {
44
+ setup()
45
+ container.innerHTML = /* html */ `
46
+ <ark-droparea></ark-droparea>
47
+ `
48
+
49
+ const droparea = container.querySelector('ark-droparea')
50
+ const preview = droparea.querySelector('[data-preview-list]')
51
+ const dropZone = droparea.querySelector('.ark-droparea__form')
52
+ const myFile = new File(['image'], 'Doggy.png', {
53
+ type: 'image/png'
54
+ })
55
+ const myFile2 = new File(['image'], 'Scooby.png', {
56
+ type: 'image/png'
57
+ })
58
+ const dropEvent = createBubbledEvent('drop', {
59
+ clientX: 0,
60
+ clientY: 1,
61
+ dataTransfer: {
62
+ files: [myFile, myFile2]
63
+ }
28
64
  })
29
65
 
30
- it('can be instantiated', () => {
31
- container.innerHTML = /* html */ `
32
- <ark-droparea></ark-droparea>
33
- `
34
- const droparea = container.querySelector('ark-droparea')
35
- const preview = droparea.querySelector('ark-droparea-preview')
36
- expect(preview).toBe(preview.init())
66
+ dropZone.dispatchEvent(dropEvent)
67
+ preview.querySelector('button').click()
68
+ preview.querySelector('button').click()
69
+ })
70
+
71
+ it('Can drag previews and sort a new list', () => {
72
+ setup()
73
+ container.innerHTML = /* html */ `
74
+ <ark-droparea></ark-droparea>
75
+ `
76
+
77
+ const droparea = container.querySelector('ark-droparea')
78
+ const preview = droparea.querySelector('[data-preview-list]')
79
+ const dropZone = droparea.querySelector('.ark-droparea__form')
80
+ const myFile = new File(['image'], 'Doggy.png', {
81
+ type: 'image/png'
82
+ })
83
+ const myFile2 = new File(['image'], 'Scooby.png', {
84
+ type: 'image/png'
85
+ })
86
+ const dropEvent = createBubbledEvent('drop', {
87
+ clientX: 0,
88
+ clientY: 1,
89
+ dataTransfer: {
90
+ files: [myFile, myFile2]
91
+ }
37
92
  })
38
93
 
39
- it('Item can be removed', () => {
40
- container.innerHTML = /* html */ `
41
- <ark-droparea></ark-droparea>
42
- `
94
+ dropZone.dispatchEvent(dropEvent)
95
+
96
+ preview.handleDrag = mock.fn()
97
+
98
+ const getThumbnails = () =>
99
+ Array.from(preview.querySelectorAll('.ark-droparea-preview__frame'))
100
+
101
+ const thumbnails = getThumbnails()
102
+
103
+ const startingNode = thumbnails[0]
104
+ const endingNode = thumbnails[1]
105
+
106
+ startingNode.dispatchEvent(
107
+ createBubbledEvent('dragstart', { clientX: 0, clientY: 0 })
108
+ )
109
+ endingNode.dispatchEvent(
110
+ createBubbledEvent('dragend', { clientX: 0, clientY: 1 })
111
+ )
112
+ })
113
+
114
+ it('handleDrag keeps the selected item when elementFromPoint returns null', () => {
115
+ setup()
116
+ const preview = document.createElement('ark-droparea-preview')
117
+ const list = document.createElement('ul')
118
+ const firstItem = document.createElement('li')
119
+ const secondItem = document.createElement('li')
120
+ list.append(firstItem, secondItem)
121
+ preview.appendChild(list)
122
+ document.body.appendChild(preview)
123
+
124
+ const originalElementFromPoint = document.elementFromPoint
125
+ document.elementFromPoint = () => null
126
+
127
+ preview.handleDrag(firstItem, { clientX: 0, clientY: 0 })
128
+
129
+ assert.ok(firstItem.classList.contains('drag-sort-active'))
130
+ assert.strictEqual(list.firstElementChild, firstItem)
131
+
132
+ document.elementFromPoint = originalElementFromPoint
133
+ })
134
+
135
+ it('handleDrag moves the selected item after its next sibling', () => {
136
+ setup()
137
+ const preview = document.createElement('ark-droparea-preview')
138
+ const list = document.createElement('ul')
139
+ const firstItem = document.createElement('li')
140
+ const secondItem = document.createElement('li')
141
+ const thirdItem = document.createElement('li')
142
+ list.append(firstItem, secondItem, thirdItem)
143
+ preview.appendChild(list)
144
+ document.body.appendChild(preview)
145
+
146
+ const originalElementFromPoint = document.elementFromPoint
147
+ document.elementFromPoint = () => secondItem
148
+
149
+ preview.handleDrag(firstItem, { clientX: 1, clientY: 1 })
150
+
151
+ assert.strictEqual(list.children[0], secondItem)
152
+ assert.strictEqual(list.children[1], firstItem)
153
+ assert.strictEqual(list.children[2], thirdItem)
43
154
 
155
+ document.elementFromPoint = originalElementFromPoint
156
+ })
157
+
158
+ it('does not add duplicate drag listeners to existing previews', () => {
159
+ setup()
160
+ const counts = new WeakMap()
161
+ const originalAddEventListener = Element.prototype.addEventListener
162
+ Element.prototype.addEventListener = function (type, listener, options) {
163
+ const isDragEvent = type === 'drag' || type === 'dragend'
164
+ if (this.tagName === 'LI' && isDragEvent) {
165
+ const itemCounts = counts.get(this) || { drag: 0, dragend: 0 }
166
+ itemCounts[type] += 1
167
+ counts.set(this, itemCounts)
168
+ }
169
+
170
+ return originalAddEventListener.call(this, type, listener, options)
171
+ }
172
+
173
+ try {
174
+ container.innerHTML = /* html */ `
175
+ <ark-droparea></ark-droparea>
176
+ `
44
177
  const droparea = container.querySelector('ark-droparea')
45
- const preview = droparea.querySelector('[data-preview-list]')
46
178
  const dropZone = droparea.querySelector('.ark-droparea__form')
47
- const myFile = new File(['image'], 'Doggy.png', {
48
- type: 'image/png'
49
- })
50
- const myFile2 = new File(['image'], 'Scooby.png', {
51
- type: 'image/png'
179
+
180
+ const firstBatch = createBubbledEvent('drop', {
181
+ dataTransfer: { files: [new File(['image'], 'Doggy.png', { type: 'image/png' })] }
52
182
  })
53
- const dropEvent = createBubbledEvent('drop', {
54
- clientX: 0,
55
- clientY: 1,
56
- dataTransfer: {
57
- files: [myFile, myFile2]
58
- }
183
+ const secondBatch = createBubbledEvent('drop', {
184
+ dataTransfer: { files: [new File(['image'], 'Scooby.png', { type: 'image/png' })] }
59
185
  })
60
186
 
61
- dropZone.dispatchEvent(dropEvent)
62
- preview.querySelector('button').click()
63
- preview.querySelector('button').click()
187
+ dropZone.dispatchEvent(firstBatch)
188
+ dropZone.dispatchEvent(secondBatch)
189
+
190
+ const firstItem = droparea.querySelector('.ark-droparea-preview__frame')
191
+ const itemCounts = counts.get(firstItem)
192
+
193
+ assert.deepStrictEqual(itemCounts.drag, 1)
194
+ assert.deepStrictEqual(itemCounts.dragend, 1)
195
+ } finally {
196
+ Element.prototype.addEventListener = originalAddEventListener
197
+ }
198
+ })
199
+
200
+ it('reuses object URLs for the media list and revokes on file removal', () => {
201
+ setup()
202
+ container.innerHTML = /* html */ `
203
+ <ark-droparea></ark-droparea>
204
+ `
205
+ const droparea = container.querySelector('ark-droparea')
206
+ const dropZone = droparea.querySelector('.ark-droparea__form')
207
+ const myFile = new File(['image'], 'Doggy.png', {
208
+ type: 'image/png'
64
209
  })
65
210
 
66
- it('Can drag previews and sort a new list', () => {
67
- container.innerHTML = /* html */ `
68
- <ark-droparea></ark-droparea>
69
- `
211
+ dropZone.dispatchEvent(createBubbledEvent('drop', {
212
+ dataTransfer: { files: [myFile] }
213
+ }))
70
214
 
71
- const droparea = container.querySelector('ark-droparea')
72
- const preview = droparea.querySelector('[data-preview-list]')
73
- const dropZone = droparea.querySelector('.ark-droparea__form')
74
- const myFile = new File(['image'], 'Doggy.png', {
75
- type: 'image/png'
76
- })
77
- const myFile2 = new File(['image'], 'Scooby.png', {
78
- type: 'image/png'
79
- })
80
- const dropEvent = createBubbledEvent('drop', {
81
- clientX: 0,
82
- clientY: 1,
83
- dataTransfer: {
84
- files: [myFile, myFile2]
85
- }
86
- })
215
+ const callsBeforeMediaRead = global.URL.createObjectURL.mock.calls.length
216
+ const mediaUrl = droparea.mediaList[0].url
217
+ const mediaUrlSecondRead = droparea.mediaList[0].url
218
+
219
+ assert.deepStrictEqual(mediaUrl, mediaUrlSecondRead)
220
+ assert.deepStrictEqual(
221
+ global.URL.createObjectURL.mock.calls.length,
222
+ callsBeforeMediaRead
223
+ )
224
+
225
+ droparea.querySelector('.ark-droparea__remove').click()
226
+ assert.deepStrictEqual(
227
+ global.URL.revokeObjectURL.mock.calls[0].arguments[0],
228
+ mediaUrl
229
+ )
230
+ })
231
+
232
+ it('returns early when handleDrop runs outside a droparea component', () => {
233
+ setup()
234
+ const preview = document.createElement('ark-droparea-preview')
235
+ const target = document.createElement('li')
236
+ preview.appendChild(target)
237
+ document.body.appendChild(preview)
238
+
239
+ assert.doesNotThrow(() => {
240
+ preview.handleDrop({ target })
241
+ })
242
+ })
243
+
244
+ it('returns early when removing a file that does not exist', () => {
245
+ setup()
246
+ container.innerHTML = /* html */ `
247
+ <ark-droparea></ark-droparea>
248
+ `
249
+ const droparea = container.querySelector('ark-droparea')
250
+ const preview = droparea.preview
251
+ const button = document.createElement('button')
252
+ const frame = document.createElement('li')
253
+ frame.appendChild(button)
254
+ preview.select('[data-preview-list]').appendChild(frame)
255
+
256
+ const unknownFile = new File(['image'], 'unknown.png', { type: 'image/png' })
87
257
 
88
- dropZone.dispatchEvent(dropEvent)
258
+ assert.doesNotThrow(() => {
259
+ preview.removeFile(unknownFile, { target: button })
260
+ })
261
+ assert.ok(preview.select('[data-preview-list]').contains(frame))
262
+ })
89
263
 
90
- preview.handleDrag = jest.fn()
264
+ it('skips drag listeners for already-enabled items', () => {
265
+ setup()
266
+ const preview = document.createElement('ark-droparea-preview')
267
+ const item = document.createElement('li')
268
+ item.setAttribute('data-drag-enabled', '')
269
+ let calls = 0
270
+ const originalAddEventListener = item.addEventListener
271
+ item.addEventListener = function (...args) {
272
+ calls += 1
273
+ return originalAddEventListener.apply(this, args)
274
+ }
91
275
 
92
- const getThumbnails = () =>
93
- Array.from(preview.querySelectorAll('.ark-droparea-preview__frame'))
276
+ preview.enableDragItem(item)
94
277
 
95
- const thumbnails = getThumbnails()
278
+ assert.deepStrictEqual(calls, 0)
279
+ })
96
280
 
97
- const startingNode = thumbnails[0]
98
- const endingNode = thumbnails[1]
281
+ it('does nothing when revoking a file without an object URL', () => {
282
+ setup()
283
+ const preview = document.createElement('ark-droparea-preview')
284
+ const file = new File(['image'], 'Snoopy.png', { type: 'image/png' })
99
285
 
100
- startingNode.dispatchEvent(
101
- createBubbledEvent('dragstart', { clientX: 0, clientY: 0 })
102
- )
103
- endingNode.dispatchEvent(
104
- createBubbledEvent('dragend', { clientX: 0, clientY: 1 })
105
- )
286
+ assert.doesNotThrow(() => {
287
+ preview.revokeFile(file)
106
288
  })
107
289
  })
@@ -7,6 +7,12 @@ import './droparea-preview.js'
7
7
  const tag = 'ark-droparea'
8
8
 
9
9
  export class Droparea extends Component {
10
+ constructor () {
11
+ super()
12
+ this._onChange = this.onChange.bind(this)
13
+ this._onOpenInput = this.openInput.bind(this)
14
+ }
15
+
10
16
  init (context = {}) {
11
17
  this.fileList = []
12
18
  this.contextFiles = context.contextFiles || this.contextFiles || []
@@ -42,6 +48,8 @@ export class Droparea extends Component {
42
48
  }
43
49
 
44
50
  async load () {
51
+ this._detachListeners()
52
+
45
53
  this.dragDropEvents.forEach((eventName) => {
46
54
  this.addEventListener(eventName, this.preventDefaults, false)
47
55
  })
@@ -55,8 +63,8 @@ export class Droparea extends Component {
55
63
  })
56
64
 
57
65
  this.addEventListener('drop', this.handleDrop, false)
58
- this._input.addEventListener('change', this.onChange.bind(this))
59
- this.openButton.addEventListener('click', this.openInput.bind(this))
66
+ this._input?.addEventListener('change', this._onChange)
67
+ this.openButton?.addEventListener('click', this._onOpenInput)
60
68
 
61
69
  /* istanbul ignore else */
62
70
  if (this.contextFiles) {
@@ -64,6 +72,35 @@ export class Droparea extends Component {
64
72
  }
65
73
  }
66
74
 
75
+ disconnectedCallback () {
76
+ this._detachListeners()
77
+ super.disconnectedCallback()
78
+ }
79
+
80
+ _detachListeners () {
81
+ if (this.dragDropEvents) {
82
+ this.dragDropEvents.forEach((eventName) => {
83
+ this.removeEventListener(eventName, this.preventDefaults, false)
84
+ })
85
+ }
86
+
87
+ if (this.dragEvents) {
88
+ this.dragEvents.forEach((eventName) => {
89
+ this.removeEventListener(eventName, this.highlight, false)
90
+ })
91
+ }
92
+
93
+ if (this.dropEvents) {
94
+ this.dropEvents.forEach((eventName) => {
95
+ this.removeEventListener(eventName, this.unhighlight, false)
96
+ })
97
+ }
98
+
99
+ this.removeEventListener('drop', this.handleDrop, false)
100
+ this._input?.removeEventListener('change', this._onChange)
101
+ this.openButton?.removeEventListener('click', this._onOpenInput)
102
+ }
103
+
67
104
  openInput (event) {
68
105
  event.stopPropagation()
69
106
  const input = this.select('[data-input]')
@@ -106,8 +143,9 @@ export class Droparea extends Component {
106
143
  !this.preview.fileExists(files[0]) &&
107
144
  this.maxSizeValidate(files[0])
108
145
  ) {
146
+ this.fileList[0] && this.preview.revokeFile(this.fileList[0])
109
147
  this.fileList[0] = files[0]
110
- this.preview.querySelector('[data-preview-list]').innerHTML = ''
148
+ this.preview.clearPreview()
111
149
  this.preview.previewFile(files[0])
112
150
  }
113
151
  } else {
@@ -155,7 +193,6 @@ export class Droparea extends Component {
155
193
  _grabSlots() {
156
194
  const [fileInput] = [this.slots.general].flat()
157
195
  this.fileInput = this.fileInput ?? fileInput
158
-
159
196
  }
160
197
 
161
198
  _buildFileInput (element) {