@1doc/1ds-react 2.9.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +434 -0
- package/dist/components/Accordion/Accordion.css +13 -0
- package/dist/components/Accordion/Accordion.d.ts +3 -0
- package/dist/components/Accordion/Accordion.js +1 -0
- package/dist/components/AppSubheader/AppSubheader.css +10 -1
- package/dist/components/AttachmentCard/AttachmentCard.css +10 -8
- package/dist/components/AttachmentCard/AttachmentCard.d.ts +3 -0
- package/dist/components/AttachmentCard/AttachmentCard.js +8 -1
- package/dist/components/Badge/Badge.css +14 -1
- package/dist/components/Badge/Badge.d.ts +11 -2
- package/dist/components/Badge/Badge.js +3 -0
- package/dist/components/Calendar/Calendar.d.ts +7 -6
- package/dist/components/Coachmark/Coachmark.css +10 -0
- package/dist/components/Coachmark/Coachmark.d.ts +12 -0
- package/dist/components/Coachmark/Coachmark.js +46 -5
- package/dist/components/ColorPicker/ColorPicker.css +8 -1
- package/dist/components/ColorPicker/ColorPicker.d.ts +2 -2
- package/dist/components/DataTable/DataTable.css +174 -0
- package/dist/components/DataTable/DataTable.d.ts +63 -2
- package/dist/components/DataTable/DataTable.js +278 -6
- package/dist/components/DescriptionList/DescriptionList.css +14 -0
- package/dist/components/DescriptionList/DescriptionList.d.ts +3 -0
- package/dist/components/DescriptionList/DescriptionList.js +1 -0
- package/dist/components/EmptyState/EmptyState.d.ts +4 -2
- package/dist/components/EmptyState/EmptyState.js +2 -2
- package/dist/components/Input/Input.css +21 -0
- package/dist/components/Input/Input.d.ts +7 -0
- package/dist/components/Input/Input.js +2 -1
- package/dist/components/List/ListItem.d.ts +14 -2
- package/dist/components/List/ListItem.js +3 -2
- package/dist/components/RichTextEditor/RichTextEditor.css +47 -0
- package/dist/components/RichTextEditor/RichTextEditor.d.ts +22 -8
- package/dist/components/RichTextEditor/RichTextEditor.js +460 -3
- package/dist/components/SegmentedControl/SegmentedControl.css +24 -0
- package/dist/components/SegmentedControl/SegmentedControl.d.ts +2 -0
- package/dist/components/SegmentedControl/SegmentedControl.js +4 -1
- package/dist/components/Select/Select.css +94 -0
- package/dist/components/Select/Select.d.ts +26 -4
- package/dist/components/Select/Select.js +210 -35
- package/dist/components/StatCard/StatCard.css +65 -0
- package/dist/components/StatCard/StatCard.d.ts +9 -0
- package/dist/components/StatCard/StatCard.js +10 -1
- package/dist/components/Toast/Toast.css +32 -0
- package/dist/components/Toast/Toast.d.ts +18 -0
- package/dist/components/Toast/Toast.js +14 -1
- package/dist/components/Toast/ToastStack.js +8 -1
- package/dist/teste/AccordionTituloNeutro.test.d.ts +1 -0
- package/dist/teste/AccordionTituloNeutro.test.js +23 -0
- package/dist/teste/AppSubheaderRegiao.test.d.ts +1 -0
- package/dist/teste/AppSubheaderRegiao.test.js +13 -0
- package/dist/teste/AttachmentCardLista.test.d.ts +1 -0
- package/dist/teste/AttachmentCardLista.test.js +49 -0
- package/dist/teste/AttachmentCardSm.test.d.ts +1 -0
- package/dist/teste/AttachmentCardSm.test.js +26 -0
- package/dist/teste/BadgeNeutroColado.test.d.ts +1 -0
- package/dist/teste/BadgeNeutroColado.test.js +11 -0
- package/dist/teste/BadgeTag.test.js +13 -0
- package/dist/teste/CalendarCabecalho.test.d.ts +1 -0
- package/dist/teste/CalendarCabecalho.test.js +11 -0
- package/dist/teste/CoachmarkSemVeu.test.d.ts +1 -0
- package/dist/teste/CoachmarkSemVeu.test.js +85 -0
- package/dist/teste/ColorPickerXs.test.d.ts +1 -0
- package/dist/teste/ColorPickerXs.test.js +21 -0
- package/dist/teste/DataTableArvoreEVariantes.test.d.ts +1 -0
- package/dist/teste/DataTableArvoreEVariantes.test.js +190 -0
- package/dist/teste/DescriptionListCompacta.test.d.ts +1 -0
- package/dist/teste/DescriptionListCompacta.test.js +22 -0
- package/dist/teste/EmptyStateTituloRico.test.d.ts +1 -0
- package/dist/teste/EmptyStateTituloRico.test.js +23 -0
- package/dist/teste/InputAgrupado.test.d.ts +1 -0
- package/dist/teste/InputAgrupado.test.js +24 -0
- package/dist/teste/ListItemTooltipStandalone.test.d.ts +1 -0
- package/dist/teste/ListItemTooltipStandalone.test.js +39 -0
- package/dist/teste/RichTextEditorAlinhaImagemTabela.test.d.ts +1 -0
- package/dist/teste/RichTextEditorAlinhaImagemTabela.test.js +212 -0
- package/dist/teste/RichTextEditorEstiloDeTexto.test.d.ts +1 -0
- package/dist/teste/RichTextEditorEstiloDeTexto.test.js +120 -0
- package/dist/teste/SegmentedControlContador.test.d.ts +1 -0
- package/dist/teste/SegmentedControlContador.test.js +39 -0
- package/dist/teste/SelectAbertoControladoEFlutuante.test.d.ts +1 -0
- package/dist/teste/SelectAbertoControladoEFlutuante.test.js +145 -0
- package/dist/teste/SelectFiltroCorETamanho.test.d.ts +1 -0
- package/dist/teste/SelectFiltroCorETamanho.test.js +77 -0
- package/dist/teste/StatCardSecundariaETom.test.d.ts +1 -0
- package/dist/teste/StatCardSecundariaETom.test.js +45 -0
- package/dist/teste/ToastAcaoEDetalhes.test.d.ts +1 -0
- package/dist/teste/ToastAcaoEDetalhes.test.js +94 -0
- package/dist/utils/html-seguro.d.ts +19 -1
- package/dist/utils/html-seguro.js +113 -2
- package/dist/utils/toast.d.ts +10 -1
- package/dist/utils/toast.js +2 -1
- package/package.json +2 -2
- package/src/teste/AccordionTituloNeutro.test.tsx +25 -0
- package/src/teste/AppSubheaderRegiao.test.tsx +17 -0
- package/src/teste/AttachmentCardLista.test.tsx +53 -0
- package/src/teste/AttachmentCardSm.test.tsx +29 -0
- package/src/teste/BadgeNeutroColado.test.tsx +15 -0
- package/src/teste/BadgeTag.test.tsx +21 -0
- package/src/teste/CalendarCabecalho.test.tsx +11 -0
- package/src/teste/CoachmarkSemVeu.test.tsx +101 -0
- package/src/teste/ColorPickerXs.test.tsx +22 -0
- package/src/teste/DataTableArvoreEVariantes.test.tsx +240 -0
- package/src/teste/DescriptionListCompacta.test.tsx +24 -0
- package/src/teste/EmptyStateTituloRico.test.tsx +34 -0
- package/src/teste/InputAgrupado.test.tsx +26 -0
- package/src/teste/ListItemTooltipStandalone.test.tsx +64 -0
- package/src/teste/RichTextEditorAlinhaImagemTabela.test.tsx +238 -0
- package/src/teste/RichTextEditorEstiloDeTexto.test.tsx +137 -0
- package/src/teste/SegmentedControlContador.test.tsx +43 -0
- package/src/teste/SelectAbertoControladoEFlutuante.test.tsx +180 -0
- package/src/teste/SelectFiltroCorETamanho.test.tsx +90 -0
- package/src/teste/StatCardSecundariaETom.test.tsx +51 -0
- package/src/teste/ToastAcaoEDetalhes.test.tsx +109 -0
- package/src/utils/html-seguro.ts +105 -2
- package/src/utils/toast.ts +12 -2
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
4
|
+
import { faBoxOpen } from "@fortawesome/free-solid-svg-icons";
|
|
5
|
+
import DataTable from "../components/DataTable/DataTable";
|
|
6
|
+
const COLUNAS = [
|
|
7
|
+
{ key: "nome", label: "Nome" },
|
|
8
|
+
{ key: "sigla", label: "Sigla" },
|
|
9
|
+
];
|
|
10
|
+
const DADOS = [
|
|
11
|
+
{ id: "a", nome: "Ana", sigla: "SADM" },
|
|
12
|
+
{ id: "b", nome: "Bruno", sigla: "SOBR" },
|
|
13
|
+
{ id: "c", nome: "Carla", sigla: "SEFIN" },
|
|
14
|
+
];
|
|
15
|
+
describe("DataTable: cabeçalho, zebra e destaque", () => {
|
|
16
|
+
it("headerVariant dark e headerUppercase ligam as classes; sem as props, nada muda", () => {
|
|
17
|
+
const { container, rerender } = render(_jsx(DataTable, { columns: COLUNAS, data: DADOS }));
|
|
18
|
+
const raiz = () => container.querySelector(".umds-datatable");
|
|
19
|
+
expect(raiz().className).not.toMatch(/header-dark|header-uppercase|striped|header-hidden/);
|
|
20
|
+
rerender(_jsx(DataTable, { columns: COLUNAS, data: DADOS, headerVariant: "dark", headerUppercase: true, striped: true, hideHeader: true }));
|
|
21
|
+
expect(raiz()).toHaveClass("umds-datatable--header-dark");
|
|
22
|
+
expect(raiz()).toHaveClass("umds-datatable--header-uppercase");
|
|
23
|
+
expect(raiz()).toHaveClass("umds-datatable--striped");
|
|
24
|
+
expect(raiz()).toHaveClass("umds-datatable--header-hidden");
|
|
25
|
+
});
|
|
26
|
+
it("hideHeader não remove o cabeçalho: as colunas continuam nomeadas", () => {
|
|
27
|
+
render(_jsx(DataTable, { columns: COLUNAS, data: DADOS, hideHeader: true }));
|
|
28
|
+
expect(screen.getAllByRole("columnheader")).toHaveLength(2);
|
|
29
|
+
});
|
|
30
|
+
it("striped marca as linhas ímpares", () => {
|
|
31
|
+
const { container } = render(_jsx(DataTable, { columns: COLUNAS, data: DADOS, striped: true }));
|
|
32
|
+
const linhas = [...container.querySelectorAll("tbody tr")];
|
|
33
|
+
expect(linhas.map((l) => l.classList.contains("umds-datatable__row--striped"))).toEqual([false, true, false]);
|
|
34
|
+
});
|
|
35
|
+
it("selectedRowKeys destaca a linha sem checkbox e marca aria-selected", () => {
|
|
36
|
+
const { container } = render(_jsx(DataTable, { columns: COLUNAS, data: DADOS, rowKey: "id", selectedRowKeys: ["b"] }));
|
|
37
|
+
const linhas = [...container.querySelectorAll("tbody tr")];
|
|
38
|
+
expect(linhas[1]).toHaveClass("umds-datatable__row--selected");
|
|
39
|
+
expect(linhas[1]).toHaveClass("umds-datatable__row--highlighted");
|
|
40
|
+
expect(linhas[1]).toHaveAttribute("aria-selected", "true");
|
|
41
|
+
expect(linhas[0]).not.toHaveClass("umds-datatable__row--selected");
|
|
42
|
+
expect(linhas[0]).not.toHaveAttribute("aria-selected");
|
|
43
|
+
expect(screen.queryByRole("checkbox")).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
it("isRowSelected destaca por função", () => {
|
|
46
|
+
const { container } = render(_jsx(DataTable, { columns: COLUNAS, data: DADOS, isRowSelected: (row) => row.sigla === "SEFIN" }));
|
|
47
|
+
const linhas = [...container.querySelectorAll("tbody tr")];
|
|
48
|
+
expect(linhas[2]).toHaveClass("umds-datatable__row--selected");
|
|
49
|
+
expect(linhas[0]).not.toHaveClass("umds-datatable__row--selected");
|
|
50
|
+
});
|
|
51
|
+
it("emptyIcon e emptyDescription entram no estado vazio", () => {
|
|
52
|
+
const { container } = render(_jsx(DataTable, { columns: COLUNAS, data: [], emptyMessage: "Nada por aqui", emptyDescription: "Ajuste o filtro de setor.", emptyIcon: faBoxOpen }));
|
|
53
|
+
expect(screen.getByText("Nada por aqui")).toBeInTheDocument();
|
|
54
|
+
expect(screen.getByText("Ajuste o filtro de setor.")).toBeInTheDocument();
|
|
55
|
+
expect(container.querySelector(".umds-datatable__empty-icon svg")).toHaveAttribute("data-icon", "box-open");
|
|
56
|
+
});
|
|
57
|
+
it("o ícone padrão do estado vazio continua o inbox", () => {
|
|
58
|
+
const { container } = render(_jsx(DataTable, { columns: COLUNAS, data: [] }));
|
|
59
|
+
expect(container.querySelector(".umds-datatable__empty-icon svg")).toHaveAttribute("data-icon", "inbox");
|
|
60
|
+
expect(container.querySelector(".umds-datatable__empty-desc")).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
it("compact na tabela de documento soma as duas classes", () => {
|
|
63
|
+
const { container } = render(_jsx(DataTable, { variant: "document", density: "compact", columns: COLUNAS, data: DADOS }));
|
|
64
|
+
const raiz = container.querySelector(".umds-datatable");
|
|
65
|
+
expect(raiz).toHaveClass("umds-datatable--document");
|
|
66
|
+
expect(raiz).toHaveClass("umds-datatable--compact");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
const ARVORE = [
|
|
70
|
+
{
|
|
71
|
+
id: "gab",
|
|
72
|
+
nome: "Gabinete",
|
|
73
|
+
sigla: "GAB",
|
|
74
|
+
children: [
|
|
75
|
+
{ id: "sadm", nome: "Administração", sigla: "SADM", children: [{ id: "rh", nome: "Recursos Humanos", sigla: "RH" }] },
|
|
76
|
+
{ id: "sobr", nome: "Obras", sigla: "SOBR" },
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
{ id: "sefin", nome: "Finanças", sigla: "SEFIN" },
|
|
80
|
+
];
|
|
81
|
+
const linhas = (c) => [...c.querySelectorAll("tbody tr")];
|
|
82
|
+
const nomes = (c) => linhas(c).map((l) => l.querySelectorAll("td")[0].textContent);
|
|
83
|
+
describe("DataTable treeData", () => {
|
|
84
|
+
it("vira treegrid; fechada, mostra só as raízes, com aria-level, posinset, setsize e expanded", () => {
|
|
85
|
+
const { container } = render(_jsx(DataTable, { columns: COLUNAS, data: ARVORE, treeData: true, rowKey: "id" }));
|
|
86
|
+
expect(screen.getByRole("treegrid")).toBeInTheDocument();
|
|
87
|
+
expect(nomes(container)).toEqual(["Gabinete", "Finanças"]);
|
|
88
|
+
const [gab, fin] = linhas(container);
|
|
89
|
+
expect(gab).toHaveAttribute("aria-level", "1");
|
|
90
|
+
expect(gab).toHaveAttribute("aria-posinset", "1");
|
|
91
|
+
expect(gab).toHaveAttribute("aria-setsize", "2");
|
|
92
|
+
expect(gab).toHaveAttribute("aria-expanded", "false");
|
|
93
|
+
expect(fin).not.toHaveAttribute("aria-expanded");
|
|
94
|
+
});
|
|
95
|
+
it("o botão expande e recolhe, com nome acessível e onExpand", () => {
|
|
96
|
+
const aoExpandir = vi.fn();
|
|
97
|
+
const { container } = render(_jsx(DataTable, { columns: COLUNAS, data: ARVORE, treeData: true, rowKey: "id", onExpand: aoExpandir }));
|
|
98
|
+
fireEvent.click(screen.getByRole("button", { name: "Expandir" }));
|
|
99
|
+
expect(nomes(container)).toEqual(["Gabinete", "Administração", "Obras", "Finanças"]);
|
|
100
|
+
expect(aoExpandir).toHaveBeenCalledWith(["gab"], ARVORE[0], true);
|
|
101
|
+
const filha = linhas(container)[1];
|
|
102
|
+
expect(filha).toHaveAttribute("aria-level", "2");
|
|
103
|
+
expect(filha).toHaveAttribute("aria-expanded", "false");
|
|
104
|
+
fireEvent.click(screen.getAllByRole("button", { name: "Recolher" })[0]);
|
|
105
|
+
expect(nomes(container)).toEqual(["Gabinete", "Finanças"]);
|
|
106
|
+
expect(aoExpandir).toHaveBeenLastCalledWith([], ARVORE[0], false);
|
|
107
|
+
});
|
|
108
|
+
it("expandLabel e collapseLabel traduzem os botões", () => {
|
|
109
|
+
render(_jsx(DataTable, { columns: COLUNAS, data: ARVORE, treeData: true, rowKey: "id", defaultExpandedKeys: ["gab"], expandLabel: "Expand", collapseLabel: "Collapse" }));
|
|
110
|
+
expect(screen.getByRole("button", { name: "Collapse" })).toBeInTheDocument();
|
|
111
|
+
expect(screen.getByRole("button", { name: "Expand" })).toBeInTheDocument();
|
|
112
|
+
});
|
|
113
|
+
it("defaultExpandAll abre tudo, e o primeiro recolher parte da árvore aberta", () => {
|
|
114
|
+
const { container } = render(_jsx(DataTable, { columns: COLUNAS, data: ARVORE, treeData: true, rowKey: "id", defaultExpandAll: true }));
|
|
115
|
+
expect(nomes(container)).toEqual(["Gabinete", "Administração", "Recursos Humanos", "Obras", "Finanças"]);
|
|
116
|
+
expect(linhas(container)[2]).toHaveAttribute("aria-level", "3");
|
|
117
|
+
fireEvent.click(screen.getAllByRole("button", { name: "Recolher" })[1]);
|
|
118
|
+
expect(nomes(container)).toEqual(["Gabinete", "Administração", "Obras", "Finanças"]);
|
|
119
|
+
});
|
|
120
|
+
it("expandedKeys controla: o clique avisa, mas só muda quando o pai muda a prop", () => {
|
|
121
|
+
const aoExpandir = vi.fn();
|
|
122
|
+
const { container, rerender } = render(_jsx(DataTable, { columns: COLUNAS, data: ARVORE, treeData: true, rowKey: "id", expandedKeys: [], onExpand: aoExpandir }));
|
|
123
|
+
fireEvent.click(screen.getByRole("button", { name: "Expandir" }));
|
|
124
|
+
expect(aoExpandir).toHaveBeenCalledWith(["gab"], ARVORE[0], true);
|
|
125
|
+
expect(nomes(container)).toEqual(["Gabinete", "Finanças"]);
|
|
126
|
+
rerender(_jsx(DataTable, { columns: COLUNAS, data: ARVORE, treeData: true, rowKey: "id", expandedKeys: ["gab"], onExpand: aoExpandir }));
|
|
127
|
+
expect(nomes(container)).toEqual(["Gabinete", "Administração", "Obras", "Finanças"]);
|
|
128
|
+
});
|
|
129
|
+
it("sem rowKey, a chave é o caminho de índices", () => {
|
|
130
|
+
const aoExpandir = vi.fn();
|
|
131
|
+
render(_jsx(DataTable, { columns: COLUNAS, data: ARVORE, treeData: true, onExpand: aoExpandir }));
|
|
132
|
+
fireEvent.click(screen.getByRole("button", { name: "Expandir" }));
|
|
133
|
+
expect(aoExpandir.mock.calls[0][0]).toEqual(["0"]);
|
|
134
|
+
});
|
|
135
|
+
it("desenha os fios: tee para quem tem irmã depois, elbow para a última, line para o que continua", () => {
|
|
136
|
+
const { container } = render(_jsx(DataTable, { columns: COLUNAS, data: ARVORE, treeData: true, rowKey: "id", defaultExpandAll: true }));
|
|
137
|
+
const guias = (l) => [...l.querySelectorAll(".umds-datatable__guide")].map((g) => g.className.replace("umds-datatable__guide umds-datatable__guide--", ""));
|
|
138
|
+
const [gab, sadm, rh, sobr] = linhas(container);
|
|
139
|
+
expect(guias(gab)).toEqual([]);
|
|
140
|
+
expect(guias(sadm)).toEqual(["tee"]);
|
|
141
|
+
expect(guias(rh)).toEqual(["line", "elbow"]);
|
|
142
|
+
expect(guias(sobr)).toEqual(["elbow"]);
|
|
143
|
+
});
|
|
144
|
+
it("o recuo cresce por nível", () => {
|
|
145
|
+
const { container } = render(_jsx(DataTable, { columns: COLUNAS, data: ARVORE, treeData: true, rowKey: "id", defaultExpandAll: true }));
|
|
146
|
+
const recuo = (l) => l.querySelector(".umds-datatable__tree-content").style.paddingLeft;
|
|
147
|
+
expect(recuo(linhas(container)[0])).toContain("calc(0 *");
|
|
148
|
+
expect(recuo(linhas(container)[2])).toContain("calc(2 *");
|
|
149
|
+
});
|
|
150
|
+
it("teclado: ↓ ↑ andam, → expande e desce, ← recolhe e sobe, Home e End", () => {
|
|
151
|
+
const { container } = render(_jsx(DataTable, { columns: COLUNAS, data: ARVORE, treeData: true, rowKey: "id" }));
|
|
152
|
+
const gab = linhas(container)[0];
|
|
153
|
+
gab.focus();
|
|
154
|
+
expect(gab).toHaveAttribute("tabindex", "0");
|
|
155
|
+
expect(linhas(container)[1]).toHaveAttribute("tabindex", "-1");
|
|
156
|
+
fireEvent.keyDown(gab, { key: "ArrowRight" });
|
|
157
|
+
expect(nomes(container)).toEqual(["Gabinete", "Administração", "Obras", "Finanças"]);
|
|
158
|
+
expect(document.activeElement).toBe(gab);
|
|
159
|
+
fireEvent.keyDown(gab, { key: "ArrowRight" });
|
|
160
|
+
expect(document.activeElement).toBe(linhas(container)[1]);
|
|
161
|
+
fireEvent.keyDown(document.activeElement, { key: "ArrowDown" });
|
|
162
|
+
expect(document.activeElement).toBe(linhas(container)[2]);
|
|
163
|
+
expect(linhas(container)[2]).toHaveAttribute("tabindex", "0");
|
|
164
|
+
fireEvent.keyDown(document.activeElement, { key: "ArrowLeft" });
|
|
165
|
+
expect(document.activeElement).toBe(linhas(container)[0]);
|
|
166
|
+
fireEvent.keyDown(document.activeElement, { key: "End" });
|
|
167
|
+
expect(document.activeElement).toBe(linhas(container)[3]);
|
|
168
|
+
fireEvent.keyDown(document.activeElement, { key: "Home" });
|
|
169
|
+
expect(document.activeElement).toBe(linhas(container)[0]);
|
|
170
|
+
fireEvent.keyDown(document.activeElement, { key: "ArrowLeft" });
|
|
171
|
+
expect(nomes(container)).toEqual(["Gabinete", "Finanças"]);
|
|
172
|
+
});
|
|
173
|
+
it("clicar no botão não aciona o onRowClick da linha", () => {
|
|
174
|
+
const aoClicar = vi.fn();
|
|
175
|
+
render(_jsx(DataTable, { columns: COLUNAS, data: ARVORE, treeData: true, rowKey: "id", onRowClick: aoClicar }));
|
|
176
|
+
fireEvent.click(screen.getByRole("button", { name: "Expandir" }));
|
|
177
|
+
expect(aoClicar).not.toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
it("sem treeData, a tabela é a de sempre: sem role, sem aria-level", () => {
|
|
180
|
+
const { container } = render(_jsx(DataTable, { columns: COLUNAS, data: ARVORE }));
|
|
181
|
+
expect(screen.queryByRole("treegrid")).toBeNull();
|
|
182
|
+
expect(container.querySelector("[aria-level]")).toBeNull();
|
|
183
|
+
expect(container.querySelector(".umds-datatable__tree-toggle")).toBeNull();
|
|
184
|
+
});
|
|
185
|
+
it("childrenKey troca o campo das filhas", () => {
|
|
186
|
+
const dados = [{ id: 1, nome: "Pai", sigla: "P", itens: [{ id: 2, nome: "Filho", sigla: "F" }] }];
|
|
187
|
+
const { container } = render(_jsx(DataTable, { columns: COLUNAS, data: dados, treeData: true, childrenKey: "itens", rowKey: "id", defaultExpandAll: true }));
|
|
188
|
+
expect(nomes(container)).toEqual(["Pai", "Filho"]);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { render } from "@testing-library/react";
|
|
4
|
+
import DescriptionList from "../components/DescriptionList/DescriptionList";
|
|
5
|
+
const ITENS = [{ label: "Dotação", value: "02.031.0001" }];
|
|
6
|
+
describe("DescriptionList · density", () => {
|
|
7
|
+
it("compact escreve a classe; o padrão não", () => {
|
|
8
|
+
const { container, rerender } = render(_jsx(DescriptionList, { items: ITENS, density: "compact" }));
|
|
9
|
+
const dl = () => container.querySelector("dl");
|
|
10
|
+
expect(dl()).toHaveClass("umds-description-list--compact");
|
|
11
|
+
rerender(_jsx(DescriptionList, { items: ITENS, density: "default" }));
|
|
12
|
+
expect(dl()).not.toHaveClass("umds-description-list--compact");
|
|
13
|
+
rerender(_jsx(DescriptionList, { items: ITENS }));
|
|
14
|
+
expect(dl()).not.toHaveClass("umds-description-list--compact");
|
|
15
|
+
});
|
|
16
|
+
it("compact vale nos dois desenhos", () => {
|
|
17
|
+
const { container, rerender } = render(_jsx(DescriptionList, { items: ITENS, density: "compact", layout: "stacked", bordered: true }));
|
|
18
|
+
expect(container.querySelector("dl")).toHaveClass("umds-description-list--stacked", "umds-description-list--compact");
|
|
19
|
+
rerender(_jsx(DescriptionList, { items: ITENS, density: "compact", fit: true }));
|
|
20
|
+
expect(container.querySelector("dl")).toHaveClass("umds-description-list--inline", "umds-description-list--compact");
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
|
+
import { faInbox } from "@fortawesome/free-solid-svg-icons";
|
|
5
|
+
import EmptyState from "../components/EmptyState/EmptyState";
|
|
6
|
+
describe("EmptyState · título rico", () => {
|
|
7
|
+
it("título em texto continua igual", () => {
|
|
8
|
+
const { container } = render(_jsx(EmptyState, { icon: faInbox, title: "Nenhuma mensagem", description: "Tudo em dia." }));
|
|
9
|
+
expect(container.querySelector(".umds-empty-state__title")).toHaveTextContent("Nenhuma mensagem");
|
|
10
|
+
expect(screen.getByText("Tudo em dia.")).toBeInTheDocument();
|
|
11
|
+
});
|
|
12
|
+
it("título aceita conteúdo rico: negrito, link", () => {
|
|
13
|
+
const { container } = render(_jsx(EmptyState, { title: _jsxs(_Fragment, { children: ["Nenhum processo para ", _jsx("strong", { "data-testid": "termo", children: "SOBR" }), " ", _jsx("a", { href: "#limpar", children: "Limpar filtro" })] }) }));
|
|
14
|
+
const titulo = container.querySelector(".umds-empty-state__title");
|
|
15
|
+
expect(titulo).toContainElement(screen.getByTestId("termo"));
|
|
16
|
+
expect(titulo.querySelector("a")).toHaveAttribute("href", "#limpar");
|
|
17
|
+
});
|
|
18
|
+
it("o título é um div (conteúdo de bloco não vira HTML inválido dentro de p)", () => {
|
|
19
|
+
const { container } = render(_jsx(EmptyState, { title: _jsx("div", { "data-testid": "bloco", children: "Bloco" }) }));
|
|
20
|
+
expect(container.querySelector(".umds-empty-state__title").tagName).toBe("DIV");
|
|
21
|
+
expect(screen.getByTestId("bloco").parentElement).toHaveClass("umds-empty-state__title");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { render } from "@testing-library/react";
|
|
4
|
+
import Input from "../components/Input/Input";
|
|
5
|
+
describe("Input · grouped", () => {
|
|
6
|
+
it("grouped liga a classe própria e a do borderless; sem a prop, nenhuma das duas", () => {
|
|
7
|
+
const { container, rerender } = render(_jsx(Input, { ariaLabel: "Adicionar setor", grouped: true }));
|
|
8
|
+
const raiz = () => container.firstElementChild;
|
|
9
|
+
expect(raiz()).toHaveClass("umds-input--grouped");
|
|
10
|
+
expect(raiz()).toHaveClass("umds-input--borderless");
|
|
11
|
+
rerender(_jsx(Input, { ariaLabel: "Adicionar setor" }));
|
|
12
|
+
expect(raiz()).not.toHaveClass("umds-input--grouped");
|
|
13
|
+
expect(raiz()).not.toHaveClass("umds-input--borderless");
|
|
14
|
+
});
|
|
15
|
+
it("borderless sozinho não ganha a classe do grupo (segue com altura de campo e foco próprio)", () => {
|
|
16
|
+
const { container } = render(_jsx(Input, { ariaLabel: "x", borderless: true }));
|
|
17
|
+
expect(container.firstElementChild).toHaveClass("umds-input--borderless");
|
|
18
|
+
expect(container.firstElementChild).not.toHaveClass("umds-input--grouped");
|
|
19
|
+
});
|
|
20
|
+
it("o campo continua digitável dentro do grupo", () => {
|
|
21
|
+
const { container } = render(_jsx(Input, { ariaLabel: "Adicionar setor", grouped: true, placeholder: "SADM, SOBR..." }));
|
|
22
|
+
expect(container.querySelector("input")).toHaveAttribute("placeholder", "SADM, SOBR...");
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
4
|
+
import ListItem from "../components/List/ListItem";
|
|
5
|
+
describe("ListItem: tooltip, standalone e atributos só no elemento interativo", () => {
|
|
6
|
+
it("tooltip vira o atributo title do elemento interativo, e o li fica limpo", () => {
|
|
7
|
+
const { container } = render(_jsx("ul", { children: _jsx(ListItem, { title: "Memorando 114/2025", tooltip: "Abrir o memorando", "aria-label": "Memorando", role: "menuitem", "data-origem": "home" }) }));
|
|
8
|
+
const li = container.querySelector("li");
|
|
9
|
+
const botao = screen.getByRole("menuitem");
|
|
10
|
+
expect(botao).toHaveAttribute("title", "Abrir o memorando");
|
|
11
|
+
expect(botao).toHaveAttribute("aria-label", "Memorando");
|
|
12
|
+
expect(botao).toHaveAttribute("data-origem", "home");
|
|
13
|
+
for (const atributo of ["title", "aria-label", "role", "data-origem"])
|
|
14
|
+
expect(li).not.toHaveAttribute(atributo);
|
|
15
|
+
});
|
|
16
|
+
it("tooltip funciona no link e na linha div; sem tooltip não há title", () => {
|
|
17
|
+
const { container } = render(_jsxs("ul", { children: [_jsx(ListItem, { title: "Link", href: "/x", tooltip: "Dica do link" }), _jsx(ListItem, { as: "div", title: "Div", tooltip: "Dica da div" }), _jsx(ListItem, { title: "Sem dica" })] }));
|
|
18
|
+
const titulos = [...container.querySelectorAll(".umds-list-item")].map((e) => e.getAttribute("title"));
|
|
19
|
+
expect(titulos).toEqual(["Dica do link", "Dica da div", null]);
|
|
20
|
+
});
|
|
21
|
+
it("o título da linha continua sendo texto, não o atributo", () => {
|
|
22
|
+
const { container } = render(_jsx("ul", { children: _jsx(ListItem, { title: "S\u00F3 o t\u00EDtulo" }) }));
|
|
23
|
+
expect(container.querySelector("[title]")).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
it("standalone renderiza sem li, só o elemento interativo", () => {
|
|
26
|
+
const aoClicar = vi.fn();
|
|
27
|
+
const { container } = render(_jsx("div", { children: _jsx(ListItem, { standalone: true, title: "12", tooltip: "P\u00E1gina 12", onClick: aoClicar }) }));
|
|
28
|
+
expect(container.querySelector("li")).toBeNull();
|
|
29
|
+
const botao = screen.getByRole("button", { name: /12/ });
|
|
30
|
+
expect(botao.parentElement?.tagName).toBe("DIV");
|
|
31
|
+
expect(botao).toHaveAttribute("title", "Página 12");
|
|
32
|
+
fireEvent.click(botao);
|
|
33
|
+
expect(aoClicar).toHaveBeenCalledTimes(1);
|
|
34
|
+
});
|
|
35
|
+
it("sem standalone o li continua lá", () => {
|
|
36
|
+
const { container } = render(_jsx("ul", { children: _jsx(ListItem, { title: "Normal" }) }));
|
|
37
|
+
expect(container.querySelectorAll("li")).toHaveLength(1);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
import RichTextEditor from "../components/RichTextEditor/RichTextEditor";
|
|
6
|
+
import { htmlDaTabela, htmlVazio, imagemSegura, sanitizarHtml } from "../utils/html-seguro";
|
|
7
|
+
let execCommand;
|
|
8
|
+
let estados;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
estados = {};
|
|
11
|
+
execCommand = vi.fn(() => true);
|
|
12
|
+
document.execCommand = execCommand;
|
|
13
|
+
document.queryCommandState = (comando) => !!estados[comando];
|
|
14
|
+
});
|
|
15
|
+
const area = () => screen.getByRole("textbox");
|
|
16
|
+
const PNG = "data:image/png;base64,iVBORw0KGgo=";
|
|
17
|
+
describe("html-seguro: alinhamento, imagem e tabela", () => {
|
|
18
|
+
it("text-align fica nos blocos, em style ou em align; o resto do style sai", () => {
|
|
19
|
+
expect(sanitizarHtml('<p style="text-align: center; color: red">a</p>')).toBe('<p style="text-align: center;">a</p>');
|
|
20
|
+
expect(sanitizarHtml('<div align="right">a</div>')).toBe('<div style="text-align: right;">a</div>');
|
|
21
|
+
expect(sanitizarHtml('<p style="text-align: expression(alert(1))">a</p>')).toBe("<p>a</p>");
|
|
22
|
+
expect(sanitizarHtml('<li style="text-align:justify">a</li>')).toBe('<li style="text-align: justify;">a</li>');
|
|
23
|
+
expect(sanitizarHtml('<b style="text-align:center">a</b>')).toBe("<b>a</b>");
|
|
24
|
+
});
|
|
25
|
+
it("imagem: http(s), caminho e data:image em PNG, JPEG, GIF e WebP ficam; o resto some inteiro", () => {
|
|
26
|
+
expect(sanitizarHtml('<img src="https://a.com/x.png" alt="Brasão" onerror="x()">')).toBe('<img src="https://a.com/x.png" alt="Brasão">');
|
|
27
|
+
expect(sanitizarHtml('<img src="/img/x.png">')).toBe('<img src="/img/x.png" alt="">');
|
|
28
|
+
expect(sanitizarHtml(`<img src="${PNG}" alt="a">`)).toBe(`<img src="${PNG}" alt="a">`);
|
|
29
|
+
for (const mau of ["javascript:alert(1)", "x", "data:image/svg+xml;base64,PHN2Zz4=", "data:text/html;base64,PGI+", "//evil.com/x.png", "ftp://a/x.png"]) {
|
|
30
|
+
expect(sanitizarHtml(`<p>a<img src="${mau}">b</p>`)).toBe("<p>ab</p>");
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
it("imagemSegura devolve vazio para o perigoso", () => {
|
|
34
|
+
expect(imagemSegura("java\nscript:alert(1)")).toBe("");
|
|
35
|
+
expect(imagemSegura("https://a.com/x.png")).toBe("https://a.com/x.png");
|
|
36
|
+
expect(imagemSegura("")).toBe("");
|
|
37
|
+
});
|
|
38
|
+
it("tabela simples: tags e colspan/rowspan ficam; atributos soltos saem", () => {
|
|
39
|
+
const html = '<table class="x" onclick="a()"><thead><tr><th colspan="2">T</th></tr></thead><tbody><tr><td rowspan="3" style="color:red">a</td><td colspan="x">b</td></tr></tbody></table>';
|
|
40
|
+
expect(sanitizarHtml(html)).toBe('<table><thead><tr><th colspan="2">T</th></tr></thead><tbody><tr><td rowspan="3">a</td><td>b</td></tr></tbody></table>');
|
|
41
|
+
});
|
|
42
|
+
it("htmlVazio: imagem e tabela contam como conteúdo", () => {
|
|
43
|
+
expect(htmlVazio("<img src='/a.png'>")).toBe(false);
|
|
44
|
+
expect(htmlVazio(htmlDaTabela(1, 1))).toBe(false);
|
|
45
|
+
expect(htmlVazio("<p><br></p>")).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
it("htmlDaTabela faz linhas × colunas de células com quebra", () => {
|
|
48
|
+
const celulas = (htmlDaTabela(2, 3).match(/<td><br><\/td>/g) || []).length;
|
|
49
|
+
expect(celulas).toBe(6);
|
|
50
|
+
expect((htmlDaTabela(2, 3).match(/<tr>/g) || []).length).toBe(2);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe("RichTextEditor: alinhamento", () => {
|
|
54
|
+
it("as quatro ferramentas de alinhamento pedem o comando e seguem o cursor", async () => {
|
|
55
|
+
const usuario = userEvent.setup();
|
|
56
|
+
render(_jsx(RichTextEditor, { ariaLabel: "x", toolbar: ["alignLeft", "alignCenter", "alignRight", "alignJustify"], defaultValue: "<p>x</p>" }));
|
|
57
|
+
const esperado = {
|
|
58
|
+
"Alinhar à esquerda": "justifyLeft",
|
|
59
|
+
Centralizar: "justifyCenter",
|
|
60
|
+
"Alinhar à direita": "justifyRight",
|
|
61
|
+
Justificar: "justifyFull",
|
|
62
|
+
};
|
|
63
|
+
for (const [nome, comando] of Object.entries(esperado)) {
|
|
64
|
+
execCommand.mockClear();
|
|
65
|
+
await usuario.click(screen.getByRole("button", { name: nome }));
|
|
66
|
+
expect(execCommand).toHaveBeenCalledWith(comando, false, undefined);
|
|
67
|
+
}
|
|
68
|
+
estados.justifyCenter = true;
|
|
69
|
+
const faixa = document.createRange();
|
|
70
|
+
faixa.selectNodeContents(area());
|
|
71
|
+
window.getSelection().removeAllRanges();
|
|
72
|
+
window.getSelection().addRange(faixa);
|
|
73
|
+
fireEvent(document, new Event("selectionchange"));
|
|
74
|
+
expect(screen.getByRole("button", { name: "Centralizar" })).toHaveAttribute("aria-pressed", "true");
|
|
75
|
+
expect(screen.getByRole("button", { name: "Justificar" })).toHaveAttribute("aria-pressed", "false");
|
|
76
|
+
});
|
|
77
|
+
it("a barra padrão não ganha as ferramentas novas", () => {
|
|
78
|
+
render(_jsx(RichTextEditor, { ariaLabel: "x" }));
|
|
79
|
+
for (const nome of ["Centralizar", "Imagem", "Tabela"])
|
|
80
|
+
expect(screen.queryByRole("button", { name: nome })).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe("RichTextEditor: imagem", () => {
|
|
84
|
+
const abrirImagem = async (props = {}, botao = "Imagem") => {
|
|
85
|
+
const usuario = userEvent.setup();
|
|
86
|
+
render(_jsx(RichTextEditor, { ariaLabel: "x", toolbar: ["image"], defaultValue: "<p>texto</p>", ...props }));
|
|
87
|
+
await usuario.click(screen.getByRole("button", { name: botao }));
|
|
88
|
+
return usuario;
|
|
89
|
+
};
|
|
90
|
+
it("o botão abre o painel com foco no endereço; Esc fecha", async () => {
|
|
91
|
+
const usuario = await abrirImagem();
|
|
92
|
+
expect(screen.getByLabelText("Endereço da imagem")).toHaveFocus();
|
|
93
|
+
expect(screen.getByLabelText("Texto alternativo")).toBeInTheDocument();
|
|
94
|
+
await usuario.keyboard("{Escape}");
|
|
95
|
+
expect(screen.queryByLabelText("Endereço da imagem")).toBeNull();
|
|
96
|
+
});
|
|
97
|
+
it("endereço e texto alternativo inserem o img com alt", async () => {
|
|
98
|
+
const aoMudar = vi.fn();
|
|
99
|
+
const usuario = await abrirImagem({ onChange: aoMudar });
|
|
100
|
+
await usuario.type(screen.getByLabelText("Endereço da imagem"), "exemplo.com.br/brasao.png");
|
|
101
|
+
await usuario.type(screen.getByLabelText("Texto alternativo"), "Brasão da prefeitura");
|
|
102
|
+
await usuario.click(screen.getByRole("button", { name: "Inserir imagem" }));
|
|
103
|
+
expect(execCommand).toHaveBeenCalledWith("insertHTML", false, '<img src="https://exemplo.com.br/brasao.png" alt="Brasão da prefeitura">');
|
|
104
|
+
expect(screen.queryByLabelText("Endereço da imagem")).toBeNull();
|
|
105
|
+
expect(aoMudar).toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
it("endereço perigoso mostra o erro e o painel continua aberto", async () => {
|
|
108
|
+
const usuario = await abrirImagem();
|
|
109
|
+
await usuario.type(screen.getByLabelText("Endereço da imagem"), "javascript:alert(1)");
|
|
110
|
+
await usuario.click(screen.getByRole("button", { name: "Inserir imagem" }));
|
|
111
|
+
expect(screen.getByText("Use um endereço http ou https, ou escolha um arquivo.")).toBeInTheDocument();
|
|
112
|
+
expect(execCommand).not.toHaveBeenCalledWith("insertHTML", expect.anything(), expect.anything());
|
|
113
|
+
expect(screen.getByLabelText("Endereço da imagem")).toBeInTheDocument();
|
|
114
|
+
});
|
|
115
|
+
it("arquivo vira data URL e entra no img", async () => {
|
|
116
|
+
const usuario = await abrirImagem();
|
|
117
|
+
const entrada = document.querySelector('input[type="file"]');
|
|
118
|
+
const arquivo = new File([new Uint8Array([137, 80, 78, 71])], "brasao.png", { type: "image/png" });
|
|
119
|
+
fireEvent.change(entrada, { target: { files: [arquivo] } });
|
|
120
|
+
await waitFor(() => expect(screen.getByText("brasao.png")).toBeInTheDocument());
|
|
121
|
+
await usuario.click(screen.getByRole("button", { name: "Inserir imagem" }));
|
|
122
|
+
const chamada = execCommand.mock.calls.find((c) => c[0] === "insertHTML");
|
|
123
|
+
expect(chamada[2]).toMatch(/^<img src="data:image\/png;base64,[A-Za-z0-9+/=]+" alt="">$/);
|
|
124
|
+
});
|
|
125
|
+
it("arquivo que não é PNG, JPEG, GIF ou WebP é recusado; acima de 2 MB também", async () => {
|
|
126
|
+
await abrirImagem();
|
|
127
|
+
const entrada = document.querySelector('input[type="file"]');
|
|
128
|
+
fireEvent.change(entrada, { target: { files: [new File(["x"], "a.svg", { type: "image/svg+xml" })] } });
|
|
129
|
+
expect(await screen.findByText("Use uma imagem PNG, JPEG, GIF ou WebP.")).toBeInTheDocument();
|
|
130
|
+
const grande = new File(["x"], "g.png", { type: "image/png" });
|
|
131
|
+
Object.defineProperty(grande, "size", { value: 3 * 1024 * 1024 });
|
|
132
|
+
fireEvent.change(entrada, { target: { files: [grande] } });
|
|
133
|
+
expect(await screen.findByText("A imagem passa de 2 MB.")).toBeInTheDocument();
|
|
134
|
+
});
|
|
135
|
+
it("os textos do painel vêm por labels", async () => {
|
|
136
|
+
await abrirImagem({ labels: { image: "Image", imageUrl: "Image address", imageAlt: "Alt text", imageApply: "Insert image", imageCancel: "Cancel", imageFile: "Choose file" } }, "Image");
|
|
137
|
+
expect(screen.getByLabelText("Image address")).toBeInTheDocument();
|
|
138
|
+
expect(screen.getByLabelText("Alt text")).toBeInTheDocument();
|
|
139
|
+
expect(screen.getByRole("button", { name: "Insert image" })).toBeInTheDocument();
|
|
140
|
+
expect(screen.getByRole("button", { name: "Choose file" })).toBeInTheDocument();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
describe("RichTextEditor: tabela", () => {
|
|
144
|
+
it("o painel insere linhas × colunas; o padrão é 3 × 3", async () => {
|
|
145
|
+
const usuario = userEvent.setup();
|
|
146
|
+
render(_jsx(RichTextEditor, { ariaLabel: "x", toolbar: ["table"] }));
|
|
147
|
+
await usuario.click(screen.getByRole("button", { name: "Tabela" }));
|
|
148
|
+
expect(screen.getByLabelText("Linhas")).toHaveFocus();
|
|
149
|
+
await usuario.clear(screen.getByLabelText("Linhas"));
|
|
150
|
+
await usuario.type(screen.getByLabelText("Linhas"), "2");
|
|
151
|
+
await usuario.click(screen.getByRole("button", { name: "Inserir tabela" }));
|
|
152
|
+
const chamada = execCommand.mock.calls.find((c) => c[0] === "insertHTML");
|
|
153
|
+
expect(chamada[2]).toBe(htmlDaTabela(2, 3));
|
|
154
|
+
});
|
|
155
|
+
it("fora de 1 a 20 linhas e 1 a 10 colunas mostra o erro e não insere", async () => {
|
|
156
|
+
const usuario = userEvent.setup();
|
|
157
|
+
render(_jsx(RichTextEditor, { ariaLabel: "x", toolbar: ["table"] }));
|
|
158
|
+
await usuario.click(screen.getByRole("button", { name: "Tabela" }));
|
|
159
|
+
await usuario.clear(screen.getByLabelText("Colunas"));
|
|
160
|
+
await usuario.type(screen.getByLabelText("Colunas"), "11");
|
|
161
|
+
await usuario.click(screen.getByRole("button", { name: "Inserir tabela" }));
|
|
162
|
+
expect(screen.getByText("Use de 1 a 20 linhas e de 1 a 10 colunas.")).toBeInTheDocument();
|
|
163
|
+
expect(execCommand).not.toHaveBeenCalledWith("insertHTML", expect.anything(), expect.anything());
|
|
164
|
+
});
|
|
165
|
+
const TABELA = "<table><tbody><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></tbody></table>";
|
|
166
|
+
const cursorNaCelula = (indice) => {
|
|
167
|
+
const celula = area().querySelectorAll("td")[indice];
|
|
168
|
+
const faixa = document.createRange();
|
|
169
|
+
faixa.selectNodeContents(celula);
|
|
170
|
+
window.getSelection().removeAllRanges();
|
|
171
|
+
window.getSelection().addRange(faixa);
|
|
172
|
+
fireEvent(document, new Event("selectionchange"));
|
|
173
|
+
};
|
|
174
|
+
it("com o cursor numa célula aparece a barra; sem tabela na toolbar, não aparece", async () => {
|
|
175
|
+
const { rerender } = render(_jsx(RichTextEditor, { ariaLabel: "x", toolbar: ["table"], defaultValue: TABELA }));
|
|
176
|
+
expect(screen.queryByRole("group", { name: "Tabela" })).toBeNull();
|
|
177
|
+
cursorNaCelula(0);
|
|
178
|
+
expect(await screen.findByRole("group", { name: "Tabela" })).toBeInTheDocument();
|
|
179
|
+
rerender(_jsx(RichTextEditor, { ariaLabel: "x", toolbar: ["bold"], defaultValue: TABELA }));
|
|
180
|
+
expect(screen.queryByRole("group", { name: "Tabela" })).toBeNull();
|
|
181
|
+
});
|
|
182
|
+
it("adiciona e remove linha e coluna na célula do cursor e avisa onChange", async () => {
|
|
183
|
+
const usuario = userEvent.setup();
|
|
184
|
+
const aoMudar = vi.fn();
|
|
185
|
+
render(_jsx(RichTextEditor, { ariaLabel: "x", toolbar: ["table"], defaultValue: TABELA, onChange: aoMudar }));
|
|
186
|
+
cursorNaCelula(0);
|
|
187
|
+
await usuario.click(await screen.findByRole("button", { name: "Adicionar coluna" }));
|
|
188
|
+
expect(area().querySelectorAll("tr")[0].cells.length).toBe(3);
|
|
189
|
+
expect(area().querySelectorAll("tr")[1].cells.length).toBe(3);
|
|
190
|
+
expect(aoMudar).toHaveBeenCalled();
|
|
191
|
+
cursorNaCelula(0);
|
|
192
|
+
await usuario.click(await screen.findByRole("button", { name: "Adicionar linha" }));
|
|
193
|
+
expect(area().querySelectorAll("tr")).toHaveLength(3);
|
|
194
|
+
cursorNaCelula(0);
|
|
195
|
+
await usuario.click(await screen.findByRole("button", { name: "Remover linha" }));
|
|
196
|
+
expect(area().querySelectorAll("tr")).toHaveLength(2);
|
|
197
|
+
cursorNaCelula(0);
|
|
198
|
+
await usuario.click(await screen.findByRole("button", { name: "Remover coluna" }));
|
|
199
|
+
expect(area().querySelectorAll("tr")[0].cells.length).toBe(2);
|
|
200
|
+
cursorNaCelula(0);
|
|
201
|
+
await usuario.click(await screen.findByRole("button", { name: "Remover tabela" }));
|
|
202
|
+
expect(area().querySelector("table")).toBeNull();
|
|
203
|
+
expect(screen.queryByRole("group", { name: "Tabela" })).toBeNull();
|
|
204
|
+
});
|
|
205
|
+
it("remover a única linha ou coluna remove a tabela", async () => {
|
|
206
|
+
const usuario = userEvent.setup();
|
|
207
|
+
render(_jsx(RichTextEditor, { ariaLabel: "x", toolbar: ["table"], defaultValue: "<table><tbody><tr><td>a</td></tr></tbody></table>" }));
|
|
208
|
+
cursorNaCelula(0);
|
|
209
|
+
await usuario.click(await screen.findByRole("button", { name: "Remover linha" }));
|
|
210
|
+
expect(area().querySelector("table")).toBeNull();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|