From 830ac39900144a431a3594624c8eb6af7738a437 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 9 May 2026 19:52:39 +0200 Subject: [PATCH] feat(documents): zod validators for folder CRUD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit createFolderSchema, renameFolderSchema, moveFolderSchema, moveDocumentToFolderSchema. Names: 1–200 chars, non-whitespace. parentId/folderId nullable to allow root. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/validators/document-folders.ts | 28 +++++++++++++ .../unit/document-folders-validators.test.ts | 42 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/lib/validators/document-folders.ts create mode 100644 tests/unit/document-folders-validators.test.ts diff --git a/src/lib/validators/document-folders.ts b/src/lib/validators/document-folders.ts new file mode 100644 index 00000000..42b0b540 --- /dev/null +++ b/src/lib/validators/document-folders.ts @@ -0,0 +1,28 @@ +import { z } from 'zod'; + +const folderName = z + .string() + .min(1, 'Folder name is required') + .max(200, 'Folder name cannot exceed 200 characters') + .refine((s) => s.trim().length > 0, 'Folder name cannot be only whitespace'); + +export const createFolderSchema = z.object({ + name: folderName, + parentId: z.string().nullable(), +}); +export type CreateFolderInput = z.infer; + +export const renameFolderSchema = z.object({ + name: folderName, +}); +export type RenameFolderInput = z.infer; + +export const moveFolderSchema = z.object({ + parentId: z.string().nullable(), +}); +export type MoveFolderInput = z.infer; + +export const moveDocumentToFolderSchema = z.object({ + folderId: z.string().nullable(), +}); +export type MoveDocumentToFolderInput = z.infer; diff --git a/tests/unit/document-folders-validators.test.ts b/tests/unit/document-folders-validators.test.ts new file mode 100644 index 00000000..533f1c05 --- /dev/null +++ b/tests/unit/document-folders-validators.test.ts @@ -0,0 +1,42 @@ +import { describe, it, expect } from 'vitest'; +import { + createFolderSchema, + renameFolderSchema, + moveFolderSchema, + moveDocumentToFolderSchema, +} from '@/lib/validators/document-folders'; + +describe('document-folder validators', () => { + it('accepts a valid create payload', () => { + expect(createFolderSchema.safeParse({ name: 'Deals', parentId: null }).success).toBe(true); + expect( + createFolderSchema.safeParse({ name: 'Q1', parentId: 'abc-123' }).success, + ).toBe(true); + }); + + it('rejects empty + over-long names', () => { + expect(createFolderSchema.safeParse({ name: '', parentId: null }).success).toBe(false); + expect( + createFolderSchema.safeParse({ name: 'x'.repeat(201), parentId: null }).success, + ).toBe(false); + }); + + it('rejects whitespace-only names', () => { + expect(createFolderSchema.safeParse({ name: ' ', parentId: null }).success).toBe(false); + }); + + it('rename schema requires only name', () => { + expect(renameFolderSchema.safeParse({ name: 'New' }).success).toBe(true); + expect(renameFolderSchema.safeParse({ name: '' }).success).toBe(false); + }); + + it('move schema accepts null parentId', () => { + expect(moveFolderSchema.safeParse({ parentId: null }).success).toBe(true); + expect(moveFolderSchema.safeParse({ parentId: 'abc' }).success).toBe(true); + }); + + it('moveDocumentToFolderSchema accepts null folderId', () => { + expect(moveDocumentToFolderSchema.safeParse({ folderId: null }).success).toBe(true); + expect(moveDocumentToFolderSchema.safeParse({ folderId: 'abc' }).success).toBe(true); + }); +});