inbox-landing-redirect.test.tsx4.7 KBView on GitHub import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter, Route, Routes, useLocation } from 'react-router';
/**
* `/mail` and `/inbox` land on the literal `inbox` slug. In the important/other
* layout nothing owns that slug, so the folder page has to forward to the first
* tab — otherwise the tab strip highlights Important while the thread query and
* the omni-channel feed still run the unscoped `inbox` folder.
*
* Everything the jest.mock factories below touch is `mock`-prefixed and declared
* as a hoisted function: the factories are lifted above this file's imports.
*/
type MockInbox = {
id: string;
name: string;
position: number;
system?: boolean;
rule: { kind: 'all' };
};
function mockGetInboxSlug(inbox: { id: string; name: string }): string {
if (inbox.id === 'default') return 'inbox';
if (inbox.id === 'important') return 'important';
if (inbox.id === 'other') return 'other';
return inbox.name.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
}
function mockGetRowInboxOrder(inboxes: MockInbox[]): MockInbox[] {
const important = inboxes.find((i) => i.id === 'important');
const other = inboxes.find((i) => i.id === 'other');
const middle = inboxes.filter((i) => i.id !== 'important' && i.id !== 'other');
if (important && other) return [important, ...middle, other];
if (important) return [important, ...middle];
if (other) return [...middle, other];
return middle;
}
function mockInboxesForLayout(layout: string): MockInbox[] {
if (layout === 'important_other') {
return [
{ id: 'important', name: 'Important', position: 0, system: true, rule: { kind: 'all' } },
{ id: 'other', name: 'Other', position: 1, system: true, rule: { kind: 'all' } },
];
}
return [{ id: 'default', name: 'Inbox', position: 0, system: true, rule: { kind: 'all' } }];
}
let mockLayout: 'inbox' | 'important_other' | 'stacked' = 'important_other';
let mockFolder = 'inbox';
jest.mock('react-router', () => ({
...jest.requireActual('react-router'),
useLoaderData: () => ({ folder: mockFolder }),
}));
jest.mock('@/modules/threads/hooks/use-inboxes', () => ({
getInboxSlug: mockGetInboxSlug,
getRowInboxOrder: mockGetRowInboxOrder,
findInboxBySlug: (inboxes: MockInbox[], slug: string) =>
inboxes.find((i) => mockGetInboxSlug(i) === slug),
useInboxes: () => ({
inboxes: mockInboxesForLayout(mockLayout),
inboxLayout: mockLayout,
hasCustomInboxOrder: false,
activeInboxId: mockInboxesForLayout(mockLayout)[0].id,
setActiveInboxId: jest.fn(),
isLoading: false,
}),
}));
jest.mock('@/modules/labels/hooks/use-labels', () => ({
useLabels: () => ({ userLabels: [], isLoading: false }),
}));
jest.mock('@/modules/threads/mail', () => ({
MailLayout: () => null,
}));
jest.mock('@/lib/performance-logger', () => ({
perfLogger: { start: jest.fn(), end: jest.fn() },
}));
import MailPage from '@/app/(routes)/mail/[folder]/page';
function LocationProbe() {
const { pathname, search } = useLocation();
return <div data-testid="location">{`${pathname}${search}`}</div>;
}
async function landsOn(entry: string) {
render(
<MemoryRouter initialEntries={[entry]}>
<LocationProbe />
<Routes>
<Route path="/:base/:folder" element={<MailPage />} />
</Routes>
</MemoryRouter>,
);
// The forward happens in an effect; let the navigation settle before reading.
await waitFor(() => expect(screen.getByTestId('location')).toBeInTheDocument());
return screen.getByTestId('location').textContent;
}
describe('inbox landing redirect', () => {
beforeEach(() => {
mockFolder = 'inbox';
});
it('forwards /mail/inbox to the first tab in the important/other layout', async () => {
mockLayout = 'important_other';
expect(await landsOn('/mail/inbox')).toBe('/mail/important');
});
it('carries the query string through so ?channel survives', async () => {
mockLayout = 'important_other';
expect(await landsOn('/mail/inbox?channel=all')).toBe('/mail/important?channel=all');
});
it('forwards on the omni-channel /inbox prefix too', async () => {
mockLayout = 'important_other';
expect(await landsOn('/inbox/inbox')).toBe('/inbox/important');
});
it('stays put when an inbox owns the `inbox` slug', async () => {
mockLayout = 'inbox';
expect(await landsOn('/mail/inbox')).toBe('/mail/inbox');
});
it('stays put in the stacked layout, whose single tab is /mail/inbox', async () => {
mockLayout = 'stacked';
expect(await landsOn('/mail/inbox')).toBe('/mail/inbox');
});
it('leaves a non-inbox folder alone', async () => {
mockLayout = 'important_other';
mockFolder = 'other';
expect(await landsOn('/mail/other')).toBe('/mail/other');
});
});