diff --git a/src/pages/StudentLearningPathPage.tsx b/src/pages/StudentLearningPathPage.tsx index 397364c9..fbd92f52 100644 --- a/src/pages/StudentLearningPathPage.tsx +++ b/src/pages/StudentLearningPathPage.tsx @@ -6,15 +6,7 @@ import Topbar from '../components/Layout/Topbar'; import Avatar from '../components/ui/Avatar'; import CefrBadge from '../components/CEFR/CefrBadge'; import CefrPlacementCard from '../components/CEFR/CefrPlacementCard'; -import { - useAssessment, - useAuthoring, - useClasses, - useFlashcards, - useGrading, - useSettings, - useStudents, -} from '../context/AppContext'; +import { useStoreSelector } from '../context/useStore'; import { getCefrStudentOverview } from '../utils/cefrStudentAggregator'; import { getLearningPathRecommendations, @@ -30,14 +22,32 @@ import type { InterventionFlag } from '../types'; export default function StudentLearningPathPage() { const { id } = useParams(); const navigate = useNavigate(); - const { students } = useStudents(); - const { classes } = useClasses(); - const { studentRubrics } = useGrading(); - - const { rubrics } = useAuthoring(); - const { selfAssessments, analysisResults, tests, studentTests } = useAssessment(); - const { flashcardDecks } = useFlashcards(); - const { settings } = useSettings(); + const { + students: allStudents, + classes, + studentRubrics: allStudentRubrics, + rubrics, + selfAssessments, + analysisResults, + tests, + studentTests, + flashcardDecks, + settings, + } = useStoreSelector((s) => ({ + students: s.students, + classes: s.classes, + studentRubrics: s.studentRubrics, + rubrics: s.rubrics, + selfAssessments: s.selfAssessments, + analysisResults: s.analysisResults, + tests: s.tests, + studentTests: s.studentTests, + flashcardDecks: s.flashcardDecks, + settings: s.settings, + })); + // The roster domain hooks filtered soft-deleted rows; keep that behavior here. + const students = useMemo(() => allStudents.filter((s) => !s.archivedAt), [allStudents]); + const studentRubrics = useMemo(() => allStudentRubrics.filter((sr) => !sr.deletedAt), [allStudentRubrics]); const { t, i18n } = useTranslation(); const lang = i18n.language.startsWith('nl') ? 'nl' : 'en'; diff --git a/src/pages/__tests__/StudentLearningPathPage.test.tsx b/src/pages/__tests__/StudentLearningPathPage.test.tsx index f62b4ae7..262511c0 100644 --- a/src/pages/__tests__/StudentLearningPathPage.test.tsx +++ b/src/pages/__tests__/StudentLearningPathPage.test.tsx @@ -49,6 +49,11 @@ vi.mock('../../context/AppContext', () => ({ usePlatform: () => makeAppContextMock(), })); +vi.mock('../../context/useStore', () => ({ + useStoreSelector: (selector: (state: any) => any) => selector(makeAppContextMock()), + useStoreActions: () => makeAppContextMock(), +})); + vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string, opts?: Record) => (opts ? `${key}:${JSON.stringify(opts)}` : key), diff --git a/src/pages/__tests__/pages.student.a11y.test.tsx b/src/pages/__tests__/pages.student.a11y.test.tsx index 4a10219a..e99ac907 100644 --- a/src/pages/__tests__/pages.student.a11y.test.tsx +++ b/src/pages/__tests__/pages.student.a11y.test.tsx @@ -50,166 +50,170 @@ const mockRubric: Rubric = { // page whose effect depends on a context array re-run that effect every render — // e.g. PeerReviewView setEntry()s a new object keyed on `peerReviews`, which loops // forever and OOMs the worker. In the real app these references are stable. -vi.mock('../../context/AppContext', () => { - const base = { - rubrics: [mockRubric], - students: [mockStudent], - classes: [mockClass], - studentRubrics: [] as StudentRubric[], - selfAssessments: [], - speakingSessions: [], - gradeScales: [{ id: 'gs1', name: 'Default', ranges: [] }], - settings: mockSettings, - updateSettings: vi.fn(), - enterLocalMode: vi.fn(), - connectForOAuth: vi.fn(() => Promise.resolve(true)), - showMigrationPrompt: false, - dismissMigrationPrompt: vi.fn(), - getActiveGradeScale: vi.fn(), - // Phase 3/4 collections - tests: [], - studentTests: [], - questionBank: [], - exportTemplates: [], - flashcardDecks: [], - flashcardAssignments: [], - flashcardReviews: [], - essayAssignments: [], - essaySubmissions: [], - peerReviews: [], - analysisResults: [], - attachments: [], - essayTemplates: [], - messages: [], - newsFlashes: [], - newsFlashReads: [], - commentBank: [], - userTemplates: [], - gradingTasks: [], - standardMasteryTargets: [], - notificationDismissals: [], - dismissNotification: vi.fn(), - markMessageReadByTeacher: vi.fn(), - // Phase 3/4 actions - updateClass: vi.fn(), - addEssayAssignments: vi.fn(), - // RubricBuilder actions - addRubric: vi.fn(), - updateRubric: vi.fn(), - syncRubricSnapshot: vi.fn(), - fetchRubricVersions: vi.fn(() => Promise.resolve([])), - saveRubricVersion: vi.fn(), - restoreRubricVersion: vi.fn(), - addVocabularyItem: vi.fn(), - updateVocabularyItem: vi.fn(), - deleteVocabularyItem: vi.fn(), - deleteVocabularyItems: vi.fn(), - // GradeStudent actions - saveStudentRubric: vi.fn(), - saveAnalysisResult: vi.fn(), - addCommentBankItem: vi.fn(), - addAttachment: vi.fn(), - saveEssayAssignment: vi.fn(), - saveEssayTemplate: vi.fn(), - fetchEssaySubmissionsForStudent: vi.fn(() => Promise.resolve([])), - deleteEssaySubmission: vi.fn(), - getEssaySignedUrl: vi.fn(() => Promise.resolve(null)), - // Tests / Question Bank actions - addTest: vi.fn(), - updateTest: vi.fn(), - deleteTest: vi.fn(), - saveStudentTest: vi.fn(), - addSectionBankItem: vi.fn(), - addQuestionBankItems: vi.fn(), - updateQuestionBankItem: vi.fn(), - deleteQuestionBankItem: vi.fn(), - deleteQuestionBankItems: vi.fn(), - bulkUpdateQuestionBankItems: vi.fn(), - // Flashcard actions - addFlashcardDeck: vi.fn(), - updateFlashcardDeck: vi.fn(), - deleteFlashcardDeck: vi.fn(), - addFlashcardAssignments: vi.fn(), - // Essay actions - deleteEssayGroup: vi.fn(), - updateEssayGroup: vi.fn(), - addEssaySubmission: vi.fn(), - // News-flash actions - addNewsFlash: vi.fn(), - updateNewsFlash: vi.fn(), - deleteNewsFlash: vi.fn(), - // Comment-bank actions - updateCommentBankItem: vi.fn(), - deleteCommentBankItem: vi.fn(), - // Speaking / live-monitor actions - saveSpeakingSession: vi.fn(), - fetchTestAssignmentTeacherKeys: vi.fn(() => Promise.resolve([])), - setPlacementOverride: vi.fn(), - fetchEssayAssignmentByKey: vi.fn(() => Promise.resolve(null)), - // Dashboard / activity / rubric-list / students / attachments actions - deleteUserTemplate: vi.fn(), - sendMessage: vi.fn(), - notifyStudentMessage: vi.fn(), - addGradingTasks: vi.fn(), - deleteGradingTask: vi.fn(), - deleteRubric: vi.fn(), - createGroupStudentRubrics: vi.fn(), - addStudent: vi.fn(), - updateStudent: vi.fn(), - deleteStudent: vi.fn(), - addClass: vi.fn(), - deleteClass: vi.fn(), - mergeClasses: vi.fn(), - setStudentPassword: vi.fn(), - deleteAttachment: vi.fn(), - // Admin / settings / moderation / onboarding actions - fetchAllUsers: vi.fn(() => Promise.resolve([])), - updateUserRole: vi.fn(() => Promise.resolve()), - getCurrentDatabaseUserId: vi.fn(() => null), - addGradeScale: vi.fn(), - updateGradeScale: vi.fn(), - deleteGradeScale: vi.fn(), - addExportTemplate: vi.fn(), - deleteExportTemplate: vi.fn(), - importBackup: vi.fn(), - deleteStandardMasteryTarget: vi.fn(), - deletePeerReview: vi.fn(), - fetchSchoolMembers: vi.fn(() => Promise.resolve([])), - createSchool: vi.fn(() => Promise.resolve()), - joinSchool: vi.fn(() => Promise.resolve()), - signOutFromDatabase: vi.fn(() => Promise.resolve()), - // Student-portal actions - saveRubricSelfAssessment: vi.fn(), - saveSelfAssessment: vi.fn(), - savePeerReview: vi.fn(), - saveFlashcardReview: vi.fn(), - saveFlashcardReviewAsStudent: vi.fn(), - sendMessageAsStudent: vi.fn(), - markMessagesReadByStudent: vi.fn(), - markNewsFlashRead: vi.fn(), - markNewsFlashReadAsStudent: vi.fn(), - fetchMyEssayAssignments: vi.fn(() => Promise.resolve([])), - fetchMyTestAssignments: vi.fn(() => Promise.resolve([])), - fetchAssignedTestContent: vi.fn(() => Promise.resolve(null)), - fetchMyMessages: vi.fn(() => Promise.resolve([])), - fetchMyFlashcardAssignments: vi.fn(() => Promise.resolve([])), - fetchMyNewsFlashes: vi.fn(() => Promise.resolve([])), - fetchAssignedFlashcardDeck: vi.fn(() => Promise.resolve(null)), - fetchMyFlashcardReview: vi.fn(() => Promise.resolve(null)), - }; - return { - useRoster: () => base, - useStudents: () => base, - useClasses: () => base, - useGrading: () => base, - useAuthoring: () => base, - useAssessment: () => base, - useEssays: () => base, - useFlashcards: () => base, - useSettings: () => base, - usePlatform: () => base, - }; -}); +const base = { + rubrics: [mockRubric], + students: [mockStudent], + classes: [mockClass], + studentRubrics: [] as StudentRubric[], + selfAssessments: [], + speakingSessions: [], + gradeScales: [{ id: 'gs1', name: 'Default', ranges: [] }], + settings: mockSettings, + updateSettings: vi.fn(), + enterLocalMode: vi.fn(), + connectForOAuth: vi.fn(() => Promise.resolve(true)), + showMigrationPrompt: false, + dismissMigrationPrompt: vi.fn(), + getActiveGradeScale: vi.fn(), + // Phase 3/4 collections + tests: [], + studentTests: [], + questionBank: [], + exportTemplates: [], + flashcardDecks: [], + flashcardAssignments: [], + flashcardReviews: [], + essayAssignments: [], + essaySubmissions: [], + peerReviews: [], + analysisResults: [], + attachments: [], + essayTemplates: [], + messages: [], + newsFlashes: [], + newsFlashReads: [], + commentBank: [], + userTemplates: [], + gradingTasks: [], + standardMasteryTargets: [], + notificationDismissals: [], + dismissNotification: vi.fn(), + markMessageReadByTeacher: vi.fn(), + // Phase 3/4 actions + updateClass: vi.fn(), + addEssayAssignments: vi.fn(), + // RubricBuilder actions + addRubric: vi.fn(), + updateRubric: vi.fn(), + syncRubricSnapshot: vi.fn(), + fetchRubricVersions: vi.fn(() => Promise.resolve([])), + saveRubricVersion: vi.fn(), + restoreRubricVersion: vi.fn(), + addVocabularyItem: vi.fn(), + updateVocabularyItem: vi.fn(), + deleteVocabularyItem: vi.fn(), + deleteVocabularyItems: vi.fn(), + // GradeStudent actions + saveStudentRubric: vi.fn(), + saveAnalysisResult: vi.fn(), + addCommentBankItem: vi.fn(), + addAttachment: vi.fn(), + saveEssayAssignment: vi.fn(), + saveEssayTemplate: vi.fn(), + fetchEssaySubmissionsForStudent: vi.fn(() => Promise.resolve([])), + deleteEssaySubmission: vi.fn(), + getEssaySignedUrl: vi.fn(() => Promise.resolve(null)), + // Tests / Question Bank actions + addTest: vi.fn(), + updateTest: vi.fn(), + deleteTest: vi.fn(), + saveStudentTest: vi.fn(), + addSectionBankItem: vi.fn(), + addQuestionBankItems: vi.fn(), + updateQuestionBankItem: vi.fn(), + deleteQuestionBankItem: vi.fn(), + deleteQuestionBankItems: vi.fn(), + bulkUpdateQuestionBankItems: vi.fn(), + // Flashcard actions + addFlashcardDeck: vi.fn(), + updateFlashcardDeck: vi.fn(), + deleteFlashcardDeck: vi.fn(), + addFlashcardAssignments: vi.fn(), + // Essay actions + deleteEssayGroup: vi.fn(), + updateEssayGroup: vi.fn(), + addEssaySubmission: vi.fn(), + // News-flash actions + addNewsFlash: vi.fn(), + updateNewsFlash: vi.fn(), + deleteNewsFlash: vi.fn(), + // Comment-bank actions + updateCommentBankItem: vi.fn(), + deleteCommentBankItem: vi.fn(), + // Speaking / live-monitor actions + saveSpeakingSession: vi.fn(), + fetchTestAssignmentTeacherKeys: vi.fn(() => Promise.resolve([])), + setPlacementOverride: vi.fn(), + fetchEssayAssignmentByKey: vi.fn(() => Promise.resolve(null)), + // Dashboard / activity / rubric-list / students / attachments actions + deleteUserTemplate: vi.fn(), + sendMessage: vi.fn(), + notifyStudentMessage: vi.fn(), + addGradingTasks: vi.fn(), + deleteGradingTask: vi.fn(), + deleteRubric: vi.fn(), + createGroupStudentRubrics: vi.fn(), + addStudent: vi.fn(), + updateStudent: vi.fn(), + deleteStudent: vi.fn(), + addClass: vi.fn(), + deleteClass: vi.fn(), + mergeClasses: vi.fn(), + setStudentPassword: vi.fn(), + deleteAttachment: vi.fn(), + // Admin / settings / moderation / onboarding actions + fetchAllUsers: vi.fn(() => Promise.resolve([])), + updateUserRole: vi.fn(() => Promise.resolve()), + getCurrentDatabaseUserId: vi.fn(() => null), + addGradeScale: vi.fn(), + updateGradeScale: vi.fn(), + deleteGradeScale: vi.fn(), + addExportTemplate: vi.fn(), + deleteExportTemplate: vi.fn(), + importBackup: vi.fn(), + deleteStandardMasteryTarget: vi.fn(), + deletePeerReview: vi.fn(), + fetchSchoolMembers: vi.fn(() => Promise.resolve([])), + createSchool: vi.fn(() => Promise.resolve()), + joinSchool: vi.fn(() => Promise.resolve()), + signOutFromDatabase: vi.fn(() => Promise.resolve()), + // Student-portal actions + saveRubricSelfAssessment: vi.fn(), + saveSelfAssessment: vi.fn(), + savePeerReview: vi.fn(), + saveFlashcardReview: vi.fn(), + saveFlashcardReviewAsStudent: vi.fn(), + sendMessageAsStudent: vi.fn(), + markMessagesReadByStudent: vi.fn(), + markNewsFlashRead: vi.fn(), + markNewsFlashReadAsStudent: vi.fn(), + fetchMyEssayAssignments: vi.fn(() => Promise.resolve([])), + fetchMyTestAssignments: vi.fn(() => Promise.resolve([])), + fetchAssignedTestContent: vi.fn(() => Promise.resolve(null)), + fetchMyMessages: vi.fn(() => Promise.resolve([])), + fetchMyFlashcardAssignments: vi.fn(() => Promise.resolve([])), + fetchMyNewsFlashes: vi.fn(() => Promise.resolve([])), + fetchAssignedFlashcardDeck: vi.fn(() => Promise.resolve(null)), + fetchMyFlashcardReview: vi.fn(() => Promise.resolve(null)), +}; + +vi.mock('../../context/AppContext', () => ({ + useRoster: () => base, + useStudents: () => base, + useClasses: () => base, + useGrading: () => base, + useAuthoring: () => base, + useAssessment: () => base, + useEssays: () => base, + useFlashcards: () => base, + useSettings: () => base, + usePlatform: () => base, +})); + +vi.mock('../../context/useStore', () => ({ + useStoreSelector: (selector: (state: any) => any) => selector(base), + useStoreActions: () => base, +})); vi.mock('../../services/database', () => ({ loadSupabaseConfig: vi.fn(() => null),