Skip to content

Commit 211cc62

Browse files
committed
test: add missing Fns tests and add test coverage report
1 parent 47af44c commit 211cc62

11 files changed

Lines changed: 634 additions & 187 deletions

File tree

.github/workflows/pr.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@ jobs:
4040
- name: Stop Nx Agents
4141
if: ${{ always() }}
4242
run: npx nx-cloud stop-all-agents
43+
coverage:
44+
name: Coverage Report
45+
runs-on: ubuntu-latest
46+
steps:
47+
- name: Checkout
48+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
49+
with:
50+
persist-credentials: false
51+
- name: Setup Tools
52+
uses: tanstack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3 # main
53+
- name: Collect Coverage
54+
run: pnpm --filter "@tanstack/table-core" test:coverage
55+
- name: Write Coverage Summary
56+
run: node scripts/coverage-summary.mjs packages/table-core/coverage/coverage-summary.json "table-core Coverage" >> "$GITHUB_STEP_SUMMARY"
4357
preview:
4458
name: Preview
4559
runs-on: ubuntu-latest

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"@tanstack/typedoc-config": "0.3.3",
6767
"@testing-library/jest-dom": "^6.9.1",
6868
"@types/node": "^26.0.0",
69+
"@vitest/coverage-v8": "4.1.9",
6970
"eslint": "^10.5.0",
7071
"jsdom": "^29.1.1",
7172
"knip": "^6.16.1",

packages/table-core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"clean": "rimraf ./build && rimraf ./dist",
7171
"lint:fix": "eslint ./src --fix",
7272
"test:eslint": "eslint ./src",
73+
"test:coverage": "vitest run --coverage",
7374
"test:lib": "vitest",
7475
"test:lib:dev": "pnpm test:lib --watch",
7576
"test:types": "tsc && tsc -p tests/tsconfig.declaration-emit.json",

packages/table-core/tests/unit/fns/aggregationFns.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { describe, expect, it } from 'vitest'
22
import {
3+
aggregationFn_count,
34
aggregationFn_extent,
45
aggregationFn_max,
56
aggregationFn_mean,
7+
aggregationFn_median,
68
aggregationFn_min,
79
aggregationFn_sum,
10+
aggregationFn_unique,
11+
aggregationFn_uniqueCount,
812
} from '../../../src'
913

1014
function makeRows(values: Array<unknown>) {
@@ -45,3 +49,53 @@ describe('Aggregation Functions', () => {
4549
expect(aggregationFn_mean('value', rows)).toBe(2)
4650
})
4751
})
52+
53+
describe('median', () => {
54+
it('returns undefined for empty groups', () => {
55+
expect(aggregationFn_median('value', makeRows([]) as any)).toBeUndefined()
56+
})
57+
58+
it('returns the single value for one-row groups', () => {
59+
expect(aggregationFn_median('value', makeRows([5]) as any)).toBe(5)
60+
})
61+
62+
it('returns the middle value for odd-length groups', () => {
63+
expect(aggregationFn_median('value', makeRows([3, 1, 2]) as any)).toBe(2)
64+
})
65+
66+
it('averages the two middle values for even-length groups', () => {
67+
expect(aggregationFn_median('value', makeRows([4, 1, 3, 2]) as any)).toBe(
68+
2.5,
69+
)
70+
})
71+
72+
it('returns undefined when any value is not a number', () => {
73+
expect(
74+
aggregationFn_median('value', makeRows([1, '2', 3]) as any),
75+
).toBeUndefined()
76+
})
77+
})
78+
79+
describe('unique / uniqueCount', () => {
80+
it('collects distinct values in first-seen order', () => {
81+
expect(
82+
aggregationFn_unique('value', makeRows(['a', 'b', 'a', 'c']) as any),
83+
).toEqual(['a', 'b', 'c'])
84+
})
85+
86+
it('counts distinct values with Set semantics', () => {
87+
expect(
88+
aggregationFn_uniqueCount(
89+
'value',
90+
makeRows(['a', 'b', 'a', null, null]) as any,
91+
),
92+
).toBe(3)
93+
})
94+
})
95+
96+
describe('count', () => {
97+
it('counts leaf rows and ignores the column id', () => {
98+
expect(aggregationFn_count('anything', makeRows([1, 2, 3]) as any)).toBe(3)
99+
expect(aggregationFn_count('anything', makeRows([]) as any)).toBe(0)
100+
})
101+
})

packages/table-core/tests/unit/fns/filterFns.test.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
filterFn_equalsStringSensitive,
88
filterFn_greaterThan,
99
filterFn_greaterThanOrEqualTo,
10+
filterFn_inNumberRange,
1011
filterFn_includesString,
1112
filterFn_includesStringSensitive,
1213
filterFn_lessThan,
@@ -571,7 +572,7 @@ describe('Filter Functions', () => {
571572
})
572573

573574
describe('filterFns.between.autoRemove', () => {
574-
const autoRemove = filterFns.between.autoRemove!
575+
const autoRemove = filterFns.between.autoRemove
575576

576577
it('should auto-remove when both endpoints are undefined', () => {
577578
expect(autoRemove([undefined, undefined])).toBe(true)
@@ -595,7 +596,7 @@ describe('Filter Functions', () => {
595596
})
596597

597598
describe('filterFns.betweenInclusive.autoRemove', () => {
598-
const autoRemove = filterFns.betweenInclusive.autoRemove!
599+
const autoRemove = filterFns.betweenInclusive.autoRemove
599600

600601
it('should auto-remove when both endpoints are undefined', () => {
601602
expect(autoRemove([undefined, undefined])).toBe(true)
@@ -727,7 +728,7 @@ describe('Filter Functions', () => {
727728
})
728729

729730
describe('filterFns.arrHas.autoRemove', () => {
730-
const autoRemove = filterFns.arrHas.autoRemove!
731+
const autoRemove = filterFns.arrHas.autoRemove
731732

732733
it('should auto-remove when the filter value is undefined', () => {
733734
expect(autoRemove(undefined)).toBe(true)
@@ -747,3 +748,50 @@ describe('Filter Functions', () => {
747748
})
748749
})
749750
})
751+
752+
describe('Number Range Filters', () => {
753+
describe('filterFn_inNumberRange', () => {
754+
it('should match values inclusively within the range', () => {
755+
const row = mockRows[0]! // age 30
756+
757+
expect(filterFn_inNumberRange(row, 'age', [29, 31])).toBe(true)
758+
expect(filterFn_inNumberRange(row, 'age', [30, 30])).toBe(true)
759+
expect(filterFn_inNumberRange(row, 'age', [31, 40])).toBe(false)
760+
expect(filterFn_inNumberRange(row, 'age', [10, 29])).toBe(false)
761+
})
762+
763+
it('should coerce string endpoints in resolveFilterValue', () => {
764+
expect(filterFn_inNumberRange.resolveFilterValue(['29', '31'])).toEqual([
765+
29, 31,
766+
])
767+
})
768+
769+
it('should treat null and non-numeric endpoints as open-ended', () => {
770+
expect(filterFn_inNumberRange.resolveFilterValue([null, 31])).toEqual([
771+
-Infinity,
772+
31,
773+
])
774+
expect(filterFn_inNumberRange.resolveFilterValue([29, 'abc'])).toEqual([
775+
29,
776+
Infinity,
777+
])
778+
})
779+
780+
it('should swap reversed ranges', () => {
781+
expect(filterFn_inNumberRange.resolveFilterValue([31, 29])).toEqual([
782+
29, 31,
783+
])
784+
})
785+
786+
it('should auto-remove only fully empty ranges', () => {
787+
const autoRemove = filterFn_inNumberRange.autoRemove
788+
789+
expect(autoRemove(undefined)).toBe(true)
790+
expect(autoRemove([null, null])).toBe(true)
791+
expect(autoRemove(['', ''])).toBe(true)
792+
expect(autoRemove([5, undefined])).toBe(false)
793+
expect(autoRemove([undefined, 10])).toBe(false)
794+
expect(autoRemove([0, 10])).toBe(false)
795+
})
796+
})
797+
})

packages/table-core/tests/unit/fns/sortFns.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, it } from 'vitest'
22
import {
3+
reSplitAlphaNumeric,
34
sortFn_alphanumeric,
45
sortFn_alphanumericCaseSensitive,
56
sortFn_basic,
@@ -276,3 +277,25 @@ describe('sortFn_datetime', () => {
276277
expect(cmp(sortFn_datetime, new Date(Number.NaN), 1000)).toBe(0)
277278
})
278279
})
280+
281+
describe('reSplitAlphaNumeric', () => {
282+
it('should keep plain text as a single segment', () => {
283+
expect('apple'.split(reSplitAlphaNumeric)).toEqual(['apple'])
284+
})
285+
286+
it('should capture numeric runs as their own segments', () => {
287+
expect('item10'.split(reSplitAlphaNumeric)).toEqual(['item', '10', ''])
288+
expect('a1b22c'.split(reSplitAlphaNumeric)).toEqual([
289+
'a',
290+
'1',
291+
'b',
292+
'22',
293+
'c',
294+
])
295+
})
296+
297+
it('should handle leading digits and empty strings', () => {
298+
expect('10abc'.split(reSplitAlphaNumeric)).toEqual(['', '10', 'abc'])
299+
expect(''.split(reSplitAlphaNumeric)).toEqual([''])
300+
})
301+
})
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { constructTable, createColumnHelper } from '../../../src'
3+
import { testFeatures } from '../../fixtures/features'
4+
5+
type Person = {
6+
firstName: string
7+
lastName: string
8+
}
9+
10+
const features = testFeatures({})
11+
12+
const helper = createColumnHelper<typeof features, Person>()
13+
14+
describe('createColumnHelper', () => {
15+
it('accessor should create an accessorKey column def from a key', () => {
16+
const columnDef = helper.accessor('firstName', { header: 'First Name' })
17+
18+
expect(columnDef).toEqual({
19+
accessorKey: 'firstName',
20+
header: 'First Name',
21+
})
22+
})
23+
24+
it('accessor should create an accessorFn column def from a function', () => {
25+
const accessorFn = (row: Person) => row.lastName.toUpperCase()
26+
const columnDef = helper.accessor(accessorFn, { id: 'lastName' })
27+
28+
expect(columnDef).toEqual({
29+
accessorFn,
30+
id: 'lastName',
31+
})
32+
})
33+
34+
it('display and group should return the column def unchanged', () => {
35+
const displayDef = { id: 'actions', header: 'Actions' }
36+
const groupDef = {
37+
id: 'name',
38+
header: 'Name',
39+
columns: helper.columns([helper.accessor('firstName', {})]),
40+
}
41+
42+
expect(helper.display(displayDef)).toBe(displayDef)
43+
expect(helper.group(groupDef)).toBe(groupDef)
44+
})
45+
46+
it('columns should return the array unchanged', () => {
47+
const columns = [
48+
helper.accessor('firstName', {}),
49+
helper.accessor('lastName', { id: 'lastName' }),
50+
]
51+
52+
expect(helper.columns(columns)).toBe(columns)
53+
})
54+
55+
it('helper-built column defs should resolve values on a real table', () => {
56+
const columns = helper.columns([
57+
helper.accessor('firstName', {}),
58+
helper.accessor((row) => row.lastName.toUpperCase(), { id: 'shouty' }),
59+
helper.display({ id: 'actions', header: 'Actions' }),
60+
])
61+
const table = constructTable<typeof features, Person>({
62+
features,
63+
columns,
64+
data: [{ firstName: 'Tanner', lastName: 'Linsley' }],
65+
})
66+
const row = table.getRowModel().rows[0]!
67+
68+
expect(table.getAllLeafColumns().map((column) => column.id)).toEqual([
69+
'firstName',
70+
'shouty',
71+
'actions',
72+
])
73+
expect(row.getValue('firstName')).toBe('Tanner')
74+
expect(row.getValue('shouty')).toBe('LINSLEY')
75+
expect(row.getValue('actions')).toBeUndefined()
76+
})
77+
})

0 commit comments

Comments
 (0)