From fedda0894ddb96e332dd2bdc2895f178f41a75cb Mon Sep 17 00:00:00 2001 From: YetAnotherSuite Dev Date: Mon, 20 Jul 2026 22:41:13 +0200 Subject: [PATCH] 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 --- apps/api/src/__tests__/auth.test.ts | 8 ++ apps/api/vitest.config.ts | 7 + apps/web/src/__tests__/notes-store.test.ts | 59 ++++++++ apps/web/src/__tests__/tasks-store.test.ts | 76 +++++++++++ apps/web/src/test-setup.ts | 21 +++ packages/types/package.json | 5 +- packages/types/src/__tests__/node.test.ts | 128 ++++++++++++++++++ packages/types/src/__tests__/user.test.ts | 71 ++++++++++ .../types/src/__tests__/workspace.test.ts | 69 ++++++++++ packages/types/src/node.ts | 26 ++-- packages/types/src/workspace.ts | 6 +- packages/ui/package.json | 8 +- packages/ui/src/__tests__/button.test.tsx | 50 +++++++ packages/ui/src/__tests__/dialog.test.tsx | 19 +++ packages/ui/src/__tests__/setup.ts | 1 + packages/ui/vitest.config.ts | 9 ++ pnpm-lock.yaml | 15 ++ turbo.json | 1 - 18 files changed, 562 insertions(+), 17 deletions(-) create mode 100644 apps/api/src/__tests__/auth.test.ts create mode 100644 apps/api/vitest.config.ts create mode 100644 apps/web/src/__tests__/notes-store.test.ts create mode 100644 apps/web/src/__tests__/tasks-store.test.ts create mode 100644 packages/types/src/__tests__/node.test.ts create mode 100644 packages/types/src/__tests__/user.test.ts create mode 100644 packages/types/src/__tests__/workspace.test.ts create mode 100644 packages/ui/src/__tests__/button.test.tsx create mode 100644 packages/ui/src/__tests__/dialog.test.tsx create mode 100644 packages/ui/src/__tests__/setup.ts create mode 100644 packages/ui/vitest.config.ts diff --git a/apps/api/src/__tests__/auth.test.ts b/apps/api/src/__tests__/auth.test.ts new file mode 100644 index 0000000..23d12a5 --- /dev/null +++ b/apps/api/src/__tests__/auth.test.ts @@ -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'); +}); diff --git a/apps/api/vitest.config.ts b/apps/api/vitest.config.ts new file mode 100644 index 0000000..7382f40 --- /dev/null +++ b/apps/api/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + }, +}); diff --git a/apps/web/src/__tests__/notes-store.test.ts b/apps/web/src/__tests__/notes-store.test.ts new file mode 100644 index 0000000..b1b0eb5 --- /dev/null +++ b/apps/web/src/__tests__/notes-store.test.ts @@ -0,0 +1,59 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { useNotesStore } from '../stores/notes'; + +function createNote(overrides: Record = {}) { + 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'); + }); +}); diff --git a/apps/web/src/__tests__/tasks-store.test.ts b/apps/web/src/__tests__/tasks-store.test.ts new file mode 100644 index 0000000..5175e06 --- /dev/null +++ b/apps/web/src/__tests__/tasks-store.test.ts @@ -0,0 +1,76 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { useTasksStore, type Task } from '../stores/tasks'; + +function createTask(overrides: Partial = {}): 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); + }); +}); diff --git a/apps/web/src/test-setup.ts b/apps/web/src/test-setup.ts index 7b0828b..50faced 100644 --- a/apps/web/src/test-setup.ts +++ b/apps/web/src/test-setup.ts @@ -1 +1,22 @@ import '@testing-library/jest-dom'; + +const localStorageMock = (() => { + let store: Record = {}; + 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, + }); +} diff --git a/packages/types/package.json b/packages/types/package.json index 323713b..ff1e4ab 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -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" } } diff --git a/packages/types/src/__tests__/node.test.ts b/packages/types/src/__tests__/node.test.ts new file mode 100644 index 0000000..2c655a5 --- /dev/null +++ b/packages/types/src/__tests__/node.test.ts @@ -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); + }); +}); diff --git a/packages/types/src/__tests__/user.test.ts b/packages/types/src/__tests__/user.test.ts new file mode 100644 index 0000000..6f96e99 --- /dev/null +++ b/packages/types/src/__tests__/user.test.ts @@ -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); + }); +}); diff --git a/packages/types/src/__tests__/workspace.test.ts b/packages/types/src/__tests__/workspace.test.ts new file mode 100644 index 0000000..2360a5e --- /dev/null +++ b/packages/types/src/__tests__/workspace.test.ts @@ -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); + }); +}); diff --git a/packages/types/src/node.ts b/packages/types/src/node.ts index c37824c..b0ac8d3 100644 --- a/packages/types/src/node.ts +++ b/packages/types/src/node.ts @@ -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, }); diff --git a/packages/types/src/workspace.ts b/packages/types/src/workspace.ts index 29f2176..33b9aa3 100644 --- a/packages/types/src/workspace.ts +++ b/packages/types/src/workspace.ts @@ -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; diff --git a/packages/ui/package.json b/packages/ui/package.json index fda3ff2..3b17660 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -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" } } diff --git a/packages/ui/src/__tests__/button.test.tsx b/packages/ui/src/__tests__/button.test.tsx new file mode 100644 index 0000000..298ca03 --- /dev/null +++ b/packages/ui/src/__tests__/button.test.tsx @@ -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(); + expect(screen.getByText('Click me')).toBeInTheDocument(); + }); + + it('applies default variant classes', () => { + render(); + const button = screen.getByText('Default'); + expect(button.className).toContain('bg-primary'); + }); + + it('applies variant classes', () => { + render(); + const button = screen.getByText('Delete'); + expect(button.className).toContain('bg-destructive'); + }); + + it('applies size classes', () => { + render(); + const button = screen.getByText('Large'); + expect(button.className).toContain('h-11'); + }); + + it('renders as child when asChild is true', () => { + render( + , + ); + const link = screen.getByText('Link'); + expect(link.tagName).toBe('A'); + }); + + it('forwards ref', () => { + const ref = { current: null }; + render(); + expect(ref.current).toBeInstanceOf(HTMLButtonElement); + }); + + it('can be disabled', () => { + render(); + const button = screen.getByText('Disabled'); + expect(button).toBeDisabled(); + }); +}); diff --git a/packages/ui/src/__tests__/dialog.test.tsx b/packages/ui/src/__tests__/dialog.test.tsx new file mode 100644 index 0000000..ee72ded --- /dev/null +++ b/packages/ui/src/__tests__/dialog.test.tsx @@ -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( + + Open + + + Title + + + , + ); + expect(screen.getByText('Open')).toBeInTheDocument(); + }); +}); diff --git a/packages/ui/src/__tests__/setup.ts b/packages/ui/src/__tests__/setup.ts new file mode 100644 index 0000000..7b0828b --- /dev/null +++ b/packages/ui/src/__tests__/setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/packages/ui/vitest.config.ts b/packages/ui/vitest.config.ts new file mode 100644 index 0000000..0d20628 --- /dev/null +++ b/packages/ui/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'jsdom', + globals: true, + setupFiles: './src/__tests__/setup.ts', + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8177698..da42030 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/turbo.json b/turbo.json index 3b784bc..d092e8f 100644 --- a/turbo.json +++ b/turbo.json @@ -17,7 +17,6 @@ "dependsOn": ["^build"] }, "test": { - "dependsOn": ["^build"], "outputs": ["coverage/**"] }, "clean": {