enrollments = Mockery::mock(EnrollmentRepository::class); $this->offerings = Mockery::mock(OfferingRepository::class); $this->payments = Mockery::mock(PaymentRepository::class); $this->controller = new GroupClassController($this->enrollments, $this->offerings, $this->payments); Functions\when('current_user_can')->justReturn(true); Functions\when('get_current_user_id')->justReturn(3); } private function offering(int $id, string $title, ?int $capacity): Offering { return new Offering( instructorId: 3, kind: Offering::KIND_GROUP_CLASS, title: $title, capacity: $capacity, id: $id, ); } private function renderInstructor(): string { ob_start(); $this->controller->renderInstructorPage(); return (string) ob_get_clean(); } public function testInstructorPageListsClassWithCapacityAndRoster(): void { Functions\when('get_userdata')->justReturn((object) ['display_name' => 'Ada Lovelace']); $offering = $this->offering(8, 'Choir', 10); $enrollment = new Enrollment(offeringId: 8, studentId: 5, instructorId: 3, paymentId: 42, id: 1); $this->offerings->shouldReceive('findAll')->once() ->with(3, Offering::KIND_GROUP_CLASS)->andReturn([$offering]); $this->enrollments->shouldReceive('findByInstructor')->once()->with(3)->andReturn([$enrollment]); $this->payments->shouldReceive('findById')->once()->with(42)->andReturn( new Payment(studentId: 5, instructorId: 3, registrationType: Payment::REG_ENROLLMENT, registrationId: 1, amount: 100.0, status: Payment::STATUS_PAID, id: 42) ); $html = $this->renderInstructor(); self::assertStringContainsString('Choir', $html); self::assertStringContainsString('(1 / 10 enrolled)', $html); self::assertStringContainsString('Ada Lovelace', $html); self::assertStringContainsString('paid', $html); } public function testEnrolmentCountExcludesCancelledButRosterKeepsThem(): void { Functions\when('get_userdata')->justReturn((object) ['display_name' => 'Grace Hopper']); $offering = $this->offering(8, 'Band', null); $active = new Enrollment(offeringId: 8, studentId: 5, instructorId: 3, id: 1); $cancelled = new Enrollment(offeringId: 8, studentId: 6, instructorId: 3, status: Enrollment::STATUS_CANCELLED, id: 2); $this->offerings->shouldReceive('findAll')->once()->andReturn([$offering]); $this->enrollments->shouldReceive('findByInstructor')->once()->andReturn([$active, $cancelled]); $html = $this->renderInstructor(); // Unlimited capacity offering counts only the active enrolment. self::assertStringContainsString('(1 enrolled)', $html); // But the roster still shows the cancelled row. self::assertStringContainsString('cancelled', $html); } public function testFreeEnrolmentShowsDashForPayment(): void { Functions\when('get_userdata')->justReturn((object) ['display_name' => 'Alan Turing']); $offering = $this->offering(8, 'Theory', 5); $enrollment = new Enrollment(offeringId: 8, studentId: 5, instructorId: 3, paymentId: null, id: 1); $this->offerings->shouldReceive('findAll')->once()->andReturn([$offering]); $this->enrollments->shouldReceive('findByInstructor')->once()->andReturn([$enrollment]); $this->payments->shouldReceive('findById')->never(); $html = $this->renderInstructor(); self::assertStringContainsString('—', $html); } public function testEnrolmentsForOtherClassesAreNotMixedIn(): void { Functions\when('get_userdata')->justReturn((object) ['display_name' => 'Katherine Johnson']); $offering = $this->offering(8, 'Choir', 5); $mine = new Enrollment(offeringId: 8, studentId: 5, instructorId: 3, id: 1); $other = new Enrollment(offeringId: 9, studentId: 6, instructorId: 3, id: 2); $this->offerings->shouldReceive('findAll')->once()->andReturn([$offering]); $this->enrollments->shouldReceive('findByInstructor')->once()->andReturn([$mine, $other]); $html = $this->renderInstructor(); self::assertStringContainsString('(1 / 5 enrolled)', $html); } public function testClassWithNoEnrolmentsShowsEmptyMessage(): void { $offering = $this->offering(8, 'Jazz', 5); $this->offerings->shouldReceive('findAll')->once()->andReturn([$offering]); $this->enrollments->shouldReceive('findByInstructor')->once()->andReturn([]); $html = $this->renderInstructor(); self::assertStringContainsString('No enrolments yet.', $html); } public function testInstructorWithNoClassesShowsEmptyMessage(): void { $this->offerings->shouldReceive('findAll')->once()->andReturn([]); $this->enrollments->shouldReceive('findByInstructor')->once()->andReturn([]); $html = $this->renderInstructor(); self::assertStringContainsString('You have no group classes.', $html); } public function testDeniesUsersWithoutViewLessonsCapability(): void { Functions\when('current_user_can')->justReturn(false); Functions\expect('wp_die')->once()->andThrow(new \RuntimeException('denied')); $this->expectException(\RuntimeException::class); $this->controller->renderInstructorPage(); } }