/**
* Behavioral tests for the imperative SvGridApi exposed via `onApiReady`.
*
* These mount the real render component in jsdom, capture the api
* object on mount, and exercise:
* - clearAllFilters (new)
* - getFilters (new)
* - getDisplayedRows (new)
* - setFilter (regression)
* - getData (regression)
*
* Bare-bones DOM env is enough; we never assert on rendered HTML, just on
* the object the component hands back.
*/
import { describe, expect, it } from 'vitest'
import { mount, unmount } from 'svelte'
import SvGrid from './SvGrid.svelte'
import {
columnFilteringFeature,
createCoreRowModel,
createFilteredRowModel,
createSortedRowModel,
rowSortingFeature,
sortFns,
tableFeatures,
} from './index'
import type { ColumnDef, SvGridApi } from './index'
type Row = { id: number; name: string; team: string; salary: number }
const features = tableFeatures({
columnFilteringFeature,
rowSortingFeature,
})
const sampleRows: Row[] = [
{ id: 1, name: 'Ada Lovelace', team: 'Research', salary: 142_000 },
{ id: 2, name: 'Grace Hopper', team: 'Compilers', salary: 158_000 },
{ id: 3, name: 'Alan Turing', team: 'Research', salary: 138_000 },
{ id: 4, name: 'Margaret Hamilton', team: 'Apollo', salary: 165_000 },
{ id: 5, name: 'Linus Torvalds', team: 'Kernel', salary: 175_000 },
]
const sampleColumns: ColumnDef[] = [
{ field: 'name', header: 'Name', width: 200 },
{ field: 'team', header: 'Team', width: 160 },
{ field: 'salary', header: 'Salary', width: 140 },
]
function mountGrid(): Promise<{
api: SvGridApi
destroy: () => void
}> {
return new Promise((resolveApi, rejectApi) => {
const target = document.createElement('div')
document.body.appendChild(target)
let capturedApi: SvGridApi | null = null
const app = mount(SvGrid, {
target,
props: {
data: sampleRows,
columns: sampleColumns,
features,
_rowModels: {
coreRowModel: createCoreRowModel(),
filteredRowModel: createFilteredRowModel(),
sortedRowModel: createSortedRowModel(sortFns),
},
rowHeight: 36,
containerHeight: 480,
virtualization: false,
onApiReady(api: SvGridApi) {
capturedApi = api
resolveApi({
api,
destroy: () => {
unmount(app)
target.remove()
},
})
},
} as any,
})
// Safety net: if onApiReady doesn't fire within a turn, fail loudly.
queueMicrotask(() => {
if (!capturedApi) rejectApi(new Error('onApiReady never fired'))
})
})
}
describe('SvGridApi - getData / getDisplayedRows', () => {
it('getData returns the full row set', async () => {
const { api, destroy } = await mountGrid()
try {
const data = api.getData()
expect(data.length).toBe(sampleRows.length)
expect(data.map((r) => (r as Row).id)).toEqual([1, 2, 3, 4, 5])
} finally {
destroy()
}
})
it('getDisplayedRows returns the post-pipeline visible rows', async () => {
const { api, destroy } = await mountGrid()
try {
// With no filters / sort, displayed rows match full data (no group rows).
const displayed = api.getDisplayedRows()
expect(displayed.length).toBe(sampleRows.length)
} finally {
destroy()
}
})
it('getDisplayedRows updates after setFilter narrows the set', async () => {
const { api, destroy } = await mountGrid()
try {
api.setFilter('team', { operator: 'contains', value: 'Research' })
// Wait one microtask so the derived row pipeline re-runs.
await Promise.resolve()
const displayed = api.getDisplayedRows()
// 2 rows have team === 'Research'
expect(displayed.length).toBe(2)
for (const r of displayed) {
expect((r as Row).team).toBe('Research')
}
} finally {
destroy()
}
})
})
describe('SvGridApi - getFilters / clearAllFilters', () => {
it('getFilters returns an empty object when nothing is filtered', async () => {
const { api, destroy } = await mountGrid()
try {
expect(api.getFilters()).toEqual({})
} finally {
destroy()
}
})
it('getFilters reflects the last setFilter call', async () => {
const { api, destroy } = await mountGrid()
try {
api.setFilter('team', { operator: 'equals', value: 'Apollo' })
await Promise.resolve()
const filters = api.getFilters()
expect(filters.team).toEqual({ operator: 'equals', value: 'Apollo' })
} finally {
destroy()
}
})
it('clearAllFilters wipes every column filter at once', async () => {
const { api, destroy } = await mountGrid()
try {
api.setFilter('team', { operator: 'contains', value: 'Research' })
api.setFilter('name', { operator: 'contains', value: 'Ada' })
await Promise.resolve()
expect(Object.keys(api.getFilters()).length).toBe(2)
api.clearAllFilters()
await Promise.resolve()
expect(api.getFilters()).toEqual({})
// The full row set is back.
const displayed = api.getDisplayedRows()
expect(displayed.length).toBe(sampleRows.length)
} finally {
destroy()
}
})
it('clearAllFilters returns a snapshot that is safe to mutate', async () => {
const { api, destroy } = await mountGrid()
try {
api.setFilter('team', { operator: 'contains', value: 'X' })
await Promise.resolve()
const snapshot = api.getFilters()
// Mutate the snapshot - internal state must not be affected.
snapshot.team!.value = 'mutated externally'
const fresh = api.getFilters()
expect(fresh.team!.value).toBe('X')
} finally {
destroy()
}
})
})