161 lines
5.9 KiB
PHP
161 lines
5.9 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Livewire\Versions\Index;
|
|
use App\Models\User;
|
|
use App\Services\DeploymentService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Blade;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Livewire\Livewire;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* The changelog browser: releases are grouped into major.minor series (a left rail / pill row)
|
|
* and the active series is paginated so a long history never becomes an endless scroll. These
|
|
* tests drive the real CHANGELOG.md, asserting structural invariants (ordering, paging, defaults)
|
|
* rather than specific version numbers, so they survive new releases.
|
|
*/
|
|
class VersionsChangelogTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
Cache::flush();
|
|
app(DeploymentService::class)->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 <svg> (invisible affordance).
|
|
$html = Blade::render('<x-icon name="chevron-right" />');
|
|
|
|
$this->assertStringContainsString('<path', $html, 'chevron-right must resolve to a real Lucide path, not an empty svg');
|
|
}
|
|
}
|