Files
unsupervised-scheduler/tests/Unit/Booking/BookingPageTest.php
T
thatguygriffandClaude Opus 5 264d9cba01
CI / Tests (PHP 8.1) (pull_request) Successful in 47s
CI / Tests (PHP 8.2) (pull_request) Successful in 54s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m51s
CI / Coding Standards (pull_request) Successful in 2m56s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
Add per-embed lesson-type and section options to the booking block
Three sidebar options on the Lesson Booking block, all mirrored as shortcode
attributes and carried to the front end as data attributes on
#us-booking-app (or as omitted containers):

- Lesson type (lessonTypeId / lesson_type) pins the calendar to a single
  private-lesson type: only the times bookable as it are listed, and it is
  the only type bookable there, auto-selected on the registration form. A
  pinned type that is no longer offered says so instead of showing an empty
  calendar.
- Show the lesson-type filter (showTypeFilter / show_filter) drops the
  "Show Only" control for studios that do not want it.
- Sections (displayMode / show) embeds one half of the page — the booking
  calendar or the student's upcoming lessons — so the two can live on
  different pages. The script skips the work belonging to a missing half:
  no availability or catalog request for an upcoming-only embed, no
  bookings request for a booking-only one. An unrecognised value renders
  the whole page. The editor preview follows the same setting.

Also fixes the expanded filter's first lesson type sharing a line with the
"Lesson type" heading — the choices now sit in their own row beneath it.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-28 13:16:18 -03:00

175 lines
6.2 KiB
PHP

<?php
declare(strict_types=1);
namespace Unsupervised\Schedular\Tests\Unit\Booking;
use Brain\Monkey\Functions;
use Unsupervised\Schedular\Booking\BookingPage;
use Unsupervised\Schedular\Tests\Unit\TestCase;
class BookingPageTest extends TestCase
{
private BookingPage $page;
protected function setUp(): void
{
parent::setUp();
$this->page = new BookingPage();
}
/**
* Renders the page as a logged-in, approved student — the path that
* includes the template and its data attributes.
*
* @param array<int|string, mixed> $atts
*/
private function renderForStudent(array $atts): string
{
Functions\when('is_user_logged_in')->justReturn(true);
Functions\when('get_current_user_id')->justReturn(3);
Functions\when('get_user_meta')->justReturn('');
Functions\when('current_user_can')->justReturn(true);
Functions\when('wp_enqueue_style')->justReturn(null);
Functions\when('wp_enqueue_script')->justReturn(null);
Functions\when('wp_create_nonce')->justReturn('nonce123');
Functions\when('absint')->alias(static fn ($value) => abs((int) $value));
return $this->page->render($atts);
}
public function testLoggedOutVisitorIsLinkedToTheWordPressLoginByDefault(): void
{
Functions\when('is_user_logged_in')->justReturn(false);
Functions\when('get_permalink')->justReturn('https://example.com/book/');
Functions\when('wp_login_url')->alias(
static fn(string $redirect): string => 'https://example.com/wp-login.php?redirect_to=' . $redirect
);
$html = $this->page->render([]);
self::assertStringContainsString(
'href="https://example.com/wp-login.php?redirect_to=https://example.com/book/"',
$html
);
self::assertStringContainsString('log in to book a lesson', $html);
}
public function testLoggedOutVisitorIsLinkedToTheChosenLoginPage(): void
{
Functions\when('is_user_logged_in')->justReturn(false);
Functions\when('get_permalink')->alias(
static fn(int $id = 0): string|false => 5 === $id ? 'https://example.com/login/' : false
);
Functions\expect('wp_login_url')->never();
$html = $this->page->render(['loginPageId' => 5]);
self::assertStringContainsString('href="https://example.com/login/"', $html);
}
public function testShortcodeStyleAttributeSelectsTheLoginPageToo(): void
{
Functions\when('is_user_logged_in')->justReturn(false);
Functions\when('get_permalink')->alias(
static fn(int $id = 0): string|false => 5 === $id ? 'https://example.com/login/' : false
);
$html = $this->page->render(['login_page_id' => '5']);
self::assertStringContainsString('href="https://example.com/login/"', $html);
}
public function testDefaultEmbedShowsBothHalvesWithTheFilterAndNoPinnedType(): void
{
$html = $this->renderForStudent([]);
self::assertStringContainsString('id="us-booking-app"', $html);
self::assertStringContainsString('id="us-my-lessons"', $html);
self::assertStringContainsString('id="us-slot-list"', $html);
self::assertStringNotContainsString('data-lesson-type', $html);
self::assertStringNotContainsString('data-type-filter', $html);
}
public function testBlockLessonTypeAttributePinsASingleType(): void
{
self::assertStringContainsString(
'data-lesson-type="12"',
$this->renderForStudent(['lessonTypeId' => 12])
);
}
public function testShortcodeLessonTypeAttributePinsASingleType(): void
{
self::assertStringContainsString(
'data-lesson-type="7"',
$this->renderForStudent(['lesson_type' => '7'])
);
}
public function testGarbageLessonTypeAttributeIsIgnored(): void
{
self::assertStringNotContainsString(
'data-lesson-type',
$this->renderForStudent(['lesson_type' => 'banana'])
);
}
public function testFilterCanBeTurnedOffByBlockAndShortcodeAlike(): void
{
self::assertStringContainsString(
'data-type-filter="0"',
$this->renderForStudent(['showTypeFilter' => false])
);
// "no" is truthy to PHP, so the shortcode wording is matched explicitly.
self::assertStringContainsString(
'data-type-filter="0"',
$this->renderForStudent(['show_filter' => 'no'])
);
self::assertStringNotContainsString(
'data-type-filter',
$this->renderForStudent(['show_filter' => 'yes'])
);
}
public function testBookingOnlyEmbedLeavesOutTheUpcomingLessons(): void
{
$html = $this->renderForStudent(['displayMode' => 'booking']);
self::assertStringContainsString('id="us-slot-list"', $html);
self::assertStringNotContainsString('us-my-lessons', $html);
}
public function testUpcomingOnlyEmbedLeavesOutTheBookingCalendar(): void
{
$html = $this->renderForStudent(['show' => 'upcoming']);
self::assertStringContainsString('id="us-my-lessons"', $html);
self::assertStringNotContainsString('us-slot-list', $html);
self::assertStringNotContainsString('us-booking-confirmation', $html);
}
public function testUnknownDisplayModeShowsTheWholePage(): void
{
$html = $this->renderForStudent(['displayMode' => 'sideways']);
self::assertStringContainsString('id="us-my-lessons"', $html);
self::assertStringContainsString('id="us-slot-list"', $html);
}
public function testLoginUrlFallsBackToWordPressLoginWhenThePageIsGone(): void
{
// The chosen page was deleted: get_permalink() returns false for it
// and for the current (test) context alike.
Functions\when('get_permalink')->justReturn(false);
Functions\when('wp_login_url')->alias(
static fn(string $redirect): string => '' === $redirect
? 'https://example.com/wp-login.php'
: 'https://example.com/wp-login.php?redirect_to=' . $redirect
);
self::assertSame('https://example.com/wp-login.php', $this->page->loginUrl(5));
}
}