41eb722369
- Invite flow: admin can invite users by email with role selection; accept-invite page sets password and creates the account; 72-hour token expiry; E2E tests - User deactivate/reactivate/delete: new tRPC procedures + UI buttons; deactivation revokes all active sessions immediately; delete cascades vacation/broadcast records; isActive field added via migration 20260402000000_user_isactive - Auth: block login for inactive users with audit entry - Favicon: SVG favicon + ICO/PNG fallbacks (16, 32, 180, 192, 512px); manifest updated - Dashboard: GridLayout dynamic-import loading skeleton prevents blank dark area on first login before react-grid-layout chunk is cached - Admin users: remove max-w-5xl constraint so table uses full page width - Dev: docker container restart workflow documented in LEARNINGS.md; Prisma generate must run inside the container after schema changes (named node_modules volume) Co-Authored-By: claude-flow <ruv@ruv.net>
71 lines
2.3 KiB
TypeScript
71 lines
2.3 KiB
TypeScript
/**
|
|
* Unit tests for getDashboardLayout — bug #27 regression guard.
|
|
*
|
|
* Verifies that getDashboardLayout returns null (not an empty layout) when the
|
|
* stored dashboardLayout is an empty object or contains only unknown widget
|
|
* types. This allows the client to fall back to the default stat-cards layout.
|
|
*/
|
|
|
|
import { describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("../lib/audit.js", () => ({ createAuditEntry: vi.fn() }));
|
|
vi.mock("../lib/system-settings-runtime.js", () => ({ resolveSystemSettingsRuntime: vi.fn() }));
|
|
|
|
const { getDashboardLayout } = await import(
|
|
"../router/user-self-service-procedure-support.js"
|
|
);
|
|
|
|
function makeCtx(dashboardLayout: unknown) {
|
|
return {
|
|
db: {
|
|
user: {
|
|
findUnique: vi.fn().mockResolvedValue(
|
|
dashboardLayout !== undefined ? { dashboardLayout, updatedAt: new Date() } : null,
|
|
),
|
|
},
|
|
} as never,
|
|
dbUser: { id: "user_1" },
|
|
session: null,
|
|
};
|
|
}
|
|
|
|
describe("getDashboardLayout — #27 regression", () => {
|
|
it("returns null when dashboardLayout is null (new user)", async () => {
|
|
const ctx = makeCtx(null);
|
|
const result = await getDashboardLayout(ctx as never);
|
|
expect(result.layout).toBeNull();
|
|
});
|
|
|
|
it("returns null when dashboardLayout is an empty object", async () => {
|
|
const ctx = makeCtx({});
|
|
const result = await getDashboardLayout(ctx as never);
|
|
expect(result.layout).toBeNull();
|
|
});
|
|
|
|
it("returns null when dashboardLayout has no valid widget types", async () => {
|
|
const ctx = makeCtx({
|
|
version: 1,
|
|
widgets: [{ type: "old-unknown-widget-type", id: "w1", x: 0, y: 0, w: 4, h: 3 }],
|
|
});
|
|
const result = await getDashboardLayout(ctx as never);
|
|
expect(result.layout).toBeNull();
|
|
});
|
|
|
|
it("returns null when dashboardLayout has an empty widgets array", async () => {
|
|
const ctx = makeCtx({ version: 2, gridCols: 12, widgets: [] });
|
|
const result = await getDashboardLayout(ctx as never);
|
|
expect(result.layout).toBeNull();
|
|
});
|
|
|
|
it("returns the layout when it contains at least one valid widget", async () => {
|
|
const ctx = makeCtx({
|
|
version: 2,
|
|
gridCols: 12,
|
|
widgets: [{ type: "stat-cards", id: "w1", x: 0, y: 0, w: 12, h: 3 }],
|
|
});
|
|
const result = await getDashboardLayout(ctx as never);
|
|
expect(result.layout).not.toBeNull();
|
|
expect(result.layout?.widgets).toHaveLength(1);
|
|
});
|
|
});
|