test: add 48 unit tests across types, ui, and web packages
packages/types: 27 tests - Zod schema validation for user, node, workspace, tag, link - Default values, required fields, enum constraints, edge cases packages/ui: 8 tests - Button rendering, variants, sizes, asChild, ref forwarding, disabled - Dialog trigger rendering apps/web: 13 tests - Notes store CRUD operations (add, update, remove, select, search) - Tasks store operations (add, toggle complete, priority, remove, view switching, projects) apps/api: 4 placeholders for DB-backed integration tests Infrastructure: - vitest configs for packages/types and packages/ui - jsdom test environment for UI components - localStorage mock for Zustand persist middleware in jsdom - crypto.randomUUID polyfill for test environment - Fixed create schemas to make non-essential fields optional
This commit is contained in:
parent
93489d5c2b
commit
fedda0894d
8
apps/api/src/__tests__/auth.test.ts
Normal file
8
apps/api/src/__tests__/auth.test.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { describe, it } from 'vitest';
|
||||
|
||||
describe('auth routes', () => {
|
||||
it.todo('registers a new user and returns token');
|
||||
it.todo('health check returns ok');
|
||||
it.todo('rejects login with invalid credentials');
|
||||
it.todo('requires authentication for /me');
|
||||
});
|
||||
7
apps/api/vitest.config.ts
Normal file
7
apps/api/vitest.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
},
|
||||
});
|
||||
59
apps/web/src/__tests__/notes-store.test.ts
Normal file
59
apps/web/src/__tests__/notes-store.test.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useNotesStore } from '../stores/notes';
|
||||
|
||||
function createNote(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
title: 'Test Note',
|
||||
type: 'note' as const,
|
||||
content: { type: 'doc', content: [] },
|
||||
plainText: '',
|
||||
status: 'active' as const,
|
||||
workspaceId: 'ws-1',
|
||||
tags: [],
|
||||
folderId: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('useNotesStore', () => {
|
||||
beforeEach(() => {
|
||||
useNotesStore.setState({ notes: [], folders: [], selectedNoteId: null, searchQuery: '', loading: false });
|
||||
});
|
||||
|
||||
it('starts with empty notes', () => {
|
||||
expect(useNotesStore.getState().notes).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('adds a note', () => {
|
||||
const note = createNote();
|
||||
useNotesStore.getState().addNote(note);
|
||||
expect(useNotesStore.getState().notes).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('updates a note', () => {
|
||||
const note = createNote();
|
||||
useNotesStore.getState().addNote(note);
|
||||
useNotesStore.getState().updateNote(note.id, { title: 'Updated' });
|
||||
expect(useNotesStore.getState().notes[0]?.title).toBe('Updated');
|
||||
});
|
||||
|
||||
it('removes a note', () => {
|
||||
const note = createNote();
|
||||
useNotesStore.getState().addNote(note);
|
||||
useNotesStore.getState().removeNote(note.id);
|
||||
expect(useNotesStore.getState().notes).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('sets selected note', () => {
|
||||
useNotesStore.getState().setSelectedNoteId('note-1');
|
||||
expect(useNotesStore.getState().selectedNoteId).toBe('note-1');
|
||||
});
|
||||
|
||||
it('sets search query', () => {
|
||||
useNotesStore.getState().setSearchQuery('hello');
|
||||
expect(useNotesStore.getState().searchQuery).toBe('hello');
|
||||
});
|
||||
});
|
||||
76
apps/web/src/__tests__/tasks-store.test.ts
Normal file
76
apps/web/src/__tests__/tasks-store.test.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useTasksStore, type Task } from '../stores/tasks';
|
||||
|
||||
function createTask(overrides: Partial<Task> = {}): Task {
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
title: 'Test Task',
|
||||
description: '',
|
||||
priority: null,
|
||||
status: 'active',
|
||||
dueDate: null,
|
||||
durationMinutes: null,
|
||||
tags: [],
|
||||
subtasks: [],
|
||||
projectId: null,
|
||||
parentId: null,
|
||||
workspaceId: 'ws-1',
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
completedAt: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('useTasksStore', () => {
|
||||
beforeEach(() => {
|
||||
useTasksStore.setState({ tasks: [], projects: [], view: 'inbox', selectedTaskId: null });
|
||||
});
|
||||
|
||||
it('adds a task', () => {
|
||||
useTasksStore.getState().addTask(createTask());
|
||||
expect(useTasksStore.getState().tasks).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('toggles task completion', () => {
|
||||
const task = createTask();
|
||||
useTasksStore.getState().addTask(task);
|
||||
useTasksStore.getState().toggleComplete(task.id);
|
||||
expect(useTasksStore.getState().tasks[0]?.status).toBe('completed');
|
||||
expect(useTasksStore.getState().tasks[0]?.completedAt).toBeTruthy();
|
||||
});
|
||||
|
||||
it('un-completes a completed task', () => {
|
||||
const task = createTask({ status: 'completed', completedAt: new Date().toISOString() });
|
||||
useTasksStore.getState().addTask(task);
|
||||
useTasksStore.getState().toggleComplete(task.id);
|
||||
expect(useTasksStore.getState().tasks[0]?.status).toBe('active');
|
||||
expect(useTasksStore.getState().tasks[0]?.completedAt).toBeNull();
|
||||
});
|
||||
|
||||
it('updates task priority', () => {
|
||||
const task = createTask();
|
||||
useTasksStore.getState().addTask(task);
|
||||
useTasksStore.getState().updateTask(task.id, { priority: 1 });
|
||||
expect(useTasksStore.getState().tasks[0]?.priority).toBe(1);
|
||||
});
|
||||
|
||||
it('removes a task', () => {
|
||||
const task = createTask();
|
||||
useTasksStore.getState().addTask(task);
|
||||
useTasksStore.getState().removeTask(task.id);
|
||||
expect(useTasksStore.getState().tasks).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('switches views', () => {
|
||||
useTasksStore.getState().setView('today');
|
||||
expect(useTasksStore.getState().view).toBe('today');
|
||||
});
|
||||
|
||||
it('adds a project', () => {
|
||||
useTasksStore.getState().addProject({ id: 'p1', name: 'Project 1', color: '#ff0000' });
|
||||
expect(useTasksStore.getState().projects).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@ -1 +1,22 @@
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
const localStorageMock = (() => {
|
||||
let store: Record<string, string> = {};
|
||||
return {
|
||||
getItem: (key: string) => store[key] ?? null,
|
||||
setItem: (key: string, value: string) => { store[key] = value; },
|
||||
removeItem: (key: string) => { delete store[key]; },
|
||||
clear: () => { store = {}; },
|
||||
get length() { return Object.keys(store).length; },
|
||||
key: (index: number) => Object.keys(store)[index] ?? null,
|
||||
};
|
||||
})();
|
||||
|
||||
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
|
||||
|
||||
if (!globalThis.crypto?.randomUUID) {
|
||||
Object.defineProperty(globalThis, 'crypto', {
|
||||
value: { randomUUID: () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = Math.random() * 16 | 0; return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); }) },
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
"scripts": {
|
||||
"lint": "eslint src/",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"clean": "rm -rf .turbo node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -16,6 +18,7 @@
|
||||
"@yetanother/tsconfig": "workspace:*",
|
||||
"@yetanother/eslint-config": "workspace:*",
|
||||
"typescript": "^5.8.0",
|
||||
"eslint": "^9.25.0"
|
||||
"eslint": "^9.25.0",
|
||||
"vitest": "^3.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
128
packages/types/src/__tests__/node.test.ts
Normal file
128
packages/types/src/__tests__/node.test.ts
Normal file
@ -0,0 +1,128 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { nodeSchema, createNodeSchema, nodeLinkSchema, tagSchema } from '../node';
|
||||
|
||||
const UUID = '550e8400-e29b-41d4-a716-446655440000';
|
||||
const TS = '2024-01-01T00:00:00.000Z';
|
||||
|
||||
describe('nodeSchema', () => {
|
||||
const validNode = {
|
||||
id: UUID,
|
||||
type: 'note' as const,
|
||||
workspaceId: UUID,
|
||||
ownerId: UUID,
|
||||
parentId: null,
|
||||
title: 'Test Note',
|
||||
content: { type: 'doc' },
|
||||
plainText: '',
|
||||
status: 'active' as const,
|
||||
priority: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
durationMinutes: null,
|
||||
recurrenceRule: null,
|
||||
completionRate: null,
|
||||
metadata: {},
|
||||
version: 1,
|
||||
isEncrypted: false,
|
||||
createdAt: TS,
|
||||
updatedAt: TS,
|
||||
completedAt: null,
|
||||
archivedAt: null,
|
||||
};
|
||||
|
||||
it('validates a complete node', () => {
|
||||
expect(nodeSchema.safeParse(validNode).success).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects invalid node type', () => {
|
||||
expect(nodeSchema.safeParse({ ...validNode, type: 'invalid' }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects empty title', () => {
|
||||
expect(nodeSchema.safeParse({ ...validNode, title: '' }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts all valid node types', () => {
|
||||
for (const type of ['task', 'event', 'note', 'project', 'goal'] as const) {
|
||||
expect(nodeSchema.safeParse({ ...validNode, type }).success).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects priority out of range', () => {
|
||||
expect(nodeSchema.safeParse({ ...validNode, priority: 6 }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createNodeSchema', () => {
|
||||
it('requires only type, workspaceId, and title', () => {
|
||||
const result = createNodeSchema.safeParse({
|
||||
type: 'task',
|
||||
workspaceId: UUID,
|
||||
title: 'My Task',
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects missing type', () => {
|
||||
expect(createNodeSchema.safeParse({ workspaceId: UUID, title: 'x' }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts optional fields', () => {
|
||||
const result = createNodeSchema.safeParse({
|
||||
type: 'event',
|
||||
workspaceId: UUID,
|
||||
title: 'Meeting',
|
||||
priority: 1,
|
||||
startTime: TS,
|
||||
durationMinutes: 60,
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('nodeLinkSchema', () => {
|
||||
const baseLink = {
|
||||
id: UUID,
|
||||
sourceId: UUID,
|
||||
targetId: UUID,
|
||||
createdAt: TS,
|
||||
};
|
||||
|
||||
it('validates a node link', () => {
|
||||
const result = nodeLinkSchema.safeParse({
|
||||
...baseLink,
|
||||
linkType: 'references',
|
||||
strength: 0.8,
|
||||
isAuto: false,
|
||||
context: null,
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('accepts all link types', () => {
|
||||
for (const linkType of ['references', 'blocks', 'relates_to', 'parent_of', 'child_of', 'scheduled_as', 'prepared_for'] as const) {
|
||||
expect(nodeLinkSchema.safeParse({ ...baseLink, linkType, context: null }).success).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('tagSchema', () => {
|
||||
it('validates a tag', () => {
|
||||
const result = tagSchema.safeParse({
|
||||
id: UUID,
|
||||
name: 'urgent',
|
||||
color: '#ff0000',
|
||||
workspaceId: UUID,
|
||||
isSystem: false,
|
||||
createdAt: TS,
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects invalid hex color', () => {
|
||||
expect(tagSchema.safeParse({
|
||||
id: UUID, name: 'urgent', color: 'red',
|
||||
workspaceId: UUID, isSystem: false, createdAt: TS,
|
||||
}).success).toBe(false);
|
||||
});
|
||||
});
|
||||
71
packages/types/src/__tests__/user.test.ts
Normal file
71
packages/types/src/__tests__/user.test.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { userSchema, createUserSchema, updateUserSchema } from '../user';
|
||||
|
||||
const UUID = '550e8400-e29b-41d4-a716-446655440000';
|
||||
const TS = '2024-01-01T00:00:00.000Z';
|
||||
|
||||
describe('userSchema', () => {
|
||||
const validUser = {
|
||||
id: UUID,
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
avatarUrl: null,
|
||||
preferences: {
|
||||
theme: 'system' as const,
|
||||
timezone: 'UTC',
|
||||
notificationSettings: { push: true, email: true, inApp: true },
|
||||
defaultViews: { tasks: 'list' as const, calendar: 'week' as const, notes: 'list' as const },
|
||||
},
|
||||
createdAt: TS,
|
||||
updatedAt: TS,
|
||||
lastActiveAt: TS,
|
||||
};
|
||||
|
||||
it('validates a complete user object', () => {
|
||||
expect(userSchema.safeParse(validUser).success).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects invalid email', () => {
|
||||
expect(userSchema.safeParse({ ...validUser, email: 'not-an-email' }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects missing required fields', () => {
|
||||
expect(userSchema.safeParse({}).success).toBe(false);
|
||||
});
|
||||
|
||||
it('applies default preferences', () => {
|
||||
const result = userSchema.safeParse({
|
||||
id: UUID,
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test',
|
||||
avatarUrl: null,
|
||||
createdAt: TS,
|
||||
updatedAt: TS,
|
||||
lastActiveAt: TS,
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.preferences.theme).toBe('system');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('createUserSchema', () => {
|
||||
it('accepts email and displayName', () => {
|
||||
expect(createUserSchema.safeParse({ email: 'a@b.com', displayName: 'Alice' }).success).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects missing displayName', () => {
|
||||
expect(createUserSchema.safeParse({ email: 'a@b.com' }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateUserSchema', () => {
|
||||
it('accepts partial update', () => {
|
||||
expect(updateUserSchema.safeParse({ displayName: 'New Name' }).success).toBe(true);
|
||||
});
|
||||
|
||||
it('accepts empty object (no-op)', () => {
|
||||
expect(updateUserSchema.safeParse({}).success).toBe(true);
|
||||
});
|
||||
});
|
||||
69
packages/types/src/__tests__/workspace.test.ts
Normal file
69
packages/types/src/__tests__/workspace.test.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { workspaceSchema, createWorkspaceSchema, workspaceMemberSchema, workspaceRoleSchema } from '../workspace';
|
||||
|
||||
const UUID = '550e8400-e29b-41d4-a716-446655440000';
|
||||
const TS = '2024-01-01T00:00:00.000Z';
|
||||
|
||||
describe('workspaceSchema', () => {
|
||||
const valid = {
|
||||
id: UUID,
|
||||
name: 'Personal',
|
||||
slug: 'personal',
|
||||
description: null,
|
||||
settings: {},
|
||||
ownerId: UUID,
|
||||
createdAt: TS,
|
||||
updatedAt: TS,
|
||||
};
|
||||
|
||||
it('validates a workspace', () => {
|
||||
expect(workspaceSchema.safeParse(valid).success).toBe(true);
|
||||
});
|
||||
|
||||
it('applies default settings', () => {
|
||||
const result = workspaceSchema.safeParse(valid);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.settings.defaultView).toBe('list');
|
||||
expect(result.data.settings.aiEnabled).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('workspaceRoleSchema', () => {
|
||||
it('accepts all roles', () => {
|
||||
for (const role of ['owner', 'admin', 'editor', 'viewer'] as const) {
|
||||
expect(workspaceRoleSchema.safeParse(role).success).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects invalid role', () => {
|
||||
expect(workspaceRoleSchema.safeParse('superadmin').success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('workspaceMemberSchema', () => {
|
||||
it('validates a member', () => {
|
||||
const result = workspaceMemberSchema.safeParse({
|
||||
id: UUID,
|
||||
workspaceId: UUID,
|
||||
userId: UUID,
|
||||
role: 'editor',
|
||||
permissions: {},
|
||||
joinedAt: TS,
|
||||
lastAccessedAt: TS,
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createWorkspaceSchema', () => {
|
||||
it('requires name and slug', () => {
|
||||
expect(createWorkspaceSchema.safeParse({ name: 'Work', slug: 'work' }).success).toBe(true);
|
||||
expect(createWorkspaceSchema.safeParse({ name: 'Work' }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts optional description', () => {
|
||||
expect(createWorkspaceSchema.safeParse({ name: 'Work', slug: 'work', description: 'My workspace' }).success).toBe(true);
|
||||
});
|
||||
});
|
||||
@ -43,18 +43,18 @@ export const nodeSchema = z.object({
|
||||
archivedAt: timestampSchema.nullable(),
|
||||
});
|
||||
|
||||
export const createNodeSchema = nodeSchema.pick({
|
||||
type: true,
|
||||
workspaceId: true,
|
||||
title: true,
|
||||
content: true,
|
||||
priority: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
durationMinutes: true,
|
||||
recurrenceRule: true,
|
||||
parentId: true,
|
||||
metadata: true,
|
||||
export const createNodeSchema = z.object({
|
||||
type: nodeTypeSchema,
|
||||
workspaceId: uuidSchema,
|
||||
title: z.string().min(1).max(500),
|
||||
content: z.record(z.unknown()).optional(),
|
||||
priority: z.number().int().min(1).max(5).optional(),
|
||||
startTime: z.string().datetime().optional(),
|
||||
endTime: z.string().datetime().optional(),
|
||||
durationMinutes: z.number().int().min(0).optional(),
|
||||
recurrenceRule: z.string().optional(),
|
||||
parentId: uuidSchema.optional(),
|
||||
metadata: z.record(z.unknown()).optional(),
|
||||
});
|
||||
|
||||
export const updateNodeSchema = nodeSchema.partial().pick({
|
||||
@ -81,7 +81,7 @@ export const nodeLinkSchema = z.object({
|
||||
linkType: linkTypeSchema,
|
||||
strength: z.number().min(0).max(1).default(0.5),
|
||||
isAuto: z.boolean().default(false),
|
||||
context: z.string().nullable(),
|
||||
context: z.string().nullable().optional(),
|
||||
createdAt: timestampSchema,
|
||||
});
|
||||
|
||||
|
||||
@ -17,7 +17,11 @@ export const workspaceSchema = z.object({
|
||||
updatedAt: timestampSchema,
|
||||
});
|
||||
|
||||
export const createWorkspaceSchema = workspaceSchema.pick({ name: true, slug: true, description: true });
|
||||
export const createWorkspaceSchema = z.object({
|
||||
name: z.string().min(1).max(100),
|
||||
slug: z.string().min(1).max(50),
|
||||
description: z.string().max(500).optional(),
|
||||
});
|
||||
export const updateWorkspaceSchema = workspaceSchema.partial().pick({ name: true, description: true, settings: true });
|
||||
|
||||
export type Workspace = z.infer<typeof workspaceSchema>;
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
"scripts": {
|
||||
"lint": "eslint src/",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"clean": "rm -rf .turbo node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -31,6 +33,10 @@
|
||||
"@types/react": "^19.1.0",
|
||||
"typescript": "^5.8.0",
|
||||
"eslint": "^9.25.0",
|
||||
"tailwindcss": "^4.1.0"
|
||||
"tailwindcss": "^4.1.0",
|
||||
"vitest": "^3.1.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/jest-dom": "^6.6.0",
|
||||
"jsdom": "^26.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
50
packages/ui/src/__tests__/button.test.tsx
Normal file
50
packages/ui/src/__tests__/button.test.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Button } from '../button';
|
||||
|
||||
describe('Button', () => {
|
||||
it('renders with text', () => {
|
||||
render(<Button>Click me</Button>);
|
||||
expect(screen.getByText('Click me')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('applies default variant classes', () => {
|
||||
render(<Button>Default</Button>);
|
||||
const button = screen.getByText('Default');
|
||||
expect(button.className).toContain('bg-primary');
|
||||
});
|
||||
|
||||
it('applies variant classes', () => {
|
||||
render(<Button variant="destructive">Delete</Button>);
|
||||
const button = screen.getByText('Delete');
|
||||
expect(button.className).toContain('bg-destructive');
|
||||
});
|
||||
|
||||
it('applies size classes', () => {
|
||||
render(<Button size="lg">Large</Button>);
|
||||
const button = screen.getByText('Large');
|
||||
expect(button.className).toContain('h-11');
|
||||
});
|
||||
|
||||
it('renders as child when asChild is true', () => {
|
||||
render(
|
||||
<Button asChild>
|
||||
<a href="/test">Link</a>
|
||||
</Button>,
|
||||
);
|
||||
const link = screen.getByText('Link');
|
||||
expect(link.tagName).toBe('A');
|
||||
});
|
||||
|
||||
it('forwards ref', () => {
|
||||
const ref = { current: null };
|
||||
render(<Button ref={ref}>Ref</Button>);
|
||||
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
|
||||
});
|
||||
|
||||
it('can be disabled', () => {
|
||||
render(<Button disabled>Disabled</Button>);
|
||||
const button = screen.getByText('Disabled');
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
});
|
||||
19
packages/ui/src/__tests__/dialog.test.tsx
Normal file
19
packages/ui/src/__tests__/dialog.test.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Dialog, DialogTrigger, DialogContent, DialogTitle, DialogHeader } from '../dialog';
|
||||
|
||||
describe('Dialog', () => {
|
||||
it('renders trigger button', () => {
|
||||
render(
|
||||
<Dialog>
|
||||
<DialogTrigger>Open</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Title</DialogTitle>
|
||||
</DialogHeader>
|
||||
</DialogContent>
|
||||
</Dialog>,
|
||||
);
|
||||
expect(screen.getByText('Open')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
1
packages/ui/src/__tests__/setup.ts
Normal file
1
packages/ui/src/__tests__/setup.ts
Normal file
@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom';
|
||||
9
packages/ui/vitest.config.ts
Normal file
9
packages/ui/vitest.config.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
globals: true,
|
||||
setupFiles: './src/__tests__/setup.ts',
|
||||
},
|
||||
});
|
||||
@ -380,6 +380,9 @@ importers:
|
||||
typescript:
|
||||
specifier: ^5.8.0
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^3.1.0
|
||||
version: 3.2.7(@types/node@22.20.1)(jiti@2.7.0)(jsdom@26.1.0(supports-color@7.2.0))(lightningcss@1.32.0)(supports-color@7.2.0)(tsx@4.23.1)(yaml@2.9.0)
|
||||
|
||||
packages/ui:
|
||||
dependencies:
|
||||
@ -426,6 +429,12 @@ importers:
|
||||
specifier: ^3.2.0
|
||||
version: 3.6.0
|
||||
devDependencies:
|
||||
'@testing-library/jest-dom':
|
||||
specifier: ^6.6.0
|
||||
version: 6.9.1
|
||||
'@testing-library/react':
|
||||
specifier: ^16.3.0
|
||||
version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@types/react':
|
||||
specifier: ^19.1.0
|
||||
version: 19.2.17
|
||||
@ -438,12 +447,18 @@ importers:
|
||||
eslint:
|
||||
specifier: ^9.25.0
|
||||
version: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
jsdom:
|
||||
specifier: ^26.0.0
|
||||
version: 26.1.0(supports-color@7.2.0)
|
||||
tailwindcss:
|
||||
specifier: ^4.1.0
|
||||
version: 4.3.3
|
||||
typescript:
|
||||
specifier: ^5.8.0
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^3.1.0
|
||||
version: 3.2.7(@types/node@22.20.1)(jiti@2.7.0)(jsdom@26.1.0(supports-color@7.2.0))(lightningcss@1.32.0)(supports-color@7.2.0)(tsx@4.23.1)(yaml@2.9.0)
|
||||
|
||||
packages/utils:
|
||||
dependencies:
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["coverage/**"]
|
||||
},
|
||||
"clean": {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user