*/ private array $shortcodes = []; /** @var array */ private array $localized = []; 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->familyPage = Mockery::mock(FamilyPage::class); $this->accountPage = Mockery::mock(AccountPage::class); $this->registrar = new ShortcodeRegistrar( $this->bookingPage, $this->loginPage, $this->registrationPage, $this->groupClassPage, $this->familyPage, $this->accountPage, ); $shortcodes = &$this->shortcodes; Functions\when('add_shortcode')->alias( static function (string $tag, callable $callback) use (&$shortcodes): void { $shortcodes[$tag] = $callback; } ); } public function testRegisterAddsAllShortcodesAndHooks(): void { Actions\expectAdded('template_redirect') ->once() ->with([$this->registrationPage, 'maybeRedirectToRegistrationPage']); Actions\expectAdded('wp_enqueue_scripts') ->once() ->with([$this->registrar, 'enqueueAssets']); $this->registrar->register(); self::assertSame( ['us_booking', 'us_student_login', 'us_student_register', 'us_group_classes', 'us_family', 'us_account'], array_keys($this->shortcodes) ); } /** * WordPress passes an empty string (not an array) to a shortcode callback * when the shortcode is used without attributes, e.g. `[us_booking]` — * the wrapper must normalize it before the typed render methods. */ public function testBareShortcodeUsageIsNormalizedToAnEmptyAttributeArray(): void { $this->registrar->register(); $this->bookingPage->shouldReceive('render')->once()->with([])->andReturn('booking'); $this->loginPage->shouldReceive('render')->once()->with([])->andReturn('login'); $this->registrationPage->shouldReceive('render')->once()->with([])->andReturn('register'); $this->groupClassPage->shouldReceive('render')->once()->with([])->andReturn('group'); self::assertSame('booking', $this->shortcodes['us_booking']('')); self::assertSame('login', $this->shortcodes['us_student_login']('')); self::assertSame('register', $this->shortcodes['us_student_register']('')); self::assertSame('group', $this->shortcodes['us_group_classes']('')); } /** * The booking and group-class scripts both read prices through the shared * pricing helper, so it must be registered ahead of them (and behind the * payment helper, which carries the localized config it reads). */ public function testPricingHelperIsRegisteredAheadOfTheBookingAndGroupScripts(): void { $scripts = $this->captureEnqueuedAssets(); self::assertSame(['us-scheduler-payment'], $scripts['us-scheduler-pricing']); self::assertSame(['us-scheduler-pricing', 'us-scheduler-guardian'], $scripts['us-scheduler']); self::assertSame(['us-scheduler-pricing', 'us-scheduler-guardian'], $scripts['us-scheduler-group']); } /** * The studio HST rate reaches the front end so a price quoted on a booking * form matches the total the student is actually billed. */ public function testStudioTaxRateIsLocalizedToTheFrontEnd(): void { $this->captureEnqueuedAssets(); self::assertSame(13.0, $this->localized['taxRate']); } /** * @return array> Registered script handle => dependencies. */ private function captureEnqueuedAssets(): array { $scripts = []; $localized = &$this->localized; Functions\when('wp_register_style')->justReturn(true); Functions\when('rest_url')->justReturn('https://example.test/wp-json/us-scheduler/v1/'); Functions\when('wp_create_nonce')->justReturn('nonce'); Functions\when('get_option')->alias( static fn (string $name, mixed $default = false): mixed => match ($name) { 'us_hst_rate' => '13', 'start_of_week' => 1, default => $default, } ); Functions\when('wp_register_script')->alias( static function (string $handle, string $src, array $deps = []) use (&$scripts): bool { $scripts[$handle] = $deps; return true; } ); Functions\when('wp_localize_script')->alias( static function (string $handle, string $object, array $data) use (&$localized): bool { $localized = $data; return true; } ); $this->registrar->enqueueAssets(); return $scripts; } public function testShortcodeAttributesArePassedThroughUnchanged(): void { $this->registrar->register(); $this->bookingPage->shouldReceive('render') ->once()->with(['login_page_id' => '5'])->andReturn('booking'); $this->loginPage->shouldReceive('render') ->once()->with(['booking_page_id' => '9'])->andReturn('login'); self::assertSame('booking', $this->shortcodes['us_booking'](['login_page_id' => '5'])); self::assertSame('login', $this->shortcodes['us_student_login'](['booking_page_id' => '9'])); } }