mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-14 02:15:51 +00:00
feat(server): add dedicated server creation flow
This commit is contained in:
1 parent
18f0f7f6be
commit
7236cb8228
31 files changed
+1158
-352
No files matched your search
@@ -81,7 +81,7 @@ test('admin sees add server button on dashboard', function () {
|
||||
createServerWithKeyForTeam($team);
|
||||
|
||||
Livewire::test(Dashboard::class)
|
||||
->assertSee('New Server');
|
||||
->assertSee(route('server.create'));
|
||||
});
|
||||
|
||||
test('member does not see add server button on dashboard', function () {
|
||||
@@ -93,5 +93,5 @@ test('member does not see add server button on dashboard', function () {
|
||||
createServerWithKeyForTeam($team);
|
||||
|
||||
Livewire::test(Dashboard::class)
|
||||
->assertDontSee('New Server');
|
||||
->assertDontSee(route('server.create'));
|
||||
});
|
||||
@@ -1,7 +1,10 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\PreventRequestsDuringMaintenance;
|
||||
use App\Livewire\Server\Create as ServerCreate;
|
||||
use App\Livewire\Server\Index as ServerIndex;
|
||||
use App\Livewire\Server\Navbar as ServerNavbar;
|
||||
use App\Models\CloudProviderToken;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
@@ -132,6 +135,144 @@ test('member cannot create server', function () {
|
||||
expect(auth()->user()->can('create', Server::class))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can access new server page', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->get(route('server.create'))
|
||||
->assertSuccessful()
|
||||
->assertSee('New Server')
|
||||
->assertSeeLivewire(ServerCreate::class);
|
||||
});
|
||||
|
||||
test('new server chooser lists providers before rendering a creation form', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServerCreate::class)
|
||||
->assertSee('Hetzner')
|
||||
->assertSee('Vultr')
|
||||
->assertSee('DigitalOcean')
|
||||
->assertSee('Manual')
|
||||
->assertDontSee('>Select<', false)
|
||||
->assertDontSee('Continue')
|
||||
->assertSee(route('server.create.type', ['type' => 'hetzner']))
|
||||
->assertSee(route('server.create.type', ['type' => 'vultr']))
|
||||
->assertSee(route('server.create.type', ['type' => 'digital-ocean']))
|
||||
->assertSee(route('server.create.type', ['type' => 'manual']))
|
||||
->assertDontSee('Add Server by IP Address');
|
||||
});
|
||||
|
||||
test('new server chooser uses compact mobile provider cards', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServerCreate::class)
|
||||
->assertSee('mx-auto flex w-full max-w-7xl flex-col', false)
|
||||
->assertSee('sm:grid', false)
|
||||
->assertSee('sm:grid-cols-2', false)
|
||||
->assertSee('xl:grid-cols-4', false)
|
||||
->assertSee('gap-3 sm:gap-6', false)
|
||||
->assertSee('dark:hover:border-warning', false)
|
||||
->assertSee('focus-visible:border-coollabs', false)
|
||||
->assertSee('aria-label="Choose Hetzner"', false)
|
||||
->assertSee('p-3 sm:p-6', false)
|
||||
->assertDontSee('sm:min-h-80', false)
|
||||
->assertSee('size-9 sm:size-14', false)
|
||||
->assertDontSee('size-9 sm:size-14 w-14 h-14', false)
|
||||
->assertSee('hidden sm:block', false)
|
||||
->assertDontSee('>Select<', false)
|
||||
->assertDontSee('<button', false);
|
||||
});
|
||||
|
||||
test('new server provider pages render the selected creation flow', function (string $type, string $heading) {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$response = $this->get(route('server.create.type', ['type' => $type]))
|
||||
->assertSuccessful()
|
||||
->assertSee($heading)
|
||||
->assertDontSee('<h1>New Server</h1>', false)
|
||||
->assertDontSee('Choose how to add your server');
|
||||
|
||||
$response->assertSeeInOrder([$heading, 'Back']);
|
||||
})->with([
|
||||
['hetzner', 'Hetzner'],
|
||||
['vultr', 'Vultr'],
|
||||
['digital-ocean', 'DigitalOcean'],
|
||||
['manual', 'Manual'],
|
||||
]);
|
||||
|
||||
test('new server provider pages do not show the new token action in the header', function (string $type, string $heading) {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$response = $this->get(route('server.create.type', ['type' => $type]))
|
||||
->assertSuccessful()
|
||||
->assertDontSee('+ New Token')
|
||||
->assertDontSee('+ Add New Token');
|
||||
|
||||
$response->assertSeeInOrder([$heading, 'Back']);
|
||||
})->with([
|
||||
['hetzner', 'Hetzner'],
|
||||
['vultr', 'Vultr'],
|
||||
['digital-ocean', 'DigitalOcean'],
|
||||
]);
|
||||
|
||||
test('new server provider pages show the new token action in the header when tokens exist', function (string $type, string $provider, string $heading, string $modalTitle) {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
CloudProviderToken::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'provider' => $provider,
|
||||
]);
|
||||
|
||||
$response = $this->get(route('server.create.type', ['type' => $type]))
|
||||
->assertSuccessful()
|
||||
->assertSee($modalTitle);
|
||||
|
||||
$response->assertSeeInOrder([$heading, 'Back', '+ New Token']);
|
||||
})->with([
|
||||
['hetzner', 'hetzner', 'Hetzner', 'Add Hetzner Token'],
|
||||
['vultr', 'vultr', 'Vultr', 'Add Vultr Token'],
|
||||
['digital-ocean', 'digitalocean', 'DigitalOcean', 'Add DigitalOcean Token'],
|
||||
]);
|
||||
|
||||
test('new server manual page does not show the new token action', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->get(route('server.create.type', ['type' => 'manual']))
|
||||
->assertSuccessful()
|
||||
->assertSee('Manual')
|
||||
->assertDontSee('+ New Token');
|
||||
});
|
||||
|
||||
test('member cannot access new server page', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->get(route('server.create'))
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
test('server index links admins to new server page', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServerIndex::class)
|
||||
->assertSee(route('server.create'));
|
||||
});
|
||||
|
||||
test('server index does not link members to new server page', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServerIndex::class)
|
||||
->assertDontSee(route('server.create'));
|
||||
});
|
||||
|
||||
// --- Cross-team isolation ---
|
||||
|
||||
test('user from different team cannot view server', function () {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use App\Livewire\Server\New\ByDigitalOcean;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
config([
|
||||
'cache.default' => 'array',
|
||||
'session.driver' => 'array',
|
||||
]);
|
||||
|
||||
InstanceSettings::unguarded(fn () => InstanceSettings::query()->create([
|
||||
'id' => 0,
|
||||
]));
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
$this->user = User::factory()->create();
|
||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||
|
||||
$this->actingAs($this->user);
|
||||
session(['currentTeam' => $this->team]);
|
||||
});
|
||||
|
||||
it('renders only the full width buy button at the bottom of the DigitalOcean form', function () {
|
||||
Livewire::test(ByDigitalOcean::class)
|
||||
->set('current_step', 2)
|
||||
->assertDontSee('wire:click="previousStep"', false)
|
||||
->assertSeeHtml('class="button w-full"')
|
||||
->assertSee('Buy & Create Server', false);
|
||||
});
|
||||
@@ -82,6 +82,13 @@ function githubPrivateRepositoryTestPrivateKeyForTeam(Team $team): PrivateKey
|
||||
}
|
||||
|
||||
describe('GitHub Private Repository Component', function () {
|
||||
test('github app selection uses coolbox loading border', function () {
|
||||
Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
|
||||
->assertSee('wire:loading.class="coolbox-loading"', false)
|
||||
->assertSee('wire:target="loadRepositories('.$this->githubApp->id.')"', false)
|
||||
->assertDontSee('loading-spinner', false);
|
||||
});
|
||||
|
||||
test('loadRepositories fetches and displays repositories', function () {
|
||||
$repos = [
|
||||
['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']],
|
||||
|
||||
@@ -290,10 +290,9 @@ describe('Hetzner data loading', function () {
|
||||
], 200),
|
||||
]);
|
||||
|
||||
$component = Livewire::test(ByHetzner::class)
|
||||
->set('selected_token_id', $this->hetznerToken->id)
|
||||
->call('nextStep')
|
||||
->assertSet('current_step', 2);
|
||||
$component = Livewire::test(ByHetzner::class, ['selectedTokenUuid' => $this->hetznerToken->uuid])
|
||||
->assertSet('current_step', 2)
|
||||
->call('loadHetznerData');
|
||||
|
||||
expect($component->get('hetznerFirewalls'))->toHaveCount(1)
|
||||
->and($component->get('hetznerNetworks'))->toHaveCount(1);
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
use App\Livewire\Server\CreatePage;
|
||||
use App\Livewire\Server\New\ByDigitalOcean;
|
||||
use App\Livewire\Server\New\ByHetzner;
|
||||
use App\Livewire\Server\New\ByVultr;
|
||||
use App\Models\CloudProviderToken;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::unguarded(fn () => InstanceSettings::updateOrCreate(['id' => 0], ['id' => 0]));
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
$this->admin = User::factory()->create();
|
||||
$this->admin->teams()->attach($this->team, ['role' => 'admin']);
|
||||
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
});
|
||||
|
||||
test('provider token selection redirects to a token specific server creation url', function (string $component, string $provider, string $type) {
|
||||
$token = CloudProviderToken::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'provider' => $provider,
|
||||
]);
|
||||
|
||||
Livewire::test($component)
|
||||
->call('selectToken', $token->id)
|
||||
->assertRedirect(route('server.create.token', [
|
||||
'type' => $type,
|
||||
'token_uuid' => $token->uuid,
|
||||
]));
|
||||
})->with([
|
||||
'hetzner' => [ByHetzner::class, 'hetzner', 'hetzner'],
|
||||
'vultr' => [ByVultr::class, 'vultr', 'vultr'],
|
||||
'digital-ocean' => [ByDigitalOcean::class, 'digitalocean', 'digital-ocean'],
|
||||
]);
|
||||
|
||||
test('provider token selection is rendered as clickable token boxes', function (string $component, string $provider, string $type, string $tokenName) {
|
||||
$token = CloudProviderToken::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'provider' => $provider,
|
||||
'name' => $tokenName,
|
||||
]);
|
||||
|
||||
Livewire::test($component)
|
||||
->assertSee($tokenName)
|
||||
->assertSee(route('server.create.token', ['type' => $type, 'token_uuid' => $token->uuid]))
|
||||
->assertDontSee('loadingProviderTokenId', false)
|
||||
->assertDontSee('coolbox-loading', false)
|
||||
->assertSee('coolbox', false)
|
||||
->assertDontSee('wire:click="selectToken(', false)
|
||||
->assertDontSee('loading-spinner', false)
|
||||
->assertDontSee('Loading Hetzner details...')
|
||||
->assertDontSee('Loading Vultr details...')
|
||||
->assertDontSee('Loading DigitalOcean details...')
|
||||
->assertDontSee('Select a saved token')
|
||||
->assertDontSee('You need a saved')
|
||||
->assertDontSee('Continue');
|
||||
})->with([
|
||||
'hetzner' => [ByHetzner::class, 'hetzner', 'hetzner', 'Production Hetzner'],
|
||||
'vultr' => [ByVultr::class, 'vultr', 'vultr', 'Production Vultr'],
|
||||
'digital-ocean' => [ByDigitalOcean::class, 'digitalocean', 'digital-ocean', 'Production DigitalOcean'],
|
||||
]);
|
||||
|
||||
test('provider token selection shows an add token box when no tokens exist', function (string $component, string $title, string $description) {
|
||||
Livewire::test($component)
|
||||
->assertSee($title)
|
||||
->assertSee($description)
|
||||
->assertSee('max-w-2xl', false)
|
||||
->assertSee('coolbox', false)
|
||||
->assertSee('M12 4.5v15m7.5-7.5h-15', false)
|
||||
->assertDontSee('wire:click="selectToken(', false);
|
||||
})->with([
|
||||
'hetzner' => [ByHetzner::class, 'Add a new token', 'Add a Hetzner API token to create servers from your account.'],
|
||||
'vultr' => [ByVultr::class, 'Add a new token', 'Add a Vultr API token to create servers from your account.'],
|
||||
'digital-ocean' => [ByDigitalOcean::class, 'Add a new token', 'Add a DigitalOcean API token to create Droplets from your account.'],
|
||||
]);
|
||||
|
||||
test('provider token urls pass the token uuid into the server creation page', function () {
|
||||
$token = CloudProviderToken::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'provider' => 'hetzner',
|
||||
]);
|
||||
|
||||
expect(route('server.create.token', [
|
||||
'type' => 'hetzner',
|
||||
'token_uuid' => $token->uuid,
|
||||
]))->toEndWith('/servers/new/hetzner/'.$token->uuid);
|
||||
|
||||
Livewire::test(CreatePage::class, [
|
||||
'type' => 'hetzner',
|
||||
'token_uuid' => $token->uuid,
|
||||
])
|
||||
->assertSet('type', 'hetzner')
|
||||
->assertSet('token_uuid', $token->uuid)
|
||||
->assertSet('title', 'Hetzner');
|
||||
});
|
||||
|
||||
test('provider token pages defer provider api data loading until wire init', function (string $component, string $provider, string $type, string $loadingText, string $wireInitCall) {
|
||||
$token = CloudProviderToken::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'provider' => $provider,
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'*' => Http::response([], 200),
|
||||
]);
|
||||
|
||||
Livewire::test($component, ['selectedTokenUuid' => $token->uuid])
|
||||
->assertSet('current_step', 2)
|
||||
->assertSet('loading_data', true)
|
||||
->assertSee($loadingText)
|
||||
->assertSee($wireInitCall, false)
|
||||
->assertSee('text-coollabs dark:text-warning animate-spin', false)
|
||||
->assertDontSee('border-b-2 border-primary', false);
|
||||
|
||||
Http::assertNothingSent();
|
||||
})->with([
|
||||
'hetzner' => [ByHetzner::class, 'hetzner', 'hetzner', 'Loading Hetzner data...', 'wire:init="loadHetznerData"'],
|
||||
'vultr' => [ByVultr::class, 'vultr', 'vultr', 'Loading Vultr data...', 'wire:init="loadVultrData"'],
|
||||
'digital-ocean' => [ByDigitalOcean::class, 'digitalocean', 'digital-ocean', 'Loading DigitalOcean data...', 'wire:init="loadDigitalOceanData"'],
|
||||
]);
|
||||
|
||||
test('back button on token specific provider creation pages returns to provider token selection', function (string $provider, string $type) {
|
||||
$token = CloudProviderToken::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'provider' => $provider,
|
||||
]);
|
||||
|
||||
Livewire::test(CreatePage::class, [
|
||||
'type' => $type,
|
||||
'token_uuid' => $token->uuid,
|
||||
])
|
||||
->assertSee(route('server.create.type', ['type' => $type]), false)
|
||||
->assertDontSee('href="'.route('server.create').'"', false);
|
||||
})->with([
|
||||
'hetzner' => ['hetzner', 'hetzner'],
|
||||
'vultr' => ['vultr', 'vultr'],
|
||||
'digital-ocean' => ['digitalocean', 'digital-ocean'],
|
||||
]);
|
||||
|
||||
test('new token header button is hidden on token specific provider creation pages', function (string $provider, string $type) {
|
||||
$token = CloudProviderToken::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'provider' => $provider,
|
||||
]);
|
||||
|
||||
Livewire::test(CreatePage::class, [
|
||||
'type' => $type,
|
||||
])
|
||||
->assertSee('+ New Token');
|
||||
|
||||
Livewire::test(CreatePage::class, [
|
||||
'type' => $type,
|
||||
'token_uuid' => $token->uuid,
|
||||
])
|
||||
->assertDontSee('+ New Token');
|
||||
})->with([
|
||||
'hetzner' => ['hetzner', 'hetzner'],
|
||||
'vultr' => ['vultr', 'vultr'],
|
||||
'digital-ocean' => ['digitalocean', 'digital-ocean'],
|
||||
]);
|
||||
@@ -75,6 +75,19 @@ it('caches parsed local service templates by bundle mtime', function () {
|
||||
expect($first->keys()->all())->toBe($second->keys()->all());
|
||||
});
|
||||
|
||||
it('renders the shared loading indicator while resource choices load', function () {
|
||||
View::share('errors', new ViewErrorBag);
|
||||
|
||||
$view = $this->view('livewire.project.new.select', [
|
||||
'current_step' => 'type',
|
||||
'environments' => collect(),
|
||||
]);
|
||||
|
||||
$view->assertSee('Loading resources...', false);
|
||||
$view->assertSee('text-coollabs dark:text-warning animate-spin', false);
|
||||
$view->assertDontSee('<div x-show="loading">Loading...</div>', false);
|
||||
});
|
||||
|
||||
it('renders the service templates last updated hint placeholder', function () {
|
||||
View::share('errors', new ViewErrorBag);
|
||||
|
||||
|
||||
@@ -102,9 +102,7 @@ it('creates a Vultr server through the Livewire flow', function () {
|
||||
], 202),
|
||||
]);
|
||||
|
||||
Livewire::test(ByVultr::class)
|
||||
->set('selected_token_id', $this->vultrToken->id)
|
||||
->call('nextStep')
|
||||
Livewire::test(ByVultr::class, ['selectedTokenUuid' => $this->vultrToken->uuid])
|
||||
->assertSet('current_step', 2)
|
||||
->set('server_name', 'test-vultr-server')
|
||||
->set('selected_region', 'ewr')
|
||||
@@ -148,3 +146,22 @@ it('requires IPv6 when public IPv4 is disabled', function () {
|
||||
->call('submit')
|
||||
->assertHasErrors(['enable_ipv6']);
|
||||
});
|
||||
|
||||
it('uses the shared dropdown UI for advanced Vultr options', function () {
|
||||
Livewire::test(ByVultr::class)
|
||||
->set('current_step', 2)
|
||||
->assertSee('Advanced Vultr options')
|
||||
->assertSeeHtml('dropdownOpen')
|
||||
->assertSeeHtml('x-ref="panel"')
|
||||
->assertSee('Additional SSH Keys (from Vultr)')
|
||||
->assertSee('Network Configuration')
|
||||
->assertSee('Cloud-Init Script');
|
||||
});
|
||||
|
||||
it('renders only the full width buy button at the bottom of the Vultr form', function () {
|
||||
Livewire::test(ByVultr::class)
|
||||
->set('current_step', 2)
|
||||
->assertDontSee('wire:click="previousStep"', false)
|
||||
->assertSeeHtml('class="button w-full"')
|
||||
->assertSee('Buy & Create Server', false);
|
||||
});
|
||||
@@ -6,11 +6,24 @@ it('allows selecting Vultr in cloud provider token forms', function () {
|
||||
|
||||
expect($view)->toContain('<x-forms.select required id="provider" label="Provider" wire:model.live="provider">')
|
||||
->and($view)->toContain('<option value="vultr">Vultr</option>')
|
||||
->and($view)->toContain('<option disabled value="digitalocean">DigitalOcean</option>')
|
||||
->and($view)->toContain('<option value="digitalocean">DigitalOcean</option>')
|
||||
->and(substr_count($view, 'Open Account → API Access.'))->toBe(2)
|
||||
->and(substr_count($view, 'https://console.vultr.com/user/apiaccess/'))->toBe(2)
|
||||
->and($view)->not->toContain('<option value="digitalocean">DigitalOcean</option>')
|
||||
->and($view)->not->toContain('cloudProviderTokens->where(\'provider\', $provider)->isEmpty()')
|
||||
->and($view)->not->toContain('<x-forms.select required id="provider" label="Provider" disabled>')
|
||||
->and($component)->toContain("'provider' => 'required|string|in:hetzner,digitalocean,vultr'");
|
||||
});
|
||||
|
||||
it('keeps provider affiliate links on server provider views', function () {
|
||||
$tokenFormView = file_get_contents(__DIR__.'/../../resources/views/livewire/security/cloud-provider-token-form.blade.php');
|
||||
$hetznerView = file_get_contents(__DIR__.'/../../resources/views/livewire/server/new/by-hetzner.blade.php');
|
||||
$vultrView = file_get_contents(__DIR__.'/../../resources/views/livewire/server/new/by-vultr.blade.php');
|
||||
$digitalOceanView = file_get_contents(__DIR__.'/../../resources/views/livewire/server/new/by-digital-ocean.blade.php');
|
||||
|
||||
expect($tokenFormView)
|
||||
->not->toContain('https://coolify.io/hetzner')
|
||||
->not->toContain('https://coolify.io/vultr')
|
||||
->and($hetznerView)->toContain('https://coolify.io/hetzner')
|
||||
->and($vultrView)->toContain('https://coolify.io/vultr')
|
||||
->and($digitalOceanView)->toContain('https://coolify.io/digitalocean');
|
||||
});
|
||||
@@ -33,6 +33,19 @@ it('ensures GlobalSearch uses redirect route helper', function () {
|
||||
->toContain('redirectRoute($this, \'project.resource.create\'');
|
||||
});
|
||||
|
||||
it('routes new server quick action to the new server page', function () {
|
||||
$globalSearchFile = file_get_contents(__DIR__.'/../../app/Livewire/GlobalSearch.php');
|
||||
$bladeFile = file_get_contents(__DIR__.'/../../resources/views/livewire/global-search.blade.php');
|
||||
|
||||
expect($globalSearchFile)
|
||||
->toContain("if (\$type === 'server')")
|
||||
->toContain("redirectRoute(\$this, 'server.create')");
|
||||
|
||||
expect($bladeFile)
|
||||
->toContain("window.location.href = '/servers/new'")
|
||||
->not->toContain('@open-create-modal-server.window');
|
||||
});
|
||||
|
||||
it('ensures docker-image item has quickcommand with new image', function () {
|
||||
$globalSearchFile = file_get_contents(__DIR__.'/../../app/Livewire/GlobalSearch.php');
|
||||
|
||||
@@ -142,3 +155,13 @@ it('uses cropped image assets instead of inline wide logos for GlobalSearch data
|
||||
|
||||
expect($globalSearchFile)->not->toContain("'logo_html' =>");
|
||||
});
|
||||
|
||||
it('uses rounded yellow plus icons for GlobalSearch creatable actions without logos', function () {
|
||||
$bladeFile = file_get_contents(__DIR__.'/../../resources/views/livewire/global-search.blade.php');
|
||||
|
||||
expect($bladeFile)
|
||||
->toContain('rounded-full bg-warning/20 flex items-center justify-center')
|
||||
->toContain('class="h-6 w-6 text-warning"')
|
||||
->not->toContain('rounded-lg bg-warning-100 dark:bg-warning-900/40')
|
||||
->not->toContain('text-warning-600 dark:text-warning-400');
|
||||
});
|
||||
Reference in new issue
Block a user