inbox-section.test.tsx3.7 KBView on GitHub
import React from 'react';
import { act, fireEvent, render } from '@testing-library/react';
import { MemoryRouter } from 'react-router';
import { InboxSection } from '@/modules/threads/components/inbox-section';

const inbox = {
  id: 'important',
  name: 'Important',
  position: 0,
  system: true,
  rule: { kind: 'all' } as const,
};

const mockPage1 = Array.from({ length: 25 }, (_, i) => ({
  id: `t-mockPage1-${i}`,
  $raw: { latestReceivedOn: '2026-05-24T00:00:00Z', subject: `mockPage1-${i}` },
}));
const mockPage2 = Array.from({ length: 25 }, (_, i) => ({
  id: `t-mockPage2-${i}`,
  $raw: { latestReceivedOn: '2026-05-24T00:00:00Z', subject: `mockPage2-${i}` },
}));

const mockFetchNextPage = jest.fn().mockResolvedValue(undefined);
const mockToggleInboxCollapsed = jest.fn();
let mockCollapsedState: Record<string, boolean> = {};

jest.mock('@/modules/threads/threadList/hooks/use-inbox-threads', () => ({
  useInboxThreads: () => ({
    threadsByPage: [mockPage1, mockPage2],
    pageCount: 2,
    hasNextPage: false,
    isFetchingNextPage: false,
    mockFetchNextPage,
    isLoading: false,
    isFetching: false,
  }),
}));

jest.mock('@/hooks/use-inbox-counts', () => ({
  useInboxCounts: () => ({
    byId: { important: { count: 42, isExact: true } },
    done: { count: 0, isExact: true },
  }),
}));

jest.mock('@/modules/store', () => ({
  useCedarStore: (selector: (state: unknown) => unknown) =>
    selector({
      collapsedInboxIds: mockCollapsedState,
      mockToggleInboxCollapsed: (id: string) => {
        mockToggleInboxCollapsed(id);
        mockCollapsedState = { ...mockCollapsedState, [id]: !mockCollapsedState[id] };
      },
      selectThreadId: jest.fn(),
      setIsThreadOpen: jest.fn(),
    }),
}));

jest.mock('@/modules/threads/threadList/threadItem', () => ({
  Thread: ({ message }: { message: { id: string } }) => (
    <div data-testid="thread-row">{message.id}</div>
  ),
}));

jest.mock('@/lib/thread-open-profiler', () => ({
  startThreadOpenProfiling: jest.fn(),
}));

describe('InboxSection', () => {
  beforeEach(() => {
    mockFetchNextPage.mockClear();
    mockToggleInboxCollapsed.mockClear();
    mockCollapsedState = {};
  });

  it('renders only the current page of threads', () => {
    const view = render(
      <MemoryRouter>
        <InboxSection inbox={inbox} />
      </MemoryRouter>,
    );
    const rows = view.getAllByTestId('thread-row');
    expect(rows).toHaveLength(25);
    expect(rows[0].textContent).toBe('t-mockPage1-0');
  });

  it('shows the inbox name in the header and the total next to the pager', () => {
    const view = render(
      <MemoryRouter>
        <InboxSection inbox={inbox} />
      </MemoryRouter>,
    );
    expect(view.getByText('Important')).toBeInTheDocument();
    expect(view.getByText('1-25 of 42')).toBeInTheDocument();
  });

  it('renders the range with total on the first page', () => {
    const view = render(
      <MemoryRouter>
        <InboxSection inbox={inbox} />
      </MemoryRouter>,
    );
    expect(view.getByText('1-25 of 42')).toBeInTheDocument();
  });

  it('updates the range when next is clicked', async () => {
    const view = render(
      <MemoryRouter>
        <InboxSection inbox={inbox} />
      </MemoryRouter>,
    );
    await act(async () => {
      fireEvent.click(view.getByLabelText('Next page'));
    });
    expect(view.getByText('26-50 of 42')).toBeInTheDocument();
  });

  it('advances to the next page when next is clicked', async () => {
    const view = render(
      <MemoryRouter>
        <InboxSection inbox={inbox} />
      </MemoryRouter>,
    );
    await act(async () => {
      fireEvent.click(view.getByLabelText('Next page'));
    });
    const rows = view.getAllByTestId('thread-row');
    expect(rows[0].textContent).toBe('t-mockPage2-0');
  });
});