Add per-embed lesson-type and section options to the booking block
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

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]>
This commit is contained in:
2026-07-28 13:16:18 -03:00
co-authored by Claude Opus 5
parent 9d11cc3b01
commit 264d9cba01
13 changed files with 426 additions and 40 deletions
+99
View File
@@ -17,6 +17,26 @@ class BookingPageTest extends TestCase
$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);
@@ -59,6 +79,85 @@ class BookingPageTest extends TestCase
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