clearUpdateRequest(); $this->actingAs(User::factory()->create(['must_change_password' => false])); } protected function tearDown(): void { app(DeploymentService::class)->clearUpdateRequest(); parent::tearDown(); } public function test_releases_are_grouped_into_series_ordered_newest_first(): void { Livewire::test(Index::class)->assertViewHas('seriesList', function ($series): bool { if (! is_array($series) || count($series) < 2) { return false; } // Newest series first: each key sorts >= the next (compare on "X.Y.0"). for ($i = 0; $i < count($series) - 1; $i++) { if (version_compare($series[$i]['key'].'.0', $series[$i + 1]['key'].'.0', '<')) { return false; } } return true; }); } public function test_default_active_series_is_the_newest_one(): void { $firstKey = null; $component = Livewire::test(Index::class) ->assertViewHas('seriesList', function ($series) use (&$firstKey): bool { $firstKey = $series[0]['key'] ?? null; return true; }); $component->assertViewHas('activeKey', fn ($key): bool => $key === $firstKey); } public function test_an_unknown_selected_series_falls_back_to_the_newest(): void { Livewire::test(Index::class) ->call('selectSeries', '99.99') ->assertViewHas('activeKey', fn ($key): bool => $key !== '99.99' && $key !== ''); } public function test_selecting_a_series_switches_the_visible_releases_and_resets_the_page(): void { // Default view shows the newest series, so the very first release (v0.1.0) is NOT visible… Livewire::test(Index::class)->assertDontSee('v0.1.0'); // …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') ->assertSet('changelogPage', 1) ->assertSee('v0.1.0'); } public function test_a_long_series_is_paginated_to_the_per_page_limit(): void { Livewire::test(Index::class) ->call('selectSeries', '0.9') ->assertViewHas('totalPages', fn ($total): bool => $total > 1) ->assertViewHas('pagedReleases', fn ($releases): bool => is_array($releases) && count($releases) <= 8); } public function test_paging_shows_a_different_slice_of_the_series(): void { $pageOne = null; Livewire::test(Index::class) ->call('selectSeries', '0.9') ->assertViewHas('pagedReleases', function ($releases) use (&$pageOne): bool { $pageOne = array_column($releases, 'version'); return true; }); Livewire::test(Index::class) ->call('selectSeries', '0.9') ->call('gotoChangelogPage', 2) ->assertViewHas('pagedReleases', function ($releases) use (&$pageOne): bool { $pageTwo = array_column($releases, 'version'); // Page 2 is non-empty and disjoint from page 1. return $pageTwo !== [] && array_intersect($pageTwo, $pageOne) === []; }); } public function test_an_out_of_range_page_is_clamped(): void { Livewire::test(Index::class) ->call('selectSeries', '0.9') ->call('gotoChangelogPage', 9999) ->assertViewHas('changelogPage', fn ($page): bool => $page >= 1) ->assertViewHas('pagedReleases', fn ($releases): bool => is_array($releases) && count($releases) >= 1) ->assertSee(__('versions.changelog_title')); } public function test_the_installed_series_is_resolved_from_the_running_version(): void { config()->set('clusev.version', '0.7.3'); Livewire::test(Index::class)->assertViewHas('currentSeries', '0.7'); } public function test_total_release_count_is_passed_for_the_panel_subtitle(): void { Livewire::test(Index::class)->assertViewHas('totalReleases', fn ($count): bool => is_int($count) && $count >= 1); } 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]) ->test(Index::class) ->assertSet('series', '0.9') ->assertSet('changelogPage', 2); } public function test_chevron_right_icon_renders_a_real_path(): void { // The accordion disclosure caret and the pagination "Next" button both use x-icon // name="chevron-right"; a missing map entry renders an empty (invisible affordance). $html = Blade::render(''); $this->assertStringContainsString('