@lion/ui 0.2.1 → 0.3.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 (272) hide show
  1. package/README.md +23 -72
  2. package/components/button/src/LionButton.js +1 -1
  3. package/components/core/src/SlotMixin.js +3 -2
  4. package/components/core/test/SlotMixin.test.js +1 -1
  5. package/components/core/types/SlotMixinTypes.ts +4 -1
  6. package/components/dialog/test/lion-dialog.test.js +56 -0
  7. package/components/form-core/src/FocusMixin.js +33 -2
  8. package/components/form-core/src/LionField.js +3 -1
  9. package/components/form-core/src/validate/LionValidationFeedback.js +2 -5
  10. package/components/form-core/src/validate/ResultValidator.js +1 -1
  11. package/components/form-core/test/FocusMixin.test.js +33 -0
  12. package/components/form-core/types/validate/ValidateMixinTypes.ts +2 -2
  13. package/components/form-integrations/test/dialog-integrations.test.js +19 -0
  14. package/components/form-integrations/test/form-group-methods.test.js +11 -37
  15. package/components/form-integrations/test/form-integrations.test.js +4 -0
  16. package/components/form-integrations/test/helpers/umbrella-form.js +21 -18
  17. package/components/input-amount/src/LionInputAmount.js +13 -4
  18. package/components/input-amount/test/lion-input-amount.test.js +19 -20
  19. package/components/input-date/src/LionInputDate.js +2 -11
  20. package/components/input-datepicker/src/LionInputDatepicker.js +5 -2
  21. package/components/input-datepicker/test/lion-input-datepicker.test.js +20 -0
  22. package/components/input-file/src/FileHandle.js +98 -0
  23. package/components/input-file/src/LionInputFile.js +892 -0
  24. package/components/input-file/src/LionSelectedFileList.js +232 -0
  25. package/components/input-file/src/localizeNamespaceLoader.js +75 -0
  26. package/components/input-file/src/validators.js +114 -0
  27. package/components/input-file/test/FileValidators.test.js +170 -0
  28. package/components/input-file/test/lion-input-file.test.js +1168 -0
  29. package/components/input-file/test/lion-selected-file-list.test.js +207 -0
  30. package/components/input-file/translations/bg-BG.js +5 -0
  31. package/components/input-file/translations/bg.js +12 -0
  32. package/components/input-file/translations/cs-CZ.js +5 -0
  33. package/components/input-file/translations/cs.js +13 -0
  34. package/components/input-file/translations/de-DE.js +5 -0
  35. package/components/input-file/translations/de.js +12 -0
  36. package/components/input-file/translations/en-AU.js +5 -0
  37. package/components/input-file/translations/en-GB.js +5 -0
  38. package/components/input-file/translations/en-PH.js +5 -0
  39. package/components/input-file/translations/en-US.js +5 -0
  40. package/components/input-file/translations/en.js +12 -0
  41. package/components/input-file/translations/es-ES.js +5 -0
  42. package/components/input-file/translations/es.js +12 -0
  43. package/components/input-file/translations/fr-BE.js +5 -0
  44. package/components/input-file/translations/fr-FR.js +5 -0
  45. package/components/input-file/translations/fr.js +13 -0
  46. package/components/input-file/translations/hu-HU.js +5 -0
  47. package/components/input-file/translations/hu.js +12 -0
  48. package/components/input-file/translations/it-IT.js +5 -0
  49. package/components/input-file/translations/it.js +12 -0
  50. package/components/input-file/translations/nl-BE.js +5 -0
  51. package/components/input-file/translations/nl-NL.js +5 -0
  52. package/components/input-file/translations/nl.js +12 -0
  53. package/components/input-file/translations/pl-PL.js +5 -0
  54. package/components/input-file/translations/pl.js +12 -0
  55. package/components/input-file/translations/ro-RO.js +5 -0
  56. package/components/input-file/translations/ro.js +12 -0
  57. package/components/input-file/translations/ru-RU.js +5 -0
  58. package/components/input-file/translations/ru.js +12 -0
  59. package/components/input-file/translations/sk-SK.js +5 -0
  60. package/components/input-file/translations/sk.js +12 -0
  61. package/components/input-file/translations/uk-UA.js +5 -0
  62. package/components/input-file/translations/uk.js +12 -0
  63. package/components/input-file/translations/zh-CN.js +5 -0
  64. package/components/input-file/translations/zh.js +12 -0
  65. package/components/input-file/types/input-file.d.ts +32 -0
  66. package/components/input-tel-dropdown/src/LionInputTelDropdown.js +1 -4
  67. package/components/localize/src/number/parseNumber.js +7 -1
  68. package/components/localize/test/number/parseNumber.test.js +18 -4
  69. package/custom-elements.json +21949 -17530
  70. package/dist-types/components/core/src/SlotMixin.d.ts.map +1 -1
  71. package/dist-types/components/core/types/SlotMixinTypes.d.ts +4 -0
  72. package/dist-types/components/core/types/SlotMixinTypes.d.ts.map +1 -1
  73. package/dist-types/components/dialog/test/lion-dialog.test.d.ts.map +1 -1
  74. package/dist-types/components/form-core/src/FocusMixin.d.ts.map +1 -1
  75. package/dist-types/components/form-core/src/LionField.d.ts +2 -1
  76. package/dist-types/components/form-core/src/LionField.d.ts.map +1 -1
  77. package/dist-types/components/form-core/src/validate/LionValidationFeedback.d.ts +4 -11
  78. package/dist-types/components/form-core/src/validate/LionValidationFeedback.d.ts.map +1 -1
  79. package/dist-types/components/form-core/src/validate/ResultValidator.d.ts +1 -1
  80. package/dist-types/components/form-core/types/validate/ValidateMixinTypes.d.ts +5 -0
  81. package/dist-types/components/form-core/types/validate/ValidateMixinTypes.d.ts.map +1 -1
  82. package/dist-types/components/form-integrations/test/dialog-integrations.test.d.ts.map +1 -1
  83. package/dist-types/components/form-integrations/test/form-group-methods.test.d.ts +1 -0
  84. package/dist-types/components/form-integrations/test/form-group-methods.test.d.ts.map +1 -1
  85. package/dist-types/components/form-integrations/test/helpers/umbrella-form.d.ts.map +1 -1
  86. package/dist-types/components/input-amount/src/LionInputAmount.d.ts +2 -4
  87. package/dist-types/components/input-amount/src/LionInputAmount.d.ts.map +1 -1
  88. package/dist-types/components/input-amount/test/lion-input-amount.test.d.ts +0 -1
  89. package/dist-types/components/input-amount/test/lion-input-amount.test.d.ts.map +1 -1
  90. package/dist-types/components/input-date/src/LionInputDate.d.ts.map +1 -1
  91. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +1 -1
  92. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts.map +1 -1
  93. package/dist-types/components/input-file/src/FileHandle.d.ts +55 -0
  94. package/dist-types/components/input-file/src/FileHandle.d.ts.map +1 -0
  95. package/dist-types/components/input-file/src/LionInputFile.d.ts +230 -0
  96. package/dist-types/components/input-file/src/LionInputFile.d.ts.map +1 -0
  97. package/dist-types/components/input-file/src/LionSelectedFileList.d.ts +69 -0
  98. package/dist-types/components/input-file/src/LionSelectedFileList.d.ts.map +1 -0
  99. package/dist-types/components/input-file/src/localizeNamespaceLoader.d.ts +2 -0
  100. package/dist-types/components/input-file/src/localizeNamespaceLoader.d.ts.map +1 -0
  101. package/dist-types/components/input-file/src/validators.d.ts +58 -0
  102. package/dist-types/components/input-file/src/validators.d.ts.map +1 -0
  103. package/dist-types/components/input-file/test/FileValidators.test.d.ts +2 -0
  104. package/dist-types/components/input-file/test/FileValidators.test.d.ts.map +1 -0
  105. package/dist-types/components/input-file/test/lion-input-file.test.d.ts +5 -0
  106. package/dist-types/components/input-file/test/lion-input-file.test.d.ts.map +1 -0
  107. package/dist-types/components/input-file/test/lion-selected-file-list.test.d.ts +4 -0
  108. package/dist-types/components/input-file/test/lion-selected-file-list.test.d.ts.map +1 -0
  109. package/dist-types/components/input-file/translations/bg-BG.d.ts +13 -0
  110. package/dist-types/components/input-file/translations/bg-BG.d.ts.map +1 -0
  111. package/dist-types/components/input-file/translations/bg.d.ts +13 -0
  112. package/dist-types/components/input-file/translations/bg.d.ts.map +1 -0
  113. package/dist-types/components/input-file/translations/cs-CZ.d.ts +13 -0
  114. package/dist-types/components/input-file/translations/cs-CZ.d.ts.map +1 -0
  115. package/dist-types/components/input-file/translations/cs.d.ts +13 -0
  116. package/dist-types/components/input-file/translations/cs.d.ts.map +1 -0
  117. package/dist-types/components/input-file/translations/de-DE.d.ts +13 -0
  118. package/dist-types/components/input-file/translations/de-DE.d.ts.map +1 -0
  119. package/dist-types/components/input-file/translations/de.d.ts +13 -0
  120. package/dist-types/components/input-file/translations/de.d.ts.map +1 -0
  121. package/dist-types/components/input-file/translations/en-AU.d.ts +13 -0
  122. package/dist-types/components/input-file/translations/en-AU.d.ts.map +1 -0
  123. package/dist-types/components/input-file/translations/en-GB.d.ts +13 -0
  124. package/dist-types/components/input-file/translations/en-GB.d.ts.map +1 -0
  125. package/dist-types/components/input-file/translations/en-PH.d.ts +13 -0
  126. package/dist-types/components/input-file/translations/en-PH.d.ts.map +1 -0
  127. package/dist-types/components/input-file/translations/en-US.d.ts +13 -0
  128. package/dist-types/components/input-file/translations/en-US.d.ts.map +1 -0
  129. package/dist-types/components/input-file/translations/en.d.ts +13 -0
  130. package/dist-types/components/input-file/translations/en.d.ts.map +1 -0
  131. package/dist-types/components/input-file/translations/es-ES.d.ts +13 -0
  132. package/dist-types/components/input-file/translations/es-ES.d.ts.map +1 -0
  133. package/dist-types/components/input-file/translations/es.d.ts +13 -0
  134. package/dist-types/components/input-file/translations/es.d.ts.map +1 -0
  135. package/dist-types/components/input-file/translations/fr-BE.d.ts +13 -0
  136. package/dist-types/components/input-file/translations/fr-BE.d.ts.map +1 -0
  137. package/dist-types/components/input-file/translations/fr-FR.d.ts +13 -0
  138. package/dist-types/components/input-file/translations/fr-FR.d.ts.map +1 -0
  139. package/dist-types/components/input-file/translations/fr.d.ts +13 -0
  140. package/dist-types/components/input-file/translations/fr.d.ts.map +1 -0
  141. package/dist-types/components/input-file/translations/hu-HU.d.ts +13 -0
  142. package/dist-types/components/input-file/translations/hu-HU.d.ts.map +1 -0
  143. package/dist-types/components/input-file/translations/hu.d.ts +13 -0
  144. package/dist-types/components/input-file/translations/hu.d.ts.map +1 -0
  145. package/dist-types/components/input-file/translations/it-IT.d.ts +13 -0
  146. package/dist-types/components/input-file/translations/it-IT.d.ts.map +1 -0
  147. package/dist-types/components/input-file/translations/it.d.ts +13 -0
  148. package/dist-types/components/input-file/translations/it.d.ts.map +1 -0
  149. package/dist-types/components/input-file/translations/nl-BE.d.ts +13 -0
  150. package/dist-types/components/input-file/translations/nl-BE.d.ts.map +1 -0
  151. package/dist-types/components/input-file/translations/nl-NL.d.ts +13 -0
  152. package/dist-types/components/input-file/translations/nl-NL.d.ts.map +1 -0
  153. package/dist-types/components/input-file/translations/nl.d.ts +13 -0
  154. package/dist-types/components/input-file/translations/nl.d.ts.map +1 -0
  155. package/dist-types/components/input-file/translations/pl-PL.d.ts +13 -0
  156. package/dist-types/components/input-file/translations/pl-PL.d.ts.map +1 -0
  157. package/dist-types/components/input-file/translations/pl.d.ts +13 -0
  158. package/dist-types/components/input-file/translations/pl.d.ts.map +1 -0
  159. package/dist-types/components/input-file/translations/ro-RO.d.ts +13 -0
  160. package/dist-types/components/input-file/translations/ro-RO.d.ts.map +1 -0
  161. package/dist-types/components/input-file/translations/ro.d.ts +13 -0
  162. package/dist-types/components/input-file/translations/ro.d.ts.map +1 -0
  163. package/dist-types/components/input-file/translations/ru-RU.d.ts +13 -0
  164. package/dist-types/components/input-file/translations/ru-RU.d.ts.map +1 -0
  165. package/dist-types/components/input-file/translations/ru.d.ts +13 -0
  166. package/dist-types/components/input-file/translations/ru.d.ts.map +1 -0
  167. package/dist-types/components/input-file/translations/sk-SK.d.ts +13 -0
  168. package/dist-types/components/input-file/translations/sk-SK.d.ts.map +1 -0
  169. package/dist-types/components/input-file/translations/sk.d.ts +13 -0
  170. package/dist-types/components/input-file/translations/sk.d.ts.map +1 -0
  171. package/dist-types/components/input-file/translations/uk-UA.d.ts +13 -0
  172. package/dist-types/components/input-file/translations/uk-UA.d.ts.map +1 -0
  173. package/dist-types/components/input-file/translations/uk.d.ts +13 -0
  174. package/dist-types/components/input-file/translations/uk.d.ts.map +1 -0
  175. package/dist-types/components/input-file/translations/zh-CN.d.ts +13 -0
  176. package/dist-types/components/input-file/translations/zh-CN.d.ts.map +1 -0
  177. package/dist-types/components/input-file/translations/zh.d.ts +13 -0
  178. package/dist-types/components/input-file/translations/zh.d.ts.map +1 -0
  179. package/dist-types/components/input-stepper/src/LionInputStepper.d.ts +6 -0
  180. package/dist-types/components/input-stepper/src/LionInputStepper.d.ts.map +1 -1
  181. package/dist-types/components/input-tel-dropdown/src/LionInputTelDropdown.d.ts +0 -1
  182. package/dist-types/components/input-tel-dropdown/src/LionInputTelDropdown.d.ts.map +1 -1
  183. package/dist-types/components/localize/src/number/parseNumber.d.ts.map +1 -1
  184. package/dist-types/exports/define/lion-input-file.d.ts +2 -0
  185. package/dist-types/exports/define/lion-input-file.d.ts.map +1 -0
  186. package/dist-types/exports/define/lion-selected-file-list.d.ts +2 -0
  187. package/dist-types/exports/define/lion-selected-file-list.d.ts.map +1 -0
  188. package/dist-types/exports/input-file.d.ts +4 -0
  189. package/dist-types/exports/input-file.d.ts.map +1 -0
  190. package/dist-types/tsconfig.tsbuildinfo +1 -1
  191. package/docs/components/button/extensions/bootstrap-button.mjs +240 -0
  192. package/docs/components/button/extensions.md +30 -0
  193. package/docs/components/checkbox-group/overview.md +2 -2
  194. package/docs/components/collapsible/overview.md +1 -1
  195. package/docs/components/combobox/{examples.md → extensions.md} +51 -43
  196. package/docs/components/combobox/overview.md +1 -1
  197. package/docs/components/combobox/src/google-combobox/google-combobox.js +4 -1
  198. package/docs/components/combobox/src/md-combobox/md-combobox.js +40 -38
  199. package/docs/components/combobox/src/wa-combobox/wa-combobox.js +204 -209
  200. package/docs/components/combobox/use-cases.md +1 -1
  201. package/docs/components/dialog/overview.md +1 -1
  202. package/docs/components/fieldset/overview.md +5 -5
  203. package/docs/components/form/overview.md +3 -3
  204. package/docs/components/icon/use-cases.md +1 -1
  205. package/docs/components/input/overview.md +1 -1
  206. package/docs/components/input-amount/overview.md +3 -3
  207. package/docs/components/input-amount/use-cases.md +4 -2
  208. package/docs/components/input-date/overview.md +3 -3
  209. package/docs/components/input-datepicker/overview.md +5 -5
  210. package/docs/components/input-datepicker/use-cases.md +11 -0
  211. package/docs/components/input-email/overview.md +2 -2
  212. package/docs/components/input-file/index.md +3 -0
  213. package/docs/components/input-file/overview.md +32 -0
  214. package/docs/components/input-file/use-cases.md +424 -0
  215. package/docs/components/input-iban/overview.md +2 -2
  216. package/docs/components/input-range/overview.md +2 -2
  217. package/docs/components/input-stepper/overview.md +1 -1
  218. package/docs/components/input-tel/overview.md +1 -1
  219. package/docs/components/input-tel/use-cases.md +6 -6
  220. package/docs/components/input-tel-dropdown/{examples.md → extensions.md} +1 -1
  221. package/docs/components/input-tel-dropdown/overview.md +1 -1
  222. package/docs/components/radio-group/overview.md +3 -3
  223. package/docs/components/select-rich/use-cases.md +1 -1
  224. package/docs/components/tabs/examples.md +1 -1
  225. package/docs/fundamentals/index.md +4 -4
  226. package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
  227. package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
  228. package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
  229. package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
  230. package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
  231. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  232. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  233. package/docs/fundamentals/rationales/accessibility.md +28 -0
  234. package/docs/fundamentals/systems/core/SlotMixin.md +148 -0
  235. package/docs/fundamentals/systems/core/overview.md +1 -1
  236. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  237. package/docs/fundamentals/systems/form/model-value.md +1 -1
  238. package/docs/fundamentals/systems/form/overview.md +28 -28
  239. package/docs/fundamentals/systems/form/use-cases.md +2 -0
  240. package/docs/fundamentals/systems/form/validate.md +1 -1
  241. package/docs/fundamentals/systems/localize/overview.md +4 -4
  242. package/docs/fundamentals/systems/overlays/_configuration-positioning.md +116 -0
  243. package/docs/fundamentals/systems/overlays/_edge-cases.md +145 -0
  244. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  245. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  246. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  247. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  248. package/docs/fundamentals/tools/singleton-manager/example-complex/index.md +21 -0
  249. package/docs/fundamentals/tools/singleton-manager/example-fail/index.md +19 -0
  250. package/docs/fundamentals/tools/singleton-manager/example-success/index.md +18 -0
  251. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  252. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  253. package/docs/guides/how-to/create-a-form.md +88 -0
  254. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  255. package/docs/guides/index.md +8 -8
  256. package/docs/guides/principles/index.md +5 -5
  257. package/docs/guides/principles/styling.md +1 -1
  258. package/exports/define/lion-input-file.js +3 -0
  259. package/exports/define/lion-selected-file-list.js +3 -0
  260. package/exports/input-file.js +3 -0
  261. package/package.json +3 -2
  262. package/docs/components/combobox/src/google-combobox/assets/googlelogo_color_272x92dp.png +0 -0
  263. package/docs/components/dialog/src/demo-dialog-style.js +0 -3
  264. package/docs/components/select-rich/src/flags.png +0 -0
  265. package/docs/components/select-rich/src/flags@2x.png +0 -0
  266. package/docs/fundamentals/systems/form/assets/FormatMixinDiagram-1.svg +0 -1
  267. package/docs/fundamentals/systems/form/assets/FormatMixinDiagram-2.svg +0 -1
  268. package/docs/fundamentals/systems/form/assets/FormatMixinDiagram-3.svg +0 -84
  269. package/docs/fundamentals/tools/ajax/assets/naga.json +0 -9
  270. package/docs/fundamentals/tools/ajax/assets/pabu.json +0 -9
  271. package/docs/fundamentals/tools/singleton-manager/.gitignore +0 -1
  272. package/docs/override.js +0 -15
@@ -0,0 +1,1168 @@
1
+ import '@lion/ui/define/lion-input-file.js';
2
+ import { Required } from '@lion/ui/form-core.js';
3
+ import { getInputMembers } from '@lion/ui/input-test-helpers.js';
4
+ import { expect, fixture as _fixture, html, oneEvent } from '@open-wc/testing';
5
+ import sinon from 'sinon';
6
+
7
+ /**
8
+ * @typedef {import('../src/LionInputFile.js').LionInputFile} LionInputFile
9
+ * @typedef {import('../types/input-file.js').InputFile} InputFile
10
+ * @typedef {import('../types/input-file.js').SystemFile} SystemFile
11
+ * @typedef {import('lit').TemplateResult} TemplateResult
12
+ */
13
+ const fixture = /** @type {(arg: TemplateResult|string) => Promise<LionInputFile>} */ (_fixture);
14
+
15
+ const filesListChanged = (/** @type {LionInputFile} */ el, /** @type { CustomEvent } */ ev) => {
16
+ // eslint-disable-next-line no-param-reassign
17
+ el.uploadResponse = [...ev.detail.newFiles];
18
+ };
19
+
20
+ function mimicSelectFile(
21
+ /** @type {LionInputFile} */ formControl,
22
+ /** @type {InputFile[]} */ mockFiles,
23
+ ) {
24
+ // @ts-expect-error [allow-protected-in-tests]
25
+ formControl._processFiles(mockFiles);
26
+ // TODO: doesn't this set latest file.name only to formControl._inputNode.value?
27
+ mockFiles.forEach(file => {
28
+ // @ts-expect-error [allow-protected-in-tests]
29
+ Object.defineProperty(formControl._inputNode, 'value', {
30
+ value: `C:\\fakepath\\${file.name}`,
31
+ writable: true,
32
+ });
33
+ });
34
+ // @ts-expect-error [allow-protected-in-tests]
35
+ Object.defineProperty(formControl._inputNode, 'files', { value: mockFiles, writable: true });
36
+ // @ts-expect-error [allow-protected-in-tests]
37
+ formControl._inputNode.dispatchEvent(new Event('input', { bubbles: true }));
38
+ }
39
+
40
+ const file = /** @type {InputFile} */ (
41
+ new File(['foo'], 'foo.txt', {
42
+ type: 'text/plain',
43
+ })
44
+ );
45
+ const file2 = /** @type {InputFile} */ (
46
+ new File(['bar'], 'bar.txt', {
47
+ type: 'text/plain',
48
+ })
49
+ );
50
+ const file3 = /** @type {InputFile} */ (
51
+ new File(['foo3'], 'foo3.txt', {
52
+ type: 'text/plain',
53
+ })
54
+ );
55
+ const file4 = /** @type {InputFile} */ (
56
+ new File(['foo4'], 'foo4.txt', {
57
+ type: 'text/plain',
58
+ })
59
+ );
60
+
61
+ describe('lion-input-file', () => {
62
+ it('has a type of "file"', async () => {
63
+ const el = await fixture(html`<lion-input-file></lion-input-file>`);
64
+ // @ts-expect-error [allow-protected-in-tests]
65
+ const { _inputNode } = getInputMembers(el);
66
+ expect(_inputNode.type).to.equal('file');
67
+ });
68
+
69
+ it('should add single file and dispatch "file-list-changed" event with newly added file', async () => {
70
+ const el = await fixture(html`<lion-input-file></lion-input-file>`);
71
+
72
+ const fileListChangedEventPromise = oneEvent(el, 'file-list-changed');
73
+ mimicSelectFile(el, [file]);
74
+ const fileListChangedEvent = await fileListChangedEventPromise;
75
+ // @ts-expect-error [allow-protected-in-tests]
76
+ expect(el._selectedFilesMetaData.length).to.equal(1);
77
+ expect(el.uploadResponse.length).to.equal(1);
78
+
79
+ expect(fileListChangedEvent).to.exist;
80
+ expect(fileListChangedEvent.detail.newFiles.length).to.equal(1);
81
+ expect(fileListChangedEvent.detail.newFiles[0].name).to.equal('foo.txt');
82
+ });
83
+
84
+ it('should select 1 file', async () => {
85
+ const el = await fixture(html`<lion-input-file></lion-input-file>`);
86
+ // @ts-expect-error [allow-protected-in-test]
87
+ const processedFilesSpy = sinon.spy(el, '_processFiles');
88
+
89
+ // @ts-expect-error [allow-protected-in-test]
90
+ await el._onChange({ target: { files: [file] } });
91
+ expect(processedFilesSpy).have.been.calledOnce;
92
+ processedFilesSpy.restore();
93
+ });
94
+
95
+ it('should retain selected file when "Cancel" button is clicked in system file explorer dialog', async () => {
96
+ const el = await fixture(html`<lion-input-file></lion-input-file>`);
97
+
98
+ mimicSelectFile(el, [file]);
99
+
100
+ await el.updateComplete;
101
+ // @ts-expect-error [allow-protected-in-test]
102
+ expect(el._selectedFilesMetaData.length).to.equal(1);
103
+ expect(el.uploadResponse.length).to.equal(1);
104
+
105
+ // when cancel is clicked, native input value is blank which means modelValue is blank
106
+ el.modelValue = [];
107
+
108
+ await el.updateComplete;
109
+ // @ts-expect-error [allow-protected-in-test]
110
+ expect(el._selectedFilesMetaData.length).to.equal(1);
111
+ });
112
+
113
+ it('has an attribute focused when focused', async () => {
114
+ const el = await fixture(html` <lion-input-file label="Select"></lion-input-file> `);
115
+
116
+ // @ts-expect-error [allow-protected-in-test]
117
+ el._buttonNode.focus();
118
+ await el.updateComplete;
119
+ expect(el.hasAttribute('focused')).to.be.true;
120
+
121
+ // @ts-expect-error [allow-protected-in-test]
122
+ el._buttonNode.blur();
123
+ await el.updateComplete;
124
+ expect(el.hasAttribute('focused')).to.be.false;
125
+ });
126
+
127
+ it('should set touched property true on change', async () => {
128
+ const el = await fixture(html` <lion-input-file label="Select"></lion-input-file> `);
129
+ expect(el.touched).to.be.false;
130
+
131
+ // @ts-expect-error [allow-protected-in-test]
132
+ await el._onChange({ target: { files: [file] } });
133
+ expect(el.touched).to.be.true;
134
+ });
135
+
136
+ it('should replace previous file when new file is selected', async () => {
137
+ const el = await fixture(html`<lion-input-file></lion-input-file>`);
138
+
139
+ mimicSelectFile(el, [file]);
140
+ await el.updateComplete;
141
+ // @ts-expect-error [allow-protected-in-test]
142
+ expect(el._selectedFilesMetaData.length).to.equal(1);
143
+ // @ts-expect-error [allow-protected-in-test]
144
+ expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('foo.txt');
145
+
146
+ mimicSelectFile(el, [file2]);
147
+ await el.updateComplete;
148
+ // @ts-expect-error [allow-protected-in-test]
149
+ expect(el._selectedFilesMetaData.length).to.equal(1);
150
+ // @ts-expect-error [allow-protected-in-test]
151
+ expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('bar.txt');
152
+ });
153
+
154
+ describe('invalid file types', async () => {
155
+ const fileWrongType = /** @type {InputFile} */ (
156
+ new File(['foobar'], 'foobar.txt', {
157
+ type: 'xxxxx',
158
+ })
159
+ );
160
+
161
+ it('should not be added to the selected list', async () => {
162
+ const el = await fixture(html`
163
+ <lion-input-file label="Select" accept="text/plain"></lion-input-file>
164
+ `);
165
+
166
+ mimicSelectFile(el, [fileWrongType]);
167
+ await el.updateComplete;
168
+
169
+ // @ts-expect-error [allow-protected-in-test]
170
+ expect(el._selectedFilesMetaData.length).to.equal(1);
171
+ // @ts-expect-error [allow-protected-in-test]
172
+ expect(el._selectedFilesMetaData[0].failedProp?.length).to.equal(1);
173
+ // @ts-expect-error [allow-protected-in-test]
174
+ expect(el._selectedFilesMetaData[0].validationFeedback).to.exist;
175
+ // @ts-expect-error [allow-protected-in-test]
176
+ expect(el._selectedFilesMetaData[0].status).to.equal('FAIL');
177
+ expect(el.uploadResponse[0].status).to.equal('FAIL');
178
+ });
179
+
180
+ it('error message should use main type when "/*" is used', async () => {
181
+ const el = await fixture(html`
182
+ <lion-input-file label="Select" accept="text/*"></lion-input-file>
183
+ `);
184
+
185
+ mimicSelectFile(el, [fileWrongType]);
186
+ await el.updateComplete;
187
+
188
+ // @ts-expect-error [allow-protected-in-test]
189
+ el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
190
+ expect(error.message).to.deep.equal('Please select a(n) text file with max 500MB.');
191
+ });
192
+ });
193
+
194
+ it('error message should use main type when "text/plain" is used', async () => {
195
+ const el = await fixture(html`
196
+ <lion-input-file label="Select" accept="text/plain"></lion-input-file>
197
+ `);
198
+
199
+ mimicSelectFile(el, [fileWrongType]);
200
+ await el.updateComplete;
201
+
202
+ // @ts-expect-error [allow-protected-in-test]
203
+ el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
204
+ expect(error.message).to.deep.equal('Please select a(n) text file with max 500MB.');
205
+ });
206
+ });
207
+
208
+ it('error message should use sub type when e.g. "text/html" is used', async () => {
209
+ const el = await fixture(html`
210
+ <lion-input-file label="Select" accept="text/html"></lion-input-file>
211
+ `);
212
+
213
+ mimicSelectFile(el, [fileWrongType]);
214
+ await el.updateComplete;
215
+
216
+ // @ts-expect-error [allow-protected-in-test]
217
+ el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
218
+ expect(error.message).to.deep.equal('Please select a(n) .html file with max 500MB.');
219
+ });
220
+ });
221
+
222
+ it('error message should use the first sub type when e.g. "image/svg+xml" is used', async () => {
223
+ const el = await fixture(html`
224
+ <lion-input-file label="Select" accept="image/svg+xml"></lion-input-file>
225
+ `);
226
+
227
+ mimicSelectFile(el, [fileWrongType]);
228
+ await el.updateComplete;
229
+
230
+ // @ts-expect-error [allow-protected-in-test]
231
+ el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
232
+ expect(error.message).to.deep.equal('Please select a(n) .svg file with max 500MB.');
233
+ });
234
+ });
235
+
236
+ it('can reflect multiple types in the error message', async () => {
237
+ const el = await fixture(html`
238
+ <lion-input-file label="Select" accept="text/html, text/csv"></lion-input-file>
239
+ `);
240
+
241
+ mimicSelectFile(el, [fileWrongType]);
242
+ await el.updateComplete;
243
+
244
+ // @ts-expect-error [allow-protected-in-test]
245
+ el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
246
+ expect(error.message).to.deep.equal('Please select a .html or .csv file with max 500MB.');
247
+ });
248
+ });
249
+
250
+ it('can reflect multiple types in the error message also with a space " "', async () => {
251
+ const el = await fixture(html`
252
+ <lion-input-file label="Select" accept="text/html,text/csv"></lion-input-file>
253
+ `);
254
+
255
+ mimicSelectFile(el, [fileWrongType]);
256
+ await el.updateComplete;
257
+
258
+ // @ts-expect-error [allow-protected-in-test]
259
+ el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
260
+ expect(error.message).to.deep.equal('Please select a .html or .csv file with max 500MB.');
261
+ });
262
+ });
263
+
264
+ it('can reflect multiple types in the error message with preference to extensions', async () => {
265
+ const el = await fixture(html`
266
+ <lion-input-file label="Select" accept=".jpg,image/svg+xml"></lion-input-file>
267
+ `);
268
+
269
+ mimicSelectFile(el, [fileWrongType]);
270
+ await el.updateComplete;
271
+
272
+ // @ts-expect-error [allow-protected-in-test]
273
+ el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
274
+ expect(error.message).to.deep.equal('Please select a(n) .jpg file with max 500MB.');
275
+ });
276
+ });
277
+ });
278
+
279
+ describe('invalid file extensions', async () => {
280
+ const fileWrongType = /** @type {InputFile} */ (
281
+ new File(['foobar'], 'foobar.txt', {
282
+ type: 'xxxxx',
283
+ })
284
+ );
285
+
286
+ it('should not be added to the selected list', async () => {
287
+ const el = await fixture(html`
288
+ <lion-input-file label="Select" accept=".jpg, .png, .pdf"></lion-input-file>
289
+ `);
290
+
291
+ mimicSelectFile(el, [fileWrongType]);
292
+ await el.updateComplete;
293
+
294
+ // @ts-expect-error [allow-protected-in-test]
295
+ expect(el._selectedFilesMetaData.length).to.equal(1);
296
+ // @ts-expect-error [allow-protected-in-test]
297
+ expect(el._selectedFilesMetaData[0].failedProp?.length).to.equal(1);
298
+ // @ts-expect-error [allow-protected-in-test]
299
+ expect(el._selectedFilesMetaData[0].validationFeedback).to.exist;
300
+ // @ts-expect-error [allow-protected-in-test]
301
+ expect(el._selectedFilesMetaData[0].status).to.equal('FAIL');
302
+ expect(el.uploadResponse[0].status).to.equal('FAIL');
303
+ });
304
+
305
+ it('error message should add the file extension to the validator message', async () => {
306
+ const el = await fixture(html`
307
+ <lion-input-file label="Select" accept=".jpg"></lion-input-file>
308
+ `);
309
+
310
+ mimicSelectFile(el, [fileWrongType]);
311
+ await el.updateComplete;
312
+
313
+ // @ts-expect-error [allow-protected-in-test]
314
+ el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
315
+ expect(error.message).to.equal('Please select a(n) .jpg file with max 500MB.');
316
+ });
317
+ });
318
+
319
+ it('error message should add all file extensions to the validator message', async () => {
320
+ const el = await fixture(html`
321
+ <lion-input-file label="Select" accept=".jpg, .png, .pdf"></lion-input-file>
322
+ `);
323
+
324
+ mimicSelectFile(el, [fileWrongType]);
325
+ await el.updateComplete;
326
+
327
+ // @ts-expect-error [allow-protected-in-test]
328
+ el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
329
+ expect(error.message).to.equal('Please select a .jpg, .png or .pdf file with max 500MB.');
330
+ });
331
+ });
332
+
333
+ it('error message should add all file extensions to the validator message also works without spaces " "', async () => {
334
+ const el = await fixture(html`
335
+ <lion-input-file label="Select" accept=".jpg,.png,.pdf"></lion-input-file>
336
+ `);
337
+
338
+ mimicSelectFile(el, [fileWrongType]);
339
+ await el.updateComplete;
340
+
341
+ // @ts-expect-error [allow-protected-in-test]
342
+ el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
343
+ expect(error.message).to.equal('Please select a .jpg, .png or .pdf file with max 500MB.');
344
+ });
345
+ });
346
+
347
+ it('error message should add all file extensions to the validator message also works without dots "."', async () => {
348
+ const el = await fixture(html`
349
+ <lion-input-file label="Select" accept="jpg, png, pdf"></lion-input-file>
350
+ `);
351
+
352
+ mimicSelectFile(el, [fileWrongType]);
353
+ await el.updateComplete;
354
+
355
+ // @ts-expect-error [allow-protected-in-test]
356
+ el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
357
+ expect(error.message).to.equal('Please select a .jpg, .png or .pdf file with max 500MB.');
358
+ });
359
+ });
360
+ });
361
+
362
+ describe('invalid file sizes', async () => {
363
+ // Size of this file is 4 bytes
364
+ const fileWrongSize = /** @type {InputFile} */ (new File(['foobar'], 'foobar.txt'));
365
+
366
+ it('should not be added to the selected list', async () => {
367
+ const el = await fixture(html` <lion-input-file max-file-size="2"></lion-input-file> `);
368
+
369
+ mimicSelectFile(el, [fileWrongSize]);
370
+ await el.updateComplete;
371
+
372
+ // @ts-expect-error [allow-protected-in-test]
373
+ expect(el._selectedFilesMetaData.length).to.equal(1);
374
+ // @ts-expect-error [allow-protected-in-test]
375
+ expect(el._selectedFilesMetaData[0].failedProp?.length).to.equal(1);
376
+ // @ts-expect-error [allow-protected-in-test]
377
+ expect(el._selectedFilesMetaData[0].validationFeedback).to.exist;
378
+ // @ts-expect-error [allow-protected-in-test]
379
+ expect(el._selectedFilesMetaData[0].status).to.equal('FAIL');
380
+ expect(el.uploadResponse[0].status).to.equal('FAIL');
381
+ });
382
+
383
+ it('error message should show only the max file size if no type/extension restrictions are defined', async () => {
384
+ const el = await fixture(html`
385
+ <lion-input-file label="Select" max-file-size="2"></lion-input-file>
386
+ `);
387
+
388
+ mimicSelectFile(el, [fileWrongSize]);
389
+ await el.updateComplete;
390
+
391
+ // @ts-expect-error [allow-protected-in-test]
392
+ el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
393
+ expect(error.message).to.equal('Please select a file with max 2 bytes.');
394
+ });
395
+ });
396
+
397
+ it('error message should show the correct max file size if type/extension restrictions are defined', async () => {
398
+ const el = await fixture(html`
399
+ <lion-input-file
400
+ label="Select"
401
+ max-file-size="2"
402
+ accept=".jpg, .png, .pdf"
403
+ ></lion-input-file>
404
+ `);
405
+
406
+ mimicSelectFile(el, [fileWrongSize]);
407
+ await el.updateComplete;
408
+
409
+ // @ts-expect-error [allow-protected-in-test]
410
+ el._selectedFilesMetaData[0].validationFeedback?.forEach(error => {
411
+ expect(error.message).to.equal('Please select a .jpg, .png or .pdf file with max 2 bytes.');
412
+ });
413
+ });
414
+ });
415
+
416
+ it('should send "file-list-changed" event if selecting files succeeded partially', async () => {
417
+ const el = await fixture(html`<lion-input-file multiple max-file-size="3"></lion-input-file>`);
418
+
419
+ // Size of this file is 4 bytes
420
+ const fileWrongSize = /** @type {InputFile} */ (new File(['foobar'], 'foobar.txt'));
421
+
422
+ setTimeout(() => {
423
+ mimicSelectFile(el, [fileWrongSize, file2]);
424
+ });
425
+
426
+ const fileListChangedEvent = await oneEvent(el, 'file-list-changed');
427
+
428
+ // @ts-expect-error [allow-protected-in-test]
429
+ expect(el._selectedFilesMetaData.length).to.equal(2);
430
+ // @ts-expect-error [allow-protected-in-test]
431
+ expect(el._selectedFilesMetaData[0].status).to.equal('FAIL');
432
+ expect(el.uploadResponse[0].status).to.equal('FAIL');
433
+
434
+ expect(fileListChangedEvent.detail.newFiles.length).to.equal(1);
435
+ });
436
+
437
+ it('should update downloadurl for successful files', async () => {
438
+ const el = await fixture(html`<lion-input-file></lion-input-file>`);
439
+
440
+ setTimeout(() => {
441
+ mimicSelectFile(el, [file]);
442
+ });
443
+ const fileListChangedEvent = await oneEvent(el, 'file-list-changed');
444
+ filesListChanged(el, fileListChangedEvent);
445
+
446
+ // @ts-expect-error [allow-protected-in-test]
447
+ expect(el._selectedFilesMetaData[0].downloadUrl).to.exist;
448
+ });
449
+
450
+ describe('format', () => {
451
+ it('modelValue is an array of files', async () => {
452
+ const el = await fixture(html` <lion-input-file name="select"></lion-input-file> `);
453
+
454
+ mimicSelectFile(el, [file]);
455
+ await el.updateComplete;
456
+ expect(el.modelValue).to.deep.equal([file]);
457
+ });
458
+
459
+ it('view value is a string', async () => {
460
+ const el = await fixture(html` <lion-input-file name="select"></lion-input-file> `);
461
+
462
+ mimicSelectFile(el, [file]);
463
+ await el.updateComplete;
464
+
465
+ expect(el.value).to.equal('C:\\fakepath\\foo.txt');
466
+ // @ts-expect-error [allow-protected-in-tests]
467
+ expect(el._inputNode.value).to.equal('C:\\fakepath\\foo.txt');
468
+ });
469
+
470
+ it('formattedValue is a string', async () => {
471
+ const el = await fixture(html` <lion-input-file name="select"></lion-input-file> `);
472
+
473
+ mimicSelectFile(el, [file]);
474
+ await el.updateComplete;
475
+
476
+ expect(el.formattedValue).to.equal('C:\\fakepath\\foo.txt');
477
+ });
478
+
479
+ it('serializedValue is an array of files', async () => {
480
+ const el = await fixture(html` <lion-input-file name="select"></lion-input-file> `);
481
+
482
+ mimicSelectFile(el, [file]);
483
+ await el.updateComplete;
484
+ expect(el.serializedValue).to.deep.equal([file]);
485
+ });
486
+
487
+ it('fires `model-value-changed` for every programmatic modelValue change', async () => {
488
+ const el = await fixture(html` <lion-input-file name="select"></lion-input-file> `);
489
+ let counter = 0;
490
+ let isTriggeredByUser = false;
491
+
492
+ el.addEventListener('model-value-changed', event => {
493
+ counter += 1;
494
+ isTriggeredByUser = /** @type {CustomEvent} */ (event).detail.isTriggeredByUser;
495
+ });
496
+
497
+ el.modelValue = [file];
498
+ expect(counter).to.equal(1);
499
+ expect(isTriggeredByUser).to.be.false;
500
+
501
+ // TODO: should no change mean no event?
502
+ // el.modelValue = [file];
503
+ // expect(counter).to.equal(1);
504
+
505
+ el.modelValue = [file, file2];
506
+ expect(counter).to.equal(2);
507
+ });
508
+
509
+ it('fires `model-value-changed` for every user input, adding `isTriggeredByUser` in event detail', async () => {
510
+ const el = await fixture(html` <lion-input-file name="select"></lion-input-file> `);
511
+ let counter = 0;
512
+ let isTriggeredByUser = false;
513
+
514
+ el.addEventListener('model-value-changed', event => {
515
+ counter += 1;
516
+ isTriggeredByUser = /** @type {CustomEvent} */ (event).detail.isTriggeredByUser;
517
+ });
518
+
519
+ mimicSelectFile(el, [file]);
520
+ await el.updateComplete;
521
+ expect(counter).to.equal(1);
522
+ expect(isTriggeredByUser).to.be.true;
523
+
524
+ mimicSelectFile(el, [file, file2]);
525
+ await el.updateComplete;
526
+ expect(counter).to.equal(2);
527
+ });
528
+ });
529
+
530
+ describe('multiple file select', () => {
531
+ it('should add multiple files', async () => {
532
+ const el = await fixture(html`
533
+ <lion-input-file label="Select" .multiple="${true}"> </lion-input-file>
534
+ `);
535
+
536
+ mimicSelectFile(el, [file, file2]);
537
+
538
+ await el.updateComplete;
539
+ // @ts-expect-error [allow-protected-in-test]
540
+ expect(el._selectedFilesMetaData.length).to.equal(2);
541
+ // @ts-expect-error [allow-protected-in-test]
542
+ expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('foo.txt');
543
+ // @ts-expect-error [allow-protected-in-test]
544
+ expect(el._selectedFilesMetaData[1].systemFile.name).to.equal('bar.txt');
545
+
546
+ expect(el.uploadResponse.length).to.equal(2);
547
+ expect(el.uploadResponse[0].name).to.equal('foo.txt');
548
+ expect(el.uploadResponse[1].name).to.equal('bar.txt');
549
+ });
550
+
551
+ it('should add new files and retain previous files', async () => {
552
+ const el = await fixture(html` <lion-input-file .multiple="${true}"></lion-input-file> `);
553
+
554
+ setTimeout(() => {
555
+ mimicSelectFile(el, [file, file2]);
556
+ });
557
+ const fileListChangedEvent = await oneEvent(el, 'file-list-changed');
558
+ filesListChanged(el, fileListChangedEvent);
559
+ await el.updateComplete;
560
+
561
+ // @ts-expect-error [allow-protected-in-test]
562
+ expect(el._selectedFilesMetaData.length).to.equal(2);
563
+
564
+ setTimeout(() => {
565
+ mimicSelectFile(el, [file3, file4]);
566
+ });
567
+ const fileListChangedEvent1 = await oneEvent(el, 'file-list-changed');
568
+ filesListChanged(el, fileListChangedEvent1);
569
+ await el.updateComplete;
570
+ // @ts-expect-error [allow-protected-in-test]
571
+ expect(el._selectedFilesMetaData.length).to.equal(4);
572
+ });
573
+
574
+ it('should add multiple files and dispatch file-list-changed event ONLY with newly added file', async () => {
575
+ const el = await fixture(html` <lion-input-file .multiple="${true}"></lion-input-file> `);
576
+
577
+ setTimeout(() => {
578
+ mimicSelectFile(el, [file, file2]);
579
+ });
580
+ const fileListChangedEvent = await oneEvent(el, 'file-list-changed');
581
+ filesListChanged(el, fileListChangedEvent);
582
+
583
+ setTimeout(() => {
584
+ mimicSelectFile(el, [file3, file4]);
585
+ });
586
+
587
+ const fileListChangedEvent1 = await oneEvent(el, 'file-list-changed');
588
+ filesListChanged(el, fileListChangedEvent1);
589
+
590
+ expect(fileListChangedEvent1).to.exist;
591
+ // @ts-expect-error [allow-protected-in-test]
592
+ expect(el._selectedFilesMetaData.length).to.equal(4);
593
+ expect(fileListChangedEvent1.detail.newFiles.length).to.equal(2);
594
+ expect(fileListChangedEvent1.detail.newFiles[0].name).to.equal('foo3.txt');
595
+ expect(fileListChangedEvent1.detail.newFiles[1].name).to.equal('foo4.txt');
596
+ });
597
+
598
+ it('should not allow duplicate files to be selected and show notification message', async () => {
599
+ const el = await fixture(html` <lion-input-file .multiple="${true}"></lion-input-file> `);
600
+
601
+ setTimeout(() => {
602
+ mimicSelectFile(el, [file]);
603
+ });
604
+
605
+ // create condition to also show the feedback
606
+ el.prefilled = true;
607
+ const fileListChangedEvent = await oneEvent(el, 'file-list-changed');
608
+
609
+ // @ts-expect-error [allow-protected-in-test]
610
+ expect(el._selectedFilesMetaData.length).to.equal(1);
611
+ // @ts-expect-error [allow-protected-in-test]
612
+ expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('foo.txt');
613
+
614
+ expect(fileListChangedEvent).to.exist;
615
+ expect(fileListChangedEvent.detail.newFiles.length).to.equal(1);
616
+ expect(fileListChangedEvent.detail.newFiles[0].name).to.equal('foo.txt');
617
+
618
+ const fileDuplicate = /** @type {InputFile} */ (
619
+ new File(['foo'], 'foo.txt', {
620
+ type: 'text/plain',
621
+ })
622
+ );
623
+
624
+ mimicSelectFile(el, [fileDuplicate]);
625
+ await el.updateComplete;
626
+ // @ts-expect-error [allow-protected-in-test]
627
+ expect(el._selectedFilesMetaData.length).to.equal(1);
628
+ expect(el.hasFeedbackFor).to.deep.equals(['info'], 'hasFeedbackFor');
629
+ expect(el.showsFeedbackFor).to.deep.equals(['info'], 'showsFeedbackFor');
630
+ });
631
+
632
+ it('should add valid files and skip invalid ones', async () => {
633
+ const fileWrongSize = /** @type {InputFile} */ (new File(['foobar'], 'foobar.txt'));
634
+ const fileWrongType = /** @type {InputFile} */ (
635
+ new File(['foobar'], 'foobar.txt', {
636
+ type: 'xxxxx',
637
+ })
638
+ );
639
+
640
+ const el = await fixture(
641
+ html` <lion-input-file multiple max-file-size="3" accept=".txt"></lion-input-file> `,
642
+ );
643
+
644
+ setTimeout(() => {
645
+ mimicSelectFile(el, [file, fileWrongSize, file2, fileWrongType]);
646
+ });
647
+
648
+ const fileListChangedEvent = await oneEvent(el, 'file-list-changed');
649
+ // @ts-expect-error [allow-protected-in-test]
650
+ expect(el._selectedFilesMetaData.length).to.equal(4);
651
+ expect(fileListChangedEvent).to.exist;
652
+ expect(fileListChangedEvent.detail.newFiles.length).to.equal(2);
653
+ expect(fileListChangedEvent.detail.newFiles[0].name).to.equal('foo.txt');
654
+ expect(fileListChangedEvent.detail.newFiles[1].name).to.equal('bar.txt');
655
+ });
656
+ });
657
+
658
+ describe('status and error', () => {
659
+ /**
660
+ * @type {LionInputFile}
661
+ */
662
+ let el;
663
+ beforeEach(async () => {
664
+ el = await fixture(html`<lion-input-file accept="text/plain"></lion-input-file>`);
665
+ });
666
+
667
+ it('should set uploadResponse data to _selectedFilesMetaData for rendering error and status', async () => {
668
+ mimicSelectFile(el, [file]);
669
+
670
+ el.uploadResponse = [{ name: 'foo.txt', status: 'LOADING', errorMessage: '500' }];
671
+
672
+ await el.updateComplete;
673
+
674
+ // @ts-expect-error [allow-protected-in-test]
675
+ expect(el._selectedFilesMetaData[0].status).to.equal('LOADING');
676
+ // @ts-ignore
677
+ expect(el._selectedFilesMetaData[0].validationFeedback[0].message).to.equal('500');
678
+ });
679
+
680
+ it('should not fire file-list-changed event if invalid file is selected', async () => {
681
+ const filePdf = /** @type {InputFile} */ (
682
+ new File(['foo'], 'foo.pdf', {
683
+ type: 'application/pdf',
684
+ })
685
+ );
686
+ // @ts-ignore
687
+ const fileListChangedSpy = sinon.spy(el, '_dispatchFileListChangeEvent');
688
+
689
+ mimicSelectFile(el, [filePdf]);
690
+
691
+ expect(fileListChangedSpy).have.not.been.called;
692
+ fileListChangedSpy.restore();
693
+ });
694
+
695
+ it('updates showFeedbackFor when a wrong file has been added/removed', async () => {
696
+ const fileWrongType = /** @type {InputFile} */ (
697
+ new File(['foobar'], 'foobar.txt', {
698
+ type: 'xxxxx',
699
+ })
700
+ );
701
+
702
+ // create condition to also show the feedback
703
+ el.prefilled = true;
704
+
705
+ mimicSelectFile(el, [fileWrongType]);
706
+ await el.updateComplete;
707
+ expect(el.hasFeedbackFor).to.deep.equals(['error'], 'hasFeedbackFor');
708
+ expect(el.showsFeedbackFor).to.deep.equals(['error'], 'showsFeedbackFor');
709
+
710
+ // on change the showsFeedbackFor should be empty again
711
+ mimicSelectFile(el, [file]);
712
+ await el.updateComplete;
713
+ expect(el.hasFeedbackFor).to.deep.equals([]);
714
+ expect(el.showsFeedbackFor).to.deep.equals([]);
715
+ });
716
+ });
717
+
718
+ describe('validations when used with lion-form', () => {
719
+ it('should throw error with Required validator', async () => {
720
+ const el = await fixture(html`
721
+ <lion-input-file
722
+ name="myFileInput"
723
+ .validators="${[new Required({}, { getMessage: () => 'FooBar' })]}"
724
+ >
725
+ </lion-input-file>
726
+ `);
727
+
728
+ el.touched = true;
729
+ el.dirty = true;
730
+ await el.updateComplete;
731
+ await el.feedbackComplete;
732
+ // @ts-ignore input type="file" is a specific input member
733
+ const { _feedbackNode } = getInputMembers(el);
734
+ expect(_feedbackNode.feedbackData?.[0].message).to.equal('FooBar');
735
+ expect(el.hasFeedbackFor.includes('error')).to.be.true;
736
+ });
737
+
738
+ it('reset method should remove File from modelValue but keep uploadResponse', async () => {
739
+ const uploadResponse = [
740
+ {
741
+ name: 'file1.txt',
742
+ status: 'SUCCESS',
743
+ errorMessage: '',
744
+ downloadUrl: '/downloadFile',
745
+ },
746
+ ];
747
+ const el = await fixture(html`
748
+ <lion-input-file label="Select" .uploadResponse="${uploadResponse}"></lion-input-file>
749
+ `);
750
+
751
+ // @ts-expect-error [allow-protected-in-test]
752
+ el.uploadResponse = uploadResponse;
753
+ await el.updateComplete;
754
+
755
+ mimicSelectFile(el, [file]);
756
+ await el.updateComplete;
757
+
758
+ await el.reset();
759
+ expect(el.modelValue).to.deep.equal([]);
760
+ // @ts-expect-error [allow-protected-in-test]
761
+ expect(el._selectedFilesMetaData).to.deep.equal([]);
762
+ expect(el.uploadResponse).to.deep.equal(uploadResponse);
763
+ });
764
+
765
+ it('clear method should remove File from modelValue and uploadResponse', async () => {
766
+ const uploadResponse = [
767
+ {
768
+ name: 'file1.txt',
769
+ status: 'SUCCESS',
770
+ errorMessage: '',
771
+ downloadUrl: '/downloadFile',
772
+ },
773
+ ];
774
+ const el = await fixture(html` <lion-input-file label="Select"></lion-input-file> `);
775
+
776
+ // @ts-expect-error [allow-protected-in-test]
777
+ el.uploadResponse = uploadResponse;
778
+
779
+ setTimeout(() => {
780
+ mimicSelectFile(el, [file]);
781
+ });
782
+ await oneEvent(el, 'file-list-changed');
783
+ await el.clear();
784
+ expect(el.modelValue).to.deep.equal([]);
785
+ expect(el.uploadResponse).to.deep.equal([]);
786
+ // @ts-expect-error [allow-protected-in-test]
787
+ expect(el._selectedFilesMetaData).to.deep.equal([]);
788
+ });
789
+ });
790
+
791
+ describe('file select component with prefilled state', () => {
792
+ /**
793
+ * @type {LionInputFile}
794
+ */
795
+ let el;
796
+ const uploadResponse = [
797
+ {
798
+ name: 'file1.txt',
799
+ status: 'SUCCESS',
800
+ errorMessage: '',
801
+ downloadUrl: '/downloadFile',
802
+ },
803
+ {
804
+ name: 'file2.txt',
805
+ status: 'FAIL',
806
+ errorMessage: 'something went wrong',
807
+ },
808
+ ];
809
+
810
+ beforeEach(async () => {
811
+ el = await fixture(html`
812
+ <lion-input-file name="myFiles" multiple="${true}"> </lion-input-file>
813
+ `);
814
+
815
+ // @ts-expect-error [allow-protected-in-test]
816
+ el.uploadResponse = uploadResponse;
817
+
818
+ await el.updateComplete;
819
+ });
820
+
821
+ it('should update the _selectedFilesMetaData according to uploadResponse', () => {
822
+ // @ts-expect-error [allow-protected-in-test]
823
+ expect(el._selectedFilesMetaData.length).to.equal(2);
824
+ // @ts-expect-error [allow-protected-in-test]
825
+ expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('file1.txt');
826
+ // @ts-expect-error [allow-protected-in-test]
827
+ expect(el._selectedFilesMetaData[0].status).to.equal('SUCCESS');
828
+ // @ts-expect-error [allow-protected-in-test]
829
+ expect(el._selectedFilesMetaData[0].downloadUrl).to.equal('/downloadFile');
830
+ // @ts-expect-error [allow-protected-in-test]
831
+ expect(el._selectedFilesMetaData[1].validationFeedback[0].message).to.equal(
832
+ 'something went wrong',
833
+ );
834
+ });
835
+
836
+ it('should remove file on click of cross button', async () => {
837
+ /**
838
+ * @type {Partial<InputFile>}
839
+ */
840
+ const removedFile = {
841
+ name: 'file2.txt',
842
+ status: 'FAIL',
843
+ systemFile: { name: 'file2.txt' },
844
+ response: { name: 'file2.txt', status: 'FAIL' },
845
+ };
846
+ // @ts-expect-error [allow-protected-in-test]
847
+ const removeFileSpy = sinon.spy(el, '_removeFile');
848
+
849
+ // assertion for displayed file list to be same
850
+ // @ts-expect-error [allow-protected-in-test]
851
+ expect(el._selectedFilesMetaData.length).to.equal(2);
852
+ // @ts-expect-error [allow-protected-in-test]
853
+ expect(el._selectedFilesMetaData[1].systemFile.name).to.equal('file2.txt');
854
+
855
+ el.dispatchEvent(
856
+ new CustomEvent('file-remove-requested', {
857
+ composed: true,
858
+ bubbles: true,
859
+ detail: {
860
+ removedFile,
861
+ status: removedFile.status,
862
+ uploadResponse: removedFile.response,
863
+ },
864
+ }),
865
+ );
866
+
867
+ await el.updateComplete;
868
+ expect(removeFileSpy).have.been.calledOnce;
869
+ // @ts-expect-error [allow-protected-in-test]
870
+ expect(el._selectedFilesMetaData.length).to.equal(1);
871
+ removeFileSpy.restore();
872
+ });
873
+
874
+ it('should fire file-removed event with uploadResponse in the details', async () => {
875
+ /**
876
+ * @type {Partial<InputFile>}
877
+ */
878
+ const removedFile = {
879
+ name: 'file2.txt',
880
+ status: 'FAIL',
881
+ systemFile: { name: 'file2.txt' },
882
+ response: { name: 'file2.txt', status: 'FAIL' },
883
+ };
884
+
885
+ setTimeout(() => {
886
+ // @ts-ignore ignore file typing
887
+ el._removeFile(removedFile);
888
+ });
889
+
890
+ await el.updateComplete;
891
+
892
+ const removeFileEvent = await oneEvent(el, 'file-removed');
893
+
894
+ // assertion for event data
895
+ expect(removeFileEvent).to.exist;
896
+ expect(removeFileEvent.detail.removedFile).to.deep.equal({
897
+ name: 'file2.txt',
898
+ status: 'FAIL',
899
+ systemFile: {
900
+ name: 'file2.txt',
901
+ },
902
+ response: {
903
+ name: 'file2.txt',
904
+ status: 'FAIL',
905
+ },
906
+ });
907
+ expect(removeFileEvent.detail.status).to.deep.equal('FAIL');
908
+ expect(removeFileEvent.detail.uploadResponse).to.deep.equal({
909
+ name: 'file2.txt',
910
+ status: 'FAIL',
911
+ });
912
+ });
913
+ });
914
+
915
+ describe('drag and drop', () => {
916
+ /**
917
+ * @type {LionInputFile}
918
+ */
919
+ let el;
920
+ beforeEach(async () => {
921
+ el = await fixture(html`
922
+ <lion-input-file name="myFiles" multiple enable-drop-zone></lion-input-file>
923
+ `);
924
+
925
+ await el.updateComplete;
926
+ });
927
+
928
+ it('should set "is-dragging" on dragenter', async () => {
929
+ const dropzone = el.shadowRoot?.querySelector('.input-file__drop-zone');
930
+ dropzone?.dispatchEvent(new Event('dragenter', { bubbles: true }));
931
+ expect(el.hasAttribute('is-dragging')).to.equal(true);
932
+ });
933
+
934
+ it('should set "is-dragging" on dragover', async () => {
935
+ const dropzone = el.shadowRoot?.querySelector('.input-file__drop-zone');
936
+ dropzone?.dispatchEvent(new Event('dragover', { bubbles: true }));
937
+ expect(el.hasAttribute('is-dragging')).to.equal(true);
938
+ });
939
+
940
+ it('should remove "is-dragging" on dragleave', async () => {
941
+ const dropzone = el.shadowRoot?.querySelector('.input-file__drop-zone');
942
+ dropzone?.dispatchEvent(new Event('dragenter', { bubbles: true }));
943
+ await el.updateComplete;
944
+
945
+ dropzone?.dispatchEvent(new Event('dragleave', { bubbles: true }));
946
+ expect(el.hasAttribute('is-dragging')).to.equal(false);
947
+ });
948
+
949
+ it('should remove "is-dragging" on drop', async () => {
950
+ const dropzone = el.shadowRoot?.querySelector('.input-file__drop-zone');
951
+ dropzone?.dispatchEvent(new Event('dragenter', { bubbles: true }));
952
+ await el.updateComplete;
953
+
954
+ window.dispatchEvent(new Event('drop', { bubbles: true }));
955
+ expect(el.hasAttribute('is-dragging')).to.equal(false);
956
+ });
957
+
958
+ it('should call _processFiles method', async () => {
959
+ const list = new DataTransfer();
960
+ // @ts-ignore
961
+ list.items.add(file);
962
+ const droppedFiles = list.files;
963
+ // @ts-ignore
964
+ const _processFilesSpy = sinon.spy(el, '_processFiles');
965
+
966
+ // @ts-expect-error [allow-protected-in-test]
967
+ await el._processDroppedFiles({
968
+ // @ts-ignore
969
+ dataTransfer: { files: droppedFiles, items: [{ name: 'test.txt' }] },
970
+ preventDefault: () => {},
971
+ });
972
+
973
+ expect(_processFilesSpy).have.been.calledOnce;
974
+ _processFilesSpy.restore();
975
+ });
976
+ });
977
+
978
+ describe('uploadOnSelect as true', () => {
979
+ it('should not remove file on click of cross button and fire file-removed event', async () => {
980
+ /**
981
+ * @type {Partial<InputFile>}
982
+ */
983
+ const removeFile = {
984
+ name: 'foo.txt',
985
+ status: 'SUCCESS',
986
+ systemFile: { name: 'foo.txt' },
987
+ response: { name: 'foo.txt', status: 'SUCCESS' },
988
+ };
989
+
990
+ const el = await fixture(html`
991
+ <lion-input-file .uploadOnSelect="${true}"> </lion-input-file>
992
+ `);
993
+
994
+ el.modelValue = [file];
995
+ mimicSelectFile(el, [file]);
996
+
997
+ await el.updateComplete;
998
+
999
+ setTimeout(() => {
1000
+ // @ts-ignore ignore file typing
1001
+ el._removeFile(removeFile);
1002
+ });
1003
+
1004
+ const removeFileEvent = await oneEvent(el, 'file-removed');
1005
+
1006
+ // assertion for displayed file list to be same
1007
+ // @ts-expect-error [allow-protected-in-test]
1008
+ expect(el._selectedFilesMetaData.length).to.equal(1);
1009
+ // @ts-expect-error [allow-protected-in-test]
1010
+ expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('foo.txt');
1011
+
1012
+ // assertion for event data
1013
+ expect(removeFileEvent).to.exist;
1014
+ expect(removeFileEvent.detail.removedFile).to.deep.equal({
1015
+ name: 'foo.txt',
1016
+ status: 'SUCCESS',
1017
+ systemFile: {
1018
+ name: 'foo.txt',
1019
+ },
1020
+ response: {
1021
+ name: 'foo.txt',
1022
+ status: 'SUCCESS',
1023
+ },
1024
+ });
1025
+ expect(removeFileEvent.detail.status).to.deep.equal('SUCCESS');
1026
+ expect(removeFileEvent.detail.uploadResponse).to.deep.equal({
1027
+ name: 'foo.txt',
1028
+ status: 'SUCCESS',
1029
+ });
1030
+ });
1031
+
1032
+ it('should remove file from displayed list if not available in uploadResponse array', async () => {
1033
+ const uploadResponse = [
1034
+ {
1035
+ name: 'file1.txt',
1036
+ status: 'SUCCESS',
1037
+ errorMessage: '',
1038
+ downloadUrl: '/downloadFile',
1039
+ },
1040
+ {
1041
+ name: 'file2.txt',
1042
+ status: 'FAIL',
1043
+ errorMessage: 'something went wrong',
1044
+ },
1045
+ ];
1046
+
1047
+ const el = await fixture(html`
1048
+ <lion-input-file label="Select" .uploadOnSelect="${true}"> </lion-input-file>
1049
+ `);
1050
+
1051
+ // @ts-expect-error [allow-protected-in-test]
1052
+ el.uploadResponse = uploadResponse;
1053
+
1054
+ await el.updateComplete;
1055
+ // assertion for displayed file list to be same
1056
+ // @ts-expect-error [allow-protected-in-test]
1057
+ expect(el._selectedFilesMetaData.length).to.equal(2);
1058
+ el.uploadResponse = [
1059
+ {
1060
+ name: 'file1.txt',
1061
+ status: 'SUCCESS',
1062
+ errorMessage: '',
1063
+ downloadUrl: '/downloadFile',
1064
+ },
1065
+ ];
1066
+ await el.updateComplete;
1067
+ // assertion for displayed file list to be same
1068
+ // @ts-expect-error [allow-protected-in-test]
1069
+ expect(el._selectedFilesMetaData.length).to.equal(1);
1070
+ // @ts-expect-error [allow-protected-in-test]
1071
+ expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('file1.txt');
1072
+ // @ts-expect-error [allow-protected-in-tests]
1073
+ expect(el._inputNode.value).to.equal('');
1074
+ });
1075
+ });
1076
+
1077
+ describe('Accessibility', async () => {
1078
+ it('is accessible', async () => {
1079
+ const el = await fixture(html`<lion-input-file label="Select"></lion-input-file>`);
1080
+ await expect(el).to.be.accessible();
1081
+ });
1082
+
1083
+ it('is accessible when a file is selected', async () => {
1084
+ const uploadResponse = [
1085
+ {
1086
+ name: 'file1.txt',
1087
+ status: 'SUCCESS',
1088
+ errorMessage: '',
1089
+ downloadUrl: '/downloadFile',
1090
+ },
1091
+ ];
1092
+ const el = await fixture(html` <lion-input-file label="Select"></lion-input-file>`);
1093
+
1094
+ // @ts-expect-error [allow-protected-in-test]
1095
+ el.uploadResponse = uploadResponse;
1096
+
1097
+ await expect(el).to.be.accessible();
1098
+ });
1099
+
1100
+ it('is accessible with an selected file and disabled', async () => {
1101
+ const uploadResponse = [
1102
+ {
1103
+ name: 'file1.txt',
1104
+ status: 'SUCCESS',
1105
+ errorMessage: '',
1106
+ downloadUrl: '/downloadFile',
1107
+ },
1108
+ ];
1109
+
1110
+ const el = await fixture(html` <lion-input-file label="Select" disabled></lion-input-file> `);
1111
+
1112
+ // @ts-expect-error [allow-protected-in-test]
1113
+ el.uploadResponse = uploadResponse;
1114
+
1115
+ await expect(el).to.be.accessible();
1116
+ });
1117
+
1118
+ describe('has correct aria-roles', async () => {
1119
+ it('select-button has aria-labelledby set to itself and the label', async () => {
1120
+ const el = await fixture(html` <lion-input-file label="Select"></lion-input-file> `);
1121
+ // @ts-expect-error [allow-protected-in-test]
1122
+ expect(el._buttonNode?.getAttribute('aria-labelledby')).to.contain(
1123
+ // @ts-expect-error [allow-protected-in-test]
1124
+ `select-button-${el._inputId}`,
1125
+ );
1126
+ // @ts-expect-error [allow-protected-in-test]
1127
+ expect(el._buttonNode?.getAttribute('aria-labelledby')).to.contain(
1128
+ // @ts-expect-error [allow-protected-in-test]
1129
+ `label-${el._inputId}`,
1130
+ );
1131
+ });
1132
+
1133
+ it('select-button has aria-describedby set to the help-text, selected list and the feedback message', async () => {
1134
+ const uploadResponse = [
1135
+ {
1136
+ name: 'file1.txt',
1137
+ status: 'SUCCESS',
1138
+ errorMessage: '',
1139
+ downloadUrl: '/downloadFile',
1140
+ },
1141
+ ];
1142
+ const el = await fixture(html`
1143
+ <lion-input-file label="Select" help-text="foo"></lion-input-file>
1144
+ `);
1145
+
1146
+ // @ts-expect-error [allow-protected-in-test]
1147
+ el.uploadResponse = uploadResponse;
1148
+
1149
+ // @ts-expect-error [allow-protected-in-test]
1150
+ expect(el._buttonNode?.getAttribute('aria-describedby')).to.contain(
1151
+ // @ts-expect-error [allow-protected-in-test]
1152
+ `help-text-${el._inputId}`,
1153
+ );
1154
+ // @ts-expect-error [allow-protected-in-test]
1155
+ expect(el._buttonNode?.getAttribute('aria-describedby')).to.contain(
1156
+ // @ts-expect-error [allow-protected-in-test]
1157
+ `feedback-${el._inputId}`,
1158
+ );
1159
+ await el.updateComplete;
1160
+ // @ts-expect-error [allow-protected-in-test]
1161
+ expect(el._buttonNode?.getAttribute('aria-describedby')).to.contain(
1162
+ // @ts-expect-error [allow-protected-in-test]
1163
+ `selected-file-list-${el._inputId}`,
1164
+ );
1165
+ });
1166
+ });
1167
+ });
1168
+ });