Save contact photos inline, and load cards so avatars show
Stalwart refuses a blobId in a card's media ("blobIds in media is not
supported"), so adding or changing a photo always failed. The editor now
saves the photo as a data: URI, which Stalwart accepts and returns
unchanged, and leaves the card's other media as it was. Checked live on
0.16.22; the mock now refuses a blobId the same way.
Avatars in the mail list come from the address book's cards, and nothing
loaded those at sign-in, so a photo showed only after Contacts had been
opened. The cards now load in the background at start, the avatar uses
whatever cards are held, and a shared card's photo is fetched from the
account it belongs to.
Fixes #376.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { CAP, client } from "@/jmap/client";
|
||||
import type { JmapSession } from "@/jmap/types";
|
||||
import { useSession } from "@/store/session";
|
||||
import { useContacts } from "@/store/contacts";
|
||||
|
||||
/**
|
||||
* #376: avatars in the mail list come from the address book's cards, and
|
||||
* nothing loaded those at sign-in -- so a contact's photo showed once Contacts
|
||||
* had been opened and was gone after the next reload.
|
||||
*/
|
||||
|
||||
const session = {
|
||||
capabilities: { [CAP.core]: { maxCallsInRequest: 16, maxObjectsInGet: 500 }, [CAP.contacts]: {} },
|
||||
accounts: { own: { name: "[email protected]", isPersonal: true, accountCapabilities: { [CAP.contacts]: {} } } },
|
||||
primaryAccounts: { [CAP.contacts]: "own" },
|
||||
state: "s",
|
||||
} as unknown as JmapSession;
|
||||
|
||||
beforeEach(() => {
|
||||
client.session = session;
|
||||
useSession.setState({ status: "authenticated", session, accountId: "own" });
|
||||
useContacts.setState({ accountId: null, loaded: false, loading: false, cards: {}, cardState: null });
|
||||
vi.stubGlobal("fetch", vi.fn(async (_url: string, init: RequestInit) => {
|
||||
const { methodCalls } = JSON.parse(init.body as string) as { methodCalls: [string, Record<string, unknown>, string][] };
|
||||
const methodResponses = methodCalls.map(([name, , id]) => {
|
||||
if (name === "ContactCard/query") return [name, { ids: ["c1"], total: 1, position: 0, queryState: "q" }, id];
|
||||
if (name === "ContactCard/get") return [name, { state: "5", list: [{ id: "c1", emails: { e: { address: "[email protected]" } }, media: { p: { kind: "photo", uri: "data:image/jpeg;base64,AA" } } }], notFound: [] }, id];
|
||||
return [name, { state: "1", list: [], notFound: [] }, id];
|
||||
});
|
||||
return { ok: true, status: 200, json: async () => ({ methodResponses, sessionState: "s" }) } as Response;
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe("contacts at sign-in", () => {
|
||||
it("loads the cards, so an avatar can be found without opening Contacts", async () => {
|
||||
await useContacts.getState().init();
|
||||
await vi.waitFor(() => expect(useContacts.getState().loaded).toBe(true));
|
||||
const card = useContacts.getState().lookupByEmail("[email protected]");
|
||||
expect(card?.id).toBe("c1");
|
||||
});
|
||||
});
|
||||
@@ -277,6 +277,13 @@ export const useContacts = create<ContactsState>((set, get) => ({
|
||||
if (!available) return;
|
||||
await get().loadBooks();
|
||||
void get().loadShared();
|
||||
/*
|
||||
* The cards too, in the background. The avatars in the mail list come from
|
||||
* them, and nothing else loaded them until Contacts was opened or an
|
||||
* address was typed -- so a photo appeared once somebody did either, and
|
||||
* was gone again after the next reload (#376).
|
||||
*/
|
||||
if (!get().loaded) void get().loadAll();
|
||||
},
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user