diff --git a/app/Livewire/Versions/Index.php b/app/Livewire/Versions/Index.php index 71989c0..f645968 100644 --- a/app/Livewire/Versions/Index.php +++ b/app/Livewire/Versions/Index.php @@ -382,7 +382,10 @@ class Index extends Component if ($rel['unreleased']) { $key = 'unreleased'; } elseif (preg_match('/^v?(\d+)\.(\d+)\./', $rel['version'], $m)) { - $key = $m[1].'.'.$m[2]; + // Prefix the series key with a non-numeric 'v' so the ?series= URL value is never a + // bare decimal — Livewire's #[Url] otherwise type-juggles "0.10" → 0.1 (the trailing + // zero is lost), silently switching the selected series on reload. The label strips it. + $key = 'v'.$m[1].'.'.$m[2]; } else { $key = 'other'; } @@ -397,7 +400,7 @@ class Index extends Component 'label' => match ($key) { 'unreleased' => __('versions.unreleased'), 'other' => __('versions.series_other'), - default => $key, + default => ltrim($key, 'v'), // display the dotted series (the 'v' is only for the URL) }, 'unreleased' => $key === 'unreleased', 'count' => count($rels), @@ -415,7 +418,7 @@ class Index extends Component return $ra <=> $rb; } - return $ra === 1 ? version_compare($b['key'].'.0', $a['key'].'.0') : 0; + return $ra === 1 ? version_compare(ltrim($b['key'], 'v').'.0', ltrim($a['key'], 'v').'.0') : 0; }); return $series; @@ -628,7 +631,7 @@ class Index extends Component // Mark the series that holds the running version (a small "installed" cue in the rail). $installed = ltrim((string) config('clusev.version'), 'vV'); - $currentSeries = preg_match('/^(\d+)\.(\d+)\./', $installed, $m) ? $m[1].'.'.$m[2] : null; + $currentSeries = preg_match('/^(\d+)\.(\d+)\./', $installed, $m) ? 'v'.$m[1].'.'.$m[2] : null; // Passive (no network): cached remote result if a check has run, else local .git. $latestTag = $this->resolveLatestTag($this->channel()); diff --git a/tests/Feature/VersionsChangelogTest.php b/tests/Feature/VersionsChangelogTest.php index ce8b980..36ea492 100644 --- a/tests/Feature/VersionsChangelogTest.php +++ b/tests/Feature/VersionsChangelogTest.php @@ -81,8 +81,8 @@ class VersionsChangelogTest extends TestCase // …selecting the 0.1 series brings it into view and jumps back to page 1. Livewire::test(Index::class) ->set('changelogPage', 3) - ->call('selectSeries', '0.1') - ->assertSet('series', '0.1') + ->call('selectSeries', 'v0.1') + ->assertSet('series', 'v0.1') ->assertSet('changelogPage', 1) ->assertSee('v0.1.0'); } @@ -90,7 +90,7 @@ class VersionsChangelogTest extends TestCase public function test_a_long_series_is_paginated_to_the_per_page_limit(): void { Livewire::test(Index::class) - ->call('selectSeries', '0.9') + ->call('selectSeries', 'v0.9') ->assertViewHas('totalPages', fn ($total): bool => $total > 1) ->assertViewHas('pagedReleases', fn ($releases): bool => is_array($releases) && count($releases) <= 8); } @@ -100,7 +100,7 @@ class VersionsChangelogTest extends TestCase $pageOne = null; Livewire::test(Index::class) - ->call('selectSeries', '0.9') + ->call('selectSeries', 'v0.9') ->assertViewHas('pagedReleases', function ($releases) use (&$pageOne): bool { $pageOne = array_column($releases, 'version'); @@ -108,7 +108,7 @@ class VersionsChangelogTest extends TestCase }); Livewire::test(Index::class) - ->call('selectSeries', '0.9') + ->call('selectSeries', 'v0.9') ->call('gotoChangelogPage', 2) ->assertViewHas('pagedReleases', function ($releases) use (&$pageOne): bool { $pageTwo = array_column($releases, 'version'); @@ -121,7 +121,7 @@ class VersionsChangelogTest extends TestCase public function test_an_out_of_range_page_is_clamped(): void { Livewire::test(Index::class) - ->call('selectSeries', '0.9') + ->call('selectSeries', 'v0.9') ->call('gotoChangelogPage', 9999) ->assertViewHas('changelogPage', fn ($page): bool => $page >= 1) ->assertViewHas('pagedReleases', fn ($releases): bool => is_array($releases) && count($releases) >= 1) @@ -132,7 +132,7 @@ class VersionsChangelogTest extends TestCase { config()->set('clusev.version', '0.7.3'); - Livewire::test(Index::class)->assertViewHas('currentSeries', '0.7'); + Livewire::test(Index::class)->assertViewHas('currentSeries', 'v0.7'); } public function test_total_release_count_is_passed_for_the_panel_subtitle(): void @@ -143,12 +143,22 @@ class VersionsChangelogTest extends TestCase public function test_series_and_page_are_deep_linkable_via_the_query_string(): void { // ?series= + ?page= drive the view so a series/page is shareable and survives reload. - Livewire::withQueryParams(['series' => '0.9', 'page' => 2]) + Livewire::withQueryParams(['series' => 'v0.9', 'page' => 2]) ->test(Index::class) - ->assertSet('series', '0.9') + ->assertSet('series', 'v0.9') ->assertSet('changelogPage', 2); } + public function test_a_trailing_zero_series_like_0_10_survives_a_deep_link(): void + { + // The 'v' prefix keeps the ?series= value non-numeric, so "0.10" is NOT type-juggled to + // "0.1" on reload (which used to silently switch the selected series). + Livewire::withQueryParams(['series' => 'v0.10']) + ->test(Index::class) + ->assertSet('series', 'v0.10') + ->assertViewHas('activeKey', 'v0.10'); + } + public function test_chevron_right_icon_renders_a_real_path(): void { // The accordion disclosure caret and the pagination "Next" button both use x-icon