Merge feature/41-widget-hud-system (#41) into develop
Implements Widget/HUD system: - BaseWidget, WidgetRegistry, WidgetGrid - TasksWidget, CalendarWidget, QuickCaptureWidget - Layout persistence with useLayouts hooks - Comprehensive test suite
This commit is contained in:
276
apps/api/src/layouts/__tests__/layouts.service.spec.ts
Normal file
276
apps/api/src/layouts/__tests__/layouts.service.spec.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* LayoutsService Unit Tests
|
||||
* Following TDD principles
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { NotFoundException } from "@nestjs/common";
|
||||
import { LayoutsService } from "../layouts.service";
|
||||
import { PrismaService } from "../../prisma/prisma.service";
|
||||
|
||||
describe("LayoutsService", () => {
|
||||
let service: LayoutsService;
|
||||
let prisma: jest.Mocked<PrismaService>;
|
||||
|
||||
const mockWorkspaceId = "workspace-123";
|
||||
const mockUserId = "user-123";
|
||||
|
||||
const mockLayout = {
|
||||
id: "layout-1",
|
||||
workspaceId: mockWorkspaceId,
|
||||
userId: mockUserId,
|
||||
name: "Default Layout",
|
||||
isDefault: true,
|
||||
layout: [
|
||||
{ i: "tasks-1", x: 0, y: 0, w: 2, h: 2 },
|
||||
{ i: "calendar-1", x: 2, y: 0, w: 2, h: 2 },
|
||||
],
|
||||
metadata: {},
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
LayoutsService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: {
|
||||
userLayout: {
|
||||
findMany: jest.fn(),
|
||||
findFirst: jest.fn(),
|
||||
findUnique: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
updateMany: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
},
|
||||
$transaction: jest.fn((callback) => callback(prisma)),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<LayoutsService>(LayoutsService);
|
||||
prisma = module.get(PrismaService) as jest.Mocked<PrismaService>;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("findAll", () => {
|
||||
it("should return all layouts for a user", async () => {
|
||||
const mockLayouts = [mockLayout];
|
||||
prisma.userLayout.findMany.mockResolvedValue(mockLayouts);
|
||||
|
||||
const result = await service.findAll(mockWorkspaceId, mockUserId);
|
||||
|
||||
expect(result).toEqual(mockLayouts);
|
||||
expect(prisma.userLayout.findMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
workspaceId: mockWorkspaceId,
|
||||
userId: mockUserId,
|
||||
},
|
||||
orderBy: {
|
||||
isDefault: "desc",
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findDefault", () => {
|
||||
it("should return default layout", async () => {
|
||||
prisma.userLayout.findFirst.mockResolvedValueOnce(mockLayout);
|
||||
|
||||
const result = await service.findDefault(mockWorkspaceId, mockUserId);
|
||||
|
||||
expect(result).toEqual(mockLayout);
|
||||
expect(prisma.userLayout.findFirst).toHaveBeenCalledWith({
|
||||
where: {
|
||||
workspaceId: mockWorkspaceId,
|
||||
userId: mockUserId,
|
||||
isDefault: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return most recent layout if no default exists", async () => {
|
||||
prisma.userLayout.findFirst
|
||||
.mockResolvedValueOnce(null) // No default
|
||||
.mockResolvedValueOnce(mockLayout); // Most recent
|
||||
|
||||
const result = await service.findDefault(mockWorkspaceId, mockUserId);
|
||||
|
||||
expect(result).toEqual(mockLayout);
|
||||
expect(prisma.userLayout.findFirst).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("should throw NotFoundException if no layouts exist", async () => {
|
||||
prisma.userLayout.findFirst
|
||||
.mockResolvedValueOnce(null) // No default
|
||||
.mockResolvedValueOnce(null); // No layouts
|
||||
|
||||
await expect(
|
||||
service.findDefault(mockWorkspaceId, mockUserId)
|
||||
).rejects.toThrow(NotFoundException);
|
||||
});
|
||||
});
|
||||
|
||||
describe("findOne", () => {
|
||||
it("should return a layout by ID", async () => {
|
||||
prisma.userLayout.findUnique.mockResolvedValue(mockLayout);
|
||||
|
||||
const result = await service.findOne("layout-1", mockWorkspaceId, mockUserId);
|
||||
|
||||
expect(result).toEqual(mockLayout);
|
||||
expect(prisma.userLayout.findUnique).toHaveBeenCalledWith({
|
||||
where: {
|
||||
id: "layout-1",
|
||||
workspaceId: mockWorkspaceId,
|
||||
userId: mockUserId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should throw NotFoundException if layout not found", async () => {
|
||||
prisma.userLayout.findUnique.mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
service.findOne("invalid-id", mockWorkspaceId, mockUserId)
|
||||
).rejects.toThrow(NotFoundException);
|
||||
});
|
||||
});
|
||||
|
||||
describe("create", () => {
|
||||
it("should create a new layout", async () => {
|
||||
const createDto = {
|
||||
name: "New Layout",
|
||||
layout: [],
|
||||
isDefault: false,
|
||||
};
|
||||
|
||||
prisma.$transaction.mockImplementation((callback) =>
|
||||
callback({
|
||||
userLayout: {
|
||||
create: jest.fn().mockResolvedValue(mockLayout),
|
||||
updateMany: jest.fn(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const result = await service.create(mockWorkspaceId, mockUserId, createDto);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it("should unset other defaults when creating default layout", async () => {
|
||||
const createDto = {
|
||||
name: "New Default",
|
||||
layout: [],
|
||||
isDefault: true,
|
||||
};
|
||||
|
||||
const mockUpdateMany = jest.fn();
|
||||
const mockCreate = jest.fn().mockResolvedValue(mockLayout);
|
||||
|
||||
prisma.$transaction.mockImplementation((callback) =>
|
||||
callback({
|
||||
userLayout: {
|
||||
updateMany: mockUpdateMany,
|
||||
create: mockCreate,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
await service.create(mockWorkspaceId, mockUserId, createDto);
|
||||
|
||||
expect(mockUpdateMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
workspaceId: mockWorkspaceId,
|
||||
userId: mockUserId,
|
||||
isDefault: true,
|
||||
},
|
||||
data: {
|
||||
isDefault: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("update", () => {
|
||||
it("should update a layout", async () => {
|
||||
const updateDto = {
|
||||
name: "Updated Layout",
|
||||
layout: [{ i: "tasks-1", x: 1, y: 0, w: 2, h: 2 }],
|
||||
};
|
||||
|
||||
const mockUpdate = jest.fn().mockResolvedValue({ ...mockLayout, ...updateDto });
|
||||
const mockFindUnique = jest.fn().mockResolvedValue(mockLayout);
|
||||
|
||||
prisma.$transaction.mockImplementation((callback) =>
|
||||
callback({
|
||||
userLayout: {
|
||||
findUnique: mockFindUnique,
|
||||
update: mockUpdate,
|
||||
updateMany: jest.fn(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const result = await service.update(
|
||||
"layout-1",
|
||||
mockWorkspaceId,
|
||||
mockUserId,
|
||||
updateDto
|
||||
);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(mockFindUnique).toHaveBeenCalled();
|
||||
expect(mockUpdate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should throw NotFoundException if layout not found", async () => {
|
||||
const mockFindUnique = jest.fn().mockResolvedValue(null);
|
||||
|
||||
prisma.$transaction.mockImplementation((callback) =>
|
||||
callback({
|
||||
userLayout: {
|
||||
findUnique: mockFindUnique,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
await expect(
|
||||
service.update("invalid-id", mockWorkspaceId, mockUserId, {})
|
||||
).rejects.toThrow(NotFoundException);
|
||||
});
|
||||
});
|
||||
|
||||
describe("remove", () => {
|
||||
it("should delete a layout", async () => {
|
||||
prisma.userLayout.findUnique.mockResolvedValue(mockLayout);
|
||||
prisma.userLayout.delete.mockResolvedValue(mockLayout);
|
||||
|
||||
await service.remove("layout-1", mockWorkspaceId, mockUserId);
|
||||
|
||||
expect(prisma.userLayout.delete).toHaveBeenCalledWith({
|
||||
where: {
|
||||
id: "layout-1",
|
||||
workspaceId: mockWorkspaceId,
|
||||
userId: mockUserId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should throw NotFoundException if layout not found", async () => {
|
||||
prisma.userLayout.findUnique.mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
service.remove("invalid-id", mockWorkspaceId, mockUserId)
|
||||
).rejects.toThrow(NotFoundException);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user