stacked-inbox-parallel.test.tsx3.6 KBView on GitHub
import React from 'react';
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router';
import { StackedInboxView } from '@/modules/threads/components/stacked-inbox-view';

/**
 * Every container in the stacked inbox must fetch on mount, in parallel.
 *
 * REGRESSION THIS LOCKS DOWN (<email>, staging, 2026-08-04): her inbox
 * "consistently misses emails" — the newest thread shown was 8:02 while 10+ newer ones sat in
 * Gmail. Staging telemetry for her connection over six hours showed 25 of 25 `mail.listThreads`
 * calls carrying `inboxName: "Starred"` and NOT ONE for any other container.
 *
 * The cause was the sequential gate in StackedInboxView: `enabled={index < loadedUpTo}` with
 * `loadedUpTo` starting at 1 and only advancing when a section called `onReady`. A section
 * reported ready once its first fetch settled — so the containers formed a chain, and:
 *
 *   - total time to fill the view was the SUM of every container's listThreads latency
 *     (p99 ~16s per call), not the slowest one, and
 *   - any container whose query never settled — a 502 retrying, a slow O(inbox) scan — held
 *     every container below it at `enabled: false` indefinitely. Those never issued a request
 *     at all, which is why they showed stale mail with no error and nothing in the logs.
 *
 * Containers are independent queries; nothing about one's result informs another's. They must
 * all start together, and one failing must not silence the rest.
 */

const inboxes = [
  { id: 'important', name: 'Important', position: 0, system: true, rule: { kind: 'all' } },
  { id: 'inbox-cal', name: 'Calendar', position: 1, rule: { kind: 'all_of', clauses: [] } },
  { id: 'other', name: 'Other', position: 2, system: true, rule: { kind: 'all' } },
];

const getRowInboxOrder = (xs: typeof inboxes) => xs;

jest.mock('@/modules/threads/hooks/use-inboxes', () => ({
  useInboxes: () => ({ inboxes }),
  getRowInboxOrder,
}));

/** Records the `enabled` prop each section was rendered with, keyed by inbox id. */
const enabledById: Record<string, boolean[]> = {};

jest.mock('@/modules/threads/components/inbox-section', () => ({
  InboxSection: ({
    inbox,
    enabled,
  }: {
    inbox: { id: string; name: string };
    enabled?: boolean;
    // `onReady` intentionally never called — these sections simulate a container whose
    // query has not settled (in-flight, retrying a 502, or a slow scan).
    onReady?: () => void;
  }) => {
    (enabledById[inbox.id] ??= []).push(enabled !== false);
    return <div data-testid={`section-${inbox.id}`}>{inbox.name}</div>;
  },
}));

beforeEach(() => {
  for (const key of Object.keys(enabledById)) delete enabledById[key];
});

describe('StackedInboxView — parallel container loading', () => {
  it('enables every container on the first render, without waiting for the one above', () => {
    render(
      <MemoryRouter>
        <StackedInboxView />
      </MemoryRouter>,
    );

    // No section calls onReady, so under the sequential gate only "Important" is ever
    // enabled and the other two never issue a listThreads at all.
    expect(enabledById['important']?.[0]).toBe(true);
    expect(enabledById['inbox-cal']?.[0]).toBe(true);
    expect(enabledById['other']?.[0]).toBe(true);
  });

  it('never disables a container because a sibling has not finished', () => {
    render(
      <MemoryRouter>
        <StackedInboxView />
      </MemoryRouter>,
    );

    for (const inbox of inboxes) {
      const renders = enabledById[inbox.id] ?? [];
      expect(renders.length).toBeGreaterThan(0);
      expect(renders.every(Boolean)).toBe(true);
    }
  });
});