diff --git a/App.test.tsx b/App.test.tsx index aba6be2..861a690 100644 --- a/App.test.tsx +++ b/App.test.tsx @@ -1,4 +1,30 @@ import { render, screen } from '@testing-library/react'; import { beforeEach, describe, expect, it } from 'vitest'; import App from './App'; -describe('Math-CS app shell', () => { beforeEach(() => localStorage.clear()); it('provides an accessible notebook shell', () => { render(); expect(screen.getAllByRole('main')).toHaveLength(1); expect(screen.getAllByRole('heading', { level: 1 })).toHaveLength(1); expect(screen.getByLabelText('Primary navigation')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Open Math I' })).toBeVisible(); expect(document.querySelector('[aria-hidden="true"]')).not.toBeNull(); }); }); +import { COURSES } from './data/courseCatalog'; +import { createDefaultMathCsState, MATH_CS_STORAGE_PREFIX } from './utils/mathCsStorage'; +import { markTopicComplete } from './utils/progress'; + +describe('Math-CS app shell', () => { + beforeEach(() => localStorage.clear()); + + it('provides an accessible notebook shell', () => { + render(); + expect(screen.getAllByRole('main')).toHaveLength(1); + expect(screen.getAllByRole('heading', { level: 1 })).toHaveLength(1); + expect(screen.getByRole('navigation', { name: 'Primary navigation' })).toBeInTheDocument(); + expect(screen.getByRole('navigation', { name: 'Mobile navigation' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Open Math I' })).toBeVisible(); + expect(document.querySelector('[aria-hidden="true"]')).not.toBeNull(); + }); + + it('derives and passes overall progress from stored course completion', () => { + const seeded = COURSES[0].topics.slice(0, 4).reduce( + (state, topic) => markTopicComplete(state, topic.id), + createDefaultMathCsState(), + ); + localStorage.setItem(`${MATH_CS_STORAGE_PREFIX}state`, JSON.stringify(seeded)); + render(); + expect(screen.getByRole('img', { name: 'Overall progress: 24%' })).toBeInTheDocument(); + }); +}); diff --git a/App.tsx b/App.tsx index 2033f5a..8241edd 100644 --- a/App.tsx +++ b/App.tsx @@ -14,9 +14,9 @@ import { FormulaWorkspaceView } from './views/FormulaWorkspaceView'; import { AIChatView } from './views/AIChatView'; import { QuizSubmission } from './components/quiz/QuizRunner'; import { AppDestination } from './types'; -import { getCourse, getTopic } from './data/courseCatalog'; +import { COURSES, getCourse, getTopic } from './data/courseCatalog'; import { createDefaultMathCsState, loadMathCsState, MathCsState, saveMathCsState } from './utils/mathCsStorage'; -import { markTopicComplete, recordQuizResult, removeFormula, saveFormula, saveFormulaNote } from './utils/progress'; +import { getOverallProgress, markTopicComplete, recordQuizResult, removeFormula, saveFormula, saveFormulaNote } from './utils/progress'; const isValidDestination = (destination: AppDestination): boolean => { if (destination.section === 'course') return Boolean(destination.courseId && getCourse(destination.courseId)); @@ -31,6 +31,7 @@ const App: React.FC = () => { const stored = initial.lastDestination as AppDestination | null; const [destination, setDestination] = useState(stored && isValidDestination(stored) ? stored : { section: 'dashboard' }); const [menuOpen, setMenuOpen] = useState(false); + const overallProgress = getOverallProgress(state, COURSES); const updateState = (next: MathCsState) => { setState(next); saveMathCsState(next); }; const navigate = (next: AppDestination) => { @@ -60,6 +61,6 @@ const App: React.FC = () => { } }; - return
setMenuOpen(false)} />
Math-CS
{renderContent()}
; + return
setMenuOpen(false)} overallProgress={overallProgress} />
Math-CS
{renderContent()}
; }; export default App; diff --git a/components/AnimatedBackground.test.tsx b/components/AnimatedBackground.test.tsx new file mode 100644 index 0000000..e86e708 --- /dev/null +++ b/components/AnimatedBackground.test.tsx @@ -0,0 +1,13 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { AnimatedBackground } from './AnimatedBackground'; + +describe('AnimatedBackground', () => { + it('renders decorative restrained cosmic layers', () => { + render(); + const background = screen.getByTestId('cosmic-background'); + expect(background).toHaveAttribute('aria-hidden', 'true'); + expect(background).toHaveClass('cosmic-shell'); + expect(background.querySelectorAll('[data-cosmic-layer]')).toHaveLength(3); + }); +}); diff --git a/components/AnimatedBackground.tsx b/components/AnimatedBackground.tsx index 1c824b4..7b38d39 100644 --- a/components/AnimatedBackground.tsx +++ b/components/AnimatedBackground.tsx @@ -1,2 +1,16 @@ import React from 'react'; -export const AnimatedBackground: React.FC = () =>