Add link-target and auto-redirect options to booking/login blocks
CI / Coding Standards (pull_request) Successful in 51s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.2) (pull_request) Successful in 50s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m2s
CI / Build Plugin Zip (pull_request) Has been skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 53s
CI / PHPStan (pull_request) Successful in 1m24s
CI / Coding Standards (pull_request) Successful in 51s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.2) (pull_request) Successful in 50s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m2s
CI / Build Plugin Zip (pull_request) Has been skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 53s
CI / PHPStan (pull_request) Successful in 1m24s
The booking block gains a loginPageId attribute choosing which page its logged-out "log in to book a lesson" link points to (default remains the WordPress login screen), and the student-login block gains a bookingPageId attribute controlling the logged-in "View available lessons" link and the post-login redirect target (default remains the current page). Both blocks also gain an autoRedirect toggle, off by default, that sends the visitor straight to the target page; block rendering starts after output, so the redirect runs on template_redirect by parsing the queried page's content for the block, with a self-target guard against redirect loops. The link targets are also available to the shortcodes as login_page_id/booking_page_id. Also fixes a pre-existing fatal: WordPress passes an empty string (not an array) to shortcode callbacks when a shortcode is used without attributes, so bare [us_booking] etc. threw a TypeError against the strictly-typed render(array $atts) methods. ShortcodeRegistrar now wraps each callback to normalize non-array attribute values. Closes #51 Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -13,18 +13,26 @@ use Unsupervised\Schedular\Booking\BookingPage;
|
||||
use Unsupervised\Schedular\GroupClass\GroupClassPage;
|
||||
|
||||
/**
|
||||
* Test double exposing editor-preview mode as a switch, since the real
|
||||
* detection relies on the REST_REQUEST constant which cannot be toggled
|
||||
* within a single PHP process.
|
||||
* 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
|
||||
@@ -52,9 +60,10 @@ class BlockRegistrarTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testRegisterHooksBlockRegistrationOntoInit(): void
|
||||
public function testRegisterHooksBlockRegistrationAndAutoRedirect(): void
|
||||
{
|
||||
Actions\expectAdded('init')->once()->with([$this->registrar, 'registerBlocks']);
|
||||
Actions\expectAdded('template_redirect')->once()->with([$this->registrar, 'maybeAutoRedirect']);
|
||||
|
||||
$this->registrar->register();
|
||||
}
|
||||
@@ -105,6 +114,19 @@ class BlockRegistrarTest extends TestCase
|
||||
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([], $registered['us-scheduler/student-register']['attributes']);
|
||||
self::assertSame([], $registered['us-scheduler/group-classes']['attributes']);
|
||||
}
|
||||
|
||||
public function testRegisterBlocksDoesNotReRegisterAnAlreadyRegisteredStyle(): void
|
||||
@@ -117,17 +139,19 @@ class BlockRegistrarTest extends TestCase
|
||||
$this->registrar->registerBlocks();
|
||||
}
|
||||
|
||||
public function testFrontEndRenderDelegatesToThePageObjects(): void
|
||||
public function testFrontEndRenderDelegatesToThePageObjectsPassingAttributes(): void
|
||||
{
|
||||
$this->registrar->preview = false;
|
||||
|
||||
$this->bookingPage->shouldReceive('render')->once()->with([])->andReturn('booking-html');
|
||||
$this->loginPage->shouldReceive('render')->once()->with([])->andReturn('login-html');
|
||||
$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());
|
||||
self::assertSame('login-html', $this->registrar->renderLogin());
|
||||
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());
|
||||
}
|
||||
@@ -164,4 +188,203 @@ class BlockRegistrarTest extends TestCase
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user