Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/lib/__tests__/email-threading.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { computeReplyThreadingHeaders } from '../email-threading';
import { computeReplyThreadingHeaders, orderThreadEmails } from '../email-threading';

describe('computeReplyThreadingHeaders', () => {
it('returns undefined when the parent has no msg-id', () => {
Expand Down Expand Up @@ -39,3 +39,22 @@ describe('computeReplyThreadingHeaders', () => {
).toEqual({ inReplyTo: '<a@host>', references: '<z@host> <a@host>' });
});
});

describe('orderThreadEmails', () => {
it('follows the server thread order, not the metadata order', () => {
const metas = [{ id: 'c' }, { id: 'a' }, { id: 'b' }];
expect(orderThreadEmails(['a', 'b', 'c'], metas)).toEqual([
{ id: 'a' }, { id: 'b' }, { id: 'c' },
]);
});

it('drops ids whose metadata did not come back', () => {
expect(orderThreadEmails(['a', 'gone', 'b'], [{ id: 'a' }, { id: 'b' }])).toEqual([
{ id: 'a' }, { id: 'b' },
]);
});

it('returns empty for an empty thread', () => {
expect(orderThreadEmails([], [])).toEqual([]);
});
});
15 changes: 15 additions & 0 deletions src/lib/email-threading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,18 @@ export function computeReplyThreadingHeaders(
references: chain.map((id) => `<${id}>`).join(' '),
};
}

/**
* Order thread members for display: JMAP's `Thread.emailIds` is the server's
* oldest-first ordering (RFC 8621 §3), so follow it and drop ids whose
* metadata didn't come back (expunged between Thread/get and Email/get).
*/
export function orderThreadEmails<T extends { id: string }>(
emailIds: string[],
metas: T[],
): T[] {
const byId = new Map(metas.map((m) => [m.id, m]));
return emailIds
.map((id) => byId.get(id))
.filter((m): m is T => !!m);
}
Loading