@@ -280,14 +280,247 @@ export default function FlowSessionList() {
280280- Use generic types for repository patterns
281281- Leverage discriminated unions for state management
282282
283- ## Testing
283+ ## Required Development Validation
284+
285+ ** CRITICAL: Always run these commands before considering development complete:**
284286
285- Run tests with:
286287``` bash
288+ # 1. REQUIRED: Run linting and fix any errors
289+ npm run lint
290+
291+ # 2. REQUIRED: Fix any auto-fixable issues
292+ npm run format
293+
294+ # 3. REQUIRED: Ensure TypeScript compilation and build succeed
295+ npm run build
296+
297+ # 4. RECOMMENDED: Run tests to validate functionality
287298npm test
288299```
289300
290- Test files should be placed alongside the code they test with ` .test.ts ` or ` .test.tsx ` extensions.
301+ ** All commands must pass without errors before:**
302+ - Committing code changes
303+ - Creating pull requests
304+ - Considering a task complete
305+
306+ ## Testing
307+
308+ ### Testing Strategy
309+
310+ Tests are written using Vitest and follow a layered approach matching our data architecture:
311+
312+ ** Test Categories:**
313+ - ** Repository Layer** : Test database operations and SQL queries
314+ - ** API Layer** : Test business logic and data transformations
315+ - ** Hooks Layer** : Generally not tested (see note below)
316+
317+ ### API Layer Testing Patterns
318+
319+ When testing API functions, use this framework based on the function's complexity:
320+
321+ ** 1. Passthrough Functions (Simple Success Test)**
322+ Functions that only call repository methods without logic:
323+
324+ ``` typescript
325+ describe (' getFocusSchedules' , () => {
326+ it (' should return schedules successfully' , async () => {
327+ const mockSchedules = [{ id: ' 1' , label: ' Test' }]
328+ vi .mocked (FocusScheduleRepo .getFocusSchedules ).mockResolvedValue (mockSchedules as any )
329+
330+ const result = await FocusScheduleApi .getFocusSchedules ()
331+
332+ expect (result ).toBe (mockSchedules )
333+ })
334+ })
335+ ```
336+
337+ ** 2. Business Logic Functions**
338+ Functions with conditional logic (if statements, loops, validations):
339+
340+ ``` typescript
341+ // Test each logical branch
342+ it (' should throw error when workflow not found' , async () => {
343+ vi .mocked (WorkflowApi .getWorkflowById ).mockResolvedValue (null )
344+
345+ await expect (
346+ FocusScheduleApi .createFocusSchedule (' invalid-workflow' , mockTime , mockRecurrence )
347+ ).rejects .toThrow (' Workflow not found' )
348+ })
349+ ```
350+
351+ ** 3. Data Transformation Functions**
352+ Functions that reshape, aggregate, or transform data:
353+
354+ ``` typescript
355+ // Test data shape and transformation accuracy
356+ it (' should transform parameters correctly with all fields' , async () => {
357+ await FocusScheduleApi .createFocusSchedule (' workflow-1' , mockTime , mockRecurrence , ' Label' )
358+
359+ expect (FocusScheduleRepo .createFocusSchedule ).toHaveBeenCalledWith ({
360+ id: ' test-uuid-123' ,
361+ label: ' Label' ,
362+ scheduled_time: ' 2024-01-15T09:00:00.000Z' ,
363+ workflow_id: ' workflow-1' ,
364+ recurrence_settings: ' {"type":"daily"}' ,
365+ is_active: 1 ,
366+ })
367+ })
368+ ```
369+
370+ ** 4. Complex Functions (Logic + Transformation)**
371+ Test both business logic flows AND data shape:
372+
373+ ``` typescript
374+ describe (' formatScheduleDisplay' , () => {
375+ // Test logic branches
376+ it (' should format daily recurring schedule' , () => {
377+ const schedule = { ... baseSchedule , recurrence: { type: ' daily' } }
378+ const result = FocusScheduleApi .formatScheduleDisplay (schedule )
379+ expect (result ).toMatch (/ Daily at \d {1,2} :\d {2} (AM| PM)/ )
380+ })
381+
382+ // Test edge cases
383+ it (' should handle edge cases gracefully' , () => {
384+ const invalidSchedule = { ... baseSchedule , recurrence: { type: ' weekly' , daysOfWeek: [] } }
385+ expect (FocusScheduleApi .formatScheduleDisplay (invalidSchedule )).toBe (' Invalid schedule' )
386+ })
387+ })
388+ ```
389+
390+ ### Repository Layer Testing Patterns
391+
392+ Repository tests focus on database operations and SQL query behavior. ** Priority areas for testing:**
393+
394+ ** 1. WHERE Conditions**
395+ Test filtering logic to ensure correct data is returned:
396+
397+ ``` typescript
398+ it (' should only return active records (is_active = 1)' , async () => {
399+ // Insert mix of active/inactive records
400+ await testDb .execute (`
401+ INSERT INTO focus_schedule (id, workflow_id, is_active)
402+ VALUES ('active-1', 'workflow-1', 1), ('inactive-1', 'workflow-1', 0)
403+ ` )
404+
405+ const results = await FocusScheduleRepo .getFocusSchedules ()
406+
407+ expect (results .every (r => r .is_active === 1 )).toBe (true )
408+ expect (results .find (r => r .id === ' inactive-1' )).toBeUndefined ()
409+ })
410+ ```
411+
412+ ** 2. ORDER BY Clauses**
413+ Test sorting behavior by inserting data in non-sorted order:
414+
415+ ``` typescript
416+ it (' should order by scheduled_time ASC' , async () => {
417+ // Insert in non-chronological order
418+ await testDb .execute (`
419+ INSERT INTO focus_schedule (id, scheduled_time, workflow_id, is_active)
420+ VALUES
421+ ('late', '2024-01-17T15:00:00Z', 'workflow-1', 1),
422+ ('early', '2024-01-15T09:00:00Z', 'workflow-1', 1),
423+ ('middle', '2024-01-16T12:00:00Z', 'workflow-1', 1)
424+ ` )
425+
426+ const results = await FocusScheduleRepo .getFocusSchedules ()
427+
428+ const times = results .map (r => r .scheduled_time )
429+ expect (times ).toEqual ([... times ].sort ()) // Should be sorted
430+ })
431+ ```
432+
433+ ** 3. Data Transformations**
434+ Test field renaming, JSON parsing, and virtual field creation:
435+
436+ ``` typescript
437+ it (' should parse JSON and create virtual fields' , async () => {
438+ await testDb .execute (`
439+ INSERT INTO focus_schedule (id, recurrence_settings, workflow_id, is_active)
440+ VALUES ('test', '{"type":"daily","daysOfWeek":[1,2,3]}', 'workflow-1', 1)
441+ ` )
442+
443+ const result = await FocusScheduleRepo .getFocusScheduleById (' test' )
444+
445+ // JSON parsed into virtual field
446+ expect (result .recurrence ).toEqual ({ type: ' daily' , daysOfWeek: [1 ,2 ,3 ] })
447+ })
448+
449+ it (' should add fields from JOINs' , async () => {
450+ await testDb .execute (`
451+ INSERT INTO workflow (id, name, settings) VALUES ('wf-1', 'Deep Work', '{}');
452+ INSERT INTO focus_schedule (id, workflow_id, is_active) VALUES ('fs-1', 'wf-1', 1);
453+ ` )
454+
455+ const results = await FocusScheduleRepo .getFocusSchedulesWithWorkflow ()
456+
457+ expect (results [0 ]).toHaveProperty (' workflow_name' , ' Deep Work' )
458+ })
459+ ```
460+
461+ ** Repository Test Setup Pattern:**
462+
463+ ``` typescript
464+ import { createEbbTestDatabase } from ' @/lib/utils/testDb.util'
465+ import { getEbbDb } from ' ../ebbDb'
466+
467+ // Mock the database connection
468+ vi .mock (' ../ebbDb' , () => ({
469+ getEbbDb: vi .fn ()
470+ }))
471+
472+ // Mock UUID generation for consistent tests
473+ Object .defineProperty (globalThis , ' self' , {
474+ value: { crypto: { randomUUID: vi .fn (() => ' test-uuid-123' ) } }
475+ })
476+
477+ describe (' SomeRepo' , () => {
478+ let testDb: Database
479+
480+ beforeEach (async () => {
481+ testDb = await createEbbTestDatabase () // Schema already applied
482+ vi .mocked (getEbbDb ).mockResolvedValue (testDb )
483+ })
484+
485+ afterEach (async () => {
486+ if (testDb ) await testDb .close ()
487+ vi .clearAllMocks ()
488+ })
489+ })
490+ ```
491+
492+ ### React Query Hooks Testing Policy
493+
494+ ** Generally, do not test React Query hooks** unless otherwise directed
495+ ### Test File Organization
496+
497+ ```
498+ src/api/ebbApi/__tests__/
499+ ├── focusScheduleApi.test.ts # API layer tests
500+ └── ...
501+
502+ src/db/ebb/__tests__/
503+ ├── focusScheduleRepo.test.ts # Repository layer tests
504+ └── ...
505+
506+ # Note: src/api/hooks/ tests omitted per policy above
507+ ```
508+
509+ ### Running Tests
510+
511+ ``` bash
512+ # Run all tests
513+ npm test
514+
515+ # Run specific test file
516+ npm test -- src/api/ebbApi/__tests__/focusScheduleApi.test.ts
517+
518+ # Run tests in watch mode
519+ npm test -- --watch
520+
521+ # Run tests with coverage
522+ npm test -- --coverage
523+ ```
291524
292525
293526## Other preferences
0 commit comments