Files
trans-form-middleware/test/parseFilters.test.js
T
2026-08-07 10:28:46 +08:00

197 lines
5.5 KiB
JavaScript

import { describe, expect, it } from 'vitest';
import {
DEFAULT_OPERATOR_MAP,
mapOperators,
parseFilters,
splitMultiValue,
toTree,
} from '../src/index.js';
describe('mapOperators', () => {
it('maps known query codes', () => {
expect(mapOperators('EQ')).toBe('=');
expect(mapOperators('NOT_IN')).toBe('NOT IN');
});
it('allows custom overrides', () => {
expect(mapOperators('EQ', { EQ: 'equals' })).toBe('equals');
});
});
describe('splitMultiValue', () => {
it('splits IN values', () => {
expect(splitMultiValue('IN', 'a,b,c')).toEqual(['a', 'b', 'c']);
});
it('splits BETWEEN values', () => {
expect(splitMultiValue('BETWEEN', '2024-01-01,2024-12-31')).toEqual([
'2024-01-01',
'2024-12-31',
]);
});
});
describe('parseFilters', () => {
const allowedFields = ['name', 'status', 'tags', 'created'];
it('requires allowedFields', () => {
expect(() => parseFilters([], {})).toThrow(/allowedFields/);
});
it('parses basic conditions with logic pass-through', () => {
const input = [
{ field: 'name', value: 'xx', query: 'EQ' },
{ field: 'tags', value: 'a,b', query: 'IN', logic: 'OR' },
];
const out = parseFilters(input, { allowedFields });
expect(out).toEqual([
{
field: 'name',
query: 'EQ',
op: '=',
value: 'xx',
},
{
field: 'tags',
query: 'IN',
op: 'IN',
value: 'a,b',
values: ['a', 'b'],
logic: 'OR',
},
]);
});
it('preserves empty markers by default', () => {
const out = parseFilters(
[{ field: 'name', value: null, query: 'EQ', empty: true }],
{ allowedFields },
);
expect(out[0].empty).toBe(true);
});
it('skips disabled and hidden by default', () => {
const out = parseFilters(
[
{ field: 'name', value: 'a', query: 'EQ', disabled: true },
{ field: 'status', value: 'b', query: 'EQ', hidden: true },
{ field: 'tags', value: 'c', query: 'EQ' },
],
{ allowedFields },
);
expect(out).toHaveLength(1);
expect(out[0].field).toBe('tags');
});
it('can skip empty items when skipEmpty is true', () => {
const out = parseFilters(
[{ field: 'name', value: null, query: 'EQ', empty: true }],
{ allowedFields, skipEmpty: true },
);
expect(out).toEqual([]);
});
it('throws on unknown field by default', () => {
expect(() =>
parseFilters([{ field: 'unknown', value: 'x', query: 'EQ' }], {
allowedFields,
}),
).toThrow(/unknown field/);
});
it('supports flat mode', () => {
const out = parseFilters(
[{ field: 'name', value: 'x', query: 'EQ', logic: 'AND' }],
{ allowedFields, mode: 'flat' },
);
expect(out[0].logic).toBeUndefined();
});
});
describe('toTree', () => {
it('nests flat filters', () => {
const flat = [
{ field: 'a', query: 'EQ', op: '=', value: '1' },
{ field: 'b', query: 'EQ', op: '=', value: '2', logic: 'OR' },
];
const tree = toTree(flat);
expect(tree[0]).toMatchObject({
logic: 'OR',
children: expect.any(Array),
});
expect(tree[0].children).toHaveLength(2);
});
it('merges adjacent same-logic leaves into one node', () => {
const flat = [
{ field: 'a', query: 'EQ', op: '=', value: '1' },
{ field: 'b', query: 'EQ', op: '=', value: '2', logic: 'AND' },
{ field: 'c', query: 'EQ', op: '=', value: '3', logic: 'AND' },
];
const tree = toTree(flat);
expect(tree).toHaveLength(1);
expect(tree[0].logic).toBe('AND');
expect(tree[0].children).toHaveLength(3);
});
it('left-associates mixed logic: A AND B OR C -> (A AND B) OR C', () => {
const flat = [
{ field: 'a', query: 'EQ', op: '=', value: '1' },
{ field: 'b', query: 'EQ', op: '=', value: '2', logic: 'AND' },
{ field: 'c', query: 'EQ', op: '=', value: '3', logic: 'OR' },
];
const tree = toTree(flat);
expect(tree).toHaveLength(1);
expect(tree[0].logic).toBe('OR');
expect(tree[0].children).toHaveLength(2);
expect(tree[0].children[0].logic).toBe('AND');
expect(tree[0].children[0].children).toHaveLength(2);
});
it('supports explicit nested group via children key', () => {
const flat = [
{ field: 'a', query: 'EQ', op: '=', value: '1' },
{
logic: 'OR',
children: [
{ field: 'b', query: 'EQ', op: '=', value: '2' },
{ field: 'c', query: 'EQ', op: '=', value: '3', logic: 'AND' },
],
},
];
const tree = toTree(flat);
// a OR (b AND c)
expect(tree[0].logic).toBe('OR');
expect(tree[0].children).toHaveLength(2);
const inner = tree[0].children[1].children;
expect(inner).toHaveLength(1);
expect(inner[0].logic).toBe('AND');
expect(inner[0].children).toHaveLength(2);
});
it('supports group key shorthand', () => {
const flat = [
{ field: 'a', query: 'EQ', op: '=', value: '1' },
{
logic: 'OR',
group: [{ field: 'b', query: 'EQ', op: '=', value: '2' }],
},
];
const tree = toTree(flat);
expect(tree[0].logic).toBe('OR');
expect(tree[0].children).toHaveLength(2);
expect(tree[0].children[1].children).toHaveLength(1);
});
it('returns empty array for empty input', () => {
expect(toTree([])).toEqual([]);
expect(toTree(null)).toEqual([]);
});
});
describe('DEFAULT_OPERATOR_MAP', () => {
it('includes IS_NULL operators', () => {
expect(DEFAULT_OPERATOR_MAP.IS_NULL).toBe('IS NULL');
});
});