@1doc/1ds-react 2.7.2 → 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.
- package/CHANGELOG.md +1280 -0
- package/dist/components/AIContent/AIContent.d.ts +10 -0
- package/dist/components/AIContent/AIContent.js +1 -1
- package/dist/components/AILabel/AILabel.d.ts +4 -0
- package/dist/components/AILabel/AILabel.js +2 -2
- package/dist/components/Accordion/Accordion.d.ts +6 -2
- package/dist/components/Alert/Alert.css +2 -0
- package/dist/components/Alert/Alert.d.ts +3 -1
- package/dist/components/Alert/Alert.js +7 -1
- package/dist/components/AppHeader/AppHeader.css +82 -8
- package/dist/components/AppHeader/AppHeader.d.ts +12 -1
- package/dist/components/AppHeader/AppHeader.js +186 -1
- package/dist/components/AppNavItem/AppNavItem.css +11 -3
- package/dist/components/AppShell/AppShell.css +9 -0
- package/dist/components/AppShell/AppShell.d.ts +8 -0
- package/dist/components/AppShell/AppShell.js +4 -1
- package/dist/components/AppSidebar/AppSidebar.css +1 -1
- package/dist/components/AppSidebar/AppSidebar.d.ts +4 -0
- package/dist/components/AppSidebar/AppSidebar.js +3 -1
- package/dist/components/AttachmentCard/AttachmentCard.css +17 -1
- package/dist/components/AttachmentCard/AttachmentCard.d.ts +29 -2
- package/dist/components/AttachmentCard/AttachmentCard.js +88 -20
- package/dist/components/Avatar/Avatar.css +7 -2
- package/dist/components/Avatar/AvatarGroup.d.ts +27 -0
- package/dist/components/Avatar/AvatarGroup.js +28 -4
- package/dist/components/Badge/Badge.css +61 -3
- package/dist/components/Badge/Badge.d.ts +14 -4
- package/dist/components/Badge/Badge.js +1 -0
- package/dist/components/BottomSheet/BottomSheet.css +1 -0
- package/dist/components/BottomSheet/BottomSheet.d.ts +8 -0
- package/dist/components/BottomSheet/BottomSheet.js +14 -2
- package/dist/components/Box/Box.css +83 -0
- package/dist/components/Box/Box.d.ts +82 -2
- package/dist/components/Box/Box.js +42 -2
- package/dist/components/Button/Button.css +29 -0
- package/dist/components/Button/Button.d.ts +4 -4
- package/dist/components/Button/Button.js +4 -1
- package/dist/components/Calendar/Calendar.css +245 -0
- package/dist/components/Calendar/Calendar.d.ts +125 -0
- package/dist/components/Calendar/Calendar.js +320 -0
- package/dist/components/Chart/BarChart.d.ts +6 -0
- package/dist/components/Chart/BarChart.js +3 -1
- package/dist/components/Chart/LineChart.d.ts +6 -0
- package/dist/components/Chart/LineChart.js +3 -1
- package/dist/components/Checkbox/Checkbox.css +4 -1
- package/dist/components/Chips/Chips.css +39 -0
- package/dist/components/Chips/Chips.d.ts +7 -2
- package/dist/components/Chips/Chips.js +6 -2
- package/dist/components/Coachmark/Coachmark.css +118 -0
- package/dist/components/Coachmark/Coachmark.d.ts +75 -0
- package/dist/components/Coachmark/Coachmark.js +321 -0
- package/dist/components/CodeBlock/CodeBlock.css +96 -0
- package/dist/components/CodeBlock/CodeBlock.d.ts +47 -0
- package/dist/components/CodeBlock/CodeBlock.js +103 -0
- package/dist/components/ColorPicker/ColorPicker.css +133 -0
- package/dist/components/ColorPicker/ColorPicker.d.ts +73 -0
- package/dist/components/ColorPicker/ColorPicker.js +219 -0
- package/dist/components/Combobox/Combobox.css +11 -3
- package/dist/components/ConfirmAIAction/ConfirmAIAction.d.ts +11 -1
- package/dist/components/ConfirmAIAction/ConfirmAIAction.js +6 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.css +3 -0
- package/dist/components/ConfirmDialog/ConfirmDialog.d.ts +14 -0
- package/dist/components/ConfirmDialog/ConfirmDialog.js +2 -2
- package/dist/components/DataTable/DataTable.css +80 -0
- package/dist/components/DataTable/DataTable.d.ts +23 -4
- package/dist/components/DataTable/DataTable.js +61 -15
- package/dist/components/DatePicker/DatePicker.css +8 -0
- package/dist/components/DatePicker/DatePicker.d.ts +4 -0
- package/dist/components/DatePicker/DatePicker.js +14 -1
- package/dist/components/DescriptionList/DescriptionList.css +113 -0
- package/dist/components/DescriptionList/DescriptionList.d.ts +59 -0
- package/dist/components/DescriptionList/DescriptionList.js +51 -0
- package/dist/components/Drawer/Drawer.css +1 -0
- package/dist/components/Dropdown/Dropdown.js +47 -29
- package/dist/components/FileInput/FileInput.css +24 -4
- package/dist/components/FileInput/FileInput.d.ts +22 -2
- package/dist/components/FileInput/FileInput.js +23 -2
- package/dist/components/Grid/Grid.js +5 -5
- package/dist/components/IconPicker/IconPicker.css +107 -0
- package/dist/components/IconPicker/IconPicker.d.ts +74 -0
- package/dist/components/IconPicker/IconPicker.js +194 -0
- package/dist/components/Input/Input.css +41 -3
- package/dist/components/Input/Input.d.ts +14 -1
- package/dist/components/Input/Input.js +72 -6
- package/dist/components/Kanban/Kanban.css +5 -0
- package/dist/components/Kanban/Kanban.d.ts +41 -0
- package/dist/components/Kanban/Kanban.js +59 -27
- package/dist/components/Kanban/KanbanCard.d.ts +2 -0
- package/dist/components/Kanban/KanbanCard.js +1 -1
- package/dist/components/Kanban/KanbanColumn.d.ts +2 -0
- package/dist/components/Kanban/KanbanColumn.js +3 -0
- package/dist/components/Kbd/Kbd.css +66 -0
- package/dist/components/Kbd/Kbd.d.ts +32 -0
- package/dist/components/Kbd/Kbd.js +40 -0
- package/dist/components/Link/Link.css +1 -1
- package/dist/components/List/List.css +136 -0
- package/dist/components/List/List.d.ts +82 -0
- package/dist/components/List/List.js +53 -0
- package/dist/components/List/ListItem.d.ts +85 -0
- package/dist/components/List/ListItem.js +81 -0
- package/dist/components/Modal/Modal.css +93 -8
- package/dist/components/Modal/Modal.d.ts +45 -7
- package/dist/components/Modal/Modal.js +106 -6
- package/dist/components/MultiSelect/MultiSelect.css +9 -1
- package/dist/components/MultiSelect/MultiSelect.js +45 -8
- package/dist/components/OrgChart/OrgChart.css +149 -0
- package/dist/components/OrgChart/OrgChart.d.ts +83 -0
- package/dist/components/OrgChart/OrgChart.js +297 -0
- package/dist/components/OtpInput/OtpInput.css +93 -0
- package/dist/components/OtpInput/OtpInput.d.ts +60 -0
- package/dist/components/OtpInput/OtpInput.js +215 -0
- package/dist/components/Pagination/Pagination.d.ts +6 -0
- package/dist/components/Pagination/Pagination.js +9 -1
- package/dist/components/PhoneInput/PhoneInput.css +9 -1
- package/dist/components/PhoneInput/PhoneInput.d.ts +10 -0
- package/dist/components/PhoneInput/PhoneInput.js +20 -3
- package/dist/components/Popover/Popover.d.ts +7 -0
- package/dist/components/Popover/Popover.js +11 -1
- package/dist/components/RichTextEditor/RichTextEditor.css +203 -0
- package/dist/components/RichTextEditor/RichTextEditor.d.ts +83 -0
- package/dist/components/RichTextEditor/RichTextEditor.js +494 -0
- package/dist/components/SegmentedControl/SegmentedControl.d.ts +2 -0
- package/dist/components/SegmentedControl/SegmentedControl.js +21 -8
- package/dist/components/Select/Select.css +71 -1
- package/dist/components/Select/Select.d.ts +12 -0
- package/dist/components/Select/Select.js +93 -10
- package/dist/components/SidePanel/SidePanel.css +4 -4
- package/dist/components/SidePanelItem/SidePanelItem.css +7 -5
- package/dist/components/SidePanelSearch/SidePanelSearch.css +2 -2
- package/dist/components/SidePanelSection/SidePanelSection.css +7 -5
- package/dist/components/Stack/Stack.css +49 -0
- package/dist/components/Stack/Stack.d.ts +65 -7
- package/dist/components/Stack/Stack.js +30 -2
- package/dist/components/StatCard/StatCard.css +73 -0
- package/dist/components/StatCard/StatCard.d.ts +17 -0
- package/dist/components/StatCard/StatCard.js +4 -1
- package/dist/components/Stepper/Stepper.css +4 -1
- package/dist/components/Stepper/Stepper.d.ts +8 -0
- package/dist/components/Stepper/Stepper.js +4 -4
- package/dist/components/Switch/Switch.css +1 -0
- package/dist/components/Tabs/Tab.d.ts +15 -0
- package/dist/components/Tabs/Tab.js +30 -1
- package/dist/components/Tabs/Tabs.css +132 -0
- package/dist/components/Tabs/Tabs.d.ts +7 -0
- package/dist/components/Tabs/Tabs.js +13 -2
- package/dist/components/Tag/Tag.css +85 -2
- package/dist/components/Tag/Tag.d.ts +13 -6
- package/dist/components/Tag/Tag.js +1 -0
- package/dist/components/Text/Text.d.ts +9 -1
- package/dist/components/Text/Text.js +2 -2
- package/dist/components/Textarea/Textarea.css +11 -3
- package/dist/components/TimePicker/TimePicker.css +8 -0
- package/dist/components/TimePicker/TimePicker.d.ts +6 -0
- package/dist/components/TimePicker/TimePicker.js +1 -1
- package/dist/components/Tooltip/Tooltip.css +40 -0
- package/dist/components/Tooltip/Tooltip.d.ts +11 -0
- package/dist/components/Tooltip/Tooltip.js +114 -11
- package/dist/index.d.ts +25 -0
- package/dist/index.js +14 -0
- package/dist/teste/AILabel.test.d.ts +1 -0
- package/dist/teste/AILabel.test.js +33 -0
- package/dist/teste/AccordionConteudoLivre.test.d.ts +1 -0
- package/dist/teste/AccordionConteudoLivre.test.js +29 -0
- package/dist/teste/Alert.test.d.ts +1 -0
- package/dist/teste/Alert.test.js +27 -0
- package/dist/teste/AtributosRepassados.test.d.ts +1 -0
- package/dist/teste/AtributosRepassados.test.js +52 -0
- package/dist/teste/AttachmentCardIA.test.d.ts +1 -0
- package/dist/teste/AttachmentCardIA.test.js +89 -0
- package/dist/teste/AvatarGroupOverlap.test.d.ts +1 -0
- package/dist/teste/AvatarGroupOverlap.test.js +56 -0
- package/dist/teste/BadgeTag.test.d.ts +1 -0
- package/dist/teste/BadgeTag.test.js +42 -0
- package/dist/teste/BottomSheet.test.d.ts +1 -0
- package/dist/teste/BottomSheet.test.js +23 -0
- package/dist/teste/BoxItemFlex.test.d.ts +1 -0
- package/dist/teste/BoxItemFlex.test.js +54 -0
- package/dist/teste/BoxLayout.test.d.ts +1 -0
- package/dist/teste/BoxLayout.test.js +35 -0
- package/dist/teste/Button.test.d.ts +1 -0
- package/dist/teste/Button.test.js +27 -0
- package/dist/teste/Calendar.test.d.ts +1 -0
- package/dist/teste/Calendar.test.js +221 -0
- package/dist/teste/ChipsXs.test.d.ts +1 -0
- package/dist/teste/ChipsXs.test.js +31 -0
- package/dist/teste/Coachmark.test.d.ts +1 -0
- package/dist/teste/Coachmark.test.js +149 -0
- package/dist/teste/ColorPicker.test.d.ts +1 -0
- package/dist/teste/ColorPicker.test.js +132 -0
- package/dist/teste/ConfirmAIAction.test.d.ts +1 -0
- package/dist/teste/ConfirmAIAction.test.js +33 -0
- package/dist/teste/DataTableCelulaLivre.test.d.ts +1 -0
- package/dist/teste/DataTableCelulaLivre.test.js +76 -0
- package/dist/teste/DataTableDocumento.test.d.ts +1 -0
- package/dist/teste/DataTableDocumento.test.js +92 -0
- package/dist/teste/DatePicker.test.js +23 -0
- package/dist/teste/Dropdown.test.js +111 -0
- package/dist/teste/FileInputTrigger.test.d.ts +1 -0
- package/dist/teste/FileInputTrigger.test.js +53 -0
- package/dist/teste/IconPicker.test.d.ts +1 -0
- package/dist/teste/IconPicker.test.js +130 -0
- package/dist/teste/InputMascaraEAfins.test.d.ts +1 -0
- package/dist/teste/InputMascaraEAfins.test.js +99 -0
- package/dist/teste/KbdCodeBlock.test.d.ts +1 -0
- package/dist/teste/KbdCodeBlock.test.js +95 -0
- package/dist/teste/List.test.d.ts +1 -0
- package/dist/teste/List.test.js +153 -0
- package/dist/teste/Modal.test.d.ts +1 -0
- package/dist/teste/Modal.test.js +26 -0
- package/dist/teste/ModalVariantes.test.d.ts +1 -0
- package/dist/teste/ModalVariantes.test.js +151 -0
- package/dist/teste/MultiSelectErro.test.d.ts +1 -0
- package/dist/teste/MultiSelectErro.test.js +62 -0
- package/dist/teste/NomeAcessivelSsr.test.d.ts +1 -0
- package/dist/teste/NomeAcessivelSsr.test.js +88 -0
- package/dist/teste/OrgChart.test.d.ts +1 -0
- package/dist/teste/OrgChart.test.js +162 -0
- package/dist/teste/OtpInput.test.d.ts +1 -0
- package/dist/teste/OtpInput.test.js +143 -0
- package/dist/teste/PopoverMaxHeight.test.d.ts +1 -0
- package/dist/teste/PopoverMaxHeight.test.js +38 -0
- package/dist/teste/RichTextEditor.test.d.ts +1 -0
- package/dist/teste/RichTextEditor.test.js +287 -0
- package/dist/teste/RotulosTraduziveis.test.d.ts +1 -0
- package/dist/teste/RotulosTraduziveis.test.js +405 -0
- package/dist/teste/SegmentedControl.test.js +33 -0
- package/dist/teste/Select.test.d.ts +1 -0
- package/dist/teste/Select.test.js +62 -0
- package/dist/teste/SelectPlacementFiltro.test.d.ts +1 -0
- package/dist/teste/SelectPlacementFiltro.test.js +88 -0
- package/dist/teste/Stack.test.d.ts +1 -0
- package/dist/teste/Stack.test.js +57 -0
- package/dist/teste/StatCardRotuloTopo.test.d.ts +1 -0
- package/dist/teste/StatCardRotuloTopo.test.js +34 -0
- package/dist/teste/TabsContadorFechavel.test.d.ts +1 -0
- package/dist/teste/TabsContadorFechavel.test.js +79 -0
- package/dist/teste/Tooltip.test.d.ts +1 -0
- package/dist/teste/Tooltip.test.js +109 -0
- package/dist/teste/TooltipDark.test.d.ts +1 -0
- package/dist/teste/TooltipDark.test.js +31 -0
- package/dist/utils/calendario.d.ts +28 -0
- package/dist/utils/calendario.js +73 -0
- package/dist/utils/chart.d.ts +4 -0
- package/dist/utils/chart.js +3 -2
- package/dist/utils/html-seguro.d.ts +27 -0
- package/dist/utils/html-seguro.js +97 -0
- package/dist/utils/id.d.ts +2 -0
- package/dist/utils/id.js +23 -0
- package/dist/utils/paleta-de-cores.d.ts +26 -0
- package/dist/utils/paleta-de-cores.js +10 -0
- package/dist/utils/texto.d.ts +5 -0
- package/dist/utils/texto.js +5 -0
- package/package.json +3 -3
- package/src/index.ts +26 -0
- package/src/teste/AILabel.test.tsx +41 -0
- package/src/teste/AccordionConteudoLivre.test.tsx +39 -0
- package/src/teste/Alert.test.tsx +32 -0
- package/src/teste/AtributosRepassados.test.tsx +73 -0
- package/src/teste/AttachmentCardIA.test.tsx +126 -0
- package/src/teste/AvatarGroupOverlap.test.tsx +74 -0
- package/src/teste/BadgeTag.test.tsx +64 -0
- package/src/teste/BottomSheet.test.tsx +25 -0
- package/src/teste/BoxItemFlex.test.tsx +81 -0
- package/src/teste/BoxLayout.test.tsx +50 -0
- package/src/teste/Button.test.tsx +39 -0
- package/src/teste/Calendar.test.tsx +282 -0
- package/src/teste/ChipsXs.test.tsx +41 -0
- package/src/teste/Coachmark.test.tsx +198 -0
- package/src/teste/ColorPicker.test.tsx +153 -0
- package/src/teste/ConfirmAIAction.test.tsx +41 -0
- package/src/teste/DataTableCelulaLivre.test.tsx +103 -0
- package/src/teste/DataTableDocumento.test.tsx +102 -0
- package/src/teste/DatePicker.test.tsx +38 -0
- package/src/teste/Dropdown.test.tsx +146 -0
- package/src/teste/FileInputTrigger.test.tsx +89 -0
- package/src/teste/IconPicker.test.tsx +145 -0
- package/src/teste/InputMascaraEAfins.test.tsx +124 -0
- package/src/teste/KbdCodeBlock.test.tsx +106 -0
- package/src/teste/List.test.tsx +269 -0
- package/src/teste/Modal.test.tsx +28 -0
- package/src/teste/ModalVariantes.test.tsx +173 -0
- package/src/teste/MultiSelectErro.test.tsx +97 -0
- package/src/teste/NomeAcessivelSsr.test.tsx +110 -0
- package/src/teste/OrgChart.test.tsx +183 -0
- package/src/teste/OtpInput.test.tsx +163 -0
- package/src/teste/PopoverMaxHeight.test.tsx +47 -0
- package/src/teste/RichTextEditor.test.tsx +329 -0
- package/src/teste/RotulosTraduziveis.test.tsx +545 -0
- package/src/teste/SegmentedControl.test.tsx +57 -0
- package/src/teste/Select.test.tsx +97 -0
- package/src/teste/SelectPlacementFiltro.test.tsx +110 -0
- package/src/teste/Stack.test.tsx +80 -0
- package/src/teste/StatCardRotuloTopo.test.tsx +44 -0
- package/src/teste/TabsContadorFechavel.test.tsx +144 -0
- package/src/teste/Tooltip.test.tsx +219 -0
- package/src/teste/TooltipDark.test.tsx +51 -0
- package/src/utils/calendario.ts +78 -0
- package/src/utils/chart.ts +7 -2
- package/src/utils/html-seguro.ts +94 -0
- package/src/utils/id.ts +24 -0
- package/src/utils/paleta-de-cores.ts +34 -0
- package/src/utils/texto.ts +5 -0
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import Modal from "../components/Modal/Modal";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Modal sem `title` saía com `role="dialog"` anônimo: o leitor de tela dizia
|
|
7
|
+
* só "diálogo". Com título, quem nomeia é o `aria-labelledby`; sem título,
|
|
8
|
+
* `ariaLabel`; sem nenhum dos dois, o genérico "Diálogo".
|
|
9
|
+
*/
|
|
10
|
+
describe("Modal · nome acessível", () => {
|
|
11
|
+
it("com title, é nomeado pelo título e ignora ariaLabel", () => {
|
|
12
|
+
render(<Modal open inline title="Admitir colaborador" ariaLabel="Outro nome" />);
|
|
13
|
+
const dialogo = screen.getByRole("dialog", { name: "Admitir colaborador" });
|
|
14
|
+
expect(dialogo).toHaveAttribute("aria-labelledby");
|
|
15
|
+
expect(dialogo).not.toHaveAttribute("aria-label");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("sem title, usa ariaLabel", () => {
|
|
19
|
+
render(<Modal open inline ariaLabel="Pré-visualização do documento" />);
|
|
20
|
+
const dialogo = screen.getByRole("dialog", { name: "Pré-visualização do documento" });
|
|
21
|
+
expect(dialogo).not.toHaveAttribute("aria-labelledby");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("sem title e sem ariaLabel, nunca sai anônimo", () => {
|
|
25
|
+
render(<Modal open inline />);
|
|
26
|
+
expect(screen.getByRole("dialog", { name: "Diálogo" })).toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
3
|
+
import Modal from "../components/Modal/Modal";
|
|
4
|
+
import Select from "../components/Select/Select";
|
|
5
|
+
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
vi.unstubAllGlobals();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
/** Troca `window.matchMedia` por um que responde `matches` e deixa disparar `change`. */
|
|
11
|
+
function simularTela(matches: boolean) {
|
|
12
|
+
const ouvintes = new Set<(e: { matches: boolean }) => void>();
|
|
13
|
+
vi.stubGlobal("matchMedia", (consulta: string) => ({
|
|
14
|
+
matches,
|
|
15
|
+
media: consulta,
|
|
16
|
+
addEventListener: (_: string, fn: any) => ouvintes.add(fn),
|
|
17
|
+
removeEventListener: (_: string, fn: any) => ouvintes.delete(fn),
|
|
18
|
+
}));
|
|
19
|
+
return (novo: boolean) => ouvintes.forEach((fn) => fn({ matches: novo }));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("Modal · variant primary", () => {
|
|
23
|
+
it("fica no modal base (três faixas), com a classe do cabeçalho cheio", () => {
|
|
24
|
+
render(<Modal open inline variant="primary" title="Resolver tarefa" />);
|
|
25
|
+
const dialogo = screen.getByRole("dialog", { name: "Resolver tarefa" });
|
|
26
|
+
expect(dialogo).toHaveClass("umds-modal--primary", "umds-modal--base");
|
|
27
|
+
expect(dialogo).not.toHaveClass("umds-modal--feedback");
|
|
28
|
+
expect(dialogo.querySelector(".umds-modal__header")).not.toBeNull();
|
|
29
|
+
expect(dialogo.querySelector(".umds-modal__icon")).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("positive continua sendo diálogo de feedback", () => {
|
|
33
|
+
render(<Modal open inline variant="positive" title="Pronto" />);
|
|
34
|
+
expect(screen.getByRole("dialog")).toHaveClass("umds-modal--feedback");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("Modal · width e maxWidth", () => {
|
|
39
|
+
it("escreve a largura e, sem maxWidth, solta o teto do size", () => {
|
|
40
|
+
render(<Modal open inline title="x" width="600px" />);
|
|
41
|
+
const dialogo = screen.getByRole("dialog");
|
|
42
|
+
expect(dialogo.style.width).toBe("600px");
|
|
43
|
+
expect(dialogo.style.maxWidth).toBe("100%");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("maxWidth explícito vale", () => {
|
|
47
|
+
render(<Modal open inline title="x" width="90%" maxWidth="720px" />);
|
|
48
|
+
expect(screen.getByRole("dialog").style.maxWidth).toBe("720px");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("sem width nem maxWidth não escreve style", () => {
|
|
52
|
+
render(<Modal open inline title="x" />);
|
|
53
|
+
expect(screen.getByRole("dialog").getAttribute("style")).toBeNull();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("Modal · Escape", () => {
|
|
58
|
+
it("fecha com Escape no borbulho", () => {
|
|
59
|
+
const aoFechar = vi.fn();
|
|
60
|
+
render(<Modal open inline title="x" onClose={aoFechar} />);
|
|
61
|
+
fireEvent.keyDown(document.body, { key: "Escape" });
|
|
62
|
+
expect(aoFechar).toHaveBeenCalledTimes(1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("não fecha quando outro componente já consumiu a tecla (defaultPrevented)", () => {
|
|
66
|
+
const aoFechar = vi.fn();
|
|
67
|
+
render(<Modal open inline title="x" onClose={aoFechar} />);
|
|
68
|
+
const consumidor = document.createElement("div");
|
|
69
|
+
consumidor.addEventListener("keydown", (e) => e.preventDefault());
|
|
70
|
+
document.body.appendChild(consumidor);
|
|
71
|
+
fireEvent.keyDown(consumidor, { key: "Escape" });
|
|
72
|
+
expect(aoFechar).not.toHaveBeenCalled();
|
|
73
|
+
consumidor.remove();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("Select aberto dentro do modal fecha só a lista, e o modal fica", () => {
|
|
77
|
+
const aoFechar = vi.fn();
|
|
78
|
+
render(
|
|
79
|
+
<Modal open inline title="x" onClose={aoFechar}>
|
|
80
|
+
<Select label="Categoria" options={[{ label: "Administrativa", value: "adm" }]} />
|
|
81
|
+
</Modal>,
|
|
82
|
+
);
|
|
83
|
+
const gatilho = screen.getByRole("combobox");
|
|
84
|
+
fireEvent.click(gatilho);
|
|
85
|
+
expect(gatilho).toHaveAttribute("aria-expanded", "true");
|
|
86
|
+
fireEvent.keyDown(gatilho, { key: "Escape" });
|
|
87
|
+
expect(gatilho).toHaveAttribute("aria-expanded", "false");
|
|
88
|
+
expect(aoFechar).not.toHaveBeenCalled();
|
|
89
|
+
// Com o Select fechado, o Esc seguinte é do modal.
|
|
90
|
+
fireEvent.keyDown(gatilho, { key: "Escape" });
|
|
91
|
+
expect(aoFechar).toHaveBeenCalledTimes(1);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("Modal · controles de janela escolhidos um a um", () => {
|
|
96
|
+
it("true liga os quatro, como antes", () => {
|
|
97
|
+
render(<Modal open inline title="x" windowControls />);
|
|
98
|
+
for (const nome of ["Modo normal", "Expandir", "Tela cheia", "Minimizar"]) {
|
|
99
|
+
expect(screen.getByRole("button", { name: nome })).toBeInTheDocument();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("lista escolhe só os pedidos, sem minimizar", () => {
|
|
104
|
+
render(<Modal open inline title="x" windowControls={["expand", "fullscreen"]} />);
|
|
105
|
+
expect(screen.getByRole("button", { name: "Expandir" })).toBeInTheDocument();
|
|
106
|
+
expect(screen.getByRole("button", { name: "Tela cheia" })).toBeInTheDocument();
|
|
107
|
+
expect(screen.queryByRole("button", { name: "Minimizar" })).toBeNull();
|
|
108
|
+
expect(screen.queryByRole("button", { name: "Modo normal" })).toBeNull();
|
|
109
|
+
expect(screen.getByRole("button", { name: "Fechar" })).toBeInTheDocument();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("sem windowControls só há o ×", () => {
|
|
113
|
+
render(<Modal open inline title="x" />);
|
|
114
|
+
expect(screen.queryByRole("button", { name: "Expandir" })).toBeNull();
|
|
115
|
+
expect(screen.getByRole("button", { name: "Fechar" })).toBeInTheDocument();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("sem o botão normal, clicar de novo no modo ativo volta ao normal; e o normal usa o tamanho do modal", () => {
|
|
119
|
+
render(<Modal open inline title="x" width="600px" windowControls={["expand"]} />);
|
|
120
|
+
const dialogo = screen.getByRole("dialog");
|
|
121
|
+
const expandir = screen.getByRole("button", { name: "Expandir" });
|
|
122
|
+
expect(dialogo).not.toHaveClass("umds-modal--janela-normal");
|
|
123
|
+
expect(dialogo.style.width).toBe("600px");
|
|
124
|
+
fireEvent.click(expandir);
|
|
125
|
+
expect(dialogo).toHaveClass("umds-modal--janela-expanded");
|
|
126
|
+
expect(dialogo.style.width).toBe("");
|
|
127
|
+
fireEvent.click(expandir);
|
|
128
|
+
expect(dialogo).not.toHaveClass("umds-modal--janela-expanded");
|
|
129
|
+
expect(dialogo.style.width).toBe("600px");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("minimizar chama onMinimize", () => {
|
|
133
|
+
const aoMinimizar = vi.fn();
|
|
134
|
+
render(<Modal open inline title="x" windowControls={["minimize"]} onMinimize={aoMinimizar} />);
|
|
135
|
+
fireEvent.click(screen.getByRole("button", { name: "Minimizar" }));
|
|
136
|
+
expect(aoMinimizar).toHaveBeenCalledTimes(1);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe("Modal · responsive", () => {
|
|
141
|
+
it("em tela larga continua centralizado", () => {
|
|
142
|
+
simularTela(false);
|
|
143
|
+
render(<Modal open inline title="x" responsive />);
|
|
144
|
+
expect(screen.getByRole("dialog")).not.toHaveClass("umds-modal--sheet");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("em tela estreita vira painel de baixo, sem controles de janela e sem a largura livre", () => {
|
|
148
|
+
simularTela(true);
|
|
149
|
+
render(<Modal open inline title="x" responsive width="600px" windowControls />);
|
|
150
|
+
const dialogo = screen.getByRole("dialog");
|
|
151
|
+
expect(dialogo).toHaveClass("umds-modal--sheet");
|
|
152
|
+
expect(dialogo.style.width).toBe("");
|
|
153
|
+
expect(screen.queryByRole("button", { name: "Expandir" })).toBeNull();
|
|
154
|
+
expect(dialogo.querySelector(".umds-modal__grabber")).not.toBeNull();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("acompanha a mudança de tamanho da tela", () => {
|
|
158
|
+
const mudar = simularTela(false);
|
|
159
|
+
render(<Modal open inline title="x" responsive />);
|
|
160
|
+
const dialogo = screen.getByRole("dialog");
|
|
161
|
+
expect(dialogo).not.toHaveClass("umds-modal--sheet");
|
|
162
|
+
act(() => mudar(true));
|
|
163
|
+
expect(screen.getByRole("dialog")).toHaveClass("umds-modal--sheet");
|
|
164
|
+
act(() => mudar(false));
|
|
165
|
+
expect(screen.getByRole("dialog")).not.toHaveClass("umds-modal--sheet");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("sem responsive nunca vira painel", () => {
|
|
169
|
+
simularTela(true);
|
|
170
|
+
render(<Modal open inline title="x" />);
|
|
171
|
+
expect(screen.getByRole("dialog")).not.toHaveClass("umds-modal--sheet");
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { renderToString } from "react-dom/server";
|
|
4
|
+
import MultiSelect from "../components/MultiSelect/MultiSelect";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* O foco do MultiSelect cai no trigger `role="combobox"`, não num `<select>`
|
|
8
|
+
* nativo. Então nada liga a mensagem de erro ao campo de graça: se o
|
|
9
|
+
* `aria-describedby` não estiver no combobox, o leitor de tela anuncia
|
|
10
|
+
* "Setor, caixa de combinação" e a pessoa nunca ouve POR QUE o campo está
|
|
11
|
+
* vermelho.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const OPCOES = [
|
|
15
|
+
{ label: "Recursos Humanos", value: "rh" },
|
|
16
|
+
{ label: "Financeiro", value: "fin" },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
describe("MultiSelect · erro e apoio ligados ao campo", () => {
|
|
20
|
+
it("a mensagem de erro descreve o combobox e o marca inválido", () => {
|
|
21
|
+
render(
|
|
22
|
+
<MultiSelect label="Setor" options={OPCOES} errorMessage="Escolha um setor" />,
|
|
23
|
+
);
|
|
24
|
+
const campo = screen.getByRole("combobox", { name: "Setor" });
|
|
25
|
+
expect(campo).toHaveAccessibleDescription("Escolha um setor");
|
|
26
|
+
expect(campo).toHaveAttribute("aria-invalid", "true");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("erro e texto de apoio entram os dois, erro primeiro", () => {
|
|
30
|
+
render(
|
|
31
|
+
<MultiSelect
|
|
32
|
+
label="Setor"
|
|
33
|
+
options={OPCOES}
|
|
34
|
+
errorMessage="Obrigatório"
|
|
35
|
+
helperText="Onde a pessoa trabalha"
|
|
36
|
+
/>,
|
|
37
|
+
);
|
|
38
|
+
expect(screen.getByRole("combobox")).toHaveAccessibleDescription(
|
|
39
|
+
"Obrigatório Onde a pessoa trabalha",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("o apelido antigo `description` também é ligado", () => {
|
|
44
|
+
render(<MultiSelect label="Setor" options={OPCOES} description="Opcional" />);
|
|
45
|
+
expect(screen.getByRole("combobox")).toHaveAccessibleDescription(
|
|
46
|
+
"Opcional",
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("sem erro nem apoio, nada de aria-describedby ou aria-invalid", () => {
|
|
51
|
+
render(<MultiSelect label="Setor" options={OPCOES} />);
|
|
52
|
+
const campo = screen.getByRole("combobox");
|
|
53
|
+
expect(campo).not.toHaveAttribute("aria-describedby");
|
|
54
|
+
expect(campo).not.toHaveAttribute("aria-invalid");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("`invalid` sem mensagem também marca aria-invalid", () => {
|
|
58
|
+
render(<MultiSelect label="Setor" options={OPCOES} invalid />);
|
|
59
|
+
expect(screen.getByRole("combobox")).toHaveAttribute(
|
|
60
|
+
"aria-invalid",
|
|
61
|
+
"true",
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("com `id`, os ids internos derivam dele", () => {
|
|
66
|
+
render(
|
|
67
|
+
<MultiSelect id="setor" label="Setor" options={OPCOES} errorMessage="Erro" />,
|
|
68
|
+
);
|
|
69
|
+
expect(screen.getByRole("combobox")).toHaveAttribute(
|
|
70
|
+
"aria-describedby",
|
|
71
|
+
"setor-error",
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("MultiSelect · ids na renderização do servidor", () => {
|
|
77
|
+
/**
|
|
78
|
+
* Id sorteado no render sai diferente no servidor e no cliente: a
|
|
79
|
+
* hidratação diverge e o describedby do HTML aponta para o nada. Duas
|
|
80
|
+
* renderizações de servidor têm de sair byte a byte iguais.
|
|
81
|
+
*/
|
|
82
|
+
it("o HTML do servidor é determinístico", () => {
|
|
83
|
+
const html = () =>
|
|
84
|
+
renderToString(
|
|
85
|
+
<MultiSelect label="Setor" options={OPCOES} errorMessage="Erro" />,
|
|
86
|
+
);
|
|
87
|
+
expect(html()).toBe(html());
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("com `id`, o servidor já manda o describedby", () => {
|
|
91
|
+
const html = renderToString(
|
|
92
|
+
<MultiSelect id="setor" label="Setor" options={OPCOES} errorMessage="Erro" />,
|
|
93
|
+
);
|
|
94
|
+
expect(html).toContain('aria-describedby="setor-error"');
|
|
95
|
+
expect(html).toContain('id="setor-error"');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { act, screen } from "@testing-library/react";
|
|
3
|
+
import { renderToString } from "react-dom/server";
|
|
4
|
+
import { hydrateRoot } from "react-dom/client";
|
|
5
|
+
import Select from "../components/Select/Select";
|
|
6
|
+
import MultiSelect from "../components/MultiSelect/MultiSelect";
|
|
7
|
+
import SegmentedControl from "../components/SegmentedControl/SegmentedControl";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Select, MultiSelect e SegmentedControl nascem SEM id automático (utils/id):
|
|
11
|
+
* o id só é gerado no onMount, para o servidor e a primeira renderização do
|
|
12
|
+
* cliente saírem idênticos. Só que o nome acessível deles vinha de
|
|
13
|
+
* `aria-labelledby` apontando para esse id — no HTML do servidor, e até a
|
|
14
|
+
* hidratação, o combobox ficava sem nome (axe `aria-input-field-name`, visto
|
|
15
|
+
* no portal Astro com `client:visible`).
|
|
16
|
+
*
|
|
17
|
+
* Aqui o HTML sai do `renderToString`, que não dispara o onMount, e vai para
|
|
18
|
+
* o DOM do jsdom como o navegador receberia. O nome é conferido pelo cálculo
|
|
19
|
+
* do Testing Library, o mesmo algoritmo de nome acessível que o axe usa.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const OPCOES = [
|
|
23
|
+
{ label: "Recursos Humanos", value: "rh" },
|
|
24
|
+
{ label: "Financeiro", value: "fin" },
|
|
25
|
+
];
|
|
26
|
+
const POR_PAGINA = [
|
|
27
|
+
{ label: "10", value: "10" },
|
|
28
|
+
{ label: "25", value: "25" },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
function comoDoServidor(html: string) {
|
|
32
|
+
const raiz = document.createElement("div");
|
|
33
|
+
raiz.innerHTML = html;
|
|
34
|
+
document.body.appendChild(raiz);
|
|
35
|
+
return raiz;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe("Nome acessível no HTML do servidor", () => {
|
|
39
|
+
// As raízes são montadas à mão (fora do render), e o cleanup global não as vê.
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
document.body.innerHTML = "";
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("Select: o combobox tem o nome do label antes do mount", () => {
|
|
45
|
+
comoDoServidor(renderToString(<Select label="Setor" options={OPCOES} />));
|
|
46
|
+
const campo = screen.getByRole("combobox", { name: "Setor" });
|
|
47
|
+
expect(campo).not.toHaveAttribute("aria-labelledby");
|
|
48
|
+
expect(campo).toHaveAttribute("aria-label", "Setor");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("Select: `ariaLabel` continua valendo sobre o label", () => {
|
|
52
|
+
comoDoServidor(
|
|
53
|
+
renderToString(<Select label="Setor" ariaLabel="Setor do servidor" options={OPCOES} />),
|
|
54
|
+
);
|
|
55
|
+
expect(
|
|
56
|
+
screen.getByRole("combobox", { name: "Setor do servidor" }),
|
|
57
|
+
).toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("Select com `id`: o servidor já liga pelo aria-labelledby", () => {
|
|
61
|
+
const html = renderToString(<Select id="setor" label="Setor" options={OPCOES} />);
|
|
62
|
+
expect(html).toContain('aria-labelledby="setor-label"');
|
|
63
|
+
expect(html).not.toContain('aria-label="Setor"');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("MultiSelect: o combobox tem o nome do label antes do mount", () => {
|
|
67
|
+
comoDoServidor(renderToString(<MultiSelect label="Setores" options={OPCOES} />));
|
|
68
|
+
expect(screen.getByRole("combobox", { name: "Setores" })).toBeInTheDocument();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("SegmentedControl: grupo e segmentos têm nome antes do mount", () => {
|
|
72
|
+
comoDoServidor(
|
|
73
|
+
renderToString(<SegmentedControl label="Itens" options={POR_PAGINA} />),
|
|
74
|
+
);
|
|
75
|
+
expect(screen.getByRole("radiogroup", { name: "Itens" })).toBeInTheDocument();
|
|
76
|
+
expect(screen.getByRole("radio", { name: "10" })).toBeInTheDocument();
|
|
77
|
+
expect(screen.getByRole("radio", { name: "25" })).toBeInTheDocument();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("hidrata sem divergência e, montado, volta ao aria-labelledby", async () => {
|
|
81
|
+
const arvore = (
|
|
82
|
+
<div>
|
|
83
|
+
<Select label="Setor" options={OPCOES} />
|
|
84
|
+
<MultiSelect label="Setores" options={OPCOES} />
|
|
85
|
+
<SegmentedControl label="Itens" options={POR_PAGINA} />
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
const raiz = comoDoServidor(renderToString(arvore));
|
|
89
|
+
const erro = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
90
|
+
const avisosDeHidratacao: unknown[] = [];
|
|
91
|
+
await act(async () => {
|
|
92
|
+
hydrateRoot(raiz, arvore, {
|
|
93
|
+
onRecoverableError: (e) => avisosDeHidratacao.push(e),
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
const chamadas = erro.mock.calls.map((c) => String(c[0]));
|
|
97
|
+
erro.mockRestore();
|
|
98
|
+
expect(avisosDeHidratacao).toEqual([]);
|
|
99
|
+
expect(chamadas.filter((c) => /hydrat|did not match/i.test(c))).toEqual([]);
|
|
100
|
+
|
|
101
|
+
const select = screen.getByRole("combobox", { name: "Setor" });
|
|
102
|
+
expect(select).toHaveAttribute("aria-labelledby");
|
|
103
|
+
expect(select).not.toHaveAttribute("aria-label");
|
|
104
|
+
const multi = screen.getByRole("combobox", { name: "Setores" });
|
|
105
|
+
expect(multi).toHaveAttribute("aria-labelledby");
|
|
106
|
+
const grupo = screen.getByRole("radiogroup", { name: "Itens" });
|
|
107
|
+
expect(grupo).toHaveAttribute("aria-labelledby");
|
|
108
|
+
expect(screen.getByRole("radio", { name: "10" })).not.toHaveAttribute("aria-label");
|
|
109
|
+
});
|
|
110
|
+
});
|