@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.
Files changed (118) hide show
  1. package/README.md +57 -45
  2. package/lib/base/component/component.js +142 -20
  3. package/lib/base/component/component.test.js +753 -374
  4. package/lib/base/component/index.js +3 -0
  5. package/lib/base/styles/index.js +4 -1
  6. package/lib/base/utils/define.js +30 -7
  7. package/lib/base/utils/define.test.js +129 -42
  8. package/lib/base/utils/format.js +12 -6
  9. package/lib/base/utils/format.test.js +16 -16
  10. package/lib/base/utils/helpers.js +42 -9
  11. package/lib/base/utils/helpers.test.js +134 -115
  12. package/lib/base/utils/index.js +1 -0
  13. package/lib/base/utils/slots.js +3 -2
  14. package/lib/base/utils/slots.test.js +38 -38
  15. package/lib/base/utils/uuid.js +1 -1
  16. package/lib/base/utils/uuid.test.js +13 -13
  17. package/lib/components/audio/components/audio.js +36 -3
  18. package/lib/components/audio/components/audio.test.js +120 -90
  19. package/lib/components/audio/index.js +1 -0
  20. package/lib/components/audio/styles/index.js +5 -1
  21. package/lib/components/camera/components/camera.js +15 -0
  22. package/lib/components/camera/components/camera.test.js +96 -91
  23. package/lib/components/camera/index.js +1 -0
  24. package/lib/components/camera/styles/index.js +5 -1
  25. package/lib/components/capture/components/capture.js +48 -4
  26. package/lib/components/capture/components/capture.test.js +165 -97
  27. package/lib/components/capture/index.js +1 -0
  28. package/lib/components/droparea/components/droparea-preview.js +114 -19
  29. package/lib/components/droparea/components/droparea-preview.test.js +344 -80
  30. package/lib/components/droparea/components/droparea.js +82 -6
  31. package/lib/components/droparea/components/droparea.test.js +309 -299
  32. package/lib/components/droparea/index.js +1 -0
  33. package/lib/components/droparea/styles/index.js +5 -1
  34. package/lib/components/emit/components/emit.js +34 -4
  35. package/lib/components/emit/components/emit.test.js +192 -134
  36. package/lib/components/emit/index.js +1 -0
  37. package/lib/components/index.js +2 -1
  38. package/lib/components/list/components/item.js +6 -0
  39. package/lib/components/list/components/item.test.js +69 -68
  40. package/lib/components/list/components/list.js +51 -7
  41. package/lib/components/list/components/list.test.js +358 -227
  42. package/lib/components/list/index.js +1 -0
  43. package/lib/components/paginator/components/paginator.js +34 -8
  44. package/lib/components/paginator/components/paginator.test.js +146 -143
  45. package/lib/components/paginator/index.js +1 -0
  46. package/lib/components/paginator/styles/index.js +5 -1
  47. package/lib/components/spinner/components/spinner.js +10 -0
  48. package/lib/components/spinner/components/spinner.test.js +36 -41
  49. package/lib/components/spinner/index.js +1 -0
  50. package/lib/components/spinner/styles/index.js +5 -1
  51. package/lib/components/splitview/components/splitview.detail.js +10 -1
  52. package/lib/components/splitview/components/splitview.detail.test.js +75 -73
  53. package/lib/components/splitview/components/splitview.js +54 -11
  54. package/lib/components/splitview/components/splitview.master.js +37 -2
  55. package/lib/components/splitview/components/splitview.master.test.js +52 -52
  56. package/lib/components/splitview/components/splitview.test.js +135 -31
  57. package/lib/components/splitview/index.js +1 -0
  58. package/lib/components/translate/components/translate.js +65 -14
  59. package/lib/components/translate/components/translate.test.js +658 -131
  60. package/lib/components/translate/index.js +1 -0
  61. package/lib/index.js +3 -0
  62. package/package.json +4 -27
  63. package/scripts/node-test-setup.js +94 -0
  64. package/tsconfig.json +1 -1
  65. package/types/base/component/component.d.ts +43 -8
  66. package/types/base/component/component.d.ts.map +1 -1
  67. package/types/base/component/index.d.ts +4 -6
  68. package/types/base/component/index.d.ts.map +1 -1
  69. package/types/base/styles/index.d.ts +3 -2
  70. package/types/base/styles/index.d.ts.map +1 -1
  71. package/types/base/utils/define.d.ts +3 -2
  72. package/types/base/utils/define.d.ts.map +1 -1
  73. package/types/base/utils/format.d.ts +12 -6
  74. package/types/base/utils/format.d.ts.map +1 -1
  75. package/types/base/utils/helpers.d.ts +27 -7
  76. package/types/base/utils/helpers.d.ts.map +1 -1
  77. package/types/base/utils/slots.d.ts +8 -10
  78. package/types/base/utils/slots.d.ts.map +1 -1
  79. package/types/base/utils/uuid.d.ts +1 -1
  80. package/types/base/utils/uuid.d.ts.map +1 -1
  81. package/types/components/audio/components/audio.d.ts +23 -9
  82. package/types/components/audio/components/audio.d.ts.map +1 -1
  83. package/types/components/audio/styles/index.d.ts +3 -2
  84. package/types/components/audio/styles/index.d.ts.map +1 -1
  85. package/types/components/camera/components/camera.d.ts +11 -3
  86. package/types/components/camera/components/camera.d.ts.map +1 -1
  87. package/types/components/camera/styles/index.d.ts +3 -2
  88. package/types/components/camera/styles/index.d.ts.map +1 -1
  89. package/types/components/capture/components/capture.d.ts +23 -3
  90. package/types/components/capture/components/capture.d.ts.map +1 -1
  91. package/types/components/droparea/components/droparea-preview.d.ts +64 -11
  92. package/types/components/droparea/components/droparea-preview.d.ts.map +1 -1
  93. package/types/components/droparea/components/droparea.d.ts +58 -13
  94. package/types/components/droparea/components/droparea.d.ts.map +1 -1
  95. package/types/components/droparea/styles/index.d.ts +3 -2
  96. package/types/components/droparea/styles/index.d.ts.map +1 -1
  97. package/types/components/emit/components/emit.d.ts +15 -3
  98. package/types/components/emit/components/emit.d.ts.map +1 -1
  99. package/types/components/list/components/item.d.ts +8 -1
  100. package/types/components/list/components/item.d.ts.map +1 -1
  101. package/types/components/list/components/list.d.ts +23 -5
  102. package/types/components/list/components/list.d.ts.map +1 -1
  103. package/types/components/paginator/components/paginator.d.ts +32 -8
  104. package/types/components/paginator/components/paginator.d.ts.map +1 -1
  105. package/types/components/paginator/styles/index.d.ts +3 -2
  106. package/types/components/paginator/styles/index.d.ts.map +1 -1
  107. package/types/components/spinner/components/spinner.d.ts +14 -3
  108. package/types/components/spinner/components/spinner.d.ts.map +1 -1
  109. package/types/components/spinner/styles/index.d.ts +3 -2
  110. package/types/components/spinner/styles/index.d.ts.map +1 -1
  111. package/types/components/splitview/components/splitview.d.ts +22 -4
  112. package/types/components/splitview/components/splitview.d.ts.map +1 -1
  113. package/types/components/splitview/components/splitview.detail.d.ts +12 -2
  114. package/types/components/splitview/components/splitview.detail.d.ts.map +1 -1
  115. package/types/components/splitview/components/splitview.master.d.ts +12 -1
  116. package/types/components/splitview/components/splitview.master.d.ts.map +1 -1
  117. package/types/components/translate/components/translate.d.ts +44 -10
  118. package/types/components/translate/components/translate.d.ts.map +1 -1
@@ -1,88 +1,457 @@
1
+ import { it } from 'node:test';
2
+ import assert from 'node:assert/strict';
1
3
  import { Translate } from './translate.js'
2
4
 
3
- describe('Translate', () => {
4
- let container = null
5
- beforeEach(() => {
6
- container = document.createElement('div')
7
- document.body.appendChild(container)
5
+ let container = null
6
+
7
+ const setup = () => {
8
+ document.body.innerHTML = '';
9
+ container = document.createElement('div')
10
+ document.body.appendChild(container)
11
+ };
12
+
13
+ it('can be instantiated', () => {
14
+ setup();
15
+ container.innerHTML = /* html */`
16
+ <ark-translate>
17
+ </ark-translate>
18
+ `
19
+
20
+ const translate = container.querySelector('ark-translate')
21
+
22
+ assert.ok(translate)
23
+ assert.strictEqual(translate, translate.init())
24
+ })
25
+
26
+ it('translates the marked text inside the given root', async () => {
27
+ setup();
28
+ const root = document.createElement('div')
29
+ root.innerHTML = /* html */`
30
+ <span data-i18n="hello">Hello</span>
31
+ <p>
32
+ <span data-i18n="happy">Happy</span>!!!
33
+ <span data-i18n="world">World</span>!!!
34
+ </p>
35
+ `
36
+ container.appendChild(root)
37
+ const translateContainer = document.createElement('div')
38
+ translateContainer.innerHTML = /* html */`
39
+ <ark-translate>
40
+ <template>{
41
+ "en": {
42
+ "default": {
43
+ "hello": "Hello",
44
+ "world": "World"
45
+ }
46
+ },
47
+ "es": {
48
+ "default": {
49
+ "hello": "Hola",
50
+ "world": "Mundo"
51
+ }
52
+ }
53
+ }</template>
54
+ </ark-translate>
55
+ `
56
+ container.appendChild(translateContainer)
57
+ const translate = /** @type Translate **/ (
58
+ translateContainer.querySelector('ark-translate'))
59
+
60
+ await translate.transliterate()
61
+
62
+ const expectedRoot = document.createElement('div')
63
+ expectedRoot.innerHTML = /* html */`
64
+ <span data-i18n="hello">Hola</span>
65
+ <p>
66
+ <span data-i18n="happy">Happy</span>!!!
67
+ <span data-i18n="world">Mundo</span>!!!
68
+ </p>
69
+ `
70
+ assert.deepStrictEqual(root, expectedRoot)
71
+ })
72
+
73
+ it('might use different translation languages and namespaces', async () => {
74
+ setup();
75
+ const root = document.createElement('div')
76
+ root.innerHTML = /* html */`
77
+ <span data-i18n="introModule:hello">hello</span>
78
+ <p>
79
+ <span data-i18n="introModule:happy">happy</span>!!!
80
+ <span data-i18n="introModule:world">world</span>!!!
81
+ </p>
82
+ `
83
+ container.appendChild(root)
84
+ const translateContainer = document.createElement('div')
85
+ translateContainer.innerHTML = /* html */`
86
+ <ark-translate>
87
+ <template>{
88
+ "es": {
89
+ "default": {
90
+ "hello": "Hola",
91
+ "world": "Mundo"
92
+ },
93
+ "introModule": {
94
+ "hello": "Quiubo",
95
+ "world": "Gente"
96
+ }
97
+ },
98
+ "en": {
99
+ "default": {
100
+ "hello": "Hello",
101
+ "world": "World"
102
+ },
103
+ "introModule": {
104
+ "hello": "Hey",
105
+ "world": "Folks"
106
+ }
107
+ }
108
+ }</template>
109
+ </ark-translate>
110
+ `
111
+ container.appendChild(translateContainer)
112
+ const translate = /** @type Translate **/ (
113
+ translateContainer.querySelector('ark-translate'))
114
+
115
+ let options = { language: 'en' }
116
+ await translate.transliterate(options)
117
+
118
+ const expectedRoot = document.createElement('div')
119
+ expectedRoot.innerHTML = /* html */`
120
+ <span data-i18n="introModule:hello">Hey</span>
121
+ <p>
122
+ <span data-i18n="introModule:happy">happy</span>!!!
123
+ <span data-i18n="introModule:world">Folks</span>!!!
124
+ </p>
125
+ `
126
+ assert.deepStrictEqual(root, expectedRoot)
127
+
128
+ // Unknown Namespace:
129
+ root.innerHTML = /* html */`
130
+ <span data-i18n="unknown:hello">Hello</span>
131
+ <p>
132
+ <span data-i18n="unknown:happy">Happy</span>!!!
133
+ <span data-i18n="unknown:world">World</span>!!!
134
+ </p>
135
+ `
136
+
137
+ options = { language: 'es' }
138
+ await translate.transliterate(options)
139
+
140
+ expectedRoot.innerHTML = /* html */`
141
+ <span data-i18n="unknown:hello">Hello</span>
142
+ <p>
143
+ <span data-i18n="unknown:happy">Happy</span>!!!
144
+ <span data-i18n="unknown:world">World</span>!!!
145
+ </p>
146
+ `
147
+ assert.deepStrictEqual(root, expectedRoot)
148
+ })
149
+
150
+ it('translates the page content on language changes', async () => {
151
+ setup();
152
+ const translateContainer = document.createElement('div')
153
+ translateContainer.innerHTML = /* html */`
154
+ <ark-translate languages="es,en">
155
+ <template>{
156
+ "es": {
157
+ "default": {
158
+ "hello": "Hola",
159
+ "world": "Mundo"
160
+ }
161
+ },
162
+ "en": {
163
+ "default": {
164
+ "hello": "Hello",
165
+ "world": "World"
166
+ }
167
+ }
168
+ }</template>
169
+ </ark-translate>
170
+ `
171
+ container.appendChild(translateContainer)
172
+ const translate = /** @type Translate **/ (
173
+ translateContainer.querySelector('ark-translate'))
174
+ const mockEvent = { target: { value: 'en' }, stopPropagation: () => {} }
175
+ let givenOptions = null
176
+ translate.transliterate = async (options) => {
177
+ givenOptions = options
178
+ }
179
+
180
+ await translate.onLanguageChanged(mockEvent)
181
+
182
+ assert.deepStrictEqual(givenOptions, { language: 'en' })
183
+ })
184
+
185
+ it('passes an undefined language when language change event has no target', async () => {
186
+ setup()
187
+ const translateContainer = document.createElement('div')
188
+ translateContainer.innerHTML = '<ark-translate></ark-translate>'
189
+ container.appendChild(translateContainer)
190
+ const translate = /** @type Translate **/ (
191
+ translateContainer.querySelector('ark-translate'))
192
+ let givenOptions = null
193
+
194
+ translate.transliterate = async (options) => {
195
+ givenOptions = options
196
+ }
197
+
198
+ await translate.onLanguageChanged({
199
+ stopPropagation: () => {},
200
+ target: null
8
201
  })
9
202
 
10
- afterEach(() => {
11
- container.remove()
12
- container = null
203
+ assert.deepStrictEqual(givenOptions, { language: undefined })
204
+ })
205
+
206
+ it('fetches its translation files from the server', async () => {
207
+ setup();
208
+ const root = document.createElement('div')
209
+ root.innerHTML = /* html */`
210
+ <span data-i18n="hello">hello</span>
211
+ <p>
212
+ <span data-i18n="happy">happy</span>!!!
213
+ <span data-i18n="world">world</span>!!!
214
+ </p>
215
+ `
216
+ container.appendChild(root)
217
+ const translateContainer = document.createElement('div')
218
+ translateContainer.innerHTML = `
219
+ <ark-translate languages="es,en"></ark-translate>
220
+ `
221
+ container.appendChild(translateContainer)
222
+ const translate = /** @type Translate **/ (
223
+ translateContainer.querySelector('ark-translate'))
224
+
225
+ const mockFetch = async (url) => ({
226
+ json: async () => ({
227
+ hello: 'Hola',
228
+ world: 'Mundo',
229
+ happy: 'Feliz'
230
+ })
231
+ })
232
+ translate.init({
233
+ global: { fetch: mockFetch, document }
13
234
  })
14
235
 
15
- it('can be instantiated', () => {
16
- container.innerHTML = /* html */`
17
- <ark-translate>
18
- </ark-translate>
19
- `
236
+ await translate.transliterate({ language: 'es' })
237
+
238
+ const expectedRoot = document.createElement('div')
239
+ expectedRoot.innerHTML = /* html */`
240
+ <span data-i18n="hello">Hola</span>
241
+ <p>
242
+ <span data-i18n="happy">Feliz</span>!!!
243
+ <span data-i18n="world">Mundo</span>!!!
244
+ </p>
245
+ `
246
+ assert.deepStrictEqual(root, expectedRoot)
247
+ })
20
248
 
21
- const translate = container.querySelector('ark-translate')
249
+ it('renders unknown language codes without crashing', () => {
250
+ setup()
251
+ const translateContainer = document.createElement('div')
252
+ translateContainer.innerHTML = `
253
+ <ark-translate languages="es,de"></ark-translate>
254
+ `
255
+ container.appendChild(translateContainer)
256
+ const translate = /** @type Translate **/ (
257
+ translateContainer.querySelector('ark-translate'))
258
+
259
+ const option = translate.querySelector('option[value="de"]')
260
+ assert.ok(option)
261
+ assert.deepStrictEqual(option.textContent, 'de')
262
+ })
22
263
 
23
- expect(translate).toBeTruthy()
24
- expect(translate).toBe(translate.init())
264
+ it('emits error when inline dictionary JSON is invalid', () => {
265
+ setup()
266
+ const translateContainer = document.createElement('div')
267
+ translateContainer.innerHTML = /* html */`
268
+ <ark-translate>
269
+ <template>{ invalid json }</template>
270
+ </ark-translate>
271
+ `
272
+ container.appendChild(translateContainer)
273
+ const translate = /** @type Translate **/ (
274
+ translateContainer.querySelector('ark-translate'))
275
+ let errorEvent = null
276
+
277
+ translate.addEventListener('error', (event) => {
278
+ errorEvent = event
25
279
  })
280
+ translate.init()
26
281
 
27
- it('translates the marked text inside the given root', async () => {
28
- const root = document.createElement('div')
29
- root.innerHTML = /* html */`
30
- <span data-i18n="hello">Hello</span>
31
- <p>
32
- <span data-i18n="happy">Happy</span>!!!
33
- <span data-i18n="world">World</span>!!!
34
- </p>
35
- `
36
- container.appendChild(root)
37
- const translateContainer = document.createElement('div')
38
- translateContainer.innerHTML = /* html */`
282
+ assert.ok(errorEvent)
283
+ })
284
+
285
+ it('emits error when dictionary fetch fails', async () => {
286
+ setup()
287
+ const root = document.createElement('div')
288
+ root.innerHTML = /* html */`
289
+ <span data-i18n="hello">hello</span>
290
+ `
291
+ container.appendChild(root)
292
+ const translateContainer = document.createElement('div')
293
+ translateContainer.innerHTML = `
294
+ <ark-translate></ark-translate>
295
+ `
296
+ container.appendChild(translateContainer)
297
+ const translate = /** @type Translate **/ (
298
+ translateContainer.querySelector('ark-translate'))
299
+ let errorEvent = null
300
+ translate.addEventListener('error', (event) => {
301
+ errorEvent = event
302
+ })
303
+
304
+ translate.init({
305
+ global: {
306
+ document,
307
+ fetch: async () => {
308
+ throw new Error('Network failed')
309
+ }
310
+ }
311
+ })
312
+
313
+ await translate.transliterate({ language: 'es' })
314
+
315
+ assert.ok(errorEvent)
316
+ assert.deepStrictEqual(errorEvent.detail.message, 'Network failed')
317
+ })
318
+
319
+ it('translates with dynamic options', async () => {
320
+ setup()
321
+ const root = document.createElement('div')
322
+ root.innerHTML = /* html */`
323
+ <span data-i18n="hello">hello</span>
324
+ <p>
325
+ <span data-i18n="happy">happy</span>!!!
326
+ <span data-i18n="world">world</span>!!!
327
+ </p>
328
+ `
329
+ container.appendChild(root)
330
+ const translateContainer = document.createElement('div')
331
+ translateContainer.innerHTML = /* html */`
332
+ <ark-translate languages="es,en">
333
+ <template>{
334
+ "es": {
335
+ "default": {
336
+ "hello": "Hola",
337
+ "world": "Mundo"
338
+ }
339
+ },
340
+ "en": {
341
+ "default": {
342
+ "hello": "Hello",
343
+ "world": "World"
344
+ }
345
+ }
346
+ }</template>
347
+ </ark-translate>
348
+ `
349
+ container.appendChild(translateContainer)
350
+ const translate = /** @type Translate **/ (
351
+ translateContainer.querySelector('ark-translate'))
352
+
353
+ await translate.transliterate({ language: 'es' })
354
+
355
+ const expectedRoot = document.createElement('div')
356
+ expectedRoot.innerHTML = /* html */`
357
+ <span data-i18n="hello">Hola</span>
358
+ <p>
359
+ <span data-i18n="happy">Happy</span>!!!
360
+ <span data-i18n="world">Mundo</span>!!!
361
+ </p>
362
+ `
363
+ assert.deepStrictEqual(root, expectedRoot)
364
+ })
365
+
366
+ it('renders the translate component correctly', async () => {
367
+ setup();
368
+ const template = `
39
369
  <ark-translate>
40
370
  <template>{
371
+ "es": {
372
+ "default": {
373
+ "hello": "Hola",
374
+ "world": "Mundo"
375
+ }
376
+ },
41
377
  "en": {
42
378
  "default": {
43
379
  "hello": "Hello",
44
380
  "world": "World"
45
381
  }
46
- },
382
+ }
383
+ }</template>
384
+ </ark-translate>
385
+ `;
386
+ container.innerHTML = template
387
+ const translateContainer = container.querySelector('ark-translate')
388
+ const translate = /** @type Translate **/ (translateContainer)
389
+
390
+ assert.strictEqual(translate, translate.init())
391
+ })
392
+
393
+ it('renders the translate component with a single language', async () => {
394
+ setup();
395
+ const root = document.createElement('div')
396
+ root.innerHTML = /* html */`
397
+ <span data-i18n="hello">Hello</span>
398
+ <p>
399
+ <span data-i18n="happy">Happy</span>!!!
400
+ <span data-i18n="world">World</span>!!!
401
+ </p>
402
+ `
403
+ container.appendChild(root)
404
+
405
+ const translateContainer = document.createElement('div')
406
+ translateContainer.innerHTML = /* html */`
407
+ <ark-translate languages="es,en">
408
+ <template>{
47
409
  "es": {
48
410
  "default": {
49
411
  "hello": "Hola",
50
412
  "world": "Mundo"
51
413
  }
414
+ },
415
+ "en": {
416
+ "default": {
417
+ "hello": "Hello",
418
+ "world": "World"
419
+ }
52
420
  }
53
421
  }</template>
54
422
  </ark-translate>
55
- `
56
- container.appendChild(translateContainer)
57
- const translate = /** @type Translate **/ (
58
- translateContainer.querySelector('ark-translate'))
423
+ `
424
+ container.appendChild(translateContainer)
425
+ const translate = /** @type Translate **/ (translateContainer.querySelector('ark-translate'))
59
426
 
60
- await translate.transliterate()
427
+ await translate.transliterate({ language: 'es' })
61
428
 
62
- const expectedRoot = document.createElement('div')
63
- expectedRoot.innerHTML = /* html */`
429
+ const expectedRoot = document.createElement('div')
430
+ expectedRoot.innerHTML = /* html */`
64
431
  <span data-i18n="hello">Hola</span>
65
432
  <p>
66
433
  <span data-i18n="happy">Happy</span>!!!
67
434
  <span data-i18n="world">Mundo</span>!!!
68
435
  </p>
69
- `
70
- expect(root).toEqual(expectedRoot)
71
- })
436
+ `
437
+ assert.deepStrictEqual(root, expectedRoot)
438
+ })
72
439
 
73
- it('might use different translation languages and namespaces', async () => {
74
- const root = document.createElement('div')
75
- root.innerHTML = /* html */`
76
- <span data-i18n="introModule:hello">hello</span>
440
+ it('renders the translate component with a nested namespace', async () => {
441
+ setup();
442
+ const root = document.createElement('div')
443
+ root.innerHTML = /* html */`
444
+ <span data-i18n="introModule:hello">Hello</span>
77
445
  <p>
78
- <span data-i18n="introModule:happy">happy</span>!!!
79
- <span data-i18n="introModule:world">world</span>!!!
446
+ <span data-i18n="introModule:happy">Happy</span>!!!
447
+ <span data-i18n="introModule:world">World</span>!!!
80
448
  </p>
81
- `
82
- container.appendChild(root)
83
- const translateContainer = document.createElement('div')
84
- translateContainer.innerHTML = /* html */`
85
- <ark-translate>
449
+ `
450
+ container.appendChild(root)
451
+
452
+ const translateContainer = document.createElement('div')
453
+ translateContainer.innerHTML = /* html */`
454
+ <ark-translate languages="es,en">
86
455
  <template>{
87
456
  "es": {
88
457
  "default": {
@@ -106,50 +475,51 @@ describe('Translate', () => {
106
475
  }
107
476
  }</template>
108
477
  </ark-translate>
109
- `
110
- container.appendChild(translateContainer)
111
- const translate = /** @type Translate **/ (
112
- translateContainer.querySelector('ark-translate'))
478
+ `
479
+ container.appendChild(translateContainer)
480
+ const translate = /** @type Translate **/ (translateContainer.querySelector('ark-translate'))
113
481
 
114
- let options = { language: 'en' }
115
- await translate.transliterate(options)
482
+ await translate.transliterate({ language: 'es' })
116
483
 
117
- const expectedRoot = document.createElement('div')
118
- expectedRoot.innerHTML = /* html */`
119
- <span data-i18n="introModule:hello">Hey</span>
484
+ const expectedRoot = document.createElement('div')
485
+ expectedRoot.innerHTML = /* html */`
486
+ <span data-i18n="introModule:hello">Quiubo</span>
120
487
  <p>
121
- <span data-i18n="introModule:happy">happy</span>!!!
122
- <span data-i18n="introModule:world">Folks</span>!!!
488
+ <span data-i18n="introModule:happy">Happy</span>!!!
489
+ <span data-i18n="introModule:world">Gente</span>!!!
123
490
  </p>
124
- `
125
- expect(root).toEqual(expectedRoot)
491
+ `
492
+ assert.deepStrictEqual(root, expectedRoot)
493
+ })
126
494
 
127
- // Unknown Namespace:
495
+ it('renders the translate component with an invalid dictionary JSON', () => {
496
+ setup();
497
+
498
+ const template = `<ark-translate>
499
+ <template>{ "invalid": {} }</template>
500
+ </ark-translate>`
501
+ container.innerHTML = template
502
+ const translateContainer = container.querySelector('ark-translate')
503
+ const translate = /** @type Translate **/ (translateContainer)
128
504
 
129
- root.innerHTML = /* html */`
130
- <span data-i18n="unknown:hello">Hello</span>
131
- <p>
132
- <span data-i18n="unknown:happy">Happy</span>!!!
133
- <span data-i18n="unknown:world">World</span>!!!
134
- </p>
135
- `
136
-
137
- options = { language: 'es' }
138
- await translate.transliterate(options)
505
+ assert.strictEqual(translate, translate.init())
506
+ })
139
507
 
140
- expectedRoot.innerHTML = /* html */`
141
- <span data-i18n="unknown:hello">Hello</span>
508
+ it('translates with dynamic options using global fetch', async () => {
509
+ setup();
510
+
511
+ const root = document.createElement('div')
512
+ root.innerHTML = /* html */`
513
+ <span data-i18n="hello">Hello</span>
142
514
  <p>
143
- <span data-i18n="unknown:happy">Happy</span>!!!
144
- <span data-i18n="unknown:world">World</span>!!!
515
+ <span data-i18n="happy">Happy</span>!!!
516
+ <span data-i18n="world">World</span>!!!
145
517
  </p>
146
- `
147
- expect(root).toEqual(expectedRoot)
148
- })
149
-
150
- it('translates the page content on language changes', async () => {
151
- const translateContainer = document.createElement('div')
152
- translateContainer.innerHTML = /* html */`
518
+ `
519
+ container.appendChild(root)
520
+
521
+ const translateContainer = document.createElement('div')
522
+ translateContainer.innerHTML = /* html */`
153
523
  <ark-translate languages="es,en">
154
524
  <template>{
155
525
  "es": {
@@ -166,60 +536,217 @@ describe('Translate', () => {
166
536
  }
167
537
  }</template>
168
538
  </ark-translate>
169
- `
170
- container.appendChild(translateContainer)
171
- const translate = /** @type Translate **/ (
172
- translateContainer.querySelector('ark-translate'))
173
- const mockEvent = { target: { value: 'en' }, stopPropagation: () => {} }
174
- let givenOptions = null
175
- translate.transliterate = async (options) => {
176
- givenOptions = options
177
- }
539
+ `
540
+ container.appendChild(translateContainer)
541
+ const translate = /** @type Translate **/ (translateContainer.querySelector('ark-translate'))
178
542
 
179
- await translate.onLanguageChanged(mockEvent)
543
+ await translate.transliterate({ language: 'es' })
180
544
 
181
- expect(givenOptions).toEqual({ language: 'en' })
182
- })
545
+ const expectedRoot = document.createElement('div')
546
+ expectedRoot.innerHTML = /* html */`
547
+ <span data-i18n="hello">Hola</span>
548
+ <p>
549
+ <span data-i18n="happy">Happy</span>!!!
550
+ <span data-i18n="world">Mundo</span>!!!
551
+ </p>
552
+ `
553
+ assert.deepStrictEqual(root, expectedRoot)
554
+ })
555
+
556
+ it('renders the translate component with empty dictionaries', async () => {
557
+ setup();
558
+
559
+ const template = `<ark-translate>
560
+ <template>{ "es": {} }</template>
561
+ </ark-translate>`
562
+ container.innerHTML = template
563
+ const translateContainer = container.querySelector('ark-translate')
564
+ const translate = /** @type Translate **/ (translateContainer)
565
+
566
+ assert.strictEqual(translate, translate.init())
567
+ })
183
568
 
184
- it('fetches its translation files from the server', async () => {
185
- const root = document.createElement('div')
186
- root.innerHTML = /* html */`
187
- <span data-i18n="hello">hello</span>
569
+ it('renders the translate component with an empty dictionary', async () => {
570
+ setup();
571
+
572
+ const root = document.createElement('div')
573
+ root.innerHTML = /* html */`
574
+ <span data-i18n="hello">Hello</span>
188
575
  <p>
189
- <span data-i18n="happy">happy</span>!!!
190
- <span data-i18n="world">world</span>!!!
576
+ <span data-i18n="happy">Happy</span>!!!
577
+ <span data-i18n="world">World</span>!!!
191
578
  </p>
192
- `
193
- container.appendChild(root)
194
- const translateContainer = document.createElement('div')
195
- translateContainer.innerHTML = `
196
- <ark-translate languages="es,en"></ark-translate>
197
- `
198
- container.appendChild(translateContainer)
199
- const translate = /** @type Translate **/ (
200
- translateContainer.querySelector('ark-translate'))
201
-
202
- const mockFetch = async (url) => ({
203
- json: async () => ({
204
- hello: 'Hola',
205
- world: 'Mundo',
206
- happy: 'Feliz'
207
- })
208
- })
209
- translate.init({
210
- global: { fetch: mockFetch, document }
211
- })
579
+ `
580
+ container.appendChild(root)
581
+
582
+ const translateContainer = document.createElement('div')
583
+ translateContainer.innerHTML = /* html */`
584
+ <ark-translate languages="es,en">
585
+ <template>{
586
+ "es": {},
587
+ "en": {}
588
+ }</template>
589
+ </ark-translate>
590
+ `
591
+ container.appendChild(translateContainer)
592
+ const translate = /** @type Translate **/ (translateContainer.querySelector('ark-translate'))
212
593
 
213
- await translate.transliterate({ language: 'es' })
594
+ await translate.transliterate({ language: 'es' })
214
595
 
215
- const expectedRoot = document.createElement('div')
216
- expectedRoot.innerHTML = /* html */`
217
- <span data-i18n="hello">Hola</span>
596
+ const expectedRoot = document.createElement('div')
597
+ expectedRoot.innerHTML = /* html */`
598
+ <span data-i18n="hello">Hello</span>
218
599
  <p>
219
- <span data-i18n="happy">Feliz</span>!!!
220
- <span data-i18n="world">Mundo</span>!!!
600
+ <span data-i18n="happy">Happy</span>!!!
601
+ <span data-i18n="world">World</span>!!!
221
602
  </p>
222
- `
223
- expect(root).toEqual(expectedRoot)
603
+ `
604
+ assert.deepStrictEqual(root, expectedRoot)
605
+ })
606
+
607
+ it('does not render language options for an empty language list', () => {
608
+ setup()
609
+ container.innerHTML = '<ark-translate languages=""></ark-translate>'
610
+ const translate = container.querySelector('ark-translate')
611
+
612
+ translate.init()
613
+
614
+ const select = translate.querySelector('select')
615
+ assert.equal(select, null)
616
+ })
617
+
618
+ it('does nothing when the configured transliterate root does not exist', async () => {
619
+ setup()
620
+ const root = document.createElement('div')
621
+ root.innerHTML = /* html */`
622
+ <span data-i18n="hello">Hello</span>
623
+ `
624
+ root.id = 'translations'
625
+ container.appendChild(root)
626
+ const translateContainer = document.createElement('div')
627
+ translateContainer.innerHTML = '<ark-translate></ark-translate>'
628
+ container.appendChild(translateContainer)
629
+ const translate = /** @type Translate **/ (
630
+ translateContainer.querySelector('ark-translate'))
631
+
632
+ let called = false
633
+ translate.resolveDictionary = async () => {
634
+ called = true
635
+ return {}
636
+ }
637
+
638
+ await translate.transliterate({ root: '#missing-root' })
639
+
640
+ assert.equal(called, false)
641
+ })
642
+
643
+ it('returns an empty dictionary when no fetch API is available', async () => {
644
+ setup()
645
+ const translateContainer = document.createElement('div')
646
+ translateContainer.innerHTML = '<ark-translate></ark-translate>'
647
+ container.appendChild(translateContainer)
648
+ const translate = /** @type Translate **/ (
649
+ translateContainer.querySelector('ark-translate'))
650
+
651
+ translate.init({ global: { document } })
652
+
653
+ const dictionary = await translate.resolveDictionary('es', 'default')
654
+ assert.deepStrictEqual(dictionary, {})
655
+ })
656
+
657
+ it('keeps original text when translation endpoint returns a non-ok response', async () => {
658
+ setup()
659
+ const root = document.createElement('div')
660
+ root.innerHTML = /* html */`
661
+ <span data-i18n="hello">Hello</span>
662
+ `
663
+ root.id = 'translate-root'
664
+ container.appendChild(root)
665
+ const translateContainer = document.createElement('div')
666
+ translateContainer.innerHTML = '<ark-translate languages="es"></ark-translate>'
667
+ container.appendChild(translateContainer)
668
+ const translate = /** @type Translate **/ (
669
+ translateContainer.querySelector('ark-translate'))
670
+ let requestedUrl = null
671
+
672
+ translate.init({
673
+ root: '#translate-root',
674
+ global: {
675
+ document,
676
+ fetch: async (url) => {
677
+ requestedUrl = url
678
+ return { ok: false }
679
+ }
680
+ }
681
+ })
682
+
683
+ await translate.transliterate({ language: 'es' })
684
+
685
+ assert.equal(requestedUrl, '/locales/es/default.json')
686
+ assert.equal(root.querySelector('[data-i18n="hello"]').textContent, 'Hello')
687
+ })
688
+
689
+ it('stores an empty dictionary cache entry when fetch returns null JSON', async () => {
690
+ setup()
691
+ const translateContainer = document.createElement('div')
692
+ translateContainer.innerHTML = '<ark-translate></ark-translate>'
693
+ container.appendChild(translateContainer)
694
+ const translate = /** @type Translate **/ (
695
+ translateContainer.querySelector('ark-translate'))
696
+
697
+ translate.init({
698
+ global: {
699
+ document,
700
+ fetch: async () => ({
701
+ ok: true,
702
+ json: async () => null
703
+ })
704
+ }
224
705
  })
706
+
707
+ const dictionary = await translate.resolveDictionary('es', 'default')
708
+
709
+ assert.equal(dictionary, null)
710
+ assert.deepStrictEqual(translate.dictionary.es.default, {})
711
+ })
712
+
713
+ it('reuses cached dictionaries for repeated transliterations', async () => {
714
+ setup()
715
+ const root = document.createElement('div')
716
+ root.innerHTML = /* html */`
717
+ <span data-i18n="hello">Hello</span>
718
+ <span data-i18n="world">World</span>
719
+ `
720
+ root.id = 'translate-root'
721
+ container.appendChild(root)
722
+ const translateContainer = document.createElement('div')
723
+ translateContainer.innerHTML = '<ark-translate></ark-translate>'
724
+ container.appendChild(translateContainer)
725
+ const translate = /** @type Translate **/ (
726
+ translateContainer.querySelector('ark-translate'))
727
+
728
+ let fetchCalls = 0
729
+ translate.init({
730
+ root: '#translate-root',
731
+ global: {
732
+ document,
733
+ fetch: async () => {
734
+ fetchCalls += 1
735
+ return {
736
+ ok: true,
737
+ json: async () => ({
738
+ hello: 'Hola',
739
+ world: 'Mundo'
740
+ })
741
+ }
742
+ }
743
+ }
744
+ })
745
+
746
+ await translate.transliterate({ language: 'es' })
747
+ await translate.transliterate({ language: 'es' })
748
+
749
+ assert.equal(fetchCalls, 1)
750
+ assert.equal(root.querySelector('[data-i18n="hello"]').textContent, 'Hola')
751
+ assert.equal(root.querySelector('[data-i18n="world"]').textContent, 'Mundo')
225
752
  })