Skip to content

Commit

Permalink
Merge pull request #127 from hotwired-laravel/turbo-native-routes
Browse files Browse the repository at this point in the history
Send flash messages via query string on Turbo Native redirects (recede, resume, & refresh)
  • Loading branch information
tonysm authored Oct 30, 2023
2 parents 347eb2c + 6dfa1a8 commit 5a348ac
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace HotwiredLaravel\TurboLaravel\Http\Controllers\Concerns;

use HotwiredLaravel\TurboLaravel\Http\TurboNativeRedirectResponse;

trait InteractsWithTurboNativeNavigation
{
protected function recedeOrRedirectTo(string $url)
Expand Down Expand Up @@ -37,7 +39,7 @@ protected function refreshOrRedirectBack(?string $fallbackUrl, array $options =
protected function redirectToTurboNativeAction(string $action, string $fallbackUrl, string $redirectType = 'to', array $options = [])
{
if (request()->wasFromTurboNative()) {
return redirect(route("turbo_{$action}_historical_location"));
return TurboNativeRedirectResponse::createFromFallbackUrl($action, $fallbackUrl);
}

if ($redirectType === 'back') {
Expand Down
68 changes: 68 additions & 0 deletions src/Http/TurboNativeRedirectResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace HotwiredLaravel\TurboLaravel\Http;

use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Str;

class TurboNativeRedirectResponse extends RedirectResponse
{
/**
* Factory Method that builds a new instance of the TurboNativeRedirectResponse
* with the given action and forwards the query strings from the given fallback
* URL to the Turbo Native redirect ones.
*/
public static function createFromFallbackUrl(string $action, string $fallbackUrl): self
{
return (new self(route("turbo_{$action}_historical_location")))
->withQueryString((new self($fallbackUrl))->getQueryString());
}

/**
* Sets the flashed data via query strings when redirecting to Turbo Native routes.
*
* @param string $key
* @param mixed $value
* @return self
*/
public function with($key, $value = null)
{
$params = $this->getQueryString();

return $this->withoutQueryStrings()
->setTargetUrl($this->getTargetUrl().'?'.http_build_query($params + [$key => urlencode($value)]));
}

/**
* Sets multiple query strings at the same time.
*/
protected function withQueryString(array $params): self
{
foreach ($params as $key => $val) {
$this->with($key, $val);
}

return $this;
}

/**
* Returns the query string as an array.
*/
protected function getQueryString(): array
{
parse_str(str_contains($this->getTargetUrl(), '?') ? Str::after($this->getTargetUrl(), '?') : '', $query);

return $query;
}

/**
* Returns the target URL without the query strings.
*/
protected function withoutQueryStrings(): self
{
$fragment = str_contains($this->getTargetUrl(), '#') ? Str::after($this->getTargetUrl(), '#') : '';

return $this->withoutFragment()
->setTargetUrl(Str::before($this->getTargetUrl(), '?').($fragment ? "#{$fragment}" : ''));
}
}
45 changes: 43 additions & 2 deletions tests/Http/TurboNativeNavigationControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class TurboNativeNavigationControllerTest extends TestCase
{
use InteractsWithTurbo;

public function actionsDataProvider()
public static function actionsDataProvider()
{
return [
['recede'],
Expand All @@ -23,7 +23,7 @@ public function actionsDataProvider()
*
* @dataProvider actionsDataProvider
*/
public function recede_resume_or_refresh_when_native_or_redirect_when_not(string $action)
public function recede_resume_or_refresh_when_native_or_redirect_when_not_without_flash(string $action)
{
$this->post(route('trays.store'), ['return_to' => "{$action}_or_redirect"])
->assertRedirect(route('trays.show', 1));
Expand All @@ -32,6 +32,47 @@ public function recede_resume_or_refresh_when_native_or_redirect_when_not(string
->assertRedirect(route("turbo_{$action}_historical_location"));
}

/**
* @test
*
* @dataProvider actionsDataProvider
*/
public function recede_resume_or_refresh_when_native_or_redirect_when_not_with_flash(string $action)
{
// Non-Turbo Native redirect with only flash...
$this->post(route('trays.store'), ['return_to' => "{$action}_or_redirect", 'with' => true])
->assertRedirect(route('trays.show', ['tray' => 1]))
->assertSessionHas('status', __('Tray created.'));

// Non-Turbo Native redirect with only flash & fragments...
$this->post(route('trays.store'), ['return_to' => "{$action}_or_redirect", 'with' => true, 'fragment' => true])
->assertRedirect(route('trays.show', ['tray' => 1]).'#newly-created-tray')
->assertSessionHas('status', __('Tray created.'));

// Non-Turbo Native redirect with only flash & fragments & queries...
$this->post(route('trays.store'), ['return_to' => "{$action}_or_redirect", 'with' => true, 'fragment' => true, 'query' => true])
->assertRedirect(route('trays.show', ['tray' => 1, 'lorem' => 'ipsum']).'#newly-created-tray')
->assertSessionHas('status', __('Tray created.'));

// Turbo Native redirect with only flash...
$this->turboNative()
->post(route('trays.store'), ['return_to' => "{$action}_or_redirect", 'with' => true])
->assertRedirect(route("turbo_{$action}_historical_location", ['status' => urlencode(__('Tray created.'))]))
->assertSessionMissing('status');

// Turbo Native redirect with only flash & fragments...
$this->turboNative()
->post(route('trays.store'), ['return_to' => "{$action}_or_redirect", 'with' => true, 'fragment' => true])
->assertRedirect(route("turbo_{$action}_historical_location", ['status' => urlencode(__('Tray created.'))]).'#newly-created-tray')
->assertSessionMissing('status');

// Turbo Native redirect with only flash & fragments & query...
$this->turboNative()
->post(route('trays.store'), ['return_to' => "{$action}_or_redirect", 'with' => true, 'fragment' => true, 'query' => true])
->assertRedirect(route("turbo_{$action}_historical_location", ['lorem' => 'ipsum', 'status' => urlencode(__('Tray created.'))]).'#newly-created-tray')
->assertSessionMissing('status');
}

/**
* @test
*
Expand Down
28 changes: 21 additions & 7 deletions workbench/app/Http/Controllers/TraysController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,28 @@ public function show($tray)

public function store(Request $request)
{
return match ($request->input('return_to')) {
'recede_or_redirect' => $this->recedeOrRedirectTo(route('trays.show', 1)),
'resume_or_redirect' => $this->resumeOrRedirectTo(route('trays.show', 1)),
'refresh_or_redirect' => $this->refreshOrRedirectTo(route('trays.show', 1)),
'recede_or_redirect_back' => $this->recedeOrRedirectBack(route('trays.show', 5)),
'resume_or_redirect_back' => $this->resumeOrRedirectBack(route('trays.show', 5)),
'refresh_or_redirect_back' => $this->refreshOrRedirectBack(route('trays.show', 5)),
$query = $request->boolean('query')
? ['lorem' => 'ipsum']
: [];

$response = match ($request->input('return_to')) {
'recede_or_redirect' => $this->recedeOrRedirectTo(route('trays.show', ['tray' => 1] + $query)),
'resume_or_redirect' => $this->resumeOrRedirectTo(route('trays.show', ['tray' => 1] + $query)),
'refresh_or_redirect' => $this->refreshOrRedirectTo(route('trays.show', ['tray' => 1] + $query)),
'recede_or_redirect_back' => $this->recedeOrRedirectBack(route('trays.show', ['tray' => 5] + $query)),
'resume_or_redirect_back' => $this->resumeOrRedirectBack(route('trays.show', ['tray' => 5] + $query)),
'refresh_or_redirect_back' => $this->refreshOrRedirectBack(route('trays.show', ['tray' => 5] + $query)),
default => throw new Exception('Missing return_to param to redirect the response.'),
};

if ($request->input('with')) {
$response = $response->with('status', __('Tray created.'));
}

if ($request->input('fragment')) {
$response = $response->withFragment('#newly-created-tray');
}

return $response;
}
}

0 comments on commit 5a348ac

Please sign in to comment.