@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,20 +1,42 @@
1
1
  // @ts-nocheck
2
2
 
3
+ const stylesheetRegistry = new Map()
4
+ const fallbackRegistry = new Map()
5
+
3
6
  /** @param {string} tag
4
7
  * @param {CustomElementConstructor} element
5
8
  * @param {string} styles **/
6
9
  export function define (tag, element, styles = '') {
7
- globalThis.customElements.define(tag, element)
10
+ const definedElement = globalThis.customElements.get(tag)
11
+ if (!definedElement) {
12
+ globalThis.customElements.define(tag, element)
13
+ }
8
14
  if (!styles?.trim()) return
15
+ if (typeof document === 'undefined') return
9
16
 
10
17
  try {
11
- const sheet = new globalThis.CSSStyleSheet()
18
+ let sheet = stylesheetRegistry.get(tag)
19
+ if (!sheet) {
20
+ sheet = new globalThis.CSSStyleSheet()
21
+ stylesheetRegistry.set(tag, sheet)
22
+ }
12
23
  sheet.replaceSync(styles)
13
- return (document.adoptedStyleSheets = [
14
- ...document.adoptedStyleSheets, sheet])
24
+
25
+ const adopted = document.adoptedStyleSheets || []
26
+ if (!adopted.includes(sheet)) {
27
+ document.adoptedStyleSheets = [...adopted, sheet]
28
+ }
29
+
30
+ return sheet
15
31
  } catch (error) {
16
- const style = document.createElement('style')
32
+ let style = fallbackRegistry.get(tag)
33
+ if (!style) {
34
+ style = document.createElement('style')
35
+ style.setAttribute('data-componark-tag', tag)
36
+ fallbackRegistry.set(tag, style)
37
+ document.head.appendChild(style)
38
+ }
17
39
  style.textContent = styles
18
- document.head.appendChild(style)
40
+ return style
19
41
  }
20
42
  }
@@ -1,62 +1,149 @@
1
- import { jest } from '@jest/globals'
1
+ import { it, mock } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
  import { define } from './define.js'
3
4
 
4
- describe('Define', () => {
5
- it('can define a custome element', () => {
6
- class NewElement extends globalThis.HTMLElement {}
7
- define('new-element', NewElement)
5
+ it('can define a custome element', () => {
6
+ class NewElement extends globalThis.HTMLElement {}
7
+ define('new-element', NewElement)
8
8
 
9
- const newElement = document.createElement('new-element')
10
- expect(newElement).toBeTruthy()
11
- })
9
+ const newElement = document.createElement('new-element')
10
+ assert.ok(newElement)
11
+ })
12
12
 
13
- it('can define a custom element using constructable CSSStyleSheet', () => {
14
- let definedStyles = null
15
- class MockCSSStyleSheet extends globalThis.CSSStyleSheet {
16
- replaceSync (styles) {
17
- definedStyles = styles
18
- }
13
+ it('can define a custom element using constructable CSSStyleSheet', () => {
14
+ let definedStyles = null
15
+ class MockCSSStyleSheet extends globalThis.CSSStyleSheet {
16
+ replaceSync (styles) {
17
+ definedStyles = styles
19
18
  }
19
+ }
20
20
 
21
- jest.spyOn(window, 'CSSStyleSheet').mockReturnValue(
22
- new MockCSSStyleSheet())
23
-
24
- class CSSStyledElement extends globalThis.HTMLElement {}
25
- const styles = `
26
- body {
27
- color: red;
21
+ const cssStyleSheetSpy = mock.method(
22
+ globalThis,
23
+ 'CSSStyleSheet',
24
+ function () {
25
+ return new MockCSSStyleSheet()
28
26
  }
27
+ )
28
+
29
+ class CSSStyledElement extends globalThis.HTMLElement {}
30
+ const styles = `
31
+ body {
32
+ color: red;
33
+ }
34
+
35
+ div {
36
+ margin: 5px;
37
+ }
38
+ `
39
+
40
+ define('css-styled-element', CSSStyledElement, styles)
29
41
 
30
- div {
31
- margin: 5px;
42
+ const styledElement = document.createElement('css-styled-element')
43
+ assert.ok(styledElement)
44
+
45
+ assert.deepStrictEqual(styles, definedStyles)
46
+ cssStyleSheetSpy.mock.restore()
47
+ })
48
+
49
+ it('can define a custom element using an style fallback element', () => {
50
+ const cssStyleSheetSpy = mock.method(
51
+ globalThis,
52
+ 'CSSStyleSheet',
53
+ function () {
54
+ throw new Error('Constructable stylesheets are unavailable')
32
55
  }
33
- `
56
+ )
57
+
58
+ class StyledElement extends globalThis.HTMLElement {}
59
+ const styles = `
60
+ body {
61
+ color: red;
62
+ }
63
+
64
+ div {
65
+ margin: 5px;
66
+ }
67
+ `
68
+
69
+ define('styled-element', StyledElement, styles)
70
+ cssStyleSheetSpy.mock.restore()
34
71
 
35
- define('css-styled-element', CSSStyledElement, styles)
72
+ const styledElement = document.createElement('styled-element')
73
+ assert.ok(styledElement)
74
+ const style = Array.from(document.head.querySelectorAll('style')).find(
75
+ (item) => item.textContent.includes('color: red;')
76
+ )
77
+ assert.ok(style)
78
+ })
36
79
 
37
- const styledElement = document.createElement('css-styled-element')
38
- expect(styledElement).toBeTruthy()
80
+ it('does not throw when defining an already defined custom element', () => {
81
+ class DuplicateElement extends globalThis.HTMLElement {}
82
+ define('duplicate-element', DuplicateElement)
39
83
 
40
- expect(styles).toEqual(definedStyles)
84
+ assert.doesNotThrow(() => {
85
+ define('duplicate-element', DuplicateElement)
41
86
  })
87
+ })
42
88
 
43
- it('can define a custom element using an style fallback element', () => {
44
- class StyledElement extends globalThis.HTMLElement {}
45
- const styles = `
46
- body {
47
- color: red;
89
+ it('reuses fallback styles for repeated definitions of the same tag', () => {
90
+ const cssStyleSheetSpy = mock.method(
91
+ globalThis,
92
+ 'CSSStyleSheet',
93
+ function () {
94
+ throw new Error('Constructable stylesheets are unavailable')
48
95
  }
96
+ )
97
+
98
+ class ReusedStyledElement extends globalThis.HTMLElement {}
99
+
100
+ define('reused-styled-element', ReusedStyledElement, 'body { color: red; }')
101
+ define('reused-styled-element', ReusedStyledElement, 'body { color: blue; }')
102
+
103
+ cssStyleSheetSpy.mock.restore()
104
+
105
+ const styles = Array.from(
106
+ document.head.querySelectorAll('style[data-componark-tag="reused-styled-element"]')
107
+ )
108
+ assert.deepStrictEqual(styles.length, 1)
109
+ assert.ok(styles[0].textContent.includes('color: blue;'))
110
+ })
49
111
 
50
- div {
51
- margin: 5px;
112
+ it('does not add a constructable stylesheet more than once for the same tag', () => {
113
+ class MockCSSStyleSheet extends globalThis.CSSStyleSheet {
114
+ replaceSync (_styles) {}
115
+ }
116
+ const cssStyleSheetSpy = mock.method(
117
+ globalThis,
118
+ 'CSSStyleSheet',
119
+ function () {
120
+ return new MockCSSStyleSheet()
52
121
  }
53
- `
122
+ )
123
+ class OnceStyledElement extends globalThis.HTMLElement {}
54
124
 
55
- define('styled-element', StyledElement, styles)
125
+ const beforeCount = document.adoptedStyleSheets.length
126
+ define('once-styled-element', OnceStyledElement, 'body { color: orange; }')
127
+ const firstCount = document.adoptedStyleSheets.length
128
+ define('once-styled-element', OnceStyledElement, 'body { color: green; }')
129
+ const secondCount = document.adoptedStyleSheets.length
56
130
 
57
- const styledElement = document.createElement('styled-element')
58
- expect(styledElement).toBeTruthy()
59
- const style = document.head.querySelector('style')
60
- expect(style).toBeTruthy()
61
- })
131
+ cssStyleSheetSpy.mock.restore()
132
+ assert.deepStrictEqual(firstCount, beforeCount + 1)
133
+ assert.deepStrictEqual(secondCount, firstCount)
134
+ })
135
+
136
+ it('returns early when styles are provided but document is unavailable', () => {
137
+ const previousDocument = globalThis.document
138
+ class NoDocumentElement extends globalThis.HTMLElement {}
139
+
140
+ delete globalThis.document
141
+
142
+ try {
143
+ assert.doesNotThrow(() => {
144
+ define('no-document-element', NoDocumentElement, 'body { color: red; }')
145
+ })
146
+ } finally {
147
+ globalThis.document = previousDocument
148
+ }
62
149
  })
@@ -1,19 +1,19 @@
1
+ import { it } from 'node:test';
2
+ import assert from 'node:assert/strict';
1
3
  import { camelToKebab, kebabToCamel, snakeToCamel } from './format.js'
2
4
 
3
- describe('Format', () => {
4
- it('can convert camel to kebab', () => {
5
- const camel = 'myVariable'
6
- const kebab = camelToKebab(camel)
7
- expect(kebab).toBe('my-variable')
8
- })
9
- it('can convert kebab to camel', () => {
10
- const kebab = 'my-variable'
11
- const camel = kebabToCamel(kebab)
12
- expect(camel).toBe('myVariable')
13
- })
14
- it('can convert snake to camel', () => {
15
- const kebab = 'my_variable'
16
- const camel = snakeToCamel(kebab)
17
- expect(camel).toBe('myVariable')
18
- })
5
+ it('can convert camel to kebab', () => {
6
+ const camel = 'myVariable'
7
+ const kebab = camelToKebab(camel)
8
+ assert.strictEqual(kebab, 'my-variable')
9
+ })
10
+ it('can convert kebab to camel', () => {
11
+ const kebab = 'my-variable'
12
+ const camel = kebabToCamel(kebab)
13
+ assert.strictEqual(camel, 'myVariable')
14
+ })
15
+ it('can convert snake to camel', () => {
16
+ const kebab = 'my_variable'
17
+ const camel = snakeToCamel(kebab)
18
+ assert.strictEqual(camel, 'myVariable')
19
19
  })
@@ -25,8 +25,11 @@ export function listen (self) {
25
25
  if (pipe?.toLocaleLowerCase() === 'object') {
26
26
  return set(this, objectPath.trim(), event.detail)
27
27
  }
28
- set(this, objectPath.trim(), transform(pipe, get(
29
- event, eventPath, get(event, 'detail', ''))))
28
+ const inputValue = (
29
+ get(event, eventPath, undefined) ||
30
+ get(event, 'detail', '')
31
+ )
32
+ set(this, objectPath.trim(), transform(pipe, inputValue))
30
33
  }
31
34
  }
32
35
 
@@ -102,6 +105,7 @@ function transform (pipe, value) {
102
105
  /** @param {object} object @param {string} path @param {any} value */
103
106
  export function set (object, path, value) {
104
107
  const pathArray = path.match(/([^[.\]])+/g)
108
+ if (!pathArray?.length) return
105
109
 
106
110
  pathArray.reduce((accumulator, key, index) => {
107
111
  if (accumulator[key.trim()] === undefined) accumulator[key.trim()] = {}
@@ -113,9 +117,12 @@ export function set (object, path, value) {
113
117
  /** @param {object} object @param {string} path @param {any} fallback */
114
118
  export function get (object, path, fallback) {
115
119
  const pathArray = path.match(/([^[.\]])+/g)
120
+ if (!pathArray?.length) return fallback
116
121
 
117
- return pathArray.reduce((accumulator, key) => accumulator &&
118
- accumulator[key.trim()], object) || fallback
122
+ const value = pathArray.reduce((accumulator, key) => accumulator &&
123
+ accumulator[key.trim()], object)
124
+
125
+ return value === undefined ? fallback : value
119
126
  }
120
127
 
121
128
  /** @param {object} object @return {string} */
@@ -1,154 +1,173 @@
1
+ import { it } from 'node:test';
2
+ import assert from 'node:assert/strict';
1
3
  import { listen, reflect, set, get, keys } from './helpers.js'
2
4
 
3
- describe('Helpers', () => {
4
- it('does not allow invalid attribute', () => {
5
- const element = document.createElement('div')
6
- element.innerHTML = /* html */`
7
- <button on-open="click" listen on-click="click" on-click="myMethod"
8
- on-close="click"></button>
9
- `
10
-
11
- listen(element)
12
-
13
- element.querySelector('button').click()
14
- expect(!element.hasAttribute('clicked-element')).toBeTruthy()
15
- })
16
-
17
- it('does not allow invalid attribute', () => {
18
- const element = document.createElement('div')
19
- element.innerHTML = /* html */`
20
- <button listen ark-on-click="myMethod"></button>
21
- `
5
+ it('does not allow invalid attribute', () => {
6
+ const element = document.createElement('div')
7
+ element.innerHTML = /* html */`
8
+ <button on-open="click" listen on-click="click" on-click="myMethod"
9
+ on-close="click"></button>
10
+ `
22
11
 
23
- listen(element)
12
+ listen(element)
24
13
 
25
- element.querySelector('button').click()
26
- expect(!element.hasAttribute('clicked-element')).toBeTruthy()
27
- })
14
+ element.querySelector('button').click()
15
+ assert.ok(!element.hasAttribute('clicked-element'))
16
+ })
17
+
18
+ it('does not allow invalid attribute', () => {
19
+ const element = document.createElement('div')
20
+ element.innerHTML = /* html */`
21
+ <button listen ark-on-click="myMethod"></button>
22
+ `
23
+
24
+ listen(element)
25
+
26
+ element.querySelector('button').click()
27
+ assert.ok(!element.hasAttribute('clicked-element'))
28
+ })
28
29
 
29
- it('attribute ', () => {
30
- const button = document.createElement('button')
31
- button.setAttribute('listen', '')
32
- button.setAttribute('on-abc', 'method')
33
- button.setAttribute('on-xyz', 'method')
30
+ it('attribute ', () => {
31
+ const button = document.createElement('button')
32
+ button.setAttribute('listen', '')
33
+ button.setAttribute('on-abc', 'method')
34
+ button.setAttribute('on-xyz', 'method')
34
35
 
35
- const content = document.createElement('div')
36
- content.appendChild(button)
36
+ const content = document.createElement('div')
37
+ content.appendChild(button)
37
38
 
38
- listen(content)
39
- })
39
+ listen(content)
40
+ })
40
41
 
41
- it('It does not allow to execute undeclared methods.', () => {
42
- const element = document.createElement('div')
43
- element.innerHTML = /* html */`
44
- <button listen on-click="myMethod"></button>
45
- `
42
+ it('It does not allow to execute undeclared methods.', () => {
43
+ const element = document.createElement('div')
44
+ element.innerHTML = /* html */`
45
+ <button listen on-click="myMethod"></button>
46
+ `
46
47
 
47
- listen(element)
48
+ listen(element)
48
49
 
49
- element.querySelector('button').click()
50
- expect(!element.hasAttribute('clicked-element')).toBeTruthy()
51
- })
50
+ element.querySelector('button').click()
51
+ assert.ok(!element.hasAttribute('clicked-element'))
52
+ })
52
53
 
53
- it('can listen events', () => {
54
- const element = document.createElement('div')
55
- Object.defineProperty(element, 'binding', {value: 'listen'})
56
- element.innerHTML = /* html */`
57
- <button listen on-click="myMethod"></button>
58
- `
59
- // @ts-ignore
60
- element.myMethod = function () {
61
- element.setAttribute('clicked-element', '')
62
- }
54
+ it('can listen events', () => {
55
+ const element = document.createElement('div')
56
+ Object.defineProperty(element, 'binding', {value: 'listen'})
57
+ element.innerHTML = /* html */`
58
+ <button listen on-click="myMethod"></button>
59
+ `
60
+ // @ts-ignore
61
+ element.myMethod = function () {
62
+ element.setAttribute('clicked-element', '')
63
+ }
64
+
65
+ listen(element)
66
+
67
+ element.querySelector('button').click()
68
+ assert.ok(element.hasAttribute('clicked-element'))
69
+ })
63
70
 
64
- listen(element)
71
+ it('ignores missing methods when listening', () => {
72
+ const element = document.createElement('div')
73
+ Object.defineProperty(element, 'binding', {value: 'listen'})
74
+ element.innerHTML = /* html */`
75
+ <button listen on-click="missing"></button>
76
+ `
77
+ // @ts-ignore
78
+ element.myMethod = function () {
79
+ element.setAttribute('clicked-element', '')
80
+ }
81
+
82
+ listen(element)
83
+
84
+ element.querySelector('button').click()
85
+ assert.ok(!element.hasAttribute('clicked-element'))
86
+ })
65
87
 
66
- element.querySelector('button').click()
67
- expect(element.hasAttribute('clicked-element')).toBeTruthy()
68
- })
88
+ it('can create attribute', () => {
89
+ const element = document.createElement('div')
90
+ const properties = ['myProperty']
69
91
 
70
- it('ignores missing methods when listening', () => {
71
- const element = document.createElement('div')
72
- Object.defineProperty(element, 'binding', {value: 'listen'})
73
- element.innerHTML = /* html */`
74
- <button listen on-click="missing"></button>
75
- `
76
- // @ts-ignore
77
- element.myMethod = function () {
78
- element.setAttribute('clicked-element', '')
79
- }
92
+ reflect(element, properties)
80
93
 
81
- listen(element)
94
+ // @ts-ignore
95
+ element.myProperty = 'value'
82
96
 
83
- element.querySelector('button').click()
84
- expect(element.hasAttribute('clicked-element')).toBeFalsy()
85
- })
97
+ // @ts-ignore
98
+ assert.strictEqual(element.myProperty, 'value')
99
+ assert.ok(element.hasAttribute('my-property'))
100
+ })
86
101
 
87
- it('can create attribute', () => {
88
- const element = document.createElement('div')
89
- const properties = ['myProperty']
102
+ it('cannot create attribute with property undefined', () => {
103
+ const element = document.createElement('div')
104
+ const properties = ['myProperty']
90
105
 
91
- reflect(element, properties)
106
+ reflect(element, properties)
92
107
 
93
- // @ts-ignore
94
- element.myProperty = 'value'
108
+ // @ts-ignore
109
+ element.myProperty = undefined
95
110
 
96
- // @ts-ignore
97
- expect(element.myProperty).toBe('value')
98
- expect(element.hasAttribute('my-property')).toBeTruthy()
99
- })
111
+ // @ts-ignore
112
+ assert.strictEqual(element.myProperty, '')
113
+ assert.ok(!element.hasAttribute('my-property'))
114
+ })
100
115
 
101
- it('cannot create attribute with property undefined', () => {
102
- const element = document.createElement('div')
103
- const properties = ['myProperty']
116
+ it('sets an object properties by a given path', () => {
117
+ const object = {}
104
118
 
105
- reflect(element, properties)
119
+ set(object, 'value.data', 'Hello')
106
120
 
107
- // @ts-ignore
108
- element.myProperty = undefined
121
+ assert.deepStrictEqual(object.value.data, 'Hello')
122
+ })
109
123
 
110
- // @ts-ignore
111
- expect(element.myProperty).toBe('')
112
- expect(!element.hasAttribute('my-property')).toBeTruthy()
113
- })
124
+ it('ignores set paths that cannot be parsed', () => {
125
+ const object = { value: 7 }
126
+ set(object, '', 'Hello')
127
+ assert.deepStrictEqual(object, { value: 7 })
128
+ })
114
129
 
115
- it('sets an object properties by a given path', () => {
116
- const object = {}
130
+ it('gets an object properties by a given path', () => {
131
+ const object = { state: { data: { value: 25 } }, data: 13 }
117
132
 
118
- set(object, 'value.data', 'Hello')
133
+ let value = get(object, 'data')
119
134
 
120
- expect(object.value.data).toEqual('Hello')
121
- })
135
+ assert.deepStrictEqual(value, 13)
122
136
 
123
- it('gets an object properties by a given path', () => {
124
- const object = { state: { data: { value: 25 } }, data: 13 }
137
+ value = get(object, 'state.data.value')
125
138
 
126
- let value = get(object, 'data')
139
+ assert.deepStrictEqual(value, 25)
127
140
 
128
- expect(value).toEqual(13)
141
+ const fallback = get(object, 'state.missing.value', 7)
129
142
 
130
- value = get(object, 'state.data.value')
143
+ assert.deepStrictEqual(fallback, 7)
144
+ })
131
145
 
132
- expect(value).toEqual(25)
146
+ it('gets falsy values by a given path without replacing them', () => {
147
+ const object = { state: { count: 0, enabled: false, empty: '' } }
133
148
 
134
- const fallback = get(object, 'state.missing.value', 7)
149
+ assert.deepStrictEqual(get(object, 'state.count', 7), 0)
150
+ assert.deepStrictEqual(get(object, 'state.enabled', true), false)
151
+ assert.deepStrictEqual(get(object, 'state.empty', 'fallback'), '')
152
+ })
135
153
 
136
- expect(fallback).toEqual(7)
137
- })
154
+ it('returns fallback when get path cannot be parsed', () => {
155
+ const object = { value: 7 }
156
+ assert.deepStrictEqual(get(object, '', 'fallback'), 'fallback')
157
+ })
138
158
 
139
- it('retrieves the object keys of truthy values', () => {
140
- const background = 'primary'
141
- const shadow = 'small'
142
- const color = ''
159
+ it('retrieves the object keys of truthy values', () => {
160
+ const background = 'primary'
161
+ const shadow = 'small'
162
+ const color = ''
143
163
 
144
- const classes = {
145
- [`background-${background}`]: background,
146
- [`color-${color}`]: color,
147
- [`shadow-${shadow}`]: shadow
148
- }
164
+ const classes = {
165
+ [`background-${background}`]: background,
166
+ [`color-${color}`]: color,
167
+ [`shadow-${shadow}`]: shadow
168
+ }
149
169
 
150
- const result = keys(classes)
170
+ const result = keys(classes)
151
171
 
152
- expect(result).toEqual('background-primary shadow-small')
153
- })
172
+ assert.deepStrictEqual(result, 'background-primary shadow-small')
154
173
  })