@1doc/1ds-react 0.0.1 → 2.7.1
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 +667 -0
- package/README.md +138 -6
- package/package.json +43 -10
- package/src/index.ts +161 -16
- package/src/teste/AttachmentCard.test.tsx +161 -0
- package/src/teste/Chart.test.tsx +233 -0
- package/src/teste/Combobox.test.tsx +251 -0
- package/src/teste/DataTable.test.tsx +70 -0
- package/src/teste/DatePicker.test.tsx +86 -0
- package/src/teste/Drawer.test.tsx +265 -0
- package/src/teste/Dropdown.test.tsx +217 -0
- package/src/teste/Input.test.tsx +64 -0
- package/src/teste/Kanban.test.tsx +333 -0
- package/src/teste/MultiSelect.test.tsx +73 -0
- package/src/teste/Podium.test.tsx +107 -0
- package/src/teste/Popover.test.tsx +221 -0
- package/src/teste/SegmentedControl.test.tsx +183 -0
- package/src/teste/Timeline.test.tsx +100 -0
- package/src/teste/Tree.test.tsx +74 -0
- package/src/teste/setup.ts +7 -0
- package/src/utils/bandeiras.ts +22 -0
- package/src/utils/chart.ts +682 -0
- package/src/utils/density.ts +27 -0
- package/src/utils/overlay-stack.ts +26 -0
- package/src/utils/theme.ts +63 -47
- package/src/utils/toast.ts +89 -0
- package/vitest.config.ts +28 -0
- package/dist/components/Alert/Alert.css +0 -188
- package/dist/components/Alert/Alert.d.ts +0 -65
- package/dist/components/Alert/Alert.js +0 -56
- package/dist/components/Avatar/Avatar.css +0 -88
- package/dist/components/Avatar/Avatar.d.ts +0 -34
- package/dist/components/Avatar/Avatar.js +0 -16
- package/dist/components/Avatar/AvatarGroup.d.ts +0 -12
- package/dist/components/Avatar/AvatarGroup.js +0 -8
- package/dist/components/Badge/Badge.css +0 -155
- package/dist/components/Badge/Badge.d.ts +0 -46
- package/dist/components/Badge/Badge.js +0 -17
- package/dist/components/Button/Button.css +0 -199
- package/dist/components/Button/Button.d.ts +0 -23
- package/dist/components/Button/Button.js +0 -62
- package/dist/components/Checkbox/Checkbox.css +0 -122
- package/dist/components/Checkbox/Checkbox.d.ts +0 -21
- package/dist/components/Checkbox/Checkbox.js +0 -49
- package/dist/components/Divider/Divider.css +0 -81
- package/dist/components/Divider/Divider.d.ts +0 -18
- package/dist/components/Divider/Divider.js +0 -26
- package/dist/components/Grid/Grid.css +0 -154
- package/dist/components/Grid/Grid.d.ts +0 -21
- package/dist/components/Grid/Grid.js +0 -53
- package/dist/components/Icon/Icon.css +0 -41
- package/dist/components/Icon/Icon.d.ts +0 -12
- package/dist/components/Icon/Icon.js +0 -29
- package/dist/components/Input/Input.css +0 -190
- package/dist/components/Input/Input.d.ts +0 -32
- package/dist/components/Input/Input.js +0 -58
- package/dist/components/Link/Link.css +0 -76
- package/dist/components/Link/Link.d.ts +0 -15
- package/dist/components/Link/Link.js +0 -19
- package/dist/components/Logo/Logo.css +0 -32
- package/dist/components/Logo/Logo.d.ts +0 -10
- package/dist/components/Logo/Logo.js +0 -16
- package/dist/components/Modal/Modal.css +0 -155
- package/dist/components/Modal/Modal.d.ts +0 -36
- package/dist/components/Modal/Modal.js +0 -55
- package/dist/components/Radio/Radio.css +0 -114
- package/dist/components/Radio/Radio.d.ts +0 -19
- package/dist/components/Radio/Radio.js +0 -39
- package/dist/components/Select/Select.css +0 -291
- package/dist/components/Select/Select.d.ts +0 -38
- package/dist/components/Select/Select.js +0 -246
- package/dist/components/Spinner/Spinner.css +0 -60
- package/dist/components/Spinner/Spinner.d.ts +0 -16
- package/dist/components/Spinner/Spinner.js +0 -16
- package/dist/components/Switch/Switch.css +0 -127
- package/dist/components/Switch/Switch.d.ts +0 -18
- package/dist/components/Switch/Switch.js +0 -57
- package/dist/components/Tabs/Tab.d.ts +0 -19
- package/dist/components/Tabs/Tab.js +0 -76
- package/dist/components/Tabs/TabContent.d.ts +0 -15
- package/dist/components/Tabs/TabContent.js +0 -11
- package/dist/components/Tabs/Tabs.css +0 -124
- package/dist/components/Tabs/Tabs.d.ts +0 -20
- package/dist/components/Tabs/Tabs.js +0 -14
- package/dist/components/Tabs/TabsContent.d.ts +0 -16
- package/dist/components/Tabs/TabsContent.js +0 -12
- package/dist/components/Tabs/tabs.context.d.ts +0 -2
- package/dist/components/Tabs/tabs.context.js +0 -2
- package/dist/components/Tag/Tag.css +0 -140
- package/dist/components/Tag/Tag.d.ts +0 -63
- package/dist/components/Tag/Tag.js +0 -48
- package/dist/components/Text/Text.css +0 -1213
- package/dist/components/Text/Text.d.ts +0 -70
- package/dist/components/Text/Text.js +0 -89
- package/dist/components/Textarea/Textarea.css +0 -133
- package/dist/components/Textarea/Textarea.d.ts +0 -28
- package/dist/components/Textarea/Textarea.js +0 -72
- package/dist/index.d.ts +0 -25
- package/dist/index.js +0 -27
- package/dist/utils/theme.d.ts +0 -30
- package/dist/utils/theme.js +0 -106
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import Kanban from "../components/Kanban/Kanban";
|
|
6
|
+
import type {
|
|
7
|
+
KanbanColumnData,
|
|
8
|
+
KanbanCardMove,
|
|
9
|
+
} from "../components/Kanban/Kanban";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* O Kanban tem três caminhos até o mesmo `onCardMove` — arrastar, teclado e
|
|
13
|
+
* (sem cartão pego) navegação de foco. Nenhum deles aparece numa foto. O que
|
|
14
|
+
* precisa ficar guardado:
|
|
15
|
+
*
|
|
16
|
+
* - o PAYLOAD: `toIndex` é a posição final, já descontada a saída do cartão
|
|
17
|
+
* da origem. Errar isso por um é o bug clássico de lista ordenável;
|
|
18
|
+
* - Esc cancela sem chamar ninguém, e o cartão volta;
|
|
19
|
+
* - o quadro é UMA parada de Tab (roving tabindex);
|
|
20
|
+
* - cada passo do teclado é anunciado na região aria-live.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const COLUNAS: KanbanColumnData[] = [
|
|
24
|
+
{
|
|
25
|
+
id: "todo",
|
|
26
|
+
title: "A fazer",
|
|
27
|
+
cards: [
|
|
28
|
+
{ id: "a", title: "Cartão A" },
|
|
29
|
+
{ id: "b", title: "Cartão B" },
|
|
30
|
+
{ id: "c", title: "Cartão C" },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "doing",
|
|
35
|
+
title: "Em andamento",
|
|
36
|
+
wipLimit: 1,
|
|
37
|
+
cards: [{ id: "d", title: "Cartão D" }],
|
|
38
|
+
},
|
|
39
|
+
{ id: "done", title: "Concluído", cards: [] },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
/** O mesmo `splice` que a documentação manda o consumidor fazer. */
|
|
43
|
+
function aplicar(colunas: KanbanColumnData[], mov: KanbanCardMove) {
|
|
44
|
+
const copia = colunas.map((c) => ({ ...c, cards: c.cards.slice() }));
|
|
45
|
+
const origem = copia.find((c) => c.id === mov.fromColumnId)!;
|
|
46
|
+
const [cartao] = origem.cards.splice(mov.fromIndex, 1);
|
|
47
|
+
copia.find((c) => c.id === mov.toColumnId)!.cards.splice(mov.toIndex, 0, cartao);
|
|
48
|
+
return copia;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Quadro controlado, como no consumo real. */
|
|
52
|
+
function Quadro({ aoMover }: { aoMover?: (m: KanbanCardMove) => void }) {
|
|
53
|
+
const [colunas, setColunas] = useState(COLUNAS);
|
|
54
|
+
return (
|
|
55
|
+
<Kanban
|
|
56
|
+
columns={colunas}
|
|
57
|
+
onCardMove={(m) => {
|
|
58
|
+
aoMover?.(m);
|
|
59
|
+
setColunas((atual) => aplicar(atual, m));
|
|
60
|
+
}}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const cartao = (nome: string) => screen.getByRole("button", { name: nome });
|
|
66
|
+
const coluna = (nome: string) => screen.getByRole("group", { name: nome });
|
|
67
|
+
const titulosDa = (nome: string) =>
|
|
68
|
+
within(coluna(nome))
|
|
69
|
+
.queryAllByRole("button")
|
|
70
|
+
.map((el) => el.getAttribute("aria-label"));
|
|
71
|
+
|
|
72
|
+
describe("Kanban · teclado", () => {
|
|
73
|
+
it("Espaço pega, seta para a direita muda a coluna, Espaço solta", async () => {
|
|
74
|
+
const usuario = userEvent.setup();
|
|
75
|
+
const aoMover = vi.fn();
|
|
76
|
+
render(<Quadro aoMover={aoMover} />);
|
|
77
|
+
|
|
78
|
+
cartao("Cartão A").focus();
|
|
79
|
+
await usuario.keyboard(" ");
|
|
80
|
+
await usuario.keyboard("{ArrowRight}");
|
|
81
|
+
await usuario.keyboard(" ");
|
|
82
|
+
|
|
83
|
+
expect(aoMover).toHaveBeenCalledTimes(1);
|
|
84
|
+
expect(aoMover).toHaveBeenCalledWith({
|
|
85
|
+
cardId: "a",
|
|
86
|
+
fromColumnId: "todo",
|
|
87
|
+
fromIndex: 0,
|
|
88
|
+
toColumnId: "doing",
|
|
89
|
+
toIndex: 0,
|
|
90
|
+
});
|
|
91
|
+
expect(titulosDa("Em andamento")).toEqual(["Cartão A", "Cartão D"]);
|
|
92
|
+
expect(titulosDa("A fazer")).toEqual(["Cartão B", "Cartão C"]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("na mesma coluna, toIndex já desconta a saída do cartão", async () => {
|
|
96
|
+
const usuario = userEvent.setup();
|
|
97
|
+
const aoMover = vi.fn();
|
|
98
|
+
render(<Quadro aoMover={aoMover} />);
|
|
99
|
+
|
|
100
|
+
cartao("Cartão A").focus();
|
|
101
|
+
await usuario.keyboard(" ");
|
|
102
|
+
await usuario.keyboard("{ArrowDown}{ArrowDown}");
|
|
103
|
+
await usuario.keyboard("{Enter}");
|
|
104
|
+
|
|
105
|
+
expect(aoMover).toHaveBeenCalledWith({
|
|
106
|
+
cardId: "a",
|
|
107
|
+
fromColumnId: "todo",
|
|
108
|
+
fromIndex: 0,
|
|
109
|
+
toColumnId: "todo",
|
|
110
|
+
toIndex: 2,
|
|
111
|
+
});
|
|
112
|
+
expect(titulosDa("A fazer")).toEqual(["Cartão B", "Cartão C", "Cartão A"]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("a prévia move o cartão na tela antes de soltar, e o foco vai junto", async () => {
|
|
116
|
+
const usuario = userEvent.setup();
|
|
117
|
+
const aoMover = vi.fn();
|
|
118
|
+
render(<Quadro aoMover={aoMover} />);
|
|
119
|
+
|
|
120
|
+
cartao("Cartão B").focus();
|
|
121
|
+
await usuario.keyboard(" ");
|
|
122
|
+
await usuario.keyboard("{ArrowRight}{ArrowRight}");
|
|
123
|
+
|
|
124
|
+
// Ainda não soltou: nada foi pedido ao consumidor…
|
|
125
|
+
expect(aoMover).not.toHaveBeenCalled();
|
|
126
|
+
// …mas o cartão já aparece na coluna de destino, vazia até então.
|
|
127
|
+
expect(titulosDa("Concluído")).toEqual(["Cartão B"]);
|
|
128
|
+
await waitFor(() => expect(cartao("Cartão B")).toHaveFocus());
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("Esc cancela: ninguém é chamado e o cartão volta", async () => {
|
|
132
|
+
const usuario = userEvent.setup();
|
|
133
|
+
const aoMover = vi.fn();
|
|
134
|
+
render(<Quadro aoMover={aoMover} />);
|
|
135
|
+
|
|
136
|
+
cartao("Cartão A").focus();
|
|
137
|
+
await usuario.keyboard(" ");
|
|
138
|
+
await usuario.keyboard("{ArrowRight}");
|
|
139
|
+
expect(titulosDa("Em andamento")).toContain("Cartão A");
|
|
140
|
+
|
|
141
|
+
await usuario.keyboard("{Escape}");
|
|
142
|
+
|
|
143
|
+
expect(aoMover).not.toHaveBeenCalled();
|
|
144
|
+
expect(titulosDa("A fazer")).toEqual(["Cartão A", "Cartão B", "Cartão C"]);
|
|
145
|
+
expect(screen.getByText(/Movimento cancelado/)).toBeInTheDocument();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("anuncia pegar, mover (com o limite de WIP) e soltar", async () => {
|
|
149
|
+
const usuario = userEvent.setup();
|
|
150
|
+
const { container } = render(<Quadro />);
|
|
151
|
+
const regiao = container.querySelector('[aria-live="assertive"]')!;
|
|
152
|
+
|
|
153
|
+
cartao("Cartão A").focus();
|
|
154
|
+
await usuario.keyboard(" ");
|
|
155
|
+
expect(regiao).toHaveTextContent(
|
|
156
|
+
"Cartão Cartão A pego. Coluna A fazer, posição 1 de 3.",
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
await usuario.keyboard("{ArrowRight}");
|
|
160
|
+
expect(regiao).toHaveTextContent(
|
|
161
|
+
"Coluna Em andamento, posição 1 de 2. Limite de 1 cartões excedido.",
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
await usuario.keyboard(" ");
|
|
165
|
+
expect(regiao).toHaveTextContent("Cartão Cartão A solto.");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("Kanban · pego", () => {
|
|
170
|
+
it("o cartão pego se marca com aria-pressed e larga as instruções", async () => {
|
|
171
|
+
const usuario = userEvent.setup();
|
|
172
|
+
render(<Quadro />);
|
|
173
|
+
|
|
174
|
+
cartao("Cartão A").focus();
|
|
175
|
+
await usuario.keyboard(" ");
|
|
176
|
+
|
|
177
|
+
expect(cartao("Cartão A")).toHaveAttribute("aria-pressed", "true");
|
|
178
|
+
expect(cartao("Cartão A").getAttribute("aria-describedby")).not.toMatch(
|
|
179
|
+
/instrucoes/,
|
|
180
|
+
);
|
|
181
|
+
expect(cartao("Cartão B")).not.toHaveAttribute("aria-pressed");
|
|
182
|
+
expect(cartao("Cartão B").getAttribute("aria-describedby")).toMatch(
|
|
183
|
+
/instrucoes/,
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("bater na borda é anunciado, mesmo repetindo", async () => {
|
|
188
|
+
const usuario = userEvent.setup();
|
|
189
|
+
const { container } = render(<Quadro />);
|
|
190
|
+
const regiao = container.querySelector('[aria-live="assertive"]')!;
|
|
191
|
+
|
|
192
|
+
cartao("Cartão A").focus();
|
|
193
|
+
await usuario.keyboard(" ");
|
|
194
|
+
await usuario.keyboard("{ArrowUp}");
|
|
195
|
+
const primeira = regiao.textContent;
|
|
196
|
+
expect(primeira).toMatch(/^Início da coluna\. Coluna A fazer, posição 1 de 3\./);
|
|
197
|
+
|
|
198
|
+
// Mesmo texto de novo: o DOM precisa mudar, senão o leitor fica mudo.
|
|
199
|
+
await usuario.keyboard("{ArrowUp}");
|
|
200
|
+
expect(regiao.textContent).not.toBe(primeira);
|
|
201
|
+
expect(regiao.textContent?.trim()).toBe(primeira?.trim());
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("focar outro cartão cancela o movimento em vez de travar o quadro", async () => {
|
|
205
|
+
const usuario = userEvent.setup();
|
|
206
|
+
const aoMover = vi.fn();
|
|
207
|
+
render(<Quadro aoMover={aoMover} />);
|
|
208
|
+
|
|
209
|
+
cartao("Cartão A").focus();
|
|
210
|
+
await usuario.keyboard(" ");
|
|
211
|
+
await usuario.keyboard("{ArrowRight}");
|
|
212
|
+
expect(titulosDa("Em andamento")).toContain("Cartão A");
|
|
213
|
+
|
|
214
|
+
await usuario.click(cartao("Cartão C"));
|
|
215
|
+
|
|
216
|
+
expect(aoMover).not.toHaveBeenCalled();
|
|
217
|
+
expect(titulosDa("A fazer")).toEqual(["Cartão A", "Cartão B", "Cartão C"]);
|
|
218
|
+
expect(cartao("Cartão A")).not.toHaveAttribute("aria-pressed");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("atalhos com Alt, Ctrl e Cmd não são engolidos", async () => {
|
|
222
|
+
const usuario = userEvent.setup();
|
|
223
|
+
render(<Quadro />);
|
|
224
|
+
|
|
225
|
+
cartao("Cartão A").focus();
|
|
226
|
+
await usuario.keyboard("{Alt>}{ArrowDown}{/Alt}");
|
|
227
|
+
expect(cartao("Cartão A")).toHaveFocus();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("Kanban · foco", () => {
|
|
232
|
+
it("o quadro é uma parada só de Tab, e as setas passam o foco", async () => {
|
|
233
|
+
const usuario = userEvent.setup();
|
|
234
|
+
render(<Quadro />);
|
|
235
|
+
|
|
236
|
+
const focaveis = screen
|
|
237
|
+
.getAllByRole("button")
|
|
238
|
+
.filter((el) => el.getAttribute("tabindex") === "0");
|
|
239
|
+
expect(focaveis).toHaveLength(1);
|
|
240
|
+
expect(focaveis[0]).toHaveAccessibleName("Cartão A");
|
|
241
|
+
|
|
242
|
+
cartao("Cartão A").focus();
|
|
243
|
+
await usuario.keyboard("{ArrowDown}");
|
|
244
|
+
await waitFor(() => expect(cartao("Cartão B")).toHaveFocus());
|
|
245
|
+
|
|
246
|
+
// Seta para o lado leva à coluna vizinha, na posição mais próxima.
|
|
247
|
+
await usuario.keyboard("{ArrowRight}");
|
|
248
|
+
await waitFor(() => expect(cartao("Cartão D")).toHaveFocus());
|
|
249
|
+
expect(cartao("Cartão D")).toHaveAttribute("tabindex", "0");
|
|
250
|
+
expect(cartao("Cartão A")).toHaveAttribute("tabindex", "-1");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("Enter abre o cartão sem mover", async () => {
|
|
254
|
+
const usuario = userEvent.setup();
|
|
255
|
+
const aoAbrir = vi.fn();
|
|
256
|
+
const aoMover = vi.fn();
|
|
257
|
+
render(
|
|
258
|
+
<Kanban columns={COLUNAS} onCardClick={aoAbrir} onCardMove={aoMover} />,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
cartao("Cartão C").focus();
|
|
262
|
+
await usuario.keyboard("{Enter}");
|
|
263
|
+
|
|
264
|
+
expect(aoAbrir).toHaveBeenCalledWith(
|
|
265
|
+
expect.objectContaining({ id: "c" }),
|
|
266
|
+
"todo",
|
|
267
|
+
);
|
|
268
|
+
expect(aoMover).not.toHaveBeenCalled();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe("Kanban · arrastar", () => {
|
|
273
|
+
/**
|
|
274
|
+
* No jsdom toda caixa mede zero, então o ponteiro cai sempre "depois" do
|
|
275
|
+
* último cartão: o destino é o fim da coluna. Basta para guardar o payload.
|
|
276
|
+
*/
|
|
277
|
+
it("soltar noutra coluna entrega origem e destino", () => {
|
|
278
|
+
const aoMover = vi.fn();
|
|
279
|
+
const { container } = render(<Quadro aoMover={aoMover} />);
|
|
280
|
+
const dados = { setData: vi.fn(), effectAllowed: "", dropEffect: "" };
|
|
281
|
+
const listaConcluido = within(coluna("Concluído")).getByRole("list");
|
|
282
|
+
|
|
283
|
+
fireEvent.dragStart(cartao("Cartão B"), { dataTransfer: dados });
|
|
284
|
+
fireEvent.dragOver(listaConcluido, { dataTransfer: dados, clientY: 10 });
|
|
285
|
+
fireEvent.drop(listaConcluido, { dataTransfer: dados, clientY: 10 });
|
|
286
|
+
|
|
287
|
+
expect(dados.setData).toHaveBeenCalledWith("text/plain", "b");
|
|
288
|
+
expect(aoMover).toHaveBeenCalledWith({
|
|
289
|
+
cardId: "b",
|
|
290
|
+
fromColumnId: "todo",
|
|
291
|
+
fromIndex: 1,
|
|
292
|
+
toColumnId: "done",
|
|
293
|
+
toIndex: 0,
|
|
294
|
+
});
|
|
295
|
+
expect(titulosDa("Concluído")).toEqual(["Cartão B"]);
|
|
296
|
+
expect(container.querySelector(".umds-kanban__indicator")).toBeNull();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("soltar onde o cartão já está não chama ninguém", () => {
|
|
300
|
+
const aoMover = vi.fn();
|
|
301
|
+
render(<Quadro aoMover={aoMover} />);
|
|
302
|
+
const dados = { setData: vi.fn(), effectAllowed: "", dropEffect: "" };
|
|
303
|
+
const listaAndamento = within(coluna("Em andamento")).getByRole("list");
|
|
304
|
+
|
|
305
|
+
// D é o único (e último) de "Em andamento": soltar no fim é ficar parado.
|
|
306
|
+
fireEvent.dragStart(cartao("Cartão D"), { dataTransfer: dados });
|
|
307
|
+
fireEvent.dragOver(listaAndamento, { dataTransfer: dados, clientY: 10 });
|
|
308
|
+
fireEvent.drop(listaAndamento, { dataTransfer: dados, clientY: 10 });
|
|
309
|
+
|
|
310
|
+
expect(aoMover).not.toHaveBeenCalled();
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe("Kanban · colunas", () => {
|
|
315
|
+
it("coluna vazia avisa, e o limite de WIP é dito por extenso", () => {
|
|
316
|
+
render(<Kanban columns={COLUNAS} />);
|
|
317
|
+
expect(within(coluna("Concluído")).getByText("Nenhum cartão")).toBeInTheDocument();
|
|
318
|
+
expect(
|
|
319
|
+
within(coluna("Em andamento")).getByText("1 de 1 cartões, limite atingido"),
|
|
320
|
+
).toBeInTheDocument();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("com onAddCard, cada coluna ganha o botão de adicionar", async () => {
|
|
324
|
+
const usuario = userEvent.setup();
|
|
325
|
+
const aoAdicionar = vi.fn();
|
|
326
|
+
render(<Kanban columns={COLUNAS} onAddCard={aoAdicionar} />);
|
|
327
|
+
|
|
328
|
+
await usuario.click(
|
|
329
|
+
within(coluna("Concluído")).getByRole("button", { name: /Adicionar cartão/ }),
|
|
330
|
+
);
|
|
331
|
+
expect(aoAdicionar).toHaveBeenCalledWith("done");
|
|
332
|
+
});
|
|
333
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
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 MultiSelect from "../components/MultiSelect/MultiSelect";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* O `MultiSelect` acumula estado que o olho não confere: o que está marcado, o
|
|
8
|
+
* que a busca filtrou, e o que sai no `onChange`. Escolher duas vezes a mesma
|
|
9
|
+
* opção tem de DESMARCAR, e é o caso que quebra quando alguém troca um `push`
|
|
10
|
+
* por um `concat` sem olhar.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const OPCOES = [
|
|
14
|
+
{ label: "Recursos Humanos", value: "rh" },
|
|
15
|
+
{ label: "Financeiro", value: "fin" },
|
|
16
|
+
{ label: "Tecnologia", value: "ti" },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
describe("MultiSelect · seleção e busca", () => {
|
|
20
|
+
it("escolher duas opções devolve as duas, na ordem", async () => {
|
|
21
|
+
const usuario = userEvent.setup();
|
|
22
|
+
const aoMudar = vi.fn();
|
|
23
|
+
render(<MultiSelect label="Setores" options={OPCOES} onChange={aoMudar} />);
|
|
24
|
+
|
|
25
|
+
await usuario.click(screen.getByRole("combobox"));
|
|
26
|
+
await usuario.click(screen.getByRole("option", { name: /Financeiro/ }));
|
|
27
|
+
await usuario.click(screen.getByRole("option", { name: /Tecnologia/ }));
|
|
28
|
+
|
|
29
|
+
expect(aoMudar).toHaveBeenLastCalledWith(["fin", "ti"]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("clicar na opção já marcada desmarca", async () => {
|
|
33
|
+
const usuario = userEvent.setup();
|
|
34
|
+
const aoMudar = vi.fn();
|
|
35
|
+
render(
|
|
36
|
+
<MultiSelect
|
|
37
|
+
label="Setores"
|
|
38
|
+
options={OPCOES}
|
|
39
|
+
defaultValue={["fin"]}
|
|
40
|
+
onChange={aoMudar}
|
|
41
|
+
/>,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
await usuario.click(screen.getByRole("combobox"));
|
|
45
|
+
await usuario.click(screen.getByRole("option", { name: /Financeiro/ }));
|
|
46
|
+
|
|
47
|
+
expect(aoMudar).toHaveBeenLastCalledWith([]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("a opção marcada se anuncia como selecionada", async () => {
|
|
51
|
+
const usuario = userEvent.setup();
|
|
52
|
+
render(<MultiSelect label="Setores" options={OPCOES} defaultValue={["ti"]} />);
|
|
53
|
+
|
|
54
|
+
await usuario.click(screen.getByRole("combobox"));
|
|
55
|
+
|
|
56
|
+
expect(screen.getByRole("option", { name: /Tecnologia/ })).toHaveAttribute(
|
|
57
|
+
"aria-selected",
|
|
58
|
+
"true",
|
|
59
|
+
);
|
|
60
|
+
expect(screen.getByRole("option", { name: /Financeiro/ })).toHaveAttribute(
|
|
61
|
+
"aria-selected",
|
|
62
|
+
"false",
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("a lista se anuncia como múltipla escolha", async () => {
|
|
67
|
+
const usuario = userEvent.setup();
|
|
68
|
+
render(<MultiSelect label="Setores" options={OPCOES} />);
|
|
69
|
+
|
|
70
|
+
await usuario.click(screen.getByRole("combobox"));
|
|
71
|
+
expect(screen.getByRole("listbox")).toHaveAttribute("aria-multiselectable", "true");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { render, screen, within } from "@testing-library/react";
|
|
3
|
+
import Podium from "../components/Podium/Podium";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* O que a foto da esteira visual não prova é a ORDEM: o pódio desenha 2º, 1º,
|
|
7
|
+
* 3º, mas o DOM (e portanto o leitor de tela) precisa seguir 1º, 2º, 3º. Se
|
|
8
|
+
* alguém "simplificar" reordenando o array no componente em vez de posicionar
|
|
9
|
+
* pelo grid, a foto continua igual e estes testes caem.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const RANKING = [
|
|
13
|
+
{ id: 1, name: "Ana Paula Ribeiro", value: 128 },
|
|
14
|
+
{ id: 2, name: "Moacir Antunes", value: 114 },
|
|
15
|
+
{ id: 3, name: "Juliana Castro", value: 97 },
|
|
16
|
+
{ id: 4, name: "Carlos Mendes", value: 80 },
|
|
17
|
+
{ id: 5, name: "Beatriz Lima", value: 64 },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
describe("Podium · ordem de leitura", () => {
|
|
21
|
+
it("o DOM segue 1º, 2º, 3º com a frase acessível completa", () => {
|
|
22
|
+
render(<Podium items={RANKING} unit="pontos" />);
|
|
23
|
+
const podio = screen.getByRole("list", { name: "Pódio" });
|
|
24
|
+
const lugares = within(podio).getAllByRole("listitem");
|
|
25
|
+
expect(lugares).toHaveLength(3);
|
|
26
|
+
expect(lugares[0]).toHaveTextContent("1º lugar: Ana Paula Ribeiro, 128 pontos");
|
|
27
|
+
expect(lugares[1]).toHaveTextContent("2º lugar: Moacir Antunes, 114 pontos");
|
|
28
|
+
expect(lugares[2]).toHaveTextContent("3º lugar: Juliana Castro, 97 pontos");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("a posição visual sai da classe do lugar, não da ordem do array", () => {
|
|
32
|
+
render(<Podium items={RANKING} />);
|
|
33
|
+
const lugares = within(screen.getByRole("list", { name: "Pódio" })).getAllByRole("listitem");
|
|
34
|
+
expect(lugares[0]).toHaveClass("umds-podium__place--1");
|
|
35
|
+
expect(lugares[1]).toHaveClass("umds-podium__place--2");
|
|
36
|
+
expect(lugares[2]).toHaveClass("umds-podium__place--3");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("Podium · demais colocações", () => {
|
|
41
|
+
it("sem showRest, do 4º em diante não aparece", () => {
|
|
42
|
+
render(<Podium items={RANKING} />);
|
|
43
|
+
expect(screen.getAllByRole("list")).toHaveLength(1);
|
|
44
|
+
expect(screen.queryByText(/Carlos Mendes/)).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("com showRest, a lista continua a contagem a partir do 4º", () => {
|
|
48
|
+
render(<Podium items={RANKING} unit="pontos" showRest restTitle="Demais" />);
|
|
49
|
+
const resto = screen.getByRole("list", { name: "Demais" });
|
|
50
|
+
const linhas = within(resto).getAllByRole("listitem");
|
|
51
|
+
expect(linhas).toHaveLength(2);
|
|
52
|
+
expect(linhas[0]).toHaveTextContent("4º lugar: Carlos Mendes, 80 pontos");
|
|
53
|
+
expect(linhas[1]).toHaveTextContent("5º lugar: Beatriz Lima, 64 pontos");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("Podium · título da lista", () => {
|
|
58
|
+
it("restTitle é um cabeçalho de verdade, h3 por padrão", () => {
|
|
59
|
+
render(<Podium items={RANKING} showRest restTitle="Demais" />);
|
|
60
|
+
expect(screen.getByRole("heading", { level: 3, name: "Demais" })).toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("restTitleAs troca o nível", () => {
|
|
64
|
+
render(<Podium items={RANKING} showRest restTitle="Demais" restTitleAs="h2" />);
|
|
65
|
+
expect(screen.getByRole("heading", { level: 2, name: "Demais" })).toBeInTheDocument();
|
|
66
|
+
expect(screen.queryByRole("heading", { level: 3 })).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("Podium · casos de borda", () => {
|
|
71
|
+
it("menos de três itens não quebra e não inventa lugar vazio", () => {
|
|
72
|
+
render(<Podium items={RANKING.slice(0, 2)} />);
|
|
73
|
+
expect(screen.getAllByRole("listitem")).toHaveLength(2);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("lista vazia mostra o emptyText e nenhuma lista", () => {
|
|
77
|
+
render(<Podium items={[]} emptyText="Sem ranking ainda." />);
|
|
78
|
+
expect(screen.queryByRole("list")).toBeNull();
|
|
79
|
+
expect(screen.getByText("Sem ranking ainda.")).toBeInTheDocument();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("valueLabel vence valueFormatter, que vence o valor cru", () => {
|
|
83
|
+
render(
|
|
84
|
+
<Podium
|
|
85
|
+
items={[
|
|
86
|
+
{ id: 1, name: "A", value: 1000 },
|
|
87
|
+
{ id: 2, name: "B", value: 0, valueLabel: "Sem empenho" },
|
|
88
|
+
]}
|
|
89
|
+
valueFormatter={(v) => `R$ ${v}`}
|
|
90
|
+
positionLabel={(p) => `Place ${p}`}
|
|
91
|
+
/>,
|
|
92
|
+
);
|
|
93
|
+
const lugares = screen.getAllByRole("listitem");
|
|
94
|
+
expect(lugares[0]).toHaveTextContent("Place 1: A, R$ 1000");
|
|
95
|
+
expect(lugares[1]).toHaveTextContent("Place 2: B, Sem empenho");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("parte vazia não deixa vírgula solta na frase acessível", () => {
|
|
99
|
+
render(<Podium items={[{ id: 1, name: "", value: 120 }]} unit="pontos" />);
|
|
100
|
+
expect(screen.getByRole("listitem")).toHaveTextContent("1º lugar: 120 pontos");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("iniciais saem da primeira e da última palavra quando não há initials", () => {
|
|
104
|
+
const { container } = render(<Podium items={[{ id: 1, name: "ana paula ribeiro", value: 1 }]} />);
|
|
105
|
+
expect(container.querySelector(".umds-avatar")).toHaveTextContent("AR");
|
|
106
|
+
});
|
|
107
|
+
});
|