@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.
- package/CHANGELOG.md +1106 -0
- package/dist/components/AILabel/AILabel.js +1 -1
- package/dist/components/Accordion/Accordion.d.ts +6 -2
- package/dist/components/AttachmentCard/AttachmentCard.css +16 -0
- package/dist/components/AttachmentCard/AttachmentCard.d.ts +19 -2
- package/dist/components/AttachmentCard/AttachmentCard.js +40 -3
- package/dist/components/Avatar/Avatar.css +7 -2
- package/dist/components/Avatar/AvatarGroup.d.ts +17 -0
- package/dist/components/Avatar/AvatarGroup.js +22 -8
- package/dist/components/Badge/Badge.css +54 -2
- package/dist/components/Badge/Badge.d.ts +14 -4
- package/dist/components/Badge/Badge.js +1 -0
- 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/Chips/Chips.css +38 -0
- package/dist/components/Chips/Chips.d.ts +5 -2
- package/dist/components/Chips/Chips.js +2 -1
- 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 +8 -0
- package/dist/components/DataTable/DataTable.css +80 -0
- package/dist/components/DataTable/DataTable.d.ts +19 -4
- package/dist/components/DataTable/DataTable.js +59 -15
- package/dist/components/DatePicker/DatePicker.css +8 -0
- package/dist/components/DatePicker/DatePicker.js +10 -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/FileInput/FileInput.css +16 -0
- package/dist/components/FileInput/FileInput.d.ts +4 -2
- package/dist/components/FileInput/FileInput.js +8 -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 +38 -0
- package/dist/components/Input/Input.d.ts +10 -1
- package/dist/components/Input/Input.js +66 -5
- package/dist/components/Kanban/Kanban.css +4 -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/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 +80 -0
- package/dist/components/Modal/Modal.d.ts +33 -6
- package/dist/components/Modal/Modal.js +101 -6
- package/dist/components/MultiSelect/MultiSelect.css +8 -0
- package/dist/components/MultiSelect/MultiSelect.js +10 -1
- 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/PhoneInput/PhoneInput.css +8 -0
- 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.js +7 -2
- package/dist/components/Select/Select.css +70 -0
- package/dist/components/Select/Select.d.ts +12 -0
- package/dist/components/Select/Select.js +59 -4
- 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/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.js +2 -2
- package/dist/components/Textarea/Textarea.css +8 -0
- package/dist/components/TimePicker/TimePicker.css +8 -0
- package/dist/components/Tooltip/Tooltip.css +25 -0
- package/dist/components/Tooltip/Tooltip.d.ts +11 -0
- package/dist/components/Tooltip/Tooltip.js +18 -3
- 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/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/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/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/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/ModalVariantes.test.d.ts +1 -0
- package/dist/teste/ModalVariantes.test.js +151 -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/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/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/html-seguro.d.ts +27 -0
- package/dist/utils/html-seguro.js +97 -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/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/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/DataTableCelulaLivre.test.tsx +103 -0
- package/src/teste/DataTableDocumento.test.tsx +102 -0
- package/src/teste/DatePicker.test.tsx +38 -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/ModalVariantes.test.tsx +173 -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/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/TooltipDark.test.tsx +51 -0
- package/src/utils/calendario.ts +78 -0
- package/src/utils/html-seguro.ts +94 -0
- package/src/utils/paleta-de-cores.ts +34 -0
- package/src/utils/texto.ts +5 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { render, screen, within } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
import Calendar from "../components/Calendar/Calendar";
|
|
6
|
+
|
|
7
|
+
const HOJE = "2026-05-15";
|
|
8
|
+
const EVENTOS = [
|
|
9
|
+
{ id: "a", date: "2026-05-04", title: "Proc. Administrativo 002/2020", color: "--umds-primitive-blue-pure" },
|
|
10
|
+
{ id: "b", date: "2026-05-11", title: "Protocolo 005/2020", color: "--umds-primitive-green-pure", time: "09:00" },
|
|
11
|
+
{ id: "c", date: "2026-05-11", title: "Ofício 001/2020", color: "--umds-primitive-red-pure", time: "08:00" },
|
|
12
|
+
{ id: "d1", date: "2026-05-20", title: "Reunião 1" },
|
|
13
|
+
{ id: "d2", date: "2026-05-20", title: "Reunião 2" },
|
|
14
|
+
{ id: "d3", date: "2026-05-20", title: "Reunião 3" },
|
|
15
|
+
{ id: "d4", date: "2026-05-20", title: "Reunião 4" },
|
|
16
|
+
{ id: "d5", date: "2026-05-20", title: "Reunião 5" },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const celula = (container: HTMLElement, iso: string) => container.querySelector(`[data-date="${iso}"]`) as HTMLElement;
|
|
20
|
+
|
|
21
|
+
describe("Calendar", () => {
|
|
22
|
+
it("é uma grade nomeada pelo mês, com sete cabeçalhos de dia", () => {
|
|
23
|
+
render(<Calendar today={HOJE} defaultDate={HOJE} />);
|
|
24
|
+
expect(screen.getByRole("grid", { name: "Maio 2026" })).toBeInTheDocument();
|
|
25
|
+
const cabecalhos = screen.getAllByRole("columnheader");
|
|
26
|
+
expect(cabecalhos).toHaveLength(7);
|
|
27
|
+
expect(cabecalhos[0]).toHaveAccessibleName("Domingo");
|
|
28
|
+
expect(screen.getByText("Maio 2026")).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("o mês de maio de 2026 ocupa seis semanas inteiras; dias de fora ganham a classe", () => {
|
|
32
|
+
const { container } = render(<Calendar today={HOJE} defaultDate={HOJE} />);
|
|
33
|
+
expect(container.querySelectorAll('[role="gridcell"]')).toHaveLength(42);
|
|
34
|
+
expect(celula(container, "2026-04-26")).toHaveClass("umds-calendar__cell--outside");
|
|
35
|
+
expect(celula(container, "2026-05-15")).not.toHaveClass("umds-calendar__cell--outside");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("hoje é marcado com aria-current", () => {
|
|
39
|
+
const { container } = render(<Calendar today={HOJE} defaultDate={HOJE} />);
|
|
40
|
+
expect(celula(container, HOJE)).toHaveAttribute("aria-current", "date");
|
|
41
|
+
expect(celula(container, "2026-05-16")).not.toHaveAttribute("aria-current");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("cada dia tem nome acessível com a data e a contagem de eventos", () => {
|
|
45
|
+
const { container } = render(<Calendar today={HOJE} defaultDate={HOJE} events={EVENTOS} />);
|
|
46
|
+
expect(celula(container, "2026-05-11")).toHaveAttribute("aria-label", "segunda-feira, 11 de maio de 2026, 2 eventos");
|
|
47
|
+
expect(celula(container, "2026-05-04")).toHaveAttribute("aria-label", "segunda-feira, 4 de maio de 2026, 1 evento");
|
|
48
|
+
expect(celula(container, "2026-05-12")).toHaveAttribute("aria-label", "terça-feira, 12 de maio de 2026, sem eventos");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("dayLabel troca o nome do dia", () => {
|
|
52
|
+
const { container } = render(<Calendar today={HOJE} defaultDate={HOJE} dayLabel={(iso, n) => `${iso}:${n}`} events={EVENTOS} />);
|
|
53
|
+
expect(celula(container, "2026-05-11")).toHaveAttribute("aria-label", "2026-05-11:2");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("os eventos caem no dia certo, em ordem de horário, com a cor do dado", () => {
|
|
57
|
+
const { container } = render(<Calendar today={HOJE} defaultDate={HOJE} events={EVENTOS} />);
|
|
58
|
+
const dia = celula(container, "2026-05-11");
|
|
59
|
+
const chips = within(dia).getAllByRole("button");
|
|
60
|
+
expect(chips).toHaveLength(2);
|
|
61
|
+
expect(chips[0]).toHaveTextContent("08:00");
|
|
62
|
+
expect(chips[0]).toHaveTextContent("Ofício 001/2020");
|
|
63
|
+
expect((chips[0] as HTMLElement).style.borderLeftColor).toBe("var(--umds-primitive-red-pure)");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("evento sem cor usa o primário", () => {
|
|
67
|
+
const { container } = render(<Calendar today={HOJE} defaultDate={HOJE} events={EVENTOS} />);
|
|
68
|
+
const chip = within(celula(container, "2026-05-20")).getAllByRole("button")[0] as HTMLElement;
|
|
69
|
+
expect(chip.style.borderLeftColor).toBe("var(--umds-semantic-color-primary-pure)");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("passou de maxVisible: mostra três e um +N, que avisa com os escondidos", async () => {
|
|
73
|
+
const usuario = userEvent.setup();
|
|
74
|
+
const aoVerMais = vi.fn();
|
|
75
|
+
const { container } = render(<Calendar today={HOJE} defaultDate={HOJE} events={EVENTOS} onMoreClick={aoVerMais} />);
|
|
76
|
+
const dia = celula(container, "2026-05-20");
|
|
77
|
+
expect(within(dia).getAllByText(/Reunião/)).toHaveLength(3);
|
|
78
|
+
const mais = within(dia).getByRole("button", { name: "2 eventos a mais em 20 de maio" });
|
|
79
|
+
expect(mais).toHaveTextContent("+2");
|
|
80
|
+
await usuario.click(mais);
|
|
81
|
+
expect(aoVerMais).toHaveBeenCalledTimes(1);
|
|
82
|
+
expect(aoVerMais.mock.calls[0][0]).toBe("2026-05-20");
|
|
83
|
+
expect(aoVerMais.mock.calls[0][1].map((e: any) => e.id)).toEqual(["d4", "d5"]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("maxVisible muda o corte", () => {
|
|
87
|
+
const { container } = render(<Calendar today={HOJE} defaultDate={HOJE} events={EVENTOS} maxVisible={1} />);
|
|
88
|
+
expect(within(celula(container, "2026-05-20")).getAllByText(/Reunião/)).toHaveLength(1);
|
|
89
|
+
expect(within(celula(container, "2026-05-20")).getByText("+4")).toBeInTheDocument();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("clicar no evento avisa com o evento e não clica no dia", async () => {
|
|
93
|
+
const usuario = userEvent.setup();
|
|
94
|
+
const aoEvento = vi.fn();
|
|
95
|
+
const aoDia = vi.fn();
|
|
96
|
+
render(<Calendar today={HOJE} defaultDate={HOJE} events={EVENTOS} onEventClick={aoEvento} onDayClick={aoDia} />);
|
|
97
|
+
await usuario.click(screen.getByRole("button", { name: /Proc\. Administrativo 002\/2020/ }));
|
|
98
|
+
expect(aoEvento).toHaveBeenCalledWith(expect.objectContaining({ id: "a" }));
|
|
99
|
+
expect(aoDia).not.toHaveBeenCalled();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("clicar no dia avisa com o ISO", async () => {
|
|
103
|
+
const usuario = userEvent.setup();
|
|
104
|
+
const aoDia = vi.fn();
|
|
105
|
+
const { container } = render(<Calendar today={HOJE} defaultDate={HOJE} onDayClick={aoDia} />);
|
|
106
|
+
await usuario.click(celula(container, "2026-05-12"));
|
|
107
|
+
expect(aoDia).toHaveBeenCalledWith("2026-05-12");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("anterior, próximo e hoje navegam e avisam em onDateChange", async () => {
|
|
111
|
+
const usuario = userEvent.setup();
|
|
112
|
+
const aoMudar = vi.fn();
|
|
113
|
+
render(<Calendar today={HOJE} defaultDate={HOJE} onDateChange={aoMudar} />);
|
|
114
|
+
await usuario.click(screen.getByRole("button", { name: "Próximo" }));
|
|
115
|
+
expect(screen.getByText("Junho 2026")).toBeInTheDocument();
|
|
116
|
+
expect(aoMudar).toHaveBeenLastCalledWith("2026-06-15");
|
|
117
|
+
await usuario.click(screen.getByRole("button", { name: "Anterior" }));
|
|
118
|
+
await usuario.click(screen.getByRole("button", { name: "Anterior" }));
|
|
119
|
+
expect(screen.getByText("Abril 2026")).toBeInTheDocument();
|
|
120
|
+
await usuario.click(screen.getByRole("button", { name: "Hoje" }));
|
|
121
|
+
expect(screen.getByText("Maio 2026")).toBeInTheDocument();
|
|
122
|
+
expect(aoMudar).toHaveBeenLastCalledWith(HOJE);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("só um dia entra na ordem de Tab (hoje), com os eventos dele", () => {
|
|
126
|
+
const { container } = render(<Calendar today="2026-05-11" defaultDate="2026-05-11" events={EVENTOS} />);
|
|
127
|
+
const comTab = [...container.querySelectorAll('[role="gridcell"]')].filter((c) => c.getAttribute("tabindex") === "0");
|
|
128
|
+
expect(comTab).toHaveLength(1);
|
|
129
|
+
expect(comTab[0]).toHaveAttribute("data-date", "2026-05-11");
|
|
130
|
+
const chipsComTab = [...container.querySelectorAll(".umds-calendar__event")].filter((c) => c.getAttribute("tabindex") === "0");
|
|
131
|
+
expect(chipsComTab).toHaveLength(2);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("setas movem o foco entre os dias; Home, End e Enter", async () => {
|
|
135
|
+
const usuario = userEvent.setup();
|
|
136
|
+
const aoDia = vi.fn();
|
|
137
|
+
const { container } = render(<Calendar today={HOJE} defaultDate={HOJE} onDayClick={aoDia} />);
|
|
138
|
+
celula(container, HOJE).focus();
|
|
139
|
+
await usuario.keyboard("{ArrowRight}");
|
|
140
|
+
expect(celula(container, "2026-05-16")).toHaveFocus();
|
|
141
|
+
await usuario.keyboard("{ArrowDown}");
|
|
142
|
+
expect(celula(container, "2026-05-23")).toHaveFocus();
|
|
143
|
+
await usuario.keyboard("{ArrowLeft}{ArrowUp}");
|
|
144
|
+
expect(celula(container, "2026-05-15")).toHaveFocus();
|
|
145
|
+
await usuario.keyboard("{Home}");
|
|
146
|
+
expect(celula(container, "2026-05-10")).toHaveFocus();
|
|
147
|
+
await usuario.keyboard("{End}");
|
|
148
|
+
expect(celula(container, "2026-05-16")).toHaveFocus();
|
|
149
|
+
await usuario.keyboard("{Enter}");
|
|
150
|
+
expect(aoDia).toHaveBeenCalledWith("2026-05-16");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("a seta que sai do mês troca de mês e leva o foco", async () => {
|
|
154
|
+
const usuario = userEvent.setup();
|
|
155
|
+
const { container } = render(<Calendar today={HOJE} defaultDate="2026-05-30" />);
|
|
156
|
+
celula(container, "2026-05-30").focus();
|
|
157
|
+
await usuario.keyboard("{ArrowRight}{ArrowRight}");
|
|
158
|
+
expect(screen.getByText("Junho 2026")).toBeInTheDocument();
|
|
159
|
+
expect(celula(container, "2026-06-01")).toHaveFocus();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("PageDown e PageUp trocam de mês", async () => {
|
|
163
|
+
const usuario = userEvent.setup();
|
|
164
|
+
const { container } = render(<Calendar today={HOJE} defaultDate={HOJE} />);
|
|
165
|
+
celula(container, HOJE).focus();
|
|
166
|
+
await usuario.keyboard("{PageDown}");
|
|
167
|
+
expect(screen.getByText("Junho 2026")).toBeInTheDocument();
|
|
168
|
+
expect(celula(container, "2026-06-15")).toHaveFocus();
|
|
169
|
+
await usuario.keyboard("{PageUp}{PageUp}");
|
|
170
|
+
expect(screen.getByText("Abril 2026")).toBeInTheDocument();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("a visão de semana mostra sete dias, todos os eventos, e avisa em onViewChange", async () => {
|
|
174
|
+
const usuario = userEvent.setup();
|
|
175
|
+
const aoVisao = vi.fn();
|
|
176
|
+
const { container } = render(<Calendar today="2026-05-20" defaultDate="2026-05-20" events={EVENTOS} onViewChange={aoVisao} />);
|
|
177
|
+
await usuario.click(screen.getByRole("radio", { name: "Semana" }));
|
|
178
|
+
expect(aoVisao).toHaveBeenCalledWith("week");
|
|
179
|
+
expect(container.querySelectorAll('[role="gridcell"]')).toHaveLength(7);
|
|
180
|
+
expect(within(celula(container, "2026-05-20")).getAllByText(/Reunião/)).toHaveLength(5);
|
|
181
|
+
expect(screen.queryByText("+2")).toBeNull();
|
|
182
|
+
expect(container.querySelector(".umds-calendar--week")).not.toBeNull();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("na semana, anterior e próximo andam sete dias", async () => {
|
|
186
|
+
const usuario = userEvent.setup();
|
|
187
|
+
const aoMudar = vi.fn();
|
|
188
|
+
render(<Calendar today="2026-05-20" defaultDate="2026-05-20" defaultView="week" onDateChange={aoMudar} />);
|
|
189
|
+
await usuario.click(screen.getByRole("button", { name: "Próximo" }));
|
|
190
|
+
expect(aoMudar).toHaveBeenLastCalledWith("2026-05-27");
|
|
191
|
+
await usuario.click(screen.getByRole("button", { name: "Anterior" }));
|
|
192
|
+
await usuario.click(screen.getByRole("button", { name: "Anterior" }));
|
|
193
|
+
expect(aoMudar).toHaveBeenLastCalledWith("2026-05-13");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("weekStartsOn=1 começa a semana na segunda", () => {
|
|
197
|
+
render(<Calendar today={HOJE} defaultDate={HOJE} weekStartsOn={1} />);
|
|
198
|
+
expect(screen.getAllByRole("columnheader")[0]).toHaveAccessibleName("Segunda-feira");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("controlado: date e view mandam", () => {
|
|
202
|
+
function Pai() {
|
|
203
|
+
const [data, setData] = useState("2026-08-10");
|
|
204
|
+
return <Calendar today={HOJE} date={data} onDateChange={setData} view="month" />;
|
|
205
|
+
}
|
|
206
|
+
render(<Pai />);
|
|
207
|
+
expect(screen.getByText("Agosto 2026")).toBeInTheDocument();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("locale e textos por prop (inglês)", () => {
|
|
211
|
+
render(
|
|
212
|
+
<Calendar
|
|
213
|
+
today={HOJE}
|
|
214
|
+
defaultDate={HOJE}
|
|
215
|
+
locale="en-US"
|
|
216
|
+
previousLabel="Previous"
|
|
217
|
+
nextLabel="Next"
|
|
218
|
+
todayLabel="Today"
|
|
219
|
+
monthLabel="Month"
|
|
220
|
+
weekLabel="Week"
|
|
221
|
+
viewLabel="View"
|
|
222
|
+
/>,
|
|
223
|
+
);
|
|
224
|
+
expect(screen.getByText("May 2026")).toBeInTheDocument();
|
|
225
|
+
expect(screen.getByRole("button", { name: "Previous" })).toBeInTheDocument();
|
|
226
|
+
expect(screen.getByRole("button", { name: "Next" })).toBeInTheDocument();
|
|
227
|
+
expect(screen.getByRole("button", { name: "Today" })).toBeInTheDocument();
|
|
228
|
+
expect(screen.getByRole("radio", { name: "Week" })).toBeInTheDocument();
|
|
229
|
+
expect(screen.getByRole("group", { name: "View" })).toBeInTheDocument();
|
|
230
|
+
expect(screen.getAllByRole("columnheader")[0]).toHaveAccessibleName("Sunday");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("moreLabel e moreAriaLabel traduzem o +N", () => {
|
|
234
|
+
render(
|
|
235
|
+
<Calendar
|
|
236
|
+
today={HOJE}
|
|
237
|
+
defaultDate={HOJE}
|
|
238
|
+
events={EVENTOS}
|
|
239
|
+
locale="en-US"
|
|
240
|
+
moreLabel="{count} more"
|
|
241
|
+
moreAriaLabel="{count} more events on {date}"
|
|
242
|
+
/>,
|
|
243
|
+
);
|
|
244
|
+
expect(screen.getByRole("button", { name: "2 more events on May 20" })).toHaveTextContent("2 more");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("hideViewSwitcher esconde o seletor; os slots do cabeçalho aparecem", () => {
|
|
248
|
+
render(
|
|
249
|
+
<Calendar
|
|
250
|
+
today={HOJE}
|
|
251
|
+
defaultDate={HOJE}
|
|
252
|
+
hideViewSwitcher
|
|
253
|
+
headerStart={<span data-testid="filtro">Todos os documentos</span>}
|
|
254
|
+
headerEnd={<span data-testid="legenda">Legenda</span>}
|
|
255
|
+
/>,
|
|
256
|
+
);
|
|
257
|
+
expect(screen.queryByRole("radio")).toBeNull();
|
|
258
|
+
expect(screen.getByTestId("filtro")).toBeInTheDocument();
|
|
259
|
+
expect(screen.getByTestId("legenda")).toBeInTheDocument();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("selectedDate marca o dia", () => {
|
|
263
|
+
const { container } = render(<Calendar today={HOJE} defaultDate={HOJE} selectedDate="2026-05-12" />);
|
|
264
|
+
expect(celula(container, "2026-05-12")).toHaveAttribute("aria-selected", "true");
|
|
265
|
+
expect(celula(container, "2026-05-12")).toHaveClass("umds-calendar__cell--selected");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("evento com Date e com data-hora entra no dia", () => {
|
|
269
|
+
const { container } = render(
|
|
270
|
+
<Calendar
|
|
271
|
+
today={HOJE}
|
|
272
|
+
defaultDate={HOJE}
|
|
273
|
+
events={[
|
|
274
|
+
{ date: new Date(2026, 4, 12), title: "Com Date" },
|
|
275
|
+
{ date: "2026-05-13T10:30:00", title: "Com hora" },
|
|
276
|
+
]}
|
|
277
|
+
/>,
|
|
278
|
+
);
|
|
279
|
+
expect(within(celula(container, "2026-05-12")).getByText("Com Date")).toBeInTheDocument();
|
|
280
|
+
expect(within(celula(container, "2026-05-13")).getByText("Com hora")).toBeInTheDocument();
|
|
281
|
+
});
|
|
282
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
3
|
+
import Chips from "../components/Chips/Chips";
|
|
4
|
+
|
|
5
|
+
const raiz = (c: HTMLElement) => c.firstElementChild as HTMLElement;
|
|
6
|
+
|
|
7
|
+
describe("Chips · xs e × em círculo", () => {
|
|
8
|
+
it("size xs escreve a classe; sem size continua md", () => {
|
|
9
|
+
const { container } = render(
|
|
10
|
+
<>
|
|
11
|
+
<Chips label="a" size="xs" />
|
|
12
|
+
<Chips label="b" />
|
|
13
|
+
</>,
|
|
14
|
+
);
|
|
15
|
+
expect(container.children[0]).toHaveClass("umds-chip--xs");
|
|
16
|
+
expect(container.children[1]).toHaveClass("umds-chip--md");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("circularRemove com removable escreve a classe do círculo", () => {
|
|
20
|
+
const { container } = render(<Chips label="Marcador" removable circularRemove />);
|
|
21
|
+
expect(raiz(container)).toHaveClass("umds-chip--remove-circle");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("circularRemove sem removable não escreve a classe (não há ×)", () => {
|
|
25
|
+
const { container } = render(<Chips label="Marcador" circularRemove />);
|
|
26
|
+
expect(raiz(container)).not.toHaveClass("umds-chip--remove-circle");
|
|
27
|
+
expect(screen.queryByRole("button")).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("o × em círculo continua sendo o mesmo botão, com o mesmo nome e o mesmo clique", () => {
|
|
31
|
+
const aoRemover = vi.fn();
|
|
32
|
+
render(<Chips label="Urgente" size="xs" removable circularRemove onRemove={aoRemover} />);
|
|
33
|
+
fireEvent.click(screen.getByRole("button", { name: "Remover Urgente" }));
|
|
34
|
+
expect(aoRemover).toHaveBeenCalledTimes(1);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("sem circularRemove o chip removível é o de antes", () => {
|
|
38
|
+
const { container } = render(<Chips label="x" removable />);
|
|
39
|
+
expect(raiz(container)).not.toHaveClass("umds-chip--remove-circle");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
import Coachmark from "../components/Coachmark/Coachmark";
|
|
6
|
+
|
|
7
|
+
const PASSOS = [
|
|
8
|
+
{ target: "#alvo-a", title: "Cada item é uma aba", content: "Fluxos e formulários abrem em abas." },
|
|
9
|
+
{ target: "#alvo-b", title: "Salvo sozinho", content: "Tudo fica neste navegador." },
|
|
10
|
+
{ title: "Pronto", content: "Sem alvo: o cartão fica no centro." },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function Pagina(props: any) {
|
|
14
|
+
return (
|
|
15
|
+
<div>
|
|
16
|
+
<button id="alvo-a">A</button>
|
|
17
|
+
<button id="alvo-b">B</button>
|
|
18
|
+
<Coachmark steps={PASSOS} {...props} />
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("Coachmark", () => {
|
|
24
|
+
it("fechado não desenha nada", () => {
|
|
25
|
+
render(<Pagina open={false} />);
|
|
26
|
+
expect(screen.queryByRole("dialog")).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("aberto é um diálogo modal nomeado pelo título, com contador e texto", () => {
|
|
30
|
+
render(<Pagina open />);
|
|
31
|
+
const dialogo = screen.getByRole("dialog", { name: "Cada item é uma aba" });
|
|
32
|
+
expect(dialogo).toHaveAttribute("aria-modal", "true");
|
|
33
|
+
expect(dialogo).toHaveAccessibleDescription("Fluxos e formulários abrem em abas.");
|
|
34
|
+
expect(screen.getAllByText("1 de 3").length).toBeGreaterThan(0);
|
|
35
|
+
expect(screen.getByRole("button", { name: "Próximo" })).toBeInTheDocument();
|
|
36
|
+
expect(screen.queryByRole("button", { name: "Anterior" })).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("o foco entra no cartão ao abrir", () => {
|
|
40
|
+
render(<Pagina open />);
|
|
41
|
+
expect(screen.getByRole("dialog")).toHaveFocus();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("Próximo e Anterior trocam de passo e avisam em onStepChange", async () => {
|
|
45
|
+
const usuario = userEvent.setup();
|
|
46
|
+
const aoMudar = vi.fn();
|
|
47
|
+
render(<Pagina open onStepChange={aoMudar} />);
|
|
48
|
+
await usuario.click(screen.getByRole("button", { name: "Próximo" }));
|
|
49
|
+
expect(aoMudar).toHaveBeenLastCalledWith(1);
|
|
50
|
+
expect(screen.getByRole("dialog", { name: "Salvo sozinho" })).toBeInTheDocument();
|
|
51
|
+
await usuario.click(screen.getByRole("button", { name: "Anterior" }));
|
|
52
|
+
expect(aoMudar).toHaveBeenLastCalledWith(0);
|
|
53
|
+
expect(screen.getByRole("dialog", { name: "Cada item é uma aba" })).toBeInTheDocument();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("setas direita e esquerda avançam e voltam", async () => {
|
|
57
|
+
const usuario = userEvent.setup();
|
|
58
|
+
render(<Pagina open />);
|
|
59
|
+
await usuario.keyboard("{ArrowRight}");
|
|
60
|
+
expect(screen.getByRole("dialog", { name: "Salvo sozinho" })).toBeInTheDocument();
|
|
61
|
+
await usuario.keyboard("{ArrowLeft}");
|
|
62
|
+
expect(screen.getByRole("dialog", { name: "Cada item é uma aba" })).toBeInTheDocument();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("o último passo troca Próximo por Concluir e fecha com 'finish'", async () => {
|
|
66
|
+
const usuario = userEvent.setup();
|
|
67
|
+
const aoFechar = vi.fn();
|
|
68
|
+
render(<Pagina open defaultStep={2} onClose={aoFechar} />);
|
|
69
|
+
expect(screen.queryByRole("button", { name: "Próximo" })).toBeNull();
|
|
70
|
+
await usuario.click(screen.getByRole("button", { name: "Concluir" }));
|
|
71
|
+
expect(aoFechar).toHaveBeenCalledWith("finish");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("Sair do tour fecha com 'skip' e hideSkip esconde o botão", async () => {
|
|
75
|
+
const usuario = userEvent.setup();
|
|
76
|
+
const aoFechar = vi.fn();
|
|
77
|
+
const { rerender } = render(<Pagina open onClose={aoFechar} />);
|
|
78
|
+
await usuario.click(screen.getByRole("button", { name: "Sair do tour" }));
|
|
79
|
+
expect(aoFechar).toHaveBeenCalledWith("skip");
|
|
80
|
+
rerender(<Pagina open hideSkip onClose={aoFechar} />);
|
|
81
|
+
expect(screen.queryByRole("button", { name: "Sair do tour" })).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("Esc fecha com 'escape'", async () => {
|
|
85
|
+
const usuario = userEvent.setup();
|
|
86
|
+
const aoFechar = vi.fn();
|
|
87
|
+
render(<Pagina open onClose={aoFechar} />);
|
|
88
|
+
await usuario.keyboard("{Escape}");
|
|
89
|
+
expect(aoFechar).toHaveBeenCalledWith("escape");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("o foco fica preso no cartão: Tab gira e Shift+Tab volta", async () => {
|
|
93
|
+
const usuario = userEvent.setup();
|
|
94
|
+
render(<Pagina open />);
|
|
95
|
+
const sair = screen.getByRole("button", { name: "Sair do tour" });
|
|
96
|
+
const proximo = screen.getByRole("button", { name: "Próximo" });
|
|
97
|
+
proximo.focus();
|
|
98
|
+
await usuario.tab();
|
|
99
|
+
expect(sair).toHaveFocus();
|
|
100
|
+
await usuario.tab({ shift: true });
|
|
101
|
+
expect(proximo).toHaveFocus();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("todos os textos vêm por prop", () => {
|
|
105
|
+
render(
|
|
106
|
+
<Pagina
|
|
107
|
+
open
|
|
108
|
+
defaultStep={1}
|
|
109
|
+
nextLabel="Next"
|
|
110
|
+
previousLabel="Back"
|
|
111
|
+
skipLabel="Skip tour"
|
|
112
|
+
counterLabel="{current} of {total}"
|
|
113
|
+
/>,
|
|
114
|
+
);
|
|
115
|
+
expect(screen.getByRole("button", { name: "Next" })).toBeInTheDocument();
|
|
116
|
+
expect(screen.getByRole("button", { name: "Back" })).toBeInTheDocument();
|
|
117
|
+
expect(screen.getByRole("button", { name: "Skip tour" })).toBeInTheDocument();
|
|
118
|
+
expect(screen.getAllByText("2 of 3").length).toBeGreaterThan(0);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("finishLabel troca o texto do último passo", () => {
|
|
122
|
+
render(<Pagina open defaultStep={2} finishLabel="Done" />);
|
|
123
|
+
expect(screen.getByRole("button", { name: "Done" })).toBeInTheDocument();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("com alvo, desenha o recorte; sem alvo (ou alvo que não existe), não", () => {
|
|
127
|
+
const { container, rerender } = render(<Pagina open />);
|
|
128
|
+
expect(document.querySelector(".umds-coachmark__cutout")).not.toBeNull();
|
|
129
|
+
rerender(<Pagina open step={2} />);
|
|
130
|
+
expect(document.querySelector(".umds-coachmark__cutout")).toBeNull();
|
|
131
|
+
expect(document.querySelector(".umds-coachmark--no-target")).not.toBeNull();
|
|
132
|
+
void container;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("alvo por ref do React e por elemento", () => {
|
|
136
|
+
function ComRef() {
|
|
137
|
+
const ref = useRef<HTMLButtonElement>(null);
|
|
138
|
+
return (
|
|
139
|
+
<div>
|
|
140
|
+
<button ref={ref}>Alvo</button>
|
|
141
|
+
<Coachmark open steps={[{ target: ref, title: "Por ref" }]} />
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
render(<ComRef />);
|
|
146
|
+
expect(screen.getByRole("dialog", { name: "Por ref" })).toBeInTheDocument();
|
|
147
|
+
expect(document.querySelector(".umds-coachmark__cutout")).not.toBeNull();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("step controlado manda no passo atual", () => {
|
|
151
|
+
render(<Pagina open step={1} />);
|
|
152
|
+
expect(screen.getByRole("dialog", { name: "Salvo sozinho" })).toBeInTheDocument();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("ao fechar, o foco volta ao elemento de antes", async () => {
|
|
156
|
+
const usuario = userEvent.setup();
|
|
157
|
+
function Dono() {
|
|
158
|
+
const [aberto, setAberto] = useState(false);
|
|
159
|
+
return (
|
|
160
|
+
<div>
|
|
161
|
+
<button id="abrir" onClick={() => setAberto(true)}>
|
|
162
|
+
Fazer o tour
|
|
163
|
+
</button>
|
|
164
|
+
<Coachmark open={aberto} steps={PASSOS} onClose={() => setAberto(false)} />
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
render(<Dono />);
|
|
169
|
+
const abrir = screen.getByRole("button", { name: "Fazer o tour" });
|
|
170
|
+
await usuario.click(abrir);
|
|
171
|
+
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
|
172
|
+
// O foco entra no cartão depois da montagem (um quadro de animação): na CI,
|
|
173
|
+
// mais lenta, o Esc chegava antes e caía no body.
|
|
174
|
+
await waitFor(() => expect(screen.getByRole("dialog").contains(document.activeElement)).toBe(true));
|
|
175
|
+
await usuario.keyboard("{Escape}");
|
|
176
|
+
await waitFor(() => expect(screen.queryByRole("dialog")).toBeNull());
|
|
177
|
+
expect(abrir).toHaveFocus();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("reabrir recomeça do primeiro passo", async () => {
|
|
181
|
+
const usuario = userEvent.setup();
|
|
182
|
+
function Dono() {
|
|
183
|
+
const [aberto, setAberto] = useState(true);
|
|
184
|
+
return (
|
|
185
|
+
<div>
|
|
186
|
+
<button onClick={() => setAberto(true)}>Reabrir</button>
|
|
187
|
+
<Coachmark open={aberto} steps={PASSOS} onClose={() => setAberto(false)} />
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
render(<Dono />);
|
|
192
|
+
await usuario.keyboard("{ArrowRight}");
|
|
193
|
+
expect(screen.getByRole("dialog", { name: "Salvo sozinho" })).toBeInTheDocument();
|
|
194
|
+
await usuario.keyboard("{Escape}");
|
|
195
|
+
await usuario.click(screen.getByRole("button", { name: "Reabrir" }));
|
|
196
|
+
expect(screen.getByRole("dialog", { name: "Cada item é uma aba" })).toBeInTheDocument();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
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 ColorPicker from "../components/ColorPicker/ColorPicker";
|
|
6
|
+
import { COLOR_PALETTE } from "../utils/paleta-de-cores";
|
|
7
|
+
|
|
8
|
+
describe("ColorPicker", () => {
|
|
9
|
+
it("é um radiogroup com uma amostra por primitivo de cor, cada uma com nome", () => {
|
|
10
|
+
render(<ColorPicker ariaLabel="Cor do marcador" />);
|
|
11
|
+
const grupo = screen.getByRole("radiogroup", { name: "Cor do marcador" });
|
|
12
|
+
expect(grupo.querySelectorAll('[role="radio"]')).toHaveLength(COLOR_PALETTE.length);
|
|
13
|
+
expect(COLOR_PALETTE).toHaveLength(31);
|
|
14
|
+
expect(screen.getByRole("radio", { name: "Azul escuro" })).toBeInTheDocument();
|
|
15
|
+
expect(screen.getByRole("radio", { name: "Cinza muito claro" })).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("pinta cada amostra com a variável do token", () => {
|
|
19
|
+
render(<ColorPicker ariaLabel="Cor" />);
|
|
20
|
+
const azul = screen.getByRole("radio", { name: "Azul" }) as HTMLElement;
|
|
21
|
+
expect(azul.style.backgroundColor).toBe("var(--umds-primitive-blue-pure)");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("clicar escolhe e entrega o NOME do token em onChange", async () => {
|
|
25
|
+
const usuario = userEvent.setup();
|
|
26
|
+
const aoMudar = vi.fn();
|
|
27
|
+
render(<ColorPicker ariaLabel="Cor" onChange={aoMudar} />);
|
|
28
|
+
await usuario.click(screen.getByRole("radio", { name: "Verde" }));
|
|
29
|
+
expect(aoMudar).toHaveBeenCalledWith("--umds-primitive-green-pure");
|
|
30
|
+
expect(screen.getByRole("radio", { name: "Verde" })).toHaveAttribute("aria-checked", "true");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("não escolhido tem aria-checked false e só a escolhida mostra a marcação", () => {
|
|
34
|
+
const { container } = render(<ColorPicker ariaLabel="Cor" defaultValue="--umds-primitive-red-pure" />);
|
|
35
|
+
expect(screen.getByRole("radio", { name: "Vermelho" })).toHaveAttribute("aria-checked", "true");
|
|
36
|
+
expect(screen.getByRole("radio", { name: "Azul" })).toHaveAttribute("aria-checked", "false");
|
|
37
|
+
expect(container.querySelectorAll(".umds-color-picker__swatch svg")).toHaveLength(1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("Tab entra na amostra escolhida (roving tabindex)", () => {
|
|
41
|
+
render(<ColorPicker ariaLabel="Cor" value="--umds-primitive-orange-pure" />);
|
|
42
|
+
const radios = screen.getAllByRole("radio");
|
|
43
|
+
const comTab = radios.filter((r) => r.getAttribute("tabindex") === "0");
|
|
44
|
+
expect(comTab).toHaveLength(1);
|
|
45
|
+
expect(comTab[0]).toHaveAccessibleName("Laranja");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("sem valor, Tab entra na primeira amostra", () => {
|
|
49
|
+
render(<ColorPicker ariaLabel="Cor" />);
|
|
50
|
+
expect(screen.getAllByRole("radio")[0]).toHaveAttribute("tabindex", "0");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("setas movem a escolha e o foco; Home e End vão às pontas; dá a volta", async () => {
|
|
54
|
+
const usuario = userEvent.setup();
|
|
55
|
+
const aoMudar = vi.fn();
|
|
56
|
+
render(<ColorPicker ariaLabel="Cor" families={["blue"]} defaultValue="--umds-primitive-blue-dark" onChange={aoMudar} />);
|
|
57
|
+
const radios = screen.getAllByRole("radio");
|
|
58
|
+
radios[0].focus();
|
|
59
|
+
await usuario.keyboard("{ArrowRight}");
|
|
60
|
+
expect(aoMudar).toHaveBeenLastCalledWith("--umds-primitive-blue-pure");
|
|
61
|
+
expect(radios[1]).toHaveFocus();
|
|
62
|
+
await usuario.keyboard("{ArrowLeft}{ArrowLeft}");
|
|
63
|
+
expect(aoMudar).toHaveBeenLastCalledWith("--umds-primitive-blue-light");
|
|
64
|
+
expect(radios[3]).toHaveFocus();
|
|
65
|
+
await usuario.keyboard("{Home}");
|
|
66
|
+
expect(radios[0]).toHaveFocus();
|
|
67
|
+
await usuario.keyboard("{End}");
|
|
68
|
+
expect(radios[3]).toHaveFocus();
|
|
69
|
+
await usuario.keyboard("{ArrowDown}");
|
|
70
|
+
expect(radios[0]).toHaveFocus();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("pula amostra desabilitada no teclado", async () => {
|
|
74
|
+
const usuario = userEvent.setup();
|
|
75
|
+
const aoMudar = vi.fn();
|
|
76
|
+
render(
|
|
77
|
+
<ColorPicker
|
|
78
|
+
ariaLabel="Cor"
|
|
79
|
+
defaultValue="a"
|
|
80
|
+
onChange={aoMudar}
|
|
81
|
+
swatches={[
|
|
82
|
+
{ value: "a", label: "A" },
|
|
83
|
+
{ value: "b", label: "B", disabled: true },
|
|
84
|
+
{ value: "c", label: "C" },
|
|
85
|
+
]}
|
|
86
|
+
/>,
|
|
87
|
+
);
|
|
88
|
+
screen.getByRole("radio", { name: "A" }).focus();
|
|
89
|
+
await usuario.keyboard("{ArrowRight}");
|
|
90
|
+
expect(aoMudar).toHaveBeenLastCalledWith("c");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("families recorta a paleta", () => {
|
|
94
|
+
render(<ColorPicker ariaLabel="Cor" families={["blue", "gray"]} />);
|
|
95
|
+
expect(screen.getAllByRole("radio")).toHaveLength(11);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("swatches substitui a paleta (nomes traduzidos)", () => {
|
|
99
|
+
render(<ColorPicker ariaLabel="Color" swatches={[{ value: "--umds-primitive-blue-pure", label: "Blue" }]} />);
|
|
100
|
+
expect(screen.getAllByRole("radio")).toHaveLength(1);
|
|
101
|
+
expect(screen.getByRole("radio", { name: "Blue" })).toBeInTheDocument();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("disabled desabilita todas as amostras", () => {
|
|
105
|
+
render(<ColorPicker ariaLabel="Cor" disabled />);
|
|
106
|
+
for (const r of screen.getAllByRole("radio")) expect(r).toBeDisabled();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("label visível nomeia o grupo", () => {
|
|
110
|
+
render(<ColorPicker label="Cor de fundo" id="cp" />);
|
|
111
|
+
expect(screen.getByRole("radiogroup", { name: "Cor de fundo" })).toBeInTheDocument();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("cor livre vem desligada: sem campo de hexadecimal", () => {
|
|
115
|
+
render(<ColorPicker ariaLabel="Cor" />);
|
|
116
|
+
expect(screen.queryByLabelText(/hexadecimal/i)).toBeNull();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("allowCustom: hexadecimal completo vira #RRGGBB em maiúsculas; incompleto não emite", async () => {
|
|
120
|
+
const usuario = userEvent.setup();
|
|
121
|
+
const aoMudar = vi.fn();
|
|
122
|
+
render(<ColorPicker ariaLabel="Cor" allowCustom onChange={aoMudar} />);
|
|
123
|
+
const campo = screen.getByLabelText("Cor livre (hexadecimal)");
|
|
124
|
+
await usuario.type(campo, "0058d");
|
|
125
|
+
expect(aoMudar).not.toHaveBeenCalled();
|
|
126
|
+
await usuario.type(campo, "b");
|
|
127
|
+
expect(aoMudar).toHaveBeenCalledTimes(1);
|
|
128
|
+
expect(aoMudar).toHaveBeenCalledWith("#0058DB");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("allowCustom: hexadecimal inválido mostra a mensagem ao sair do campo", async () => {
|
|
132
|
+
const usuario = userEvent.setup();
|
|
133
|
+
render(<ColorPicker ariaLabel="Cor" allowCustom customErrorMessage="Formato inválido" />);
|
|
134
|
+
const campo = screen.getByLabelText("Cor livre (hexadecimal)");
|
|
135
|
+
await usuario.type(campo, "xyz");
|
|
136
|
+
expect(screen.queryByText("Formato inválido")).toBeNull();
|
|
137
|
+
await usuario.tab();
|
|
138
|
+
expect(screen.getByText("Formato inválido")).toBeInTheDocument();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("controlado: reflete o value e escolher uma amostra limpa o campo livre", async () => {
|
|
142
|
+
const usuario = userEvent.setup();
|
|
143
|
+
function Controlado() {
|
|
144
|
+
const [valor, setValor] = useState("#336699");
|
|
145
|
+
return <ColorPicker ariaLabel="Cor" allowCustom value={valor} onChange={setValor} />;
|
|
146
|
+
}
|
|
147
|
+
render(<Controlado />);
|
|
148
|
+
const campo = screen.getByLabelText("Cor livre (hexadecimal)") as HTMLInputElement;
|
|
149
|
+
expect(campo.value).toBe("#336699");
|
|
150
|
+
await usuario.click(screen.getByRole("radio", { name: "Azul" }));
|
|
151
|
+
expect(campo.value).toBe("");
|
|
152
|
+
});
|
|
153
|
+
});
|