@1doc/1ds-react 2.10.0 → 2.11.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +235 -0
  2. package/dist/components/Accordion/Accordion.d.ts +6 -0
  3. package/dist/components/Accordion/Accordion.js +17 -5
  4. package/dist/components/Badge/Badge.css +13 -4
  5. package/dist/components/Badge/Badge.d.ts +9 -2
  6. package/dist/components/Badge/Badge.js +3 -0
  7. package/dist/components/Button/Button.css +5 -0
  8. package/dist/components/Button/Button.d.ts +3 -0
  9. package/dist/components/Button/Button.js +1 -0
  10. package/dist/components/Calendar/Calendar.css +10 -0
  11. package/dist/components/Calendar/Calendar.d.ts +3 -0
  12. package/dist/components/Calendar/Calendar.js +6 -1
  13. package/dist/components/Coachmark/Coachmark.d.ts +2 -1
  14. package/dist/components/Coachmark/Coachmark.js +6 -1
  15. package/dist/components/CodeBlock/CodeBlock.css +3 -3
  16. package/dist/components/CodeBlock/CodeBlock.d.ts +2 -2
  17. package/dist/components/DataTable/DataTable.css +42 -0
  18. package/dist/components/DataTable/DataTable.d.ts +9 -2
  19. package/dist/components/DataTable/DataTable.js +15 -0
  20. package/dist/components/Input/Input.css +9 -0
  21. package/dist/components/Input/Input.d.ts +3 -0
  22. package/dist/components/Input/Input.js +1 -0
  23. package/dist/components/Kbd/Kbd.css +3 -3
  24. package/dist/components/Kbd/Kbd.d.ts +1 -1
  25. package/dist/components/Modal/Modal.css +19 -0
  26. package/dist/components/Modal/Modal.d.ts +7 -0
  27. package/dist/components/Modal/Modal.js +1 -0
  28. package/dist/components/RichTextEditor/RichTextEditor.d.ts +2 -2
  29. package/dist/components/RichTextEditor/RichTextEditor.js +0 -5
  30. package/dist/components/SegmentedControl/SegmentedControl.css +46 -0
  31. package/dist/components/SegmentedControl/SegmentedControl.d.ts +3 -0
  32. package/dist/components/SegmentedControl/SegmentedControl.js +1 -0
  33. package/dist/components/Select/Select.css +28 -2
  34. package/dist/components/Select/Select.d.ts +7 -0
  35. package/dist/components/Select/Select.js +55 -9
  36. package/dist/components/StatCard/StatCard.css +8 -0
  37. package/dist/components/StatCard/StatCard.d.ts +3 -0
  38. package/dist/components/StatCard/StatCard.js +1 -0
  39. package/dist/components/Tabs/Tabs.css +11 -0
  40. package/dist/components/Tabs/Tabs.d.ts +7 -0
  41. package/dist/components/Tabs/Tabs.js +7 -1
  42. package/dist/components/Tag/Tag.css +3 -1
  43. package/dist/teste/AccordionControlado.test.d.ts +1 -0
  44. package/dist/teste/AccordionControlado.test.js +73 -0
  45. package/dist/teste/BadgeAttachedTomEPadding.test.d.ts +1 -0
  46. package/dist/teste/BadgeAttachedTomEPadding.test.js +43 -0
  47. package/dist/teste/ButtonDensidade.test.d.ts +1 -0
  48. package/dist/teste/ButtonDensidade.test.js +28 -0
  49. package/dist/teste/CalendarCompacto.test.d.ts +1 -0
  50. package/dist/teste/CalendarCompacto.test.js +29 -0
  51. package/dist/teste/CoachmarkUmPasso.test.d.ts +1 -0
  52. package/dist/teste/CoachmarkUmPasso.test.js +34 -0
  53. package/dist/teste/DataTableCompactaETom.test.d.ts +1 -0
  54. package/dist/teste/DataTableCompactaETom.test.js +69 -0
  55. package/dist/teste/InputGhost.test.d.ts +1 -0
  56. package/dist/teste/InputGhost.test.js +32 -0
  57. package/dist/teste/ModalProporcoes1Doc.test.d.ts +1 -0
  58. package/dist/teste/ModalProporcoes1Doc.test.js +32 -0
  59. package/dist/teste/RichTextEditorEstiloDeTexto.test.js +7 -7
  60. package/dist/teste/SegmentedControlSuperficie.test.d.ts +1 -0
  61. package/dist/teste/SegmentedControlSuperficie.test.js +39 -0
  62. package/dist/teste/SelectItemComEscopo.test.d.ts +1 -0
  63. package/dist/teste/SelectItemComEscopo.test.js +54 -0
  64. package/dist/teste/SelectListaFiltro.test.d.ts +1 -0
  65. package/dist/teste/SelectListaFiltro.test.js +102 -0
  66. package/dist/teste/StatCardTamanhoSm.test.d.ts +1 -0
  67. package/dist/teste/StatCardTamanhoSm.test.js +32 -0
  68. package/dist/teste/TabsCompacta.test.d.ts +1 -0
  69. package/dist/teste/TabsCompacta.test.js +35 -0
  70. package/dist/teste/TagPositivoTingido.test.d.ts +1 -0
  71. package/dist/teste/TagPositivoTingido.test.js +54 -0
  72. package/dist/teste/lerArquivo.d.ts +1 -0
  73. package/dist/teste/lerArquivo.js +12 -0
  74. package/dist/utils/html-seguro.js +1 -1
  75. package/package.json +3 -3
  76. package/src/teste/AccordionControlado.test.tsx +87 -0
  77. package/src/teste/BadgeAttachedTomEPadding.test.tsx +60 -0
  78. package/src/teste/ButtonDensidade.test.tsx +40 -0
  79. package/src/teste/CalendarCompacto.test.tsx +37 -0
  80. package/src/teste/CoachmarkUmPasso.test.tsx +45 -0
  81. package/src/teste/DataTableCompactaETom.test.tsx +85 -0
  82. package/src/teste/InputGhost.test.tsx +42 -0
  83. package/src/teste/ModalProporcoes1Doc.test.tsx +36 -0
  84. package/src/teste/RichTextEditorEstiloDeTexto.test.tsx +8 -8
  85. package/src/teste/SegmentedControlSuperficie.test.tsx +52 -0
  86. package/src/teste/SelectItemComEscopo.test.tsx +69 -0
  87. package/src/teste/SelectListaFiltro.test.tsx +120 -0
  88. package/src/teste/StatCardTamanhoSm.test.tsx +42 -0
  89. package/src/teste/TabsCompacta.test.tsx +45 -0
  90. package/src/teste/TagPositivoTingido.test.tsx +60 -0
  91. package/src/teste/lerArquivo.ts +13 -0
  92. package/src/utils/html-seguro.ts +1 -1
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import Input from "../components/Input/Input";
3
+ import { render } from "@testing-library/react";
4
+ import { lerArquivo } from "./lerArquivo";
5
+
6
+ const cssInput = await lerArquivo("../core/src/components/Input/Input.css");
7
+
8
+ const CSS: Record<string, string> = { Input: cssInput };
9
+ const css = (componente: string) => CSS[componente].replace(/\r\n/g, "\n");
10
+ const bloco = (texto: string, seletor: string) => {
11
+ const inicio = texto.indexOf(seletor + " {");
12
+ expect(inicio, seletor).toBeGreaterThanOrEqual(0);
13
+ return texto.slice(inicio, texto.indexOf("}", inicio));
14
+ };
15
+
16
+ describe("Input · variant ghost", () => {
17
+ it("ghost escreve a classe; sem a prop e com default, não", () => {
18
+ const { container } = render(
19
+ <>
20
+ <Input ariaLabel="a" variant="ghost" />
21
+ <Input ariaLabel="b" />
22
+ <Input ariaLabel="c" variant="default" />
23
+ </>,
24
+ );
25
+ expect(container.children[0]).toHaveClass("umds-input--ghost");
26
+ expect(container.children[1]).not.toHaveClass("umds-input--ghost");
27
+ expect(container.children[2]).not.toHaveClass("umds-input--ghost");
28
+ });
29
+
30
+ it("CSS: só o repouso muda (fundo claro, borda clara) e com baixa especificidade, para o hover e o foco vencerem", () => {
31
+ const t = css("Input");
32
+ const ghost = bloco(t, ".umds-input--ghost:where(:not(.umds-input--error):not(.umds-input--disabled):not(.umds-input--readonly)) .umds-input__container");
33
+ expect(ghost).toMatch(/background-color:\s*var\(--umds-semantic-color-background-light\)/);
34
+ expect(ghost).toMatch(/border-color:\s*var\(--umds-semantic-color-neutral-light-medium\)/);
35
+ expect(t.indexOf(".umds-input__container:focus-within:not(")).toBeGreaterThanOrEqual(0);
36
+ });
37
+
38
+ it("o foco, o erro e o desabilitado do campo ghost seguem as classes do padrão", () => {
39
+ const { container } = render(<Input ariaLabel="d" variant="ghost" errorMessage="Obrigatório" />);
40
+ expect(container.firstElementChild).toHaveClass("umds-input--ghost", "umds-input--error");
41
+ });
42
+ });
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import Modal from "../components/Modal/Modal";
3
+ import { render, screen } from "@testing-library/react";
4
+ import { lerArquivo } from "./lerArquivo";
5
+
6
+ const cssModal = await lerArquivo("../core/src/components/Modal/Modal.css");
7
+
8
+ const CSS: Record<string, string> = { Modal: cssModal };
9
+ const css = (componente: string) => CSS[componente].replace(/\r\n/g, "\n");
10
+ const bloco = (texto: string, seletor: string) => {
11
+ const inicio = texto.indexOf(seletor + " {");
12
+ expect(inicio, seletor).toBeGreaterThanOrEqual(0);
13
+ return texto.slice(inicio, texto.indexOf("}", inicio));
14
+ };
15
+
16
+ describe("Modal · density 1doc", () => {
17
+ it("escreve a classe e convive com variant primary; sem a prop, não", () => {
18
+ render(<Modal open inline density="1doc" variant="primary" title="Resolver tarefa" />);
19
+ expect(screen.getByRole("dialog", { name: "Resolver tarefa" })).toHaveClass("umds-modal--1doc", "umds-modal--primary", "umds-modal--base");
20
+ });
21
+
22
+ it("sem density o modal é o de antes", () => {
23
+ render(<Modal open inline title="Padrão" />);
24
+ expect(screen.getByRole("dialog", { name: "Padrão" })).not.toHaveClass("umds-modal--1doc");
25
+ });
26
+
27
+ it("CSS: cabeçalho de 67px, rodapé de 65px (padding 12px 24px) e raio de 12px; o padrão (8px) não muda", () => {
28
+ const t = css("Modal");
29
+ expect(bloco(t, ".umds-modal--1doc.umds-modal--base .umds-modal__header")).toMatch(/min-height:\s*67px/);
30
+ const rodape = bloco(t, ".umds-modal--1doc.umds-modal--base .umds-modal__footer");
31
+ expect(rodape).toMatch(/min-height:\s*65px/);
32
+ expect(rodape).toMatch(/padding:\s*var\(--umds-primitive-spacing-mini\)\s+var\(--umds-primitive-spacing-xxs\)/);
33
+ expect(t).toMatch(/\.umds-modal--1doc:not\(\.umds-modal--fullscreen\):not\(\.umds-modal--janela-fullscreen\):not\(\.umds-modal--sheet\) \{\n(?:\s*\/\*.*\*\/\n)?\s*border-radius:\s*12px/);
34
+ expect(bloco(t, ".umds-modal")).toMatch(/border-radius:\s*var\(--umds-primitive-border-radius-sm\)/);
35
+ });
36
+ });
@@ -29,7 +29,7 @@ describe("html-seguro: estilo de texto por token", () => {
29
29
  expect(estiloDeTexto("color", "var(--umds-primitive-red-pure)")).toBe("color: var(--umds-primitive-red-pure);");
30
30
  expect(estiloDeTexto("color", "var(--umds-semantic-color-feedback-negative-pure)")).toBe("color: var(--umds-semantic-color-feedback-negative-pure);");
31
31
  expect(estiloDeTexto("font-size", "var(--umds-primitive-font-size-xs)")).toBe("font-size: var(--umds-primitive-font-size-xs);");
32
- expect(estiloDeTexto("font-family", "var(--umds-semantic-font-family-mono)")).toBe("font-family: var(--umds-semantic-font-family-mono);");
32
+ expect(estiloDeTexto("font-family", "var(--umds-semantic-font-family-base)")).toBe("font-family: var(--umds-semantic-font-family-base);");
33
33
  for (const mau of ["red", "#ff0000", "rgb(255,0,0)", "var(--outra-coisa)", "var(--umds-primitive-red-pure); background: red", "expression(alert(1))", "url(x)"]) {
34
34
  expect(estiloDeTexto("color", mau)).toBe("");
35
35
  }
@@ -43,8 +43,8 @@ describe("html-seguro: estilo de texto por token", () => {
43
43
  expect(sanitizarHtml('<span style="color: var(--umds-primitive-red-pure); background: red; font-size: 40px">a</span>')).toBe(
44
44
  '<span style="color: var(--umds-primitive-red-pure);">a</span>',
45
45
  );
46
- expect(sanitizarHtml('<span style="font-family: var(--umds-semantic-font-family-mono); font-size: var(--umds-primitive-font-size-sm)">a</span>')).toBe(
47
- '<span style="font-family: var(--umds-semantic-font-family-mono); font-size: var(--umds-primitive-font-size-sm);">a</span>',
46
+ expect(sanitizarHtml('<span style="font-family: var(--umds-semantic-font-family-base); font-size: var(--umds-primitive-font-size-sm)">a</span>')).toBe(
47
+ '<span style="font-family: var(--umds-semantic-font-family-base); font-size: var(--umds-primitive-font-size-sm);">a</span>',
48
48
  );
49
49
  expect(sanitizarHtml('<span style="color: red">a</span>')).toBe("<span>a</span>");
50
50
  });
@@ -74,15 +74,15 @@ describe("RichTextEditor: fonte, tamanho e cor do texto", () => {
74
74
  expect(screen.queryByRole("group", { name: "Cor do texto" })).toBeNull();
75
75
  });
76
76
 
77
- it("a fonte monoespaçada embrulha o texto selecionado num span com o token", async () => {
77
+ it("a fonte do DS embrulha o texto selecionado num span com o token", async () => {
78
78
  const usuario = userEvent.setup();
79
79
  const aoMudar = vi.fn();
80
80
  abrir(["fontFamily"], { onChange: aoMudar });
81
81
  selecionarTexto();
82
82
  await usuario.click(screen.getByRole("button", { name: "Fonte" }));
83
83
  await usuario.click(screen.getByRole("combobox", { name: "Fonte" }));
84
- await usuario.click(screen.getByRole("option", { name: "Monoespaçada" }));
85
- expect(execCommand).toHaveBeenCalledWith("insertHTML", false, '<span style="font-family: var(--umds-semantic-font-family-mono);">Parecer</span>');
84
+ await usuario.click(screen.getByRole("option", { name: "Padrão" }));
85
+ expect(execCommand).toHaveBeenCalledWith("insertHTML", false, '<span style="font-family: var(--umds-semantic-font-family-base);">Parecer</span>');
86
86
  expect(aoMudar).toHaveBeenCalled();
87
87
  expect(screen.queryByRole("group", { name: "Fonte" })).toBeNull();
88
88
  });
@@ -121,11 +121,11 @@ describe("RichTextEditor: fonte, tamanho e cor do texto", () => {
121
121
 
122
122
  it("os textos vêm por labels", async () => {
123
123
  const usuario = userEvent.setup();
124
- abrir(["fontFamily", "textColor"], { labels: { fontFamily: "Font", textColor: "Text color", fontFamilyMono: "Monospaced", styleClose: "Close" } });
124
+ abrir(["fontFamily", "textColor"], { labels: { fontFamily: "Font", textColor: "Text color", fontFamilyBase: "Default", styleClose: "Close" } });
125
125
  selecionarTexto();
126
126
  await usuario.click(screen.getByRole("button", { name: "Font" }));
127
127
  await usuario.click(screen.getByRole("combobox", { name: "Font" }));
128
- expect(screen.getByRole("option", { name: "Monospaced" })).toBeInTheDocument();
128
+ expect(screen.getByRole("option", { name: "Default" })).toBeInTheDocument();
129
129
  await usuario.keyboard("{Escape}");
130
130
  expect(screen.getByRole("button", { name: "Close" })).toBeInTheDocument();
131
131
  });
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import SegmentedControl from "../components/SegmentedControl/SegmentedControl";
3
+ import { render } from "@testing-library/react";
4
+ import { lerArquivo } from "./lerArquivo";
5
+
6
+ const cssSegmentedControl = await lerArquivo("../core/src/components/SegmentedControl/SegmentedControl.css");
7
+
8
+ const CSS: Record<string, string> = { SegmentedControl: cssSegmentedControl };
9
+ const css = (componente: string) => CSS[componente].replace(/\r\n/g, "\n");
10
+ const bloco = (texto: string, seletor: string) => {
11
+ const inicio = texto.indexOf(seletor + " {");
12
+ expect(inicio, seletor).toBeGreaterThanOrEqual(0);
13
+ return texto.slice(inicio, texto.indexOf("}", inicio));
14
+ };
15
+
16
+ describe("SegmentedControl · variant surface", () => {
17
+ const OPCOES = [
18
+ { label: "Individual", value: "individual", count: 12 },
19
+ { label: "Em lote", value: "lote" },
20
+ ];
21
+
22
+ it("surface escreve a classe; sem a prop e com default, não", () => {
23
+ const { container } = render(
24
+ <>
25
+ <SegmentedControl options={OPCOES} variant="surface" defaultValue="individual" />
26
+ <SegmentedControl options={OPCOES} defaultValue="individual" />
27
+ <SegmentedControl options={OPCOES} variant="default" defaultValue="individual" />
28
+ </>,
29
+ );
30
+ expect(container.children[0]).toHaveClass("umds-segmented--surface");
31
+ expect(container.children[1]).not.toHaveClass("umds-segmented--surface");
32
+ expect(container.children[2]).not.toHaveClass("umds-segmented--surface");
33
+ });
34
+
35
+ it("CSS: o escolhido é branco, em negrito e com sombra; o trilho é cinza claro; o azul cheio do padrão segue", () => {
36
+ const t = css("SegmentedControl");
37
+ const escolhido = bloco(t, ".umds-segmented--surface .umds-segmented__input:checked + .umds-segmented__item,\n.umds-segmented--surface .umds-segmented__input:checked + .umds-segmented__item:hover");
38
+ expect(escolhido).toMatch(/background-color:\s*var\(--umds-semantic-color-background-white\)/);
39
+ expect(escolhido).toMatch(/font-weight:\s*var\(--umds-primitive-font-weight-bold\)/);
40
+ expect(escolhido).toMatch(/box-shadow:\s*var\(--umds-semantic-shadow-md\)/);
41
+ expect(bloco(t, ".umds-segmented--surface .umds-segmented__track")).toMatch(/background-color:\s*var\(--umds-semantic-color-neutral-light-medium\)/);
42
+ expect(t).toMatch(/\.umds-segmented__input:checked \+ \.umds-segmented__item,\n\.umds-segmented__input:checked \+ \.umds-segmented__item:hover \{\n\s*background-color:\s*var\(--umds-semantic-color-primary-pure\)/);
43
+ });
44
+
45
+ it("o contador do escolhido fica cinza sobre o branco e o dos outros, branco sobre o trilho", () => {
46
+ const t = css("SegmentedControl");
47
+ expect(bloco(t, ".umds-segmented--surface .umds-segmented__count")).toMatch(/background-color:\s*var\(--umds-semantic-color-background-white\)/);
48
+ expect(bloco(t, ".umds-segmented--surface .umds-segmented__input:checked + .umds-segmented__item .umds-segmented__count")).toMatch(
49
+ /background-color:\s*var\(--umds-semantic-color-neutral-light-medium\)/,
50
+ );
51
+ });
52
+ });
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { fireEvent, render, screen } from "@testing-library/react";
3
+ import Select from "../components/Select/Select";
4
+
5
+ const OPCOES = [
6
+ { label: "Urgente", value: "urgente", etiqueta: "Prazo hoje" },
7
+ { label: "Rotina", value: "rotina", etiqueta: "Sem prazo" },
8
+ ];
9
+ const item = ({ optionData }: any) => (
10
+ <span data-testid="item">
11
+ <b>{optionData.label}</b> <i>{optionData.etiqueta}</i>
12
+ </span>
13
+ );
14
+
15
+ describe("Select · item com escopo da opção", () => {
16
+ it("a lista mostra o item de cada opção, recebendo a opção (optionData e o apelido option)", () => {
17
+ const vistos: any[] = [];
18
+ render(
19
+ <Select
20
+ ariaLabel="Prioridade"
21
+ options={OPCOES}
22
+ item={(escopo: any) => {
23
+ vistos.push(escopo);
24
+ return <span data-testid="item">{escopo.optionData.label}</span>;
25
+ }}
26
+ />,
27
+ );
28
+ fireEvent.click(screen.getByRole("combobox"));
29
+ expect(screen.getAllByTestId("item").map((n) => n.textContent)).toEqual(["Urgente", "Rotina"]);
30
+ expect(vistos[0].optionData).toBe(OPCOES[0]);
31
+ expect(vistos[0].option).toBe(OPCOES[0]);
32
+ });
33
+
34
+ it("o gatilho com a opção escolhida mostra o conteúdo do item, não o texto", () => {
35
+ render(<Select ariaLabel="Prioridade" options={OPCOES} value="urgente" item={item} />);
36
+ const gatilho = screen.getByRole("combobox");
37
+ expect(gatilho.querySelector(".umds-select__trigger-item")).not.toBeNull();
38
+ expect(gatilho).toHaveTextContent("Urgente");
39
+ expect(gatilho).toHaveTextContent("Prazo hoje");
40
+ expect(gatilho.querySelector(".umds-select__placeholder")).toBeNull();
41
+ });
42
+
43
+ it("sem valor o gatilho mostra o placeholder; escolher na lista passa o item para o gatilho", () => {
44
+ function App() {
45
+ return <Select ariaLabel="Prioridade" placeholder="Escolha" options={OPCOES} item={item} />;
46
+ }
47
+ render(<App />);
48
+ const gatilho = screen.getByRole("combobox");
49
+ expect(gatilho).toHaveTextContent("Escolha");
50
+ expect(gatilho.querySelector(".umds-select__trigger-item")).toBeNull();
51
+ fireEvent.click(gatilho);
52
+ fireEvent.click(screen.getAllByRole("option")[1]);
53
+ expect(screen.getByRole("combobox").querySelector(".umds-select__trigger-item")).toHaveTextContent("Rotina");
54
+ });
55
+
56
+ it("seleção múltipla segue com o texto no gatilho", () => {
57
+ render(<Select ariaLabel="Prioridade" multiple options={OPCOES} value={["urgente"]} item={item} />);
58
+ expect(screen.getByRole("combobox").querySelector(".umds-select__trigger-item")).toBeNull();
59
+ });
60
+
61
+ it("sem item nada muda: o gatilho mostra o rótulo; item como nó solto (legado) não vai ao gatilho", () => {
62
+ const { rerender } = render(<Select ariaLabel="Prioridade" options={OPCOES} value="urgente" />);
63
+ expect(screen.getByRole("combobox")).toHaveTextContent("Urgente");
64
+ expect(screen.getByRole("combobox").querySelector(".umds-select__trigger-item")).toBeNull();
65
+ rerender(<Select ariaLabel="Prioridade" options={OPCOES} value="urgente" item={<em>fixo</em>} />);
66
+ expect(screen.getByRole("combobox").querySelector(".umds-select__trigger-item")).toBeNull();
67
+ expect(screen.getByRole("combobox")).toHaveTextContent("Urgente");
68
+ });
69
+ });
@@ -0,0 +1,120 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
3
+ import Select from "../components/Select/Select";
4
+ import { lerArquivo } from "./lerArquivo";
5
+
6
+ const cssSelect = await lerArquivo("../core/src/components/Select/Select.css");
7
+
8
+ const OPCOES = [
9
+ { label: "Urgente", value: "urgente" },
10
+ { label: "Revisão", value: "revisao" },
11
+ ];
12
+
13
+ afterEach(() => vi.restoreAllMocks());
14
+
15
+ const lista = (container: HTMLElement) => container.querySelector(".umds-select__dropdown") as HTMLElement;
16
+
17
+ /** jsdom não tem layout: só a lista "mede" o que o teste mandar. */
18
+ function medirLista(direita: number, larguraDaJanela: number) {
19
+ vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(function (this: HTMLElement) {
20
+ const eLista = this.classList.contains("umds-select__dropdown");
21
+ return { top: 0, bottom: 0, left: 0, right: eLista ? direita : 0, width: 0, height: 0, x: 0, y: 0, toJSON() {} } as any;
22
+ });
23
+ Object.defineProperty(window, "innerWidth", { value: larguraDaJanela, configurable: true });
24
+ }
25
+
26
+ describe("Select filtro · listMinWidth", () => {
27
+ it("um número vira px e a lista nunca fica mais estreita que o gatilho", () => {
28
+ const { container } = render(<Select ariaLabel="x" options={OPCOES} variant="filter" listMinWidth={220} />);
29
+ fireEvent.click(screen.getByRole("combobox"));
30
+ expect(lista(container).style.minWidth).toBe("max(100%, 220px)");
31
+ });
32
+
33
+ it("uma medida CSS vale como veio", () => {
34
+ const { container } = render(<Select ariaLabel="x" options={OPCOES} variant="filter" listMinWidth="18rem" />);
35
+ fireEvent.click(screen.getByRole("combobox"));
36
+ expect(lista(container).style.minWidth).toBe("max(100%, 18rem)");
37
+ });
38
+
39
+ it("0 é um valor (sem mínimo além do gatilho), não ausência", () => {
40
+ const { container } = render(<Select ariaLabel="x" options={OPCOES} variant="filter" listMinWidth={0} />);
41
+ fireEvent.click(screen.getByRole("combobox"));
42
+ expect(lista(container).style.minWidth).toBe("max(100%, 0px)");
43
+ });
44
+
45
+ it("sem a prop o mínimo é o do CSS (240px), sem estilo inline", () => {
46
+ const { container } = render(<Select ariaLabel="x" options={OPCOES} variant="filter" />);
47
+ fireEvent.click(screen.getByRole("combobox"));
48
+ expect(lista(container).style.minWidth).toBe("");
49
+ });
50
+
51
+ it("o Select padrão ignora a prop (a lista é a largura do campo)", () => {
52
+ const { container } = render(<Select ariaLabel="x" options={OPCOES} listMinWidth={220} />);
53
+ fireEvent.click(screen.getByRole("combobox"));
54
+ expect(lista(container).style.minWidth).toBe("");
55
+ });
56
+ });
57
+
58
+ describe("Select filtro · nunca passa da borda da janela", () => {
59
+ it("a lista que estoura a borda direita se alinha ao fim do gatilho", async () => {
60
+ medirLista(1457, 1440);
61
+ const { container } = render(<Select ariaLabel="x" options={OPCOES} variant="filter" />);
62
+ fireEvent.click(screen.getByRole("combobox"));
63
+ await waitFor(() => expect(container.querySelector(".umds-select--list-end")).not.toBeNull());
64
+ });
65
+
66
+ it("a lista que cabe fica onde está", async () => {
67
+ medirLista(900, 1440);
68
+ const { container } = render(<Select ariaLabel="x" options={OPCOES} variant="filter" />);
69
+ fireEvent.click(screen.getByRole("combobox"));
70
+ await waitFor(() => expect(lista(container)).not.toBeNull());
71
+ expect(container.querySelector(".umds-select--list-end")).toBeNull();
72
+ });
73
+
74
+ it("deixa 8px de respiro: encostada na borda ainda alinha", async () => {
75
+ medirLista(1436, 1440);
76
+ const { container } = render(<Select ariaLabel="x" options={OPCOES} variant="filter" />);
77
+ fireEvent.click(screen.getByRole("combobox"));
78
+ await waitFor(() => expect(container.querySelector(".umds-select--list-end")).not.toBeNull());
79
+ });
80
+
81
+ it("fechar desliga o alinhamento", async () => {
82
+ medirLista(1457, 1440);
83
+ const { container } = render(<Select ariaLabel="x" options={OPCOES} variant="filter" />);
84
+ fireEvent.click(screen.getByRole("combobox"));
85
+ await waitFor(() => expect(container.querySelector(".umds-select--list-end")).not.toBeNull());
86
+ fireEvent.click(screen.getByRole("combobox"));
87
+ await waitFor(() => expect(container.querySelector(".umds-select--list-end")).toBeNull());
88
+ });
89
+
90
+ it("o Select padrão (não filtro) não é movido", async () => {
91
+ medirLista(1457, 1440);
92
+ const { container } = render(<Select ariaLabel="x" options={OPCOES} />);
93
+ fireEvent.click(screen.getByRole("combobox"));
94
+ await waitFor(() => expect(lista(container)).not.toBeNull());
95
+ expect(container.querySelector(".umds-select--list-end")).toBeNull();
96
+ });
97
+ });
98
+
99
+ describe("Select filtro · CSS", () => {
100
+ const css = cssSelect.replace(/\r\n/g, "\n");
101
+ const bloco = (seletor: string) => {
102
+ const inicio = css.indexOf(seletor + " {");
103
+ return css.slice(inicio, css.indexOf("}", inicio));
104
+ };
105
+
106
+ it("o wrapper do filtro é fit-content (o auto o esticava até o pai e a lista herdava essa largura)", () => {
107
+ expect(bloco(".umds-select--filter")).toMatch(/width:\s*fit-content/);
108
+ });
109
+
110
+ it("a busca da lista fica fora da conta da largura natural", () => {
111
+ expect(bloco(".umds-select--filter .umds-select__search-container")).toMatch(/contain:\s*inline-size/);
112
+ expect(bloco(".umds-select__search-input")).toMatch(/min-width:\s*0/);
113
+ });
114
+
115
+ it("o alinhamento ao fim ancora a lista pela direita", () => {
116
+ const b = bloco(".umds-select--filter.umds-select--list-end .umds-select__dropdown");
117
+ expect(b).toMatch(/left:\s*auto/);
118
+ expect(b).toMatch(/right:\s*0/);
119
+ });
120
+ });
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { render } from "@testing-library/react";
3
+ import StatCard from "../components/StatCard/StatCard";
4
+ import { lerArquivo } from "./lerArquivo";
5
+
6
+ const cssStatCard = await lerArquivo("../core/src/components/StatCard/StatCard.css");
7
+
8
+ const css = cssStatCard.replace(/\r\n/g, "\n");
9
+ const bloco = (seletor: string) => {
10
+ const inicio = css.indexOf(seletor + " {");
11
+ return css.slice(inicio, css.indexOf("}", inicio));
12
+ };
13
+
14
+ describe("StatCard · size", () => {
15
+ it("sm escreve a classe; sem a prop e com md o cartão é o de antes", () => {
16
+ const { container } = render(
17
+ <>
18
+ <StatCard value={44} label="Não lidos" size="sm" />
19
+ <StatCard value={44} label="Não lidos" />
20
+ <StatCard value={44} label="Não lidos" size="md" />
21
+ </>,
22
+ );
23
+ expect(container.children[0]).toHaveClass("umds-stat-card--sm");
24
+ expect(container.children[1]).not.toHaveClass("umds-stat-card--sm");
25
+ expect(container.children[2]).not.toHaveClass("umds-stat-card--sm");
26
+ });
27
+
28
+ it("o número do sm é 18px/800, e a regra vence o 32/800 do rótulo no topo", () => {
29
+ const regra = bloco(".umds-stat-card.umds-stat-card--sm .umds-stat-card__valor");
30
+ expect(regra).toMatch(/font-size:\s*1\.125rem/);
31
+ expect(regra).toMatch(/font-weight:\s*var\(--umds-primitive-font-weight-extrabold\)/);
32
+ const { container } = render(<StatCard value={44} label="Não lidos" size="sm" labelPosition="top" />);
33
+ expect(container.firstElementChild).toHaveClass("umds-stat-card--sm", "umds-stat-card--rotulo-topo");
34
+ });
35
+
36
+ it("o número continua antes do rótulo no HTML", () => {
37
+ const { container } = render(<StatCard value={44} label="Não lidos" size="sm" />);
38
+ const valor = container.querySelector(".umds-stat-card__valor") as HTMLElement;
39
+ const rotulo = container.querySelector(".umds-stat-card__rotulo") as HTMLElement;
40
+ expect(valor.compareDocumentPosition(rotulo) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
41
+ });
42
+ });
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import Tabs from "../components/Tabs/Tabs";
3
+ import Tab from "../components/Tabs/Tab";
4
+ import { render } from "@testing-library/react";
5
+ import { lerArquivo } from "./lerArquivo";
6
+
7
+ const cssTabs = await lerArquivo("../core/src/components/Tabs/Tabs.css");
8
+
9
+ const CSS: Record<string, string> = { Tabs: cssTabs };
10
+ const css = (componente: string) => CSS[componente].replace(/\r\n/g, "\n");
11
+ const bloco = (texto: string, seletor: string) => {
12
+ const inicio = texto.indexOf(seletor + " {");
13
+ expect(inicio, seletor).toBeGreaterThanOrEqual(0);
14
+ return texto.slice(inicio, texto.indexOf("}", inicio));
15
+ };
16
+
17
+ describe("Tabs · density", () => {
18
+ it("compact escreve a classe na raiz; sem a prop e com default, não", () => {
19
+ const { container } = render(
20
+ <>
21
+ <Tabs value="0" density="compact"><Tab label="A" index="0" /></Tabs>
22
+ <Tabs value="0"><Tab label="B" index="0" /></Tabs>
23
+ <Tabs value="0" density="default"><Tab label="C" index="0" /></Tabs>
24
+ </>,
25
+ );
26
+ expect(container.children[0]).toHaveClass("umds-tabs--compact");
27
+ expect(container.children[1]).not.toHaveClass("umds-tabs--compact");
28
+ expect(container.children[2]).not.toHaveClass("umds-tabs--compact");
29
+ });
30
+
31
+ it("CSS: a compacta tem altura mínima de 40px (degrau sm) e padding vertical de 8px; o padrão segue em 48px", () => {
32
+ const t = css("Tabs");
33
+ const compacta = bloco(t, ".umds-tabs--compact .umds-tabs__tab");
34
+ expect(compacta).toMatch(/min-height:\s*var\(--umds-primitive-spacing-sm\)/);
35
+ expect(compacta).toMatch(/padding-block:\s*var\(--umds-primitive-spacing-nano\)/);
36
+ expect(bloco(t, ".umds-tabs__tab")).toMatch(/min-height:\s*48px/);
37
+ });
38
+
39
+ it("a pílula do contador é cinza na aba inativa e azul só na ativa (nas duas densidades)", () => {
40
+ const t = css("Tabs");
41
+ expect(bloco(t, ".umds-tabs__count")).toMatch(/background-color:\s*var\(--umds-semantic-color-neutral-light-medium\)/);
42
+ expect(bloco(t, ".umds-tabs__tab--selected .umds-tabs__count")).toMatch(/background-color:\s*var\(--umds-semantic-color-primary-pure\)/);
43
+ expect(t).not.toMatch(/--compact[^{]*\.umds-tabs__count/);
44
+ });
45
+ });
@@ -0,0 +1,60 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { render } from "@testing-library/react";
3
+ import Tag from "../components/Tag/Tag";
4
+ import { lerArquivo } from "./lerArquivo";
5
+
6
+ const cssTag = await lerArquivo("../core/src/components/Tag/Tag.css");
7
+ const tema1doc = await lerArquivo("../tokens/dist/css/theme-1doc.css");
8
+ const temaEscuro = await lerArquivo("../tokens/dist/css/theme-1doc-dark.css");
9
+ const temaAltoContraste = await lerArquivo("../tokens/dist/css/theme-1doc-high-contrast.css");
10
+
11
+ const css = cssTag.replace(/\r\n/g, "\n");
12
+ const bloco = (seletor: string) => {
13
+ const inicio = css.indexOf(seletor + " {");
14
+ return css.slice(inicio, css.indexOf("}", inicio));
15
+ };
16
+
17
+ const TEMAS: Record<string, string> = { "theme-1doc.css": tema1doc, "theme-1doc-dark.css": temaEscuro, "theme-1doc-high-contrast.css": temaAltoContraste };
18
+ const tema = (arquivo: string) => {
19
+ const texto = TEMAS[arquivo];
20
+ const cor = (nome: string) => (texto.match(new RegExp("--umds-theme-" + nome + ":\\s*(#[0-9A-Fa-f]{6})")) as RegExpMatchArray)[1];
21
+ return cor;
22
+ };
23
+ const luz = (hex: string) => {
24
+ const c = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255).map((v) => (v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4));
25
+ return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
26
+ };
27
+ const contraste = (a: string, b: string) => {
28
+ const [x, y] = [luz(a), luz(b)].sort((m, n) => n - m);
29
+ return (x + 0.05) / (y + 0.05);
30
+ };
31
+
32
+ describe("Tag positive outlined sobre fundo tingido", () => {
33
+ it("escreve as classes que a regra de CSS escolhe", () => {
34
+ const { container } = render(<Tag label="Entregue" variant="positive" appearance="outlined" />);
35
+ const tag = container.firstElementChild as HTMLElement;
36
+ expect(tag).toHaveClass("umds-tag--outlined", "umds-tag--positive");
37
+ });
38
+
39
+ it("o texto é positive-dark e o contorno segue positive-pure", () => {
40
+ const regra = bloco(".umds-tag--outlined.umds-tag--positive");
41
+ expect(regra).toMatch(/color:\s*var\(--umds-semantic-color-feedback-positive-dark\)/);
42
+ expect(regra).toMatch(/border-color:\s*var\(--umds-semantic-color-feedback-positive-pure\)/);
43
+ });
44
+
45
+ it("no tema 1doc, sobre o azul-claro do card o texto passa de 4,5:1 (o pure dava 3,6:1)", () => {
46
+ const cor = tema("theme-1doc.css");
47
+ const fundo = cor("primary-light");
48
+ expect(contraste(cor("feedback-positive-pure"), fundo)).toBeLessThan(4.5);
49
+ expect(contraste(cor("feedback-positive-dark"), fundo)).toBeGreaterThanOrEqual(4.5);
50
+ });
51
+
52
+ it("nos temas escuro e de alto contraste o texto também passa de 4,5:1 sobre o fundo tingido e sobre a página", () => {
53
+ for (const arquivo of ["theme-1doc-dark.css", "theme-1doc-high-contrast.css"]) {
54
+ const cor = tema(arquivo);
55
+ for (const fundo of [cor("primary-light"), cor("background-white")]) {
56
+ expect(contraste(cor("feedback-positive-dark"), fundo)).toBeGreaterThanOrEqual(4.5);
57
+ }
58
+ }
59
+ });
60
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Lê um arquivo do pacote (relativo à pasta `packages/react`) para os testes que conferem uma regra de CSS na
3
+ * fonte: o jsdom não tem cascata de arquivo, e o `css: false` do vitest esvazia o `?raw` do CSS.
4
+ * O `node:fs` entra por import dinâmico com nome calculado porque o `tsc` do pacote não traz os tipos do Node.
5
+ */
6
+ const modulo = (nome: string): Promise<any> => import(/* @vite-ignore */ nome);
7
+
8
+ export async function lerArquivo(relativo: string): Promise<string> {
9
+ const fs = await modulo("node:fs");
10
+ const path = await modulo("node:path");
11
+ const raiz: string = (globalThis as any).process.cwd();
12
+ return fs.readFileSync(path.resolve(raiz, relativo), "utf8");
13
+ }
@@ -51,7 +51,7 @@ export const normalizarEndereco = (bruto: string): string => {
51
51
  /** Cor, tamanho e família de fonte só valem como TOKEN do DS: `var(--umds-...)`. Cor em hex, rgb ou nome sai. */
52
52
  const COR_TOKEN = /^var\(--umds-(?:primitive|semantic)-[a-z0-9-]+\)$/;
53
53
  const TAMANHO_TOKEN = /^var\(--umds-primitive-font-size-[a-z0-9]+\)$/;
54
- const FAMILIA_TOKEN = /^var\(--umds-(?:primitive|semantic)-font-family-(?:base|mono)\)$/;
54
+ const FAMILIA_TOKEN = /^var\(--umds-(?:primitive|semantic)-font-family-base\)$/;
55
55
 
56
56
  /**
57
57
  * Declaração de estilo de texto que o editor aceita: `color`, `font-size` e `font-family`,