/** * Course List Component Tests (Mobile) * Based on pallet-perwerde tests * * Tests cover: * - create_course_works * - enroll_works * - enroll_fails_for_archived_course * - enroll_fails_if_already_enrolled * - complete_course_works * - multiple_students_can_enroll_same_course */ import { render, fireEvent, waitFor } from '@testing-library/react-native'; import { generateMockCourse, generateMockCourseList, generateMockEnrollment, } from '../../../utils/mockDataGenerators'; import { buildPolkadotContextState } from '../../../utils/testHelpers'; // Mock the Course List component (adjust path as needed) // import { CourseList } from '@/src/components/perwerde/CourseList'; describe('Course List Component (Mobile)', () => { let mockApi: any; beforeEach(() => { mockApi = { query: { perwerde: { courses: jest.fn(), enrollments: jest.fn(), courseCount: jest.fn(() => ({ toNumber: () => 5, })), }, }, tx: { perwerde: { enrollInCourse: jest.fn(() => ({ signAndSend: jest.fn((account, callback) => { callback({ status: { isInBlock: true } }); return Promise.resolve('0x123'); }), })), completeCourse: jest.fn(() => ({ signAndSend: jest.fn((account, callback) => { callback({ status: { isInBlock: true } }); return Promise.resolve('0x123'); }), })), }, }, }; }); afterEach(() => { jest.clearAllMocks(); }); describe('Course Display', () => { test('should render list of active courses', () => { const courses = generateMockCourseList(5, 3); // 5 total, 3 active // Mock component rendering // const { getAllByTestId } = render(); // const courseCards = getAllByTestId('course-card'); // expect(courseCards).toHaveLength(5); const activeCourses = courses.filter(c => c.status === 'Active'); expect(activeCourses).toHaveLength(3); }); test('should display course status badges', () => { const activeCourse = generateMockCourse('Active'); const archivedCourse = generateMockCourse('Archived'); // Component should show: // - Active course: Green "Active" badge // - Archived course: Gray "Archived" badge }); test('should show course details (title, description, content URL)', () => { const course = generateMockCourse('Active', 0); expect(course).toHaveProperty('title'); expect(course).toHaveProperty('description'); expect(course).toHaveProperty('contentUrl'); // Component should display all these fields }); test('should filter courses by status', () => { const courses = generateMockCourseList(10, 6); const activeCourses = courses.filter(c => c.status === 'Active'); const archivedCourses = courses.filter(c => c.status === 'Archived'); expect(activeCourses).toHaveLength(6); expect(archivedCourses).toHaveLength(4); // Component should have filter toggle: // [All] [Active] [Archived] }); }); describe('Course Enrollment', () => { test('should show enroll button for active courses', () => { const activeCourse = generateMockCourse('Active'); // Component should show "Enroll" button // Button should be enabled }); test('should disable enroll button for archived courses', () => { // Test: enroll_fails_for_archived_course const archivedCourse = generateMockCourse('Archived'); // Component should show "Archived" or disabled "Enroll" button }); test('should show "Already Enrolled" state', () => { // Test: enroll_fails_if_already_enrolled const course = generateMockCourse('Active', 0); const enrollment = generateMockEnrollment(0, false); mockApi.query.perwerde.enrollments.mockResolvedValue({ unwrap: () => enrollment, }); // Component should show "Enrolled" badge or "Continue Learning" button // instead of "Enroll" button }); test('should successfully enroll in course', async () => { // Test: enroll_works const course = generateMockCourse('Active', 0); mockApi.query.perwerde.enrollments.mockResolvedValue({ unwrap: () => null, // Not enrolled yet }); const tx = mockApi.tx.perwerde.enrollInCourse(course.courseId); await expect(tx.signAndSend('address', jest.fn())).resolves.toBe('0x123'); expect(mockApi.tx.perwerde.enrollInCourse).toHaveBeenCalledWith(course.courseId); }); test('should support multiple students enrolling in same course', () => { // Test: multiple_students_can_enroll_same_course const course = generateMockCourse('Active', 0); const student1 = '5Student1...'; const student2 = '5Student2...'; const enrollment1 = generateMockEnrollment(0); const enrollment2 = generateMockEnrollment(0); expect(enrollment1.student).not.toBe(enrollment2.student); // Both can enroll independently }); test('should show enrolled courses count (max 100)', () => { // Test: enroll_fails_when_too_many_courses const maxCourses = 100; const currentEnrollments = 98; // Component should show: "Enrolled: 98/100 courses" // Warning when approaching limit: "You can enroll in 2 more courses" expect(currentEnrollments).toBeLessThan(maxCourses); }); }); describe('Course Completion', () => { test('should show completion button for enrolled students', () => { const enrollment = generateMockEnrollment(0, false); // Component should show: // - "Complete Course" button // - Progress indicator }); test('should successfully complete course with points', async () => { // Test: complete_course_works const course = generateMockCourse('Active', 0); const pointsEarned = 85; const tx = mockApi.tx.perwerde.completeCourse(course.courseId, pointsEarned); await expect(tx.signAndSend('address', jest.fn())).resolves.toBe('0x123'); expect(mockApi.tx.perwerde.completeCourse).toHaveBeenCalledWith( course.courseId, pointsEarned ); }); test('should show completed course with certificate', () => { const completedEnrollment = generateMockEnrollment(0, true); // Component should display: // - "Completed" badge (green) // - Points earned: "85 points" // - "View Certificate" button // - Completion date }); test('should prevent completing course twice', () => { // Test: complete_course_fails_if_already_completed const completedEnrollment = generateMockEnrollment(0, true); mockApi.query.perwerde.enrollments.mockResolvedValue({ unwrap: () => completedEnrollment, }); // "Complete Course" button should be hidden or disabled }); test('should prevent completing without enrollment', () => { // Test: complete_course_fails_without_enrollment mockApi.query.perwerde.enrollments.mockResolvedValue({ unwrap: () => null, }); // "Complete Course" button should not appear // Only "Enroll" button should be visible }); }); describe('Course Categories', () => { test('should categorize courses (Blockchain, Programming, Kurdistan Culture)', () => { // Courses can have categories const categories = ['Blockchain', 'Programming', 'Kurdistan Culture', 'History']; // Component should show category filter pills }); test('should filter courses by category', () => { const courses = generateMockCourseList(10, 8); // Mock categories courses.forEach((course, index) => { (course as any).category = ['Blockchain', 'Programming', 'Culture'][index % 3]; }); const blockchainCourses = courses.filter((c: any) => c.category === 'Blockchain'); // Component should filter when category pill is tapped }); }); describe('Course Progress', () => { test('should show enrollment progress (enrolled but not completed)', () => { const enrollment = generateMockEnrollment(0, false); // Component should show: // - "In Progress" badge // - Start date // - "Continue Learning" button }); test('should track completion percentage if available', () => { // Future feature: track lesson completion percentage const progressPercentage = 67; // 67% complete // Component should show progress bar: 67% }); }); describe('Admin Features', () => { test('should show create course button for admins', () => { // Test: create_course_works const isAdmin = true; if (isAdmin) { // Component should show "+ Create Course" FAB } }); test('should show archive course button for course owners', () => { // Test: archive_course_works const course = generateMockCourse('Active', 0); const isOwner = true; if (isOwner) { // Component should show "Archive" button in course menu } }); test('should prevent non-admins from creating courses', () => { // Test: create_course_fails_for_non_admin const isAdmin = false; if (!isAdmin) { // "Create Course" button should not be visible } }); }); describe('Pull to Refresh', () => { test('should refresh course list on pull down', async () => { const initialCourses = generateMockCourseList(5, 3); // Simulate pull-to-refresh // const { getByTestId } = render(); // fireEvent(getByTestId('course-list'), 'refresh'); // await waitFor(() => { // expect(mockApi.query.perwerde.courses).toHaveBeenCalledTimes(2); // }); }); }); describe('Empty States', () => { test('should show empty state when no courses exist', () => { const emptyCourses: any[] = []; // Component should display: // - Icon (📚) // - Message: "No courses available yet" // - Subtext: "Check back later for new courses" }); test('should show empty state when no active courses', () => { const courses = generateMockCourseList(5, 0); // All archived const activeCourses = courses.filter(c => c.status === 'Active'); expect(activeCourses).toHaveLength(0); // Component should display: // - Message: "No active courses" // - Button: "Show Archived Courses" }); }); }); /** * TEST DATA FIXTURES */ export const educationTestFixtures = { activeCourse: generateMockCourse('Active', 0), archivedCourse: generateMockCourse('Archived', 1), courseList: generateMockCourseList(10, 7), pendingEnrollment: generateMockEnrollment(0, false), completedEnrollment: generateMockEnrollment(0, true), categories: ['Blockchain', 'Programming', 'Kurdistan Culture', 'History', 'Languages'], };