diff --git a/src/pages/SpeakingSession.tsx b/src/pages/SpeakingSession.tsx index c5d3fae0..aba973de 100644 --- a/src/pages/SpeakingSession.tsx +++ b/src/pages/SpeakingSession.tsx @@ -6,7 +6,7 @@ import type { EventData } from 'react-joyride'; import { getSpeakingTourSteps } from '../data/TutorialSteps'; import { ArrowLeft, Play, Pause, Square, Save, Mic, X, Trash2 } from 'lucide-react'; import Topbar from '../components/Layout/Topbar'; -import { useAssessment, useAuthoring, useSettings, useStudents } from '../context/AppContext'; +import { useStoreActions, useStoreSelector } from '../context/useStore'; import { calcEntryPoints, calcGradeSummary, criterionMaxPoints } from '../utils/gradeCalc'; import { nanoid } from '../utils/nanoid'; import RecordingControls from '../components/Recordings/RecordingControls'; @@ -32,11 +32,23 @@ export default function SpeakingSession() { const { rubricId, studentId } = useParams<{ rubricId: string; studentId: string }>(); const navigate = useNavigate(); const { t } = useTranslation(); - const { students } = useStudents(); - - const { rubrics, gradeScales } = useAuthoring(); - const { speakingSessions, saveSpeakingSession } = useAssessment(); - const { settings } = useSettings(); + const { + students: allStudents, + rubrics, + gradeScales, + speakingSessions, + settings, + } = useStoreSelector((s) => ({ + students: s.students, + rubrics: s.rubrics, + gradeScales: s.gradeScales, + speakingSessions: s.speakingSessions, + settings: s.settings, + })); + // The roster domain hooks filtered soft-deleted rows; archived students must not + // be reachable from a speaking-session route. + const students = React.useMemo(() => allStudents.filter((s) => !s.archivedAt), [allStudents]); + const { saveSpeakingSession } = useStoreActions(); const rubric = rubrics.find((r) => r.id === rubricId); const student = students.find((s) => s.id === studentId); diff --git a/src/pages/__tests__/SpeakingSession.extended.test.tsx b/src/pages/__tests__/SpeakingSession.extended.test.tsx index 08d0e3fd..70bd3458 100644 --- a/src/pages/__tests__/SpeakingSession.extended.test.tsx +++ b/src/pages/__tests__/SpeakingSession.extended.test.tsx @@ -3,6 +3,7 @@ import { render, screen, fireEvent } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { createMemoryRouter, RouterProvider } from 'react-router-dom'; import { DEFAULT_FORMAT } from '../../types'; +import type { StoreData } from '../../store/storage'; import type { AppSettings, Class, @@ -77,14 +78,13 @@ const mockClassesArr = [mockClass]; const mockGradeScalesArr = [mockGradeScale]; const emptyArr: never[] = []; -const mockAppValue: Record = { +const mockAppValue: Partial = { rubrics: mockRubricsArr, students: mockStudentsArr, classes: mockClassesArr, gradeScales: mockGradeScalesArr, settings: mockSettings, speakingSessions: emptyArr, - saveSpeakingSession: mockSaveSpeakingSession, studentRubrics: emptyArr, }; @@ -102,6 +102,12 @@ vi.mock('../../context/AppContext', () => ({ usePlatform: () => mockAppValue, })); +vi.mock('../../context/useStore', () => ({ + useStoreSelector: (selector: (state: StoreData) => T): T => selector(mockAppValue as StoreData), + // SpeakingSession only triggers saveSpeakingSession; keep the action mock narrow. + useStoreActions: () => ({ saveSpeakingSession: mockSaveSpeakingSession }), +})); + vi.mock('react-router-dom', async () => { const actual = await vi.importActual('react-router-dom'); return { ...actual, useNavigate: () => mockNavigate }; diff --git a/src/pages/__tests__/SpeakingSession.test.tsx b/src/pages/__tests__/SpeakingSession.test.tsx index 4dd9c127..1709eeb5 100644 --- a/src/pages/__tests__/SpeakingSession.test.tsx +++ b/src/pages/__tests__/SpeakingSession.test.tsx @@ -4,6 +4,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { createMemoryRouter, RouterProvider } from 'react-router-dom'; import { DEFAULT_FORMAT } from '../../types'; import type { AppSettings, Class, GradeScale, Rubric, Student } from '../../types'; +import type { StoreData } from '../../store/storage'; const mockGradeScale: GradeScale = { id: 'gs1', @@ -59,14 +60,13 @@ const mockClassesArr = [mockClass]; const mockGradeScalesArr = [mockGradeScale]; const emptyArr: never[] = []; -const mockAppValue = { +const mockAppValue: Partial = { rubrics: mockRubricsArr, students: mockStudentsArr, classes: mockClassesArr, gradeScales: mockGradeScalesArr, settings: mockSettings, speakingSessions: emptyArr, - saveSpeakingSession: mockSaveSpeakingSession, studentRubrics: emptyArr, }; @@ -83,6 +83,12 @@ vi.mock('../../context/AppContext', () => ({ usePlatform: () => mockAppValue, })); +vi.mock('../../context/useStore', () => ({ + useStoreSelector: (selector: (state: StoreData) => T): T => selector(mockAppValue as StoreData), + // SpeakingSession only triggers saveSpeakingSession; keep the action mock narrow. + useStoreActions: () => ({ saveSpeakingSession: mockSaveSpeakingSession }), +})); + vi.mock('react-router-dom', async () => { const actual = await vi.importActual('react-router-dom'); return { ...actual, useNavigate: () => mockNavigate };