feat(#82): implement Personality Module
- Add Personality model to Prisma schema with FormalityLevel enum - Create migration and seed with 6 default personalities - Implement CRUD API with TDD approach (97.67% coverage) * PersonalitiesService: findAll, findOne, findDefault, create, update, remove * PersonalitiesController: REST endpoints with auth guards * Comprehensive test coverage (21 passing tests) - Add Personality types to shared package - Create frontend components: * PersonalitySelector: dropdown for choosing personality * PersonalityPreview: preview personality style and system prompt * PersonalityForm: create/edit personalities with validation * Settings page: manage personalities with CRUD operations - Integrate with Ollama API: * Support personalityId in chat endpoint * Auto-inject system prompt from personality * Fall back to default personality if not specified - API client for frontend personality management All tests passing with 97.67% backend coverage (exceeds 85% requirement)
This commit is contained in:
127
apps/web/src/components/domains/DomainSelector.test.tsx
Normal file
127
apps/web/src/components/domains/DomainSelector.test.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { DomainSelector } from "./DomainSelector";
|
||||
import type { Domain } from "@mosaic/shared";
|
||||
|
||||
describe("DomainSelector", () => {
|
||||
const mockDomains: Domain[] = [
|
||||
{
|
||||
id: "domain-1",
|
||||
workspaceId: "workspace-1",
|
||||
name: "Work",
|
||||
slug: "work",
|
||||
description: "Work-related tasks",
|
||||
color: "#3B82F6",
|
||||
icon: "💼",
|
||||
sortOrder: 0,
|
||||
metadata: {},
|
||||
createdAt: new Date("2026-01-28"),
|
||||
updatedAt: new Date("2026-01-28"),
|
||||
},
|
||||
{
|
||||
id: "domain-2",
|
||||
workspaceId: "workspace-1",
|
||||
name: "Personal",
|
||||
slug: "personal",
|
||||
description: null,
|
||||
color: "#10B981",
|
||||
icon: null,
|
||||
sortOrder: 1,
|
||||
metadata: {},
|
||||
createdAt: new Date("2026-01-28"),
|
||||
updatedAt: new Date("2026-01-28"),
|
||||
},
|
||||
];
|
||||
|
||||
it("should render with default placeholder", () => {
|
||||
const onChange = vi.fn();
|
||||
render(
|
||||
<DomainSelector domains={mockDomains} value={null} onChange={onChange} />
|
||||
);
|
||||
expect(screen.getByText("Select a domain")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render with custom placeholder", () => {
|
||||
const onChange = vi.fn();
|
||||
render(
|
||||
<DomainSelector
|
||||
domains={mockDomains}
|
||||
value={null}
|
||||
onChange={onChange}
|
||||
placeholder="Choose domain"
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText("Choose domain")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render all domains as options", () => {
|
||||
const onChange = vi.fn();
|
||||
render(
|
||||
<DomainSelector domains={mockDomains} value={null} onChange={onChange} />
|
||||
);
|
||||
expect(screen.getByText("💼 Work")).toBeInTheDocument();
|
||||
expect(screen.getByText("Personal")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call onChange when selection changes", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
|
||||
render(
|
||||
<DomainSelector domains={mockDomains} value={null} onChange={onChange} />
|
||||
);
|
||||
|
||||
const select = screen.getByRole("combobox");
|
||||
await user.selectOptions(select, "domain-1");
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith("domain-1");
|
||||
});
|
||||
|
||||
it("should call onChange with null when cleared", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
|
||||
render(
|
||||
<DomainSelector
|
||||
domains={mockDomains}
|
||||
value="domain-1"
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const select = screen.getByRole("combobox");
|
||||
await user.selectOptions(select, "");
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
it("should show selected value", () => {
|
||||
const onChange = vi.fn();
|
||||
render(
|
||||
<DomainSelector
|
||||
domains={mockDomains}
|
||||
value="domain-1"
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const select = screen.getByRole("combobox") as HTMLSelectElement;
|
||||
expect(select.value).toBe("domain-1");
|
||||
});
|
||||
|
||||
it("should apply custom className", () => {
|
||||
const onChange = vi.fn();
|
||||
render(
|
||||
<DomainSelector
|
||||
domains={mockDomains}
|
||||
value={null}
|
||||
onChange={onChange}
|
||||
className="custom-class"
|
||||
/>
|
||||
);
|
||||
|
||||
const select = screen.getByRole("combobox");
|
||||
expect(select.className).toContain("custom-class");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user