Repository navigation
fix(table-core): treat null endpoints as open-ended in filterFn_between
#6660
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -71,7 +71,7 @@ function _createFacetedUniqueValues< | |
| .map((column) => column.id) | ||
| : [columnId] | ||
|
|
||
| const facetedUniqueValues = new Map<any, number>() | ||
| const facetedUniqueValues = new ArrayKeyMap() | ||
|
|
||
| for (let i = 0; i < flatRows.length; i++) { | ||
| for (let c = 0; c < columnIds.length; c++) { | ||
|
|
@@ -94,3 +94,26 @@ function _createFacetedUniqueValues< | |
|
|
||
| return facetedUniqueValues | ||
| } | ||
|
|
||
| /** | ||
| * A Map subclass that normalises array keys so that: | ||
| * - Two distinct array instances with the same contents map to the same entry. | ||
| * - A serialised string like `'["a","b"]'` never collides with an array `['a','b']`. | ||
| * | ||
| * Internally arrays are stored under a null-byte-prefixed JSON key, which cannot | ||
| * appear in ordinary strings. | ||
| */ | ||
| class ArrayKeyMap extends Map<any, number> { | ||
| static toKey(key: unknown): unknown { | ||
| return Array.isArray(key) ? '\0' + JSON.stringify(key) : key | ||
| } | ||
| override get(key: unknown): number | undefined { | ||
| return super.get(ArrayKeyMap.toKey(key)) | ||
| } | ||
| override set(key: unknown, value: number): this { | ||
| return super.set(ArrayKeyMap.toKey(key), value) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preserve array values in the returned Map's keys.
|
||
| } | ||
| override has(key: unknown): boolean { | ||
| return super.has(ArrayKeyMap.toKey(key)) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ import type { ColumnDef } from '../../../../src' | |
| type Person = { | ||
| firstName: string | ||
| status: string | undefined | ||
| tags?: Array<string> | ||
| } | ||
|
|
||
| const features = testFeatures({ | ||
|
|
@@ -45,12 +46,16 @@ const columns: Array<ColumnDef<typeof features, Person, any>> = [ | |
| filterFn: 'equalsString', | ||
| id: 'status', | ||
| }, | ||
| { | ||
| accessorKey: 'tags', | ||
| id: 'tags', | ||
| }, | ||
| ] | ||
|
|
||
| const data: Array<Person> = [ | ||
| { firstName: 'Alice', status: 'active' }, | ||
| { firstName: 'Bob', status: undefined }, | ||
| { firstName: 'Carol', status: 'active' }, | ||
| { firstName: 'Alice', status: 'active', tags: ['a', 'b'] }, | ||
| { firstName: 'Bob', status: undefined, tags: ['a', 'b'] }, | ||
| { firstName: 'Carol', status: 'active', tags: ['c'] }, | ||
| ] | ||
|
|
||
| function makeTable(columnFilters: Array<{ id: string; value: unknown }>) { | ||
|
|
@@ -81,8 +86,8 @@ describe('column faceting row model', () => { | |
| expect( | ||
| firstNameColumn.getFacetedRowModel().rows.map((row) => row.original), | ||
| ).toEqual([ | ||
| { firstName: 'Alice', status: 'active' }, | ||
| { firstName: 'Carol', status: 'active' }, | ||
| { firstName: 'Alice', status: 'active', tags: ['a', 'b'] }, | ||
| { firstName: 'Carol', status: 'active', tags: ['c'] }, | ||
| ]) | ||
| }) | ||
|
|
||
|
|
@@ -98,6 +103,20 @@ describe('column faceting row model', () => { | |
| ) | ||
| }) | ||
|
|
||
| it('counts array cell values by content rather than reference', () => { | ||
| const table = makeTable([]) | ||
| const tagsColumn = table.getColumn('tags')! | ||
|
|
||
| // ['a','b'] appears in two rows with different array references — | ||
| // without JSON.stringify as the map key both would get count 1 | ||
| expect(tagsColumn.getFacetedUniqueValues()).toEqual( | ||
| new Map<any, number>([ | ||
| [JSON.stringify(['a', 'b']), 2], | ||
| [JSON.stringify(['c']), 1], | ||
| ]), | ||
|
Comment on lines
+112
to
+116
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct the failing Map assertion. The actual keys contain
|
||
| ) | ||
| }) | ||
|
|
||
| it('caches faceted factory functions per column and global context', () => { | ||
| const facetedRowModel = vi.fn( | ||
| (table: any, _columnId: string) => () => table.getPreFilteredRowModel(), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not merge arrays with different element values.
Based on learnings,
[undefined]and[null]both serialize as[null]. This implementation therefore combines their counts into one facet, although their contents differ. The same problem affects[NaN]and[null]. Use a normalization strategy that preserves element types and values. (262.ecma-international.org)Source: Learnings
Handle arrays that JSON cannot serialize.
If an accessor returns
[1n]or a cyclic array,JSON.stringifythrowsTypeError. The exception escapes the initialfacetedUniqueValues.get(value)call and aborts the complete facet calculation. These values worked as ordinary Map keys before this change. Provide a non-throwing key strategy or an identity-based fallback for unsupported values. (262.ecma-international.org)