Files
unsupervised-scheduler/tests/Unit/GroupClass/GroupClassControllerTest.php
T
thatguygriffandClaude Opus 4.8 5f9d5ffc4f
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / Tests (PHP 8.1) (pull_request) Successful in 38s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m50s
CI / PHPStan (pull_request) Successful in 2m54s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
Add instructor group-class roster view under My Lessons
Instructors can now see their own group classes under My Lessons →
My Group Classes (view_own_lessons): each class shows its active
enrolment count against capacity plus a roster of enrolled students
with enrolment and payment status.

GroupClassController gains renderInstructorPage(), backed by the
existing per-instructor enrolment query and a newly injected
PaymentRepository for payment status. Wired as a submenu under the
existing My Lessons menu, inside the same !view_all_lessons guard so
owner-operators don't get a duplicate item.

Closes #71

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 12:52:27 -03:00

161 lines
6.3 KiB
PHP

<?php
declare(strict_types=1);
namespace Unsupervised\Schedular\Tests\Unit\GroupClass;
use Brain\Monkey\Functions;
use Mockery;
use Unsupervised\Schedular\GroupClass\Enrollment;
use Unsupervised\Schedular\GroupClass\EnrollmentRepository;
use Unsupervised\Schedular\GroupClass\GroupClassController;
use Unsupervised\Schedular\Offering\Offering;
use Unsupervised\Schedular\Offering\OfferingRepository;
use Unsupervised\Schedular\Payment\Payment;
use Unsupervised\Schedular\Payment\PaymentRepository;
use Unsupervised\Schedular\Tests\Unit\TestCase;
class GroupClassControllerTest extends TestCase
{
private EnrollmentRepository&Mockery\MockInterface $enrollments;
private OfferingRepository&Mockery\MockInterface $offerings;
private PaymentRepository&Mockery\MockInterface $payments;
private GroupClassController $controller;
protected function setUp(): void
{
parent::setUp();
$this->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();
}
}