@1doc/1ds-react 2.11.1 → 2.12.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
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @1doc/1ds-react
|
|
2
2
|
|
|
3
|
+
## 2.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d03c66d: Timeline: estado vazio dentro do componente, como o List. `isEmpty`, `emptyTitle`, `emptyDescription`, `emptyIcon`, `emptySize`, `emptyActionLabel` com `onEmptyAction` e o slot `empty` (no React, a prop `empty`) trocam só a trilha por um `EmptyState` com `role="status"`, sem desmontar o que está em volta. Sem `isEmpty`, `items` vazio mostra o vazio quando há `emptyTitle` ou o slot; sem nenhum dos dois, a trilha vazia segue como antes. No Vue, `isEmpty` ausente agora chega como `undefined` (e não `false`), para distinguir "não passou" de "passou false".
|
|
8
|
+
|
|
3
9
|
## 2.11.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -214,3 +214,11 @@
|
|
|
214
214
|
.umds-timeline--sm.umds-timeline--card .umds-timeline__body {
|
|
215
215
|
padding: var(--umds-primitive-spacing-mini); /* 12px */
|
|
216
216
|
}
|
|
217
|
+
|
|
218
|
+
/* ── Estado vazio ──
|
|
219
|
+
O miolo da trilha troca de conteúdo; quem rodeia não muda. O EmptyState já
|
|
220
|
+
tem o próprio respiro e alinhamento, o wrapper só dá o lugar. */
|
|
221
|
+
.umds-timeline-empty {
|
|
222
|
+
display: block;
|
|
223
|
+
font-family: var(--umds-semantic-font-family-base);
|
|
224
|
+
}
|
|
@@ -35,20 +35,56 @@ export interface TimelineItem {
|
|
|
35
35
|
* Extraída do histórico de versões do processo e da timeline de despachos da
|
|
36
36
|
* 1Doc.
|
|
37
37
|
*
|
|
38
|
+
* Sem eventos, a trilha troca por um estado vazio dentro do próprio componente:
|
|
39
|
+
* um `EmptyState` (`emptyTitle`, `emptyDescription`, `emptyIcon`,
|
|
40
|
+
* `emptyActionLabel` com `onEmptyAction`) ou, para um conteúdo próprio, o slot
|
|
41
|
+
* `empty` (no React, a prop `empty` recebe o nó). Só o miolo troca: o título do
|
|
42
|
+
* cartão, os filtros e o que mais estiver ao redor continuam montados, e o
|
|
43
|
+
* vazio é anunciado ao leitor de tela (`role="status"`). Passe `isEmpty` quando
|
|
44
|
+
* a decisão for sua (`:is-empty="!eventos.length"`); sem ele, `items` vazio já
|
|
45
|
+
* mostra o vazio, desde que haja `emptyTitle` ou o slot. Sem nenhum dos dois,
|
|
46
|
+
* `items` vazio rende a trilha sem eventos, como sempre.
|
|
47
|
+
*
|
|
38
48
|
* @property {TimelineItem[]} items - Eventos, na ordem em que devem aparecer.
|
|
39
49
|
* @property {boolean} card - Envolve o conteúdo de cada evento num card branco bordered.
|
|
40
50
|
* @property {string} size - Tamanho do marcador e do texto: 'sm' | 'md'.
|
|
41
51
|
* @property {string} ariaLabel - Nome acessível da lista. Padrão: "Linha do tempo".
|
|
52
|
+
* @property {boolean} isEmpty - Decide o estado vazio: `true` mostra o vazio no lugar da trilha, `false` mantém a trilha. Sem ele, o vazio aparece quando `items` está vazio e há `emptyTitle` ou o slot `empty`.
|
|
53
|
+
* @property {string} emptyTitle - Título do estado vazio (padrão: "Nenhum evento registrado")
|
|
54
|
+
* @property {string} emptyDescription - Texto de apoio do estado vazio
|
|
55
|
+
* @property {IconProp} emptyIcon - Ícone do estado vazio (objeto do Font Awesome)
|
|
56
|
+
* @property {string} emptySize - Tamanho do estado vazio: 'sm' (padrão, compacto) | 'md' | 'lg'
|
|
57
|
+
* @property {string} emptyActionLabel - Rótulo do botão do estado vazio (junto de onEmptyAction)
|
|
58
|
+
* @property {function} onEmptyAction - Callback do botão do estado vazio
|
|
59
|
+
* @property {any} empty - Slot `empty`: conteúdo próprio no lugar do EmptyState (React node ou Vue slot)
|
|
42
60
|
* @property {string} className - Classes CSS adicionais.
|
|
43
|
-
* @
|
|
44
|
-
* @
|
|
61
|
+
* @slot empty - Conteúdo do estado vazio (substitui o EmptyState padrão).
|
|
62
|
+
* @quandoUsar Mostrar o que aconteceu e quando — auditoria, histórico de versões, despachos de um processo — inclusive quando ainda não aconteceu nada: o "nenhum despacho registrado" fica no componente, sem `v-if` por fora.
|
|
63
|
+
* @quandoNaoUsar Para um processo com etapas futuras e progresso, use `Stepper`. Para uma lista sem eixo de tempo, use `List` ou `Card`. Para um vazio que ocupa a tela inteira (sem trilha), use `EmptyState` direto.
|
|
45
64
|
*/
|
|
46
65
|
export interface TimelineProps {
|
|
47
66
|
items: TimelineItem[];
|
|
48
67
|
card?: boolean;
|
|
49
68
|
size?: "sm" | "md";
|
|
50
69
|
ariaLabel?: string;
|
|
70
|
+
/** Decide o estado vazio. Sem ele, vale `items` vazio com `emptyTitle` ou slot. */
|
|
71
|
+
isEmpty?: boolean;
|
|
72
|
+
/** Título do estado vazio. @default 'Nenhum evento registrado' */
|
|
73
|
+
emptyTitle?: string;
|
|
74
|
+
/** Texto de apoio do estado vazio. */
|
|
75
|
+
emptyDescription?: string;
|
|
76
|
+
/** Ícone do estado vazio: o objeto importado do Font Awesome. */
|
|
77
|
+
emptyIcon?: IconProp;
|
|
78
|
+
/** Tamanho do estado vazio. @default 'sm' */
|
|
79
|
+
emptySize?: "sm" | "md" | "lg";
|
|
80
|
+
/** Rótulo do botão do estado vazio (exibido junto de `onEmptyAction`). */
|
|
81
|
+
emptyActionLabel?: string;
|
|
82
|
+
/** Chamado no botão do estado vazio. */
|
|
83
|
+
onEmptyAction?: () => void;
|
|
84
|
+
/** Conteúdo próprio do estado vazio. No Vue, slot `empty`. */
|
|
85
|
+
empty?: any;
|
|
51
86
|
className?: string;
|
|
52
87
|
}
|
|
88
|
+
import type { IconProp } from "../Icon/Icon";
|
|
53
89
|
declare function Timeline(props: TimelineProps): import("react/jsx-runtime").JSX.Element;
|
|
54
90
|
export default Timeline;
|
|
@@ -1,8 +1,36 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import './Timeline.css';
|
|
3
3
|
import Text from "../Text/Text";
|
|
4
4
|
import Icon from "../Icon/Icon";
|
|
5
|
+
import EmptyState from "../EmptyState/EmptyState";
|
|
5
6
|
function Timeline(props) {
|
|
7
|
+
function showEmpty() {
|
|
8
|
+
if (props.isEmpty !== undefined && props.isEmpty !== null) {
|
|
9
|
+
return !!props.isEmpty;
|
|
10
|
+
}
|
|
11
|
+
const semEventos = !props.items || props.items.length === 0;
|
|
12
|
+
return semEventos && !!(props.emptyTitle || props.empty);
|
|
13
|
+
}
|
|
14
|
+
function showItems() {
|
|
15
|
+
return !showEmpty();
|
|
16
|
+
}
|
|
17
|
+
function classesDoVazio() {
|
|
18
|
+
return ["umds-timeline-empty", props.className].filter(Boolean).join(" ");
|
|
19
|
+
}
|
|
20
|
+
function emptyTitleText() {
|
|
21
|
+
return props.emptyTitle || "Nenhum evento registrado";
|
|
22
|
+
}
|
|
23
|
+
function emptyActionProps() {
|
|
24
|
+
if (!props.onEmptyAction)
|
|
25
|
+
return {};
|
|
26
|
+
return {
|
|
27
|
+
actionLabel: props.emptyActionLabel,
|
|
28
|
+
onAction: props.onEmptyAction,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function emptySizeValue() {
|
|
32
|
+
return props.emptySize || "sm";
|
|
33
|
+
}
|
|
6
34
|
function classesDaLista() {
|
|
7
35
|
return [
|
|
8
36
|
"umds-timeline",
|
|
@@ -36,6 +64,6 @@ function Timeline(props) {
|
|
|
36
64
|
function temCabecalho(item) {
|
|
37
65
|
return !!(item.title || item.timestamp || item.actor);
|
|
38
66
|
}
|
|
39
|
-
return (_jsx("ol", { role: "list", "aria-label": props.ariaLabel || "Linha do tempo", className: classesDaLista(), children: props.items?.map((item, index) => (_jsxs("li", { "aria-current": item.current ? "true" : undefined, className: classesDoItem(item), children: [_jsxs("div", { "aria-hidden": "true", className: "umds-timeline__rail", children: [_jsx("span", { className: classesDoMarcador(item), children: item.icon ? _jsx(Icon, { size: "sm", icon: item.icon }) : null }), !ehUltimo(index) ? (_jsx("span", { className: "umds-timeline__connector" })) : null] }), _jsxs("div", { className: "umds-timeline__body", children: [temCabecalho(item) ? (_jsxs("div", { className: "umds-timeline__header", children: [item.title ? (_jsx(Text, { as: "span", variant: "body2", weight: "semibold", children: item.title })) : null, item.timestamp ? (_jsx(Text, { as: "span", variant: "caption", color: "secondary", children: item.timestamp })) : null, item.actor ? (_jsx(Text, { as: "span", variant: "caption", color: "secondary", children: item.actor })) : null] })) : null, item.description ? (_jsx(Text, { as: "p", variant: "body2", color: "secondary", children: item.description })) : null] })] }, index))) }));
|
|
67
|
+
return (_jsxs(_Fragment, { children: [showEmpty() ? (_jsx("div", { role: "status", className: classesDoVazio(), children: _jsx(_Fragment, { children: props.empty || (_jsx(EmptyState, { icon: props.emptyIcon, title: emptyTitleText(), description: props.emptyDescription, size: emptySizeValue(), ...emptyActionProps() })) }) })) : null, showItems() ? (_jsx("ol", { role: "list", "aria-label": props.ariaLabel || "Linha do tempo", className: classesDaLista(), children: props.items?.map((item, index) => (_jsxs("li", { "aria-current": item.current ? "true" : undefined, className: classesDoItem(item), children: [_jsxs("div", { "aria-hidden": "true", className: "umds-timeline__rail", children: [_jsx("span", { className: classesDoMarcador(item), children: item.icon ? _jsx(Icon, { size: "sm", icon: item.icon }) : null }), !ehUltimo(index) ? (_jsx("span", { className: "umds-timeline__connector" })) : null] }), _jsxs("div", { className: "umds-timeline__body", children: [temCabecalho(item) ? (_jsxs("div", { className: "umds-timeline__header", children: [item.title ? (_jsx(Text, { as: "span", variant: "body2", weight: "semibold", children: item.title })) : null, item.timestamp ? (_jsx(Text, { as: "span", variant: "caption", color: "secondary", children: item.timestamp })) : null, item.actor ? (_jsx(Text, { as: "span", variant: "caption", color: "secondary", children: item.actor })) : null] })) : null, item.description ? (_jsx(Text, { as: "p", variant: "body2", color: "secondary", children: item.description })) : null] })] }, index))) })) : null] }));
|
|
40
68
|
}
|
|
41
69
|
export default Timeline;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { render, screen } from "@testing-library/react";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
4
5
|
import { faCheck } from "@fortawesome/free-solid-svg-icons";
|
|
5
6
|
import Timeline from "../components/Timeline/Timeline";
|
|
6
7
|
/**
|
|
@@ -71,3 +72,72 @@ describe("Timeline · conteúdo", () => {
|
|
|
71
72
|
expect(screen.getByRole("list", { name: "Despachos do processo" })).toBeInTheDocument();
|
|
72
73
|
});
|
|
73
74
|
});
|
|
75
|
+
describe("Timeline · estado vazio", () => {
|
|
76
|
+
it("isEmpty troca a trilha pelo vazio, com role status e o título padrão", () => {
|
|
77
|
+
const { container } = render(_jsx(Timeline, { items: EVENTOS, isEmpty: true }));
|
|
78
|
+
expect(screen.getByRole("status")).toBeInTheDocument();
|
|
79
|
+
expect(screen.getByText("Nenhum evento registrado")).toBeInTheDocument();
|
|
80
|
+
expect(screen.queryByRole("list")).toBeNull();
|
|
81
|
+
expect(screen.queryAllByRole("listitem")).toHaveLength(0);
|
|
82
|
+
expect(container.querySelector(".umds-timeline__marker")).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
it("título, descrição e ícone do vazio vêm das props", () => {
|
|
85
|
+
const { container } = render(_jsx(Timeline, { items: [], isEmpty: true, emptyTitle: "Nenhum despacho registrado", emptyDescription: "O hist\u00F3rico aparece aqui.", emptyIcon: faCheck }));
|
|
86
|
+
expect(screen.getByText("Nenhum despacho registrado")).toBeInTheDocument();
|
|
87
|
+
expect(screen.getByText("O histórico aparece aqui.")).toBeInTheDocument();
|
|
88
|
+
expect(container.querySelector(".umds-empty-state svg")).toBeInTheDocument();
|
|
89
|
+
});
|
|
90
|
+
it("sem isEmpty, items vazio com emptyTitle mostra o vazio", () => {
|
|
91
|
+
render(_jsx(Timeline, { items: [], emptyTitle: "Nada por aqui" }));
|
|
92
|
+
expect(screen.getByRole("status")).toBeInTheDocument();
|
|
93
|
+
expect(screen.getByText("Nada por aqui")).toBeInTheDocument();
|
|
94
|
+
expect(screen.queryByRole("list")).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
it("sem isEmpty, items vazio com a prop empty (slot) mostra o conteúdo próprio", () => {
|
|
97
|
+
render(_jsx(Timeline, { items: [], empty: _jsx("span", { "data-testid": "proprio", children: "Sem despachos" }) }));
|
|
98
|
+
expect(screen.getByRole("status")).toContainElement(screen.getByTestId("proprio"));
|
|
99
|
+
// o EmptyState padrão não entra: o conteúdo próprio o substitui
|
|
100
|
+
expect(screen.queryByText("Nenhum evento registrado")).toBeNull();
|
|
101
|
+
});
|
|
102
|
+
it("sem isEmpty e sem emptyTitle nem slot, items vazio segue sendo a trilha vazia", () => {
|
|
103
|
+
render(_jsx(Timeline, { items: [] }));
|
|
104
|
+
expect(screen.queryByRole("status")).toBeNull();
|
|
105
|
+
expect(screen.getByRole("list")).toBeInTheDocument();
|
|
106
|
+
});
|
|
107
|
+
it("isEmpty false manda: items vazio com emptyTitle mantém a trilha", () => {
|
|
108
|
+
render(_jsx(Timeline, { items: [], isEmpty: false, emptyTitle: "Nada por aqui" }));
|
|
109
|
+
expect(screen.queryByRole("status")).toBeNull();
|
|
110
|
+
expect(screen.getByRole("list")).toBeInTheDocument();
|
|
111
|
+
});
|
|
112
|
+
it("items com eventos e emptyTitle, sem isEmpty, mostra a trilha", () => {
|
|
113
|
+
render(_jsx(Timeline, { items: EVENTOS, emptyTitle: "Nada por aqui" }));
|
|
114
|
+
expect(screen.queryByRole("status")).toBeNull();
|
|
115
|
+
expect(screen.getAllByRole("listitem")).toHaveLength(3);
|
|
116
|
+
});
|
|
117
|
+
it("o botão do vazio só aparece com rótulo e callback, e chama o callback", async () => {
|
|
118
|
+
const usuario = userEvent.setup();
|
|
119
|
+
const aoAgir = vi.fn();
|
|
120
|
+
const { rerender } = render(_jsx(Timeline, { items: [], isEmpty: true, emptyActionLabel: "Limpar filtro" }));
|
|
121
|
+
expect(screen.queryByRole("button")).toBeNull();
|
|
122
|
+
rerender(_jsx(Timeline, { items: [], isEmpty: true, emptyActionLabel: "Limpar filtro", onEmptyAction: aoAgir }));
|
|
123
|
+
await usuario.click(screen.getByRole("button", { name: "Limpar filtro" }));
|
|
124
|
+
expect(aoAgir).toHaveBeenCalledTimes(1);
|
|
125
|
+
});
|
|
126
|
+
it("o entorno não desmonta: só o miolo troca ao esvaziar e ao voltar", async () => {
|
|
127
|
+
const { rerender } = render(_jsxs("section", { children: [_jsx("h3", { "data-testid": "titulo", children: "Hist\u00F3rico" }), _jsx(Timeline, { items: EVENTOS, isEmpty: false, emptyTitle: "Sem eventos" })] }));
|
|
128
|
+
const titulo = screen.getByTestId("titulo");
|
|
129
|
+
expect(screen.getAllByRole("listitem")).toHaveLength(3);
|
|
130
|
+
rerender(_jsxs("section", { children: [_jsx("h3", { "data-testid": "titulo", children: "Hist\u00F3rico" }), _jsx(Timeline, { items: [], isEmpty: true, emptyTitle: "Sem eventos" })] }));
|
|
131
|
+
await waitFor(() => expect(screen.getByRole("status")).toBeInTheDocument());
|
|
132
|
+
expect(screen.getByTestId("titulo")).toBe(titulo);
|
|
133
|
+
expect(screen.queryByRole("list")).toBeNull();
|
|
134
|
+
rerender(_jsxs("section", { children: [_jsx("h3", { "data-testid": "titulo", children: "Hist\u00F3rico" }), _jsx(Timeline, { items: EVENTOS, isEmpty: false, emptyTitle: "Sem eventos" })] }));
|
|
135
|
+
await waitFor(() => expect(screen.getAllByRole("listitem")).toHaveLength(3));
|
|
136
|
+
expect(screen.getByTestId("titulo")).toBe(titulo);
|
|
137
|
+
expect(screen.queryByRole("status")).toBeNull();
|
|
138
|
+
});
|
|
139
|
+
it("className vai ao wrapper do vazio", () => {
|
|
140
|
+
render(_jsx(Timeline, { items: [], isEmpty: true, className: "meu-vazio" }));
|
|
141
|
+
expect(screen.getByRole("status")).toHaveClass("meu-vazio");
|
|
142
|
+
});
|
|
143
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1doc/1ds-react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"react-dom": "^18.0.0",
|
|
46
46
|
"typescript": "^5.0.0",
|
|
47
47
|
"vitest": "^3.2.7",
|
|
48
|
-
"@1doc/1ds-core": "2.
|
|
48
|
+
"@1doc/1ds-core": "2.12.0",
|
|
49
49
|
"@1doc/1ds-tokens": "0.7.0"
|
|
50
50
|
},
|
|
51
51
|
"publishConfig": {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { render, screen } from "@testing-library/react";
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
3
4
|
import { faCheck } from "@fortawesome/free-solid-svg-icons";
|
|
4
5
|
import Timeline from "../components/Timeline/Timeline";
|
|
5
6
|
|
|
@@ -98,3 +99,120 @@ describe("Timeline · conteúdo", () => {
|
|
|
98
99
|
).toBeInTheDocument();
|
|
99
100
|
});
|
|
100
101
|
});
|
|
102
|
+
|
|
103
|
+
describe("Timeline · estado vazio", () => {
|
|
104
|
+
it("isEmpty troca a trilha pelo vazio, com role status e o título padrão", () => {
|
|
105
|
+
const { container } = render(<Timeline items={EVENTOS} isEmpty />);
|
|
106
|
+
expect(screen.getByRole("status")).toBeInTheDocument();
|
|
107
|
+
expect(screen.getByText("Nenhum evento registrado")).toBeInTheDocument();
|
|
108
|
+
expect(screen.queryByRole("list")).toBeNull();
|
|
109
|
+
expect(screen.queryAllByRole("listitem")).toHaveLength(0);
|
|
110
|
+
expect(container.querySelector(".umds-timeline__marker")).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("título, descrição e ícone do vazio vêm das props", () => {
|
|
114
|
+
const { container } = render(
|
|
115
|
+
<Timeline
|
|
116
|
+
items={[]}
|
|
117
|
+
isEmpty
|
|
118
|
+
emptyTitle="Nenhum despacho registrado"
|
|
119
|
+
emptyDescription="O histórico aparece aqui."
|
|
120
|
+
emptyIcon={faCheck}
|
|
121
|
+
/>,
|
|
122
|
+
);
|
|
123
|
+
expect(screen.getByText("Nenhum despacho registrado")).toBeInTheDocument();
|
|
124
|
+
expect(screen.getByText("O histórico aparece aqui.")).toBeInTheDocument();
|
|
125
|
+
expect(container.querySelector(".umds-empty-state svg")).toBeInTheDocument();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("sem isEmpty, items vazio com emptyTitle mostra o vazio", () => {
|
|
129
|
+
render(<Timeline items={[]} emptyTitle="Nada por aqui" />);
|
|
130
|
+
expect(screen.getByRole("status")).toBeInTheDocument();
|
|
131
|
+
expect(screen.getByText("Nada por aqui")).toBeInTheDocument();
|
|
132
|
+
expect(screen.queryByRole("list")).toBeNull();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("sem isEmpty, items vazio com a prop empty (slot) mostra o conteúdo próprio", () => {
|
|
136
|
+
render(
|
|
137
|
+
<Timeline items={[]} empty={<span data-testid="proprio">Sem despachos</span>} />,
|
|
138
|
+
);
|
|
139
|
+
expect(screen.getByRole("status")).toContainElement(
|
|
140
|
+
screen.getByTestId("proprio"),
|
|
141
|
+
);
|
|
142
|
+
// o EmptyState padrão não entra: o conteúdo próprio o substitui
|
|
143
|
+
expect(screen.queryByText("Nenhum evento registrado")).toBeNull();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("sem isEmpty e sem emptyTitle nem slot, items vazio segue sendo a trilha vazia", () => {
|
|
147
|
+
render(<Timeline items={[]} />);
|
|
148
|
+
expect(screen.queryByRole("status")).toBeNull();
|
|
149
|
+
expect(screen.getByRole("list")).toBeInTheDocument();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("isEmpty false manda: items vazio com emptyTitle mantém a trilha", () => {
|
|
153
|
+
render(<Timeline items={[]} isEmpty={false} emptyTitle="Nada por aqui" />);
|
|
154
|
+
expect(screen.queryByRole("status")).toBeNull();
|
|
155
|
+
expect(screen.getByRole("list")).toBeInTheDocument();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("items com eventos e emptyTitle, sem isEmpty, mostra a trilha", () => {
|
|
159
|
+
render(<Timeline items={EVENTOS} emptyTitle="Nada por aqui" />);
|
|
160
|
+
expect(screen.queryByRole("status")).toBeNull();
|
|
161
|
+
expect(screen.getAllByRole("listitem")).toHaveLength(3);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("o botão do vazio só aparece com rótulo e callback, e chama o callback", async () => {
|
|
165
|
+
const usuario = userEvent.setup();
|
|
166
|
+
const aoAgir = vi.fn();
|
|
167
|
+
const { rerender } = render(
|
|
168
|
+
<Timeline items={[]} isEmpty emptyActionLabel="Limpar filtro" />,
|
|
169
|
+
);
|
|
170
|
+
expect(screen.queryByRole("button")).toBeNull();
|
|
171
|
+
rerender(
|
|
172
|
+
<Timeline
|
|
173
|
+
items={[]}
|
|
174
|
+
isEmpty
|
|
175
|
+
emptyActionLabel="Limpar filtro"
|
|
176
|
+
onEmptyAction={aoAgir}
|
|
177
|
+
/>,
|
|
178
|
+
);
|
|
179
|
+
await usuario.click(screen.getByRole("button", { name: "Limpar filtro" }));
|
|
180
|
+
expect(aoAgir).toHaveBeenCalledTimes(1);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("o entorno não desmonta: só o miolo troca ao esvaziar e ao voltar", async () => {
|
|
184
|
+
const { rerender } = render(
|
|
185
|
+
<section>
|
|
186
|
+
<h3 data-testid="titulo">Histórico</h3>
|
|
187
|
+
<Timeline items={EVENTOS} isEmpty={false} emptyTitle="Sem eventos" />
|
|
188
|
+
</section>,
|
|
189
|
+
);
|
|
190
|
+
const titulo = screen.getByTestId("titulo");
|
|
191
|
+
expect(screen.getAllByRole("listitem")).toHaveLength(3);
|
|
192
|
+
|
|
193
|
+
rerender(
|
|
194
|
+
<section>
|
|
195
|
+
<h3 data-testid="titulo">Histórico</h3>
|
|
196
|
+
<Timeline items={[]} isEmpty emptyTitle="Sem eventos" />
|
|
197
|
+
</section>,
|
|
198
|
+
);
|
|
199
|
+
await waitFor(() => expect(screen.getByRole("status")).toBeInTheDocument());
|
|
200
|
+
expect(screen.getByTestId("titulo")).toBe(titulo);
|
|
201
|
+
expect(screen.queryByRole("list")).toBeNull();
|
|
202
|
+
|
|
203
|
+
rerender(
|
|
204
|
+
<section>
|
|
205
|
+
<h3 data-testid="titulo">Histórico</h3>
|
|
206
|
+
<Timeline items={EVENTOS} isEmpty={false} emptyTitle="Sem eventos" />
|
|
207
|
+
</section>,
|
|
208
|
+
);
|
|
209
|
+
await waitFor(() => expect(screen.getAllByRole("listitem")).toHaveLength(3));
|
|
210
|
+
expect(screen.getByTestId("titulo")).toBe(titulo);
|
|
211
|
+
expect(screen.queryByRole("status")).toBeNull();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("className vai ao wrapper do vazio", () => {
|
|
215
|
+
render(<Timeline items={[]} isEmpty className="meu-vazio" />);
|
|
216
|
+
expect(screen.getByRole("status")).toHaveClass("meu-vazio");
|
|
217
|
+
});
|
|
218
|
+
});
|