import { useState } from 'react';
import { Table, TableProps } from './Tablev2.component';
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
jest.mock('./TableUtils', () => ({
...jest.requireActual('./TableUtils'),
// since convertRemToPixels rely on getComputedStyle(document.documentElement) which is not available in jest
// we mock it
convertRemToPixels: () => 12,
}));
jest.mock('react-virtualized-auto-sizer', () => ({ children }) => {
return children({
height: 600,
width: 600,
});
});
const data = [
{
firstName: 'Sotiria',
lastName: 'Agathangelou',
age: 90,
health: 'healthy',
},
{
firstName: 'Stefania',
lastName: 'Evgenios',
age: 27,
health: 'warning',
},
{
firstName: 'Yohann',
lastName: 'Rodolph',
age: 27,
health: 'critical',
},
{
firstName: 'Ninette',
lastName: 'Caroline',
age: 31,
health: 'healthy',
},
];
const columns: TableProps['columns'] = [
{
Header: 'First Name',
accessor: 'firstName',
},
{
Header: 'Last Name',
accessor: 'lastName',
},
{
Header: 'Age',
accessor: 'age',
},
{
Header: 'Health',
accessor: 'health',
sortType: 'health',
},
];
describe('TableV2', () => {
test('it should display all the data', async () => {
const { getAllByRole } = render(
);
await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
// we check that the table is displaying all the data
const rows = getAllByRole('row');
expect(rows[4]).toHaveTextContent(/Ninette/i);
expect(rows[1]).toHaveTextContent(/yohann/i);
expect(rows.length).toBe(5);
});
test('it should sort by defaultSortingKey', async () => {
const { getAllByRole } = render(
);
await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
// we check that the table is displaying all the data
const rows = getAllByRole('row');
expect(rows[1]).toHaveTextContent(/ninette/i);
expect(rows[4]).toHaveTextContent(/Yohann/i);
expect(rows.length).toBe(5);
});
test('it should filterGlobally', async () => {
const { getAllByRole } = render(
);
await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
// we check that the table is displaying all the data
const rows = getAllByRole('row');
expect(rows[1]).toHaveTextContent(/an/i); //first name yoh-an-n
expect(rows[2]).toHaveTextContent(/an/i); //last name agath-an-gelou
expect(rows[3]).toHaveTextContent(/an/i); //first name stef-an-ia
expect(rows.length).toBe(4);
});
test('it should not produce false positive when searching for JSON brace character', async () => {
const { getAllByRole } = render(
);
await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
const rows = getAllByRole('row');
// only the header row should remain, no data rows
expect(rows.length).toBe(1);
});
test('it should not produce false positive when searching for a column key name', async () => {
const { getAllByRole } = render(
);
await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
const rows = getAllByRole('row');
// only the header row should remain, no data rows
expect(rows.length).toBe(1);
});
test('it should not produce false positive when searching for ISO date millisecond component', async () => {
const dateData = [
{ name: 'Alpha', createdAt: new Date('2023-01-01T00:00:00.000Z') },
{ name: 'Beta', createdAt: new Date('2023-06-15T12:30:45.123Z') },
];
const dateColumns: TableProps['columns'] = [
{ Header: 'Name', accessor: 'name' },
{
Header: 'Created At',
accessor: 'createdAt',
Cell: ({ value }: { value: Date }) => value.toLocaleDateString(),
},
];
const { getAllByRole } = render(
);
await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
const rows = getAllByRole('row');
// only the header row should remain, no data rows
expect(rows.length).toBe(1);
});
test('it should still match rows when search term appears in a column value', async () => {
const { getAllByRole } = render(
);
await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
const rows = getAllByRole('row');
expect(rows.length).toBe(2); // header + 1 matching data row
expect(rows[1]).toHaveTextContent(/Yohann/i);
});
});
describe('TableV2 responsive columns', () => {
// jsdom ships no ResizeObserver and getBoundingClientRect always reports 0,
// so we stub both to drive the container width the Table measures.
let mockWidth = 1000;
const originalResizeObserver = global.ResizeObserver;
const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect;
beforeAll(() => {
class ResizeObserverMock {
observe() {}
unobserve() {}
disconnect() {}
}
// @ts-expect-error assigning a stub to the global
global.ResizeObserver = ResizeObserverMock;
Element.prototype.getBoundingClientRect = function () {
return { width: mockWidth, height: 600 } as DOMRect;
};
});
afterAll(() => {
global.ResizeObserver = originalResizeObserver;
Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
});
const responsiveColumns: TableProps['columns'] = [
{ Header: 'First Name', accessor: 'firstName' },
{ Header: 'Last Name', accessor: 'lastName' },
{ Header: 'Age', accessor: 'age', dropAt: 500 },
{ Header: 'Health', accessor: 'health', sortType: 'health' },
];
const renderResponsiveTable = () =>
render(
,
);
test('it keeps a droppable column visible when the table is wide enough', async () => {
mockWidth = 1000;
renderResponsiveTable();
await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
expect(screen.getByText('Age')).toBeInTheDocument();
expect(screen.getByText('First Name')).toBeInTheDocument();
});
test('it hides a droppable column when the table is too narrow', async () => {
mockWidth = 400;
renderResponsiveTable();
await waitFor(() =>
expect(screen.queryByText('Age')).not.toBeInTheDocument(),
);
// columns without a dropAt stay visible at any width
expect(screen.getByText('First Name')).toBeInTheDocument();
expect(screen.getByText('Health')).toBeInTheDocument();
});
test('it hides a droppable column defined with a function accessor when narrow', async () => {
mockWidth = 400;
const columns: TableProps['columns'] = [
{ Header: 'First Name', accessor: 'firstName' },
{
Header: 'Full Name',
id: 'fullName',
accessor: (row) => `${row.firstName} ${row.lastName}`,
dropAt: 500,
},
];
render(
,
);
test('lets the user read a dropped column value from a per-row popover', async () => {
mockWidth = 400;
renderRevealTable();
// Age (dropAt 500) is no longer shown inline at 400px wide...
await waitFor(() =>
expect(screen.queryByText('Age')).not.toBeInTheDocument(),
);
// ...but each row offers a trigger that reveals it.
const triggers = await screen.findAllByRole('button', {
name: /show 1 hidden column/i,
});
await userEvent.click(triggers[0]);
const popover = screen.getByRole('dialog');
expect(within(popover).getByText('Age')).toBeInTheDocument();
expect(within(popover).getByText('90')).toBeInTheDocument();
});
test('does not offer the reveal trigger while every column fits', async () => {
mockWidth = 1000;
renderRevealTable();
await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
expect(screen.getByText('Age')).toBeInTheDocument();
expect(
screen.queryByRole('button', { name: /hidden column/i }),
).not.toBeInTheDocument();
});
test('shows no reveal trigger when the feature is not opted into', async () => {
mockWidth = 400;
renderResponsiveTable();
await waitFor(() =>
expect(screen.queryByText('Age')).not.toBeInTheDocument(),
);
expect(
screen.queryByRole('button', { name: /hidden column/i }),
).not.toBeInTheDocument();
});
test('starts offering the reveal trigger when the feature is enabled while a column is already dropped', async () => {
mockWidth = 400;
const TogglingTable = () => {
const [reveal, setReveal] = useState(false);
return (
);
};
render();
// Age is dropped, but with the feature still off there is no reveal trigger.
await waitFor(() =>
expect(screen.queryByText('Age')).not.toBeInTheDocument(),
);
expect(
screen.queryByRole('button', { name: /hidden column/i }),
).not.toBeInTheDocument();
// Turning the feature on — while the same column stays dropped — reveals it.
await userEvent.click(
screen.getByRole('button', { name: /enable reveal/i }),
);
expect(
await screen.findAllByRole('button', { name: /show 1 hidden column/i }),
).not.toHaveLength(0);
});
});