From 1dde70399308a66d6467df17bfcd53363f5125c5 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Tue, 3 Nov 2026 09:53:04 +0100 Subject: [PATCH] fix: list files again when the real client takes over from the local copy --- integration/tests/22-ent-offline.spec.ts | 4 +++- stores/__tests__/file-store.test.ts | 20 +++++++++++++++++++- stores/file-store.ts | 6 ++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/integration/tests/22-ent-offline.spec.ts b/integration/tests/22-ent-offline.spec.ts index 5e2b6e642..e9f684b81 100644 --- a/integration/tests/22-ent-offline.spec.ts +++ b/integration/tests/22-ent-offline.spec.ts @@ -842,7 +842,9 @@ test('nothing of a kept message is readable on the device', async ({ browser }) await enableOffline(page); await expect(async () => { const client = await jmapAs(ENT.alice); - const total = await client.request([['Email/query', { accountId: client.accountId, calculateTotal: true, limit: 0 }, '0']]); + // The default window: the old message of the cap test stays out. + const after = new Date(Date.now() - 90 * 86_400_000).toISOString().replace(/\.\d+Z$/, 'Z'); + const total = await client.request([['Email/query', { accountId: client.accountId, filter: { after }, calculateTotal: true, limit: 0 }, '0']]); expect(await keptMessages(page)).toBeGreaterThanOrEqual(total.methodResponses[0][1].total); }).toPass({ timeout: 180_000, intervals: [2000] }); await toMail(page); diff --git a/stores/__tests__/file-store.test.ts b/stores/__tests__/file-store.test.ts index bf7080b02..74dbad63c 100644 --- a/stores/__tests__/file-store.test.ts +++ b/stores/__tests__/file-store.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeEach } from 'vitest'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; import { useFileStore, linkedPreviewResource, resourceServerRef, sharedFilesLocation } from '../file-store'; import type { IJMAPClient } from '@/lib/jmap/client-interface'; import type { FileNode } from '@/lib/jmap/types'; @@ -106,6 +106,24 @@ const file = (id: string, name: string, parentId: string | null): FileNode => ({ id, parentId, name, type: 'text/plain', blobId: `b-${id}`, size: 10, created: '', modified: '', }); +describe('after a start from the local copy', () => { + it('lists again when the real client takes over, and the error of asking the device goes', async () => { + const local = makeMockClient([]); + (local as unknown as { isOfflineOnly: () => boolean }).isOfflineOnly = () => true; + local.listAllFileNodes = async () => { throw new Error('Not available offline'); }; + useFileStore.setState({ client: null, supportsFiles: null, resources: [], error: null, currentParentId: null, pathStack: [{ id: null, name: '' }] } as never); + useFileStore.getState().initClient(local as unknown as IJMAPClient, 'acc'); + useFileStore.setState({ supportsFiles: true }); + await useFileStore.getState().navigate(null); + expect(useFileStore.getState().error).toBeTruthy(); + + const real = makeMockClient([{ id: 'f1', parentId: null, name: 'kept.txt', type: 'text/plain', blobId: 'b', size: 1, created: '', modified: '' }]); + useFileStore.getState().initClient(real as unknown as IJMAPClient, 'acc'); + await vi.waitFor(() => expect(useFileStore.getState().resources.map((r) => r.name)).toEqual(['kept.txt'])); + expect(useFileStore.getState().error).toBeFalsy(); + }); +}); + describe('file-store hierarchy (issue #379)', () => { beforeEach(() => { useFileStore.setState({ diff --git a/stores/file-store.ts b/stores/file-store.ts index 328635142..fcee224eb 100644 --- a/stores/file-store.ts +++ b/stores/file-store.ts @@ -649,9 +649,15 @@ export const useFileStore = create((set, get) => ({ })(), initClient: (client: IJMAPClient, accountId?: string | null) => { + const before = get().client as (IJMAPClient & { isOfflineOnly?: () => boolean }) | null; const patch: Partial = { client }; if (accountId !== undefined) patch.currentAccountId = accountId; set(patch); + // A start from the local copy: the real client just took over, and what's + // listed is what the device had, or the error of asking it. + const offlineBefore = before !== client && before?.isOfflineOnly?.(); + const offlineNow = (client as IJMAPClient & { isOfflineOnly?: () => boolean }).isOfflineOnly?.(); + if (offlineBefore && !offlineNow && get().supportsFiles) void get().refresh(); }, clearClient: () => {