diff --git a/admin-web/test/helpers.ts b/admin-web/test/helpers.ts new file mode 100644 index 0000000..a2b1bf7 --- /dev/null +++ b/admin-web/test/helpers.ts @@ -0,0 +1,157 @@ +import { createClient } from '@supabase/supabase-js' + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL +const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY + +// Create test client with service role for admin access (only if credentials exist) +export const testSupabase = supabaseUrl && supabaseKey + ? createClient( + supabaseUrl, + supabaseKey, + { + auth: { + autoRefreshToken: false, + persistSession: false, + }, + } + ) + : null + +// Test data generators +export function generateTestTrainer(overrides = {}) { + return { + name: `Test Trainer ${Date.now()}`, + specialty: 'Test Specialty', + color: '#FF6B35', + ...overrides, + } +} + +export function generateTestWorkout(overrides = {}) { + return { + title: `Test Workout ${Date.now()}`, + category: 'full-body' as const, + level: 'Beginner' as const, + duration: 4, + calories: 45, + rounds: 8, + prep_time: 10, + work_time: 20, + rest_time: 10, + equipment: [], + music_vibe: 'electronic' as const, + exercises: [{ name: 'Test Exercise', duration: 20 }], + is_featured: false, + ...overrides, + } +} + +// Database cleanup helpers +export async function cleanupTestData() { + if (!testSupabase) { + console.warn('⚠️ No test database configured, skipping cleanup') + return + } + + // Delete test workouts (with timestamps to avoid deleting real data) + const { error: workoutsError } = await testSupabase + .from('workouts') + .delete() + .ilike('title', 'Test Workout%') + + if (workoutsError) { + console.error('Failed to cleanup test workouts:', workoutsError) + } + + // Delete test trainers + const { error: trainersError } = await testSupabase + .from('trainers') + .delete() + .ilike('name', 'Test Trainer%') + + if (trainersError) { + console.error('Failed to cleanup test trainers:', trainersError) + } + + // Delete test collections + const { error: collectionsError } = await testSupabase + .from('collections') + .delete() + .ilike('title', 'Test Collection%') + + if (collectionsError) { + console.error('Failed to cleanup test collections:', collectionsError) + } +} + +// Setup test trainer +export async function createTestTrainer(overrides = {}) { + if (!testSupabase) { + throw new Error('Test database not configured') + } + + const trainer = generateTestTrainer(overrides) + const { data, error } = await testSupabase + .from('trainers') + .insert(trainer) + .select() + .single() + + if (error) { + throw new Error(`Failed to create test trainer: ${error.message}`) + } + + return data +} + +// Setup test workout +export async function createTestWorkout(trainerId: string, overrides = {}) { + if (!testSupabase) { + throw new Error('Test database not configured') + } + + const workout = generateTestWorkout(overrides) + const { data, error } = await testSupabase + .from('workouts') + .insert({ ...workout, trainer_id: trainerId }) + .select() + .single() + + if (error) { + throw new Error(`Failed to create test workout: ${error.message}`) + } + + return data +} + +// Delete specific workout +export async function deleteWorkout(id: string) { + if (!testSupabase) { + throw new Error('Test database not configured') + } + + const { error } = await testSupabase + .from('workouts') + .delete() + .eq('id', id) + + if (error) { + throw new Error(`Failed to delete workout: ${error.message}`) + } +} + +// Delete specific trainer +export async function deleteTrainer(id: string) { + if (!testSupabase) { + throw new Error('Test database not configured') + } + + const { error } = await testSupabase + .from('trainers') + .delete() + .eq('id', id) + + if (error) { + throw new Error(`Failed to delete trainer: ${error.message}`) + } +} diff --git a/admin-web/test/setup.ts b/admin-web/test/setup.ts new file mode 100644 index 0000000..2f79c81 --- /dev/null +++ b/admin-web/test/setup.ts @@ -0,0 +1,59 @@ +import '@testing-library/jest-dom' +import { vi, beforeAll, afterAll, beforeEach } from 'vitest' + +// Check if we have test database credentials +const hasTestDb = process.env.NEXT_PUBLIC_SUPABASE_URL && + (process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) + +// Global test setup +beforeAll(async () => { + if (hasTestDb) { + console.log('🧪 Setting up test environment with real database...') + const testUrl = process.env.NEXT_PUBLIC_SUPABASE_URL + if (!testUrl?.includes('test') && !testUrl?.includes('localhost')) { + console.warn('⚠️ WARNING: Not using a test database!') + console.warn('Current URL:', testUrl) + } + } else { + console.log('🧪 Running tests with mocked database...') + } +}) + +beforeEach(async () => { + // Cleanup before each test to ensure isolation (only if we have real DB) + if (hasTestDb) { + try { + const { cleanupTestData } = await import('./helpers') + await cleanupTestData() + } catch (e) { + // Silently ignore if helpers not available + } + } +}) + +afterAll(async () => { + if (hasTestDb) { + console.log('🧹 Cleaning up test environment...') + try { + const { cleanupTestData } = await import('./helpers') + await cleanupTestData() + } catch (e) { + // Silently ignore if helpers not available + } + } +}) + +// Mock Supabase client for component tests that don't need real DB +vi.mock('@/lib/supabase', () => ({ + supabase: { + storage: { + from: vi.fn(() => ({ + upload: vi.fn(), + remove: vi.fn(), + move: vi.fn(), + list: vi.fn(), + getPublicUrl: vi.fn(() => ({ data: { publicUrl: 'https://example.com/test.jpg' } })), + })), + }, + }, +})) diff --git a/admin-web/vitest.config.ts b/admin-web/vitest.config.ts new file mode 100644 index 0000000..47d4e2a --- /dev/null +++ b/admin-web/vitest.config.ts @@ -0,0 +1,37 @@ +import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react' +import path from 'path' + +export default defineConfig({ + plugins: [react()], + test: { + environment: 'jsdom', + globals: true, + setupFiles: ['./test/setup.ts'], + include: ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/', + 'test/', + '**/*.d.ts', + '**/*.config.*', + '**/mock/**', + ], + thresholds: { + lines: 88, + functions: 90, + branches: 78, + statements: 85, + }, + }, + testTimeout: 30000, + hookTimeout: 30000, + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './'), + }, + }, +})