mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-05 08:16:22 +00:00
fix: show one preview line in regular list rows, two in comfortable ones
The skeleton test counted two preview lines per regular row; regular rows now draw one, so it expects one there and two for comfortable rows.
This commit is contained in:
@@ -362,6 +362,8 @@
|
||||
@custom-variant boot-dense (&:where([data-boot-density="compact"] *, [data-boot-density="extra-compact"] *));
|
||||
/* Message previews turned off (showPreview). */
|
||||
@custom-variant boot-no-preview (&:where([data-boot-preview="off"] *));
|
||||
/* The preview is one line except in comfortable rows. */
|
||||
@custom-variant boot-one-preview-line (&:where(:root:not([data-boot-density="comfortable"]) *));
|
||||
/* The default interface layout, the opposite of mv: (both can't be told apart by order). */
|
||||
@custom-variant boot-std (&:where(:root:not([data-interface-layout="mountain-view"]) *));
|
||||
/* Mountain View's side panel shows only with calendar or contacts. */
|
||||
|
||||
@@ -30,14 +30,16 @@ describe('mail skeleton', () => {
|
||||
expect(pane.querySelector('.lg\\:mv\\:hidden.border-b')).toBeNull();
|
||||
});
|
||||
|
||||
// Regular rows have a one-line preview, comfortable ones two (mail-15).
|
||||
it.each([
|
||||
['extra-compact', 0, 0],
|
||||
['compact', 16, 0],
|
||||
['regular', 16, 16],
|
||||
] as const)('draws %s stacked rows with %i avatars and %i previews', (density, avatars, previews) => {
|
||||
['comfortable', 16, 32],
|
||||
] as const)('draws %s stacked rows with %i avatars and %i preview lines', (density, avatars, previewLines) => {
|
||||
const rows = html(<MailListRowsSkeleton shape="stacked" density={density} preview />);
|
||||
expect(rows.querySelectorAll('.w-10.h-10').length).toBe(avatars);
|
||||
expect(rows.querySelectorAll('.leading-relaxed').length).toBe(previews * 2);
|
||||
expect(rows.querySelectorAll('.leading-relaxed').length).toBe(previewLines);
|
||||
});
|
||||
|
||||
it('leaves the preview out when previews are turned off', () => {
|
||||
@@ -49,6 +51,7 @@ describe('mail skeleton', () => {
|
||||
const rows = html(<MailListRowsSkeleton />);
|
||||
expect(rows.querySelector('.boot-density-xc\\:hidden')).not.toBeNull();
|
||||
expect(rows.querySelector('.boot-dense\\:hidden')).not.toBeNull();
|
||||
expect(rows.querySelector('.boot-one-preview-line\\:hidden')).not.toBeNull();
|
||||
expect(rows.querySelector('.md\\:mv\\:block')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -181,6 +181,12 @@ function previewClass(o: RowOptions): string | null {
|
||||
return o.preview === false || o.density === "compact" || o.density === "extra-compact" ? null : "";
|
||||
}
|
||||
|
||||
/** Classes for the preview's second line, which only comfortable rows have (mail-15). */
|
||||
function secondPreviewLineClass(o: RowOptions): string | null {
|
||||
if (o.density === undefined) return "boot-one-preview-line:hidden";
|
||||
return o.density === "comfortable" ? "" : null;
|
||||
}
|
||||
|
||||
/*
|
||||
* The rows below are drawn dozens of times, so they use the short classes in
|
||||
* app/boot-skeleton.css (mail section) for what every row repeats: sk-mrow
|
||||
@@ -202,6 +208,7 @@ function L({ className, w }: { className?: string; w: string }) {
|
||||
function StackedRow({ i, o }: { i: number; o: RowOptions }) {
|
||||
const avatar = avatarClass(o);
|
||||
const preview = previewClass(o);
|
||||
const secondLine = secondPreviewLineClass(o);
|
||||
return (
|
||||
<div className="sk-mrow max-md:mv:border-b-transparent">
|
||||
<div className="sk-mcell sk-mcell-min">
|
||||
@@ -220,7 +227,9 @@ function StackedRow({ i, o }: { i: number; o: RowOptions }) {
|
||||
<span className={cn("block", preview)}>
|
||||
{/* Mountain View's phone rows keep the preview to one line, beside a 32px star. */}
|
||||
<L className="text-sm leading-relaxed max-md:mv:h-8" w="95%" />
|
||||
<L className="text-sm leading-relaxed max-md:mv:hidden" w={textWidth(i + 5, 45, 40)} />
|
||||
{secondLine !== null && (
|
||||
<L className={cn("text-sm leading-relaxed max-md:mv:hidden", secondLine)} w={textWidth(i + 5, 45, 40)} />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { render, screen } 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 { useUIStore } from '@/stores/ui-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', () => {
|
||||
const state = { identities: [], username: null, client: null, activeAccountId: null, getClientForAccount: () => undefined };
|
||||
const useAuthStore = Object.assign(
|
||||
(selector?: (s: typeof state) => unknown) => (selector ? selector(state) : state),
|
||||
{ getState: () => state },
|
||||
);
|
||||
return { useAuthStore };
|
||||
});
|
||||
|
||||
const PREVIEW = 'Hi Robin, thanks for the quick reply. I went through the points again.';
|
||||
|
||||
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',
|
||||
preview: PREVIEW,
|
||||
hasAttachment: false,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
function renderRow(emails: Email[]) {
|
||||
const [thread] = groupEmailsByThread(emails);
|
||||
return render(
|
||||
<ThreadListItem thread={thread} isExpanded={false} onToggleExpand={() => {}} onEmailSelect={() => {}} />,
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
useUIStore.setState({ isMobile: false, isTablet: false, isDesktop: true });
|
||||
useSettingsStore.setState({
|
||||
emailKeywords: [...DEFAULT_KEYWORDS],
|
||||
showPreview: true,
|
||||
mailLayout: 'split',
|
||||
interfaceLayout: 'default',
|
||||
});
|
||||
useEmailStore.setState({ selectedEmailKeys: new Set<string>(), selectedMailbox: 'inbox' });
|
||||
});
|
||||
|
||||
// mail-15: the default density shows one preview line, so more rows fit;
|
||||
// comfortable keeps two.
|
||||
describe('list row preview lines', () => {
|
||||
it.each([
|
||||
['regular', 'line-clamp-1'],
|
||||
['comfortable', 'line-clamp-2'],
|
||||
] as const)('a %s row clamps its preview with %s', (density, clamp) => {
|
||||
useSettingsStore.setState({ density });
|
||||
renderRow([makeEmail()]);
|
||||
expect(screen.getByText(PREVIEW).className.split(/\s+/)).toContain(clamp);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['regular', 'line-clamp-1'],
|
||||
['comfortable', 'line-clamp-2'],
|
||||
] as const)('a %s conversation row clamps its preview with %s', (density, clamp) => {
|
||||
useSettingsStore.setState({ density });
|
||||
renderRow([makeEmail({ id: 'a' }), makeEmail({ id: 'b', receivedAt: '2024-01-16T10:00:00Z' })]);
|
||||
expect(screen.getByText(PREVIEW).className.split(/\s+/)).toContain(clamp);
|
||||
});
|
||||
});
|
||||
@@ -463,9 +463,10 @@ export function EmailList({
|
||||
let size = { 'extra-compact': 32, compact: 60, regular: 84, comfortable: 104 }[density];
|
||||
if (showPreview && density !== 'extra-compact') {
|
||||
// A mail without a preview draws a one-line "No preview available",
|
||||
// a line (23px) shorter than a real one.
|
||||
// a line (23px) shorter than a real one. Only comfortable rows show a
|
||||
// second preview line (mail-15).
|
||||
const emptyPreview = !!latest && !cleanPreview(latest.preview) && !latest.searchSnippet?.preview;
|
||||
size += emptyPreview ? 36 - 23 : 36;
|
||||
size += emptyPreview || density !== 'comfortable' ? 36 - 23 : 36;
|
||||
}
|
||||
// The chip row (attachments, verification code): a 22px chip plus 6px margin.
|
||||
const hasCode = !!latest && showVerificationCodes && !!listVerificationCode(latest);
|
||||
|
||||
@@ -975,7 +975,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
<div className={cn("flex min-w-0 items-center gap-1", !mvMobileRow && "contents")}>
|
||||
<p dir="auto" className={cn(
|
||||
"text-sm leading-relaxed",
|
||||
mvMobileRow ? "line-clamp-1 min-w-0 flex-1" : "line-clamp-2",
|
||||
mvMobileRow ? "line-clamp-1 min-w-0 flex-1" : density === 'comfortable' ? "line-clamp-2" : "line-clamp-1",
|
||||
isUnread
|
||||
? "text-muted-foreground"
|
||||
: "text-muted-foreground"
|
||||
@@ -1754,7 +1754,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
<div className={cn("flex min-w-0 items-center gap-1", !mvMobileRow && "contents")}>
|
||||
<p dir="auto" className={cn(
|
||||
"text-sm leading-relaxed",
|
||||
mvMobileRow ? "line-clamp-1 min-w-0 flex-1" : "line-clamp-2",
|
||||
mvMobileRow ? "line-clamp-1 min-w-0 flex-1" : density === 'comfortable' ? "line-clamp-2" : "line-clamp-1",
|
||||
hasUnread
|
||||
? "text-muted-foreground"
|
||||
: "text-muted-foreground"
|
||||
|
||||
Reference in New Issue
Block a user