import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { act, fireEvent, render, screen } from '@testing-library/react'
import { Provider } from '../provider/widget-provider'
import { clearAllWidgetStores, getWidgetStore } from '../stores'
import { Table } from './table'
import type { TableColumn, TableWidgetState } from './types'
beforeEach(() => clearAllWidgetStores())
afterEach(() => clearAllWidgetStores())
const COLUMNS: TableColumn[] = [
{ id: 'name', label: 'Name', sortable: true },
{ id: 'score', label: 'Score', sortable: true },
]
const DATA = Array.from({ length: 23 }, (_, i) => ({
id: i + 1,
name: `Row ${String(i + 1).padStart(2, '0')}`,
score: ((i * 7) % 11) + 1,
}))
describe('
', () => {
it('paginates rows from the store, defaulting to page 0', () => {
render(
,
)
expect(screen.getByText('Row 01')).toBeTruthy()
expect(screen.getByText('Row 05')).toBeTruthy()
expect(screen.queryByText('Row 06')).toBeNull()
})
it('writes sort to the store and resets page=0', () => {
render(
,
)
// Move to page 2 first.
getWidgetStore('t2').setState({ page: 2 } as Partial)
fireEvent.click(screen.getByText('Score'))
const state = getWidgetStore('t2').getState() as TableWidgetState
expect(state.sort).toEqual({ columnId: 'score', direction: 'asc' })
expect(state.page).toBe(0)
})
it('honors columnOrder from the store', () => {
render(
,
)
act(() => {
getWidgetStore('t3').setState({
columnOrder: ['score', 'name'],
} as Partial)
})
const headers = screen.getAllByRole('columnheader')
expect(headers[0]?.textContent).toContain('Score')
expect(headers[1]?.textContent).toContain('Name')
})
it('in remote mode renders data as-is and uses external `total` for the footer', () => {
// Only 3 rows worth of "current page" data, but the server says
// there are 87 total. The bridge must not re-sort / re-slice.
const PAGE = DATA.slice(0, 3)
render(
,
)
expect(screen.getByText('Row 01')).toBeTruthy()
expect(screen.getByText('Row 03')).toBeTruthy()
expect(screen.queryByText('Row 04')).toBeNull()
// Pagination footer surfaces the remote total, not data.length.
expect(screen.getByText(/of 87/)).toBeTruthy()
})
it('fires onSortChange / onPageChange / onPageSizeChange after store writes', () => {
const onSortChange = vi.fn()
const onPageChange = vi.fn()
const onPageSizeChange = vi.fn()
render(
,
)
fireEvent.click(screen.getByText('Score'))
expect(onSortChange).toHaveBeenCalledWith({
columnId: 'score',
direction: 'asc',
})
})
it('selection prop drives the row checkbox state and onSelectionChange fires', () => {
const onSelectionChange = vi.fn()
render(
,
)
const cb = screen.getByLabelText('Select row 2')
expect((cb as HTMLInputElement).checked).toBe(true)
fireEvent.click(screen.getByLabelText('Select row 1'))
expect(onSelectionChange).toHaveBeenCalledWith([2, 1])
})
})