@1doc/1ds-react 2.8.0 → 2.9.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 (213) hide show
  1. package/CHANGELOG.md +1106 -0
  2. package/dist/components/AILabel/AILabel.js +1 -1
  3. package/dist/components/Accordion/Accordion.d.ts +6 -2
  4. package/dist/components/AttachmentCard/AttachmentCard.css +16 -0
  5. package/dist/components/AttachmentCard/AttachmentCard.d.ts +19 -2
  6. package/dist/components/AttachmentCard/AttachmentCard.js +40 -3
  7. package/dist/components/Avatar/Avatar.css +7 -2
  8. package/dist/components/Avatar/AvatarGroup.d.ts +17 -0
  9. package/dist/components/Avatar/AvatarGroup.js +22 -8
  10. package/dist/components/Badge/Badge.css +54 -2
  11. package/dist/components/Badge/Badge.d.ts +14 -4
  12. package/dist/components/Badge/Badge.js +1 -0
  13. package/dist/components/Box/Box.css +83 -0
  14. package/dist/components/Box/Box.d.ts +82 -2
  15. package/dist/components/Box/Box.js +42 -2
  16. package/dist/components/Button/Button.css +29 -0
  17. package/dist/components/Button/Button.d.ts +4 -4
  18. package/dist/components/Button/Button.js +4 -1
  19. package/dist/components/Calendar/Calendar.css +245 -0
  20. package/dist/components/Calendar/Calendar.d.ts +125 -0
  21. package/dist/components/Calendar/Calendar.js +320 -0
  22. package/dist/components/Chips/Chips.css +38 -0
  23. package/dist/components/Chips/Chips.d.ts +5 -2
  24. package/dist/components/Chips/Chips.js +2 -1
  25. package/dist/components/Coachmark/Coachmark.css +118 -0
  26. package/dist/components/Coachmark/Coachmark.d.ts +75 -0
  27. package/dist/components/Coachmark/Coachmark.js +321 -0
  28. package/dist/components/CodeBlock/CodeBlock.css +96 -0
  29. package/dist/components/CodeBlock/CodeBlock.d.ts +47 -0
  30. package/dist/components/CodeBlock/CodeBlock.js +103 -0
  31. package/dist/components/ColorPicker/ColorPicker.css +133 -0
  32. package/dist/components/ColorPicker/ColorPicker.d.ts +73 -0
  33. package/dist/components/ColorPicker/ColorPicker.js +219 -0
  34. package/dist/components/Combobox/Combobox.css +8 -0
  35. package/dist/components/DataTable/DataTable.css +80 -0
  36. package/dist/components/DataTable/DataTable.d.ts +19 -4
  37. package/dist/components/DataTable/DataTable.js +59 -15
  38. package/dist/components/DatePicker/DatePicker.css +8 -0
  39. package/dist/components/DatePicker/DatePicker.js +10 -1
  40. package/dist/components/DescriptionList/DescriptionList.css +113 -0
  41. package/dist/components/DescriptionList/DescriptionList.d.ts +59 -0
  42. package/dist/components/DescriptionList/DescriptionList.js +51 -0
  43. package/dist/components/FileInput/FileInput.css +16 -0
  44. package/dist/components/FileInput/FileInput.d.ts +4 -2
  45. package/dist/components/FileInput/FileInput.js +8 -2
  46. package/dist/components/Grid/Grid.js +5 -5
  47. package/dist/components/IconPicker/IconPicker.css +107 -0
  48. package/dist/components/IconPicker/IconPicker.d.ts +74 -0
  49. package/dist/components/IconPicker/IconPicker.js +194 -0
  50. package/dist/components/Input/Input.css +38 -0
  51. package/dist/components/Input/Input.d.ts +10 -1
  52. package/dist/components/Input/Input.js +66 -5
  53. package/dist/components/Kanban/Kanban.css +4 -0
  54. package/dist/components/Kbd/Kbd.css +66 -0
  55. package/dist/components/Kbd/Kbd.d.ts +32 -0
  56. package/dist/components/Kbd/Kbd.js +40 -0
  57. package/dist/components/List/List.css +136 -0
  58. package/dist/components/List/List.d.ts +82 -0
  59. package/dist/components/List/List.js +53 -0
  60. package/dist/components/List/ListItem.d.ts +85 -0
  61. package/dist/components/List/ListItem.js +81 -0
  62. package/dist/components/Modal/Modal.css +80 -0
  63. package/dist/components/Modal/Modal.d.ts +33 -6
  64. package/dist/components/Modal/Modal.js +101 -6
  65. package/dist/components/MultiSelect/MultiSelect.css +8 -0
  66. package/dist/components/MultiSelect/MultiSelect.js +10 -1
  67. package/dist/components/OrgChart/OrgChart.css +149 -0
  68. package/dist/components/OrgChart/OrgChart.d.ts +83 -0
  69. package/dist/components/OrgChart/OrgChart.js +297 -0
  70. package/dist/components/OtpInput/OtpInput.css +93 -0
  71. package/dist/components/OtpInput/OtpInput.d.ts +60 -0
  72. package/dist/components/OtpInput/OtpInput.js +215 -0
  73. package/dist/components/PhoneInput/PhoneInput.css +8 -0
  74. package/dist/components/Popover/Popover.d.ts +7 -0
  75. package/dist/components/Popover/Popover.js +11 -1
  76. package/dist/components/RichTextEditor/RichTextEditor.css +203 -0
  77. package/dist/components/RichTextEditor/RichTextEditor.d.ts +83 -0
  78. package/dist/components/RichTextEditor/RichTextEditor.js +494 -0
  79. package/dist/components/SegmentedControl/SegmentedControl.js +7 -2
  80. package/dist/components/Select/Select.css +70 -0
  81. package/dist/components/Select/Select.d.ts +12 -0
  82. package/dist/components/Select/Select.js +59 -4
  83. package/dist/components/Stack/Stack.css +49 -0
  84. package/dist/components/Stack/Stack.d.ts +65 -7
  85. package/dist/components/Stack/Stack.js +30 -2
  86. package/dist/components/StatCard/StatCard.css +73 -0
  87. package/dist/components/StatCard/StatCard.d.ts +17 -0
  88. package/dist/components/StatCard/StatCard.js +4 -1
  89. package/dist/components/Tabs/Tab.d.ts +15 -0
  90. package/dist/components/Tabs/Tab.js +30 -1
  91. package/dist/components/Tabs/Tabs.css +132 -0
  92. package/dist/components/Tabs/Tabs.d.ts +7 -0
  93. package/dist/components/Tabs/Tabs.js +13 -2
  94. package/dist/components/Tag/Tag.css +85 -2
  95. package/dist/components/Tag/Tag.d.ts +13 -6
  96. package/dist/components/Tag/Tag.js +1 -0
  97. package/dist/components/Text/Text.js +2 -2
  98. package/dist/components/Textarea/Textarea.css +8 -0
  99. package/dist/components/TimePicker/TimePicker.css +8 -0
  100. package/dist/components/Tooltip/Tooltip.css +25 -0
  101. package/dist/components/Tooltip/Tooltip.d.ts +11 -0
  102. package/dist/components/Tooltip/Tooltip.js +18 -3
  103. package/dist/index.d.ts +25 -0
  104. package/dist/index.js +14 -0
  105. package/dist/teste/AILabel.test.d.ts +1 -0
  106. package/dist/teste/AILabel.test.js +33 -0
  107. package/dist/teste/AccordionConteudoLivre.test.d.ts +1 -0
  108. package/dist/teste/AccordionConteudoLivre.test.js +29 -0
  109. package/dist/teste/AtributosRepassados.test.d.ts +1 -0
  110. package/dist/teste/AtributosRepassados.test.js +52 -0
  111. package/dist/teste/AttachmentCardIA.test.d.ts +1 -0
  112. package/dist/teste/AttachmentCardIA.test.js +89 -0
  113. package/dist/teste/AvatarGroupOverlap.test.d.ts +1 -0
  114. package/dist/teste/AvatarGroupOverlap.test.js +56 -0
  115. package/dist/teste/BadgeTag.test.d.ts +1 -0
  116. package/dist/teste/BadgeTag.test.js +42 -0
  117. package/dist/teste/BoxItemFlex.test.d.ts +1 -0
  118. package/dist/teste/BoxItemFlex.test.js +54 -0
  119. package/dist/teste/BoxLayout.test.d.ts +1 -0
  120. package/dist/teste/BoxLayout.test.js +35 -0
  121. package/dist/teste/Button.test.d.ts +1 -0
  122. package/dist/teste/Button.test.js +27 -0
  123. package/dist/teste/Calendar.test.d.ts +1 -0
  124. package/dist/teste/Calendar.test.js +221 -0
  125. package/dist/teste/ChipsXs.test.d.ts +1 -0
  126. package/dist/teste/ChipsXs.test.js +31 -0
  127. package/dist/teste/Coachmark.test.d.ts +1 -0
  128. package/dist/teste/Coachmark.test.js +149 -0
  129. package/dist/teste/ColorPicker.test.d.ts +1 -0
  130. package/dist/teste/ColorPicker.test.js +132 -0
  131. package/dist/teste/DataTableCelulaLivre.test.d.ts +1 -0
  132. package/dist/teste/DataTableCelulaLivre.test.js +76 -0
  133. package/dist/teste/DataTableDocumento.test.d.ts +1 -0
  134. package/dist/teste/DataTableDocumento.test.js +92 -0
  135. package/dist/teste/DatePicker.test.js +23 -0
  136. package/dist/teste/FileInputTrigger.test.d.ts +1 -0
  137. package/dist/teste/FileInputTrigger.test.js +53 -0
  138. package/dist/teste/IconPicker.test.d.ts +1 -0
  139. package/dist/teste/IconPicker.test.js +130 -0
  140. package/dist/teste/InputMascaraEAfins.test.d.ts +1 -0
  141. package/dist/teste/InputMascaraEAfins.test.js +99 -0
  142. package/dist/teste/KbdCodeBlock.test.d.ts +1 -0
  143. package/dist/teste/KbdCodeBlock.test.js +95 -0
  144. package/dist/teste/List.test.d.ts +1 -0
  145. package/dist/teste/List.test.js +153 -0
  146. package/dist/teste/ModalVariantes.test.d.ts +1 -0
  147. package/dist/teste/ModalVariantes.test.js +151 -0
  148. package/dist/teste/NomeAcessivelSsr.test.d.ts +1 -0
  149. package/dist/teste/NomeAcessivelSsr.test.js +88 -0
  150. package/dist/teste/OrgChart.test.d.ts +1 -0
  151. package/dist/teste/OrgChart.test.js +162 -0
  152. package/dist/teste/OtpInput.test.d.ts +1 -0
  153. package/dist/teste/OtpInput.test.js +143 -0
  154. package/dist/teste/PopoverMaxHeight.test.d.ts +1 -0
  155. package/dist/teste/PopoverMaxHeight.test.js +38 -0
  156. package/dist/teste/RichTextEditor.test.d.ts +1 -0
  157. package/dist/teste/RichTextEditor.test.js +287 -0
  158. package/dist/teste/SelectPlacementFiltro.test.d.ts +1 -0
  159. package/dist/teste/SelectPlacementFiltro.test.js +88 -0
  160. package/dist/teste/Stack.test.d.ts +1 -0
  161. package/dist/teste/Stack.test.js +57 -0
  162. package/dist/teste/StatCardRotuloTopo.test.d.ts +1 -0
  163. package/dist/teste/StatCardRotuloTopo.test.js +34 -0
  164. package/dist/teste/TabsContadorFechavel.test.d.ts +1 -0
  165. package/dist/teste/TabsContadorFechavel.test.js +79 -0
  166. package/dist/teste/TooltipDark.test.d.ts +1 -0
  167. package/dist/teste/TooltipDark.test.js +31 -0
  168. package/dist/utils/calendario.d.ts +28 -0
  169. package/dist/utils/calendario.js +73 -0
  170. package/dist/utils/html-seguro.d.ts +27 -0
  171. package/dist/utils/html-seguro.js +97 -0
  172. package/dist/utils/paleta-de-cores.d.ts +26 -0
  173. package/dist/utils/paleta-de-cores.js +10 -0
  174. package/dist/utils/texto.d.ts +5 -0
  175. package/dist/utils/texto.js +5 -0
  176. package/package.json +3 -3
  177. package/src/index.ts +26 -0
  178. package/src/teste/AILabel.test.tsx +41 -0
  179. package/src/teste/AccordionConteudoLivre.test.tsx +39 -0
  180. package/src/teste/AtributosRepassados.test.tsx +73 -0
  181. package/src/teste/AttachmentCardIA.test.tsx +126 -0
  182. package/src/teste/AvatarGroupOverlap.test.tsx +74 -0
  183. package/src/teste/BadgeTag.test.tsx +64 -0
  184. package/src/teste/BoxItemFlex.test.tsx +81 -0
  185. package/src/teste/BoxLayout.test.tsx +50 -0
  186. package/src/teste/Button.test.tsx +39 -0
  187. package/src/teste/Calendar.test.tsx +282 -0
  188. package/src/teste/ChipsXs.test.tsx +41 -0
  189. package/src/teste/Coachmark.test.tsx +198 -0
  190. package/src/teste/ColorPicker.test.tsx +153 -0
  191. package/src/teste/DataTableCelulaLivre.test.tsx +103 -0
  192. package/src/teste/DataTableDocumento.test.tsx +102 -0
  193. package/src/teste/DatePicker.test.tsx +38 -0
  194. package/src/teste/FileInputTrigger.test.tsx +89 -0
  195. package/src/teste/IconPicker.test.tsx +145 -0
  196. package/src/teste/InputMascaraEAfins.test.tsx +124 -0
  197. package/src/teste/KbdCodeBlock.test.tsx +106 -0
  198. package/src/teste/List.test.tsx +269 -0
  199. package/src/teste/ModalVariantes.test.tsx +173 -0
  200. package/src/teste/NomeAcessivelSsr.test.tsx +110 -0
  201. package/src/teste/OrgChart.test.tsx +183 -0
  202. package/src/teste/OtpInput.test.tsx +163 -0
  203. package/src/teste/PopoverMaxHeight.test.tsx +47 -0
  204. package/src/teste/RichTextEditor.test.tsx +329 -0
  205. package/src/teste/SelectPlacementFiltro.test.tsx +110 -0
  206. package/src/teste/Stack.test.tsx +80 -0
  207. package/src/teste/StatCardRotuloTopo.test.tsx +44 -0
  208. package/src/teste/TabsContadorFechavel.test.tsx +144 -0
  209. package/src/teste/TooltipDark.test.tsx +51 -0
  210. package/src/utils/calendario.ts +78 -0
  211. package/src/utils/html-seguro.ts +94 -0
  212. package/src/utils/paleta-de-cores.ts +34 -0
  213. package/src/utils/texto.ts +5 -0
@@ -0,0 +1,103 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { render, screen, within } from "@testing-library/react";
3
+ import DataTable, { type DataTableColumn } from "../components/DataTable/DataTable";
4
+
5
+ /**
6
+ * Célula livre (`render` devolvendo nó) e linha de grupo (`rowGroup`).
7
+ * No Vue o equivalente é o slot `#cell-<key>`: ver `datatable-slots.test.mjs`.
8
+ */
9
+
10
+ const USUARIOS = [
11
+ { id: 1, nome: "Lucas Andrade", setor: "SADM", perfil: "Administrador" },
12
+ { id: 2, nome: "Marina Souza", setor: "SADM", perfil: "Nível 1" },
13
+ { id: 3, nome: "Rafael Pereira", setor: "SOBR", perfil: "Nível 2" },
14
+ ];
15
+
16
+ describe("DataTable · célula livre", () => {
17
+ it("render devolvendo elemento desenha o nó, não texto", () => {
18
+ const colunas: DataTableColumn[] = [
19
+ { key: "nome", label: "Usuário", render: (row) => <strong data-testid="nome">{row.nome}</strong> },
20
+ { key: "perfil", label: "Perfil" },
21
+ ];
22
+ render(<DataTable columns={colunas} data={USUARIOS} />);
23
+
24
+ const nos = screen.getAllByTestId("nome");
25
+ expect(nos).toHaveLength(3);
26
+ expect(nos[0].tagName).toBe("STRONG");
27
+ expect(nos[0]).toHaveTextContent("Lucas Andrade");
28
+ });
29
+
30
+ it("render recebe a linha e o índice", () => {
31
+ const colunas: DataTableColumn[] = [
32
+ { key: "nome", label: "Usuário", render: (row, i) => `${i + 1}. ${row.nome}` },
33
+ ];
34
+ render(<DataTable columns={colunas} data={USUARIOS} />);
35
+ expect(screen.getByText("2. Marina Souza")).toBeInTheDocument();
36
+ });
37
+
38
+ it("DataTableColumn é exportado e tipa a coluna com render de nó", () => {
39
+ const coluna: DataTableColumn = {
40
+ key: "acoes",
41
+ label: "Ações",
42
+ render: () => <button type="button">Editar</button>,
43
+ };
44
+ render(<DataTable columns={[coluna]} data={USUARIOS} />);
45
+ expect(screen.getAllByRole("button", { name: "Editar" })).toHaveLength(3);
46
+ });
47
+ });
48
+
49
+ describe("DataTable · linha de grupo (rowGroup)", () => {
50
+ const colunas: DataTableColumn[] = [
51
+ { key: "nome", label: "Usuário" },
52
+ { key: "perfil", label: "Perfil" },
53
+ ];
54
+
55
+ it("sem rowGroup não desenha linha de grupo", () => {
56
+ const { container } = render(<DataTable columns={colunas} data={USUARIOS} />);
57
+ expect(container.querySelector("th[scope='rowgroup']")).toBeNull();
58
+ });
59
+
60
+ it("rowGroup por campo abre um grupo por valor consecutivo", () => {
61
+ const { container } = render(<DataTable columns={colunas} data={USUARIOS} rowGroup="setor" />);
62
+ const grupos = container.querySelectorAll("th[scope='rowgroup']");
63
+ expect(Array.from(grupos).map((g) => g.textContent)).toEqual(["SADM", "SOBR"]);
64
+ // a linha de grupo atravessa as colunas todas
65
+ expect(grupos[0]).toHaveAttribute("colspan", "2");
66
+ });
67
+
68
+ it("rowGroup por função e colspan com selectable", () => {
69
+ const { container } = render(
70
+ <DataTable
71
+ columns={colunas}
72
+ data={USUARIOS}
73
+ selectable
74
+ rowGroup={(row) => (row.id === 3 ? undefined : "Equipe A")}
75
+ />,
76
+ );
77
+ const grupos = container.querySelectorAll("th[scope='rowgroup']");
78
+ expect(grupos).toHaveLength(1);
79
+ expect(grupos[0]).toHaveTextContent("Equipe A");
80
+ expect(grupos[0]).toHaveAttribute("colspan", "3");
81
+ });
82
+
83
+ it("renderRowGroup devolve nó", () => {
84
+ render(
85
+ <DataTable
86
+ columns={colunas}
87
+ data={USUARIOS}
88
+ rowGroup="setor"
89
+ renderRowGroup={(rotulo) => <em data-testid="grupo">{rotulo}!</em>}
90
+ />,
91
+ );
92
+ expect(screen.getAllByTestId("grupo").map((n) => n.textContent)).toEqual(["SADM!", "SOBR!"]);
93
+ });
94
+
95
+ it("a linha de grupo não entra na seleção por índice", async () => {
96
+ const { container } = render(
97
+ <DataTable columns={colunas} data={USUARIOS} rowGroup="setor" selectable selectedRows={[2]} />,
98
+ );
99
+ const selecionadas = container.querySelectorAll(".umds-datatable__row--selected");
100
+ expect(selecionadas).toHaveLength(1);
101
+ expect(within(selecionadas[0] as HTMLElement).getByText("Rafael Pereira")).toBeInTheDocument();
102
+ });
103
+ });
@@ -0,0 +1,102 @@
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 DataTable from "../components/DataTable/DataTable";
5
+ import DescriptionList from "../components/DescriptionList/DescriptionList";
6
+
7
+ const COLUNAS = [
8
+ { key: "descricao", label: "Descrição", sortable: true },
9
+ { key: "quantidade", label: "Quantidade", align: "right" as const },
10
+ ];
11
+ const DADOS = [
12
+ { descricao: "Diesel S10", quantidade: "5.760" },
13
+ { descricao: "Gasolina comum", quantidade: "1.200" },
14
+ ];
15
+
16
+ describe("DataTable variant=document", () => {
17
+ it("é a tabela de documento: classe própria, cabeçalho e linhas", () => {
18
+ const { container } = render(<DataTable variant="document" columns={COLUNAS} data={DADOS} />);
19
+ expect(container.querySelector(".umds-datatable--document")).not.toBeNull();
20
+ expect(screen.getAllByRole("columnheader")).toHaveLength(2);
21
+ expect(screen.getByText("Diesel S10")).toBeInTheDocument();
22
+ });
23
+
24
+ it("não ordena: sem botão de ordenar, sem aria-sort", () => {
25
+ render(<DataTable variant="document" columns={COLUNAS} data={DADOS} sortKey="descricao" sortDirection="asc" />);
26
+ expect(screen.queryByRole("button")).toBeNull();
27
+ for (const th of screen.getAllByRole("columnheader")) expect(th).not.toHaveAttribute("aria-sort");
28
+ });
29
+
30
+ it("não seleciona: selectable é ignorado", () => {
31
+ render(<DataTable variant="document" selectable columns={COLUNAS} data={DADOS} />);
32
+ expect(screen.queryByRole("checkbox")).toBeNull();
33
+ expect(screen.getAllByRole("columnheader")).toHaveLength(2);
34
+ });
35
+
36
+ it("linha não é clicável nem entra na ordem de Tab", async () => {
37
+ const usuario = userEvent.setup();
38
+ const aoClicar = vi.fn();
39
+ const { container } = render(<DataTable variant="document" columns={COLUNAS} data={DADOS} onRowClick={aoClicar} />);
40
+ const linha = container.querySelector("tbody tr") as HTMLElement;
41
+ expect(linha).not.toHaveAttribute("tabindex");
42
+ expect(linha).not.toHaveClass("umds-datatable__row--clickable");
43
+ await usuario.click(linha);
44
+ expect(aoClicar).not.toHaveBeenCalled();
45
+ });
46
+
47
+ it("a tabela de interface continua igual (ordena, seleciona, clica)", async () => {
48
+ const usuario = userEvent.setup();
49
+ const aoOrdenar = vi.fn();
50
+ render(<DataTable columns={COLUNAS} data={DADOS} selectable onSort={aoOrdenar} />);
51
+ expect(screen.getAllByRole("checkbox").length).toBeGreaterThan(0);
52
+ await usuario.click(screen.getByRole("button", { name: /Descrição/ }));
53
+ expect(aoOrdenar).toHaveBeenCalledWith("descricao", "asc");
54
+ });
55
+ });
56
+
57
+ describe("DescriptionList", () => {
58
+ const ITENS = [
59
+ { label: "Dotação", value: "02.031.0001" },
60
+ { label: "Empenho", value: "2026NE000184" },
61
+ { label: "Fonte", value: "" },
62
+ ];
63
+
64
+ it("é um dl com um dt e um dd por par", () => {
65
+ const { container } = render(<DescriptionList items={ITENS} ariaLabel="Dados de empenho" />);
66
+ const dl = container.querySelector("dl") as HTMLElement;
67
+ expect(dl).toHaveAttribute("aria-label", "Dados de empenho");
68
+ expect(dl.querySelectorAll("dt")).toHaveLength(3);
69
+ expect(dl.querySelectorAll("dd")).toHaveLength(3);
70
+ expect(dl.querySelector("dt")).toHaveTextContent("Dotação");
71
+ expect(dl.querySelector("dd")).toHaveTextContent("02.031.0001");
72
+ });
73
+
74
+ it("valor vazio mostra o traço, ou o texto de emptyValue", () => {
75
+ const { container, rerender } = render(<DescriptionList items={ITENS} />);
76
+ expect(container.querySelectorAll("dd")[2]).toHaveTextContent("—");
77
+ rerender(<DescriptionList items={ITENS} emptyValue="Não informado" />);
78
+ expect(container.querySelectorAll("dd")[2]).toHaveTextContent("Não informado");
79
+ });
80
+
81
+ it("inline é o padrão; fit encolhe", () => {
82
+ const { container, rerender } = render(<DescriptionList items={ITENS} />);
83
+ const dl = container.querySelector("dl") as HTMLElement;
84
+ expect(dl).toHaveClass("umds-description-list--inline");
85
+ expect(dl).not.toHaveClass("umds-description-list--fit");
86
+ rerender(<DescriptionList items={ITENS} fit />);
87
+ expect(container.querySelector("dl")).toHaveClass("umds-description-list--fit");
88
+ });
89
+
90
+ it("stacked ganha as colunas e a caixa do valor", () => {
91
+ const { container } = render(<DescriptionList items={ITENS} layout="stacked" columns={3} bordered />);
92
+ const dl = container.querySelector("dl") as HTMLElement;
93
+ expect(dl).toHaveClass("umds-description-list--stacked");
94
+ expect(dl).toHaveClass("umds-description-list--cols-3");
95
+ expect(dl).toHaveClass("umds-description-list--bordered");
96
+ });
97
+
98
+ it("sem itens, não desenha par nenhum", () => {
99
+ const { container } = render(<DescriptionList />);
100
+ expect(container.querySelectorAll("dt")).toHaveLength(0);
101
+ });
102
+ });
@@ -84,3 +84,41 @@ describe("DatePicker · teclado", () => {
84
84
  expect(screen.getByRole("combobox")).toHaveTextContent(/15\/\d{2}\/\d{4}/);
85
85
  });
86
86
  });
87
+
88
+ /**
89
+ * A story "Com valor" mostrava "Selecione o período" com `value="2025-12-31"`.
90
+ * Não era a story: no modo single o componente só lia `range`, e `value` e
91
+ * `defaultValue` eram declarados e nunca chegavam ao campo.
92
+ */
93
+ describe("DatePicker · valor no modo single", () => {
94
+ it("`value` aparece no campo, no formato pedido", () => {
95
+ render(
96
+ <DatePicker label="Vencimento" value="2025-12-31" format="DD/MM/YYYY" />,
97
+ );
98
+ expect(screen.getByRole("combobox")).toHaveTextContent("31/12/2025");
99
+ });
100
+
101
+ it("`defaultValue` semeia o campo no modo não-controlado", () => {
102
+ render(<DatePicker label="Vencimento" defaultValue="2025-06-15" />);
103
+ expect(screen.getByRole("combobox")).toHaveTextContent("15/06/2025");
104
+ });
105
+
106
+ it("`value` vazio no modo controlado mostra o placeholder", () => {
107
+ render(<DatePicker label="Vencimento" value="" placeholder="DD/MM/AAAA" />);
108
+ expect(screen.getByRole("combobox")).toHaveTextContent("DD/MM/AAAA");
109
+ });
110
+
111
+ it("o modo range continua lendo `range`, não `value`", () => {
112
+ render(
113
+ <DatePicker
114
+ mode="range"
115
+ label="Período"
116
+ value="2025-01-01"
117
+ range={{ from: "2025-03-01", to: "2025-03-10" }}
118
+ />,
119
+ );
120
+ expect(screen.getByRole("combobox")).toHaveTextContent(
121
+ "01/03/2025 – 10/03/2025",
122
+ );
123
+ });
124
+ });
@@ -0,0 +1,89 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { fireEvent, render, screen } from "@testing-library/react";
3
+ import FileInput from "../components/FileInput/FileInput";
4
+
5
+ const arquivo = (nome: string) => new File(["conteudo"], nome, { type: "application/pdf" });
6
+
7
+ describe("FileInput · variant trigger", () => {
8
+ it("renderiza o conteúdo como gatilho, sem a caixa de arrastar", () => {
9
+ const { container } = render(
10
+ <FileInput variant="trigger">
11
+ <button type="button">Anexar arquivo</button>
12
+ </FileInput>,
13
+ );
14
+ expect(screen.getByRole("button", { name: "Anexar arquivo" })).toBeInTheDocument();
15
+ expect(container.querySelector(".umds-file-input__drop-zone")).toBeNull();
16
+ expect(container.querySelector(".umds-file-input--trigger")).not.toBeNull();
17
+ });
18
+
19
+ it("clicar no conteúdo abre o seletor (clique no input nativo)", () => {
20
+ const { container } = render(
21
+ <FileInput variant="trigger">
22
+ <button type="button">Anexar arquivo</button>
23
+ </FileInput>,
24
+ );
25
+ const nativo = container.querySelector('input[type="file"]') as HTMLInputElement;
26
+ const clique = vi.spyOn(nativo, "click");
27
+ fireEvent.click(screen.getByRole("button", { name: "Anexar arquivo" }));
28
+ expect(clique).toHaveBeenCalledTimes(1);
29
+ });
30
+
31
+ it("disabled não abre o seletor", () => {
32
+ const { container } = render(
33
+ <FileInput variant="trigger" disabled>
34
+ <span>Alterar foto</span>
35
+ </FileInput>,
36
+ );
37
+ const nativo = container.querySelector('input[type="file"]') as HTMLInputElement;
38
+ const clique = vi.spyOn(nativo, "click");
39
+ fireEvent.click(screen.getByText("Alterar foto"));
40
+ expect(clique).not.toHaveBeenCalled();
41
+ });
42
+
43
+ it("escolher arquivos chama onChange, e accept e multiple chegam ao input", () => {
44
+ const aoMudar = vi.fn();
45
+ const { container } = render(
46
+ <FileInput variant="trigger" accept=".pdf" multiple onChange={aoMudar}>
47
+ <button type="button">Anexar</button>
48
+ </FileInput>,
49
+ );
50
+ const nativo = container.querySelector('input[type="file"]') as HTMLInputElement;
51
+ expect(nativo).toHaveAttribute("accept", ".pdf");
52
+ expect(nativo).toHaveAttribute("multiple");
53
+ fireEvent.change(nativo, { target: { files: [arquivo("a.pdf"), arquivo("b.pdf")] } });
54
+ expect(aoMudar).toHaveBeenCalledTimes(1);
55
+ expect(aoMudar.mock.calls[0][0].map((f: File) => f.name)).toEqual(["a.pdf", "b.pdf"]);
56
+ });
57
+
58
+ it("o input nativo não entra no Tab e tem nome acessível", () => {
59
+ const { container } = render(
60
+ <FileInput variant="trigger" label="Anexar arquivo">
61
+ <button type="button">Anexar</button>
62
+ </FileInput>,
63
+ );
64
+ const nativo = container.querySelector('input[type="file"]') as HTMLInputElement;
65
+ expect(nativo).toHaveAttribute("tabindex", "-1");
66
+ expect(nativo).toHaveAttribute("aria-label", "Anexar arquivo");
67
+ // o rótulo visível não aparece: o gatilho é o conteúdo
68
+ expect(container.querySelector(".umds-file-input__label")).toBeNull();
69
+ });
70
+
71
+ it("a lista de arquivos continua aparecendo no modo trigger", () => {
72
+ render(
73
+ <FileInput variant="trigger" files={[{ name: "oficio.pdf", size: 2048 }]}>
74
+ <button type="button">Anexar</button>
75
+ </FileInput>,
76
+ );
77
+ expect(screen.getByText("oficio.pdf")).toBeInTheDocument();
78
+ });
79
+
80
+ it("as variantes base e card seguem com a caixa", () => {
81
+ const { container } = render(
82
+ <>
83
+ <FileInput variant="base" />
84
+ <FileInput variant="card" />
85
+ </>,
86
+ );
87
+ expect(container.querySelectorAll(".umds-file-input__drop-zone")).toHaveLength(2);
88
+ });
89
+ });
@@ -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,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
+ });