mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-22 08:53:46 +08:00
@@ -1,5 +1,44 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { filterRowsByGridConditions } from './dataGridClientFilter';
|
||||
import { countGridColumnValues, filterRowsByGridConditions } from './dataGridClientFilter';
|
||||
|
||||
describe('countGridColumnValues', () => {
|
||||
it('counts nullish, empty, scalar, and stable complex values separately', () => {
|
||||
const rows = [
|
||||
{ value: null },
|
||||
{ value: undefined },
|
||||
{ value: '' },
|
||||
{ value: 1 },
|
||||
{ value: '1' },
|
||||
{ value: true },
|
||||
{ value: { b: 2, a: 1 } },
|
||||
{ value: { a: 1, b: 2 } },
|
||||
{ value: ['x', 1] },
|
||||
];
|
||||
|
||||
expect(countGridColumnValues(rows, 'value')).toEqual([
|
||||
{ key: 'nullish', display: '', kind: 'nullish', count: 2 },
|
||||
{ key: 'object:{"a":1,"b":2}', display: '{"a":1,"b":2}', kind: 'value', count: 2 },
|
||||
{ key: 'empty', display: '', kind: 'empty', count: 1 },
|
||||
{ key: 'object:["x",1]', display: '["x",1]', kind: 'value', count: 1 },
|
||||
{ key: 'number:1', display: '1', kind: 'value', count: 1 },
|
||||
{ key: 'string:1', display: '1', kind: 'value', count: 1 },
|
||||
{ key: 'boolean:true', display: 'true', kind: 'value', count: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('sorts by count, display text, and stable key', () => {
|
||||
expect(countGridColumnValues([
|
||||
{ value: '10' },
|
||||
{ value: '2' },
|
||||
{ value: 2 },
|
||||
{ value: '10' },
|
||||
], 'value').map(({ key, count }) => ({ key, count }))).toEqual([
|
||||
{ key: 'string:10', count: 2 },
|
||||
{ key: 'number:2', count: 1 },
|
||||
{ key: 'string:2', count: 1 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterRowsByGridConditions', () => {
|
||||
const rows = [
|
||||
@@ -23,6 +62,17 @@ describe('filterRowsByGridConditions', () => {
|
||||
]).map((row) => row.id)).toEqual([2]);
|
||||
});
|
||||
|
||||
it('uses the stable complex display text for equality filtering', () => {
|
||||
const rows = [
|
||||
{ id: 1, value: { b: 2, a: 1 } },
|
||||
{ id: 2, value: { a: 1, b: 3 } },
|
||||
];
|
||||
|
||||
expect(filterRowsByGridConditions(rows, [
|
||||
{ column: 'value', op: '=', value: '{"a":1,"b":2}', enabled: true },
|
||||
]).map((row) => row.id)).toEqual([1]);
|
||||
});
|
||||
|
||||
it('supports null checks and OR logic', () => {
|
||||
expect(filterRowsByGridConditions(rows, [
|
||||
{ column: 'status', op: 'IS_NULL', enabled: true },
|
||||
|
||||
@@ -1,17 +1,94 @@
|
||||
import type { FilterCondition } from './sql';
|
||||
import { parseListValues } from './sql';
|
||||
|
||||
export type DataGridColumnValueCount = {
|
||||
key: string;
|
||||
display: string;
|
||||
kind: 'nullish' | 'empty' | 'value';
|
||||
count: number;
|
||||
};
|
||||
|
||||
const stableSerializeComplexValue = (value: unknown, seen = new WeakSet<object>()): string => {
|
||||
if (value === null) return 'null';
|
||||
if (value === undefined) return 'undefined';
|
||||
if (typeof value === 'string') return JSON.stringify(value);
|
||||
if (typeof value === 'number') {
|
||||
if (Number.isNaN(value)) return 'NaN';
|
||||
if (value === Number.POSITIVE_INFINITY) return 'Infinity';
|
||||
if (value === Number.NEGATIVE_INFINITY) return '-Infinity';
|
||||
if (Object.is(value, -0)) return '-0';
|
||||
return String(value);
|
||||
}
|
||||
if (typeof value === 'boolean') return String(value);
|
||||
if (typeof value === 'bigint') return `${value}n`;
|
||||
if (typeof value === 'symbol' || typeof value === 'function') return String(value);
|
||||
|
||||
if (seen.has(value)) return '"[Circular]"';
|
||||
seen.add(value);
|
||||
try {
|
||||
if (Array.isArray(value)) {
|
||||
return `[${value.map((item) => stableSerializeComplexValue(item, seen)).join(',')}]`;
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return JSON.stringify(Number.isNaN(value.getTime()) ? String(value) : value.toISOString());
|
||||
}
|
||||
const entries = Object.keys(value)
|
||||
.sort()
|
||||
.map((key) => `${JSON.stringify(key)}:${stableSerializeComplexValue((value as Record<string, unknown>)[key], seen)}`);
|
||||
return `{${entries.join(',')}}`;
|
||||
} finally {
|
||||
seen.delete(value);
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeColumnValueCount = (value: unknown): Omit<DataGridColumnValueCount, 'count'> => {
|
||||
if (value === null || value === undefined) {
|
||||
return { key: 'nullish', display: '', kind: 'nullish' };
|
||||
}
|
||||
if (value === '') {
|
||||
return { key: 'empty', display: '', kind: 'empty' };
|
||||
}
|
||||
|
||||
const valueType = typeof value;
|
||||
const display = valueType === 'object'
|
||||
? stableSerializeComplexValue(value)
|
||||
: String(value);
|
||||
return {
|
||||
key: `${valueType}:${display}`,
|
||||
display,
|
||||
kind: 'value',
|
||||
};
|
||||
};
|
||||
|
||||
export const countGridColumnValues = <T extends Record<string, any>>(
|
||||
rows: T[],
|
||||
columnName: string,
|
||||
): DataGridColumnValueCount[] => {
|
||||
const counts = new Map<string, DataGridColumnValueCount>();
|
||||
rows.forEach((row) => {
|
||||
const normalized = normalizeColumnValueCount(row?.[columnName]);
|
||||
const existing = counts.get(normalized.key);
|
||||
if (existing) {
|
||||
existing.count += 1;
|
||||
} else {
|
||||
counts.set(normalized.key, { ...normalized, count: 1 });
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(counts.values()).sort((left, right) => (
|
||||
right.count - left.count
|
||||
|| left.display.localeCompare(right.display, undefined, { numeric: true, sensitivity: 'base' })
|
||||
|| left.key.localeCompare(right.key)
|
||||
));
|
||||
};
|
||||
|
||||
const normalizeCellText = (value: unknown): string => {
|
||||
if (value === null || value === undefined) return '';
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
|
||||
return String(value);
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
return stableSerializeComplexValue(value);
|
||||
};
|
||||
|
||||
const isNullishCell = (value: unknown): boolean => value === null || value === undefined;
|
||||
|
||||
@@ -37,6 +37,7 @@ export type DetachedQueryResultSnapshot = {
|
||||
readOnly: boolean;
|
||||
showRowNumberColumn?: boolean;
|
||||
truncated?: boolean;
|
||||
pinned?: boolean;
|
||||
};
|
||||
|
||||
export type DetachedQueryResultWindow = DetachedWindowBounds & {
|
||||
|
||||
Reference in New Issue
Block a user