Skip to content

Commit 6702bf9

Browse files
committed
test: add frontend tests for filter condition and filter group
1 parent 8455983 commit 6702bf9

1 file changed

Lines changed: 304 additions & 0 deletions

File tree

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
/* Copyright 2026 Marimo. All rights reserved. */
2+
import { describe, expect, it } from "vitest";
3+
import {
4+
filterToFilterCondition,
5+
filtersToFilterGroup,
6+
Filter,
7+
} from "../filters";
8+
import {
9+
FilterConditionSchema,
10+
FilterGroupSchema,
11+
} from "@/plugins/impl/data-frames/schema";
12+
13+
describe("filterToFilterCondition", () => {
14+
it("returns empty array for undefined filter", () => {
15+
expect(filterToFilterCondition("col", undefined)).toEqual([]);
16+
});
17+
18+
it("handles is_null filter", () => {
19+
const result = filterToFilterCondition(
20+
"col",
21+
Filter.number({ operator: "is_null" }),
22+
);
23+
expect(result).toEqual([
24+
{
25+
column_id: "col",
26+
operator: "is_null",
27+
value: undefined,
28+
type: "condition",
29+
negate: false,
30+
},
31+
]);
32+
});
33+
34+
it("handles is_not_null filter", () => {
35+
const result = filterToFilterCondition(
36+
"col",
37+
Filter.number({ operator: "is_not_null" }),
38+
);
39+
expect(result).toEqual([
40+
{
41+
column_id: "col",
42+
operator: "is_not_null",
43+
value: undefined,
44+
type: "condition",
45+
negate: false,
46+
},
47+
]);
48+
});
49+
50+
it("handles number filter with min only", () => {
51+
const result = filterToFilterCondition("age", Filter.number({ min: 18 }));
52+
expect(result).toHaveLength(1);
53+
expect(result[0]).toMatchObject({
54+
column_id: "age",
55+
operator: ">=",
56+
value: 18,
57+
type: "condition",
58+
negate: false,
59+
});
60+
});
61+
62+
it("handles number filter with max only", () => {
63+
const result = filterToFilterCondition("age", Filter.number({ max: 65 }));
64+
expect(result).toHaveLength(1);
65+
expect(result[0]).toMatchObject({
66+
column_id: "age",
67+
operator: "<=",
68+
value: 65,
69+
type: "condition",
70+
negate: false,
71+
});
72+
});
73+
74+
it("handles number filter with min and max", () => {
75+
const result = filterToFilterCondition(
76+
"age",
77+
Filter.number({ min: 18, max: 65 }),
78+
);
79+
expect(result).toHaveLength(2);
80+
expect(result[0]).toMatchObject({ operator: ">=", value: 18 });
81+
expect(result[1]).toMatchObject({ operator: "<=", value: 65 });
82+
});
83+
84+
it("handles text filter", () => {
85+
const result = filterToFilterCondition(
86+
"name",
87+
Filter.text({ text: "foo", operator: "contains" }),
88+
);
89+
expect(result).toEqual([
90+
{
91+
column_id: "name",
92+
operator: "contains",
93+
value: "foo",
94+
type: "condition",
95+
negate: false,
96+
},
97+
]);
98+
});
99+
100+
it("handles boolean true filter", () => {
101+
const result = filterToFilterCondition(
102+
"active",
103+
Filter.boolean({ value: true }),
104+
);
105+
expect(result).toEqual([
106+
{
107+
column_id: "active",
108+
operator: "is_true",
109+
value: undefined,
110+
type: "condition",
111+
negate: false,
112+
},
113+
]);
114+
});
115+
116+
it("handles boolean false filter", () => {
117+
const result = filterToFilterCondition(
118+
"active",
119+
Filter.boolean({ value: false }),
120+
);
121+
expect(result).toEqual([
122+
{
123+
column_id: "active",
124+
operator: "is_false",
125+
value: undefined,
126+
type: "condition",
127+
negate: false,
128+
},
129+
]);
130+
});
131+
132+
it("handles select in filter", () => {
133+
const result = filterToFilterCondition(
134+
"status",
135+
Filter.select({ options: ["a", "b"], operator: "in" }),
136+
);
137+
expect(result).toEqual([
138+
{
139+
column_id: "status",
140+
operator: "in",
141+
value: ["a", "b"],
142+
type: "condition",
143+
negate: false,
144+
},
145+
]);
146+
});
147+
148+
it("handles date filter with min and max", () => {
149+
const min = new Date("2024-01-01");
150+
const max = new Date("2024-12-31");
151+
const result = filterToFilterCondition(
152+
"created",
153+
Filter.date({ min, max }),
154+
);
155+
expect(result).toHaveLength(2);
156+
expect(result[0]).toMatchObject({
157+
operator: ">=",
158+
value: min.toISOString(),
159+
});
160+
expect(result[1]).toMatchObject({
161+
operator: "<=",
162+
value: max.toISOString(),
163+
});
164+
});
165+
166+
it("every condition has type and negate fields", () => {
167+
const result = filterToFilterCondition(
168+
"col",
169+
Filter.number({ min: 1, max: 10 }),
170+
);
171+
for (const condition of result) {
172+
expect(condition).toHaveProperty("type", "condition");
173+
expect(condition).toHaveProperty("negate", false);
174+
}
175+
});
176+
});
177+
178+
describe("filtersToFilterGroup", () => {
179+
it("returns empty AND group for no filters", () => {
180+
const result = filtersToFilterGroup([]);
181+
expect(result).toEqual({
182+
type: "group",
183+
operator: "and",
184+
children: [],
185+
negate: false,
186+
});
187+
});
188+
189+
it("wraps single filter in AND group", () => {
190+
const result = filtersToFilterGroup([
191+
{ id: "age", value: Filter.number({ min: 18 }) },
192+
]);
193+
expect(result.type).toBe("group");
194+
expect(result.operator).toBe("and");
195+
expect(result.negate).toBe(false);
196+
expect(result.children).toHaveLength(1);
197+
});
198+
199+
it("wraps multiple filters in AND group", () => {
200+
const result = filtersToFilterGroup([
201+
{ id: "age", value: Filter.number({ min: 18 }) },
202+
{ id: "name", value: Filter.text({ text: "foo", operator: "contains" }) },
203+
]);
204+
expect(result.children).toHaveLength(2);
205+
expect(result.operator).toBe("and");
206+
});
207+
208+
it("flattens multi-condition filters", () => {
209+
const result = filtersToFilterGroup([
210+
{ id: "age", value: Filter.number({ min: 18, max: 65 }) },
211+
]);
212+
// min + max = 2 conditions
213+
expect(result.children).toHaveLength(2);
214+
});
215+
});
216+
217+
describe("schema validation", () => {
218+
it("FilterConditionSchema accepts valid condition", () => {
219+
const result = FilterConditionSchema.safeParse({
220+
column_id: "age",
221+
operator: ">=",
222+
value: 18,
223+
});
224+
expect(result.success).toBe(true);
225+
if (result.success) {
226+
expect(result.data.type).toBe("condition");
227+
expect(result.data.negate).toBe(false);
228+
}
229+
});
230+
231+
it("FilterConditionSchema defaults type and negate", () => {
232+
const result = FilterConditionSchema.safeParse({
233+
column_id: "age",
234+
operator: "==",
235+
value: 5,
236+
});
237+
expect(result.success).toBe(true);
238+
if (result.success) {
239+
expect(result.data.type).toBe("condition");
240+
expect(result.data.negate).toBe(false);
241+
}
242+
});
243+
244+
it("FilterConditionSchema accepts negate=true", () => {
245+
const result = FilterConditionSchema.safeParse({
246+
column_id: "age",
247+
operator: "==",
248+
value: 5,
249+
negate: true,
250+
});
251+
expect(result.success).toBe(true);
252+
if (result.success) {
253+
expect(result.data.negate).toBe(true);
254+
}
255+
});
256+
257+
it("FilterGroupSchema accepts valid group", () => {
258+
const result = FilterGroupSchema.safeParse({
259+
type: "group",
260+
operator: "and",
261+
children: [{ column_id: "age", operator: ">=", value: 18 }],
262+
});
263+
expect(result.success).toBe(true);
264+
});
265+
266+
it("FilterGroupSchema accepts nested groups", () => {
267+
const result = FilterGroupSchema.safeParse({
268+
type: "group",
269+
operator: "or",
270+
children: [
271+
{
272+
type: "group",
273+
operator: "and",
274+
children: [
275+
{ column_id: "a", operator: "==", value: 1 },
276+
{ column_id: "b", operator: ">", value: 2 },
277+
],
278+
},
279+
{ column_id: "c", operator: "==", value: 3 },
280+
],
281+
});
282+
expect(result.success).toBe(true);
283+
});
284+
285+
it("FilterGroupSchema rejects invalid operator", () => {
286+
const result = FilterGroupSchema.safeParse({
287+
type: "group",
288+
operator: "xor",
289+
children: [],
290+
});
291+
expect(result.success).toBe(false);
292+
});
293+
294+
it("FilterGroupSchema defaults fields", () => {
295+
const result = FilterGroupSchema.safeParse({});
296+
expect(result.success).toBe(true);
297+
if (result.success) {
298+
expect(result.data.type).toBe("group");
299+
expect(result.data.operator).toBe("and");
300+
expect(result.data.children).toEqual([]);
301+
expect(result.data.negate).toBe(false);
302+
}
303+
});
304+
});

0 commit comments

Comments
 (0)