From d0a26126cc8f15147c9960203d723a0b4fbb2890 Mon Sep 17 00:00:00 2001 From: Amit Haridas Date: Fri, 5 Jun 2026 18:25:13 +0530 Subject: [PATCH] feat(renderer): zod validators for settings + export schemas --- src/renderer/lib/validators.ts | 48 ++++++++++++++++++++++++++++ tests/unit/lib/validators.test.ts | 53 +++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/renderer/lib/validators.ts create mode 100644 tests/unit/lib/validators.test.ts diff --git a/src/renderer/lib/validators.ts b/src/renderer/lib/validators.ts new file mode 100644 index 0000000..5000c1c --- /dev/null +++ b/src/renderer/lib/validators.ts @@ -0,0 +1,48 @@ +import { z } from 'zod'; + +export const settingsSchema = z.object({ + fontSize: z.number().min(10).max(24).default(14), + tabSize: z.union([z.literal(2), z.literal(4), z.literal(8)]).default(4), + lineNumbers: z.boolean().default(true), + wordWrap: z.boolean().default(true), + minimap: z.boolean().default(true), + theme: z.enum(['light', 'dark', 'auto']).default('auto'), + accentColor: z.enum(['brand', 'blue', 'green', 'purple', 'orange']).default('brand'), + fontFamily: z.enum(['system', 'jetbrains', 'fira']).default('system'), + pdfFormat: z.enum(['letter', 'a4', 'legal']).default('a4'), + pdfMargins: z.enum(['normal', 'narrow', 'wide']).default('normal'), + pdfEmbedFonts: z.boolean().default(true), + docxTemplate: z.enum(['standard', 'minimal', 'modern']).default('standard'), + htmlHighlightStyle: z.enum(['github', 'monokai', 'nord', 'none']).default('github'), + renderTablesAsAscii: z.boolean().default(false), + welcomeDismissed: z.boolean().default(false), +}); +export type Settings = z.infer; + +export const exportPdfSchema = z.object({ + format: z.enum(['letter', 'a4', 'legal']), + margins: z.enum(['normal', 'narrow', 'wide']), + embedFonts: z.boolean(), + renderTablesAsAscii: z.boolean().optional(), +}); +export type ExportPdfOptions = z.infer; + +export const exportDocxSchema = z.object({ + template: z.enum(['standard', 'minimal', 'modern']), + renderTablesAsAscii: z.boolean().optional(), +}); +export type ExportDocxOptions = z.infer; + +export const exportHtmlSchema = z.object({ + standalone: z.boolean(), + highlightStyle: z.enum(['github', 'monokai', 'nord', 'none']), + renderTablesAsAscii: z.boolean().optional(), +}); +export type ExportHtmlOptions = z.infer; + +export const exportBatchSchema = z.object({ + format: z.enum(['pdf', 'docx', 'html', 'png']), + concurrency: z.number().int().min(1).max(16), + filePaths: z.array(z.string()).min(1), +}); +export type ExportBatchOptions = z.infer; diff --git a/tests/unit/lib/validators.test.ts b/tests/unit/lib/validators.test.ts new file mode 100644 index 0000000..49b4990 --- /dev/null +++ b/tests/unit/lib/validators.test.ts @@ -0,0 +1,53 @@ +import { describe, it, expect } from 'vitest'; +import { + settingsSchema, + exportPdfSchema, + exportDocxSchema, + exportHtmlSchema, + exportBatchSchema, +} from '@/lib/validators'; + +describe('settingsSchema', () => { + it('accepts an empty object (all fields have defaults)', () => { + const r = settingsSchema.safeParse({}); + expect(r.success).toBe(true); + }); + + it('rejects out-of-range fontSize', () => { + const r = settingsSchema.safeParse({ fontSize: 100 }); + expect(r.success).toBe(false); + }); +}); + +describe('exportPdfSchema', () => { + it('accepts a4 + normal margins + embedFonts true', () => { + expect(exportPdfSchema.safeParse({ format: 'a4', margins: 'normal', embedFonts: true }).success).toBe(true); + }); + it('rejects unknown format', () => { + expect(exportPdfSchema.safeParse({ format: 'b4' }).success).toBe(false); + }); +}); + +describe('exportDocxSchema', () => { + it('accepts standard template', () => { + expect(exportDocxSchema.safeParse({ template: 'standard' }).success).toBe(true); + }); + it('rejects unknown template', () => { + expect(exportDocxSchema.safeParse({ template: 'fancy' }).success).toBe(false); + }); +}); + +describe('exportHtmlSchema', () => { + it('accepts github highlight style', () => { + expect(exportHtmlSchema.safeParse({ standalone: true, highlightStyle: 'github' }).success).toBe(true); + }); +}); + +describe('exportBatchSchema', () => { + it('accepts a non-empty file list', () => { + expect(exportBatchSchema.safeParse({ format: 'pdf', concurrency: 4, filePaths: ['/a.md'] }).success).toBe(true); + }); + it('rejects empty file list', () => { + expect(exportBatchSchema.safeParse({ format: 'pdf', concurrency: 4, filePaths: [] }).success).toBe(false); + }); +});