@12-apps/ui 1.5.0 → 1.5.2
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/package.json +17 -9
- package/src/button/index.ts +1 -0
- package/src/social-login-button/index.ts +1 -0
- package/src/{social-login-button.tsx → social-login-button/social-login-button.tsx} +5 -5
- package/src/user-avatar/index.ts +1 -0
- package/src/{user-avatar.tsx → user-avatar/user-avatar.tsx} +4 -4
- package/eslint.config.js +0 -89
- package/src/__tests__/data-grid.test.tsx +0 -125
- package/src/__tests__/package-wiring.test.ts +0 -41
- package/src/__tests__/setup.ts +0 -1
- package/src/__tests__/social-login-button.test.tsx +0 -186
- package/src/__tests__/user-avatar.test.tsx +0 -195
- package/src/__tests__/viewport.test.ts +0 -50
- package/src/components/command/__tests__/command.test.tsx +0 -112
- package/src/components/data-display/Alert/Alert.stories.tsx +0 -559
- package/src/components/data-display/Alert/Alert.test.stories.tsx +0 -707
- package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +0 -362
- package/src/components/data-display/AlertDialog/AlertDialog.test.stories.tsx +0 -763
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.stories.tsx +0 -230
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +0 -270
- package/src/components/data-display/Avatar/Avatar.stories.tsx +0 -721
- package/src/components/data-display/Avatar/Avatar.test.stories.tsx +0 -822
- package/src/components/data-display/Badge/Badge.stories.tsx +0 -1134
- package/src/components/data-display/Badge/Badge.test.stories.tsx +0 -1194
- package/src/components/data-display/Banner/Banner.stories.tsx +0 -528
- package/src/components/data-display/Banner/Banner.test.stories.tsx +0 -511
- package/src/components/data-display/Carousel/Carousel.stories.tsx +0 -607
- package/src/components/data-display/Carousel/Carousel.test.stories.tsx +0 -701
- package/src/components/data-display/Chart/Chart.stories.tsx +0 -568
- package/src/components/data-display/Chart/Chart.test.stories.tsx +0 -174
- package/src/components/data-display/Chart/SpecChart.stories.tsx +0 -150
- package/src/components/data-display/Chart/SpecChart.test.stories.tsx +0 -56
- package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +0 -60
- package/src/components/data-display/Chart/__tests__/chart-format.test.ts +0 -43
- package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +0 -119
- package/src/components/data-display/Chip/Chip.stories.tsx +0 -252
- package/src/components/data-display/Chip/Chip.test.stories.tsx +0 -266
- package/src/components/data-display/DataGrid/DataGrid.stories.tsx +0 -515
- package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +0 -653
- package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +0 -271
- package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +0 -134
- package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +0 -83
- package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +0 -115
- package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +0 -53
- package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +0 -114
- package/src/components/data-display/DataViews/base-card.stories.tsx +0 -136
- package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +0 -254
- package/src/components/data-display/EmptyState/EmptyState.stories.tsx +0 -282
- package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +0 -441
- package/src/components/data-display/ErrorState/ErrorState.stories.tsx +0 -110
- package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +0 -133
- package/src/components/data-display/HoverCard/HoverCard.stories.tsx +0 -650
- package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +0 -929
- package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.stories.tsx +0 -411
- package/src/components/data-display/LazyImage/LazyImage.stories.tsx +0 -387
- package/src/components/data-display/Lightbox/Lightbox.stories.tsx +0 -315
- package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +0 -598
- package/src/components/data-display/LoadingState/LoadingState.stories.tsx +0 -124
- package/src/components/data-display/LoadingState/LoadingState.test.stories.tsx +0 -99
- package/src/components/data-display/MapPreview/MapPreview.stories.tsx +0 -458
- package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +0 -394
- package/src/components/data-display/Popover/Popover.stories.tsx +0 -295
- package/src/components/data-display/Popover/Popover.test.stories.tsx +0 -1162
- package/src/components/data-display/Progress/Progress.stories.tsx +0 -481
- package/src/components/data-display/Progress/Progress.test.stories.tsx +0 -2070
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +0 -160
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.test.stories.tsx +0 -190
- package/src/components/data-display/Sheet/Sheet.stories.tsx +0 -883
- package/src/components/data-display/Sheet/Sheet.test.stories.tsx +0 -1105
- package/src/components/data-display/StatCard/StatCard.stories.tsx +0 -167
- package/src/components/data-display/StatCard/StatCard.test.stories.tsx +0 -240
- package/src/components/data-display/Stepper/Stepper.stories.tsx +0 -540
- package/src/components/data-display/Stepper/Stepper.test.stories.tsx +0 -600
- package/src/components/data-display/Table/Table.stories.tsx +0 -803
- package/src/components/data-display/Table/Table.test.stories.tsx +0 -369
- package/src/components/data-display/Timeline/Timeline.stories.tsx +0 -442
- package/src/components/data-display/Timeline/Timeline.test.stories.tsx +0 -930
- package/src/components/data-display/TimingDiagram/TimingDiagram.stories.tsx +0 -475
- package/src/components/data-display/TimingDiagram/TimingDiagram.test.stories.tsx +0 -734
- package/src/components/data-display/Tooltip/Tooltip.stories.tsx +0 -468
- package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +0 -1154
- package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +0 -146
- package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +0 -114
- package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +0 -288
- package/src/components/feedback/Dialog/Dialog.stories.tsx +0 -833
- package/src/components/feedback/Dialog/Dialog.test.stories.tsx +0 -964
- package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +0 -66
- package/src/components/feedback/Modal/Modal.stories.tsx +0 -765
- package/src/components/feedback/Modal/Modal.test.stories.tsx +0 -251
- package/src/components/feedback/Sonner/Sonner.stories.tsx +0 -698
- package/src/components/feedback/Sonner/Sonner.test.stories.tsx +0 -896
- package/src/components/feedback/StackedModal/StackedModal.stories.tsx +0 -1783
- package/src/components/feedback/StackedModal/StackedModal.test.stories.tsx +0 -973
- package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +0 -104
- package/src/components/feedback/Toast/Toast.stories.tsx +0 -548
- package/src/components/feedback/Toast/Toast.test.stories.tsx +0 -834
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.stories.tsx +0 -691
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +0 -634
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +0 -665
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +0 -727
- package/src/components/form/Autocomplete/Autocomplete.stories.tsx +0 -715
- package/src/components/form/Autocomplete/Autocomplete.test.stories.tsx +0 -825
- package/src/components/form/Button/Button.stories.tsx +0 -365
- package/src/components/form/Button/Button.test.stories.tsx +0 -607
- package/src/components/form/Calendar/Calendar.stories.tsx +0 -349
- package/src/components/form/Calendar/Calendar.test.stories.tsx +0 -464
- package/src/components/form/CepField/CepField.stories.tsx +0 -138
- package/src/components/form/CepField/CepField.test.stories.tsx +0 -281
- package/src/components/form/Checkbox/Checkbox.stories.tsx +0 -265
- package/src/components/form/Checkbox/Checkbox.test.stories.tsx +0 -181
- package/src/components/form/CodeEditor/CodeEditor.stories.tsx +0 -871
- package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +0 -574
- package/src/components/form/Command/Command.stories.tsx +0 -580
- package/src/components/form/Command/Command.test.stories.tsx +0 -215
- package/src/components/form/CreatableSelect/CreatableSelect.stories.tsx +0 -112
- package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +0 -134
- package/src/components/form/Form/Form.stories.tsx +0 -687
- package/src/components/form/Form/Form.test.stories.tsx +0 -601
- package/src/components/form/Input/Input.stories.tsx +0 -260
- package/src/components/form/Input/Input.test.stories.tsx +0 -1191
- package/src/components/form/InputOTP/InputOTP.stories.tsx +0 -774
- package/src/components/form/InputOTP/InputOTP.test.stories.tsx +0 -588
- package/src/components/form/Label/Label.stories.tsx +0 -473
- package/src/components/form/Label/Label.test.stories.tsx +0 -915
- package/src/components/form/Menubar/Menubar.stories.tsx +0 -472
- package/src/components/form/Menubar/Menubar.test.stories.tsx +0 -584
- package/src/components/form/PasswordStrength/PasswordStrength.stories.tsx +0 -562
- package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +0 -327
- package/src/components/form/PhoneInput/PhoneInput.stories.tsx +0 -495
- package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +0 -666
- package/src/components/form/RadioGroup/RadioGroup.stories.tsx +0 -518
- package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +0 -685
- package/src/components/form/RichTextEditor/RichTextEditor.stories.tsx +0 -261
- package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +0 -491
- package/src/components/form/SearchPalette/SearchPalette.stories.tsx +0 -135
- package/src/components/form/Select/Select.stories.tsx +0 -364
- package/src/components/form/Select/Select.test.stories.tsx +0 -985
- package/src/components/form/Slider/Slider.stories.tsx +0 -418
- package/src/components/form/Slider/Slider.test.stories.tsx +0 -1042
- package/src/components/form/Switch/Switch.stories.tsx +0 -697
- package/src/components/form/Switch/Switch.test.stories.tsx +0 -597
- package/src/components/form/Textarea/Textarea.stories.tsx +0 -473
- package/src/components/form/Textarea/Textarea.test.stories.tsx +0 -702
- package/src/components/form/Toggle/Toggle.stories.tsx +0 -638
- package/src/components/form/Toggle/Toggle.test.stories.tsx +0 -1159
- package/src/components/form/ToggleGroup/ToggleGroup.stories.tsx +0 -617
- package/src/components/form/ToggleGroup/ToggleGroup.test.stories.tsx +0 -915
- package/src/components/form/UploadButton/UploadButton.stories.tsx +0 -367
- package/src/components/form/UploadButton/UploadButton.test.stories.tsx +0 -573
- package/src/components/form/total-form/total-form.test.tsx +0 -214
- package/src/components/layout/Accordion/Accordion.stories.tsx +0 -694
- package/src/components/layout/Accordion/Accordion.test.stories.tsx +0 -1344
- package/src/components/layout/Card/Card.stories.tsx +0 -591
- package/src/components/layout/Card/Card.test.stories.tsx +0 -1134
- package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +0 -92
- package/src/components/layout/Collapsible/Collapsible.stories.tsx +0 -180
- package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +0 -308
- package/src/components/layout/Container/Container.stories.tsx +0 -557
- package/src/components/layout/Container/Container.test.stories.tsx +0 -745
- package/src/components/layout/ContentToolbar/ContentToolbar.stories.tsx +0 -129
- package/src/components/layout/ContentToolbar/ContentToolbar.test.stories.tsx +0 -251
- package/src/components/layout/ContentToolbar/__tests__/ColumnsMenu.test.tsx +0 -39
- package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +0 -131
- package/src/components/layout/ContentToolbar/__tests__/SavedViewsMenu.test.tsx +0 -99
- package/src/components/layout/Dashboard/Dashboard.stories.tsx +0 -104
- package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +0 -190
- package/src/components/layout/Drawer/Drawer.stories.tsx +0 -658
- package/src/components/layout/Drawer/Drawer.test.stories.tsx +0 -1343
- package/src/components/layout/Resizable/Resizable.stories.tsx +0 -1039
- package/src/components/layout/Resizable/Resizable.test.stories.tsx +0 -754
- package/src/components/layout/ScrollArea/ScrollArea.stories.tsx +0 -651
- package/src/components/layout/ScrollArea/ScrollArea.test.stories.tsx +0 -619
- package/src/components/layout/Separator/Separator.stories.tsx +0 -563
- package/src/components/layout/Separator/Separator.test.stories.tsx +0 -518
- package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +0 -192
- package/src/components/layout/SettingsLayout/SettingsLayout.test.stories.tsx +0 -175
- package/src/components/layout/Sidebar/Sidebar.stories.tsx +0 -609
- package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +0 -1324
- package/src/components/layout/Skeleton/Skeleton.stories.tsx +0 -689
- package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +0 -521
- package/src/components/layout/Spacer/Spacer.stories.tsx +0 -502
- package/src/components/layout/Spacer/Spacer.test.stories.tsx +0 -474
- package/src/components/layout/TableFilter/TableFilter.stories.tsx +0 -82
- package/src/components/layout/TableFilter/TableFilter.test.stories.tsx +0 -87
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +0 -638
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +0 -1008
- package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +0 -386
- package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +0 -435
- package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +0 -614
- package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +0 -845
- package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +0 -578
- package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +0 -660
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +0 -516
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +0 -935
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +0 -501
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +0 -453
- package/src/components/navigation/Pagination/Pagination.stories.tsx +0 -602
- package/src/components/navigation/Pagination/Pagination.test.stories.tsx +0 -520
- package/src/components/navigation/Tabs/Tabs.stories.tsx +0 -1004
- package/src/components/navigation/Tabs/Tabs.test.stories.tsx +0 -1225
- package/src/components/typography/Blockquote/Blockquote.stories.tsx +0 -359
- package/src/components/typography/Blockquote/Blockquote.test.stories.tsx +0 -432
- package/src/components/typography/Code/Code.stories.tsx +0 -686
- package/src/components/typography/Code/Code.test.stories.tsx +0 -703
- package/src/components/typography/Heading/Heading.stories.tsx +0 -451
- package/src/components/typography/Heading/Heading.test.stories.tsx +0 -830
- package/src/components/typography/Paragraph/Paragraph.stories.tsx +0 -280
- package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +0 -231
- package/src/components/typography/Text/Text.stories.tsx +0 -359
- package/src/components/typography/Text/Text.test.stories.tsx +0 -759
- package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +0 -945
- package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +0 -603
- package/src/components/utility/AspectRatio/AspectRatio.stories.tsx +0 -414
- package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +0 -859
- package/src/components/utility/InfiniteScroll/InfiniteScroll.stories.tsx +0 -620
- package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +0 -361
- package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +0 -118
- package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +0 -83
- package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +0 -65
- package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +0 -271
- package/src/components/utility/Portal/Portal.stories.tsx +0 -340
- package/src/components/utility/Portal/Portal.test.stories.tsx +0 -2086
- package/src/components/utility/Transition/Transition.stories.tsx +0 -493
- package/src/components/utility/Transition/Transition.test.stories.tsx +0 -854
- package/src/components/utility/VirtualList/VirtualList.stories.tsx +0 -1073
- package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +0 -1085
- package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +0 -431
- package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +0 -364
- /package/src/{button.tsx → button/button.tsx} +0 -0
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
3
|
-
import { ThemeProvider, createTheme } from "../../../../mui/styles";
|
|
4
|
-
|
|
5
|
-
import { DataViewsGrid } from "../DataViewsGrid";
|
|
6
|
-
import type { DataViewColumn, FilterFieldConfig } from "../data-views-types";
|
|
7
|
-
|
|
8
|
-
interface Row extends Record<string, unknown> {
|
|
9
|
-
id: string;
|
|
10
|
-
name: string;
|
|
11
|
-
status: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const rows: Row[] = [
|
|
15
|
-
{ id: "1", name: "Ana", status: "ACTIVE" },
|
|
16
|
-
{ id: "2", name: "Bruno", status: "INACTIVE" },
|
|
17
|
-
{ id: "3", name: "Carla", status: "ACTIVE" },
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const columns: DataViewColumn<Row>[] = [
|
|
21
|
-
{ id: "name", header: "Nome", accessor: "name", searchable: true },
|
|
22
|
-
{ id: "status", header: "Status", accessor: "status", searchable: true },
|
|
23
|
-
{ id: "actions", header: "", type: "actions", hideable: false, cell: () => null },
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
const fields: FilterFieldConfig<Row>[] = [
|
|
27
|
-
{
|
|
28
|
-
id: "status",
|
|
29
|
-
label: "Status",
|
|
30
|
-
options: [
|
|
31
|
-
{ value: "ACTIVE", label: "Ativo" },
|
|
32
|
-
{ value: "INACTIVE", label: "Inativo" },
|
|
33
|
-
],
|
|
34
|
-
},
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
function renderGrid(props: Partial<React.ComponentProps<typeof DataViewsGrid<Row>>> = {}) {
|
|
38
|
-
return render(
|
|
39
|
-
<ThemeProvider theme={createTheme()}>
|
|
40
|
-
<DataViewsGrid<Row>
|
|
41
|
-
rows={rows}
|
|
42
|
-
columns={columns}
|
|
43
|
-
fields={fields}
|
|
44
|
-
getRowId={(row) => row.id}
|
|
45
|
-
dataTestId="people"
|
|
46
|
-
testIdPrefix="people"
|
|
47
|
-
{...props}
|
|
48
|
-
/>
|
|
49
|
-
</ThemeProvider>,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const counter = (): string => screen.getByTestId("people-counter").textContent ?? "";
|
|
54
|
-
|
|
55
|
-
/** The panel keeps its fields mounted (visibility-hidden when closed), so they
|
|
56
|
-
* stay interactable in tests. Query the input directly — `getByRole` would skip
|
|
57
|
-
* the hidden panel. */
|
|
58
|
-
function statusCheckbox(value: string): HTMLInputElement {
|
|
59
|
-
const input = screen.getByTestId(`people-filter-status-${value}`).querySelector("input");
|
|
60
|
-
if (!input) throw new Error(`checkbox input for ${value} not found`);
|
|
61
|
-
return input;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
describe("DataViewsGrid", () => {
|
|
65
|
-
beforeEach(() => window.localStorage.clear());
|
|
66
|
-
|
|
67
|
-
it("renders every row and a matched/total counter", () => {
|
|
68
|
-
renderGrid();
|
|
69
|
-
expect(screen.getByText("Ana")).toBeInTheDocument();
|
|
70
|
-
expect(screen.getByText("Bruno")).toBeInTheDocument();
|
|
71
|
-
expect(counter()).toContain("3 de 3");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("narrows via the keyword search (commit on blur) and updates the counter", async () => {
|
|
75
|
-
renderGrid();
|
|
76
|
-
const search = screen.getByTestId("people-search-all");
|
|
77
|
-
fireEvent.change(search, { target: { value: "car" } });
|
|
78
|
-
fireEvent.blur(search);
|
|
79
|
-
await waitFor(() => expect(screen.queryByText("Ana")).not.toBeInTheDocument());
|
|
80
|
-
expect(screen.getByText("Carla")).toBeInTheDocument();
|
|
81
|
-
expect(counter()).toContain("1 de 3");
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("applies a checkbox filter field and updates the counter", async () => {
|
|
85
|
-
renderGrid();
|
|
86
|
-
fireEvent.click(statusCheckbox("INACTIVE"));
|
|
87
|
-
await waitFor(() => expect(screen.queryByText("Ana")).not.toBeInTheDocument());
|
|
88
|
-
expect(screen.getByText("Bruno")).toBeInTheDocument();
|
|
89
|
-
expect(counter()).toContain("1 de 3");
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("reports state changes and re-applies a supplied view state", async () => {
|
|
93
|
-
const onStateChange = vi.fn();
|
|
94
|
-
const { rerender } = renderGrid({ onStateChange });
|
|
95
|
-
expect(onStateChange).toHaveBeenCalled();
|
|
96
|
-
|
|
97
|
-
rerender(
|
|
98
|
-
<ThemeProvider theme={createTheme()}>
|
|
99
|
-
<DataViewsGrid<Row>
|
|
100
|
-
rows={rows}
|
|
101
|
-
columns={columns}
|
|
102
|
-
fields={fields}
|
|
103
|
-
getRowId={(row) => row.id}
|
|
104
|
-
dataTestId="people"
|
|
105
|
-
testIdPrefix="people"
|
|
106
|
-
onStateChange={onStateChange}
|
|
107
|
-
appliedState={{ search: "", pills: { status: ["ACTIVE"] }, sortBy: [], visibleColumns: ["name", "status", "actions"] }}
|
|
108
|
-
/>
|
|
109
|
-
</ThemeProvider>,
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
await waitFor(() => expect(screen.queryByText("Bruno")).not.toBeInTheDocument());
|
|
113
|
-
expect(counter()).toContain("2 de 3");
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("syncState re-applies the URL controls on change while preserving hidden columns", async () => {
|
|
117
|
-
// A stable appliedState hides the `status` column; only `syncState` changes
|
|
118
|
-
// between renders (as a URL back/forward would), so the appliedState effect
|
|
119
|
-
// never re-fires and we isolate the syncState merge.
|
|
120
|
-
const applied = { search: "", pills: {}, sortBy: [], visibleColumns: ["name", "actions"] };
|
|
121
|
-
const { rerender } = renderGrid({
|
|
122
|
-
appliedState: applied,
|
|
123
|
-
syncState: { search: "", pills: {}, ranges: {}, sortBy: [] },
|
|
124
|
-
});
|
|
125
|
-
// `name` header present (evidence the grid rendered) but `status` is hidden.
|
|
126
|
-
expect(screen.getByRole("columnheader", { name: "Nome" })).toBeInTheDocument();
|
|
127
|
-
await waitFor(() =>
|
|
128
|
-
expect(screen.queryByRole("columnheader", { name: "Status" })).not.toBeInTheDocument(),
|
|
129
|
-
);
|
|
130
|
-
expect(counter()).toContain("3 de 3");
|
|
131
|
-
|
|
132
|
-
// A new syncState reference applies a status filter (e.g. browser back/forward).
|
|
133
|
-
rerender(
|
|
134
|
-
<ThemeProvider theme={createTheme()}>
|
|
135
|
-
<DataViewsGrid<Row>
|
|
136
|
-
rows={rows}
|
|
137
|
-
columns={columns}
|
|
138
|
-
fields={fields}
|
|
139
|
-
getRowId={(row) => row.id}
|
|
140
|
-
dataTestId="people"
|
|
141
|
-
testIdPrefix="people"
|
|
142
|
-
appliedState={applied}
|
|
143
|
-
syncState={{ search: "", pills: { status: ["ACTIVE"] }, ranges: {}, sortBy: [] }}
|
|
144
|
-
/>
|
|
145
|
-
</ThemeProvider>,
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
// The filter re-applied (Bruno is INACTIVE)...
|
|
149
|
-
await waitFor(() => expect(screen.queryByText("Bruno")).not.toBeInTheDocument());
|
|
150
|
-
expect(counter()).toContain("2 de 3");
|
|
151
|
-
// ...and the user's hidden `status` column was NOT resurrected by the re-sync.
|
|
152
|
-
await waitFor(() =>
|
|
153
|
-
expect(screen.queryByRole("columnheader", { name: "Status" })).not.toBeInTheDocument(),
|
|
154
|
-
);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("the funnel toggle opens/closes the filter panel", async () => {
|
|
158
|
-
renderGrid();
|
|
159
|
-
const funnel = screen.getByTestId("people-filters-toggle");
|
|
160
|
-
expect(funnel).toHaveAttribute("aria-expanded", "false");
|
|
161
|
-
fireEvent.click(funnel);
|
|
162
|
-
await waitFor(() => expect(funnel).toHaveAttribute("aria-expanded", "true"));
|
|
163
|
-
// Counter stays visible in the toolbar regardless of panel state.
|
|
164
|
-
expect(screen.getByTestId("people-counter")).toBeInTheDocument();
|
|
165
|
-
fireEvent.click(funnel);
|
|
166
|
-
await waitFor(() => expect(funnel).toHaveAttribute("aria-expanded", "false"));
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it("selects all rows, shows the count, and renders the bulk action", async () => {
|
|
170
|
-
const onBulk = vi.fn();
|
|
171
|
-
renderGrid({
|
|
172
|
-
bulkActions: (selected, clear) => (
|
|
173
|
-
<button
|
|
174
|
-
type="button"
|
|
175
|
-
data-testid="people-bulk"
|
|
176
|
-
onClick={() => {
|
|
177
|
-
onBulk(selected.length);
|
|
178
|
-
clear();
|
|
179
|
-
}}
|
|
180
|
-
>
|
|
181
|
-
Bulk {selected.length}
|
|
182
|
-
</button>
|
|
183
|
-
),
|
|
184
|
-
});
|
|
185
|
-
// No selection: no count indicator, no bulk action.
|
|
186
|
-
await waitFor(() => expect(screen.queryByTestId("selected-count-indicator")).not.toBeInTheDocument());
|
|
187
|
-
|
|
188
|
-
fireEvent.click(screen.getByTestId("people-select-all"));
|
|
189
|
-
await waitFor(() =>
|
|
190
|
-
expect(screen.getByTestId("selected-count-indicator")).toHaveTextContent("3 items selected"),
|
|
191
|
-
);
|
|
192
|
-
const bulk = screen.getByTestId("people-bulk");
|
|
193
|
-
fireEvent.click(bulk);
|
|
194
|
-
expect(onBulk).toHaveBeenCalledWith(3);
|
|
195
|
-
// Bulk action clears the selection.
|
|
196
|
-
await waitFor(() => expect(screen.queryByTestId("selected-count-indicator")).not.toBeInTheDocument());
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("drives the row kebab AND the bulk menu from one rowActions list", async () => {
|
|
200
|
-
const onArchive = vi.fn();
|
|
201
|
-
const onEdit = vi.fn();
|
|
202
|
-
renderGrid({
|
|
203
|
-
// No actions column here so the grid appends its auto kebab from rowActions.
|
|
204
|
-
columns: [
|
|
205
|
-
{ id: "name", header: "Nome", accessor: "name", searchable: true },
|
|
206
|
-
{ id: "status", header: "Status", accessor: "status" },
|
|
207
|
-
],
|
|
208
|
-
rowActions: [
|
|
209
|
-
// `bulk: false` → appears in the kebab but NOT the bulk menu.
|
|
210
|
-
{ id: "edit", label: "Editar", bulk: false, onSelect: (r) => onEdit(r.length) },
|
|
211
|
-
{ id: "archive", label: "Arquivar", color: "error", onSelect: (r) => onArchive(r.length) },
|
|
212
|
-
],
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// Per-row kebab: both actions present; Editar acts on the single row.
|
|
216
|
-
fireEvent.click(screen.getByTestId("people-actions-1"));
|
|
217
|
-
expect(screen.getByText("Editar")).toBeInTheDocument();
|
|
218
|
-
expect(screen.getByText("Arquivar")).toBeInTheDocument();
|
|
219
|
-
fireEvent.click(screen.getByText("Editar"));
|
|
220
|
-
expect(onEdit).toHaveBeenCalledWith(1);
|
|
221
|
-
|
|
222
|
-
// Select all → the bulk "Ações" menu shows only the bulk-eligible action.
|
|
223
|
-
fireEvent.click(screen.getByTestId("people-select-all"));
|
|
224
|
-
await waitFor(() => expect(screen.getByTestId("people-bulk-actions")).toBeInTheDocument());
|
|
225
|
-
fireEvent.click(screen.getByTestId("people-bulk-actions"));
|
|
226
|
-
await waitFor(() => expect(screen.getByText("Arquivar")).toBeInTheDocument());
|
|
227
|
-
// "Editar" is single-item only, so it must NOT appear in the bulk menu.
|
|
228
|
-
await waitFor(() => expect(screen.queryByText("Editar")).not.toBeInTheDocument());
|
|
229
|
-
fireEvent.click(screen.getByText("Arquivar"));
|
|
230
|
-
expect(onArchive).toHaveBeenCalledWith(3);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it("re-emits visible rows on a same-id content change, but not on a content-equal rerender", async () => {
|
|
234
|
-
// Local seed (not the shared module `rows`) so this test owns its data.
|
|
235
|
-
const seed: Row[] = [
|
|
236
|
-
{ id: "1", name: "Ana", status: "ACTIVE" },
|
|
237
|
-
{ id: "2", name: "Bruno", status: "INACTIVE" },
|
|
238
|
-
];
|
|
239
|
-
const onVisibleRowsChange = vi.fn<(rows: Row[]) => void>();
|
|
240
|
-
const { rerender } = renderGrid({ onVisibleRowsChange, rows: seed });
|
|
241
|
-
await waitFor(() => expect(onVisibleRowsChange).toHaveBeenCalled());
|
|
242
|
-
const afterInitial = onVisibleRowsChange.mock.calls.length;
|
|
243
|
-
|
|
244
|
-
const rerenderWith = (next: Row[]): void =>
|
|
245
|
-
rerender(
|
|
246
|
-
<ThemeProvider theme={createTheme()}>
|
|
247
|
-
<DataViewsGrid<Row>
|
|
248
|
-
rows={next}
|
|
249
|
-
columns={columns}
|
|
250
|
-
fields={fields}
|
|
251
|
-
getRowId={(row) => row.id}
|
|
252
|
-
dataTestId="people"
|
|
253
|
-
testIdPrefix="people"
|
|
254
|
-
onVisibleRowsChange={onVisibleRowsChange}
|
|
255
|
-
/>
|
|
256
|
-
</ThemeProvider>,
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
// Fresh array + row refs, identical values → no re-emit (guards the loop).
|
|
260
|
-
rerenderWith(seed.map((row) => ({ ...row })));
|
|
261
|
-
expect(onVisibleRowsChange.mock.calls.length).toBe(afterInitial);
|
|
262
|
-
|
|
263
|
-
// Same ids, changed values → must re-emit so the export isn't stale.
|
|
264
|
-
rerenderWith(seed.map((row) => (row.id === "1" ? { ...row, name: "Ana Editada" } : { ...row })));
|
|
265
|
-
await waitFor(() =>
|
|
266
|
-
expect(onVisibleRowsChange.mock.calls.length).toBeGreaterThan(afterInitial),
|
|
267
|
-
);
|
|
268
|
-
const emitted = onVisibleRowsChange.mock.calls.at(-1)?.[0] ?? [];
|
|
269
|
-
expect(emitted.find((row) => row.id === "1")?.name).toBe("Ana Editada");
|
|
270
|
-
});
|
|
271
|
-
});
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { render, screen, waitFor } from "@testing-library/react";
|
|
3
|
-
import { ThemeProvider, createTheme } from "../../../../mui/styles";
|
|
4
|
-
|
|
5
|
-
import { DataViewsTableBase } from "../DataViewsTableBase";
|
|
6
|
-
import type {
|
|
7
|
-
DataViewColumn,
|
|
8
|
-
DataViewPersistence,
|
|
9
|
-
DataViewRouter,
|
|
10
|
-
DataViewServer,
|
|
11
|
-
DataViewState,
|
|
12
|
-
FilterFieldConfig,
|
|
13
|
-
} from "../data-views-types";
|
|
14
|
-
|
|
15
|
-
interface Row extends Record<string, unknown> {
|
|
16
|
-
id: string;
|
|
17
|
-
name: string;
|
|
18
|
-
status: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const rows: Row[] = [
|
|
22
|
-
{ id: "1", name: "Ana", status: "ACTIVE" },
|
|
23
|
-
{ id: "2", name: "Bruno", status: "INACTIVE" },
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
const columns: DataViewColumn<Row>[] = [
|
|
27
|
-
{ id: "name", header: "Nome", accessor: "name", searchable: true },
|
|
28
|
-
{ id: "status", header: "Status", accessor: "status", searchable: true },
|
|
29
|
-
{ id: "actions", header: "", type: "actions", hideable: false, cell: () => null },
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
const fields: FilterFieldConfig<Row>[] = [];
|
|
33
|
-
|
|
34
|
-
const persistence: DataViewPersistence = {
|
|
35
|
-
create: vi.fn(async () => ({ ok: true as const })),
|
|
36
|
-
update: vi.fn(async () => ({ ok: true as const })),
|
|
37
|
-
remove: vi.fn(async () => ({ ok: true as const })),
|
|
38
|
-
};
|
|
39
|
-
const router: DataViewRouter = { syncViewParam: vi.fn(), refresh: vi.fn() };
|
|
40
|
-
|
|
41
|
-
/** A server-mode wiring; `onQueryChange` is a no-op — we only assert the controls. */
|
|
42
|
-
const server: DataViewServer = { totalCount: 2, page: 1, pageSize: 20, onQueryChange: vi.fn() };
|
|
43
|
-
|
|
44
|
-
/** The URL-seeded controls a server page would parse off the address bar. */
|
|
45
|
-
function urlState(search: string): DataViewState {
|
|
46
|
-
return { search, pills: {}, ranges: {}, sortBy: [], visibleColumns: [] };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function renderBase(props: Partial<React.ComponentProps<typeof DataViewsTableBase<Row>>> = {}) {
|
|
50
|
-
return render(
|
|
51
|
-
<ThemeProvider theme={createTheme()}>
|
|
52
|
-
<DataViewsTableBase<Row>
|
|
53
|
-
persistence={persistence}
|
|
54
|
-
router={router}
|
|
55
|
-
rows={rows}
|
|
56
|
-
columns={columns}
|
|
57
|
-
fields={fields}
|
|
58
|
-
getRowId={(row) => row.id}
|
|
59
|
-
views={[]}
|
|
60
|
-
dataTestId="people"
|
|
61
|
-
testIdPrefix="people"
|
|
62
|
-
{...props}
|
|
63
|
-
/>
|
|
64
|
-
</ThemeProvider>,
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const searchInput = (): HTMLInputElement =>
|
|
69
|
-
screen.getByTestId("people-search-all") as HTMLInputElement;
|
|
70
|
-
|
|
71
|
-
describe("DataViewsTableBase — URL sync (FUT-180)", () => {
|
|
72
|
-
it("seeds the search box from initialState", () => {
|
|
73
|
-
renderBase({ server, initialState: urlState("maria") });
|
|
74
|
-
expect(searchInput().value).toBe("maria");
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("re-applies the URL controls on a later initialState change (server mode: back/forward)", async () => {
|
|
78
|
-
// A new initialState reference is exactly what a browser back/forward produces:
|
|
79
|
-
// the server component re-renders with the restored URL's controls. In server
|
|
80
|
-
// mode the base forwards them as the grid's reactive `syncState`, so the
|
|
81
|
-
// controls reconcile instead of staying stuck on the previous query.
|
|
82
|
-
const { rerender } = renderBase({ server, initialState: urlState("maria") });
|
|
83
|
-
expect(searchInput().value).toBe("maria");
|
|
84
|
-
|
|
85
|
-
rerender(
|
|
86
|
-
<ThemeProvider theme={createTheme()}>
|
|
87
|
-
<DataViewsTableBase<Row>
|
|
88
|
-
persistence={persistence}
|
|
89
|
-
router={router}
|
|
90
|
-
rows={rows}
|
|
91
|
-
columns={columns}
|
|
92
|
-
fields={fields}
|
|
93
|
-
getRowId={(row) => row.id}
|
|
94
|
-
views={[]}
|
|
95
|
-
dataTestId="people"
|
|
96
|
-
testIdPrefix="people"
|
|
97
|
-
server={server}
|
|
98
|
-
initialState={urlState("ana")}
|
|
99
|
-
/>
|
|
100
|
-
</ThemeProvider>,
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
await waitFor(() => expect(searchInput().value).toBe("ana"));
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("does NOT reactively re-apply in client mode (no server): initialState only seeds on mount", () => {
|
|
107
|
-
// Client-mode lists don't keep their filters in the URL, so a changing
|
|
108
|
-
// initialState must not force the controls — only the mount seed applies.
|
|
109
|
-
// Without a `server` wiring the base forwards no `syncState`, so re-rendering
|
|
110
|
-
// with a new initialState leaves the seeded search untouched (a synchronous
|
|
111
|
-
// fact: no reconciling effect exists to fire).
|
|
112
|
-
const { rerender } = renderBase({ initialState: urlState("maria") });
|
|
113
|
-
expect(searchInput().value).toBe("maria");
|
|
114
|
-
|
|
115
|
-
rerender(
|
|
116
|
-
<ThemeProvider theme={createTheme()}>
|
|
117
|
-
<DataViewsTableBase<Row>
|
|
118
|
-
persistence={persistence}
|
|
119
|
-
router={router}
|
|
120
|
-
rows={rows}
|
|
121
|
-
columns={columns}
|
|
122
|
-
fields={fields}
|
|
123
|
-
getRowId={(row) => row.id}
|
|
124
|
-
views={[]}
|
|
125
|
-
dataTestId="people"
|
|
126
|
-
testIdPrefix="people"
|
|
127
|
-
initialState={urlState("ana")}
|
|
128
|
-
/>
|
|
129
|
-
</ThemeProvider>,
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
expect(searchInput().value).toBe("maria");
|
|
133
|
-
});
|
|
134
|
-
});
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
|
3
|
-
import { ThemeProvider, createTheme } from "../../../../mui/styles";
|
|
4
|
-
|
|
5
|
-
import { SaveViewModal } from "../SaveViewModal";
|
|
6
|
-
import type { DataViewColumn, DataViewState, FilterFieldConfig } from "../data-views-types";
|
|
7
|
-
|
|
8
|
-
interface Row extends Record<string, unknown> {
|
|
9
|
-
id: string;
|
|
10
|
-
name: string;
|
|
11
|
-
status: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const columns: DataViewColumn<Row>[] = [
|
|
15
|
-
{ id: "name", header: "Nome" },
|
|
16
|
-
{ id: "status", header: "Status" },
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
const fields: FilterFieldConfig<Row>[] = [
|
|
20
|
-
{ id: "status", label: "Status", options: [{ value: "ACTIVE", label: "Ativo" }] },
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
const currentState: DataViewState = {
|
|
24
|
-
search: "",
|
|
25
|
-
pills: { status: ["ACTIVE"] },
|
|
26
|
-
sortBy: [],
|
|
27
|
-
visibleColumns: ["name", "status"],
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
function renderModal(onSave = vi.fn()) {
|
|
31
|
-
render(
|
|
32
|
-
<ThemeProvider theme={createTheme()}>
|
|
33
|
-
<SaveViewModal<Row>
|
|
34
|
-
open
|
|
35
|
-
onClose={vi.fn()}
|
|
36
|
-
currentState={currentState}
|
|
37
|
-
fields={fields}
|
|
38
|
-
columns={columns}
|
|
39
|
-
onSave={onSave}
|
|
40
|
-
testIdPrefix="views"
|
|
41
|
-
/>
|
|
42
|
-
</ThemeProvider>,
|
|
43
|
-
);
|
|
44
|
-
return onSave;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
describe("SaveViewModal", () => {
|
|
48
|
-
it("previews the current filters and columns", () => {
|
|
49
|
-
renderModal();
|
|
50
|
-
const filters = screen.getByTestId("views-preview-filters");
|
|
51
|
-
expect(within(filters).getByText("Status: Ativo")).toBeInTheDocument();
|
|
52
|
-
const cols = screen.getByTestId("views-preview-columns");
|
|
53
|
-
expect(within(cols).getByText("Nome")).toBeInTheDocument();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("requires a name before saving", () => {
|
|
57
|
-
renderModal();
|
|
58
|
-
expect(screen.getByTestId("views-save-submit")).toBeDisabled();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("saves the entered name + share toggle", () => {
|
|
62
|
-
const onSave = renderModal();
|
|
63
|
-
fireEvent.change(screen.getByTestId("views-save-name"), { target: { value: "Baixo estoque" } });
|
|
64
|
-
fireEvent.click(screen.getByTestId("views-save-shared")); // the switch input carries the testid
|
|
65
|
-
fireEvent.click(screen.getByTestId("views-save-submit"));
|
|
66
|
-
expect(onSave).toHaveBeenCalledWith(
|
|
67
|
-
expect.objectContaining({ name: "Baixo estoque", shared: true, pinned: false, isDefault: false }),
|
|
68
|
-
);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("surfaces the error and keeps the modal open when the save is rejected", async () => {
|
|
72
|
-
const onSave = vi.fn().mockRejectedValue(new Error("Já existe uma visão com esse nome."));
|
|
73
|
-
renderModal(onSave);
|
|
74
|
-
fireEvent.change(screen.getByTestId("views-save-name"), { target: { value: "Duplicada" } });
|
|
75
|
-
fireEvent.click(screen.getByTestId("views-save-submit"));
|
|
76
|
-
|
|
77
|
-
await waitFor(() =>
|
|
78
|
-
expect(screen.getByTestId("views-save-error")).toHaveTextContent(/já existe uma visão/i),
|
|
79
|
-
);
|
|
80
|
-
// Still open + the button re-enabled for a retry.
|
|
81
|
-
expect(screen.getByTestId("views-save-submit")).toBeEnabled();
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { filterRows } from "../data-views-filter";
|
|
4
|
-
import type {
|
|
5
|
-
DataViewColumn,
|
|
6
|
-
FilterFieldConfig,
|
|
7
|
-
RangeFieldConfig,
|
|
8
|
-
} from "../data-views-types";
|
|
9
|
-
|
|
10
|
-
interface Row extends Record<string, unknown> {
|
|
11
|
-
id: string;
|
|
12
|
-
name: string;
|
|
13
|
-
status: string;
|
|
14
|
-
tags: string[];
|
|
15
|
-
price: number;
|
|
16
|
-
stock: number | null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const rows: Row[] = [
|
|
20
|
-
{ id: "1", name: "Ana", status: "ACTIVE", tags: ["vip"], price: 10, stock: 5 },
|
|
21
|
-
{ id: "2", name: "Bruno", status: "INACTIVE", tags: ["new"], price: 20, stock: 0 },
|
|
22
|
-
{ id: "3", name: "Carla", status: "ACTIVE", tags: ["vip", "new"], price: 30, stock: null },
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
const rangeFields: RangeFieldConfig<Row>[] = [
|
|
26
|
-
{ id: "price", label: "Preço", accessor: (row) => row.price },
|
|
27
|
-
{ id: "stock", label: "Estoque", accessor: (row) => row.stock },
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
const columns: DataViewColumn<Row>[] = [
|
|
31
|
-
{ id: "name", header: "Nome", accessor: "name", searchable: true },
|
|
32
|
-
{ id: "status", header: "Status", accessor: "status" },
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
const fields: FilterFieldConfig<Row>[] = [
|
|
36
|
-
{ id: "status", label: "Status", options: [
|
|
37
|
-
{ value: "ACTIVE", label: "Ativo" },
|
|
38
|
-
{ value: "INACTIVE", label: "Inativo" },
|
|
39
|
-
] },
|
|
40
|
-
{ id: "tags", label: "Tags", accessor: (row) => row.tags, options: [
|
|
41
|
-
{ value: "vip", label: "VIP" },
|
|
42
|
-
{ value: "new", label: "Novo" },
|
|
43
|
-
] },
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
const ids = (result: Row[]): string[] => result.map((r) => r.id);
|
|
47
|
-
|
|
48
|
-
describe("filterRows", () => {
|
|
49
|
-
it("returns every row with no search or pills", () => {
|
|
50
|
-
expect(ids(filterRows(rows, columns, fields, { search: "", pills: {} }))).toEqual(["1", "2", "3"]);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("matches the search term across searchable columns only", () => {
|
|
54
|
-
expect(ids(filterRows(rows, columns, fields, { search: "car", pills: {} }))).toEqual(["3"]);
|
|
55
|
-
// status is not searchable, so a status term matches nothing by search.
|
|
56
|
-
expect(filterRows(rows, columns, fields, { search: "active", pills: {} })).toHaveLength(0);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("narrows by a single-value pill", () => {
|
|
60
|
-
expect(ids(filterRows(rows, columns, fields, { search: "", pills: { status: ["ACTIVE"] } }))).toEqual(["1", "3"]);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("treats a multi-value field via its accessor as OR membership", () => {
|
|
64
|
-
expect(ids(filterRows(rows, columns, fields, { search: "", pills: { tags: ["new"] } }))).toEqual(["2", "3"]);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("combines search AND pills", () => {
|
|
68
|
-
const result = filterRows(rows, columns, fields, { search: "a", pills: { status: ["ACTIVE"] } });
|
|
69
|
-
// "a" matches Ana + Carla; ACTIVE keeps both.
|
|
70
|
-
expect(ids(result)).toEqual(["1", "3"]);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("ignores a pill with an empty selection", () => {
|
|
74
|
-
expect(ids(filterRows(rows, columns, fields, { search: "", pills: { status: [] } }))).toEqual(["1", "2", "3"]);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe("numeric ranges", () => {
|
|
78
|
-
const crit = (ranges: Record<string, { min?: number; max?: number }>) => ({
|
|
79
|
-
search: "",
|
|
80
|
-
pills: {},
|
|
81
|
-
ranges,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("filters by a minimum bound", () => {
|
|
85
|
-
expect(ids(filterRows(rows, columns, fields, crit({ price: { min: 20 } }), rangeFields))).toEqual(["2", "3"]);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("filters by a maximum bound", () => {
|
|
89
|
-
expect(ids(filterRows(rows, columns, fields, crit({ price: { max: 20 } }), rangeFields))).toEqual(["1", "2"]);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("filters by an inclusive min+max window", () => {
|
|
93
|
-
expect(ids(filterRows(rows, columns, fields, crit({ price: { min: 10, max: 20 } }), rangeFields))).toEqual(["1", "2"]);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("excludes rows whose value is null when a bound is set", () => {
|
|
97
|
-
// Carla (id 3) has null stock, so any bounded stock range drops her.
|
|
98
|
-
expect(ids(filterRows(rows, columns, fields, crit({ stock: { min: 0 } }), rangeFields))).toEqual(["1", "2"]);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("ignores an empty range (no bounds)", () => {
|
|
102
|
-
expect(ids(filterRows(rows, columns, fields, crit({ price: {} }), rangeFields))).toEqual(["1", "2", "3"]);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("ignores an inverted range (max < min) instead of filtering everything out", () => {
|
|
106
|
-
expect(ids(filterRows(rows, columns, fields, crit({ price: { min: 30, max: 10 } }), rangeFields))).toEqual(["1", "2", "3"]);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("combines ranges with pills", () => {
|
|
110
|
-
const result = filterRows(rows, columns, fields, { search: "", pills: { status: ["ACTIVE"] }, ranges: { price: { min: 25 } } }, rangeFields);
|
|
111
|
-
// ACTIVE → Ana(10), Carla(30); price ≥ 25 → only Carla.
|
|
112
|
-
expect(ids(result)).toEqual(["3"]);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { describeViewState } from "../data-views-preview";
|
|
4
|
-
import type { DataViewColumn, DataViewState, FilterFieldConfig } from "../data-views-types";
|
|
5
|
-
|
|
6
|
-
interface Row extends Record<string, unknown> {
|
|
7
|
-
id: string;
|
|
8
|
-
name: string;
|
|
9
|
-
status: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const columns: DataViewColumn<Row>[] = [
|
|
13
|
-
{ id: "name", header: "Nome" },
|
|
14
|
-
{ id: "status", header: "Status" },
|
|
15
|
-
{ id: "actions", header: "", hideable: false },
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
const fields: FilterFieldConfig<Row>[] = [
|
|
19
|
-
{ id: "status", label: "Status", options: [{ value: "ACTIVE", label: "Ativo" }] },
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
describe("describeViewState", () => {
|
|
23
|
-
it("summarizes search, pills, visible columns, and sort with labels", () => {
|
|
24
|
-
const state: DataViewState = {
|
|
25
|
-
search: "burg",
|
|
26
|
-
pills: { status: ["ACTIVE"] },
|
|
27
|
-
sortBy: [{ id: "name", dir: "asc" }],
|
|
28
|
-
visibleColumns: ["name", "status"],
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const result = describeViewState(state, fields, columns);
|
|
32
|
-
|
|
33
|
-
expect(result.filters).toEqual(['Busca: "burg"', "Status: Ativo"]);
|
|
34
|
-
expect(result.columns).toEqual(["Nome", "Status"]);
|
|
35
|
-
expect(result.sort).toEqual(["Nome (crescente)"]);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("returns empty sections for a pristine state", () => {
|
|
39
|
-
const state: DataViewState = { search: "", pills: {}, sortBy: [], visibleColumns: [] };
|
|
40
|
-
const result = describeViewState(state, fields, columns);
|
|
41
|
-
expect(result).toEqual({ filters: [], columns: [], sort: [] });
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("omits the actions column (non-hideable) from the columns summary", () => {
|
|
45
|
-
const state: DataViewState = {
|
|
46
|
-
search: "",
|
|
47
|
-
pills: {},
|
|
48
|
-
sortBy: [],
|
|
49
|
-
visibleColumns: ["name", "actions"],
|
|
50
|
-
};
|
|
51
|
-
expect(describeViewState(state, fields, columns).columns).toEqual(["Nome"]);
|
|
52
|
-
});
|
|
53
|
-
});
|