// @vitest-environment happy-dom // // DynamicTable classic pagination ('pages', the default mode): // 1. The pager footer renders; clicking page 2 issues a NEW query with // page=2 and REPLACES the visible rows (no accumulation). // 2. Changing the filters resets back to page 1. // 3. The chosen page size persists per table in localStorage and is adopted // on the next mount (over the model's server default). import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' vi.mock('@tanstack/react-router', () => ({ useNavigate: () => () => {}, })) const I18N = { t: (_k: string, o?: { defaultValue?: string; count?: number; total?: number }) => { if (o?.defaultValue && o.count != null && o.total != null) { return o.defaultValue.replace('{{count}}', String(o.count)).replace('{{total}}', String(o.total)) } return o?.defaultValue ?? _k }, i18n: { language: 'es' }, } vi.mock('react-i18next', () => ({ useTranslation: () => I18N })) import { act } from '@testing-library/react' import { emitRecordMutation } from '../record-mutation-events' import { DynamicTable } from '../dynamic-table' import { ApiProvider, type ApiClient } from '../api-context' import { useMetadataCache } from '../metadata-cache' import type { TableMetadata } from '../types' afterEach(cleanup) function meta(): TableMetadata { return { title: 'Issues', endpoint: '/data/issue', group_by: 'stage', columns: [ { key: 'title', label: 'Title', type: 'text', sortable: true, filterable: false, searchable: true }, ], actions: [], perPageOptions: [10, 20, 50], defaultPerPage: 10, searchPlaceholder: 'Buscar...', enableCRUDActions: false, hasActions: false, } } function fakeApi(total: number): ApiClient { const ok = (data: unknown) => ({ data: { success: true, data, meta: { total } } }) const page = (n: number, size: number) => { const start = (n - 1) * size const rows: Array<{ id: number; title: string }> = [] for (let i = start; i < Math.min(start + size, total); i++) { rows.push({ id: i + 1, title: `Issue ${i + 1}` }) } return rows } return { get: vi.fn(async (url: string, cfg?: any) => { if (url.startsWith('/metadata/table/')) return ok(meta()) if (url.endsWith('/facets')) return ok([]) const p = cfg?.params?.page ?? 1 const size = cfg?.params?.per_page ?? 10 return ok(page(p, size)) }), post: vi.fn(async () => ok(null)), put: vi.fn(async () => ok(null)), delete: vi.fn(async () => ok(null)), } } function dataCalls(api: ApiClient) { return (api.get as any).mock.calls.filter((c: any[]) => c[0] === '/data/issue') } describe('DynamicTable reloads on record mutation events (#1020)', () => { beforeEach(() => { localStorage.clear() sessionStorage.clear() useMetadataCache.getState().setMetadata('issue', meta()) }) it('repeats the GET when a record of the same model (any case) is mutated', async () => { const api = fakeApi(5) render( , ) await waitFor(() => expect(dataCalls(api).length).toBeGreaterThan(0)) await new Promise((r) => setTimeout(r, 450)) const before = dataCalls(api).length act(() => emitRecordMutation('ISSUE', 'create')) await waitFor(() => expect(dataCalls(api).length).toBeGreaterThan(before)) }) it('ignores mutations of other models', async () => { const api = fakeApi(5) render( , ) await waitFor(() => expect(dataCalls(api).length).toBeGreaterThan(0)) await new Promise((r) => setTimeout(r, 450)) const before = dataCalls(api).length act(() => emitRecordMutation('other_model', 'delete')) await new Promise((r) => setTimeout(r, 500)) expect(dataCalls(api).length).toBe(before) }) })