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);
}
});
});