feat: europeanize mock JMAP data and refine email list layout
Replace all mock data in the dev JMAP server with European-themed content: 42 emails (was 13), 20 contacts across 2 address books (was 3), 5 calendars and 35 events with recurrence, overlaps, and all-day entries. Move keyword tags in email/thread list items from the subject line to the header row alongside the date for a cleaner layout. Update test assertions to match the new mock data.
This commit is contained in:
+1018
-154
File diff suppressed because one or more lines are too long
@@ -147,8 +147,18 @@ export function EmailListItem({ email, selected, onClick, onContextMenu }: Email
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||
{keywordDef && (
|
||||
<span className={cn(
|
||||
"text-xs flex-shrink-0 tabular-nums",
|
||||
"inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded-full",
|
||||
KEYWORD_PALETTE[keywordDef.color]?.bg || "bg-muted"
|
||||
)}>
|
||||
<span className={cn("w-1.5 h-1.5 rounded-full", KEYWORD_PALETTE[keywordDef.color]?.dot || "bg-gray-400")} />
|
||||
{keywordDef.label}
|
||||
</span>
|
||||
)}
|
||||
<span className={cn(
|
||||
"text-xs tabular-nums",
|
||||
isUnread
|
||||
? "text-foreground font-semibold"
|
||||
: "text-muted-foreground"
|
||||
@@ -156,26 +166,16 @@ export function EmailListItem({ email, selected, onClick, onContextMenu }: Email
|
||||
{formatDate(email.receivedAt)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Second Line: Subject + Tag */}
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
<span className={cn(
|
||||
"line-clamp-1 text-sm min-w-0",
|
||||
{/* Second Line: Subject */}
|
||||
<div className={cn(
|
||||
"mb-1 line-clamp-1 text-sm",
|
||||
isUnread
|
||||
? "font-semibold text-foreground"
|
||||
: "font-normal text-foreground/90"
|
||||
)}>
|
||||
{email.subject || t('no_subject')}
|
||||
</span>
|
||||
{keywordDef && (
|
||||
<span className={cn(
|
||||
"flex-shrink-0 inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded-full",
|
||||
KEYWORD_PALETTE[keywordDef.color]?.bg || "bg-muted"
|
||||
)}>
|
||||
<span className={cn("w-1.5 h-1.5 rounded-full", KEYWORD_PALETTE[keywordDef.color]?.dot || "bg-gray-400")} />
|
||||
{keywordDef.label}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Third Line: Preview (controlled by showPreview setting) */}
|
||||
|
||||
@@ -129,8 +129,18 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||
{resolvedKeywordDef && (
|
||||
<span className={cn(
|
||||
"text-xs flex-shrink-0 tabular-nums",
|
||||
"inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded-full",
|
||||
KEYWORD_PALETTE[resolvedKeywordDef.color]?.bg || "bg-muted"
|
||||
)}>
|
||||
<span className={cn("w-1.5 h-1.5 rounded-full", KEYWORD_PALETTE[resolvedKeywordDef.color]?.dot || "bg-gray-400")} />
|
||||
{resolvedKeywordDef.label}
|
||||
</span>
|
||||
)}
|
||||
<span className={cn(
|
||||
"text-xs tabular-nums",
|
||||
isUnread
|
||||
? "text-foreground font-semibold"
|
||||
: "text-muted-foreground"
|
||||
@@ -138,25 +148,15 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
{formatDate(email.receivedAt)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
<span className={cn(
|
||||
"line-clamp-1 text-sm min-w-0",
|
||||
<div className={cn(
|
||||
"mb-1 line-clamp-1 text-sm",
|
||||
isUnread
|
||||
? "font-semibold text-foreground"
|
||||
: "font-normal text-foreground/90"
|
||||
)}>
|
||||
{email.subject || "(no subject)"}
|
||||
</span>
|
||||
{resolvedKeywordDef && (
|
||||
<span className={cn(
|
||||
"flex-shrink-0 inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded-full",
|
||||
KEYWORD_PALETTE[resolvedKeywordDef.color]?.bg || "bg-muted"
|
||||
)}>
|
||||
<span className={cn("w-1.5 h-1.5 rounded-full", KEYWORD_PALETTE[resolvedKeywordDef.color]?.dot || "bg-gray-400")} />
|
||||
{resolvedKeywordDef.label}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showPreview && (
|
||||
@@ -354,8 +354,18 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||
{keywordDef && (
|
||||
<span className={cn(
|
||||
"text-xs flex-shrink-0 tabular-nums",
|
||||
"inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded-full",
|
||||
KEYWORD_PALETTE[keywordDef.color]?.bg || "bg-muted"
|
||||
)}>
|
||||
<span className={cn("w-1.5 h-1.5 rounded-full", KEYWORD_PALETTE[keywordDef.color]?.dot || "bg-gray-400")} />
|
||||
{keywordDef.label}
|
||||
</span>
|
||||
)}
|
||||
<span className={cn(
|
||||
"text-xs tabular-nums",
|
||||
hasUnread
|
||||
? "text-foreground font-semibold"
|
||||
: "text-muted-foreground"
|
||||
@@ -363,25 +373,15 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
{formatDate(latestEmail.receivedAt)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
<span className={cn(
|
||||
"line-clamp-1 text-sm min-w-0",
|
||||
<div className={cn(
|
||||
"mb-1 line-clamp-1 text-sm",
|
||||
hasUnread
|
||||
? "font-semibold text-foreground"
|
||||
: "font-normal text-foreground/90"
|
||||
)}>
|
||||
{latestEmail.subject || "(no subject)"}
|
||||
</span>
|
||||
{keywordDef && (
|
||||
<span className={cn(
|
||||
"flex-shrink-0 inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded-full",
|
||||
KEYWORD_PALETTE[keywordDef.color]?.bg || "bg-muted"
|
||||
)}>
|
||||
<span className={cn("w-1.5 h-1.5 rounded-full", KEYWORD_PALETTE[keywordDef.color]?.dot || "bg-gray-400")} />
|
||||
{keywordDef.label}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showPreview && (
|
||||
|
||||
@@ -47,13 +47,13 @@ describe('dev-jmap mock server', () => {
|
||||
|
||||
describe('GET /download', () => {
|
||||
it('should return a response with attachment disposition', async () => {
|
||||
const req = makeRequest('http://localhost:3000/api/dev-jmap/download/dev-account-001/blob-att-001/Q1-Report.pdf?accept=application/pdf', {
|
||||
const req = makeRequest('http://localhost:3000/api/dev-jmap/download/dev-account-001/blob-att-001/Q1-Bericht.pdf?accept=application/pdf', {
|
||||
headers: { host: 'localhost:3000' },
|
||||
});
|
||||
const res = await GET(req, { params: Promise.resolve({ path: ['download', 'dev-account-001', 'blob-att-001', 'Q1-Report.pdf'] }) });
|
||||
const res = await GET(req, { params: Promise.resolve({ path: ['download', 'dev-account-001', 'blob-att-001', 'Q1-Bericht.pdf'] }) });
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('Content-Type')).toBe('application/pdf');
|
||||
expect(res.headers.get('Content-Disposition')).toContain('Q1-Report.pdf');
|
||||
expect(res.headers.get('Content-Disposition')).toContain('Q1-Bericht.pdf');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('dev-jmap mock server', () => {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', host: 'localhost:3000' },
|
||||
body: JSON.stringify({
|
||||
methodCalls: [['Email/query', { accountId: 'dev-account-001', filter: { text: 'welcome' } }, 'c0']],
|
||||
methodCalls: [['Email/query', { accountId: 'dev-account-001', filter: { text: 'willkommen' } }, 'c0']],
|
||||
}),
|
||||
});
|
||||
const res = await POST(req, { params: Promise.resolve({ path: ['api'] }) });
|
||||
@@ -130,7 +130,7 @@ describe('dev-jmap mock server', () => {
|
||||
const data = await res.json();
|
||||
expect(data.methodResponses[0][0]).toBe('Email/get');
|
||||
expect(data.methodResponses[0][1].list).toHaveLength(1);
|
||||
expect(data.methodResponses[0][1].list[0].subject).toContain('Welcome');
|
||||
expect(data.methodResponses[0][1].list[0].subject).toContain('Willkommen');
|
||||
});
|
||||
|
||||
it('should filter properties when specified', async () => {
|
||||
|
||||
Reference in New Issue
Block a user