@multiplatform.one/frappe-ui 7.86.0 → 7.87.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/FileBrowser/index.cjs +1 -1
- package/dist/cjs/FileBrowser/index.native.js +1 -1
- package/dist/cjs/FileBrowser/index.native.js.map +1 -1
- package/dist/cjs/desk/awesomebar.cjs +1 -1
- package/dist/cjs/desk/awesomebar.native.js +1 -1
- package/dist/cjs/desk/awesomebar.native.js.map +1 -1
- package/dist/cjs/fields/CodeEditor/index.cjs +1 -1
- package/dist/cjs/fields/CodeEditor/index.native.js +1 -1
- package/dist/cjs/fields/CodeEditor/index.native.js.map +1 -1
- package/dist/cjs/fields/Duration/index.native.js.map +1 -1
- package/dist/cjs/fields/LongText/index.cjs +1 -1
- package/dist/cjs/fields/LongText/index.native.js +1 -1
- package/dist/cjs/fields/LongText/index.native.js.map +1 -1
- package/dist/cjs/fields/MarkdownEditor/index.native.js +1 -1
- package/dist/cjs/fields/MarkdownEditor/index.native.js.map +1 -1
- package/dist/cjs/fields/RadioGroup/index.cjs +1 -1
- package/dist/cjs/fields/RadioGroup/index.native.js +1 -1
- package/dist/cjs/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/cjs/fields/Slider/index.cjs +1 -1
- package/dist/cjs/fields/Slider/index.native.js +1 -1
- package/dist/cjs/fields/Slider/index.native.js.map +1 -1
- package/dist/cjs/fields/SmallText/index.cjs +1 -1
- package/dist/cjs/fields/SmallText/index.native.js +1 -1
- package/dist/cjs/fields/SmallText/index.native.js.map +1 -1
- package/dist/cjs/fields/TimePicker/index.native.js.map +1 -1
- package/dist/cjs/fields/ToggleGroup/index.cjs +1 -1
- package/dist/cjs/fields/ToggleGroup/index.native.js +1 -1
- package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/cjs/insights/InsightsRecords.cjs +2 -7
- package/dist/cjs/insights/InsightsRecords.native.js +2 -7
- package/dist/cjs/insights/InsightsRecords.native.js.map +1 -1
- package/dist/cjs/list/FacetList.cjs +1 -1
- package/dist/cjs/list/FacetList.native.js +1 -1
- package/dist/cjs/list/FacetList.native.js.map +1 -1
- package/dist/cjs/useFrappeAggregate.cjs +6 -1
- package/dist/cjs/useFrappeAggregate.native.js +12 -2
- package/dist/cjs/useFrappeAggregate.native.js.map +1 -1
- package/dist/esm/ChartCard.mjs.map +1 -1
- package/dist/esm/ChartCard.native.js.map +1 -1
- package/dist/esm/FileBrowser/index.mjs +1 -1
- package/dist/esm/FileBrowser/index.mjs.map +1 -1
- package/dist/esm/FileBrowser/index.native.js +1 -1
- package/dist/esm/FileBrowser/index.native.js.map +1 -1
- package/dist/esm/desk/awesomebar.mjs +1 -1
- package/dist/esm/desk/awesomebar.mjs.map +1 -1
- package/dist/esm/desk/awesomebar.native.js +1 -1
- package/dist/esm/desk/awesomebar.native.js.map +1 -1
- package/dist/esm/fields/CodeEditor/index.mjs +1 -1
- package/dist/esm/fields/CodeEditor/index.mjs.map +1 -1
- package/dist/esm/fields/CodeEditor/index.native.js +1 -1
- package/dist/esm/fields/CodeEditor/index.native.js.map +1 -1
- package/dist/esm/fields/Duration/index.mjs.map +1 -1
- package/dist/esm/fields/Duration/index.native.js.map +1 -1
- package/dist/esm/fields/LongText/index.mjs +1 -1
- package/dist/esm/fields/LongText/index.mjs.map +1 -1
- package/dist/esm/fields/LongText/index.native.js +1 -1
- package/dist/esm/fields/LongText/index.native.js.map +1 -1
- package/dist/esm/fields/MarkdownEditor/index.native.js +1 -1
- package/dist/esm/fields/MarkdownEditor/index.native.js.map +1 -1
- package/dist/esm/fields/RadioGroup/index.mjs +1 -1
- package/dist/esm/fields/RadioGroup/index.mjs.map +1 -1
- package/dist/esm/fields/RadioGroup/index.native.js +1 -1
- package/dist/esm/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/esm/fields/Slider/index.mjs +1 -1
- package/dist/esm/fields/Slider/index.mjs.map +1 -1
- package/dist/esm/fields/Slider/index.native.js +1 -1
- package/dist/esm/fields/Slider/index.native.js.map +1 -1
- package/dist/esm/fields/SmallText/index.mjs +1 -1
- package/dist/esm/fields/SmallText/index.mjs.map +1 -1
- package/dist/esm/fields/SmallText/index.native.js +1 -1
- package/dist/esm/fields/SmallText/index.native.js.map +1 -1
- package/dist/esm/fields/TimePicker/index.mjs.map +1 -1
- package/dist/esm/fields/TimePicker/index.native.js.map +1 -1
- package/dist/esm/fields/ToggleGroup/index.mjs +1 -1
- package/dist/esm/fields/ToggleGroup/index.mjs.map +1 -1
- package/dist/esm/fields/ToggleGroup/index.native.js +1 -1
- package/dist/esm/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/esm/insights/InsightsRecords.mjs +3 -8
- package/dist/esm/insights/InsightsRecords.mjs.map +1 -1
- package/dist/esm/insights/InsightsRecords.native.js +3 -8
- package/dist/esm/insights/InsightsRecords.native.js.map +1 -1
- package/dist/esm/list/FacetList.mjs +1 -1
- package/dist/esm/list/FacetList.mjs.map +1 -1
- package/dist/esm/list/FacetList.native.js +1 -1
- package/dist/esm/list/FacetList.native.js.map +1 -1
- package/dist/esm/useFrappeAggregate.mjs +6 -1
- package/dist/esm/useFrappeAggregate.mjs.map +1 -1
- package/dist/esm/useFrappeAggregate.native.js +12 -2
- package/dist/esm/useFrappeAggregate.native.js.map +1 -1
- package/dist/jsx/ChartCard.mjs.map +1 -1
- package/dist/jsx/FileBrowser/index.mjs +1 -1
- package/dist/jsx/FileBrowser/index.mjs.map +1 -1
- package/dist/jsx/FileBrowser/index.native.js +1 -1
- package/dist/jsx/FileBrowser/index.native.js.map +1 -1
- package/dist/jsx/desk/awesomebar.mjs +1 -1
- package/dist/jsx/desk/awesomebar.mjs.map +1 -1
- package/dist/jsx/desk/awesomebar.native.js +1 -1
- package/dist/jsx/desk/awesomebar.native.js.map +1 -1
- package/dist/jsx/fields/CodeEditor/index.mjs +1 -1
- package/dist/jsx/fields/CodeEditor/index.mjs.map +1 -1
- package/dist/jsx/fields/CodeEditor/index.native.js +1 -1
- package/dist/jsx/fields/CodeEditor/index.native.js.map +1 -1
- package/dist/jsx/fields/Duration/index.mjs.map +1 -1
- package/dist/jsx/fields/Duration/index.native.js.map +1 -1
- package/dist/jsx/fields/LongText/index.mjs +1 -1
- package/dist/jsx/fields/LongText/index.mjs.map +1 -1
- package/dist/jsx/fields/LongText/index.native.js +1 -1
- package/dist/jsx/fields/LongText/index.native.js.map +1 -1
- package/dist/jsx/fields/MarkdownEditor/index.native.js +1 -1
- package/dist/jsx/fields/MarkdownEditor/index.native.js.map +1 -1
- package/dist/jsx/fields/RadioGroup/index.mjs +1 -1
- package/dist/jsx/fields/RadioGroup/index.mjs.map +1 -1
- package/dist/jsx/fields/RadioGroup/index.native.js +1 -1
- package/dist/jsx/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/jsx/fields/Slider/index.mjs +1 -1
- package/dist/jsx/fields/Slider/index.mjs.map +1 -1
- package/dist/jsx/fields/Slider/index.native.js +1 -1
- package/dist/jsx/fields/Slider/index.native.js.map +1 -1
- package/dist/jsx/fields/SmallText/index.mjs +1 -1
- package/dist/jsx/fields/SmallText/index.mjs.map +1 -1
- package/dist/jsx/fields/SmallText/index.native.js +1 -1
- package/dist/jsx/fields/SmallText/index.native.js.map +1 -1
- package/dist/jsx/fields/TimePicker/index.mjs.map +1 -1
- package/dist/jsx/fields/TimePicker/index.native.js.map +1 -1
- package/dist/jsx/fields/ToggleGroup/index.mjs +1 -1
- package/dist/jsx/fields/ToggleGroup/index.mjs.map +1 -1
- package/dist/jsx/fields/ToggleGroup/index.native.js +1 -1
- package/dist/jsx/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/jsx/insights/InsightsRecords.mjs +3 -8
- package/dist/jsx/insights/InsightsRecords.mjs.map +1 -1
- package/dist/jsx/insights/InsightsRecords.native.js +2 -7
- package/dist/jsx/insights/InsightsRecords.native.js.map +1 -1
- package/dist/jsx/list/FacetList.mjs +1 -1
- package/dist/jsx/list/FacetList.mjs.map +1 -1
- package/dist/jsx/list/FacetList.native.js +1 -1
- package/dist/jsx/list/FacetList.native.js.map +1 -1
- package/dist/jsx/useFrappeAggregate.mjs +6 -1
- package/dist/jsx/useFrappeAggregate.mjs.map +1 -1
- package/dist/jsx/useFrappeAggregate.native.js +12 -2
- package/dist/jsx/useFrappeAggregate.native.js.map +1 -1
- package/package.json +16 -16
- package/src/ChartCard.tsx +1 -1
- package/src/FileBrowser/index.tsx +1 -1
- package/src/FrappeInsights.spec.tsx +9 -0
- package/src/desk/awesomebar.tsx +1 -1
- package/src/fields/CodeEditor/index.tsx +1 -1
- package/src/fields/Duration/index.tsx +1 -1
- package/src/fields/LongText/index.tsx +1 -1
- package/src/fields/MarkdownEditor/index.native.tsx +1 -1
- package/src/fields/RadioGroup/index.tsx +1 -1
- package/src/fields/Slider/index.tsx +1 -1
- package/src/fields/SmallText/index.tsx +1 -1
- package/src/fields/TimePicker/index.tsx +1 -1
- package/src/fields/ToggleGroup/index.tsx +1 -1
- package/src/insights/InsightsRecords.tsx +3 -5
- package/src/list/FacetList.spec.tsx +128 -0
- package/src/list/FacetList.tsx +1 -1
- package/src/list/ListControlsSheet.behaviour.spec.tsx +283 -0
- package/src/useFrappeAggregate.groupLabel.spec.tsx +42 -0
- package/src/useFrappeAggregate.ts +7 -1
- package/types/ChartCard.d.ts +1 -1
- package/types/ChartCard.d.ts.map +1 -1
- package/types/fields/Duration/index.d.ts +1 -1
- package/types/fields/Duration/index.d.ts.map +1 -1
- package/types/fields/TimePicker/index.d.ts +1 -1
- package/types/fields/TimePicker/index.d.ts.map +1 -1
- package/types/insights/InsightsRecords.d.ts.map +1 -1
- package/types/useFrappeAggregate.d.ts.map +1 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
2
|
+
import { cleanup, fireEvent, screen, waitFor, within } from "@testing-library/react";
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { ListControlsSheet, type ListControlsSheetProps } from "./ListControlsSheet";
|
|
5
|
+
import { fixtureControls, fixtureList } from "./listControls.fixture";
|
|
6
|
+
import type { FrappeListState } from "./listState";
|
|
7
|
+
|
|
8
|
+
vi.mock("./RangeFilter", async (importOriginal) => {
|
|
9
|
+
const actual = await importOriginal<typeof import("./RangeFilter")>();
|
|
10
|
+
return {
|
|
11
|
+
...actual,
|
|
12
|
+
RangeFilter: ({
|
|
13
|
+
label,
|
|
14
|
+
value,
|
|
15
|
+
max,
|
|
16
|
+
onChange,
|
|
17
|
+
}: {
|
|
18
|
+
label: string;
|
|
19
|
+
value: number;
|
|
20
|
+
max: number;
|
|
21
|
+
onChange: (value: number) => void;
|
|
22
|
+
}) => (
|
|
23
|
+
<button type="button" data-max={max} data-value={value} onClick={() => onChange(50)}>
|
|
24
|
+
{label}
|
|
25
|
+
</button>
|
|
26
|
+
),
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(cleanup);
|
|
31
|
+
|
|
32
|
+
const controls = {
|
|
33
|
+
...fixtureControls,
|
|
34
|
+
facets: fixtureControls.facets!.map((facet) =>
|
|
35
|
+
facet.param === "type"
|
|
36
|
+
? { ...facet, optionLabel: (value: string) => value.toUpperCase() }
|
|
37
|
+
: facet,
|
|
38
|
+
),
|
|
39
|
+
views: ["grid", "list", "table"] as const,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function renderSheet(
|
|
43
|
+
state: Partial<FrappeListState> = {},
|
|
44
|
+
props: Partial<ListControlsSheetProps> = {},
|
|
45
|
+
listPatch: Partial<ReturnType<typeof fixtureList>> = {},
|
|
46
|
+
) {
|
|
47
|
+
const update = vi.fn();
|
|
48
|
+
const list = {
|
|
49
|
+
...fixtureList(state, update),
|
|
50
|
+
facets: { type: { counts: { fire: 5, water: 7 } } },
|
|
51
|
+
...listPatch,
|
|
52
|
+
} as ReturnType<typeof fixtureList>;
|
|
53
|
+
const result = renderWithProviders(
|
|
54
|
+
<ListControlsSheet
|
|
55
|
+
controls={controls as ListControlsSheetProps["controls"]}
|
|
56
|
+
list={list}
|
|
57
|
+
searchId="roster-search"
|
|
58
|
+
showSearch={false}
|
|
59
|
+
showFavorites
|
|
60
|
+
separator={false}
|
|
61
|
+
noun="Pokemon"
|
|
62
|
+
testID="roster"
|
|
63
|
+
{...props}
|
|
64
|
+
/>,
|
|
65
|
+
);
|
|
66
|
+
return { ...result, update, list };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function openSheet() {
|
|
70
|
+
fireEvent.click(screen.getByTestId("roster-filters"));
|
|
71
|
+
return screen.findByTestId("roster-filters-sheet");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const confirmLabel = () =>
|
|
75
|
+
screen.getByTestId("roster-filters-header-confirm").getAttribute("aria-label");
|
|
76
|
+
const counted = "Show {{count}} {{noun}}";
|
|
77
|
+
|
|
78
|
+
describe("ListControlsSheet: the filters in force as chips", () => {
|
|
79
|
+
it("shows a facet chip by its option label and drops the facet on a tap", () => {
|
|
80
|
+
const { update } = renderSheet({ facets: { type: "fire" } });
|
|
81
|
+
fireEvent.click(screen.getByRole("button", { name: "FIRE" }));
|
|
82
|
+
expect(update).toHaveBeenCalledWith({ facets: { type: "" } });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("shows a facet chip by its raw value when the facet has no option label", () => {
|
|
86
|
+
renderSheet({ facets: { region: "kanto" } });
|
|
87
|
+
const chip = screen
|
|
88
|
+
.getAllByRole("button", { name: "kanto" })
|
|
89
|
+
.find((node) => !node.closest('[role="radiogroup"]'));
|
|
90
|
+
expect(chip).toBeTruthy();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("shows a range chip as its minimum and drops it on a tap", () => {
|
|
94
|
+
const { update } = renderSheet({ ranges: { xp: 120 } });
|
|
95
|
+
fireEvent.click(screen.getByRole("button", { name: "Min XP 120+" }));
|
|
96
|
+
expect(update).toHaveBeenCalledWith({ ranges: { xp: 0 } });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("shows a Favorites chip and turns favorites-only off on a tap", () => {
|
|
100
|
+
const { update } = renderSheet({ favoritesOnly: true });
|
|
101
|
+
fireEvent.click(screen.getByRole("button", { name: "Favorites" }));
|
|
102
|
+
expect(update).toHaveBeenCalledWith({ favoritesOnly: false });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("draws no chip row with no filter, view or shuffle", () => {
|
|
106
|
+
renderSheet();
|
|
107
|
+
expect(screen.queryByRole("button", { name: "Favorites" })).toBeNull();
|
|
108
|
+
expect(screen.queryByTestId("roster-views")).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("ListControlsSheet: view switch and shuffle", () => {
|
|
113
|
+
it("switches views from icon segments named Cards, List and Table", () => {
|
|
114
|
+
const onChange = vi.fn();
|
|
115
|
+
renderSheet({}, { view: { current: "grid", onChange } });
|
|
116
|
+
const views = screen.getByTestId("roster-views");
|
|
117
|
+
expect(
|
|
118
|
+
[...views.querySelectorAll("[aria-label]")]
|
|
119
|
+
.map((node) => node.getAttribute("aria-label"))
|
|
120
|
+
.filter((name) => ["Cards", "List", "Table"].includes(name ?? "")),
|
|
121
|
+
).toEqual(["Cards", "List", "Table"]);
|
|
122
|
+
fireEvent.click(within(views).getByLabelText("Table"));
|
|
123
|
+
expect(onChange).toHaveBeenCalledWith("table");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("toggles the shuffle panel from a dice button wired to the panel", () => {
|
|
127
|
+
const toggle = vi.fn();
|
|
128
|
+
renderSheet({}, { shuffle: { open: true, toggle, panelId: "roster-shuffle" } });
|
|
129
|
+
const dice = screen.getByTestId("roster-shuffle-toggle");
|
|
130
|
+
expect(dice.getAttribute("aria-label")).toBe("Surprise me");
|
|
131
|
+
expect(dice.getAttribute("aria-expanded")).toBe("true");
|
|
132
|
+
expect(dice.getAttribute("aria-controls")).toBe("roster-shuffle");
|
|
133
|
+
fireEvent.click(dice);
|
|
134
|
+
expect(toggle).toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("ListControlsSheet: the sheet", () => {
|
|
139
|
+
it("counts its confirm for the whole list when nothing filters it", async () => {
|
|
140
|
+
renderSheet();
|
|
141
|
+
await openSheet();
|
|
142
|
+
expect(confirmLabel()).toBe(counted);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("counts its confirm for the one active facet", async () => {
|
|
146
|
+
renderSheet({ facets: { type: "water" } });
|
|
147
|
+
await openSheet();
|
|
148
|
+
expect(confirmLabel()).toBe(counted);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("counts its confirm for the one active band", async () => {
|
|
152
|
+
renderSheet({ bands: { generation: "2" } });
|
|
153
|
+
await openSheet();
|
|
154
|
+
expect(confirmLabel()).toBe(counted);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("counts its confirm for favorites under favorites-only", async () => {
|
|
158
|
+
renderSheet({ favoritesOnly: true });
|
|
159
|
+
await openSheet();
|
|
160
|
+
expect(confirmLabel()).toBe(counted);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("names Show results when the one active facet has no count yet", async () => {
|
|
164
|
+
renderSheet({ facets: { region: "kanto" } });
|
|
165
|
+
await openSheet();
|
|
166
|
+
expect(confirmLabel()).toBe("Show results");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("falls back to Show results when two filters are in force", async () => {
|
|
170
|
+
renderSheet({ facets: { type: "fire" }, favoritesOnly: true });
|
|
171
|
+
await openSheet();
|
|
172
|
+
expect(confirmLabel()).toBe("Show results");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("falls back to Show results when a range alone has no count", async () => {
|
|
176
|
+
renderSheet({ ranges: { xp: 50 } });
|
|
177
|
+
await openSheet();
|
|
178
|
+
expect(confirmLabel()).toBe("Show results");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("closes on the confirm and on Escape", async () => {
|
|
182
|
+
renderSheet();
|
|
183
|
+
const expanded = () => screen.getByTestId("roster-filters").getAttribute("aria-expanded");
|
|
184
|
+
await openSheet();
|
|
185
|
+
expect(expanded()).toBe("true");
|
|
186
|
+
fireEvent.click(screen.getByTestId("roster-filters-header-confirm"));
|
|
187
|
+
await waitFor(() => expect(expanded()).toBe("false"));
|
|
188
|
+
await openSheet();
|
|
189
|
+
fireEvent.keyDown(document, { key: "Tab" });
|
|
190
|
+
expect(expanded()).toBe("true");
|
|
191
|
+
fireEvent.keyDown(document, { key: "Escape" });
|
|
192
|
+
await waitFor(() => expect(expanded()).toBe("false"));
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("resets sort to the first, favorites off, and every facet, range and band", async () => {
|
|
196
|
+
const { update } = renderSheet({ sort: "xp", favoritesOnly: true });
|
|
197
|
+
await openSheet();
|
|
198
|
+
fireEvent.click(screen.getByTestId("roster-reset"));
|
|
199
|
+
expect(update).toHaveBeenCalledWith({
|
|
200
|
+
sort: "number",
|
|
201
|
+
favoritesOnly: false,
|
|
202
|
+
facets: { type: "", region: "" },
|
|
203
|
+
ranges: { xp: 0 },
|
|
204
|
+
bands: { generation: "" },
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("sorts from the Sort by select", async () => {
|
|
209
|
+
const { update } = renderSheet();
|
|
210
|
+
const sheet = await openSheet();
|
|
211
|
+
const trigger = within(sheet).getByRole("combobox", { name: "Sort by" });
|
|
212
|
+
trigger.focus();
|
|
213
|
+
fireEvent.keyDown(trigger, { key: "Enter" });
|
|
214
|
+
await screen.findByRole("option", { name: "XP" });
|
|
215
|
+
fireEvent.keyDown(document.activeElement!, { key: "ArrowDown" });
|
|
216
|
+
fireEvent.keyDown(document.activeElement!, { key: "Enter" });
|
|
217
|
+
expect(update).toHaveBeenCalledWith({ sort: "xp" });
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("turns favorites-only on from its switch", async () => {
|
|
221
|
+
const { update } = renderSheet();
|
|
222
|
+
const sheet = await openSheet();
|
|
223
|
+
fireEvent.click(within(sheet).getByRole("switch", { name: "Favorites only" }));
|
|
224
|
+
expect(update).toHaveBeenCalledWith({ favoritesOnly: true });
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("lists each facet as chips with All first and the counts read so far", async () => {
|
|
228
|
+
renderSheet({ facets: { type: "fire" } });
|
|
229
|
+
const sheet = await openSheet();
|
|
230
|
+
const type = within(sheet).getByRole("radiogroup", { name: "Type" });
|
|
231
|
+
const chips = within(type).getAllByRole("button");
|
|
232
|
+
expect(chips.map((chip) => chip.textContent)).toEqual(["All types 12", "FIRE 5", "WATER 7"]);
|
|
233
|
+
const region = within(sheet).getByRole("radiogroup", { name: "Region" });
|
|
234
|
+
expect(
|
|
235
|
+
within(region)
|
|
236
|
+
.getAllByRole("button")
|
|
237
|
+
.map((chip) => chip.textContent),
|
|
238
|
+
).toEqual(["All 12", "kanto", "johto"]);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("narrows on a facet chip, clears on the active one, and All clears", async () => {
|
|
242
|
+
const { update } = renderSheet({ facets: { type: "fire" } });
|
|
243
|
+
const sheet = await openSheet();
|
|
244
|
+
const type = within(sheet).getByRole("radiogroup", { name: "Type" });
|
|
245
|
+
fireEvent.click(within(type).getByRole("button", { name: /^WATER/ }));
|
|
246
|
+
expect(update).toHaveBeenLastCalledWith({ facets: { type: "water" } });
|
|
247
|
+
fireEvent.click(within(type).getByRole("button", { name: /^FIRE/ }));
|
|
248
|
+
expect(update).toHaveBeenLastCalledWith({ facets: { type: "" } });
|
|
249
|
+
fireEvent.click(within(type).getByRole("button", { name: /^All types/ }));
|
|
250
|
+
expect(update).toHaveBeenLastCalledWith({ facets: { type: "" } });
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("caps each range at the list's highest value and commits a move as a range patch", async () => {
|
|
254
|
+
const { update } = renderSheet();
|
|
255
|
+
const sheet = await openSheet();
|
|
256
|
+
const range = within(sheet).getByRole("button", { name: "Min XP" });
|
|
257
|
+
expect(Number(range.getAttribute("data-max"))).toBeGreaterThanOrEqual(300);
|
|
258
|
+
expect(within(sheet).getByText(`0–${range.getAttribute("data-max")}`)).toBeTruthy();
|
|
259
|
+
fireEvent.click(range);
|
|
260
|
+
expect(update).toHaveBeenCalledWith({ ranges: { xp: 50 } });
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("caps a range with no reading at the floor ceiling", async () => {
|
|
264
|
+
renderSheet({}, {}, { highest: {} });
|
|
265
|
+
const sheet = await openSheet();
|
|
266
|
+
const range = within(sheet).getByRole("button", { name: "Min XP" });
|
|
267
|
+
expect(range.getAttribute("data-value")).toBe("0");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("has no Filters button when there is nothing to put in the sheet", () => {
|
|
271
|
+
renderSheet({}, { controls: {}, showFavorites: false });
|
|
272
|
+
expect(screen.queryByTestId("roster-filters")).toBeNull();
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe("ListControlsSheet: search", () => {
|
|
277
|
+
it("searches as the text settles", async () => {
|
|
278
|
+
const { update } = renderSheet({}, { showSearch: true, searchPlaceholder: "Find Pokemon" });
|
|
279
|
+
const input = screen.getByLabelText("Find Pokemon") as HTMLInputElement;
|
|
280
|
+
fireEvent.change(input, { target: { value: "pika" } });
|
|
281
|
+
await waitFor(() => expect(update).toHaveBeenCalledWith({ search: "pika" }));
|
|
282
|
+
});
|
|
283
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { renderHook, waitFor } from "@testing-library/react";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { useFrappeAggregate } from "./useFrappeAggregate";
|
|
4
|
+
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.stubGlobal(
|
|
7
|
+
"fetch",
|
|
8
|
+
async () =>
|
|
9
|
+
new Response(
|
|
10
|
+
JSON.stringify({
|
|
11
|
+
data: [
|
|
12
|
+
{ status: "Open", _aggregate_value: 4 },
|
|
13
|
+
{ status: 3, _aggregate_value: 2 },
|
|
14
|
+
{ status: null, _aggregate_value: 1 },
|
|
15
|
+
{ status: { level: 2 }, _aggregate_value: 5 },
|
|
16
|
+
],
|
|
17
|
+
}),
|
|
18
|
+
{ headers: { "content-type": "application/json" } },
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
24
|
+
|
|
25
|
+
describe("useFrappeAggregate group labels", () => {
|
|
26
|
+
it("writes primitives as text, a missing group as empty and an object group as JSON", async () => {
|
|
27
|
+
const { result } = renderHook(() =>
|
|
28
|
+
useFrappeAggregate(
|
|
29
|
+
{ baseURL: "http://group-label.test", realtime: false },
|
|
30
|
+
{ doctype: "ToDo", groupBy: "status" },
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
34
|
+
|
|
35
|
+
expect(result.current.groups).toEqual([
|
|
36
|
+
{ label: "Open", value: 4 },
|
|
37
|
+
{ label: "3", value: 2 },
|
|
38
|
+
{ label: "", value: 1 },
|
|
39
|
+
{ label: '{"level":2}', value: 5 },
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -130,6 +130,12 @@ function toFiniteNumber(raw: unknown): number | undefined {
|
|
|
130
130
|
return Number.isFinite(num) ? num : undefined;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
function groupLabel(raw: unknown): string {
|
|
134
|
+
if (raw === null || raw === undefined) return "";
|
|
135
|
+
if (typeof raw === "object") return JSON.stringify(raw);
|
|
136
|
+
return String(raw);
|
|
137
|
+
}
|
|
138
|
+
|
|
133
139
|
interface AggregateData {
|
|
134
140
|
value?: number;
|
|
135
141
|
groups?: FrappeAggregateGroup[];
|
|
@@ -162,7 +168,7 @@ async function fetchLiveAggregate(db: DbModule, spec: FrappeAggregateSpec): Prom
|
|
|
162
168
|
const groupBy = spec.groupBy;
|
|
163
169
|
return {
|
|
164
170
|
groups: rows.map((row) => ({
|
|
165
|
-
label:
|
|
171
|
+
label: groupLabel(row[groupBy]),
|
|
166
172
|
value: toFiniteNumber(row._aggregate_value) ?? 0,
|
|
167
173
|
})),
|
|
168
174
|
};
|
package/types/ChartCard.d.ts
CHANGED
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
* invalid SVG), so negatives clamp to the baseline there.
|
|
10
10
|
*/
|
|
11
11
|
import { type ChartCardProps } from "@multiplatform.one/components";
|
|
12
|
-
export declare function ChartCard({ config, ...chartCardProps }: ChartCardProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function ChartCard({ config, ...chartCardProps }: Readonly<ChartCardProps>): import("react/jsx-runtime").JSX.Element;
|
|
13
13
|
//# sourceMappingURL=ChartCard.d.ts.map
|
package/types/ChartCard.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartCard.d.ts","sourceRoot":"","sources":["../src/ChartCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,+BAA+B,CAAC;AAGvC,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"ChartCard.d.ts","sourceRoot":"","sources":["../src/ChartCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,+BAA+B,CAAC;AAGvC,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,QAAQ,CAAC,cAAc,CAAC,2CAahF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DurationProps as FormsDurationProps } from "@multiplatform.one/forms";
|
|
2
2
|
export type DurationProps = FormsDurationProps;
|
|
3
3
|
/** Thin adapter — forwards helperText / readOnly / skeleton / compact via props spread. */
|
|
4
|
-
export declare function Duration(props: DurationProps): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
export declare function Duration(props: Readonly<DurationProps>): import("react/jsx-runtime").JSX.Element;
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fields/Duration/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAKpF,MAAM,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAE/C,2FAA2F;AAC3F,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fields/Duration/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAKpF,MAAM,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAE/C,2FAA2F;AAC3F,wBAAgB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,2CAWtD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TimePickerProps as FormsTimePickerProps } from "@multiplatform.one/forms";
|
|
2
2
|
export type TimePickerProps = FormsTimePickerProps;
|
|
3
3
|
/** Thin adapter — forwards helperText / readOnly / skeleton / compact via props spread. */
|
|
4
|
-
export declare function TimePicker(props: TimePickerProps): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
export declare function TimePicker(props: Readonly<TimePickerProps>): import("react/jsx-runtime").JSX.Element;
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fields/TimePicker/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,IAAI,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAKxF,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAEnD,2FAA2F;AAC3F,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fields/TimePicker/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,IAAI,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAKxF,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAEnD,2FAA2F;AAC3F,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,2CAW1D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InsightsRecords.d.ts","sourceRoot":"","sources":["../../src/insights/InsightsRecords.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,MAAM,WAAW,kBAAkB,CAAC,IAAI;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,IAAI,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB,CAAC,IAAI;IACxC,KAAK,EAAE,SAAS,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;IAC3C,MAAM,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,oBAAoB,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"InsightsRecords.d.ts","sourceRoot":"","sources":["../../src/insights/InsightsRecords.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,MAAM,WAAW,kBAAkB,CAAC,IAAI;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,IAAI,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB,CAAC,IAAI;IACxC,KAAK,EAAE,SAAS,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;IAC3C,MAAM,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,oBAAoB,CAAC,IAAI,CAAC,2CA6C1F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFrappeAggregate.d.ts","sourceRoot":"","sources":["../src/useFrappeAggregate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAML,KAAK,eAAe,EACrB,MAAM,2BAA2B,CAAC;AAGnC,MAAM,MAAM,wBAAwB,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAE/E,gEAAgE;AAChE,MAAM,WAAW,yBAAyB;IACxC,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChG,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,wBAAwB,CAAC;IACrC,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACrE,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB;IACvC,iDAAiD;IACjD,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,oDAAoD;IACpD,MAAM,EAAE,oBAAoB,EAAE,GAAG,SAAS,CAAC;IAC3C,oDAAoD;IACpD,SAAS,EAAE,OAAO,CAAC;IACnB,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;IACzB,yDAAyD;IACzD,iBAAiB,EAAE,OAAO,CAAC;IAC3B,6DAA6D;IAC7D,WAAW,EAAE,IAAI,GAAG,SAAS,CAAC;IAC9B,qDAAqD;IACrD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;
|
|
1
|
+
{"version":3,"file":"useFrappeAggregate.d.ts","sourceRoot":"","sources":["../src/useFrappeAggregate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAML,KAAK,eAAe,EACrB,MAAM,2BAA2B,CAAC;AAGnC,MAAM,MAAM,wBAAwB,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAE/E,gEAAgE;AAChE,MAAM,WAAW,yBAAyB;IACxC,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChG,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,wBAAwB,CAAC;IACrC,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACrE,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB;IACvC,iDAAiD;IACjD,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,oDAAoD;IACpD,MAAM,EAAE,oBAAoB,EAAE,GAAG,SAAS,CAAC;IAC3C,oDAAoD;IACpD,SAAS,EAAE,OAAO,CAAC;IACnB,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;IACzB,yDAAyD;IACzD,iBAAiB,EAAE,OAAO,CAAC;IAC3B,6DAA6D;IAC7D,WAAW,EAAE,IAAI,GAAG,SAAS,CAAC;IAC9B,qDAAqD;IACrD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAoID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,yBAAyB,GAAG,SAAS,EACjD,IAAI,EAAE,mBAAmB,GACxB,wBAAwB,CA+H1B"}
|