@1doc/1ds-react 2.8.0 → 2.10.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 (287) hide show
  1. package/CHANGELOG.md +1540 -0
  2. package/dist/components/AILabel/AILabel.js +1 -1
  3. package/dist/components/Accordion/Accordion.css +13 -0
  4. package/dist/components/Accordion/Accordion.d.ts +9 -2
  5. package/dist/components/Accordion/Accordion.js +1 -0
  6. package/dist/components/AppSubheader/AppSubheader.css +10 -1
  7. package/dist/components/AttachmentCard/AttachmentCard.css +26 -8
  8. package/dist/components/AttachmentCard/AttachmentCard.d.ts +22 -2
  9. package/dist/components/AttachmentCard/AttachmentCard.js +47 -3
  10. package/dist/components/Avatar/Avatar.css +7 -2
  11. package/dist/components/Avatar/AvatarGroup.d.ts +17 -0
  12. package/dist/components/Avatar/AvatarGroup.js +22 -8
  13. package/dist/components/Badge/Badge.css +67 -2
  14. package/dist/components/Badge/Badge.d.ts +23 -4
  15. package/dist/components/Badge/Badge.js +4 -0
  16. package/dist/components/Box/Box.css +83 -0
  17. package/dist/components/Box/Box.d.ts +82 -2
  18. package/dist/components/Box/Box.js +42 -2
  19. package/dist/components/Button/Button.css +29 -0
  20. package/dist/components/Button/Button.d.ts +4 -4
  21. package/dist/components/Button/Button.js +4 -1
  22. package/dist/components/Calendar/Calendar.css +245 -0
  23. package/dist/components/Calendar/Calendar.d.ts +126 -0
  24. package/dist/components/Calendar/Calendar.js +320 -0
  25. package/dist/components/Chips/Chips.css +38 -0
  26. package/dist/components/Chips/Chips.d.ts +5 -2
  27. package/dist/components/Chips/Chips.js +2 -1
  28. package/dist/components/Coachmark/Coachmark.css +128 -0
  29. package/dist/components/Coachmark/Coachmark.d.ts +87 -0
  30. package/dist/components/Coachmark/Coachmark.js +362 -0
  31. package/dist/components/CodeBlock/CodeBlock.css +96 -0
  32. package/dist/components/CodeBlock/CodeBlock.d.ts +47 -0
  33. package/dist/components/CodeBlock/CodeBlock.js +103 -0
  34. package/dist/components/ColorPicker/ColorPicker.css +140 -0
  35. package/dist/components/ColorPicker/ColorPicker.d.ts +73 -0
  36. package/dist/components/ColorPicker/ColorPicker.js +219 -0
  37. package/dist/components/Combobox/Combobox.css +8 -0
  38. package/dist/components/DataTable/DataTable.css +254 -0
  39. package/dist/components/DataTable/DataTable.d.ts +81 -5
  40. package/dist/components/DataTable/DataTable.js +335 -19
  41. package/dist/components/DatePicker/DatePicker.css +8 -0
  42. package/dist/components/DatePicker/DatePicker.js +10 -1
  43. package/dist/components/DescriptionList/DescriptionList.css +127 -0
  44. package/dist/components/DescriptionList/DescriptionList.d.ts +62 -0
  45. package/dist/components/DescriptionList/DescriptionList.js +52 -0
  46. package/dist/components/EmptyState/EmptyState.d.ts +4 -2
  47. package/dist/components/EmptyState/EmptyState.js +2 -2
  48. package/dist/components/FileInput/FileInput.css +16 -0
  49. package/dist/components/FileInput/FileInput.d.ts +4 -2
  50. package/dist/components/FileInput/FileInput.js +8 -2
  51. package/dist/components/Grid/Grid.js +5 -5
  52. package/dist/components/IconPicker/IconPicker.css +107 -0
  53. package/dist/components/IconPicker/IconPicker.d.ts +74 -0
  54. package/dist/components/IconPicker/IconPicker.js +194 -0
  55. package/dist/components/Input/Input.css +59 -0
  56. package/dist/components/Input/Input.d.ts +17 -1
  57. package/dist/components/Input/Input.js +67 -5
  58. package/dist/components/Kanban/Kanban.css +4 -0
  59. package/dist/components/Kbd/Kbd.css +66 -0
  60. package/dist/components/Kbd/Kbd.d.ts +32 -0
  61. package/dist/components/Kbd/Kbd.js +40 -0
  62. package/dist/components/List/List.css +136 -0
  63. package/dist/components/List/List.d.ts +82 -0
  64. package/dist/components/List/List.js +53 -0
  65. package/dist/components/List/ListItem.d.ts +97 -0
  66. package/dist/components/List/ListItem.js +82 -0
  67. package/dist/components/Modal/Modal.css +80 -0
  68. package/dist/components/Modal/Modal.d.ts +33 -6
  69. package/dist/components/Modal/Modal.js +101 -6
  70. package/dist/components/MultiSelect/MultiSelect.css +8 -0
  71. package/dist/components/MultiSelect/MultiSelect.js +10 -1
  72. package/dist/components/OrgChart/OrgChart.css +149 -0
  73. package/dist/components/OrgChart/OrgChart.d.ts +83 -0
  74. package/dist/components/OrgChart/OrgChart.js +297 -0
  75. package/dist/components/OtpInput/OtpInput.css +93 -0
  76. package/dist/components/OtpInput/OtpInput.d.ts +60 -0
  77. package/dist/components/OtpInput/OtpInput.js +215 -0
  78. package/dist/components/PhoneInput/PhoneInput.css +8 -0
  79. package/dist/components/Popover/Popover.d.ts +7 -0
  80. package/dist/components/Popover/Popover.js +11 -1
  81. package/dist/components/RichTextEditor/RichTextEditor.css +250 -0
  82. package/dist/components/RichTextEditor/RichTextEditor.d.ts +97 -0
  83. package/dist/components/RichTextEditor/RichTextEditor.js +951 -0
  84. package/dist/components/SegmentedControl/SegmentedControl.css +24 -0
  85. package/dist/components/SegmentedControl/SegmentedControl.d.ts +2 -0
  86. package/dist/components/SegmentedControl/SegmentedControl.js +10 -2
  87. package/dist/components/Select/Select.css +164 -0
  88. package/dist/components/Select/Select.d.ts +34 -0
  89. package/dist/components/Select/Select.js +258 -28
  90. package/dist/components/Stack/Stack.css +49 -0
  91. package/dist/components/Stack/Stack.d.ts +65 -7
  92. package/dist/components/Stack/Stack.js +30 -2
  93. package/dist/components/StatCard/StatCard.css +138 -0
  94. package/dist/components/StatCard/StatCard.d.ts +26 -0
  95. package/dist/components/StatCard/StatCard.js +13 -1
  96. package/dist/components/Tabs/Tab.d.ts +15 -0
  97. package/dist/components/Tabs/Tab.js +30 -1
  98. package/dist/components/Tabs/Tabs.css +132 -0
  99. package/dist/components/Tabs/Tabs.d.ts +7 -0
  100. package/dist/components/Tabs/Tabs.js +13 -2
  101. package/dist/components/Tag/Tag.css +85 -2
  102. package/dist/components/Tag/Tag.d.ts +13 -6
  103. package/dist/components/Tag/Tag.js +1 -0
  104. package/dist/components/Text/Text.js +2 -2
  105. package/dist/components/Textarea/Textarea.css +8 -0
  106. package/dist/components/TimePicker/TimePicker.css +8 -0
  107. package/dist/components/Toast/Toast.css +32 -0
  108. package/dist/components/Toast/Toast.d.ts +18 -0
  109. package/dist/components/Toast/Toast.js +14 -1
  110. package/dist/components/Toast/ToastStack.js +8 -1
  111. package/dist/components/Tooltip/Tooltip.css +25 -0
  112. package/dist/components/Tooltip/Tooltip.d.ts +11 -0
  113. package/dist/components/Tooltip/Tooltip.js +18 -3
  114. package/dist/index.d.ts +25 -0
  115. package/dist/index.js +14 -0
  116. package/dist/teste/AILabel.test.d.ts +1 -0
  117. package/dist/teste/AILabel.test.js +33 -0
  118. package/dist/teste/AccordionConteudoLivre.test.d.ts +1 -0
  119. package/dist/teste/AccordionConteudoLivre.test.js +29 -0
  120. package/dist/teste/AccordionTituloNeutro.test.d.ts +1 -0
  121. package/dist/teste/AccordionTituloNeutro.test.js +23 -0
  122. package/dist/teste/AppSubheaderRegiao.test.d.ts +1 -0
  123. package/dist/teste/AppSubheaderRegiao.test.js +13 -0
  124. package/dist/teste/AtributosRepassados.test.d.ts +1 -0
  125. package/dist/teste/AtributosRepassados.test.js +52 -0
  126. package/dist/teste/AttachmentCardIA.test.d.ts +1 -0
  127. package/dist/teste/AttachmentCardIA.test.js +89 -0
  128. package/dist/teste/AttachmentCardLista.test.d.ts +1 -0
  129. package/dist/teste/AttachmentCardLista.test.js +49 -0
  130. package/dist/teste/AttachmentCardSm.test.d.ts +1 -0
  131. package/dist/teste/AttachmentCardSm.test.js +26 -0
  132. package/dist/teste/AvatarGroupOverlap.test.d.ts +1 -0
  133. package/dist/teste/AvatarGroupOverlap.test.js +56 -0
  134. package/dist/teste/BadgeNeutroColado.test.d.ts +1 -0
  135. package/dist/teste/BadgeNeutroColado.test.js +11 -0
  136. package/dist/teste/BadgeTag.test.d.ts +1 -0
  137. package/dist/teste/BadgeTag.test.js +55 -0
  138. package/dist/teste/BoxItemFlex.test.d.ts +1 -0
  139. package/dist/teste/BoxItemFlex.test.js +54 -0
  140. package/dist/teste/BoxLayout.test.d.ts +1 -0
  141. package/dist/teste/BoxLayout.test.js +35 -0
  142. package/dist/teste/Button.test.d.ts +1 -0
  143. package/dist/teste/Button.test.js +27 -0
  144. package/dist/teste/Calendar.test.d.ts +1 -0
  145. package/dist/teste/Calendar.test.js +221 -0
  146. package/dist/teste/CalendarCabecalho.test.d.ts +1 -0
  147. package/dist/teste/CalendarCabecalho.test.js +11 -0
  148. package/dist/teste/ChipsXs.test.d.ts +1 -0
  149. package/dist/teste/ChipsXs.test.js +31 -0
  150. package/dist/teste/Coachmark.test.d.ts +1 -0
  151. package/dist/teste/Coachmark.test.js +149 -0
  152. package/dist/teste/CoachmarkSemVeu.test.d.ts +1 -0
  153. package/dist/teste/CoachmarkSemVeu.test.js +85 -0
  154. package/dist/teste/ColorPicker.test.d.ts +1 -0
  155. package/dist/teste/ColorPicker.test.js +132 -0
  156. package/dist/teste/ColorPickerXs.test.d.ts +1 -0
  157. package/dist/teste/ColorPickerXs.test.js +21 -0
  158. package/dist/teste/DataTableArvoreEVariantes.test.d.ts +1 -0
  159. package/dist/teste/DataTableArvoreEVariantes.test.js +190 -0
  160. package/dist/teste/DataTableCelulaLivre.test.d.ts +1 -0
  161. package/dist/teste/DataTableCelulaLivre.test.js +76 -0
  162. package/dist/teste/DataTableDocumento.test.d.ts +1 -0
  163. package/dist/teste/DataTableDocumento.test.js +92 -0
  164. package/dist/teste/DatePicker.test.js +23 -0
  165. package/dist/teste/DescriptionListCompacta.test.d.ts +1 -0
  166. package/dist/teste/DescriptionListCompacta.test.js +22 -0
  167. package/dist/teste/EmptyStateTituloRico.test.d.ts +1 -0
  168. package/dist/teste/EmptyStateTituloRico.test.js +23 -0
  169. package/dist/teste/FileInputTrigger.test.d.ts +1 -0
  170. package/dist/teste/FileInputTrigger.test.js +53 -0
  171. package/dist/teste/IconPicker.test.d.ts +1 -0
  172. package/dist/teste/IconPicker.test.js +130 -0
  173. package/dist/teste/InputAgrupado.test.d.ts +1 -0
  174. package/dist/teste/InputAgrupado.test.js +24 -0
  175. package/dist/teste/InputMascaraEAfins.test.d.ts +1 -0
  176. package/dist/teste/InputMascaraEAfins.test.js +99 -0
  177. package/dist/teste/KbdCodeBlock.test.d.ts +1 -0
  178. package/dist/teste/KbdCodeBlock.test.js +95 -0
  179. package/dist/teste/List.test.d.ts +1 -0
  180. package/dist/teste/List.test.js +153 -0
  181. package/dist/teste/ListItemTooltipStandalone.test.d.ts +1 -0
  182. package/dist/teste/ListItemTooltipStandalone.test.js +39 -0
  183. package/dist/teste/ModalVariantes.test.d.ts +1 -0
  184. package/dist/teste/ModalVariantes.test.js +151 -0
  185. package/dist/teste/NomeAcessivelSsr.test.d.ts +1 -0
  186. package/dist/teste/NomeAcessivelSsr.test.js +88 -0
  187. package/dist/teste/OrgChart.test.d.ts +1 -0
  188. package/dist/teste/OrgChart.test.js +162 -0
  189. package/dist/teste/OtpInput.test.d.ts +1 -0
  190. package/dist/teste/OtpInput.test.js +143 -0
  191. package/dist/teste/PopoverMaxHeight.test.d.ts +1 -0
  192. package/dist/teste/PopoverMaxHeight.test.js +38 -0
  193. package/dist/teste/RichTextEditor.test.d.ts +1 -0
  194. package/dist/teste/RichTextEditor.test.js +287 -0
  195. package/dist/teste/RichTextEditorAlinhaImagemTabela.test.d.ts +1 -0
  196. package/dist/teste/RichTextEditorAlinhaImagemTabela.test.js +212 -0
  197. package/dist/teste/RichTextEditorEstiloDeTexto.test.d.ts +1 -0
  198. package/dist/teste/RichTextEditorEstiloDeTexto.test.js +120 -0
  199. package/dist/teste/SegmentedControlContador.test.d.ts +1 -0
  200. package/dist/teste/SegmentedControlContador.test.js +39 -0
  201. package/dist/teste/SelectAbertoControladoEFlutuante.test.d.ts +1 -0
  202. package/dist/teste/SelectAbertoControladoEFlutuante.test.js +145 -0
  203. package/dist/teste/SelectFiltroCorETamanho.test.d.ts +1 -0
  204. package/dist/teste/SelectFiltroCorETamanho.test.js +77 -0
  205. package/dist/teste/SelectPlacementFiltro.test.d.ts +1 -0
  206. package/dist/teste/SelectPlacementFiltro.test.js +88 -0
  207. package/dist/teste/Stack.test.d.ts +1 -0
  208. package/dist/teste/Stack.test.js +57 -0
  209. package/dist/teste/StatCardRotuloTopo.test.d.ts +1 -0
  210. package/dist/teste/StatCardRotuloTopo.test.js +34 -0
  211. package/dist/teste/StatCardSecundariaETom.test.d.ts +1 -0
  212. package/dist/teste/StatCardSecundariaETom.test.js +45 -0
  213. package/dist/teste/TabsContadorFechavel.test.d.ts +1 -0
  214. package/dist/teste/TabsContadorFechavel.test.js +79 -0
  215. package/dist/teste/ToastAcaoEDetalhes.test.d.ts +1 -0
  216. package/dist/teste/ToastAcaoEDetalhes.test.js +94 -0
  217. package/dist/teste/TooltipDark.test.d.ts +1 -0
  218. package/dist/teste/TooltipDark.test.js +31 -0
  219. package/dist/utils/calendario.d.ts +28 -0
  220. package/dist/utils/calendario.js +73 -0
  221. package/dist/utils/html-seguro.d.ts +45 -0
  222. package/dist/utils/html-seguro.js +208 -0
  223. package/dist/utils/paleta-de-cores.d.ts +26 -0
  224. package/dist/utils/paleta-de-cores.js +10 -0
  225. package/dist/utils/texto.d.ts +5 -0
  226. package/dist/utils/texto.js +5 -0
  227. package/dist/utils/toast.d.ts +10 -1
  228. package/dist/utils/toast.js +2 -1
  229. package/package.json +3 -3
  230. package/src/index.ts +26 -0
  231. package/src/teste/AILabel.test.tsx +41 -0
  232. package/src/teste/AccordionConteudoLivre.test.tsx +39 -0
  233. package/src/teste/AccordionTituloNeutro.test.tsx +25 -0
  234. package/src/teste/AppSubheaderRegiao.test.tsx +17 -0
  235. package/src/teste/AtributosRepassados.test.tsx +73 -0
  236. package/src/teste/AttachmentCardIA.test.tsx +126 -0
  237. package/src/teste/AttachmentCardLista.test.tsx +53 -0
  238. package/src/teste/AttachmentCardSm.test.tsx +29 -0
  239. package/src/teste/AvatarGroupOverlap.test.tsx +74 -0
  240. package/src/teste/BadgeNeutroColado.test.tsx +15 -0
  241. package/src/teste/BadgeTag.test.tsx +85 -0
  242. package/src/teste/BoxItemFlex.test.tsx +81 -0
  243. package/src/teste/BoxLayout.test.tsx +50 -0
  244. package/src/teste/Button.test.tsx +39 -0
  245. package/src/teste/Calendar.test.tsx +282 -0
  246. package/src/teste/CalendarCabecalho.test.tsx +11 -0
  247. package/src/teste/ChipsXs.test.tsx +41 -0
  248. package/src/teste/Coachmark.test.tsx +198 -0
  249. package/src/teste/CoachmarkSemVeu.test.tsx +101 -0
  250. package/src/teste/ColorPicker.test.tsx +153 -0
  251. package/src/teste/ColorPickerXs.test.tsx +22 -0
  252. package/src/teste/DataTableArvoreEVariantes.test.tsx +240 -0
  253. package/src/teste/DataTableCelulaLivre.test.tsx +103 -0
  254. package/src/teste/DataTableDocumento.test.tsx +102 -0
  255. package/src/teste/DatePicker.test.tsx +38 -0
  256. package/src/teste/DescriptionListCompacta.test.tsx +24 -0
  257. package/src/teste/EmptyStateTituloRico.test.tsx +34 -0
  258. package/src/teste/FileInputTrigger.test.tsx +89 -0
  259. package/src/teste/IconPicker.test.tsx +145 -0
  260. package/src/teste/InputAgrupado.test.tsx +26 -0
  261. package/src/teste/InputMascaraEAfins.test.tsx +124 -0
  262. package/src/teste/KbdCodeBlock.test.tsx +106 -0
  263. package/src/teste/List.test.tsx +269 -0
  264. package/src/teste/ListItemTooltipStandalone.test.tsx +64 -0
  265. package/src/teste/ModalVariantes.test.tsx +173 -0
  266. package/src/teste/NomeAcessivelSsr.test.tsx +110 -0
  267. package/src/teste/OrgChart.test.tsx +183 -0
  268. package/src/teste/OtpInput.test.tsx +163 -0
  269. package/src/teste/PopoverMaxHeight.test.tsx +47 -0
  270. package/src/teste/RichTextEditor.test.tsx +329 -0
  271. package/src/teste/RichTextEditorAlinhaImagemTabela.test.tsx +238 -0
  272. package/src/teste/RichTextEditorEstiloDeTexto.test.tsx +137 -0
  273. package/src/teste/SegmentedControlContador.test.tsx +43 -0
  274. package/src/teste/SelectAbertoControladoEFlutuante.test.tsx +180 -0
  275. package/src/teste/SelectFiltroCorETamanho.test.tsx +90 -0
  276. package/src/teste/SelectPlacementFiltro.test.tsx +110 -0
  277. package/src/teste/Stack.test.tsx +80 -0
  278. package/src/teste/StatCardRotuloTopo.test.tsx +44 -0
  279. package/src/teste/StatCardSecundariaETom.test.tsx +51 -0
  280. package/src/teste/TabsContadorFechavel.test.tsx +144 -0
  281. package/src/teste/ToastAcaoEDetalhes.test.tsx +109 -0
  282. package/src/teste/TooltipDark.test.tsx +51 -0
  283. package/src/utils/calendario.ts +78 -0
  284. package/src/utils/html-seguro.ts +197 -0
  285. package/src/utils/paleta-de-cores.ts +34 -0
  286. package/src/utils/texto.ts +5 -0
  287. package/src/utils/toast.ts +12 -2
@@ -0,0 +1,145 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { useState } from "react";
3
+ import { render, screen } from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
5
+ import { faBuilding, faFileLines, faFolder, faGavel, faUser } from "@fortawesome/free-solid-svg-icons";
6
+ import IconPicker from "../components/IconPicker/IconPicker";
7
+
8
+ const ICONES = [
9
+ faUser,
10
+ faFolder,
11
+ faFileLines,
12
+ { icon: faBuilding, label: "Prédio", keywords: ["prefeitura", "órgão"] },
13
+ { icon: faGavel, label: "Martelo", keywords: ["jurídico", "decisão"] },
14
+ ];
15
+
16
+ const radios = () => screen.getAllByRole("radio");
17
+
18
+ describe("IconPicker", () => {
19
+ it("é um radiogroup com um radio por ícone, nomeado pelo iconName sem hífen", () => {
20
+ render(<IconPicker ariaLabel="Ícone da categoria" icons={ICONES} />);
21
+ expect(screen.getByRole("radiogroup", { name: "Ícone da categoria" })).toBeInTheDocument();
22
+ expect(radios()).toHaveLength(5);
23
+ expect(screen.getByRole("radio", { name: "user" })).toBeInTheDocument();
24
+ expect(screen.getByRole("radio", { name: "file lines" })).toBeInTheDocument();
25
+ expect(screen.getByRole("radio", { name: "Prédio" })).toBeInTheDocument();
26
+ });
27
+
28
+ it("iconLabel traduz os nomes que não vêm com label", () => {
29
+ render(<IconPicker ariaLabel="x" icons={[faUser, faFolder]} iconLabel={(n) => ({ user: "Pessoa", folder: "Pasta" } as any)[n]} />);
30
+ expect(screen.getByRole("radio", { name: "Pessoa" })).toBeInTheDocument();
31
+ expect(screen.getByRole("radio", { name: "Pasta" })).toBeInTheDocument();
32
+ });
33
+
34
+ it("clicar escolhe e entrega o NOME do ícone e o item em onChange", async () => {
35
+ const usuario = userEvent.setup();
36
+ const aoMudar = vi.fn();
37
+ render(<IconPicker ariaLabel="x" icons={ICONES} onChange={aoMudar} />);
38
+ await usuario.click(screen.getByRole("radio", { name: "folder" }));
39
+ expect(aoMudar).toHaveBeenCalledWith("folder", faFolder);
40
+ expect(screen.getByRole("radio", { name: "folder" })).toHaveAttribute("aria-checked", "true");
41
+ await usuario.click(screen.getByRole("radio", { name: "Prédio" }));
42
+ expect(aoMudar).toHaveBeenLastCalledWith("building", ICONES[3]);
43
+ });
44
+
45
+ it("defaultValue marca o escolhido; só ele entra na ordem de Tab", () => {
46
+ render(<IconPicker ariaLabel="x" icons={ICONES} defaultValue="file-lines" />);
47
+ expect(screen.getByRole("radio", { name: "file lines" })).toHaveAttribute("aria-checked", "true");
48
+ const comTab = radios().filter((r) => r.getAttribute("tabindex") === "0");
49
+ expect(comTab).toHaveLength(1);
50
+ expect(comTab[0]).toHaveAccessibleName("file lines");
51
+ });
52
+
53
+ it("sem valor, Tab entra no primeiro", () => {
54
+ render(<IconPicker ariaLabel="x" icons={ICONES} />);
55
+ expect(radios()[0]).toHaveAttribute("tabindex", "0");
56
+ });
57
+
58
+ it("a busca filtra por nome, rótulo e palavras-chave, sem acento nem caixa", async () => {
59
+ const usuario = userEvent.setup();
60
+ render(<IconPicker ariaLabel="x" icons={ICONES} />);
61
+ const campo = screen.getByRole("searchbox", { name: "Buscar ícone" });
62
+ await usuario.type(campo, "PREFEITURA");
63
+ expect(radios()).toHaveLength(1);
64
+ expect(radios()[0]).toHaveAccessibleName("Prédio");
65
+ await usuario.clear(campo);
66
+ await usuario.type(campo, "juridico");
67
+ expect(radios().map((r) => r.getAttribute("aria-label"))).toEqual(["Martelo"]);
68
+ await usuario.clear(campo);
69
+ await usuario.type(campo, "fol");
70
+ expect(radios().map((r) => r.getAttribute("aria-label"))).toEqual(["folder"]);
71
+ });
72
+
73
+ it("a quantidade de resultados é anunciada e resultsLabel traduz", async () => {
74
+ const usuario = userEvent.setup();
75
+ render(<IconPicker ariaLabel="x" icons={ICONES} resultsLabel="{count} icons" />);
76
+ expect(screen.getByRole("status")).toHaveTextContent("5 icons");
77
+ await usuario.type(screen.getByRole("searchbox"), "user");
78
+ expect(screen.getByRole("status")).toHaveTextContent("1 icons");
79
+ });
80
+
81
+ it("sem resultado mostra o texto e some a grade", async () => {
82
+ const usuario = userEvent.setup();
83
+ render(<IconPicker ariaLabel="x" icons={ICONES} emptyText="Nada por aqui" />);
84
+ await usuario.type(screen.getByRole("searchbox"), "zzz");
85
+ expect(screen.getByText("Nada por aqui")).toBeInTheDocument();
86
+ expect(screen.queryByRole("radiogroup")).toBeNull();
87
+ });
88
+
89
+ it("setas movem a escolha e o foco; cima e baixo andam uma linha (columns)", async () => {
90
+ const usuario = userEvent.setup();
91
+ const aoMudar = vi.fn();
92
+ render(<IconPicker ariaLabel="x" icons={ICONES} columns={2} defaultValue="user" onChange={aoMudar} />);
93
+ radios()[0].focus();
94
+ await usuario.keyboard("{ArrowRight}");
95
+ expect(aoMudar).toHaveBeenLastCalledWith("folder", faFolder);
96
+ expect(radios()[1]).toHaveFocus();
97
+ await usuario.keyboard("{ArrowDown}");
98
+ expect(radios()[3]).toHaveFocus();
99
+ await usuario.keyboard("{ArrowUp}");
100
+ expect(radios()[1]).toHaveFocus();
101
+ await usuario.keyboard("{End}");
102
+ expect(radios()[4]).toHaveFocus();
103
+ await usuario.keyboard("{ArrowRight}");
104
+ expect(radios()[0]).toHaveFocus();
105
+ await usuario.keyboard("{ArrowLeft}");
106
+ expect(radios()[4]).toHaveFocus();
107
+ await usuario.keyboard("{Home}");
108
+ expect(radios()[0]).toHaveFocus();
109
+ });
110
+
111
+ it("seta para baixo no campo de busca leva à grade", async () => {
112
+ const usuario = userEvent.setup();
113
+ render(<IconPicker ariaLabel="x" icons={ICONES} defaultValue="folder" />);
114
+ await usuario.click(screen.getByRole("searchbox"));
115
+ await usuario.keyboard("{ArrowDown}");
116
+ expect(screen.getByRole("radio", { name: "folder" })).toHaveFocus();
117
+ });
118
+
119
+ it("hideSearch tira o campo; disabled desabilita tudo", () => {
120
+ const { rerender } = render(<IconPicker ariaLabel="x" icons={ICONES} hideSearch />);
121
+ expect(screen.queryByRole("searchbox")).toBeNull();
122
+ rerender(<IconPicker ariaLabel="x" icons={ICONES} disabled />);
123
+ for (const r of radios()) expect(r).toBeDisabled();
124
+ });
125
+
126
+ it("textos da busca por prop; label visível nomeia o grupo; columns vira o grid", () => {
127
+ const { container } = render(
128
+ <IconPicker label="Ícone" id="ip" icons={ICONES} searchLabel="Search icon" searchPlaceholder="Search..." columns={4} maxHeight={120} />,
129
+ );
130
+ expect(screen.getByRole("radiogroup", { name: "Ícone" })).toBeInTheDocument();
131
+ expect(screen.getByRole("searchbox", { name: "Search icon" })).toHaveAttribute("placeholder", "Search...");
132
+ const grade = container.querySelector(".umds-icon-picker__grid") as HTMLElement;
133
+ expect(grade.style.gridTemplateColumns).toBe("repeat(4, var(--umds-icon-picker-tile))");
134
+ expect(grade.style.maxHeight).toBe("120px");
135
+ });
136
+
137
+ it("controlado: reflete o value", () => {
138
+ function Pai() {
139
+ const [valor, setValor] = useState("gavel");
140
+ return <IconPicker ariaLabel="x" icons={ICONES} value={valor} onChange={setValor} />;
141
+ }
142
+ render(<Pai />);
143
+ expect(screen.getByRole("radio", { name: "Martelo" })).toHaveAttribute("aria-checked", "true");
144
+ });
145
+ });
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { render } from "@testing-library/react";
3
+ import Input from "../components/Input/Input";
4
+
5
+ describe("Input · grouped", () => {
6
+ it("grouped liga a classe própria e a do borderless; sem a prop, nenhuma das duas", () => {
7
+ const { container, rerender } = render(<Input ariaLabel="Adicionar setor" grouped />);
8
+ const raiz = () => container.firstElementChild as HTMLElement;
9
+ expect(raiz()).toHaveClass("umds-input--grouped");
10
+ expect(raiz()).toHaveClass("umds-input--borderless");
11
+ rerender(<Input ariaLabel="Adicionar setor" />);
12
+ expect(raiz()).not.toHaveClass("umds-input--grouped");
13
+ expect(raiz()).not.toHaveClass("umds-input--borderless");
14
+ });
15
+
16
+ it("borderless sozinho não ganha a classe do grupo (segue com altura de campo e foco próprio)", () => {
17
+ const { container } = render(<Input ariaLabel="x" borderless />);
18
+ expect(container.firstElementChild).toHaveClass("umds-input--borderless");
19
+ expect(container.firstElementChild).not.toHaveClass("umds-input--grouped");
20
+ });
21
+
22
+ it("o campo continua digitável dentro do grupo", () => {
23
+ const { container } = render(<Input ariaLabel="Adicionar setor" grouped placeholder="SADM, SOBR..." />);
24
+ expect(container.querySelector("input")).toHaveAttribute("placeholder", "SADM, SOBR...");
25
+ });
26
+ });
@@ -0,0 +1,124 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { fireEvent, render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
5
+ import Input from "../components/Input/Input";
6
+
7
+ describe("Input · onChange a cada tecla, uma vez só", () => {
8
+ it("digitar chama onChange uma vez por tecla, com o valor novo", async () => {
9
+ const aoMudar = vi.fn();
10
+ render(<Input label="Nome" onChange={aoMudar} />);
11
+ await userEvent.type(screen.getByLabelText("Nome"), "ab");
12
+ expect(aoMudar.mock.calls.map((c) => c[0])).toEqual(["a", "ab"]);
13
+ });
14
+
15
+ it("change disparado à mão também chega (testes de quem consome)", () => {
16
+ const aoMudar = vi.fn();
17
+ render(<Input label="Nome" onChange={aoMudar} />);
18
+ fireEvent.change(screen.getByLabelText("Nome"), { target: { value: "xyz" } });
19
+ expect(aoMudar).toHaveBeenCalledTimes(1);
20
+ expect(aoMudar).toHaveBeenCalledWith("xyz");
21
+ });
22
+
23
+ it("onBlur continua sendo o evento de saída do campo", async () => {
24
+ const aoSair = vi.fn();
25
+ const aoMudar = vi.fn();
26
+ render(<Input label="Nome" onBlur={aoSair} onChange={aoMudar} />);
27
+ const campo = screen.getByLabelText("Nome");
28
+ await userEvent.type(campo, "a");
29
+ expect(aoSair).not.toHaveBeenCalled();
30
+ await userEvent.tab();
31
+ expect(aoSair).toHaveBeenCalledTimes(1);
32
+ expect(aoMudar).toHaveBeenCalledTimes(1);
33
+ });
34
+ });
35
+
36
+ describe("Input · mask", () => {
37
+ async function digitar(mask: any, texto: string) {
38
+ const aoMudar = vi.fn();
39
+ render(<Input label="Campo" mask={mask} onChange={aoMudar} />);
40
+ const campo = screen.getByLabelText("Campo") as HTMLInputElement;
41
+ await userEvent.type(campo, texto);
42
+ return { campo, aoMudar };
43
+ }
44
+
45
+ it("cpf", async () => {
46
+ const { campo, aoMudar } = await digitar("cpf", "12345678901");
47
+ expect(campo.value).toBe("123.456.789-01");
48
+ expect(aoMudar).toHaveBeenLastCalledWith("123.456.789-01");
49
+ });
50
+
51
+ it("cnpj", async () => {
52
+ const { campo } = await digitar("cnpj", "12345678000190");
53
+ expect(campo.value).toBe("12.345.678/0001-90");
54
+ });
55
+
56
+ it("cep", async () => {
57
+ const { campo } = await digitar("cep", "80010000");
58
+ expect(campo.value).toBe("80010-000");
59
+ });
60
+
61
+ it("phone troca de formato com 10 ou 11 dígitos", async () => {
62
+ expect((await digitar("phone", "4133334444")).campo.value).toBe("(41) 3333-4444");
63
+ });
64
+
65
+ it("phone de 11 dígitos", async () => {
66
+ expect((await digitar("phone", "41999998888")).campo.value).toBe("(41) 99999-8888");
67
+ });
68
+
69
+ it("ignora letras e passa do tamanho, sem estourar", async () => {
70
+ const { campo } = await digitar("cpf", "12a3.4b5-678901999");
71
+ expect(campo.value).toBe("123.456.789-01");
72
+ });
73
+
74
+ it("valor controlado com dígitos aparece formatado", () => {
75
+ render(<Input label="CPF" mask="cpf" value="12345678901" onChange={() => {}} />);
76
+ expect((screen.getByLabelText("CPF") as HTMLInputElement).value).toBe("123.456.789-01");
77
+ });
78
+
79
+ it("abre o teclado numérico e limita o tamanho; sem máscara, nenhum dos dois", () => {
80
+ render(
81
+ <>
82
+ <Input label="Com" mask="cep" />
83
+ <Input label="Sem" />
84
+ </>,
85
+ );
86
+ expect(screen.getByLabelText("Com")).toHaveAttribute("inputmode", "numeric");
87
+ expect(screen.getByLabelText("Com")).toHaveAttribute("maxlength", "9");
88
+ expect(screen.getByLabelText("Sem")).not.toHaveAttribute("inputmode");
89
+ expect(screen.getByLabelText("Sem")).not.toHaveAttribute("maxlength");
90
+ });
91
+ });
92
+
93
+ describe("Input · iconRight e borderless", () => {
94
+ it("iconRight é decorativo: aparece, não é botão e fica fora da árvore de acessibilidade", () => {
95
+ const { container } = render(<Input ariaLabel="Número" iconRight={faMagnifyingGlass} />);
96
+ const icone = container.querySelector(".umds-input__icon-right") as HTMLElement;
97
+ expect(icone).not.toBeNull();
98
+ expect(icone).toHaveAttribute("aria-hidden", "true");
99
+ expect(icone.querySelector("svg")).not.toBeNull();
100
+ expect(screen.queryByRole("button")).toBeNull();
101
+ });
102
+
103
+ it("sem iconRight não há ícone à direita", () => {
104
+ const { container } = render(<Input ariaLabel="x" />);
105
+ expect(container.querySelector(".umds-input__icon-right")).toBeNull();
106
+ });
107
+
108
+ it("borderless escreve a classe; sem a prop não", () => {
109
+ const { container } = render(
110
+ <>
111
+ <Input ariaLabel="a" borderless />
112
+ <Input ariaLabel="b" />
113
+ </>,
114
+ );
115
+ expect(container.children[0]).toHaveClass("umds-input--borderless");
116
+ expect(container.children[1]).not.toHaveClass("umds-input--borderless");
117
+ });
118
+
119
+ it("borderless com erro continua marcando o erro", () => {
120
+ const { container } = render(<Input ariaLabel="a" borderless errorMessage="Obrigatório" />);
121
+ expect(container.firstElementChild).toHaveClass("umds-input--borderless", "umds-input--error");
122
+ expect(screen.getByLabelText("a")).toHaveAttribute("aria-invalid", "true");
123
+ });
124
+ });
@@ -0,0 +1,106 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { act, render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import Kbd from "../components/Kbd/Kbd";
5
+ import CodeBlock from "../components/CodeBlock/CodeBlock";
6
+
7
+ describe("Kbd", () => {
8
+ it("uma tecla é um kbd com o texto", () => {
9
+ const { container } = render(<Kbd>Esc</Kbd>);
10
+ const tecla = container.querySelector("kbd") as HTMLElement;
11
+ expect(tecla).toHaveTextContent("Esc");
12
+ expect(tecla).toHaveClass("umds-kbd", "umds-kbd--md");
13
+ expect(container.querySelectorAll("kbd")).toHaveLength(1);
14
+ });
15
+
16
+ it("keys desenha uma tecla por item, com o separador entre elas", () => {
17
+ const { container } = render(<Kbd keys={["Ctrl", "Shift", "P"]} />);
18
+ const conjunto = container.querySelector("kbd.umds-kbd-combo") as HTMLElement;
19
+ expect(conjunto).not.toBeNull();
20
+ expect(conjunto.querySelectorAll("kbd.umds-kbd")).toHaveLength(3);
21
+ expect(conjunto.textContent).toBe("Ctrl+Shift+P");
22
+ });
23
+
24
+ it("separator troca o texto entre as teclas; size vira classe", () => {
25
+ const { container } = render(<Kbd keys={["Ctrl", "K"]} separator=" + " size="sm" />);
26
+ expect(container.querySelector("kbd")).toHaveClass("umds-kbd--sm");
27
+ expect(container.textContent).toBe("Ctrl + K");
28
+ });
29
+
30
+ it("atributos HTML vão para o kbd", () => {
31
+ const { container } = render(<Kbd title="Atalho" data-x="1">Tab</Kbd>);
32
+ const tecla = container.querySelector("kbd") as HTMLElement;
33
+ expect(tecla).toHaveAttribute("title", "Atalho");
34
+ expect(tecla).toHaveAttribute("data-x", "1");
35
+ });
36
+ });
37
+
38
+ describe("CodeBlock", () => {
39
+ afterEach(() => {
40
+ vi.useRealTimers();
41
+ vi.restoreAllMocks();
42
+ });
43
+
44
+ it("mostra o código em pre/code, mantendo as quebras, numa região focável e nomeada", () => {
45
+ const { container } = render(<CodeBlock code={'{\n "ok": true\n}'} ariaLabel="Resposta" />);
46
+ const regiao = screen.getByRole("region", { name: "Resposta" });
47
+ expect(regiao.tagName).toBe("PRE");
48
+ expect(regiao).toHaveAttribute("tabindex", "0");
49
+ expect(container.querySelector("code")?.textContent).toBe('{\n "ok": true\n}');
50
+ });
51
+
52
+ it("o texto também vem como conteúdo; o nome padrão é a linguagem, ou Código", () => {
53
+ const { rerender } = render(<CodeBlock language="JSON">{"{}"}</CodeBlock>);
54
+ expect(screen.getByRole("region", { name: "JSON" })).toHaveTextContent("{}");
55
+ expect(screen.getByText("JSON", { selector: ".umds-code-block__language" })).toBeInTheDocument();
56
+ rerender(<CodeBlock code="x" />);
57
+ expect(screen.getByRole("region", { name: "Código" })).toBeInTheDocument();
58
+ });
59
+
60
+ it("wrap e maxHeight", () => {
61
+ const { container } = render(<CodeBlock code="linha longa" wrap maxHeight={120} />);
62
+ expect(container.querySelector(".umds-code-block--wrap")).not.toBeNull();
63
+ expect((container.querySelector("pre") as HTMLElement).style.maxHeight).toBe("120px");
64
+ });
65
+
66
+ it("sem copyable e sem linguagem, não há cabeçalho nem botão", () => {
67
+ render(<CodeBlock code="x" />);
68
+ expect(screen.queryByRole("button")).toBeNull();
69
+ });
70
+
71
+ it("copiar manda o texto inteiro à área de transferência, confirma e volta depois de 2 s", async () => {
72
+ vi.useFakeTimers({ shouldAdvanceTime: true });
73
+ const escrever = vi.fn(() => Promise.resolve());
74
+ const aoCopiar = vi.fn();
75
+ const usuario = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
76
+ // O userEvent.setup instala a própria área de transferência: o espião entra depois.
77
+ Object.defineProperty(navigator, "clipboard", { value: { writeText: escrever }, configurable: true });
78
+ render(<CodeBlock code={'{"a":1}'} copyable onCopy={aoCopiar} />);
79
+ await usuario.click(screen.getByRole("button", { name: "Copiar" }));
80
+ expect(escrever).toHaveBeenCalledWith('{"a":1}');
81
+ expect(await screen.findByRole("button", { name: "Copiado" })).toBeInTheDocument();
82
+ expect(aoCopiar).toHaveBeenCalledWith('{"a":1}');
83
+ expect(screen.getByRole("status")).toHaveTextContent("Copiado");
84
+ await act(async () => {
85
+ vi.advanceTimersByTime(2100);
86
+ });
87
+ expect(screen.getByRole("button", { name: "Copiar" })).toBeInTheDocument();
88
+ });
89
+
90
+ it("copiar com o texto vindo como conteúdo lê o texto do bloco", async () => {
91
+ const escrever = vi.fn(() => Promise.resolve());
92
+ const usuario = userEvent.setup();
93
+ Object.defineProperty(navigator, "clipboard", { value: { writeText: escrever }, configurable: true });
94
+ render(<CodeBlock copyable>{"npm run build"}</CodeBlock>);
95
+ await usuario.click(screen.getByRole("button", { name: "Copiar" }));
96
+ expect(escrever).toHaveBeenCalledWith("npm run build");
97
+ });
98
+
99
+ it("copyLabel e copiedLabel por prop", async () => {
100
+ const usuario = userEvent.setup();
101
+ Object.defineProperty(navigator, "clipboard", { value: { writeText: () => Promise.resolve() }, configurable: true });
102
+ render(<CodeBlock code="x" copyable copyLabel="Copy" copiedLabel="Copied" />);
103
+ await usuario.click(screen.getByRole("button", { name: "Copy" }));
104
+ expect(await screen.findByRole("button", { name: "Copied" })).toBeInTheDocument();
105
+ });
106
+ });
@@ -0,0 +1,269 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { faInbox } from "@fortawesome/free-solid-svg-icons";
5
+ import List from "../components/List/List";
6
+ import ListItem from "../components/List/ListItem";
7
+
8
+ describe("ListItem", () => {
9
+ it("sem as nem href é um button dentro de um li", () => {
10
+ const { container } = render(
11
+ <ul>
12
+ <ListItem title="Memorando 114/2025" />
13
+ </ul>,
14
+ );
15
+ const botao = screen.getByRole("button", { name: /Memorando 114\/2025/ });
16
+ expect(botao.tagName).toBe("BUTTON");
17
+ expect(botao).toHaveAttribute("type", "button");
18
+ expect(botao.parentElement?.tagName).toBe("LI");
19
+ expect(container.querySelectorAll("li")).toHaveLength(1);
20
+ });
21
+
22
+ it("com href vira link; target _blank ganha rel seguro", () => {
23
+ render(
24
+ <ul>
25
+ <ListItem title="Ofício" href="/oficios/31" target="_blank" />
26
+ </ul>,
27
+ );
28
+ const link = screen.getByRole("link", { name: /Ofício/ });
29
+ expect(link).toHaveAttribute("href", "/oficios/31");
30
+ expect(link).toHaveAttribute("rel", "noopener noreferrer");
31
+ });
32
+
33
+ it("as='div' sem onClick é só leitura: sem role nem tabindex", () => {
34
+ const { container } = render(
35
+ <ul>
36
+ <ListItem as="div" title="Somente leitura" />
37
+ </ul>,
38
+ );
39
+ const linha = container.querySelector(".umds-list-item") as HTMLElement;
40
+ expect(linha.tagName).toBe("DIV");
41
+ expect(linha).not.toHaveAttribute("role");
42
+ expect(linha).not.toHaveAttribute("tabindex");
43
+ expect(linha).not.toHaveClass("umds-list-item--interactive");
44
+ });
45
+
46
+ it("as='div' com onClick é acionável por Enter e Espaço", async () => {
47
+ const usuario = userEvent.setup();
48
+ const aoClicar = vi.fn();
49
+ render(
50
+ <ul>
51
+ <ListItem as="div" title="Acionável" onClick={aoClicar} />
52
+ </ul>,
53
+ );
54
+ const linha = screen.getByRole("button", { name: /Acionável/ });
55
+ linha.focus();
56
+ await usuario.keyboard("{Enter}");
57
+ await usuario.keyboard(" ");
58
+ expect(aoClicar).toHaveBeenCalledTimes(2);
59
+ });
60
+
61
+ it("monta título, subtítulo, meta, início e fim nos lugares certos", () => {
62
+ const { container } = render(
63
+ <ul>
64
+ <ListItem
65
+ title="Memorando 114/2025"
66
+ subtitle="Novo contrato - Governo"
67
+ meta="23 de agosto de 2025"
68
+ start={<span data-testid="inicio">AV</span>}
69
+ end={<span data-testid="fim">novo</span>}
70
+ >
71
+ <span data-testid="livre">conteúdo livre</span>
72
+ </ListItem>
73
+ </ul>,
74
+ );
75
+ expect(container.querySelector(".umds-list-item__title")).toHaveTextContent("Memorando 114/2025");
76
+ expect(container.querySelector(".umds-list-item__subtitle")).toHaveTextContent("Novo contrato - Governo");
77
+ expect(container.querySelector(".umds-list-item__meta")).toHaveTextContent("23 de agosto de 2025");
78
+ expect(container.querySelector(".umds-list-item__start")).toContainElement(screen.getByTestId("inicio"));
79
+ expect(container.querySelector(".umds-list-item__end")).toContainElement(screen.getByTestId("fim"));
80
+ expect(container.querySelector(".umds-list-item__body")).toContainElement(screen.getByTestId("livre"));
81
+ });
82
+
83
+ it("title aceita nó e não vira o atributo title do HTML", () => {
84
+ const { container } = render(
85
+ <ul>
86
+ <ListItem title={<b data-testid="rico">Rico</b>} />
87
+ </ul>,
88
+ );
89
+ expect(screen.getByTestId("rico")).toBeInTheDocument();
90
+ expect(container.querySelector("[title]")).toBeNull();
91
+ });
92
+
93
+ it("sem start, end e textos, não desenha as regiões vazias", () => {
94
+ const { container } = render(
95
+ <ul>
96
+ <ListItem title="Só título" />
97
+ </ul>,
98
+ );
99
+ expect(container.querySelector(".umds-list-item__start")).toBeNull();
100
+ expect(container.querySelector(".umds-list-item__end")).toBeNull();
101
+ expect(container.querySelector(".umds-list-item__subtitle")).toBeNull();
102
+ expect(container.querySelector(".umds-list-item__meta")).toBeNull();
103
+ });
104
+
105
+ it("selected marca aria-current e a classe de seleção", () => {
106
+ render(
107
+ <ul>
108
+ <ListItem title="Atual" selected />
109
+ </ul>,
110
+ );
111
+ const botao = screen.getByRole("button", { name: /Atual/ });
112
+ expect(botao).toHaveAttribute("aria-current", "true");
113
+ expect(botao).toHaveClass("umds-list-item--selected");
114
+ });
115
+
116
+ it("disabled bloqueia o clique e o foco do botão", async () => {
117
+ const usuario = userEvent.setup();
118
+ const aoClicar = vi.fn();
119
+ render(
120
+ <ul>
121
+ <ListItem title="Bloqueada" disabled onClick={aoClicar} />
122
+ </ul>,
123
+ );
124
+ const botao = screen.getByRole("button", { name: /Bloqueada/ });
125
+ expect(botao).toBeDisabled();
126
+ await usuario.click(botao);
127
+ expect(aoClicar).not.toHaveBeenCalled();
128
+ });
129
+
130
+ it("link disabled perde o href e declara aria-disabled", () => {
131
+ render(
132
+ <ul>
133
+ <ListItem title="Link bloqueado" href="/x" disabled />
134
+ </ul>,
135
+ );
136
+ const link = screen.getByText("Link bloqueado").closest("a") as HTMLElement;
137
+ expect(link).not.toHaveAttribute("href");
138
+ expect(link).toHaveAttribute("aria-disabled", "true");
139
+ });
140
+
141
+ it("clique chama onClick com o evento", async () => {
142
+ const usuario = userEvent.setup();
143
+ const aoClicar = vi.fn();
144
+ render(
145
+ <ul>
146
+ <ListItem title="Abrir" onClick={aoClicar} />
147
+ </ul>,
148
+ );
149
+ await usuario.click(screen.getByRole("button", { name: /Abrir/ }));
150
+ expect(aoClicar).toHaveBeenCalledTimes(1);
151
+ });
152
+
153
+ it("repassa aria-label, id, data-* e style ao elemento interativo", () => {
154
+ render(
155
+ <ul>
156
+ <ListItem aria-label="Abrir memorando" id="item-1" data-origem="home" style={{ marginTop: 4 }} />
157
+ </ul>,
158
+ );
159
+ const botao = screen.getByRole("button", { name: "Abrir memorando" });
160
+ expect(botao).toHaveAttribute("id", "item-1");
161
+ expect(botao).toHaveAttribute("data-origem", "home");
162
+ expect(botao).toHaveStyle({ marginTop: "4px" });
163
+ });
164
+ });
165
+
166
+ describe("List", () => {
167
+ it("é uma ul com role list, nome acessível e gap padrão", () => {
168
+ render(
169
+ <List ariaLabel="Documentos não lidos">
170
+ <ListItem title="A" />
171
+ <ListItem title="B" />
172
+ </List>,
173
+ );
174
+ const lista = screen.getByRole("list", { name: "Documentos não lidos" });
175
+ expect(lista.tagName).toBe("UL");
176
+ expect(lista).toHaveClass("umds-list--gap-quarck");
177
+ expect(screen.getAllByRole("listitem")).toHaveLength(2);
178
+ });
179
+
180
+ it("as='ol' e gap trocam o elemento e a classe", () => {
181
+ render(
182
+ <List as="ol" gap="nano" ariaLabel="Passos">
183
+ <ListItem title="A" />
184
+ </List>,
185
+ );
186
+ const lista = screen.getByRole("list", { name: "Passos" });
187
+ expect(lista.tagName).toBe("OL");
188
+ expect(lista).toHaveClass("umds-list--gap-nano");
189
+ });
190
+
191
+ it("ariaLabelledby aponta para o título visível", () => {
192
+ render(
193
+ <>
194
+ <h2 id="titulo">Prazos a vencer</h2>
195
+ <List ariaLabelledby="titulo">
196
+ <ListItem title="A" />
197
+ </List>
198
+ </>,
199
+ );
200
+ expect(screen.getByRole("list", { name: "Prazos a vencer" })).toBeInTheDocument();
201
+ });
202
+
203
+ it("sem isEmpty mostra os itens e nunca o estado vazio", () => {
204
+ render(
205
+ <List ariaLabel="Lista" emptyTitle="Nada aqui">
206
+ <ListItem title="A" />
207
+ </List>,
208
+ );
209
+ expect(screen.queryByText("Nada aqui")).toBeNull();
210
+ });
211
+
212
+ it("isEmpty troca os itens pelo EmptyState dentro da própria lista", () => {
213
+ const aoAgir = vi.fn();
214
+ const { container } = render(
215
+ <List
216
+ ariaLabel="Lista"
217
+ isEmpty
218
+ emptyTitle="Nenhum documento"
219
+ emptyDescription="Tudo em dia."
220
+ emptyIcon={faInbox}
221
+ emptyActionLabel="Atualizar"
222
+ onEmptyAction={aoAgir}
223
+ >
224
+ <ListItem title="Não aparece" />
225
+ </List>,
226
+ );
227
+ expect(screen.queryByRole("list")).toBeNull();
228
+ expect(screen.queryByText("Não aparece")).toBeNull();
229
+ const raiz = container.firstElementChild as HTMLElement;
230
+ expect(raiz).toHaveAttribute("role", "status");
231
+ expect(raiz).toHaveClass("umds-list--empty");
232
+ expect(raiz.querySelector(".umds-empty-state")).not.toBeNull();
233
+ expect(screen.getByText("Nenhum documento")).toBeInTheDocument();
234
+ expect(screen.getByText("Tudo em dia.")).toBeInTheDocument();
235
+ expect(raiz.querySelector(".umds-empty-state--sm")).not.toBeNull();
236
+ screen.getByRole("button", { name: "Atualizar" }).click();
237
+ expect(aoAgir).toHaveBeenCalledTimes(1);
238
+ });
239
+
240
+ it("isEmpty sem título usa o texto padrão", () => {
241
+ render(<List ariaLabel="Lista" isEmpty />);
242
+ expect(screen.getByText("Nenhum item encontrado")).toBeInTheDocument();
243
+ });
244
+
245
+ it("o conteúdo ao redor da lista não é desmontado ao esvaziar", () => {
246
+ const Tela = ({ vazia }: { vazia: boolean }) => (
247
+ <section>
248
+ <h2>Documentos</h2>
249
+ <input aria-label="Buscar" />
250
+ <List ariaLabel="Documentos" isEmpty={vazia}>
251
+ <ListItem title="A" />
252
+ </List>
253
+ <nav aria-label="Paginação">pág. 1</nav>
254
+ </section>
255
+ );
256
+ const { rerender } = render(<Tela vazia={false} />);
257
+ const busca = screen.getByLabelText("Buscar");
258
+ rerender(<Tela vazia />);
259
+ expect(screen.getByLabelText("Buscar")).toBe(busca);
260
+ expect(screen.getByRole("heading", { name: "Documentos" })).toBeInTheDocument();
261
+ expect(screen.getByRole("navigation", { name: "Paginação" })).toBeInTheDocument();
262
+ });
263
+
264
+ it("slot empty (prop empty no React) substitui o EmptyState", () => {
265
+ render(<List ariaLabel="Lista" isEmpty empty={<p data-testid="vazio">Nada por aqui</p>} />);
266
+ expect(screen.getByTestId("vazio")).toBeInTheDocument();
267
+ expect(document.querySelector(".umds-empty-state")).toBeNull();
268
+ });
269
+ });