Files
ihasmail/web/src/store/__tests__/contacts-cards-at-start.test.ts
T
jcoffey-dev d38dee7eb9 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.
2026-09-16 11:27:47 -07:00

47 lines
2.2 KiB
TypeScript

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");
});
});