From e6d9668d1918d634eba4e7102f54cc23568c0633 Mon Sep 17 00:00:00 2001 From: AbuJulaybeeb Date: Thu, 23 Apr 2026 04:51:05 +0100 Subject: [PATCH 1/4] perf: implement lazy loading for heavy components and optimize bundle size --- .../components/dashboard/DashboardGrid.tsx | 44 +- .../dashboard/widgets/RecentSalesWidget.tsx | 11 + .../dashboard/widgets/RevenueChart.tsx | 65 +++ src/app/components/quizzes/QuestionCard.tsx | 7 +- src/app/editor/page.tsx | 12 +- src/app/video-player-demo/page.tsx | 61 +++ test-output-verbose.txt | 448 ++++++++++++++++++ test-output.txt | 139 ++++++ 8 files changed, 779 insertions(+), 8 deletions(-) create mode 100644 src/app/components/dashboard/widgets/RevenueChart.tsx create mode 100644 src/app/video-player-demo/page.tsx create mode 100644 test-output-verbose.txt create mode 100644 test-output.txt diff --git a/src/app/components/dashboard/DashboardGrid.tsx b/src/app/components/dashboard/DashboardGrid.tsx index b7aa3e36..6c063689 100644 --- a/src/app/components/dashboard/DashboardGrid.tsx +++ b/src/app/components/dashboard/DashboardGrid.tsx @@ -20,14 +20,46 @@ import { } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { Settings, Plus, Grid3X3, Calendar } from 'lucide-react'; -import { ProgressSummaryWidget } from './widgets/ProgressSummaryWidget'; -import { UpcomingDeadlinesWidget } from './widgets/UpcomingDeadlinesWidget'; -import { RecommendedCoursesWidget } from './widgets/RecommendedCoursesWidget'; -import { LearningStreakWidget } from './widgets/LearningStreakWidget'; -import { RecentActivityWidget } from './widgets/RecentActivityWidget'; -import { RecentSalesWidget } from './widgets/RecentSalesWidget'; +import dynamic from 'next/dynamic'; import { useDashboardWidgets } from '../../hooks/useDashboardWidgets'; +const ProgressSummaryWidget = dynamic( + () => import('./widgets/ProgressSummaryWidget').then((mod) => mod.ProgressSummaryWidget), + { + loading: () =>
, + }, +); +const UpcomingDeadlinesWidget = dynamic( + () => import('./widgets/UpcomingDeadlinesWidget').then((mod) => mod.UpcomingDeadlinesWidget), + { + loading: () =>
, + }, +); +const RecommendedCoursesWidget = dynamic( + () => import('./widgets/RecommendedCoursesWidget').then((mod) => mod.RecommendedCoursesWidget), + { + loading: () =>
, + }, +); +const LearningStreakWidget = dynamic( + () => import('./widgets/LearningStreakWidget').then((mod) => mod.LearningStreakWidget), + { + loading: () =>
, + }, +); +const RecentActivityWidget = dynamic( + () => import('./widgets/RecentActivityWidget').then((mod) => mod.RecentActivityWidget), + { + loading: () =>
, + }, +); +const RecentSalesWidget = dynamic( + () => import('./widgets/RecentSalesWidget').then((mod) => mod.RecentSalesWidget), + { + loading: () =>
, + }, +); + interface Widget { id: string; type: string; diff --git a/src/app/components/dashboard/widgets/RecentSalesWidget.tsx b/src/app/components/dashboard/widgets/RecentSalesWidget.tsx index 1020fc7e..b7bd8d3e 100644 --- a/src/app/components/dashboard/widgets/RecentSalesWidget.tsx +++ b/src/app/components/dashboard/widgets/RecentSalesWidget.tsx @@ -4,6 +4,14 @@ import React, { useEffect, useState } from 'react'; import { motion } from 'framer-motion'; import { DollarSign, Settings } from 'lucide-react'; import { format } from 'date-fns'; +import dynamic from 'next/dynamic'; + +const RevenueChart = dynamic(() => import('./RevenueChart'), { + loading: () => ( +
+ ), + ssr: false, +}); interface Sale { id: string; @@ -230,6 +238,9 @@ export const RecentSalesWidget: React.FC = ({ You made {totalSales} sales this month.

+ {/* Revenue Chart */} + + {/* Sales List */}
{sales.map((sale, index) => ( diff --git a/src/app/components/dashboard/widgets/RevenueChart.tsx b/src/app/components/dashboard/widgets/RevenueChart.tsx new file mode 100644 index 00000000..18de90a0 --- /dev/null +++ b/src/app/components/dashboard/widgets/RevenueChart.tsx @@ -0,0 +1,65 @@ +'use client'; + +import React from 'react'; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, +} from 'recharts'; + +const data = [ + { name: 'Mon', revenue: 400 }, + { name: 'Tue', revenue: 300 }, + { name: 'Wed', revenue: 600 }, + { name: 'Thu', revenue: 800 }, + { name: 'Fri', revenue: 500 }, + { name: 'Sat', revenue: 900 }, + { name: 'Sun', revenue: 1000 }, +]; + +export const RevenueChart = () => { + return ( +
+ + + + + + + + + + + + + + + +
+ ); +}; + +export default RevenueChart; diff --git a/src/app/components/quizzes/QuestionCard.tsx b/src/app/components/quizzes/QuestionCard.tsx index 2051a5cf..1d2eb3c7 100644 --- a/src/app/components/quizzes/QuestionCard.tsx +++ b/src/app/components/quizzes/QuestionCard.tsx @@ -3,7 +3,12 @@ import { Question, useQuizStore } from '@/app/store/quizStore'; import MultipleChoiceQuestion from './question-types/MultipleChoiceQuestion'; import TrueFalseQuestion from './question-types/TrueFalseQuestion'; -import CodeChallengeQuestion from './question-types/CodeChallengeQuestion'; +import dynamic from 'next/dynamic'; + +const CodeChallengeQuestion = dynamic(() => import('./question-types/CodeChallengeQuestion'), { + loading: () =>
, + ssr: false, +}); interface QuestionCardProps { question: Question; diff --git a/src/app/editor/page.tsx b/src/app/editor/page.tsx index 2c9366f2..af120228 100644 --- a/src/app/editor/page.tsx +++ b/src/app/editor/page.tsx @@ -1,7 +1,17 @@ 'use client'; import React, { useState } from 'react'; -import { RichContentEditor } from '@/components/editor/RichContentEditor'; +import dynamic from 'next/dynamic'; + +const RichContentEditor = dynamic( + () => import('@/components/editor/RichContentEditor').then((mod) => mod.RichContentEditor), + { + loading: () => ( +
+ ), + ssr: false, + }, +); export default function EditorPage() { const [content, setContent] = useState('

Start editing...

'); diff --git a/src/app/video-player-demo/page.tsx b/src/app/video-player-demo/page.tsx new file mode 100644 index 00000000..e7bf488e --- /dev/null +++ b/src/app/video-player-demo/page.tsx @@ -0,0 +1,61 @@ +'use client'; + +import React from 'react'; +import dynamic from 'next/dynamic'; + +const VideoPlayer = dynamic( + () => import('../components/video/VideoPlayer').then((mod) => mod.VideoPlayer), + { + loading: () => ( +
+ ), + ssr: false, + }, +); + +export default function VideoPlayerDemo() { + const mockVideoUrl = 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4'; + + return ( +
+

+ Lazy-Loaded Video Player Demo +

+ +

+ The video player below is loaded dynamically using next/dynamic. This reduces + the initial JavaScript bundle size by splitting the video player and its dependencies into a + separate chunk. +

+ +
+ +
+ +
+
+

+ Performance Benefit +

+

+ By lazy loading the video player, users who don't watch videos won't download + the extra JavaScript required for the player controls and logic. +

+
+ +
+

+ Implementation +

+

+ Uses ssr: false to ensure the player only hydrates on the client, avoiding + potential hydration mismatches with heavy media elements. +

+
+
+
+ ); +} diff --git a/test-output-verbose.txt b/test-output-verbose.txt new file mode 100644 index 00000000..08a41dec --- /dev/null +++ b/test-output-verbose.txt @@ -0,0 +1,448 @@ + +> my-app@0.1.0 test +> vitest run --reporter=verbose + +The CJS build of Vite's Node API is deprecated. See https://vite.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details. + + RUN v2.1.9 /home/abujulaybeeb/Documents/Drips/teachLink_web + + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > initialization > should create initial state with correct metadata + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > initialization > should create initial state without userId + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > field value management > should update field value and mark as touched and dirty + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > field value management > should not mark field as dirty if value is the same + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > field value management > should get all field values + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > field value management > should set multiple values at once + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > validation management > should set and get validation state + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > validation management > should check if form is valid + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > validation management > should clear field validation + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > form reset > should reset form to initial state + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > submission state > should manage submission state + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > field state helpers > should mark field as touched + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > field state helpers > should not update lastModified if field already touched + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > state change subscriptions > should notify subscribers of field changes + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > state change subscriptions > should notify subscribers of validation changes + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > state change subscriptions > should handle subscription errors gracefully + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > state change subscriptions > should unsubscribe correctly + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > metadata management > should return metadata copy + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > metadata management > should update lastModified when field changes + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > edge cases > should handle undefined field values + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > edge cases > should handle null field values + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > edge cases > should handle empty string field values + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > edge cases > should handle complex object field values + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > silent field updates > should set field value without triggering change events + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > batch operations > should set multiple validation states at once + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > batch operations > should set multiple field values in batch + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > field reset operations > should reset specific fields + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > metadata management > should set partial metadata + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > validation summary > should provide comprehensive validation summary + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > form state queries > should check if form is dirty + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > form state queries > should get dirty fields list + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > form state queries > should get touched fields list + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > submission control > should start submission with callback + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > submission control > should complete submission successfully + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > submission control > should complete submission with failure + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > state snapshots > should create and restore snapshots + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > state snapshots > should create independent snapshots + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > validation control > should validate specific fields + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > validation control > should clear all validation + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > pristine state management > should mark field as pristine + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > pristine state management > should mark multiple fields as pristine + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > field state summary > should provide comprehensive field state + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > programmatic state control methods > field state summary > should handle field state for non-existent field + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > cascading state updates > should initialize dependencies correctly + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > cascading state updates > should handle field visibility changes + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > cascading state updates > should get all field visibility states + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > cascading state updates > should trigger cascading updates manually + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > cascading state updates > should preview cascading updates without applying them + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > cascading state updates > should get field processing order + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > cascading state updates > should evaluate all conditional logic + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > cascading state updates > should reset cascading state + ✓ src/form-management/state/form-state-manager.test.ts > FormStateManager > cascading state updates > should handle complex dependency chains + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > initialization > should initialize offline mode successfully + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > initialization > should handle initialization errors + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > course operations > should download and save a course + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > course operations > should check course availability + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > progress tracking > should save and retrieve progress + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > progress tracking > should get course progress + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > sync operations > should sync data successfully + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > sync operations > should handle sync errors gracefully + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > storage management > should get storage information + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > storage management > should clear all data + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > sync queue operations > should add items to sync queue + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > sync queue operations > should cache and retrieve assets + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > error handling > should handle database not initialized errors + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > error handling > should handle cleanup errors gracefully + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > performance and optimization > should handle large datasets efficiently + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx > useOfflineMode > performance and optimization > should handle concurrent operations + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > rule registration > should register a custom validation rule + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > rule registration > should throw error for duplicate rule names + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > rule registration > should throw error for invalid rule name + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > rule registration > should throw error for invalid validation function + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > rule registration > should register multiple rules at once + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > rule unregistration > should unregister a rule + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > rule unregistration > should return false for non-existent rule + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > category management > should organize rules by category + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > category management > should clean up empty categories + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > dependency management > should track rule dependencies + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > dependency management > should validate dependencies + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > rule execution > should execute a custom validation rule + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > rule execution > should handle rule execution errors + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > rule execution > should handle unknown rules + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > rule execution > should execute multiple rules for a field + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > execution context > should provide execution context to validation functions + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > execution context > should handle custom data in execution context + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > statistics and utilities > should provide registry statistics + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > statistics and utilities > should export rules configuration + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > statistics and utilities > should clear all rules and data + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > ValidationRuleBuilders > should create field comparison rule + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > ValidationRuleBuilders > should create conditional rule + ✓ src/form-management/validation/custom-validation-registry.test.ts > CustomValidationRegistry > ValidationRuleBuilders > should create async API rule + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Schema Validation > should validate a minimal valid configuration + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Schema Validation > should reject configuration with empty form ID + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Schema Validation > should reject configuration with no fields + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Schema Validation > should detect duplicate field IDs + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Schema Validation > should detect invalid field dependencies + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Schema Validation > should detect circular dependencies + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > JSON Parsing > should parse valid JSON configuration + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > JSON Parsing > should throw error for invalid JSON + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > JSON Parsing > should throw error for JSON with invalid schema + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Pretty Printing > should format configuration back to JSON + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Pretty Printing > should format configuration to compact JSON + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Pretty Printing > should format configuration with custom options + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Pretty Printing > should handle configurations with functions by omitting them + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Pretty Printing > should preserve structure and formatting + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Round-Trip Property Tests > Property: Configuration round-trip should produce equivalent object 1681ms + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Error Handling > should handle malformed JSON gracefully + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Error Handling > should provide descriptive error messages for validation failures + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Error Handling > should handle nested validation errors + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Complex Configuration Scenarios > should handle configuration with wizard steps + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Complex Configuration Scenarios > should handle configuration with auto-save settings + ✓ src/form-management/utils/configuration-parser.test.ts > FormConfigurationParser > Complex Configuration Scenarios > should handle configuration with analytics settings + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Authentication > completes a successful login flow 410ms + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Authentication > shows loading state while authenticating + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Authentication > displays error message on failed login + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Authentication > clears error on subsequent login attempt + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Todo CRUD > adds a new todo + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Todo CRUD > does not add empty todos + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Todo CRUD > clears input after adding + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Todo CRUD > toggles a todo done state + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > validateField > should validate required fields correctly + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > validateField > should validate email format correctly + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > validateField > should validate minimum length correctly + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > validateField > should handle non-existent fields gracefully + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > validateField > should skip validation when condition is not met + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > custom validation rules > should allow adding and using custom validation rules + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > custom validation rules > should handle custom rule errors gracefully + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > validateForm > should validate entire form and return combined results + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > validateForm > should return invalid result when any field fails validation + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > async validation > should handle async validation rules + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Todo CRUD > deletes a todo + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > async validation > should handle async validation timeout + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > built-in validation rules > should validate patterns correctly + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > built-in validation rules > should validate max length correctly + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > utility methods > should manage custom rules correctly + ✓ src/form-management/validation/validation-engine.test.ts > ValidationEngine > utility methods > should clear async cache + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Todo CRUD > manages multiple todos independently + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Search > shows all items when query is empty + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Search > filters results by query + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Search > is case-insensitive + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Search > shows no results for unmatched query + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Search > restores full list when query is cleared + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Multi-step form > starts on step 1 and shows correct label + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Multi-step form > advances to next step + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > initialization > should initialize with default container if none provided + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > initialization > should initialize styles on creation + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > feedback display > should display error feedback + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > feedback display > should display warning feedback + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > feedback display > should display success feedback when valid + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > feedback display > should not display success feedback when disabled + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > error processing > should limit number of errors displayed + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > error processing > should group similar errors when enabled + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > positioning and styling > should apply custom positioning + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > positioning and styling > should apply custom styles + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > positioning and styling > should update positioning after display + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > callback system > should notify callbacks on state changes + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > callback system > should handle callback errors gracefully + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > cleanup and management > should clear feedback for specific field + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > cleanup and management > should clear all feedback + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > cleanup and management > should get all display states + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > statistics > should provide feedback statistics + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > accessibility > should set appropriate ARIA attributes + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > accessibility > should create proper content structure with icons + ✓ src/form-management/validation/validation-feedback-display.test.ts > ValidationFeedbackDisplay > accessibility > should create content without icons when disabled + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Multi-step form > goes back a step + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Multi-step form > completes full workflow and submits data + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Multi-step form > hides back button on first step + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – Multi-step form > hides next button on last step + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – API-backed flow > fetches and displays user profile + ✓ src/testing/UserFlowTester.test.tsx > UserFlowTester – API-backed flow > handles network failure gracefully + ✓ src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > validation state management > should initialize with empty state + ✓ src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > validation state management > should track validation state correctly + ✓ src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > successful validation > should execute successful async validation + ✓ src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > successful validation > should handle debouncing correctly + ✓ src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > successful validation > should cancel previous debounced validation when new one starts + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > initialization > should initialize with field descriptors + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > initialization > should initialize with conditional rules + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > dependency validation > should detect missing dependencies + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > dependency validation > should detect circular dependencies + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > dependency validation > should validate correct dependencies + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > cascading updates > should calculate visibility changes + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > cascading updates > should calculate value changes + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > cascading updates > should identify fields to revalidate + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > topological sorting > should return fields in dependency order + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > dependency queries > should check if field has dependencies + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > dependency queries > should check if field has dependents + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > dependency queries > should get field dependencies + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > dependency queries > should get dependent fields + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > error handling > should handle invalid conditional rule gracefully + ✓ src/form-management/state/dependency-manager.test.ts > DependencyManager > cleanup > should clear all dependency information + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Basic Auto-Save Functionality > should save form data immediately + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Basic Auto-Save Functionality > should include metadata in saved draft + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Basic Auto-Save Functionality > should update save status during save operation + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Basic Auto-Save Functionality > should set lastSaved timestamp after successful save + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Draft Data Recovery > should load previously saved draft + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Draft Data Recovery > should return null for non-existent draft + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Draft Data Recovery > should return null for expired draft + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Draft Data Recovery > should validate draft data integrity + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Draft Cleanup > should clear draft data + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Draft Cleanup > should not throw error when clearing non-existent draft + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Storage Quota Management > should set storage quota + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Storage Quota Management > should cleanup oldest drafts when quota is exceeded + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Save Status Indication > should notify subscribers of status changes + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Save Status Indication > should allow unsubscribing from status changes + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Save Status Indication > should include queued saves count in status + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Error Handling > should handle storage errors gracefully + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Error Handling > should update status to error on save failure + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Auto-Save Intervals > should enable auto-save with interval + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Auto-Save Intervals > should clear previous interval when enabling auto-save again + ✓ src/form-management/auto-save/auto-save-manager.test.ts > AutoSaveManager > Resource Cleanup > should cleanup resources on destroy + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Button > renders with the provided label + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Button > applies "primary" variant class + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Button > applies "secondary" variant class + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Button > applies "danger" variant class + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Button > calls onClick handler when clicked + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Button > does not call onClick when disabled + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Button > has an accessible aria-label + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Button > is focusable via keyboard + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Counter > renders initial count + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Counter > increments by default step on each click + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Counter > increments by custom step + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Counter > fires onCount callback with the new value + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Counter > accumulates across multiple clicks + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – AsyncDataComponent > shows loading state initially + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – AsyncDataComponent > displays data after successful fetch + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – AsyncDataComponent > displays error when fetch rejects + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – AsyncDataComponent > removes loading indicator after fetch completes + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – ErrorBoundary > renders fallback when child throws + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – ErrorBoundary > renders children when no error occurs + ✓ src/testing/visualRegression.test.tsx > VisualRegression – Snapshots > Card (default variant) matches snapshot + ✓ src/testing/visualRegression.test.tsx > VisualRegression – Snapshots > Card (featured variant) matches snapshot + ✓ src/testing/visualRegression.test.tsx > VisualRegression – Snapshots > Card (compact variant) matches snapshot + ✓ src/testing/visualRegression.test.tsx > VisualRegression – Snapshots > Badge matches snapshot for each color + ✓ src/testing/visualRegression.test.tsx > VisualRegression – Snapshots > ThemeWrapper light mode matches snapshot + ✓ src/testing/visualRegression.test.tsx > VisualRegression – Snapshots > ThemeWrapper dark mode matches snapshot + ✓ src/testing/visualRegression.test.tsx > VisualRegression – CSS class assertions > Card applies correct variant class + ✓ src/testing/visualRegression.test.tsx > VisualRegression – CSS class assertions > Card always has base "card" class + ✓ src/testing/visualRegression.test.tsx > VisualRegression – CSS class assertions > Badge applies correct color class + ✓ src/testing/visualRegression.test.tsx > VisualRegression – CSS class assertions > ThemeWrapper has correct theme class + ✓ src/testing/visualRegression.test.tsx > VisualRegression – CSS class assertions > ThemeWrapper exposes data-theme attribute + ✓ src/testing/visualRegression.test.tsx > VisualRegression – ARIA & semantic structure > Card has role="article" + ✓ src/testing/visualRegression.test.tsx > VisualRegression – ARIA & semantic structure > Card title is rendered as h2 + ✓ src/testing/visualRegression.test.tsx > VisualRegression – ARIA & semantic structure > Card image has an alt attribute matching the title + ✓ src/testing/visualRegression.test.tsx > VisualRegression – ARIA & semantic structure > ResponsiveNav renders all links + ✓ src/testing/visualRegression.test.tsx > VisualRegression – Theme switching > swaps CSS variable values between light and dark + ✓ src/testing/visualRegression.test.tsx > VisualRegression – Theme switching > accent colour differs between themes + ✓ src/testing/visualRegression.test.tsx > VisualRegression – Responsive layout > fires resize event when viewport changes + ✓ src/testing/visualRegression.test.tsx > VisualRegression – Responsive layout > reports correct innerWidth after resize + ✓ src/testing/visualRegression.test.tsx > VisualRegression – Responsive layout > reports mobile viewport after resize + ✓ src/testing/visualRegression.test.tsx > VisualRegression – Inline style validation > Badge style references a CSS variable + ✓ src/testing/visualRegression.test.tsx > VisualRegression – Inline style validation > ThemeWrapper sets all expected CSS variables + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – SimpleForm > submits with the entered value + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – SimpleForm > is associated with a visible label + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – SimpleForm > clears input value when user clears it + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – File handling > accepts a mock file object + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – File handling > simulates file selection on an input + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Snapshot > matches stored snapshot for default Button + ✓ src/testing/ComponentTester.test.tsx > ComponentTester – Snapshot > matches stored snapshot for Counter at initial=0 + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Engine > Initialisation > creates a framework with default configuration + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Engine > Initialisation > accepts custom configuration + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Engine > Suite registration > registers a single suite + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Engine > Suite registration > registers multiple suites of different types + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Engine > Suite registration > preserves registration order + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Engine > Sequential execution (parallel: false) > runs all suites and marks them as passed + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Engine > Sequential execution (parallel: false) > sets running flag during execution + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Engine > Sequential execution (parallel: false) > throws when runAll is called while already running + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Engine > Parallel execution (parallel: true) > runs suites in parallel and returns all passed + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Engine > runById > runs only the specified suite + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Engine > runById > does not affect other suites + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Engine > runById > throws for an unknown suite id + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Engine > reset > clears all registered suites + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – API integration > reports results to a remote endpoint + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – API integration > handles API errors gracefully + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Performance > registers 100 suites in under 50ms + ✓ src/testing/TestingFramework.test.tsx > TestingFramework – Performance > runs 20 suites sequentially in under 500ms + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Render budgets > renders a simple component within budget + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Render budgets > renders a 500-item list within budget + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Render budgets > renders a 1000-item list within extended budget + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Render budgets > memoized component does not re-render on identical props + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Computation benchmarks > computes sqrt-sum of 10 000 numbers within budget + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Computation benchmarks > sorts 1000 items within budget + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Computation benchmarks > filters 10 000 items within search budget + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Computation benchmarks > JSON serialises 1000 objects under 50ms + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Computation benchmarks > JSON parses a 1000-item payload under 50ms + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Re-render cycles > re-renders 50 times within budget + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Re-render cycles > average rerender time stays below 5ms per cycle + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Storage estimates > reads navigator.storage.estimate without error + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Storage estimates > calculates storage utilisation percentage correctly + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Async operation benchmarks > resolves 100 sequential microtasks under 200ms + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Async operation benchmarks > resolves 100 parallel microtasks under 100ms + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Async operation benchmarks > mocks 50 fetch calls and resolves them under 200ms + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Benchmark utility > returns avg, min, max for a synchronous operation + ✓ src/testing/PerformanceTester.test.tsx > PerformanceTester – Benchmark utility > runs exactly the requested number of iterations + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > calculateContrastRatio > should calculate correct contrast ratio for black and white + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > calculateContrastRatio > should calculate correct contrast ratio for similar colors + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > calculateContrastRatio > should pass AA for sufficient contrast + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > calculateContrastRatio > should pass AA Large for lower contrast + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > calculateContrastRatio > should handle invalid hex colors + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > isFocusable > should identify button as focusable + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > isFocusable > should identify link as focusable + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > isFocusable > should identify element with tabindex as focusable + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > isFocusable > should not identify regular div as focusable + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > hasAccessibleName > should detect aria-label + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > hasAccessibleName > should detect text content + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > hasAccessibleName > should detect title attribute + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > hasAccessibleName > should return false for element without accessible name + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > generateAriaId > should generate unique IDs + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > generateAriaId > should use provided prefix + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > generateAriaId > should use default prefix + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > getWCAGLevel > should return Fail for critical issues + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > getWCAGLevel > should return A for serious issues + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > getWCAGLevel > should return AA for moderate issues + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > getWCAGLevel > should return AAA for few minor issues + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts > accessibilityUtils > getWCAGLevel > should return AAA for no issues + × src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > failed validation with retry > should retry failed validations 5007ms + → Test timed out in 5000ms. +If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". + ✓ src/app/hooks/__tests__/useDashboardWidgets.test.tsx > useDashboardWidgets > initializes with defaults and persists layout + ✓ src/app/hooks/__tests__/useDashboardWidgets.test.tsx > useDashboardWidgets > adds, reorders, updates, collapses, resizes, and removes widgets + ✓ src/app/hooks/__tests__/useStudyGroups.test.tsx > useStudyGroups > creates a group and allows joining + × src/app/hooks/__tests__/useStudyGroups.test.tsx > useStudyGroups > posts messages and builds leaderboard from challenges + → expected 'Alice' to be 'Bob' // Object.is equality + ✓ src/app/hooks/__tests__/useStudyGroups.test.tsx > useStudyGroups > adds resources (link and file) + ✓ src/form-management/types/core.test.ts > Core Types > should have valid FieldType values + ✓ src/form-management/types/core.test.ts > Core Types > should create valid ValidationRule objects + ✓ src/form-management/types/core.test.ts > Core Types > should create valid FieldDescriptor objects + ✓ src/form-management/types/core.test.ts > Core Types > Property: FieldType values are always valid + ✓ src/app/components/social/__tests__/StudyGroupCard.test.tsx > StudyGroupCard > renders group info and triggers join/leave + ✓ src/app/components/social/__tests__/LearningChallengeBoard.test.tsx > LearningChallengeBoard > renders challenge and updates progress + × src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > failed validation with retry > should fail after max retry attempts 5003ms + → Test timed out in 5000ms. +If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". + ✓ src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > failed validation with retry > should handle timeout correctly + ✓ src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > callback system > should notify callbacks on validation completion + ✓ src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > callback system > should handle callback errors gracefully + ✓ src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > concurrent validation > should validate multiple fields concurrently + ✓ src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > cancellation > should cancel individual field validation + ✓ src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > cancellation > should cancel all validations + ✓ src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > statistics > should provide validation statistics +stderr | src/app/components/social/__tests__/GroupDiscussionThread.test.tsx > GroupDiscussionThread > posts content via onPost +Error: Uncaught [TypeError: messagesEndRef.current?.scrollIntoView is not a function] + at reportException (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24) + at innerInvokeEventListeners (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:353:9) + at invokeEventListeners (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3) + at HTMLUnknownElementImpl._dispatch (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9) + at HTMLUnknownElementImpl.dispatchEvent (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17) + at HTMLUnknownElement.dispatchEvent (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34) + at Object.invokeGuardedCallbackDev (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:4213:16) + at invokeGuardedCallback (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:4277:31) + at reportUncaughtErrorInDEV (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:22877:5) + at captureCommitPhaseError (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:27165:5) TypeError: messagesEndRef.current?.scrollIntoView is not a function + at /home/abujulaybeeb/Documents/Drips/teachLink_web/src/app/components/social/GroupDiscussionThread.tsx:43:29 + at commitHookEffectListMount (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:23189:26) + at commitPassiveMountOnFiber (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:24970:11) + at commitPassiveMountEffects_complete (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:24930:9) + at commitPassiveMountEffects_begin (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:24917:7) + at commitPassiveMountEffects (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:24905:3) + at flushPassiveEffectsImpl (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:27078:3) + at flushPassiveEffects (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:27023:14) + at /home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:26808:9 + at flushActQueue (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react/cjs/react.development.js:2667:24) + + × src/app/components/social/__tests__/GroupDiscussionThread.test.tsx > GroupDiscussionThread > posts content via onPost + → messagesEndRef.current?.scrollIntoView is not a function + ✓ src/app/components/social/__tests__/SharedResourceLibrary.test.tsx > SharedResourceLibrary > calls onAdd for link resource + +⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL src/components/tipping/TipForm/TipForm.test.tsx [ src/components/tipping/TipForm/TipForm.test.tsx ] +Error: Failed to resolve import "@/testing" from "src/components/tipping/TipForm/TipForm.test.tsx". Does the file exist? + Plugin: vite:import-analysis + File: /home/abujulaybeeb/Documents/Drips/teachLink_web/src/components/tipping/TipForm/TipForm.test.tsx:6:38 + 4 | const __vi_import_0__ = await import('react/jsx-dev-runtime') + 5 | const __vi_import_1__ = await import('react') + 6 | const __vi_import_2__ = await import('@/testing') + | ^ + 7 | const __vi_import_3__ = await import('@/testing/utils/mocks') + 8 | + ❯ TransformPluginContext._formatError node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:49258:41 + ❯ TransformPluginContext.error node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:49253:16 + ❯ normalizeUrl node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:64307:23 + ❯ node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:64439:39 + ❯ TransformPluginContext.transform node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:64366:7 + ❯ PluginContainer.transform node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:49099:18 + ❯ loadAndTransform node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:51978:27 + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ +Serialized Error: { __vitest_rollup_error__: { plugin: 'vite:import-analysis', id: '/home/abujulaybeeb/Documents/Drips/teachLink_web/src/components/tipping/TipForm/TipForm.test.tsx', loc: { file: '/home/abujulaybeeb/Documents/Drips/teachLink_web/src/components/tipping/TipForm/TipForm.test.tsx', line: 6, column: 38 }, frame: '4 | const __vi_import_0__ = await import(\'react/jsx-dev-runtime\')\n5 | const __vi_import_1__ = await import(\'react\')\n6 | const __vi_import_2__ = await import(\'@/testing\')\n | ^\n7 | const __vi_import_3__ = await import(\'@/testing/utils/mocks\')\n8 | ' } } +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/5]⎯ + +⎯⎯⎯⎯⎯⎯⎯ Failed Tests 4 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > failed validation with retry > should retry failed validations + FAIL src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > failed validation with retry > should fail after max retry attempts +Error: Test timed out in 5000ms. +If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/5]⎯ + + FAIL src/app/hooks/__tests__/useStudyGroups.test.tsx > useStudyGroups > posts messages and builds leaderboard from challenges +AssertionError: expected 'Alice' to be 'Bob' // Object.is equality + +Expected: "Bob" +Received: "Alice" + + ❯ src/app/hooks/__tests__/useStudyGroups.test.tsx:69:28 + 67| + 68| const lb = result.current.challengeLeaderboard(challengeId); + 69| expect(lb[0].userName).toBe('Bob'); + | ^ + 70| expect(lb[0].progress).toBe(70); + 71| }); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/5]⎯ + + FAIL src/app/components/social/__tests__/GroupDiscussionThread.test.tsx > GroupDiscussionThread > posts content via onPost +TypeError: messagesEndRef.current?.scrollIntoView is not a function + ❯ src/app/components/social/GroupDiscussionThread.tsx:43:29 + 41| // Auto-scroll to bottom when new messages arrive + 42| useEffect(() => { + 43| messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + | ^ + 44| }, [messages]); + 45| + ❯ commitHookEffectListMount node_modules/react-dom/cjs/react-dom.development.js:23189:26 + ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom.development.js:24970:11 + ❯ commitPassiveMountEffects_complete node_modules/react-dom/cjs/react-dom.development.js:24930:9 + ❯ commitPassiveMountEffects_begin node_modules/react-dom/cjs/react-dom.development.js:24917:7 + ❯ commitPassiveMountEffects node_modules/react-dom/cjs/react-dom.development.js:24905:3 + ❯ flushPassiveEffectsImpl node_modules/react-dom/cjs/react-dom.development.js:27078:3 + ❯ flushPassiveEffects node_modules/react-dom/cjs/react-dom.development.js:27023:14 + ❯ node_modules/react-dom/cjs/react-dom.development.js:26808:9 + ❯ flushActQueue node_modules/react/cjs/react.development.js:2667:24 + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/5]⎯ + + Test Files 4 failed | 19 passed (23) + Tests 4 failed | 332 passed (336) + Start at 12:27:40 + Duration 17.93s (transform 1.05s, setup 3.40s, collect 5.87s, tests 16.29s, environment 18.08s, prepare 2.88s) + diff --git a/test-output.txt b/test-output.txt new file mode 100644 index 00000000..9d4b895e --- /dev/null +++ b/test-output.txt @@ -0,0 +1,139 @@ + +> my-app@0.1.0 test +> vitest run + +The CJS build of Vite's Node API is deprecated. See https://vite.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details. + + RUN v2.1.9 /home/abujulaybeeb/Documents/Drips/teachLink_web + + ✓ src/form-management/state/form-state-manager.test.ts (52 tests) 96ms + ✓ src/app/hooks/__tests__/useOfflineMode.test.tsx (16 tests) 154ms + ✓ src/form-management/utils/configuration-parser.test.ts (21 tests) 2073ms + ✓ FormConfigurationParser > Round-Trip Property Tests > Property: Configuration round-trip should produce equivalent object 2029ms + ✓ src/form-management/validation/custom-validation-registry.test.ts (23 tests) 39ms + ✓ src/form-management/validation/validation-engine.test.ts (15 tests) 101ms + ✓ src/form-management/validation/validation-feedback-display.test.ts (20 tests) 99ms + ✓ src/testing/UserFlowTester.test.tsx (23 tests) 2026ms + ✓ UserFlowTester – Authentication > completes a successful login flow 462ms + ✓ src/form-management/state/dependency-manager.test.ts (15 tests) 24ms + ✓ src/form-management/auto-save/auto-save-manager.test.ts (20 tests) 47ms + ✓ src/testing/ComponentTester.test.tsx (26 tests) 593ms + ✓ src/testing/visualRegression.test.tsx (22 tests) 286ms + ✓ src/testing/TestingFramework.test.tsx (17 tests) 81ms + ✓ src/testing/PerformanceTester.test.tsx (18 tests) 409ms + ✓ src/app/components/accessibility/__tests__/accessibilityUtils.test.ts (21 tests) 23ms + ❯ src/components/tipping/TipForm/TipForm.test.tsx (0 test) + ✓ src/app/hooks/__tests__/useDashboardWidgets.test.tsx (2 tests) 31ms + ❯ src/app/hooks/__tests__/useStudyGroups.test.tsx (3 tests | 1 failed) 51ms + × useStudyGroups > posts messages and builds leaderboard from challenges 19ms + → expected 'Alice' to be 'Bob' // Object.is equality + ✓ src/form-management/types/core.test.ts (4 tests) 20ms + ✓ src/app/components/social/__tests__/StudyGroupCard.test.tsx (1 test) 96ms + ✓ src/app/components/social/__tests__/LearningChallengeBoard.test.tsx (1 test) 147ms + ❯ src/form-management/validation/async-validation-manager.test.ts (14 tests | 2 failed) 10053ms + × AsyncValidationManager > failed validation with retry > should retry failed validations 5006ms + → Test timed out in 5000ms. +If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". + × AsyncValidationManager > failed validation with retry > should fail after max retry attempts 5003ms + → Test timed out in 5000ms. +If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". +stderr | src/app/components/social/__tests__/GroupDiscussionThread.test.tsx > GroupDiscussionThread > posts content via onPost +Error: Uncaught [TypeError: messagesEndRef.current?.scrollIntoView is not a function] + at reportException (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24) + at innerInvokeEventListeners (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:353:9) + at invokeEventListeners (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3) + at HTMLUnknownElementImpl._dispatch (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9) + at HTMLUnknownElementImpl.dispatchEvent (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17) + at HTMLUnknownElement.dispatchEvent (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34) + at Object.invokeGuardedCallbackDev (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:4213:16) + at invokeGuardedCallback (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:4277:31) + at reportUncaughtErrorInDEV (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:22877:5) + at captureCommitPhaseError (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:27165:5) TypeError: messagesEndRef.current?.scrollIntoView is not a function + at /home/abujulaybeeb/Documents/Drips/teachLink_web/src/app/components/social/GroupDiscussionThread.tsx:43:29 + at commitHookEffectListMount (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:23189:26) + at commitPassiveMountOnFiber (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:24970:11) + at commitPassiveMountEffects_complete (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:24930:9) + at commitPassiveMountEffects_begin (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:24917:7) + at commitPassiveMountEffects (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:24905:3) + at flushPassiveEffectsImpl (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:27078:3) + at flushPassiveEffects (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:27023:14) + at /home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react-dom/cjs/react-dom.development.js:26808:9 + at flushActQueue (/home/abujulaybeeb/Documents/Drips/teachLink_web/node_modules/react/cjs/react.development.js:2667:24) + + ❯ src/app/components/social/__tests__/GroupDiscussionThread.test.tsx (1 test | 1 failed) 91ms + × GroupDiscussionThread > posts content via onPost 89ms + → messagesEndRef.current?.scrollIntoView is not a function + ✓ src/app/components/social/__tests__/SharedResourceLibrary.test.tsx (1 test) 172ms + +⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL src/components/tipping/TipForm/TipForm.test.tsx [ src/components/tipping/TipForm/TipForm.test.tsx ] +Error: Failed to resolve import "@/testing" from "src/components/tipping/TipForm/TipForm.test.tsx". Does the file exist? + Plugin: vite:import-analysis + File: /home/abujulaybeeb/Documents/Drips/teachLink_web/src/components/tipping/TipForm/TipForm.test.tsx:6:38 + 4 | const __vi_import_0__ = await import('react/jsx-dev-runtime') + 5 | const __vi_import_1__ = await import('react') + 6 | const __vi_import_2__ = await import('@/testing') + | ^ + 7 | const __vi_import_3__ = await import('@/testing/utils/mocks') + 8 | + ❯ TransformPluginContext._formatError node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:49258:41 + ❯ TransformPluginContext.error node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:49253:16 + ❯ normalizeUrl node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:64307:23 + ❯ node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:64439:39 + ❯ TransformPluginContext.transform node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:64366:7 + ❯ PluginContainer.transform node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:49099:18 + ❯ loadAndTransform node_modules/vite/dist/node/chunks/dep-BK3b2jBa.js:51978:27 + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/5]⎯ + +⎯⎯⎯⎯⎯⎯⎯ Failed Tests 4 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > failed validation with retry > should retry failed validations + FAIL src/form-management/validation/async-validation-manager.test.ts > AsyncValidationManager > failed validation with retry > should fail after max retry attempts +Error: Test timed out in 5000ms. +If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/5]⎯ + + FAIL src/app/hooks/__tests__/useStudyGroups.test.tsx > useStudyGroups > posts messages and builds leaderboard from challenges +AssertionError: expected 'Alice' to be 'Bob' // Object.is equality + +Expected: "Bob" +Received: "Alice" + + ❯ src/app/hooks/__tests__/useStudyGroups.test.tsx:69:28 + 67| + 68| const lb = result.current.challengeLeaderboard(challengeId); + 69| expect(lb[0].userName).toBe('Bob'); + | ^ + 70| expect(lb[0].progress).toBe(70); + 71| }); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/5]⎯ + + FAIL src/app/components/social/__tests__/GroupDiscussionThread.test.tsx > GroupDiscussionThread > posts content via onPost +TypeError: messagesEndRef.current?.scrollIntoView is not a function + ❯ src/app/components/social/GroupDiscussionThread.tsx:43:29 + 41| // Auto-scroll to bottom when new messages arrive + 42| useEffect(() => { + 43| messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + | ^ + 44| }, [messages]); + 45| + ❯ commitHookEffectListMount node_modules/react-dom/cjs/react-dom.development.js:23189:26 + ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom.development.js:24970:11 + ❯ commitPassiveMountEffects_complete node_modules/react-dom/cjs/react-dom.development.js:24930:9 + ❯ commitPassiveMountEffects_begin node_modules/react-dom/cjs/react-dom.development.js:24917:7 + ❯ commitPassiveMountEffects node_modules/react-dom/cjs/react-dom.development.js:24905:3 + ❯ flushPassiveEffectsImpl node_modules/react-dom/cjs/react-dom.development.js:27078:3 + ❯ flushPassiveEffects node_modules/react-dom/cjs/react-dom.development.js:27023:14 + ❯ node_modules/react-dom/cjs/react-dom.development.js:26808:9 + ❯ flushActQueue node_modules/react/cjs/react.development.js:2667:24 + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/5]⎯ + + Test Files 4 failed | 19 passed (23) + Tests 4 failed | 332 passed (336) + Start at 12:25:51 + Duration 18.04s (transform 1.15s, setup 3.49s, collect 5.85s, tests 16.71s, environment 18.29s, prepare 2.82s) + From e48a2991e1ecbd1ff5a0c0d33b6c6098510760ab Mon Sep 17 00:00:00 2001 From: AbuJulaybeeb Date: Thu, 23 Apr 2026 04:58:13 +0100 Subject: [PATCH 2/4] fix: convert validation scripts to ES modules --- scripts/validate-ui.js | 8 ++++---- scripts/validate-web3.js | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/validate-ui.js b/scripts/validate-ui.js index 827c5b18..1f395d6c 100644 --- a/scripts/validate-ui.js +++ b/scripts/validate-ui.js @@ -6,11 +6,11 @@ * Exit code 0 = pass, 1 = fail */ -const fs = require('fs'); -const path = require('path'); -const { execSync } = require('child_process'); +import fs from 'fs'; +import path from 'path'; +import { execSync } from 'child_process'; -const SRC_DIR = path.join(__dirname, '../src'); +const SRC_DIR = path.join(import.meta.dirname, '../src'); const COMPONENT_DIRS = ['components', 'app', 'pages']; // Disallowed icon libraries (should use lucide-react) diff --git a/scripts/validate-web3.js b/scripts/validate-web3.js index 1b8afd85..82c768a2 100644 --- a/scripts/validate-web3.js +++ b/scripts/validate-web3.js @@ -6,10 +6,10 @@ * Exit code 0 = pass, 1 = fail */ -const fs = require('fs'); -const path = require('path'); +import fs from 'fs'; +import path from 'path'; -const SRC_DIR = path.join(__dirname, '../src'); +const SRC_DIR = path.join(import.meta.dirname, '../src'); let errors = []; let warnings = []; @@ -67,8 +67,8 @@ function checkWeb3Utils() { } function checkEnvExample() { - const envExamplePath = path.join(__dirname, '../.env.example'); - const envLocalPath = path.join(__dirname, '../.env.local.example'); + const envExamplePath = path.join(import.meta.dirname, '../.env.example'); + const envLocalPath = path.join(import.meta.dirname, '../.env.local.example'); if (!fs.existsSync(envExamplePath) && !fs.existsSync(envLocalPath)) { warnings.push('[WEB3] Consider adding .env.example with NEXT_PUBLIC_STARKNET_* variables'); From fffc126dd9b9e959936e2e801e28d36a7719ad9a Mon Sep 17 00:00:00 2001 From: AbuJulaybeeb Date: Thu, 23 Apr 2026 05:09:26 +0100 Subject: [PATCH 3/4] fix: resolve syntax and type errors in wallet provider and env validation --- src/providers/WalletProvider.tsx | 59 -------------------------------- src/utils/web3/envValidation.ts | 18 +++++----- 2 files changed, 10 insertions(+), 67 deletions(-) diff --git a/src/providers/WalletProvider.tsx b/src/providers/WalletProvider.tsx index 3c542627..9763fd0a 100644 --- a/src/providers/WalletProvider.tsx +++ b/src/providers/WalletProvider.tsx @@ -1,57 +1,5 @@ 'use client'; -import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; - -interface WalletContextType { - address: string | null; - isConnected: boolean; - connect: () => Promise; - disconnect: () => void; -} - -const WalletContext = createContext(null); - -export function WalletProvider({ children }: { children: ReactNode }) { - const [address, setAddress] = useState(null); - const [isConnected, setIsConnected] = useState(false); - - useEffect(() => { - if (typeof window === 'undefined') return; - // Restore session on mount - const saved = localStorage.getItem('wallet_address'); - if (saved) { - setAddress(saved); - setIsConnected(true); - } - }, []); - - const connect = async () => { - try { - if (typeof window === 'undefined') return; - // Wallet connection logic placeholder - const mockAddress = '0x0000000000000000000000000000000000000000'; - setAddress(mockAddress); - setIsConnected(true); - localStorage.setItem('wallet_address', mockAddress); - } catch (error) { - console.error('Wallet connection failed:', error); - } - }; - - const disconnect = () => { - try { - setAddress(null); - setIsConnected(false); - if (typeof window !== 'undefined') { - localStorage.removeItem('wallet_address'); - } - } catch (error) { - console.error('Wallet disconnect failed:', error); - } - }; - - return ( - import React, { createContext, useContext, useState, useCallback, useEffect, ReactNode } from 'react'; // Environment validation for wallet config @@ -204,13 +152,6 @@ export function WalletProvider({ children }: WalletProviderProps) { return {children}; } -export function useWallet() { - const context = useContext(WalletContext); - if (context === null) { - return { address: null, isConnected: false, connect: async () => {}, disconnect: () => {} }; - } - return context; -} export function useWallet(): WalletContextType { const context = useContext(WalletContext); diff --git a/src/utils/web3/envValidation.ts b/src/utils/web3/envValidation.ts index 583f61f0..488ac581 100644 --- a/src/utils/web3/envValidation.ts +++ b/src/utils/web3/envValidation.ts @@ -1,11 +1,3 @@ -export function validateStarknetEnv(): { valid: boolean; missing: string[] } { - const required = ['NEXT_PUBLIC_STARKNET_NETWORK']; - const missing = required.filter((key) => !process.env[key]); - return { valid: missing.length === 0, missing }; -} - -export function getStarknetNetwork(): string { - return process.env.NEXT_PUBLIC_STARKNET_NETWORK ?? 'testnet'; /** * Web3 Environment Validation * Validates required environment variables for Starknet integration @@ -106,3 +98,13 @@ export function isValidStarknetAddress(address: string): boolean { const cleanAddress = address.toLowerCase(); return /^0x[a-f0-9]{1,64}$/i.test(cleanAddress); } + +// Compatibility helpers for older code +export function validateStarknetEnv(): { valid: boolean; missing: string[] } { + const { isValid, errors } = validateWeb3Env(); + return { valid: isValid, missing: errors }; +} + +export function getStarknetNetwork(): string { + return process.env.NEXT_PUBLIC_STARKNET_NETWORK ?? 'testnet'; +} From 006d68f57dfa7ab7342e107adb0dd496319eed08 Mon Sep 17 00:00:00 2001 From: AbuJulaybeeb Date: Thu, 23 Apr 2026 06:13:11 +0100 Subject: [PATCH 4/4] fix: resolve typescript and linting errors across codebase --- PR_SUMMARY.md | 37 +- VISUALIZATION_IMPLEMENTATION.md | 47 +- docs/ACCESSIBILITY.md | 18 +- package-lock.json | 25 ++ package.json | 6 +- scripts/validate-ui.js | 13 +- src/app/components/courses/InstructorBio.tsx | 9 +- .../notifications/MultiChannelDelivery.tsx | 96 +++-- .../notifications/NotificationCenter.tsx | 21 +- .../notifications/NotificationTemplates.tsx | 30 +- .../notifications/UserPreferences.tsx | 141 +++--- src/app/components/quizzes/QuizContainer.tsx | 5 +- src/app/components/shared/ImageUploader.tsx | 9 +- src/app/hooks/useSearchFilters.ts | 1 - src/app/layout.tsx | 4 +- .../components/OfflineContentManager.tsx | 1 - .../components/TouchOptimizedControls.tsx | 6 +- src/app/services/offlineSync.ts | 2 +- src/app/store/messagingStore.ts | 1 - .../animations/TransitionManager.tsx | 4 +- src/components/courses/InstructorBio.tsx | 9 +- .../dashboard/AdvancedDashboard.tsx | 23 +- src/components/dashboard/DashboardFilters.tsx | 402 +++++++++--------- .../dashboard/InteractiveCharts.tsx | 214 +++++----- src/components/dashboard/RealTimeUpdater.tsx | 384 +++++++++-------- src/components/errors/ErrorBoundarySystem.tsx | 25 +- .../instructor/CourseCreationWizard.tsx | 4 +- .../search/AdvancedSearchInterface.tsx | 13 +- src/components/search/FacetedFilterSystem.tsx | 308 +++++++------- src/components/search/FilterSidebar.tsx | 298 ++++++------- .../search/IntelligentAutoComplete.tsx | 343 +++++++-------- src/components/search/SearchFilters.tsx | 12 +- .../search/SearchResultsVisualizer.tsx | 245 ++++++----- src/components/shared/ImageUploader.tsx | 9 +- src/components/visualization/QUICK_START.md | 70 +-- src/components/visualization/README.md | 29 +- .../state/form-state-manager.ts | 1 - src/form-management/types/core.ts | 3 +- .../validation/validation-system-example.ts | 2 - src/hooks/useAdvancedForms.tsx | 1 - src/hooks/useDataVisualization.tsx | 2 +- src/hooks/useErrorHandling.tsx | 1 - src/hooks/useSearchFilters.tsx | 1 - src/hooks/useThemeCustomization.tsx | 1 - src/lib/theme-provider.tsx | 6 +- src/locales/translationManager.ts | 1 - src/providers/WalletProvider.tsx | 9 +- src/services/errorReporting.ts | 1 - src/services/offlineSync.ts | 1 - src/store/devTools.ts | 1 - src/tsconfig.json | 7 +- src/utils/errorUtils.ts | 1 - src/utils/formUtils.ts | 1 - src/utils/notificationUtils.ts | 78 ++-- src/utils/performanceUtils.ts | 8 - 55 files changed, 1571 insertions(+), 1419 deletions(-) diff --git a/PR_SUMMARY.md b/PR_SUMMARY.md index e82bab29..ea1e6661 100644 --- a/PR_SUMMARY.md +++ b/PR_SUMMARY.md @@ -3,29 +3,36 @@ ## Issue #84: Implement Advanced Data Visualization ### Overview + This PR implements a comprehensive data visualization system for the TeachLink platform with interactive charts, real-time updates, custom chart builder, and data exploration tools. ### Changes Made #### New Components (4) + 1. **InteractiveChartLibrary** - Multi-type chart library with 7 chart types 2. **RealTimeDataVisualizer** - Live data visualization with WebSocket support 3. **CustomVisualizationBuilder** - User-friendly chart creation interface 4. **DataExplorationTools** - Interactive data analysis and filtering #### New Hooks (1) + 1. **useDataVisualization** - Centralized state management for visualizations #### New Utilities (1) + 1. **visualizationUtils** - 20+ helper functions for data transformation and analysis #### New Pages (1) + 1. **Visualization Demo** - Interactive showcase at `/visualization-demo` #### Tests (1) + 1. **visualizationUtils.test.ts** - 25+ test cases with comprehensive coverage #### Documentation (3) + 1. **README.md** - Complete API documentation 2. **QUICK_START.md** - 5-minute getting started guide 3. **VISUALIZATION_IMPLEMENTATION.md** - Implementation details @@ -59,6 +66,7 @@ src/ ### Features Implemented #### ✅ Chart Library + - 7 chart types (Line, Bar, Area, Pie, Doughnut, Scatter, Radar) - Interactive tooltips and legends - Click event handlers @@ -67,6 +75,7 @@ src/ - Responsive design #### ✅ Real-Time Visualization + - WebSocket integration for live updates - Automatic reconnection handling - Data simulation fallback @@ -75,6 +84,7 @@ src/ - Real-time statistics (mean, median, trend) #### ✅ Custom Chart Builder + - Add/remove labels and datasets - Real-time data editing - Live preview @@ -83,6 +93,7 @@ src/ - Export configuration to JSON #### ✅ Data Exploration + - Time range filtering (7d, 30d, 90d, 1y, all) - Chart type switching - Dataset selection @@ -92,6 +103,7 @@ src/ - Responsive statistics cards #### ✅ Additional Features + - Dark mode support - Full TypeScript types - Accessibility (WCAG 2.1 Level AA) @@ -145,6 +157,7 @@ src/ ### Demo Visit `/visualization-demo` to see: + - Interactive chart library with all 7 chart types - Real-time data visualization with live updates - Custom chart builder with full editing capabilities @@ -153,17 +166,15 @@ Visit `/visualization-demo` to see: ### Usage Examples #### Basic Chart + ```tsx import { InteractiveChartLibrary } from '@/components/visualization'; - +; ``` #### Real-Time Data + ```tsx import { RealTimeDataVisualizer } from '@/components/visualization'; @@ -171,26 +182,23 @@ import { RealTimeDataVisualizer } from '@/components/visualization'; websocketUrl="wss://api.example.com/data" chartType="area" title="Live Activity" -/> +/>; ``` #### Custom Builder + ```tsx import { CustomVisualizationBuilder } from '@/components/visualization'; - saveChart(config)} -/> + saveChart(config)} />; ``` #### Data Exploration + ```tsx import { DataExplorationTools } from '@/components/visualization'; - +; ``` ### Acceptance Criteria @@ -206,6 +214,7 @@ All acceptance criteria from issue #84 have been met: ### Documentation Comprehensive documentation provided: + - API reference for all components - Usage examples and code snippets - Best practices guide @@ -271,6 +280,7 @@ Closes #84 ### Future Enhancements Potential improvements for future PRs: + - 3D chart support - Heatmap visualizations - Gantt charts for course timelines @@ -283,6 +293,7 @@ Potential improvements for future PRs: ### Questions? For questions or clarifications, please: + 1. Check the comprehensive README 2. Review the demo page at `/visualization-demo` 3. Read the implementation guide diff --git a/VISUALIZATION_IMPLEMENTATION.md b/VISUALIZATION_IMPLEMENTATION.md index 18121b97..fa45100d 100644 --- a/VISUALIZATION_IMPLEMENTATION.md +++ b/VISUALIZATION_IMPLEMENTATION.md @@ -9,12 +9,14 @@ This document describes the implementation of advanced data visualization compon ### Components Created 1. **InteractiveChartLibrary** (`src/components/visualization/InteractiveChartLibrary.tsx`) + - Comprehensive chart library with 7 chart types - Interactive features with click handlers - Customizable styling and animations - Responsive design with dark mode support 2. **RealTimeDataVisualizer** (`src/components/visualization/RealTimeDataVisualizer.tsx`) + - Live data updates via WebSocket - Automatic data simulation fallback - Pause/resume functionality @@ -22,6 +24,7 @@ This document describes the implementation of advanced data visualization compon - Connection status monitoring 3. **CustomVisualizationBuilder** (`src/components/visualization/CustomVisualizationBuilder.tsx`) + - User-friendly chart builder interface - Add/remove labels and datasets - Real-time data editing @@ -38,6 +41,7 @@ This document describes the implementation of advanced data visualization compon ### Hooks Created **useDataVisualization** (`src/hooks/useDataVisualization.tsx`) + - Centralized state management for visualizations - WebSocket integration for real-time updates - Auto-refresh functionality @@ -47,6 +51,7 @@ This document describes the implementation of advanced data visualization compon ### Utilities Created **visualizationUtils** (`src/utils/visualizationUtils.ts`) + - Number and percentage formatting - Date label generation - Data aggregation and transformation @@ -60,6 +65,7 @@ This document describes the implementation of advanced data visualization compon ### Demo Page **Visualization Demo** (`src/app/visualization-demo/page.tsx`) + - Interactive showcase of all components - Multiple examples for each chart type - Real-time data demonstrations @@ -69,6 +75,7 @@ This document describes the implementation of advanced data visualization compon ### Tests **Visualization Utils Tests** (`src/utils/__tests__/visualizationUtils.test.ts`) + - Comprehensive test coverage for utility functions - 25+ test cases covering all major functions - Edge case handling @@ -77,6 +84,7 @@ This document describes the implementation of advanced data visualization compon ### Documentation **README** (`src/components/visualization/README.md`) + - Complete API documentation - Usage examples for all components - Best practices guide @@ -86,6 +94,7 @@ This document describes the implementation of advanced data visualization compon ## Features Implemented ### ✅ Chart Library + - [x] Line charts - [x] Bar charts - [x] Area charts @@ -99,6 +108,7 @@ This document describes the implementation of advanced data visualization compon - [x] Smooth animations ### ✅ Real-Time Visualization + - [x] WebSocket integration - [x] Live data streaming - [x] Automatic reconnection @@ -109,6 +119,7 @@ This document describes the implementation of advanced data visualization compon - [x] Trend analysis ### ✅ Custom Chart Builder + - [x] Add/remove labels - [x] Add/remove datasets - [x] Edit data values @@ -119,6 +130,7 @@ This document describes the implementation of advanced data visualization compon - [x] Export to JSON ### ✅ Data Exploration + - [x] Time range filtering - [x] Chart type switching - [x] Dataset selection @@ -129,6 +141,7 @@ This document describes the implementation of advanced data visualization compon - [x] Responsive statistics cards ### ✅ Additional Features + - [x] Dark mode support - [x] Responsive design - [x] Accessibility features @@ -180,15 +193,17 @@ import { InteractiveChartLibrary } from '@/components/visualization'; +/>; ``` ### Real-Time Data @@ -201,7 +216,7 @@ import { RealTimeDataVisualizer } from '@/components/visualization'; chartType="area" title="Live Activity" updateInterval={2000} -/> +/>; ``` ### Custom Builder @@ -209,9 +224,7 @@ import { RealTimeDataVisualizer } from '@/components/visualization'; ```tsx import { CustomVisualizationBuilder } from '@/components/visualization'; - saveToDatabase(config)} -/> + saveToDatabase(config)} />; ``` ### Data Exploration @@ -219,10 +232,7 @@ import { CustomVisualizationBuilder } from '@/components/visualization'; ```tsx import { DataExplorationTools } from '@/components/visualization'; - +; ``` ## Testing @@ -234,6 +244,7 @@ npm test -- src/utils/__tests__/visualizationUtils.test.ts ``` Test coverage includes: + - Number formatting - Percentage formatting - Date label generation @@ -246,6 +257,7 @@ Test coverage includes: ## Accessibility All components follow WCAG 2.1 Level AA guidelines: + - Keyboard navigation support - ARIA labels and roles - Screen reader compatibility @@ -271,6 +283,7 @@ All components follow WCAG 2.1 Level AA guidelines: ## Future Enhancements Potential improvements for future iterations: + - 3D chart support - Heatmap visualizations - Gantt charts for course timelines @@ -285,6 +298,7 @@ Potential improvements for future iterations: ## Integration Points The visualization components integrate with: + - Course analytics system - Student progress tracking - Real-time activity monitoring @@ -304,16 +318,19 @@ The visualization components integrate with: ## Deployment Notes 1. Ensure all dependencies are installed: + ```bash npm install ``` 2. Build the project: + ```bash npm run build ``` 3. Run tests: + ```bash npm test ``` @@ -354,6 +371,7 @@ Date: March 25, 2026 ## Screenshots Visit `/visualization-demo` to see live examples of: + - Interactive chart library with 7 chart types - Real-time data visualization with live updates - Custom chart builder with drag-and-drop @@ -364,6 +382,7 @@ Visit `/visualization-demo` to see live examples of: This implementation provides a comprehensive, production-ready data visualization solution for the TeachLink platform. All components are fully tested, documented, and ready for integration into the main application. The visualization system is: + - **Scalable**: Handles large datasets efficiently - **Flexible**: Supports multiple chart types and configurations - **Interactive**: Provides rich user interactions diff --git a/docs/ACCESSIBILITY.md b/docs/ACCESSIBILITY.md index 48554385..f9bd7816 100644 --- a/docs/ACCESSIBILITY.md +++ b/docs/ACCESSIBILITY.md @@ -4,14 +4,14 @@ This app ships a layered accessibility toolkit aimed at **WCAG 2.1 Level AA** pa ## Architecture -| Piece | Role | -| --- | --- | -| `AccessibilityProvider` | Global context: `announce`, motion preference, keyboard modality, `runPageAudit`. | -| `ScreenReaderSupport` | Permanent **polite** and **assertive** live regions for reliable announcements. | -| `KeyboardNavigation` | **Alt+M** focuses main content; **Shift+?** opens a shortcuts dialog (focus-trapped). Toolbar **roving** focus with `[data-roving-root]`. | -| `AccessibilityAudit` | Dev-only (by default) floating panel with heuristic DOM checks. | -| `useAccessibility()` | Reads context, or a safe fallback when used outside the provider. | -| `accessibilityUtils` | Focus helpers, contrast math, `checkAccessibilityIssues`, `runAccessibilityAudit`. | +| Piece | Role | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `AccessibilityProvider` | Global context: `announce`, motion preference, keyboard modality, `runPageAudit`. | +| `ScreenReaderSupport` | Permanent **polite** and **assertive** live regions for reliable announcements. | +| `KeyboardNavigation` | **Alt+M** focuses main content; **Shift+?** opens a shortcuts dialog (focus-trapped). Toolbar **roving** focus with `[data-roving-root]`. | +| `AccessibilityAudit` | Dev-only (by default) floating panel with heuristic DOM checks. | +| `useAccessibility()` | Reads context, or a safe fallback when used outside the provider. | +| `accessibilityUtils` | Focus helpers, contrast math, `checkAccessibilityIssues`, `runAccessibilityAudit`. | ## Using the provider @@ -39,7 +39,7 @@ Use **assertive** only for urgent errors or time-sensitive status. - Give the primary `
` a stable id such as `main-content` so skip links and **Alt+M** work everywhere. There should be **exactly one** `
` (or `role="main"`) per view. - For horizontal toolbars, add `data-roving-root` on the toolbar container. **Left/Right arrow** moves among buttons, links, tabs, and elements marked with `data-roving-item` (including those using `tabindex="-1"` for roving patterns). -## What automation does *not* prove +## What automation does _not_ prove - **WCAG 2.1 AA** for the whole product requires page-by-page review (contrast in context, timing, reflow, errors, etc.). - The audit panel and `checkAccessibilityIssues` only flag **some** DOM patterns. They miss false positives/negatives and cannot judge screen reader UX. diff --git a/package-lock.json b/package-lock.json index 92ed1ebf..1358b726 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3362,6 +3362,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3375,6 +3376,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3388,6 +3390,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3401,6 +3404,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3414,6 +3418,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3427,6 +3432,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3440,6 +3446,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3453,6 +3460,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3466,6 +3474,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3479,6 +3488,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3492,6 +3502,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3505,6 +3516,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3518,6 +3530,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3531,6 +3544,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3544,6 +3558,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3557,6 +3572,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3570,6 +3586,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3583,6 +3600,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3596,6 +3614,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3609,6 +3628,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3622,6 +3642,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3635,6 +3656,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3648,6 +3670,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3661,6 +3684,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3674,6 +3698,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ diff --git a/package.json b/package.json index 965f9e21..3de8f02f 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,9 @@ "build": "next build", "start": "next start", "type-check": "tsc --noEmit", - "lint": "next lint", + "lint": "next lint --max-warnings=0", "validate:ui": "node scripts/validate-ui.js", "validate:web3": "node scripts/validate-web3.js", - "lint": "next lint --max-warnings=0", "lint:fix": "eslint --ext .ts,.tsx,.js,.jsx . --fix", "format": "prettier --write .", "prepare": "husky install", @@ -19,9 +18,6 @@ "test:coverage": "vitest run --coverage", "test:ui": "vitest --ui", "test:watch": "vitest --watch", - "type-check": "tsc --noEmit", - "validate:ui": "node scripts/validate-ui.js", - "validate:web3": "node scripts/validate-web3.js", "validate": "npm run validate:ui && npm run validate:web3" }, "dependencies": { diff --git a/scripts/validate-ui.js b/scripts/validate-ui.js index 1f395d6c..95235a02 100644 --- a/scripts/validate-ui.js +++ b/scripts/validate-ui.js @@ -108,14 +108,23 @@ function validateFiles() { } function printResults() { + console.log('UI Validation Summary:'); + console.log(`Files checked in: ${COMPONENT_DIRS.join(', ')}`); + console.log(`Found ${warnings.length} warnings and ${errors.length} errors.\n`); + if (warnings.length > 0) { - warnings.forEach((w) => {}); + console.log('--- WARNINGS ---'); + warnings.forEach((w) => console.warn(w)); + console.log(''); } if (errors.length > 0) { - errors.forEach((e) => {}); + console.log('--- ERRORS ---'); + errors.forEach((e) => console.error(e)); process.exit(1); } + + console.log('UI validation passed successfully! ✨'); process.exit(0); } diff --git a/src/app/components/courses/InstructorBio.tsx b/src/app/components/courses/InstructorBio.tsx index 37a02ecc..6a9e0009 100644 --- a/src/app/components/courses/InstructorBio.tsx +++ b/src/app/components/courses/InstructorBio.tsx @@ -32,7 +32,14 @@ export default function InstructorBio({
- {name} + {name}

diff --git a/src/app/components/notifications/MultiChannelDelivery.tsx b/src/app/components/notifications/MultiChannelDelivery.tsx index c06f60f5..69776aaa 100644 --- a/src/app/components/notifications/MultiChannelDelivery.tsx +++ b/src/app/components/notifications/MultiChannelDelivery.tsx @@ -70,7 +70,7 @@ export default function MultiChannelDelivery({ }); const [selectedChannels, setSelectedChannels] = useState>( - new Set(['in-app']) + new Set(['in-app']), ); const [deliveryStatuses, setDeliveryStatuses] = useState([]); const [message, setMessage] = useState(''); @@ -99,9 +99,7 @@ export default function MultiChannelDelivery({ const channels = Array.from(selectedChannels); // Initialize delivery statuses - setDeliveryStatuses( - channels.map((channel) => ({ channel, status: 'sending' })) - ); + setDeliveryStatuses(channels.map((channel) => ({ channel, status: 'sending' }))); try { // Create the notification @@ -122,10 +120,8 @@ export default function MultiChannelDelivery({ channels.map((channel) => ({ channel, status: results[channel] ? 'success' : 'failed', - message: results[channel] - ? 'Delivered successfully' - : 'Delivery failed', - })) + message: results[channel] ? 'Delivered successfully' : 'Delivery failed', + })), ); onDeliveryComplete?.(results); @@ -144,12 +140,20 @@ export default function MultiChannelDelivery({ channel, status: 'failed', message: 'An error occurred', - })) + })), ); } finally { setIsSending(false); } - }, [message, selectedChannels, category, priority, sendNotification, sendToAllChannels, onDeliveryComplete]); + }, [ + message, + selectedChannels, + category, + priority, + sendNotification, + sendToAllChannels, + onDeliveryComplete, + ]); // Check if channel is enabled in preferences const isChannelEnabled = (channel: NotificationChannel): boolean => { @@ -191,43 +195,47 @@ export default function MultiChannelDelivery({ Select Delivery Channels
- {(Object.entries(channelConfig) as [NotificationChannel, typeof channelConfig['in-app']][]).map( - ([channel, config]) => { - const isSelected = selectedChannels.has(channel); - const isEnabled = isChannelEnabled(channel); + {( + Object.entries(channelConfig) as [ + NotificationChannel, + (typeof channelConfig)['in-app'], + ][] + ).map(([channel, config]) => { + const isSelected = selectedChannels.has(channel); + const isEnabled = isChannelEnabled(channel); - return ( - - ); - } - )} + > +
+ {config.icon} + + {config.label} + + {isSelected && } +
+

{config.description}

+ {!isEnabled && ( +

+ + Disabled in preferences +

+ )} + + ); + })}

@@ -282,9 +290,7 @@ export default function MultiChannelDelivery({ {/* Delivery Status */} {deliveryStatuses.length > 0 && (
- +
{deliveryStatuses.map((status) => { const config = channelConfig[status.channel]; diff --git a/src/app/components/notifications/NotificationCenter.tsx b/src/app/components/notifications/NotificationCenter.tsx index 7a299b34..1307de87 100644 --- a/src/app/components/notifications/NotificationCenter.tsx +++ b/src/app/components/notifications/NotificationCenter.tsx @@ -70,9 +70,7 @@ export default function NotificationCenter({ // Apply search filter if (searchQuery) { const query = searchQuery.toLowerCase(); - result = result.filter((n) => - n.message.toLowerCase().includes(query) - ); + result = result.filter((n) => n.message.toLowerCase().includes(query)); } // Apply read filter @@ -149,7 +147,8 @@ export default function NotificationCenter({ setSortBy('newest'); }; - const hasActiveFilters = searchQuery || filterRead !== 'all' || filterCategory !== 'all' || sortBy !== 'newest'; + const hasActiveFilters = + searchQuery || filterRead !== 'all' || filterCategory !== 'all' || sortBy !== 'newest'; return (
@@ -224,10 +223,7 @@ export default function NotificationCenter({
Filters {hasActiveFilters && ( - )} @@ -321,15 +317,10 @@ export default function NotificationCenter({

- {hasActiveFilters - ? 'No notifications match your filters' - : "You're all caught up!"} + {hasActiveFilters ? 'No notifications match your filters' : "You're all caught up!"}

{hasActiveFilters && ( - )} diff --git a/src/app/components/notifications/NotificationTemplates.tsx b/src/app/components/notifications/NotificationTemplates.tsx index e7b11c0e..bfe8afc0 100644 --- a/src/app/components/notifications/NotificationTemplates.tsx +++ b/src/app/components/notifications/NotificationTemplates.tsx @@ -119,8 +119,7 @@ export default function NotificationTemplates({ template.title.toLowerCase().includes(searchQuery.toLowerCase()) || template.body.toLowerCase().includes(searchQuery.toLowerCase()); - const matchesCategory = - filterCategory === 'all' || template.category === filterCategory; + const matchesCategory = filterCategory === 'all' || template.category === filterCategory; return matchesSearch && matchesCategory; }); @@ -142,9 +141,7 @@ export default function NotificationTemplates({ const handleSaveTemplate = (template: NotificationTemplate) => { if (editingTemplate) { // Update existing - setTemplates((prev) => - prev.map((t) => (t.id === template.id ? template : t)) - ); + setTemplates((prev) => prev.map((t) => (t.id === template.id ? template : t))); onTemplateUpdate?.(template); } else { // Create new @@ -275,10 +272,7 @@ export default function NotificationTemplates({ const preview = getPreviewContent(template); return ( -
+
@@ -340,9 +334,7 @@ export default function NotificationTemplates({ ))}
diff --git a/src/app/components/notifications/UserPreferences.tsx b/src/app/components/notifications/UserPreferences.tsx index 8856609b..08ecc8fa 100644 --- a/src/app/components/notifications/UserPreferences.tsx +++ b/src/app/components/notifications/UserPreferences.tsx @@ -69,7 +69,9 @@ const channelIcons: Record = { export default function UserPreferences({ userId, onSave }: UserPreferencesProps) { const { preferences, updatePreferences, isLoading } = useNotifications({ userId }); - const [localPreferences, setLocalPreferences] = useState(null); + const [localPreferences, setLocalPreferences] = useState( + null, + ); const [hasChanges, setHasChanges] = useState(false); const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'success' | 'error'>('idle'); const [errors, setErrors] = useState([]); @@ -116,7 +118,7 @@ export default function UserPreferences({ userId, onSave }: UserPreferencesProps const toggleCategory = (category: NotificationCategory) => { updateLocalPref( `categories.${category}.enabled`, - !localPreferences?.categories[category]?.enabled + !localPreferences?.categories[category]?.enabled, ); }; @@ -202,9 +204,10 @@ export default function UserPreferences({ userId, onSave }: UserPreferencesProps onClick={() => toggleChannel(channel)} className={` p-3 rounded-lg border-2 text-left transition-all - ${isEnabled - ? 'bg-blue-50 border-blue-200' - : 'bg-white border-gray-200 hover:border-gray-300' + ${ + isEnabled + ? 'bg-blue-50 border-blue-200' + : 'bg-white border-gray-200 hover:border-gray-300' } `} > @@ -212,15 +215,23 @@ export default function UserPreferences({ userId, onSave }: UserPreferencesProps {channelIcons[channel]} - + {channel === 'in-app' ? 'In-App' : channel} -
-
+
+
@@ -237,14 +248,18 @@ export default function UserPreferences({ userId, onSave }: UserPreferencesProps

Quiet Hours

@@ -297,41 +312,48 @@ export default function UserPreferences({ userId, onSave }: UserPreferencesProps

Notification Categories

- {(Object.entries(categoryLabels) as [NotificationCategory, typeof categoryLabels['system']][]).map( - ([category, { label, description }]) => { - const categoryPrefs = localPreferences.categories[category]; - const isEnabled = categoryPrefs?.enabled ?? true; - - return ( -
-
-
-
-
{label}
-
{description}
-
- + {( + Object.entries(categoryLabels) as [ + NotificationCategory, + (typeof categoryLabels)['system'], + ][] + ).map(([category, { label, description }]) => { + const categoryPrefs = localPreferences.categories[category]; + const isEnabled = categoryPrefs?.enabled ?? true; + + return ( +
+
+
+
+
{label}
+
{description}
+ +
- {isEnabled && ( -
-
Channels for this category:
-
- {(['in-app', 'push', 'email', 'sms'] as NotificationChannel[]).map((channel) => { + {isEnabled && ( +
+
Channels for this category:
+
+ {(['in-app', 'push', 'email', 'sms'] as NotificationChannel[]).map( + (channel) => { const isChannelEnabled = categoryPrefs?.channels?.includes(channel); const isGlobalEnabled = localPreferences.channels[channel === 'in-app' ? 'inApp' : channel]; @@ -344,9 +366,10 @@ export default function UserPreferences({ userId, onSave }: UserPreferencesProps className={` inline-flex items-center gap-1 px-2 py-1 rounded text-xs font-medium transition-colors - ${isChannelEnabled - ? 'bg-blue-600 text-white' - : 'bg-white text-gray-600 border border-gray-300' + ${ + isChannelEnabled + ? 'bg-blue-600 text-white' + : 'bg-white text-gray-600 border border-gray-300' } ${!isGlobalEnabled ? 'opacity-50 cursor-not-allowed' : ''} `} @@ -357,15 +380,15 @@ export default function UserPreferences({ userId, onSave }: UserPreferencesProps ); - })} -
+ }, + )}
- )} -
+
+ )}
- ); - } - )} +
+ ); + })}
diff --git a/src/app/components/quizzes/QuizContainer.tsx b/src/app/components/quizzes/QuizContainer.tsx index 01081641..93ba19bf 100644 --- a/src/app/components/quizzes/QuizContainer.tsx +++ b/src/app/components/quizzes/QuizContainer.tsx @@ -4,9 +4,8 @@ import { useEffect } from 'react'; import { useRouter } from 'next/navigation'; import Countdown from 'react-countdown'; import { FaArrowLeft, FaArrowRight, FaCheck } from 'react-icons/fa'; -import { useQuizStore } from '@/store/quizStore'; -import type { Quiz } from '@/store/quizStore'; -import { Quiz, useQuizStore } from '@/app/store/quizStore'; +import { useQuizStore } from '@/app/store/quizStore'; +import type { Quiz } from '@/app/store/quizStore'; import QuestionCard from './QuestionCard'; interface QuizContainerProps { diff --git a/src/app/components/shared/ImageUploader.tsx b/src/app/components/shared/ImageUploader.tsx index 659a4ce2..3c266c8b 100644 --- a/src/app/components/shared/ImageUploader.tsx +++ b/src/app/components/shared/ImageUploader.tsx @@ -35,7 +35,14 @@ export default function ImageUploader({ onImageSelect, initialImageUrl }: ImageU onClick={handleClick} > {previewUrl ? ( - Profile preview + Profile preview ) : (
{ params.set('q', filters.searchTerm); } - const newUrl = params.toString() ? `${pathname}?${params.toString()}` : (pathname ?? '/'); const newUrl = params.toString() ? `${pathname ?? ''}?${params.toString()}` : pathname ?? ''; router.replace(newUrl, { scroll: false }); }, [filters, pathname, router]); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 6fa2bfd3..e77644e4 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -32,12 +32,12 @@ export const metadata: Metadata = { manifest: '/manifest.json', }; -export default function RootLayout({ +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { - const cookieStore = cookies(); + const cookieStore = await cookies(); const themeCookie = cookieStore.get('theme'); const defaultTheme = themeCookie ? themeCookie.value : 'system'; diff --git a/src/app/mobile/components/OfflineContentManager.tsx b/src/app/mobile/components/OfflineContentManager.tsx index 6e7c638c..0687421f 100644 --- a/src/app/mobile/components/OfflineContentManager.tsx +++ b/src/app/mobile/components/OfflineContentManager.tsx @@ -1,6 +1,5 @@ import { useState, useEffect } from 'react'; import { Download, Check, X, Wifi, WifiOff, Trash2, AlertCircle, RefreshCw } from 'lucide-react'; -import { Download, Check, Wifi, WifiOff, Trash2, AlertCircle, RefreshCw } from 'lucide-react'; import { apiService } from '../services/api'; import { offlineStorage } from '../services/offlineStorage'; import { Course, OfflineContent } from '../types/mobile'; diff --git a/src/app/mobile/components/TouchOptimizedControls.tsx b/src/app/mobile/components/TouchOptimizedControls.tsx index ca54b1c5..560916d5 100644 --- a/src/app/mobile/components/TouchOptimizedControls.tsx +++ b/src/app/mobile/components/TouchOptimizedControls.tsx @@ -1,6 +1,6 @@ -import { useState, useRef, useEffect } from "react"; -import { Play, Pause, SkipBack, SkipForward, Volume2, VolumeX, Maximize, X } from "lucide-react"; -import { offlineStorage } from "../services/offlineStorage"; +import { useState, useRef, useEffect } from 'react'; +import { Play, Pause, SkipBack, SkipForward, Volume2, VolumeX, Maximize, X } from 'lucide-react'; +import { offlineStorage } from '../services/offlineStorage'; interface TouchOptimizedControlsProps { videoTitle: string; diff --git a/src/app/services/offlineSync.ts b/src/app/services/offlineSync.ts index 23778a40..c4f43ae1 100644 --- a/src/app/services/offlineSync.ts +++ b/src/app/services/offlineSync.ts @@ -304,7 +304,7 @@ class OfflineSyncService { const tx = this.db.transaction('conflicts', 'readonly'); const store = tx.objectStore('conflicts'); const index = store.index('resolved'); - + return await index.getAll(false as unknown as IDBValidKey); const all = await index.getAll(); return all.filter((c) => !c.resolved); diff --git a/src/app/store/messagingStore.ts b/src/app/store/messagingStore.ts index e0e7afb4..8f51e046 100644 --- a/src/app/store/messagingStore.ts +++ b/src/app/store/messagingStore.ts @@ -1,7 +1,6 @@ import { create } from 'zustand'; import { io } from 'socket.io-client'; import type { Socket } from 'socket.io-client'; -import io from 'socket.io-client'; export interface Attachment { id: string; diff --git a/src/components/animations/TransitionManager.tsx b/src/components/animations/TransitionManager.tsx index 66474e40..5c777591 100644 --- a/src/components/animations/TransitionManager.tsx +++ b/src/components/animations/TransitionManager.tsx @@ -22,8 +22,8 @@ export async function orchestrateTransitions( for (const item of items) { await item.run(); // small frame gap to allow layout/paint - await new Promise((r) => scheduleFrame(r)) - await new Promise((r) => scheduleFrame(() => r())) + await new Promise((r) => scheduleFrame(r)); + await new Promise((r) => scheduleFrame(() => r())); } } diff --git a/src/components/courses/InstructorBio.tsx b/src/components/courses/InstructorBio.tsx index 803e63e3..958ff82a 100644 --- a/src/components/courses/InstructorBio.tsx +++ b/src/components/courses/InstructorBio.tsx @@ -32,7 +32,14 @@ export default function InstructorBio({
- {name} + {name}

diff --git a/src/components/dashboard/AdvancedDashboard.tsx b/src/components/dashboard/AdvancedDashboard.tsx index ee929a06..ff9bb81a 100644 --- a/src/components/dashboard/AdvancedDashboard.tsx +++ b/src/components/dashboard/AdvancedDashboard.tsx @@ -103,16 +103,19 @@ export const AdvancedDashboard = React.memo(({ className }), ); - const handleDragEnd = useCallback((event: DragEndEvent) => { - const { active, over } = event; - if (!over || active.id === over.id) return; - - const fromIndex = panels.findIndex((p) => p.id === active.id); - const toIndex = panels.findIndex((p) => p.id === over.id); - if (fromIndex !== -1 && toIndex !== -1) { - reorderPanels(fromIndex, toIndex); - } - }, [panels, reorderPanels]); + const handleDragEnd = useCallback( + (event: DragEndEvent) => { + const { active, over } = event; + if (!over || active.id === over.id) return; + + const fromIndex = panels.findIndex((p) => p.id === active.id); + const toIndex = panels.findIndex((p) => p.id === over.id); + if (fromIndex !== -1 && toIndex !== -1) { + reorderPanels(fromIndex, toIndex); + } + }, + [panels, reorderPanels], + ); const handleShare = useCallback(async () => { const url = generateShareURL(); diff --git a/src/components/dashboard/DashboardFilters.tsx b/src/components/dashboard/DashboardFilters.tsx index 21c5ca86..2f269062 100644 --- a/src/components/dashboard/DashboardFilters.tsx +++ b/src/components/dashboard/DashboardFilters.tsx @@ -45,211 +45,225 @@ const DEFAULT_METRICS = ['enrollments', 'revenue', 'completions', 'views']; // ─── Component ──────────────────────────────────────────────────────────────── -export const DashboardFilters = React.memo(({ - filters, - onFiltersChange, - onReset, - categories = DEFAULT_CATEGORIES, - metrics = DEFAULT_METRICS, - className = '', -}) => { - const [isOpen, setIsOpen] = useState(false); - - const toggleCategory = useCallback((cat: string) => { - const next = filters.categories.includes(cat) - ? filters.categories.filter((c) => c !== cat) - : [...filters.categories, cat]; - onFiltersChange({ categories: next }); - }, [filters.categories, onFiltersChange]); - - const removeCategory = useCallback((cat: string) => { - onFiltersChange({ categories: filters.categories.filter((c) => c !== cat) }); - }, [filters.categories, onFiltersChange]); - - const activeFilterCount = useMemo(() => ( - (filters.timeRange !== '30d' ? 1 : 0) + - filters.categories.length + - (filters.metric !== 'enrollments' ? 1 : 0) + - (filters.aggregation !== 'sum' ? 1 : 0) - ), [filters]); - - return ( -
- {/* Header bar */} -
-
- - - {/* Active filter badges */} -
- {filters.timeRange !== '30d' && ( - - {TIME_RANGE_OPTIONS.find((o) => o.value === filters.timeRange)?.label} - + + {/* Active filter badges */} +
+ {filters.timeRange !== '30d' && ( + - - - - )} - {filters.categories.map((cat, i) => ( - - {cat} - + + )} + {filters.categories.map((cat, i) => ( + - - - - ))} + {cat} + + + ))} +
+ + {activeFilterCount > 0 && ( + + )}
- {activeFilterCount > 0 && ( - - )} -
+ {/* Time Range */} +
+ + +
- {/* Expandable panel */} - {isOpen && ( -
- {/* Time Range */} -
- - -
+ {/* Aggregation */} +
+ + +
- {/* Aggregation */} -
- - -
+ {/* Metric */} +
+ + Metric + +
+ {metrics.map((m) => ( + + ))} +
+
- {/* Metric */} -
- - Metric - -
- {metrics.map((m) => ( - - ))} -
-
- - {/* Categories */} -
- - Categories - -
- {categories.map((cat, i) => { - const isActive = filters.categories.includes(cat); - return ( - - ); - })} + {/* Categories */} +
+ + Categories + +
+ {categories.map((cat, i) => { + const isActive = filters.categories.includes(cat); + return ( + + ); + })} +
-
- )} -
- ); -}); + )} +
+ ); + }, +); DashboardFilters.displayName = 'DashboardFilters'; diff --git a/src/components/dashboard/InteractiveCharts.tsx b/src/components/dashboard/InteractiveCharts.tsx index b79548fa..6648b7ea 100644 --- a/src/components/dashboard/InteractiveCharts.tsx +++ b/src/components/dashboard/InteractiveCharts.tsx @@ -49,118 +49,122 @@ const CHART_TYPE_BUTTONS: { type: ChartType; Icon: React.ElementType; label: str // ─── Component ──────────────────────────────────────────────────────────────── -export const InteractiveCharts = React.memo(({ - panelId, - data, - chartType, - title, - drillDownIndex, - onChartTypeChange, - onDrillDown, - onClearDrillDown, - className = '', -}) => { - const isDrillDown = drillDownIndex !== null; - const drillDownData = isDrillDown ? getDrillDownData(data, drillDownIndex) : null; - const drillDownLabel = isDrillDown ? data.labels[drillDownIndex] ?? 'Selected' : null; +export const InteractiveCharts = React.memo( + ({ + panelId, + data, + chartType, + title, + drillDownIndex, + onChartTypeChange, + onDrillDown, + onClearDrillDown, + className = '', + }) => { + const isDrillDown = drillDownIndex !== null; + const drillDownData = isDrillDown ? getDrillDownData(data, drillDownIndex) : null; + const drillDownLabel = isDrillDown ? data.labels[drillDownIndex] ?? 'Selected' : null; - return ( -
- {/* Chart type toolbar */} -
- {CHART_TYPE_BUTTONS.map(({ type, Icon, label }) => ( - - ))} -
- - {/* Main chart */} - - {!isDrillDown ? ( - - - onDrillDown(data?.activeTooltipIndex ?? data?.index ?? 0) - } - /> - - ) : ( - - {/* Breadcrumb */} -
- -
+ return ( +
+ {/* Chart type toolbar */} +
+ {CHART_TYPE_BUTTONS.map(({ type, Icon, label }) => ( + + ))} +
- {/* Drill-down chart */} - {drillDownData && ( + {/* Main chart */} + + {!isDrillDown ? ( + + onDrillDown(data?.activeTooltipIndex ?? data?.index ?? 0) + } /> - )} - - {/* Back button */} - - - )} - -
- ); -}); + {/* Breadcrumb */} +
+ +
+ + {/* Drill-down chart */} + {drillDownData && ( + + )} + + {/* Back button */} + +
+ )} +
+
+ ); + }, +); InteractiveCharts.displayName = 'InteractiveCharts'; diff --git a/src/components/dashboard/RealTimeUpdater.tsx b/src/components/dashboard/RealTimeUpdater.tsx index 08a2cb8f..4bcd0dd7 100644 --- a/src/components/dashboard/RealTimeUpdater.tsx +++ b/src/components/dashboard/RealTimeUpdater.tsx @@ -32,20 +32,29 @@ const SPEED_OPTIONS = [ // ─── Component ──────────────────────────────────────────────────────────────── -export const RealTimeUpdater = React.memo(({ - title = 'Live Activity', - chartType = 'line', - websocketUrl, - updateInterval: initialInterval = 2000, - maxDataPoints = 20, - className = '', -}) => { - const [isPaused, setIsPaused] = useState(false); - const [interval, setIntervalValue] = useState(initialInterval); - const simulationEnabled = !websocketUrl; - - const { data, isConnected, isLoading, error, updateData, addDataPoint, config, calculateStats } = - useDataVisualization({ +export const RealTimeUpdater = React.memo( + ({ + title = 'Live Activity', + chartType = 'line', + websocketUrl, + updateInterval: initialInterval = 2000, + maxDataPoints = 20, + className = '', + }) => { + const [isPaused, setIsPaused] = useState(false); + const [interval, setIntervalValue] = useState(initialInterval); + const simulationEnabled = !websocketUrl; + + const { + data, + isConnected, + isLoading, + error, + updateData, + addDataPoint, + config, + calculateStats, + } = useDataVisualization({ initialData: { labels: [], datasets: [ @@ -68,187 +77,192 @@ export const RealTimeUpdater = React.memo(({ websocketUrl, }); - // Simulate real-time data when no WebSocket URL provided - useEffect(() => { - if (!simulationEnabled || isPaused) return; - - const timer = setInterval(() => { - const timeLabel = new Date().toLocaleTimeString(); - const value = Math.floor(Math.random() * 100); - addDataPoint(0, value, timeLabel); - - if (data && data.labels.length > maxDataPoints) { - updateData({ - labels: data.labels.slice(-maxDataPoints), - datasets: data.datasets.map((ds) => ({ - ...ds, - data: ds.data.slice(-maxDataPoints), - })), - }); - } - }, interval); - - return () => clearInterval(timer); - }, [simulationEnabled, isPaused, interval, maxDataPoints, addDataPoint, data, updateData]); - - const stats = calculateStats(); - - const statusText = isPaused - ? 'Paused' - : isConnected - ? 'Connected' - : simulationEnabled - ? 'Simulating' - : 'Disconnected'; - - const statusColor = isPaused - ? 'text-yellow-600 dark:text-yellow-400' - : isConnected || simulationEnabled - ? 'text-green-600 dark:text-green-400' - : 'text-red-600 dark:text-red-400'; - - const handleReset = useCallback(() => { - updateData({ - labels: [], - datasets: [ - { - label: 'Live Data', - data: [], - borderColor: '#3b82f6', - backgroundColor: 'rgba(59,130,246,0.1)', - borderWidth: 2, - }, - ], - }); - }, [updateData]); - - return ( -
- {/* Status bar */} -
-
- {/* Connection status */} -
- {(isConnected || simulationEnabled) && !isPaused ? ( -
+ // Simulate real-time data when no WebSocket URL provided + useEffect(() => { + if (!simulationEnabled || isPaused) return; + + const timer = setInterval(() => { + const timeLabel = new Date().toLocaleTimeString(); + const value = Math.floor(Math.random() * 100); + addDataPoint(0, value, timeLabel); + + if (data && data.labels.length > maxDataPoints) { + updateData({ + labels: data.labels.slice(-maxDataPoints), + datasets: data.datasets.map((ds) => ({ + ...ds, + data: ds.data.slice(-maxDataPoints), + })), + }); + } + }, interval); + + return () => clearInterval(timer); + }, [simulationEnabled, isPaused, interval, maxDataPoints, addDataPoint, data, updateData]); + + const stats = calculateStats(); + + const statusText = isPaused + ? 'Paused' + : isConnected + ? 'Connected' + : simulationEnabled + ? 'Simulating' + : 'Disconnected'; + + const statusColor = isPaused + ? 'text-yellow-600 dark:text-yellow-400' + : isConnected || simulationEnabled + ? 'text-green-600 dark:text-green-400' + : 'text-red-600 dark:text-red-400'; + + const handleReset = useCallback(() => { + updateData({ + labels: [], + datasets: [ + { + label: 'Live Data', + data: [], + borderColor: '#3b82f6', + backgroundColor: 'rgba(59,130,246,0.1)', + borderWidth: 2, + }, + ], + }); + }, [updateData]); - {/* Data point count */} - {data && data.labels.length > 0 && ( -
-