Add instructor group-class roster view under My Lessons
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
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
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]>
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user