test(integration): live unified/All-Mail counter coverage

Add 09-live-counters: a background-login account updates the unified counter
live (no reconcile), and a shared-folder change reconciles the All-Mail counter
on focus. Document the shared-account counter behaviour in the README.
This commit is contained in:
Stefan Hildebrandt
2026-07-11 21:15:35 +02:00
parent 060c5d00d1
commit c3acb537d0
2 changed files with 82 additions and 4 deletions
+8 -4
View File
@@ -93,10 +93,14 @@ integration/
Some tests assert server-side truth (or use `test.fail` to pin a known gap)
because the UI behaviour is currently incomplete. Worth a look:
- **Shared destination counters don't refresh live.** `forceSync`
(visibilitychange) reconciles only the *active* account, so moving a message
into a shared Trash/Junk/folder doesn't update that shared folder's sidebar
badge until a full reload. Source counters and server state are correct.
- **Shared-account counters now reconcile on focus/interval** (`09-live-counters`).
Stalwart's SSE only pushes StateChange for the *primary* account, so a
background change in a shared/delegated account is never pushed. The client
now also polls the session's secondary accounts, so their folder badges and
the unified/All-Mail counter refresh on the visibility reconcile and on a slow
background poll. (A *login* account already updates live via its own SSE.)
Note: these shared counters still don't update the instant a local action
runs — they follow the reconcile, not the optimistic path.
- **`mark-as-spam` doesn't optimistically decrement the source counter** the
way `delete` does; it settles after a reconcile.
- **Reopening a draft resets the From selector** to the default identity even
@@ -0,0 +1,74 @@
import { test, expect } from '@playwright/test';
import { ACCOUNTS } from './helpers/config';
import { sendMail } from './helpers/smtp';
import { JmapClient } from './helpers/jmap';
import {
login,
addAccount,
seedUnifiedSettings,
seedAllMailSettings,
expandSharedFolders,
folderCounts,
expectFolderUnread,
forceSync,
} from './helpers/app';
/**
* Live currency of the unified / All-Mail counters across every source folder.
*
* Stalwart's SSE only pushes StateChange for the *primary* account, so:
* - a background *login* account updates the badge live (each login has its
* own SSE) — asserted with no reconcile;
* - a *shared/delegated* account gets no push at all, so the client polls the
* session's secondary accounts too; the badge reconciles on focus/interval.
* (Regression test for the shared-account state-poll.)
*/
const { alice, bob, carol } = ACCOUNTS;
const subj = (l: string) => `IT ${l} ${Date.now()}`;
async function deliverIntoSharedFolder(owner: JmapClient, folderId: string, subject: string) {
const acct = ACCOUNTS.alice;
await sendMail({ from: acct.email, authPass: acct.password, to: acct.email, subject, body: 'x' });
const m = await owner.waitForEmail(subject);
await owner.moveEmail(m.id, folderId);
}
test.describe('Live unified/All-Mail counters', () => {
test('a background login account updates the unified counter live (no reconcile)', async ({ page }) => {
for (const a of [alice, bob]) {
const j = await JmapClient.connect(a.email, a.password);
await j.reset();
}
await seedUnifiedSettings(page);
await login(page, alice);
await addAccount(page, bob); // bob active, alice in the background
await expectFolderUnread(page, { name: 'unified-inbox' }, 0);
// Mail lands in alice's inbox while bob is active — no focus/forceSync here.
await sendMail({ from: alice.email, authPass: alice.password, to: alice.email, subject: subj('bg-live'), body: 'x' });
await expectFolderUnread(page, { name: 'unified-inbox' }, 1);
});
test('a shared-folder change reconciles the All-Mail counter on focus', async ({ page }) => {
const ja = await JmapClient.connect(alice.email, alice.password);
const jc = await JmapClient.connect(carol.email, carol.password);
await ja.reset();
await jc.reset();
const shared = await ja.createSharedFolder('TeamShared', carol.email);
await seedAllMailSettings(page, { crossAccount: false });
await login(page, carol);
await expandSharedFolders(page, alice.email);
expect((await folderCounts(page, { name: '__cross_all__' })).unread).toBe(0);
// A background change in the shared (delegated) account gets no SSE push.
await deliverIntoSharedFolder(ja, shared, subj('sh-live'));
// Focus reconcile now polls the shared account too, so the All-Mail badge
// picks up the shared folder's new unread.
await forceSync(page);
await expect
.poll(async () => (await folderCounts(page, { name: '__cross_all__' })).unread, { timeout: 15000 })
.toBe(1);
});
});