import { describe, it, expect } from 'vitest'; import fc from 'fast-check'; import { generateIntroduction } from './intro-generator'; import type { EventContext, IntroductionInput } from './intro-generator'; import type { Person, Skill, Experience, SkillCategory, SkillLevel } from '../schemas/types'; // --- Arbitraries --- const kebabCaseId = fc .array( fc.stringMatching(/^[a-z0-9]{1,8}$/), { minLength: 1, maxLength: 4 } ) .map(parts => parts.join('-')); const isoDate = fc .record({ year: fc.integer({ min: 1990, max: 2030 }), month: fc.integer({ min: 1, max: 12 }), }) .map(({ year, month }) => `${year}-${String(month).padStart(2, '0')}`); const skillCategory: fc.Arbitrary = fc.constantFrom( 'programming-language', 'framework', 'methodology', 'soft-skill', 'domain' ); const skillLevel: fc.Arbitrary = fc.constantFrom( 'beginner', 'intermediate', 'advanced', 'expert' ); const eventContext: fc.Arbitrary = fc.constantFrom( 'technical', 'business', 'academic' ); const skillArb: fc.Arbitrary = fc.record({ id: kebabCaseId, type: fc.constant('skill' as const), created: isoDate, modified: isoDate, name: fc.string({ minLength: 2, maxLength: 30 }).filter(s => s.trim().length > 0), category: skillCategory, level: skillLevel, years: fc.option(fc.integer({ min: 1, max: 20 }), { nil: undefined }), }).map(fields => fields as Skill); const experienceArb: fc.Arbitrary = fc.record({ id: kebabCaseId, type: fc.constant('experience' as const), created: isoDate, modified: isoDate, title: fc.string({ minLength: 3, maxLength: 40 }).filter(s => s.trim().length > 0), organization: fc.string({ minLength: 2, maxLength: 30 }).filter(s => s.trim().length > 0), start: isoDate, end: fc.option(isoDate, { nil: null }), description: fc.option(fc.string({ minLength: 10, maxLength: 100 }), { nil: undefined }), achievements: fc.option( fc.array(fc.string({ minLength: 5, maxLength: 60 }).filter(s => s.trim().length > 0), { minLength: 1, maxLength: 3 }), { nil: undefined } ), }).map(fields => fields as Experience); const personArb: fc.Arbitrary = fc.record({ id: kebabCaseId, type: fc.constant('person' as const), created: isoDate, modified: isoDate, name: fc.record({ first: fc.string({ minLength: 2, maxLength: 15 }).filter(s => s.trim().length > 0), last: fc.string({ minLength: 2, maxLength: 15 }).filter(s => s.trim().length > 0), display: fc.string({ minLength: 4, maxLength: 30 }).filter(s => s.trim().length > 0), }), education: fc.option( fc.array(fc.record({ institution: fc.string({ minLength: 3, maxLength: 30 }).filter(s => s.trim().length > 0), degree: fc.string({ minLength: 2, maxLength: 20 }).filter(s => s.trim().length > 0), field: fc.string({ minLength: 2, maxLength: 20 }).filter(s => s.trim().length > 0), start: isoDate, end: isoDate, }), { minLength: 1, maxLength: 2 }), { nil: undefined } ), }).map(fields => fields as Person); const topicArb = fc.string({ minLength: 3, maxLength: 50 }).filter(s => s.trim().length > 0); /** Generates a complete IntroductionInput with at least 1 skill and 1 experience */ const introductionInputArb: fc.Arbitrary = fc.record({ person: personArb, topic: topicArb, eventContext: eventContext, relevantSkills: fc.array(skillArb, { minLength: 1, maxLength: 5 }), relevantExperiences: fc.array(experienceArb, { minLength: 1, maxLength: 4 }), }); // --- Property 12: Introduction length variants (short < medium < long) --- describe('Property 12: Introduction length variants', () => { it('produces exactly three variants where short.length < medium.length < long.length', () => { fc.assert( fc.property(introductionInputArb, (input) => { const result = generateIntroduction(input); // Verify exactly three variants exist expect(result).toHaveProperty('short'); expect(result).toHaveProperty('medium'); expect(result).toHaveProperty('long'); // Verify character count ordering: short < medium < long expect(result.short.length).toBeGreaterThan(0); expect(result.medium.length).toBeGreaterThan(result.short.length); expect(result.long.length).toBeGreaterThan(result.medium.length); }), { numRuns: 100 } ); }); /** * Validates: Requirements 6.2 */ });