guardians = Mockery::mock(GuardianService::class); // Most cases are a single-student account: one bookable person, no picker. $this->guardians->shouldReceive('bookableStudents')->andReturn( [['id' => 3, 'name' => 'Ada', 'is_self' => true]] )->byDefault(); $this->page = new BookingPage($this->guardians); } /** * Renders the page as a logged-in, approved student — the path that * includes the template and its data attributes. * * @param array $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)); } /** * A single-student account gets a one-entry list, which the script renders * as no picker at all. */ public function testStudentListIsEmbeddedForTheScript(): void { $html = $this->renderForStudent([]); self::assertStringContainsString('data-students=', $html); self::assertStringContainsString('"is_self":true', $html); } /** * Children lead the embedded list, so the picker's default selection is a * child rather than the parent. */ public function testGuardianListLeadsWithChildren(): void { $this->guardians->shouldReceive('bookableStudents')->with(3)->andReturn([ ['id' => 42, 'name' => 'Ada', 'is_self' => false], ['id' => 3, 'name' => 'Grace', 'is_self' => true], ]); $html = $this->renderForStudent([]); $students = json_decode(html_entity_decode((string) preg_replace('/.*data-students="([^"]*)".*/s', '$1', $html)), true); self::assertSame([42, 3], array_column((array) $students, 'id')); self::assertFalse($students[0]['is_self']); } }