controller()->redirectTo($this->request(['HTTP_HX-Request' => 'true']), '/buchungen'); self::assertInstanceOf(HxRedirectResponse::class, $response); self::assertSame('/buchungen', $response->headers->get('HX-Redirect')); // A 3xx would be followed by the browser and swapped as a whole page into the fragment. self::assertSame(Response::HTTP_OK, $response->getStatusCode()); } public function testOrdinaryRequestsGetANormalRedirect(): void { $response = $this->controller()->redirectTo($this->request(), '/buchungen'); self::assertInstanceOf(RedirectResponse::class, $response); self::assertSame('/buchungen', $response->getTargetUrl()); } public function testOobResponseConcatenatesEveryRequestedBlock(): void { $controller = $this->controller(); $response = $controller->oob('booking/index.html.twig', ['grid', 'floorplan']); self::assertSame('[grid][floorplan]', $response->getContent()); self::assertSame( [['booking/index.html.twig', 'grid'], ['booking/index.html.twig', 'floorplan']], $controller->renderedBlocks, ); } public function testOobResponseFlagsTheContextSoBlocksCanEmitHxSwapOob(): void { $controller = $this->controller(); $controller->oob('booking/index.html.twig', ['grid'], ['date' => '2026-09-13']); self::assertSame(['date' => '2026-09-13', 'htmx_oob_swap' => true], $controller->renderedContext); } public function testCallerSuppliedContextWinsOverTheInjectedFlag(): void { $controller = $this->controller(); $controller->oob('booking/index.html.twig', ['grid'], ['htmx_oob_swap' => false]); self::assertFalse($controller->renderedContext['htmx_oob_swap']); } public function testPushUrlIsOptional(): void { $controller = $this->controller(); self::assertFalse($controller->oob('t.html.twig', ['b'])->headers->has('HX-Push-Url')); self::assertSame( '/buchungen?datum=2026-09-13', $controller->oob('t.html.twig', ['b'], [], '/buchungen?datum=2026-09-13')->headers->get('HX-Push-Url'), ); } /** * @param array $server */ private function request(array $server = []): Request { return Request::create('/buchungen', server: $server); } private function controller(): object { return new class extends AbstractController { use HxTrait; /** @var list */ public array $renderedBlocks = []; /** @var array */ public array $renderedContext = []; /** * @param string[] $blockNames * @param array $context */ public function oob(string $template, array $blockNames, array $context = [], ?string $pushUrl = null): Response { return $this->htmxOobResponse($template, $blockNames, $context, $pushUrl); } public function redirectTo(Request $request, string $url): Response { return $this->htmxRedirect($request, $url); } /** * Stands in for Twig so the trait's own behaviour can be tested without a kernel. * * @param array $parameters */ protected function renderBlockView(string $view, string $block, array $parameters = []): string { $this->renderedBlocks[] = [$view, $block]; $this->renderedContext = $parameters; return \sprintf('[%s]', $block); } }; } }