This code is nowhere used, so to prevent extra work during an upcoming refactor of tags, it is removed and some related tests are now actually made useful
185 lines
5.9 KiB
TypeScript
185 lines
5.9 KiB
TypeScript
import { render, screen, act } from '@testing-library/react';
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { ThreadListItem } from '../thread-list-item';
|
|
import { useSettingsStore, DEFAULT_KEYWORDS } from '@/stores/settings-store';
|
|
import { useEmailStore } from '@/stores/email-store';
|
|
import { groupEmailsByThread } from '@/lib/thread-utils';
|
|
import type { Email } from '@/lib/jmap/types';
|
|
|
|
vi.mock('@/hooks/use-email-drag', () => ({
|
|
useEmailDrag: () => ({ dragHandlers: {}, isDragging: false }),
|
|
}));
|
|
|
|
vi.mock('@/stores/auth-store', () => ({
|
|
useAuthStore: () => ({ identities: [] }),
|
|
}));
|
|
|
|
const makeEmail = (overrides: Partial<Email> = {}): Email => ({
|
|
id: 'email-1',
|
|
threadId: 'thread-1',
|
|
mailboxIds: { inbox: true },
|
|
keywords: { $seen: true },
|
|
size: 1000,
|
|
receivedAt: '2024-01-15T10:00:00Z',
|
|
from: [{ name: 'Alice', email: 'alice@example.com' }],
|
|
subject: 'Test Subject',
|
|
hasAttachment: false,
|
|
...overrides,
|
|
});
|
|
|
|
/**
|
|
* A one-message thread, built through the real grouping so the fixture cannot
|
|
* drift from what the list actually feeds this component. `ThreadListItem`
|
|
* delegates to `SingleEmailItem` at that size, which is what draws every
|
|
* single-message row in the app.
|
|
*/
|
|
function renderRow(email: Email) {
|
|
const [thread] = groupEmailsByThread([email]);
|
|
return render(
|
|
<ThreadListItem
|
|
thread={thread}
|
|
isExpanded={false}
|
|
onToggleExpand={() => {}}
|
|
onEmailSelect={() => {}}
|
|
/>,
|
|
);
|
|
}
|
|
|
|
describe('ThreadListItem tag badge', () => {
|
|
beforeEach(() => {
|
|
useSettingsStore.setState({
|
|
emailKeywords: [...DEFAULT_KEYWORDS],
|
|
showPreview: false,
|
|
mailLayout: 'split',
|
|
});
|
|
useEmailStore.setState({
|
|
selectedEmailIds: new Set<string>(),
|
|
selectedMailbox: 'inbox',
|
|
});
|
|
});
|
|
|
|
it('does not show a tag badge when the email has no label keyword', () => {
|
|
renderRow(makeEmail({ keywords: { $seen: true } }));
|
|
|
|
expect(screen.getByText('Test Subject')).toBeInTheDocument();
|
|
DEFAULT_KEYWORDS.forEach((kw) => {
|
|
expect(screen.queryByText(kw.label)).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('shows a tag badge for a $label: keyword', () => {
|
|
renderRow(makeEmail({ keywords: { $seen: true, '$label:red': true } }));
|
|
|
|
expect(screen.getByText('Red')).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows a tag badge for the legacy $color: keyword', () => {
|
|
renderRow(makeEmail({ keywords: { $seen: true, '$color:blue': true } }));
|
|
|
|
expect(screen.getByText('Blue')).toBeInTheDocument();
|
|
});
|
|
|
|
it('falls back to the raw id when the tag is not in settings', () => {
|
|
// A keyword created by another client, or one whose definition was deleted.
|
|
renderRow(makeEmail({ keywords: { $seen: true, '$label:unknown-tag': true } }));
|
|
|
|
expect(screen.getByText('unknown-tag')).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows a custom tag label', () => {
|
|
useSettingsStore.setState({
|
|
emailKeywords: [...DEFAULT_KEYWORDS, { id: 'work', label: 'Work', color: 'teal' }],
|
|
});
|
|
renderRow(makeEmail({ keywords: { $seen: true, '$label:work': true } }));
|
|
|
|
expect(screen.getByText('Work')).toBeInTheDocument();
|
|
});
|
|
|
|
it('follows a renamed tag definition', () => {
|
|
const email = makeEmail({ keywords: { $seen: true, '$label:red': true } });
|
|
const { rerender } = renderRow(email);
|
|
expect(screen.getByText('Red')).toBeInTheDocument();
|
|
|
|
act(() => {
|
|
useSettingsStore.getState().updateKeyword('red', { label: 'Urgent' });
|
|
});
|
|
const [thread] = groupEmailsByThread([email]);
|
|
rerender(
|
|
<ThreadListItem
|
|
thread={thread}
|
|
isExpanded={false}
|
|
onToggleExpand={() => {}}
|
|
onEmailSelect={() => {}}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('Urgent')).toBeInTheDocument();
|
|
expect(screen.queryByText('Red')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('ThreadListItem row content', () => {
|
|
beforeEach(() => {
|
|
useSettingsStore.setState({
|
|
emailKeywords: [...DEFAULT_KEYWORDS],
|
|
showPreview: false,
|
|
mailLayout: 'split',
|
|
});
|
|
useEmailStore.setState({
|
|
selectedEmailIds: new Set<string>(),
|
|
selectedMailbox: 'inbox',
|
|
});
|
|
});
|
|
|
|
it('renders the subject without a tag', () => {
|
|
renderRow(makeEmail({ subject: 'Hello World' }));
|
|
|
|
expect(screen.getByText('Hello World')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders preview text inline in the focused layout', () => {
|
|
useSettingsStore.setState({ showPreview: true, mailLayout: 'focus' });
|
|
const { container } = renderRow(makeEmail({ preview: 'Inline preview content' }));
|
|
|
|
expect(screen.getByText('Test Subject')).toBeInTheDocument();
|
|
expect(screen.getByText(/Inline preview content/)).toBeInTheDocument();
|
|
// Focused rows are one line: the preview shares the subject's element
|
|
// rather than getting a paragraph of its own.
|
|
expect(container.querySelector('p')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('ThreadListItem shift-range checkbox', () => {
|
|
beforeEach(() => {
|
|
useSettingsStore.setState({
|
|
emailKeywords: [...DEFAULT_KEYWORDS],
|
|
showPreview: false,
|
|
mailLayout: 'split',
|
|
});
|
|
});
|
|
|
|
it('shift-clicking the checkbox extends the selection from the anchor', () => {
|
|
const e1 = makeEmail({ id: 'e1', threadId: 't1' });
|
|
const e2 = makeEmail({ id: 'e2', threadId: 't2' });
|
|
const e3 = makeEmail({ id: 'e3', threadId: 't3' });
|
|
// Selection mode active so the checkbox renders, with the anchor on e1.
|
|
useEmailStore.setState({
|
|
emails: [e1, e2, e3],
|
|
selectedEmailIds: new Set(['e1']),
|
|
lastSelectedEmailId: 'e1',
|
|
selectedMailbox: 'inbox',
|
|
});
|
|
|
|
renderRow(e3);
|
|
const checkbox = screen.getAllByRole('button')[0];
|
|
act(() => {
|
|
checkbox.dispatchEvent(new MouseEvent('click', { bubbles: true, shiftKey: true }));
|
|
});
|
|
|
|
const selected = useEmailStore.getState().selectedEmailIds;
|
|
expect(selected.has('e1')).toBe(true);
|
|
expect(selected.has('e2')).toBe(true); // the row in between got filled in
|
|
expect(selected.has('e3')).toBe(true);
|
|
});
|
|
});
|