diff --git a/VERSION b/VERSION index 3eba9bd..36a5f6b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.42 +1.3.43 diff --git a/app/Livewire/Admin/CustomerDetail.php b/app/Livewire/Admin/CustomerDetail.php index 76df2d5..a43cd40 100644 --- a/app/Livewire/Admin/CustomerDetail.php +++ b/app/Livewire/Admin/CustomerDetail.php @@ -50,7 +50,17 @@ class CustomerDetail extends Component public Customer $customer; - #[Url(except: 'overview')] + /** + * Which tab is open, in the address bar. + * + * `history: true`, so each tab is a step the back button can take — and no + * `except`, so the parameter is there from the first render rather than + * appearing only once somebody leaves the default tab. Hiding it on the + * default read as "the tab is not in the URL", which is exactly how it was + * reported: a link to this page could not say which tab it meant unless the + * tab happened not to be the first one. + */ + #[Url(history: true)] public string $tab = 'overview'; // ---- Writing to them ---- diff --git a/app/Livewire/Admin/Integrations.php b/app/Livewire/Admin/Integrations.php index a9fbcea..bd0f8e7 100644 --- a/app/Livewire/Admin/Integrations.php +++ b/app/Livewire/Admin/Integrations.php @@ -76,7 +76,17 @@ class Integrations extends Component */ public const TABS = ['services', 'platform', 'env']; - #[Url(except: 'services')] + /** + * Which tab is open, in the address bar. + * + * `history: true`, so each tab is a step the back button can take — and no + * `except`, so the parameter is there from the first render rather than + * appearing only once somebody leaves the default tab. Hiding it on the + * default read as "the tab is not in the URL", which is exactly how it was + * reported: a link to this page could not say which tab it meant unless the + * tab happened not to be the first one. + */ + #[Url(history: true)] public string $tab = 'services'; use ConfirmsPassword { diff --git a/app/Livewire/Admin/Settings.php b/app/Livewire/Admin/Settings.php index 297a8f7..0a369d5 100644 --- a/app/Livewire/Admin/Settings.php +++ b/app/Livewire/Admin/Settings.php @@ -64,7 +64,17 @@ class Settings extends Component * needs no parameter, and a query string that appears the moment a page * loads makes every link to it look different from the one in the menu. */ - #[Url(except: 'installation')] + /** + * Which tab is open, in the address bar. + * + * `history: true`, so each tab is a step the back button can take — and no + * `except`, so the parameter is there from the first render rather than + * appearing only once somebody leaves the default tab. Hiding it on the + * default read as "the tab is not in the URL", which is exactly how it was + * reported: a link to this page could not say which tab it meant unless the + * tab happened not to be the first one. + */ + #[Url(history: true)] public string $tab = 'installation'; /** This page changes the signed-in OPERATOR's password, never a portal one. */ diff --git a/tests/Feature/Admin/CustomerDetailTabsTest.php b/tests/Feature/Admin/CustomerDetailTabsTest.php index 33e3748..2b5b3f1 100644 --- a/tests/Feature/Admin/CustomerDetailTabsTest.php +++ b/tests/Feature/Admin/CustomerDetailTabsTest.php @@ -34,7 +34,7 @@ beforeEach(function () { ]); }); -it('opens on the details, and keeps that tab out of the address bar', function () { +it('opens on the details', function () { Livewire::actingAs(operator('Owner'), 'operator') ->test(CustomerDetail::class, ['uuid' => $this->customer->uuid]) ->assertSet('tab', 'overview') diff --git a/tests/Feature/Admin/SettingsTabsTest.php b/tests/Feature/Admin/SettingsTabsTest.php index e876cc7..b1809e3 100644 --- a/tests/Feature/Admin/SettingsTabsTest.php +++ b/tests/Feature/Admin/SettingsTabsTest.php @@ -13,7 +13,7 @@ use Livewire\Livewire; * refreshes itself while an update runs, so a tab kept only in the component * would drop them back to the first one mid-deployment. */ -it('opens the installation tab by default and keeps it out of the address bar', function () { +it('opens the installation tab by default', function () { Livewire::actingAs(operator('Owner'), 'operator') ->test(Settings::class) ->assertSet('tab', 'installation') @@ -133,3 +133,34 @@ it('tabs the integrations page the same way, and keeps the raw file to the Owner ->assertSet('tab', 'services') ->assertViewHas('tabs', fn (array $tabs) => ! in_array('env', $tabs, true)); }); + +it('puts the open tab in the address bar from the first render, on every tabbed page', function () { + // Reported as "die Tabs fehlen in der Query beim Kunden". The binding was + // there, but `except` hid the parameter while the default tab was open — so + // a link to the page could not say which tab it meant unless the tab + // happened not to be the first one, and the address bar looked like the + // feature was missing. + // + // Asserted against the rendered page rather than the component: the + // parameter is written by Livewire's JS from the `url` memo in the snapshot, + // so the memo IS the behaviour. + $customer = App\Models\Customer::factory()->create(); + + $pages = [ + route('admin.settings'), + route('admin.integrations'), + route('admin.customer', $customer->uuid), + ]; + + foreach ($pages as $url) { + $html = html_entity_decode( + (string) $this->actingAs(operator('Owner'), 'operator')->get($url)->getContent() + ); + + expect($html)->toContain('"url":{"tab":') + // No `except`: shown even on the default tab. + ->and($html)->toMatch('/"tab":\{[^}]*"except":null/') + // push, not replace: each tab is a step the back button can take. + ->and($html)->toMatch('/"tab":\{[^}]*"use":"push"/'); + } +});