CI / Tests (PHP 8.1) (pull_request) Successful in 1m29s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m40s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 3m1s
CI / Coding Standards (pull_request) Successful in 3m28s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m49s
CI / Build Plugin Zip (pull_request) Skipped
The Group Classes block can be pinned to a single class via its Class option so it can be embedded on a page dedicated to that class. On such a page the surrounding copy already describes the class, so the card repeated it. In single-class mode the description is now left out and the card shows only the schedule, instructor, schedule note, price, enrolment deadline and the enrol/withdraw controls. The editor preview follows the same rule: BlockPreview::groupClasses() takes the mode from the block's offeringId attribute, drops the sample description when a class is pinned, and notes what the published page shows. Its sample card also gained the .us-class-when and .us-enrol-deadline elements the live markup has always rendered. Co-Authored-By: Claude Opus 5 <[email protected]>
410 lines
14 KiB
PHP
410 lines
14 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Unsupervised\Schedular\Tests\Unit;
|
|
|
|
use Brain\Monkey\Actions;
|
|
use Brain\Monkey\Functions;
|
|
use Mockery;
|
|
use Unsupervised\Schedular\Auth\LoginPage;
|
|
use Unsupervised\Schedular\Auth\RegistrationPage;
|
|
use Unsupervised\Schedular\BlockRegistrar;
|
|
use Unsupervised\Schedular\Booking\BookingPage;
|
|
use Unsupervised\Schedular\GroupClass\GroupClassPage;
|
|
|
|
/**
|
|
* Test double exposing editor-preview mode as a switch (the real detection
|
|
* relies on the REST_REQUEST constant, which cannot be toggled within a
|
|
* single PHP process) and recording redirects instead of exiting.
|
|
*/
|
|
class TestableBlockRegistrar extends BlockRegistrar
|
|
{
|
|
public bool $preview = false;
|
|
|
|
/** @var list<string> */
|
|
public array $redirects = [];
|
|
|
|
protected function isEditorPreview(): bool
|
|
{
|
|
return $this->preview;
|
|
}
|
|
|
|
protected function redirect(string $url): void
|
|
{
|
|
$this->redirects[] = $url;
|
|
}
|
|
}
|
|
|
|
class BlockRegistrarTest extends TestCase
|
|
{
|
|
private BookingPage&Mockery\MockInterface $bookingPage;
|
|
private LoginPage&Mockery\MockInterface $loginPage;
|
|
private RegistrationPage&Mockery\MockInterface $registrationPage;
|
|
private GroupClassPage&Mockery\MockInterface $groupClassPage;
|
|
private TestableBlockRegistrar $registrar;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->bookingPage = Mockery::mock(BookingPage::class);
|
|
$this->loginPage = Mockery::mock(LoginPage::class);
|
|
$this->registrationPage = Mockery::mock(RegistrationPage::class);
|
|
$this->groupClassPage = Mockery::mock(GroupClassPage::class);
|
|
|
|
$this->registrar = new TestableBlockRegistrar(
|
|
$this->bookingPage,
|
|
$this->loginPage,
|
|
$this->registrationPage,
|
|
$this->groupClassPage,
|
|
);
|
|
}
|
|
|
|
public function testRegisterHooksBlockRegistrationAndAutoRedirect(): void
|
|
{
|
|
Actions\expectAdded('init')->once()->with([$this->registrar, 'registerBlocks']);
|
|
Actions\expectAdded('template_redirect')->once()->with([$this->registrar, 'maybeAutoRedirect']);
|
|
|
|
$this->registrar->register();
|
|
}
|
|
|
|
public function testRegisterBlocksRegistersAllFourBlocksWithAssets(): void
|
|
{
|
|
Functions\expect('wp_register_script')
|
|
->once()
|
|
->with(
|
|
BlockRegistrar::SCRIPT_HANDLE,
|
|
Mockery::pattern('~assets/js/blocks\.js$~'),
|
|
Mockery::type('array'),
|
|
USC_VERSION,
|
|
true
|
|
);
|
|
Functions\when('wp_style_is')->justReturn(false);
|
|
Functions\expect('wp_register_style')
|
|
->once()
|
|
->with(
|
|
BlockRegistrar::STYLE_HANDLE,
|
|
Mockery::pattern('~assets/css/frontend\.css$~'),
|
|
[],
|
|
USC_VERSION
|
|
);
|
|
|
|
$registered = [];
|
|
Functions\when('register_block_type')->alias(
|
|
static function (string $name, array $args) use (&$registered): bool {
|
|
$registered[$name] = $args;
|
|
return true;
|
|
}
|
|
);
|
|
|
|
$this->registrar->registerBlocks();
|
|
|
|
self::assertSame(
|
|
[
|
|
'us-scheduler/booking',
|
|
'us-scheduler/student-login',
|
|
'us-scheduler/student-register',
|
|
'us-scheduler/group-classes',
|
|
],
|
|
array_keys($registered)
|
|
);
|
|
|
|
foreach ($registered as $args) {
|
|
self::assertSame(BlockRegistrar::SCRIPT_HANDLE, $args['editor_script']);
|
|
self::assertSame(BlockRegistrar::STYLE_HANDLE, $args['style']);
|
|
self::assertIsCallable($args['render_callback']);
|
|
}
|
|
|
|
// The link-target and auto-redirect options must be declared
|
|
// server-side or the block-renderer preview rejects them.
|
|
self::assertSame(
|
|
['loginPageId', 'autoRedirect'],
|
|
array_keys($registered['us-scheduler/booking']['attributes'])
|
|
);
|
|
self::assertSame(
|
|
['bookingPageId', 'autoRedirect'],
|
|
array_keys($registered['us-scheduler/student-login']['attributes'])
|
|
);
|
|
self::assertSame(
|
|
['loginPageId', 'inviteOnlyMessage'],
|
|
array_keys($registered['us-scheduler/student-register']['attributes'])
|
|
);
|
|
self::assertSame(
|
|
['offeringId'],
|
|
array_keys($registered['us-scheduler/group-classes']['attributes'])
|
|
);
|
|
}
|
|
|
|
public function testRegisterBlocksDoesNotReRegisterAnAlreadyRegisteredStyle(): void
|
|
{
|
|
Functions\when('wp_register_script')->justReturn(true);
|
|
Functions\when('wp_style_is')->justReturn(true);
|
|
Functions\expect('wp_register_style')->never();
|
|
Functions\when('register_block_type')->justReturn(true);
|
|
|
|
$this->registrar->registerBlocks();
|
|
}
|
|
|
|
public function testFrontEndRenderDelegatesToThePageObjectsPassingAttributes(): void
|
|
{
|
|
$this->registrar->preview = false;
|
|
|
|
$this->bookingPage->shouldReceive('render')
|
|
->once()->with(['loginPageId' => 5])->andReturn('booking-html');
|
|
$this->loginPage->shouldReceive('render')
|
|
->once()->with(['bookingPageId' => 9])->andReturn('login-html');
|
|
$this->registrationPage->shouldReceive('render')->once()->with([])->andReturn('register-html');
|
|
$this->groupClassPage->shouldReceive('render')->once()->with([])->andReturn('group-html');
|
|
|
|
self::assertSame('booking-html', $this->registrar->renderBooking(['loginPageId' => 5]));
|
|
self::assertSame('login-html', $this->registrar->renderLogin(['bookingPageId' => 9]));
|
|
self::assertSame('register-html', $this->registrar->renderRegistration());
|
|
self::assertSame('group-html', $this->registrar->renderGroupClasses());
|
|
}
|
|
|
|
public function testEditorPreviewRendersStaticMarkupWithoutTouchingThePages(): void
|
|
{
|
|
$this->registrar->preview = true;
|
|
|
|
Functions\when('wp_nonce_field')->justReturn('');
|
|
|
|
$this->bookingPage->shouldNotReceive('render');
|
|
$this->loginPage->shouldNotReceive('render');
|
|
$this->registrationPage->shouldNotReceive('render');
|
|
$this->groupClassPage->shouldNotReceive('render');
|
|
|
|
self::assertStringContainsString('us-booking-app', $this->registrar->renderBooking());
|
|
self::assertStringContainsString('us-login-form', $this->registrar->renderLogin());
|
|
self::assertStringContainsString('us-register-form', $this->registrar->renderRegistration());
|
|
self::assertStringContainsString('us-group-app', $this->registrar->renderGroupClasses());
|
|
}
|
|
|
|
public function testEditorPreviewOfAPinnedGroupClassOmitsTheDescription(): void
|
|
{
|
|
$this->registrar->preview = true;
|
|
$this->groupClassPage->shouldNotReceive('render');
|
|
|
|
$all = $this->registrar->renderGroupClasses();
|
|
$single = $this->registrar->renderGroupClasses(['offeringId' => 12]);
|
|
|
|
self::assertStringContainsString('A sample class shown so the page can be styled.', $all);
|
|
self::assertStringNotContainsString('A sample class shown so the page can be styled.', $single);
|
|
self::assertStringContainsString('us-enrol-deadline', $single);
|
|
}
|
|
|
|
public function testIsEditorPreviewIsFalseOutsideRestRequests(): void
|
|
{
|
|
// REST_REQUEST is undefined in the test process, so the real
|
|
// registrar must take the front-end path.
|
|
$registrar = new BlockRegistrar(
|
|
$this->bookingPage,
|
|
$this->loginPage,
|
|
$this->registrationPage,
|
|
$this->groupClassPage,
|
|
);
|
|
|
|
$this->bookingPage->shouldReceive('render')->once()->with([])->andReturn('live');
|
|
|
|
self::assertSame('live', $registrar->renderBooking());
|
|
}
|
|
|
|
/**
|
|
* Stubs the front-end request context for maybeAutoRedirect: a singular
|
|
* page whose content parses to the given blocks.
|
|
*
|
|
* @param list<array<string, mixed>> $parsedBlocks
|
|
*/
|
|
private function stubSingularRequest(int $postId, array $parsedBlocks, bool $loggedIn): void
|
|
{
|
|
$post = new \WP_Post();
|
|
$post->ID = $postId;
|
|
$post->post_content = 'serialized-block-content';
|
|
|
|
Functions\when('is_admin')->justReturn(false);
|
|
Functions\when('is_singular')->justReturn(true);
|
|
Functions\when('get_post')->justReturn($post);
|
|
Functions\when('is_user_logged_in')->justReturn($loggedIn);
|
|
Functions\when('has_block')->alias(
|
|
static function (string $blockName) use ($parsedBlocks): bool {
|
|
foreach ($parsedBlocks as $block) {
|
|
if (($block['blockName'] ?? null) === $blockName) {
|
|
return true;
|
|
}
|
|
foreach ((array) ($block['innerBlocks'] ?? []) as $inner) {
|
|
if (is_array($inner) && ($inner['blockName'] ?? null) === $blockName) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
);
|
|
Functions\when('parse_blocks')->justReturn($parsedBlocks);
|
|
}
|
|
|
|
public function testAutoRedirectSendsLoggedOutVisitorToTheLoginPage(): void
|
|
{
|
|
$this->stubSingularRequest(
|
|
10,
|
|
[
|
|
[
|
|
'blockName' => 'us-scheduler/booking',
|
|
'attrs' => ['autoRedirect' => true, 'loginPageId' => 7],
|
|
'innerBlocks' => [],
|
|
],
|
|
],
|
|
false
|
|
);
|
|
|
|
$this->bookingPage->shouldReceive('loginUrl')->once()->with(7)->andReturn('https://example.com/login/');
|
|
|
|
$this->registrar->maybeAutoRedirect();
|
|
|
|
self::assertSame(['https://example.com/login/'], $this->registrar->redirects);
|
|
}
|
|
|
|
public function testAutoRedirectFindsTheBookingBlockNestedInsideAnotherBlock(): void
|
|
{
|
|
$this->stubSingularRequest(
|
|
10,
|
|
[
|
|
[
|
|
'blockName' => 'core/group',
|
|
'attrs' => [],
|
|
'innerBlocks' => [
|
|
[
|
|
'blockName' => 'us-scheduler/booking',
|
|
'attrs' => ['autoRedirect' => true],
|
|
'innerBlocks' => [],
|
|
],
|
|
],
|
|
],
|
|
],
|
|
false
|
|
);
|
|
|
|
// loginPageId omitted from the serialized block (defaults are not
|
|
// stored) — falls back to the WordPress login screen.
|
|
$this->bookingPage->shouldReceive('loginUrl')->once()->with(0)->andReturn('https://example.com/wp-login.php');
|
|
|
|
$this->registrar->maybeAutoRedirect();
|
|
|
|
self::assertSame(['https://example.com/wp-login.php'], $this->registrar->redirects);
|
|
}
|
|
|
|
public function testNoRedirectWhenTheBookingBlockDoesNotOptIn(): void
|
|
{
|
|
$this->stubSingularRequest(
|
|
10,
|
|
[
|
|
[
|
|
'blockName' => 'us-scheduler/booking',
|
|
'attrs' => ['loginPageId' => 7],
|
|
'innerBlocks' => [],
|
|
],
|
|
],
|
|
false
|
|
);
|
|
|
|
$this->bookingPage->shouldNotReceive('loginUrl');
|
|
|
|
$this->registrar->maybeAutoRedirect();
|
|
|
|
self::assertSame([], $this->registrar->redirects);
|
|
}
|
|
|
|
public function testNoRedirectWhenTheBookingBlockPointsAtItsOwnPage(): void
|
|
{
|
|
$this->stubSingularRequest(
|
|
7,
|
|
[
|
|
[
|
|
'blockName' => 'us-scheduler/booking',
|
|
'attrs' => ['autoRedirect' => true, 'loginPageId' => 7],
|
|
'innerBlocks' => [],
|
|
],
|
|
],
|
|
false
|
|
);
|
|
|
|
$this->bookingPage->shouldNotReceive('loginUrl');
|
|
|
|
$this->registrar->maybeAutoRedirect();
|
|
|
|
self::assertSame([], $this->registrar->redirects);
|
|
}
|
|
|
|
public function testAutoRedirectSendsLoggedInVisitorToTheBookingPage(): void
|
|
{
|
|
$this->stubSingularRequest(
|
|
20,
|
|
[
|
|
[
|
|
'blockName' => 'us-scheduler/student-login',
|
|
'attrs' => ['autoRedirect' => true, 'bookingPageId' => 9],
|
|
'innerBlocks' => [],
|
|
],
|
|
],
|
|
true
|
|
);
|
|
|
|
$this->loginPage->shouldReceive('bookingUrl')->once()->with(9)->andReturn('https://example.com/book/');
|
|
|
|
$this->registrar->maybeAutoRedirect();
|
|
|
|
self::assertSame(['https://example.com/book/'], $this->registrar->redirects);
|
|
}
|
|
|
|
public function testNoRedirectWhenTheLoginBlockHasNoBookingPageChosen(): void
|
|
{
|
|
$this->stubSingularRequest(
|
|
20,
|
|
[
|
|
[
|
|
'blockName' => 'us-scheduler/student-login',
|
|
'attrs' => ['autoRedirect' => true],
|
|
'innerBlocks' => [],
|
|
],
|
|
],
|
|
true
|
|
);
|
|
|
|
$this->loginPage->shouldReceive('bookingUrl')->once()->with(0)->andReturnNull();
|
|
|
|
$this->registrar->maybeAutoRedirect();
|
|
|
|
self::assertSame([], $this->registrar->redirects);
|
|
}
|
|
|
|
public function testNoRedirectForLoggedInVisitorOnTheBookingPage(): void
|
|
{
|
|
$this->stubSingularRequest(
|
|
10,
|
|
[
|
|
[
|
|
'blockName' => 'us-scheduler/booking',
|
|
'attrs' => ['autoRedirect' => true, 'loginPageId' => 7],
|
|
'innerBlocks' => [],
|
|
],
|
|
],
|
|
true
|
|
);
|
|
|
|
$this->bookingPage->shouldNotReceive('loginUrl');
|
|
|
|
$this->registrar->maybeAutoRedirect();
|
|
|
|
self::assertSame([], $this->registrar->redirects);
|
|
}
|
|
|
|
public function testNoRedirectOutsideSingularFrontEndRequests(): void
|
|
{
|
|
Functions\when('is_admin')->justReturn(false);
|
|
Functions\when('is_singular')->justReturn(false);
|
|
|
|
$this->registrar->maybeAutoRedirect();
|
|
|
|
self::assertSame([], $this->registrar->redirects);
|
|
}
|
|
}
|