Files
coolify/tests/Feature/NavbarSubmenuTest.php
T
Andras Bacsai fa1895c99a feat(dashboard): add canvas view with active deployment overlay
Add a /dashboard/canvas page that renders every team server with its
resources and their running or queued deployments, backed by a new
ServerCanvasController::dashboard endpoint and a per-server usage
endpoint for zone headers. The dashboard remembers the list/canvas
choice and each section (projects, servers) its own table/grid view.

- Show Deploying/Queued pills on canvas cards and a foldable recent
  deployments panel; add shared CanvasControls component
- Allow backup downloads from the server host via the S3 download
  storage (PrepareFileDownloadJob accepts a null container)
- Extract backup toggle/run actions into ManagesDatabaseBackup and
  surface them in backup execution headers; merge S3 and retention
  sections into the general backup settings
- Move deployments indicator to the top bar and add a main nav
  resource menu with submenus; simplify breadcrumbs and settings
  layouts
- Add shared variables overview canvas and scheduled task modals
- Add feature tests for dashboard canvas, view modes, navbar
  submenus, backup S3 downloads and scheduled task modals
2026-10-14 23:55:28 +02:00

134 lines
4.6 KiB
PHP

<?php
use App\Models\InstanceSettings;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Request;
uses(RefreshDatabase::class);
beforeEach(function () {
InstanceSettings::forceCreate(['id' => 0]);
$this->team = Team::factory()->create();
$this->user = User::factory()->create();
});
function renderNavbarAt(string $path, bool $mobile = false): string
{
app()->instance('request', Request::create($path));
return (string) test()->blade($mobile ? '<x-navbar />' : '<x-navbar desktop />');
}
function loginNavbarUser(string $role): void
{
test()->team->members()->attach(test()->user->id, ['role' => $role]);
test()->actingAs(test()->user);
session(['currentTeam' => test()->team]);
}
test('a submenu page opens its submenu with the current page active', function () {
loginNavbarUser('owner');
$html = renderNavbarAt('/metrics');
expect($html)
->toContain('Back to the main menu')
->toMatch('#aria-current="page"[^>]*>\s*<svg[^>]*>.*?</svg>\s*<span[^>]*>Metrics</span>#s');
});
test('a page outside every submenu starts on the main menu', function () {
loginNavbarUser('owner');
expect(renderNavbarAt('/servers'))->not->toContain('data-active-submenu="');
});
test('a submenu entry links to its first visible page', function () {
loginNavbarUser('owner');
expect(renderNavbarAt('/servers'))->toContain('href="'.route('analytics').'"');
});
test('the registries page is hidden from members in the integrations submenu', function () {
loginNavbarUser('member');
expect(renderNavbarAt('/sources'))
->toContain('Destinations')
->not->toContain('href="'.route('registries.index').'"');
});
test('the team submenu lists the team pages by role', function () {
loginNavbarUser('owner');
expect(renderNavbarAt('/team/members'))
->toContain('href="'.route('team.member.index').'"')
->toContain('href="'.route('team.audit-log').'"')
->toContain('href="'.route('team.danger-zone').'"');
});
test('members do not see the audit log or danger zone in the team submenu', function () {
loginNavbarUser('member');
expect(renderNavbarAt('/team'))
->toContain('href="'.route('team.member.index').'"')
->not->toContain('href="'.route('team.audit-log').'"')
->not->toContain('href="'.route('team.danger-zone').'"');
});
test('notifications and keys & tokens open their own submenus', function () {
loginNavbarUser('owner');
expect(renderNavbarAt('/notifications/discord'))
->toContain('href="'.route('notifications.slack').'"')
->toMatch('#aria-current="page"[^>]*>.*?discord\.svg#s');
expect(renderNavbarAt('/security/api-tokens'))
->toContain('href="'.route('security.private-key.index').'"')
->toContain('Back to the main menu');
});
test('the admin link belongs to the main menu, not to submenus', function () {
config()->set('app.env', 'local');
$root = User::factory()->create(['id' => 0]);
test()->team->members()->attach($root->id, ['role' => 'owner']);
test()->actingAs($root);
session(['currentTeam' => test()->team]);
expect(renderNavbarAt('/team'))
->toMatch('#<li\s+x-show="!submenu"\s+x-cloak[^>]*>\s*<a title="Admin"#');
});
test('shared variables open their own submenu', function () {
loginNavbarUser('owner');
expect(renderNavbarAt('/shared-variables/servers'))
->toContain('Back to the main menu')
->toContain('href="'.route('shared-variables.team.index').'"')
->toMatch('#aria-current="page"[^>]*>\s*<svg[^>]*>.*?</svg>\s*<span[^>]*>Servers</span>#s');
});
test('instance settings open their own submenu for instance admins', function () {
config()->set('app.env', 'local');
$root = User::factory()->create(['id' => 0]);
test()->team->members()->attach($root->id, ['role' => 'owner']);
test()->actingAs($root);
session(['currentTeam' => test()->team]);
expect(renderNavbarAt('/settings/email'))
->toContain('Back to the main menu')
->toContain('href="'.route('settings.oauth').'"')
->toMatch('#aria-current="page"[^>]*>\s*<svg[^>]*>.*?</svg>\s*<span[^>]*>Email</span>#s');
});
test('a parent entry opens its submenu in place on mobile and desktop', function () {
loginNavbarUser('owner');
foreach ([renderNavbarAt('/servers', mobile: true), renderNavbarAt('/servers')] as $html) {
expect($html)
->toContain('x-show="submenu === \'Monitoring\'"')
->toMatch('#<button type="button" title="Monitoring" @click="submenu = \'Monitoring\'#')
->not->toMatch('#<a title="Monitoring"#');
}
});