From 816cd1bf565b8ca1fe8ae4236fb9ea09a0cfe100 Mon Sep 17 00:00:00 2001 From: zisisvakras Date: Mon, 17 Aug 2026 15:22:39 +0300 Subject: [PATCH] test(jmap): cover the Email/set keyword removal patch shape Extend the existing keyword write coverage to pin the three call sites that clear a keyword. markAsRead, batchMarkAsRead and toggleStar must patch with null and never with false, and must still send true when setting the keyword. The three removal cases fail against the previous boolean form, one per call site. The three set cases pass either way, since that path was already correct. --- lib/__tests__/jmap-keyword-account.test.ts | 62 ++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/lib/__tests__/jmap-keyword-account.test.ts b/lib/__tests__/jmap-keyword-account.test.ts index 7c4585e3e..0ee4b49b3 100644 --- a/lib/__tests__/jmap-keyword-account.test.ts +++ b/lib/__tests__/jmap-keyword-account.test.ts @@ -38,6 +38,11 @@ function mockEmailSet() { return { captured, fetchSpy }; } +function patchFor(call: JMAPMethodCall, emailId: string): Record { + const update = call[1].update as Record>; + return update[emailId]; +} + describe('JMAP keyword writes route to the email account (#281)', () => { beforeEach(() => vi.restoreAllMocks()); afterEach(() => vi.restoreAllMocks()); @@ -72,3 +77,60 @@ describe('JMAP keyword writes route to the email account (#281)', () => { expect(captured[0][1].accountId).toBe('primary-account'); }); }); + +describe('Email/set clears keywords with null, not false', () => { + beforeEach(() => vi.restoreAllMocks()); + afterEach(() => vi.restoreAllMocks()); + + it('markAsRead(false) removes $seen with null', async () => { + const client = createClient(); + const { captured } = mockEmailSet(); + await client.markAsRead('email-x', false); + const patch = patchFor(captured[0], 'email-x'); + expect(patch['keywords/$seen']).toBeNull(); + expect(patch['keywords/$seen']).not.toBe(false); + }); + + it('markAsRead(true) sets $seen to true', async () => { + const client = createClient(); + const { captured } = mockEmailSet(); + await client.markAsRead('email-x', true); + expect(patchFor(captured[0], 'email-x')['keywords/$seen']).toBe(true); + }); + + it('batchMarkAsRead(false) removes $seen with null for every id', async () => { + const client = createClient(); + const { captured } = mockEmailSet(); + await client.batchMarkAsRead(['email-a', 'email-b'], false); + for (const id of ['email-a', 'email-b']) { + const patch = patchFor(captured[0], id); + expect(patch['keywords/$seen']).toBeNull(); + expect(patch['keywords/$seen']).not.toBe(false); + } + }); + + it('batchMarkAsRead(true) sets $seen to true for every id', async () => { + const client = createClient(); + const { captured } = mockEmailSet(); + await client.batchMarkAsRead(['email-a', 'email-b'], true); + for (const id of ['email-a', 'email-b']) { + expect(patchFor(captured[0], id)['keywords/$seen']).toBe(true); + } + }); + + it('toggleStar(false) removes $flagged with null', async () => { + const client = createClient(); + const { captured } = mockEmailSet(); + await client.toggleStar('email-x', false); + const patch = patchFor(captured[0], 'email-x'); + expect(patch['keywords/$flagged']).toBeNull(); + expect(patch['keywords/$flagged']).not.toBe(false); + }); + + it('toggleStar(true) sets $flagged to true', async () => { + const client = createClient(); + const { captured } = mockEmailSet(); + await client.toggleStar('email-x', true); + expect(patchFor(captured[0], 'email-x')['keywords/$flagged']).toBe(true); + }); +});