/** * Regression: the members wire row is FLAT (the API's workspaces.service.ts * `listMembers` returns `{ userId, email, name, displayName, avatarUrl, role, * joinedAt }`), but the web `WorkspaceMember` model and every consumer of it * expect a NESTED `user` object. Reading `m.user.name` on the flat row threw * "Cannot read properties of undefined (reading 'name')" and crashed the * Settings + Members pages under the Next.js dev error overlay (the user's * "grey screen + popup"). `normalizeWorkspaceMember` bridges the two at the * single seam (`workspaceApi.members`); these tests pin the mapping. */ import { describe, it, expect } from "vitest"; import { normalizeWorkspaceMember } from "../api"; describe("normalizeWorkspaceMember (flat wire → nested model)", () => { it("maps userId → user.id, name/email straight, and avatarUrl → image", () => { const m = normalizeWorkspaceMember({ userId: "usr_1", email: "demo@factory.local", name: "Demo User", displayName: "Demo", avatarUrl: "https://x/img.png", role: "OWNER", joinedAt: "2024-01-02T03:04:05.000Z" }); expect(m.userId).toBe("usr_1"); expect(m.user).toEqual({ id: "usr_1", name: "Demo User", email: "demo@factory.local", image: "https://x/img.png" }); expect(m.joinedAt).toBe("2024-01-02T03:04:05.000Z"); }); it("lowercases the UPPERCASE role enum into the core lowercase type", () => { for (const [wire, core] of [ ["OWNER", "owner"], ["ADMIN", "admin"], ["MEMBER", "member"], ["GUEST", "guest"] ] as const) { expect(normalizeWorkspaceMember({ userId: "u", email: "e@x", name: "n", role: wire, joinedAt: null }).role).toBe(core); } }); it("nulls an absent avatarUrl (image → null, not undefined)", () => { const m = normalizeWorkspaceMember({ userId: "u", email: "e@x", name: "n", avatarUrl: null, role: "member", joinedAt: "2024-05-05T00:00:00.000Z" }); expect(m.user.image).toBeNull(); }); it("coerces a null joinedAt to the epoch ISO string (never undefined — consumers format it)", () => { const m = normalizeWorkspaceMember({ userId: "u", email: "e@x", name: "n", role: "owner", joinedAt: null }); expect(m.joinedAt).toBe(new Date(0).toISOString()); }); });