From 447dc1b682cd4b4db39594e431aca85c6c8f01c2 Mon Sep 17 00:00:00 2001 From: dfedoryshchev Date: Fri, 2 Oct 2026 09:52:16 +0100 Subject: [PATCH] fix(table-core): compare equal values consistently in greaterThanOrEqualTo and lessThan --- .changeset/even-filters-compare.md | 5 +++ .../features/column-filtering/filterFns.ts | 19 +++++++++-- .../tests/unit/fns/filterFns.test.ts | 32 +++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 .changeset/even-filters-compare.md diff --git a/.changeset/even-filters-compare.md b/.changeset/even-filters-compare.md new file mode 100644 index 0000000000..9745a68490 --- /dev/null +++ b/.changeset/even-filters-compare.md @@ -0,0 +1,5 @@ +--- +'@tanstack/table-core': patch +--- + +Make `filterFn_greaterThanOrEqualTo` and `filterFn_lessThan` (and the endpoints of `filterFn_between` / `filterFn_betweenInclusive`) treat values as equal using the same normalization as `filterFn_greaterThan`, so `30` equals `'30'`, two `Date`s with the same time are equal, and strings that differ only in case are equal. diff --git a/packages/table-core/src/features/column-filtering/filterFns.ts b/packages/table-core/src/features/column-filtering/filterFns.ts index fc94971170..df40236230 100644 --- a/packages/table-core/src/features/column-filtering/filterFns.ts +++ b/packages/table-core/src/features/column-filtering/filterFns.ts @@ -216,7 +216,8 @@ export const filterFn_greaterThan = constructFilterFn({ /** * Keeps rows whose value is greater than or equal to the filter value. * - * Delegates to the built-in greater-than and strict-equality comparisons. + * Equality uses the same normalization as greater-than, so `30` equals `'30'` + * and two `Date`s with the same time are equal. */ export const filterFn_greaterThanOrEqualTo = constructFilterFn({ filter: (dataValue, filterValue) => @@ -487,12 +488,16 @@ function toDateTimestamp(value: any): number { return new Date(value).getTime() } -function compareGreaterThan(dataValue: any, filterValue: any): boolean { +function compareValues(dataValue: any, filterValue: any): number { const numericDataValue = dataValue == null ? 0 : +dataValue const numericFilterValue = Number(filterValue) if (!isNaN(numericFilterValue) && !isNaN(numericDataValue)) { return numericDataValue > numericFilterValue + ? 1 + : numericDataValue < numericFilterValue + ? -1 + : 0 } const stringDataValue = String(dataValue ?? '') @@ -500,10 +505,18 @@ function compareGreaterThan(dataValue: any, filterValue: any): boolean { .trim() const stringFilterValue = String(filterValue).toLowerCase().trim() return stringDataValue > stringFilterValue + ? 1 + : stringDataValue < stringFilterValue + ? -1 + : 0 +} + +function compareGreaterThan(dataValue: any, filterValue: any): boolean { + return compareValues(dataValue, filterValue) > 0 } function compareGreaterThanOrEqualTo(dataValue: any, filterValue: any) { - return dataValue === filterValue || compareGreaterThan(dataValue, filterValue) + return compareValues(dataValue, filterValue) >= 0 } function compareBetween( diff --git a/packages/table-core/tests/unit/fns/filterFns.test.ts b/packages/table-core/tests/unit/fns/filterFns.test.ts index 8c0c359a4e..eedd004418 100644 --- a/packages/table-core/tests/unit/fns/filterFns.test.ts +++ b/packages/table-core/tests/unit/fns/filterFns.test.ts @@ -512,6 +512,38 @@ describe('Filter Functions', () => { expect(result).toBe(true) }) }) + describe('equal values compared the way greaterThan compares them', () => { + const makeRow = (value: unknown) => ({ getValue: () => value }) as any + + it('treats a numeric string filter value as equal to the number', () => { + const row = mockRows[0]! // age 30 + expect(filterFn_greaterThanOrEqualTo(row, 'age', '30')).toBe(true) + expect(filterFn_lessThan(row, 'age', '30')).toBe(false) + }) + + it('treats two Date instances with the same time as equal', () => { + const row = makeRow(new Date(2024, 0, 15)) + const filterValue = new Date(2024, 0, 15) + expect(filterFn_greaterThanOrEqualTo(row, 'date', filterValue)).toBe( + true, + ) + expect(filterFn_lessThan(row, 'date', filterValue)).toBe(false) + }) + + it('treats strings that differ only in case as equal', () => { + const row = mockRows[0]! // firstName 'John' + expect(filterFn_greaterThanOrEqualTo(row, 'firstName', 'john')).toBe( + true, + ) + expect(filterFn_lessThan(row, 'firstName', 'john')).toBe(false) + }) + + it('applies the same equality to range endpoints', () => { + const row = mockRows[0]! // age 30 + expect(filterFns.betweenInclusive(row, 'age', ['30', '40'])).toBe(true) + expect(filterFns.between(row, 'age', ['20', '30'])).toBe(false) + }) + }) }) describe('Range Filters', () => {