diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index c69863c572..f24b9699f2 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -81,7 +81,7 @@ class CreateNewUser implements CreatesNewUsers } } // Set session variable - session(['currentTeam' => $user->currentTeam = $team]); + session(['currentTeam' => $team]); return $user; } diff --git a/app/Livewire/Project/Database/BackupNow.php b/app/Livewire/Project/Database/BackupNow.php deleted file mode 100644 index 39a1960119..0000000000 --- a/app/Livewire/Project/Database/BackupNow.php +++ /dev/null @@ -1,40 +0,0 @@ -authorize('manageBackups', $this->backup->database); - - $database = $this->backup->database->refresh(); - if ($database->id !== 0 && ! str($database->status)->startsWith('running')) { - $this->dispatch('error', 'The database must be running to start a backup.'); - - return; - } - - DatabaseBackupJob::dispatch($this->backup); - $database = $this->backup->database; - auditLog('ui.database.backup_started', [ - 'team_id' => $database->team()?->id, - 'database_uuid' => $database->uuid, - 'database_name' => $database->name, - 'backup_uuid' => $this->backup->uuid, - ]); - $this->dispatch('success', 'Backup queued. It will be available in a few minutes.'); - } catch (\Throwable $e) { - return handleError($e, $this); - } - } -} diff --git a/app/Livewire/Project/New/EmptyProject.php b/app/Livewire/Project/New/EmptyProject.php deleted file mode 100644 index b2187c6155..0000000000 --- a/app/Livewire/Project/New/EmptyProject.php +++ /dev/null @@ -1,25 +0,0 @@ -authorize('create', Project::class); - - $project = Project::create([ - 'name' => generate_random_name(), - 'team_id' => currentTeam()->id, - 'uuid' => new_public_id(), - ]); - - return redirectRoute($this, 'project.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $project->environments->first()->uuid]); - } -} diff --git a/app/Livewire/Project/Service/EditDomain.php b/app/Livewire/Project/Service/EditDomain.php deleted file mode 100644 index f099891534..0000000000 --- a/app/Livewire/Project/Service/EditDomain.php +++ /dev/null @@ -1,168 +0,0 @@ - ValidationPatterns::applicationDomainRules(), - ]; - } - - public function mount() - { - $this->application = ServiceApplication::ownedByCurrentTeam()->findOrFail($this->applicationId); - $this->authorize('view', $this->application); - $this->requiredPort = $this->application->getRequiredPort(); - $this->syncData(); - } - - private function syncData(bool $toModel = false): void - { - if ($toModel) { - $this->validate(); - - // Sync to model - $this->application->setEditableUrls($this->fqdn); - - $this->application->save(); - } else { - // Sync from model - $this->fqdn = $this->application->url; - } - } - - public function confirmDomainUsage() - { - $this->forceSaveDomains = true; - $this->showDomainConflictModal = false; - $this->submit(); - } - - public function confirmRemovePort() - { - $this->forceRemovePort = true; - $this->showPortWarningModal = false; - $this->submit(); - } - - public function cancelRemovePort() - { - $this->showPortWarningModal = false; - $this->syncData(); // Reset to original FQDN - } - - public function submit() - { - try { - $persistedApplication = $this->application->fresh(); - $previousEditableUrls = $persistedApplication->url; - $previousFqdn = $persistedApplication->fqdn; - $previousPortOverrides = $persistedApplication->domain_port_overrides; - $this->authorize('update', $this->application); - $this->validate(); - - $this->fqdn = ValidationPatterns::normalizeApplicationDomains($this->fqdn); - $warning = sslipDomainWarning($this->fqdn); - if ($warning) { - $this->dispatch('warning', __('warning.sslipdomain')); - } - // Sync to model for domain conflict check (without validation) - $this->application->setEditableUrls($this->fqdn); - // Check for domain conflicts if not forcing save - if (! $this->forceSaveDomains) { - $result = checkDomainUsage(resource: $this->application); - if ($result['hasConflicts']) { - $this->domainConflicts = $result['conflicts']; - $this->showDomainConflictModal = true; - - return; - } - } else { - // Reset the force flag after using it - $this->forceSaveDomains = false; - } - - // Check for required port - if (! $this->forceRemovePort) { - $requiredPort = $this->application->getRequiredPort(); - - if ($requiredPort !== null) { - foreach (str($this->fqdn)->trim()->explode(',') as $fqdn) { - $fqdn = trim((string) $fqdn); - if ($fqdn === '') { - continue; - } - - if ($this->application->portRequiresConfirmation($fqdn, $requiredPort, $previousEditableUrls)) { - $this->requiredPort = $requiredPort; - $this->showPortWarningModal = true; - $this->application->fqdn = $previousFqdn; - $this->application->domain_port_overrides = $previousPortOverrides; - - return; - } - } - } - } else { - // Reset the force flag after using it - $this->forceRemovePort = false; - } - - $this->validate(); - $this->application->save(); - $this->application->refresh(); - $this->syncData(); - updateCompose($this->application); - if (str($this->application->fqdn)->contains(',')) { - $this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.

Only use multiple domains if you know what you are doing.'); - } - $this->application->service->parse(); - $this->dispatch('refresh'); - $this->dispatch('refreshServices'); - $this->dispatch('configurationChanged'); - } catch (\Throwable $e) { - $originalFqdn = $this->application->getOriginal('fqdn'); - if ($originalFqdn !== $this->application->fqdn) { - $this->application->fqdn = $originalFqdn; - $this->syncData(); - } - - return handleError($e, $this); - } - } - - public function render() - { - return view('livewire.project.service.edit-domain'); - } -} diff --git a/app/Livewire/Project/Shared/UploadConfig.php b/app/Livewire/Project/Shared/UploadConfig.php deleted file mode 100644 index 0f08946879..0000000000 --- a/app/Livewire/Project/Shared/UploadConfig.php +++ /dev/null @@ -1,48 +0,0 @@ -config = '{ - "build_pack": "nixpacks", - "base_directory": "/nodejs", - "publish_directory": "/", - "ports_exposes": "3000", - "settings": { - "is_static": false - } -}'; - } - } - - public function uploadConfig() - { - try { - $application = Application::ownedByCurrentTeam()->findOrFail($this->applicationId); - $this->authorize('update', $application); - $application->setConfig($this->config); - $this->dispatch('success', 'Application settings updated'); - } catch (\Throwable $e) { - return handleError($e, $this); - } - } - - public function render() - { - return view('livewire.project.shared.upload-config'); - } -} diff --git a/app/Livewire/Tags/Deployments.php b/app/Livewire/Tags/Deployments.php deleted file mode 100644 index d5c26e32c9..0000000000 --- a/app/Livewire/Tags/Deployments.php +++ /dev/null @@ -1,39 +0,0 @@ -whereIn('id', $this->resourceIds)->pluck('id'); - $this->deploymentsPerTagPerServer = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('application_id', $applicationIds)->get([ - 'id', - 'application_id', - 'application_name', - 'deployment_url', - 'pull_request_id', - 'server_name', - 'server_id', - 'status', - ])->sortBy('id')->groupBy('server_name')->toArray(); - $this->dispatch('deployments', $this->deploymentsPerTagPerServer); - } catch (\Exception $e) { - return handleError($e, $this); - } - } -} diff --git a/app/Livewire/Team/Storage/Show.php b/app/Livewire/Team/Storage/Show.php deleted file mode 100644 index d3051afd42..0000000000 --- a/app/Livewire/Team/Storage/Show.php +++ /dev/null @@ -1,24 +0,0 @@ -storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->first(); - if (! $this->storage) { - abort(404); - } - } - - public function render() - { - return view('livewire.storage.show'); - } -} diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index 6426860187..50df845f7b 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -27,7 +27,7 @@ class FortifyServiceProvider extends ServiceProvider public function toResponse($request) { // First user (root) will be redirected to /settings instead of / on registration. - if ($request->user()->currentTeam->id === 0) { + if ($request->user()->currentTeam()?->id === 0) { return redirect()->route('settings.index'); } @@ -88,9 +88,9 @@ class FortifyServiceProvider extends ServiceProvider if (! $user->teams()->where('team_id', $invitation->team->id)->exists()) { $user->teams()->attach($invitation->team->id, ['role' => $invitation->role]); } - $user->currentTeam = $invitation->team; + $team = $invitation->team; $invitation->delete(); - session(['currentTeam' => $user->currentTeam]); + session(['currentTeam' => $team]); } else { // Restore the last active team; only fall back when unambiguous. $team = $user->resolveStoredTeam(); @@ -98,7 +98,7 @@ class FortifyServiceProvider extends ServiceProvider $team = $user->recreate_personal_team(); } if ($team) { - session(['currentTeam' => $user->currentTeam = $team]); + session(['currentTeam' => $team]); } // Otherwise (multiple teams, no stored choice) leave the session // team unset so the user is sent to the team-selection screen. diff --git a/app/Services/Auth/OauthLoginService.php b/app/Services/Auth/OauthLoginService.php index 761b6a8ccb..5915b0cd8a 100644 --- a/app/Services/Auth/OauthLoginService.php +++ b/app/Services/Auth/OauthLoginService.php @@ -29,7 +29,7 @@ class OauthLoginService : $this->resolveOauthUser($oauthUser, $oauthSetting, $email); $team = $user->currentTeam() ?? $user->teams()->first() ?? $user->recreate_personal_team(); - session(['currentTeam' => $user->currentTeam = $team]); + session(['currentTeam' => $team]); if ($this->requiresTwoFactorChallenge($user)) { Auth::logout(); diff --git a/app/View/Components/Forms/Select.php b/app/View/Components/Forms/Select.php deleted file mode 100644 index 327c33da65..0000000000 --- a/app/View/Components/Forms/Select.php +++ /dev/null @@ -1,71 +0,0 @@ -canGate && $this->canResource && $this->autoDisable) { - $hasPermission = Gate::allows($this->canGate, $this->canResource); - - if (! $hasPermission) { - $this->disabled = true; - } - } - } - - /** - * Get the view / contents that represent the component. - */ - public function render(): View|Closure|string - { - // Store original ID for wire:model binding (property name) - $this->modelBinding = $this->id; - - if (is_null($this->id)) { - $this->id = new_public_id(); - // Don't create wire:model binding for auto-generated IDs - $this->modelBinding = 'null'; - } - - // Generate unique HTML ID by adding random suffix - // This prevents duplicate IDs when multiple forms are on the same page - if ($this->modelBinding && $this->modelBinding !== 'null') { - // Use original ID with random suffix for uniqueness - $uniqueSuffix = new_public_id(); - $this->htmlId = $this->modelBinding.'-'.$uniqueSuffix; - } else { - $this->htmlId = (string) $this->id; - } - - if (is_null($this->name)) { - $this->name = $this->modelBinding !== 'null' ? $this->modelBinding : (string) $this->id; - } - - return view('components.forms.select'); - } -} diff --git a/app/View/Components/Modal.php b/app/View/Components/Modal.php deleted file mode 100644 index 7e254ebdcd..0000000000 --- a/app/View/Components/Modal.php +++ /dev/null @@ -1,34 +0,0 @@ -complexStatus = $service->status; - } - - /** - * Get the view / contents that represent the component. - */ - public function render(): View|Closure|string - { - return view('components.status.services'); - } -} diff --git a/resources/views/components/applications/advanced.blade.php b/resources/views/components/applications/advanced.blade.php deleted file mode 100644 index 8bd169b015..0000000000 --- a/resources/views/components/applications/advanced.blade.php +++ /dev/null @@ -1,4 +0,0 @@ -{{-- Application Advanced is unused. Deploy (without cache) lives on the Deploy - dropdown. Keep this file so Advanced menus stay on the name="grid" icon. --}} -@props(['application']) - diff --git a/resources/views/components/applications/deploy.blade.php b/resources/views/components/applications/deploy.blade.php deleted file mode 100644 index e5214353f2..0000000000 --- a/resources/views/components/applications/deploy.blade.php +++ /dev/null @@ -1,79 +0,0 @@ -@props(['application']) - -@php - $canDeploy = auth()->user()->can('deploy', $application); - $isExited = str($application->status)->startsWith('exited'); - $isSwarm = $application->destination->server->isSwarm(); - $isCompose = $application->build_pack === 'dockercompose'; - $withoutCacheAction = $application->status === 'running' ? 'force_deploy_without_cache' : 'deploy(true)'; -@endphp - -@if ($isExited) - @if ($isSwarm) - - @else -
- - - -
- @endif -@elseif (! $isSwarm) -
- - - -
-@elseif (! $isCompose) - -@endif diff --git a/resources/views/components/confirm-modal.blade.php b/resources/views/components/confirm-modal.blade.php deleted file mode 100644 index a0cd8d01ee..0000000000 --- a/resources/views/components/confirm-modal.blade.php +++ /dev/null @@ -1,30 +0,0 @@ - -
-
-
-
-
-
- Confirm - Cancel -
-
-
-
diff --git a/resources/views/components/dropdown.blade.php b/resources/views/components/dropdown.blade.php deleted file mode 100644 index e6d4c0af5d..0000000000 --- a/resources/views/components/dropdown.blade.php +++ /dev/null @@ -1,84 +0,0 @@ -@props([ - 'inline' => false, - 'triggerClass' => '', - 'panelClass' => '', -]) - -
$inline]) @click.outside="if (! {{ $inline ? 'true' : 'false' }}) close()" x-on:resize.window="if (dropdownOpen) updatePanelPosition()"> - - -
true, - 'mt-1 w-full' => $inline, - 'absolute top-full z-50 mt-1 min-w-max max-w-[calc(100vw-1rem)] md:top-0 md:mt-6' => ! $inline, - ]) x-cloak> -
! $inline, - 'border-0 bg-transparent shadow-none dark:border-0 dark:bg-transparent' => $inline, - $panelClass, - ])> - {{ $slot }} -
-
-
diff --git a/resources/views/components/forms/domain-chips.blade.php b/resources/views/components/forms/domain-chips.blade.php deleted file mode 100644 index 7148eb7d28..0000000000 --- a/resources/views/components/forms/domain-chips.blade.php +++ /dev/null @@ -1,91 +0,0 @@ -@props([ - 'label' => 'Domains', - 'helper' => 'Add one domain per entry. Press Enter (or type a comma) to add it. You can specify a path and a port to bind the domain to.

Example
- https://app.coolify.io/api/v3
- https://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.
- https://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container.', - 'model' => 'fqdn', - 'disabled' => false, - 'required' => false, - 'placeholder' => 'https://coolify.io', - 'canUpdate' => null, -]) - -@php - $canEdit = $canUpdate ?? ! $disabled; -@endphp - -
class(['w-full']) }}> - @if ($label) - - @endif -
- - -
-
diff --git a/resources/views/components/forms/select.blade.php b/resources/views/components/forms/select.blade.php deleted file mode 100644 index a085d521ca..0000000000 --- a/resources/views/components/forms/select.blade.php +++ /dev/null @@ -1,25 +0,0 @@ -
- @if ($label) -
- - @if ($helper) - - @endif -
- @endif - - @error($modelBinding) - - @enderror -
diff --git a/resources/views/components/internal-link.blade.php b/resources/views/components/internal-link.blade.php deleted file mode 100644 index cedea2a408..0000000000 --- a/resources/views/components/internal-link.blade.php +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/resources/views/components/modal.blade.php b/resources/views/components/modal.blade.php deleted file mode 100644 index 9dd8c9e9d6..0000000000 --- a/resources/views/components/modal.blade.php +++ /dev/null @@ -1,56 +0,0 @@ - - @if ($yesOrNo) - - @else - - @endif - - - diff --git a/resources/views/components/notification/navbar.blade.php b/resources/views/components/notification/navbar.blade.php deleted file mode 100644 index 25f75598b8..0000000000 --- a/resources/views/components/notification/navbar.blade.php +++ /dev/null @@ -1,8 +0,0 @@ -@props([ - 'title' => 'Notifications', - 'subtitle' => 'Delivery channels for team events', - // Topbar + channel tabs identify the page at xl+; keep the H1 on tablet. - 'titleOnDesktop' => false, -]) - - diff --git a/resources/views/components/pricing-plans.blade.php b/resources/views/components/pricing-plans.blade.php deleted file mode 100644 index 67b97e2d1d..0000000000 --- a/resources/views/components/pricing-plans.blade.php +++ /dev/null @@ -1,444 +0,0 @@ -@props([ - 'showSubscribeButtons' => true, -]) -
-
-
-
- Payment frequency - - -
-
- @if (config('constants.limits.trial_period') > 0) -
{{ config('constants.limits.trial_period') }} - days trial included on all plans, without credit card details.
- @endif -
-
Save 10% annually with the yearly plans. -
-
-
-
-
-
-
-

Unlimited Trial - Get Started -

-

Start self-hosting without limits - with - our - OSS version. Same features as the paid version, but you have to manage by yourself.

-
- -
-
For the detailed list of features, please visit our landing page: coolify.io
-
- -
-

Basic

-

- - $5 - /month + VAT - - - $4 - /month + VAT - -

- - billed monthly - - - billed annually - - @if ($showSubscribeButtons) - @isset($basic) - {{ $basic }} - @endisset - @endif -

Begin hosting your own services in the - cloud. -

-
    -
  • - - Connect 2 servers -
  • -
  • - - Included Email System -
  • -
  • - - Email Support -
  • -
  • - - - - - - - + All upcoming features -
  • -
-
-
-

Pro

-

- - $30 - /month + VAT - - - $27 - /month + VAT - -

- - billed monthly - - - billed annually - - @if ($showSubscribeButtons) - @isset($pro) - {{ $pro }} - @endisset - @endif -

Expand your business or set up your own - hosting - environment. -

-
    -
  • - - Connect 10 servers -
  • -
  • - - Included Email System -
  • -
  • - - Priority Email Support -
  • -
  • - - - - - - - + All upcoming features -
  • -
-
-
-

Ultimate

-

- - Custom - {{-- pay-as-you-go --}} - - - Custom - {{-- /month + VAT --}} - -

- - pay-as-you-go - - - pay-as-you-go - - @if ($showSubscribeButtons) - @isset($ultimate) - {{ $ultimate }} - @endisset - @endif -

Easily manage complex infrastructures in a - single location.

-
    -
  • - - Connect 10+ servers -
  • - -
  • - - Included Email System -
  • -
  • - - Priority (Email/Chat) Support -
  • -
  • - - - - - - - + All upcoming features -
  • -
-
-
- {{--
-
Need official support for - your self-hosted instance? - - Contact - Us - -
-
--}} -
- - {{--
Included in all plans
-
-
-
-
- - - -
-
Bring Your Own Servers
-
-
- Bring your own server from any cloud providers, or even your own server at home! All you need is SSH - access. You will have full control over your server, and you can even use it for other purposes. -
-
-
-
-
- - - - - - - - -
-
Server Automations
-
-
- Once you connected your server, Coolify will start managing it and do a - lot of administrative tasks for you. You can also write your own scripts to - automate your server*. -
-
-
-
-
- - - - - - -
-
No Vendor Lock-in
-
-
- You own your own data. All configurations saved on your own servers, so if - you decide to stop using Coolify, you can still continue to manage your - deployed resources. -
-
- -
-
-
- - - - - - - - -
-
Monitoring
-
-
- Coolify will automatically monitor your configured servers and deployed - resources. Notifies you if something goes wrong on your favourite - channels, like Discord, Telegram, via Email and more... -
-
-
-
-
- - - - - - -
-
Automatic Backups
-
-
- We automatically backup your databases to any S3 compatible solution. If - something goes wrong, you can easily restore your data with a few clicks. -
-
-
-
-
- - - - - -
-
Powerful API
-
-
- Programmatically deploy, query, and manage your servers & resources. - Integrate to your CI/CD pipelines, or build your own custom integrations. * -
-
-
-
-
- - - - - - - -
-
Push to Deploy
-
-
- Git integration is default today. We support hosted (github.com, - gitlab.com*) or self-hosted* - (Github Enterprise, Gitlab) Git repositories. -
-
-
-
-
- - - -
-
Pull Request Deployments
-
-
- Automagically deploy new commits and pull requests separately to quickly - review contributions and speed up your teamwork! -
-
-
-
- * Some features are work in progress and will be available soon. -
--}} -
- @isset($other) - {{ $other }} - @endisset diff --git a/resources/views/components/profile/navbar.blade.php b/resources/views/components/profile/navbar.blade.php deleted file mode 100644 index 18f4e9fe94..0000000000 --- a/resources/views/components/profile/navbar.blade.php +++ /dev/null @@ -1,7 +0,0 @@ -@props([ - 'title' => 'Profile', - 'subtitle' => 'Your account preferences', - 'titleOnDesktop' => false, -]) - - diff --git a/resources/views/components/resource-heading-overflow.blade.php b/resources/views/components/resource-heading-overflow.blade.php deleted file mode 100644 index 44554e899f..0000000000 --- a/resources/views/components/resource-heading-overflow.blade.php +++ /dev/null @@ -1,149 +0,0 @@ -{{-- - Resource header actions: render inline in the top bar, then collapse into - an Actions dropdown when the remaining header width cannot fit them. ---}} -@props([ - 'id' => null, - 'label' => 'Actions', -]) - -
class('resource-heading-overflow relative shrink-0') }} - data-resource-heading-overflow - x-data="{ - collapsed: false, - open: false, - ro: null, - onNavigated: null, - update() { - const needed = this.measureInlineWidth() - const available = this.availableWidth() - const next = needed > 0 && needed > available - if (next === this.collapsed) { - return - } - this.collapsed = next - if (!next) { - this.open = false - } - }, - hudSlot() { - return this.$el.closest('#resource-action-hud-slot') - ?? document.getElementById('resource-action-hud-slot') - }, - headerRow() { - return this.hudSlot()?.parentElement ?? null - }, - measureInlineWidth() { - const items = this.$refs.items - if (!items) { - return 0 - } - items.classList.add('is-measuring') - const width = items.scrollWidth - items.classList.remove('is-measuring') - return width - }, - availableWidth() { - const row = this.headerRow() - const hud = this.hudSlot() - if (!row || !hud) { - return Infinity - } - - let reserved = 0 - for (const child of row.children) { - if (child === hud) { - continue - } - if (child.classList.contains('flex-1') || child.querySelector(':scope .flex-1')) { - reserved += 200 - continue - } - reserved += child.getBoundingClientRect().width - } - - let hudSiblings = 0 - const cluster = this.$el.parentElement - if (cluster) { - for (const child of cluster.children) { - if (child === this.$el) { - continue - } - hudSiblings += child.getBoundingClientRect().width - } - } - - return Math.max(0, row.clientWidth - reserved - hudSiblings - 16) - }, - observe() { - if (typeof ResizeObserver === 'undefined') { - return - } - if (!this.ro) { - this.ro = new ResizeObserver(() => this.update()) - } - const row = this.headerRow() - if (row) { - this.ro.observe(row) - } - this.ro.observe(this.$el) - }, - init() { - this.onNavigated = () => this.$nextTick(() => { - this.observe() - this.update() - }) - document.addEventListener('livewire:navigated', this.onNavigated) - - this.$nextTick(() => { - this.observe() - this.update() - requestAnimationFrame(() => { - this.observe() - this.update() - }) - }) - }, - destroy() { - this.ro?.disconnect() - if (this.onNavigated) { - document.removeEventListener('livewire:navigated', this.onNavigated) - } - }, - }" - x-init="init()" - x-effect="$dispatch('resource-actions-toggled', { open })" - @click.outside="open = false" - @keydown.escape.window="open = false" - @resize.window.debounce.50ms="update()" - :class="{ 'is-collapsed': collapsed }" -> - - -
- {{ $slot }} -
-
diff --git a/resources/views/components/resource-view.blade.php b/resources/views/components/resource-view.blade.php deleted file mode 100644 index 3a4a635aa9..0000000000 --- a/resources/views/components/resource-view.blade.php +++ /dev/null @@ -1,22 +0,0 @@ -
$upgrade, - 'hover:border-neutral-300 hover:bg-neutral-50 hover:shadow-sm dark:hover:border-white/[0.12] dark:hover:bg-white/[0.035]' => ! $upgrade, -])> -
- {{ $logo }} -
-
-
- {{ $title }} -
- @if ($upgrade) -
{{ $upgrade }}
- @else -
- {{ $description }} -
- @endif -
-
diff --git a/resources/views/components/resources/breadcrumbs.blade.php b/resources/views/components/resources/breadcrumbs.blade.php deleted file mode 100644 index 52724002f1..0000000000 --- a/resources/views/components/resources/breadcrumbs.blade.php +++ /dev/null @@ -1,281 +0,0 @@ -@props([ - 'lastDeploymentInfo' => null, - 'lastDeploymentLink' => null, - 'resource' => null, - 'projects' => null, - 'environments' => null, -]) -@php - use App\Models\Project; - - // Use passed props if available, otherwise query (backwards compatible) - $projects = $projects ?? Project::ownedByCurrentTeamCached(); - $environments = $environments ?? $resource->environment->project - ->environments() - ->select('id', 'uuid', 'name', 'project_id') - ->with([ - 'applications:id,uuid,name,environment_id', - 'services:id,uuid,name,environment_id', - 'postgresqls:id,uuid,name,environment_id', - 'redis:id,uuid,name,environment_id', - 'mongodbs:id,uuid,name,environment_id', - 'mysqls:id,uuid,name,environment_id', - 'mariadbs:id,uuid,name,environment_id', - 'keydbs:id,uuid,name,environment_id', - 'dragonflies:id,uuid,name,environment_id', - 'clickhouses:id,uuid,name,environment_id', - 'sqlites:id,uuid,name,environment_id', - ]) - ->get(); - $currentProjectUuid = data_get($resource, 'environment.project.uuid'); - $currentEnvironmentUuid = data_get($resource, 'environment.uuid'); - $currentResourceUuid = data_get($resource, 'uuid'); - $resourceUuid = data_get($resource, 'uuid'); - $resourceType = $resource->getMorphClass(); - $isApplication = $resourceType === 'App\Models\Application'; - $isService = $resourceType === 'App\Models\Service'; - $isDatabase = str_contains($resourceType, 'Database') || str_contains($resourceType, 'Standalone'); - $hasMultipleServers = $isApplication && method_exists($resource, 'additional_servers') && - ($resource->relationLoaded('additional_servers') ? $resource->additional_servers->count() > 0 : ($resource->additional_servers_count ?? 0) > 0); - $serverName = $hasMultipleServers ? null : data_get($resource, 'destination.server.name'); - $routeParams = [ - 'project_uuid' => $currentProjectUuid, - 'environment_uuid' => $currentEnvironmentUuid, - ]; - if ($isApplication) { - $routeParams['application_uuid'] = $resourceUuid; - } elseif ($isService) { - $routeParams['service_uuid'] = $resourceUuid; - } else { - $routeParams['database_uuid'] = $resourceUuid; - } -@endphp - - - diff --git a/resources/views/components/security/navbar.blade.php b/resources/views/components/security/navbar.blade.php deleted file mode 100644 index 77dd645ddf..0000000000 --- a/resources/views/components/security/navbar.blade.php +++ /dev/null @@ -1,20 +0,0 @@ -@props([ - 'title' => 'Keys & Tokens', - 'subtitle' => 'SSH keys, cloud tokens, and API access', - // Family index/list views hide the H1 only at xl+ (topbar + tabs cover context). - // Detail views pass true so the resource name stays visible. - 'titleOnDesktop' => false, -]) - - - @isset($titleActions) - - {{ $titleActions }} - - @endisset - @isset($actions) - - {{ $actions }} - - @endisset - diff --git a/resources/views/components/server/advanced.blade.php b/resources/views/components/server/advanced.blade.php deleted file mode 100644 index 63cc23714f..0000000000 --- a/resources/views/components/server/advanced.blade.php +++ /dev/null @@ -1,38 +0,0 @@ -@props([ - 'serverIp', - 'traefikDashboardAvailable' => false, -]) - -
- - - -
diff --git a/resources/views/components/server/sidebar-proxy.blade.php b/resources/views/components/server/sidebar-proxy.blade.php deleted file mode 100644 index ae799d1628..0000000000 --- a/resources/views/components/server/sidebar-proxy.blade.php +++ /dev/null @@ -1,49 +0,0 @@ -@php - $proxyMenuItems = [ - [ - 'label' => 'Configuration', - 'route' => 'server.proxy', - 'active' => request()->routeIs('server.proxy'), - 'icon' => 'settings', - ], - [ - 'label' => 'Dynamic Configurations', - 'route' => 'server.proxy.dynamic-confs', - 'active' => request()->routeIs('server.proxy.dynamic-confs'), - 'icon' => 'sliders', - 'visible' => $server->proxySet(), - ], - [ - 'label' => 'Logs', - 'route' => 'server.proxy.logs', - 'active' => request()->routeIs('server.proxy.logs'), - 'icon' => 'file-content', - 'visible' => $server->proxySet(), - 'navigate' => false, - ], - ]; - - $proxyMenuItems = array_values(array_filter( - $proxyMenuItems, - fn (array $item): bool => $item['visible'] ?? true, - )); -@endphp - - diff --git a/resources/views/components/server/sidebar-security.blade.php b/resources/views/components/server/sidebar-security.blade.php deleted file mode 100644 index 7ecebfb3b3..0000000000 --- a/resources/views/components/server/sidebar-security.blade.php +++ /dev/null @@ -1,36 +0,0 @@ -@php - $securityMenuItems = [ - [ - 'label' => 'Server Patching', - 'route' => 'server.security.patches', - 'active' => request()->routeIs('server.security.patches'), - 'icon' => 'bandage', - ], - [ - 'label' => 'Terminal Access', - 'route' => 'server.security.terminal-access', - 'active' => request()->routeIs('server.security.terminal-access'), - 'icon' => 'browser-terminal', - 'navigate' => false, - ], - ]; -@endphp - - diff --git a/resources/views/components/server/sidebar-sentinel.blade.php b/resources/views/components/server/sidebar-sentinel.blade.php deleted file mode 100644 index e8e6157f16..0000000000 --- a/resources/views/components/server/sidebar-sentinel.blade.php +++ /dev/null @@ -1,37 +0,0 @@ -@php - $sentinelMenuItems = [ - [ - 'label' => 'Configuration', - 'route' => 'server.sentinel', - 'active' => request()->routeIs('server.sentinel'), - 'icon' => 'settings', - ], - [ - 'label' => 'Logs', - 'route' => 'server.sentinel.logs', - 'active' => request()->routeIs('server.sentinel.logs'), - 'icon' => 'file-content', - ], - ]; -@endphp - - diff --git a/resources/views/components/services/advanced.blade.php b/resources/views/components/services/advanced.blade.php deleted file mode 100644 index 3a6377aec0..0000000000 --- a/resources/views/components/services/advanced.blade.php +++ /dev/null @@ -1,49 +0,0 @@ -@php - $status = str($service->status ?? ''); - $canDeploy = auth()->user()->can('deploy', $service); - $canStop = auth()->user()->can('stop', $service); - $hasAdvancedItems = ! $status->contains('running'); -@endphp - -@if ($hasAdvancedItems) -
- - - -
-@endif diff --git a/resources/views/components/services/restart.blade.php b/resources/views/components/services/restart.blade.php deleted file mode 100644 index 6acfd29633..0000000000 --- a/resources/views/components/services/restart.blade.php +++ /dev/null @@ -1,50 +0,0 @@ -@props(['service']) - -@php - $status = str($service->status ?? ''); - $canDeploy = auth()->user()->can('deploy', $service); - $isRunning = $status->contains('running'); - $isDegraded = $status->contains('degraded'); -@endphp - -@if ($isRunning || $isDegraded) - @if ($isRunning) -
- - - -
- @else - - @endif -@endif diff --git a/resources/views/components/settings/navbar.blade.php b/resources/views/components/settings/navbar.blade.php deleted file mode 100644 index 79b333ca3e..0000000000 --- a/resources/views/components/settings/navbar.blade.php +++ /dev/null @@ -1,20 +0,0 @@ -@props([ - 'title' => 'Settings', - 'subtitle' => 'Instance configuration and maintenance', -]) - -{{-- Same 1180px shell as the workspace. Title hidden only at xl+ (full desktop). --}} -
- - @isset($titleActions) - - {{ $titleActions }} - - @endisset - @isset($actions) - - {{ $actions }} - - @endisset - -
diff --git a/resources/views/components/settings/sidebar.blade.php b/resources/views/components/settings/sidebar.blade.php deleted file mode 100644 index dbe381e050..0000000000 --- a/resources/views/components/settings/sidebar.blade.php +++ /dev/null @@ -1,57 +0,0 @@ -@php - $settingsMenuItems = [ - [ - 'label' => 'General', - 'route' => 'settings.index', - 'active' => $activeMenu === 'general', - 'icon' => 'settings', - ], - [ - 'label' => 'Advanced', - 'route' => 'settings.advanced', - 'active' => $activeMenu === 'advanced', - 'icon' => 'grid', - ], - [ - 'label' => 'Authentication', - 'route' => 'settings.oauth', - 'active' => $activeMenu === 'oauth', - 'icon' => 'keys', - ], - [ - 'label' => 'Transactional Email', - 'route' => 'settings.email', - 'active' => $activeMenu === 'email', - 'icon' => 'notifications', - ], - [ - 'label' => 'Instance Backup', - 'route' => 'settings.backup', - 'active' => $activeMenu === 'backup', - 'icon' => 'database', - ], - [ - 'label' => 'Updates', - 'route' => 'settings.updates', - 'active' => $activeMenu === 'updates', - 'icon' => 'refresh3', - ], - ]; -@endphp - - diff --git a/resources/views/components/slide-over.blade.php b/resources/views/components/slide-over.blade.php deleted file mode 100644 index a8a9b96653..0000000000 --- a/resources/views/components/slide-over.blade.php +++ /dev/null @@ -1,68 +0,0 @@ -@props(['closeWithX' => false, 'fullScreen' => false]) -{{-- `contents` so event-only shells (no trigger in the default slot) do not leave a - ghost layout box above layer-2 tabs. Trigger buttons in the slot still flow - into the parent as if unwrapped. --}} -
merge(['class' => 'contents']) }}> - {{ $slot }} - -
diff --git a/resources/views/components/status/degraded.blade.php b/resources/views/components/status/degraded.blade.php deleted file mode 100644 index 99ae2d4cdc..0000000000 --- a/resources/views/components/status/degraded.blade.php +++ /dev/null @@ -1,19 +0,0 @@ -@props([ - 'status' => 'Degraded', -]) -@php - if (str($status)->contains('(')) { - $displayStatus = $status; - $healthStatus = null; - } elseif (str($status)->contains(':') && ! str($status)->startsWith('Proxy')) { - $parts = explode(':', $status); - $displayStatus = str($parts[0])->headline()->value(); - $healthStatus = $parts[1] ?? null; - } else { - $displayStatus = str($status)->headline()->value(); - $healthStatus = null; - } - - $badgeStatus = $healthStatus ? "{$displayStatus} ({$healthStatus})" : $displayStatus; -@endphp - diff --git a/resources/views/components/status/index.blade.php b/resources/views/components/status/index.blade.php deleted file mode 100644 index aafa106a7c..0000000000 --- a/resources/views/components/status/index.blade.php +++ /dev/null @@ -1,33 +0,0 @@ -@props([ - 'title' => null, - 'lastDeploymentLink' => null, - 'resource' => null, - 'showRefreshButton' => true, -]) -@php - $stoppedAfterRestartLimit = $resource && method_exists($resource, 'stoppedAfterRestartLimit') && $resource->stoppedAfterRestartLimit(); -@endphp -
- @if (str($resource->status)->startsWith('running')) - - @elseif(str($resource->status)->startsWith('degraded')) - - @elseif(str($resource->status)->startsWith('restarting') || str($resource->status)->startsWith('starting')) - - @else - - @endif - @if (isset($resource->restart_count) && $resource->restart_count > 0 && (!str($resource->status)->startsWith('exited') || $stoppedAfterRestartLimit)) - - @endif - @if ($stoppedAfterRestartLimit) - - @endif - @if (!str($resource->status)->contains('exited') && $showRefreshButton) - - @endif -
diff --git a/resources/views/components/status/services.blade.php b/resources/views/components/status/services.blade.php deleted file mode 100644 index 0f90751b51..0000000000 --- a/resources/views/components/status/services.blade.php +++ /dev/null @@ -1,22 +0,0 @@ -@php - $displayStatus = formatContainerStatus($complexStatus); -@endphp -
- @if (str($displayStatus)->lower()->contains('running')) - - @elseif(str($displayStatus)->lower()->contains('starting')) - - @elseif(str($displayStatus)->lower()->contains('restarting')) - - @elseif(str($displayStatus)->lower()->contains('degraded')) - - @else - - @endif - @if (!str($complexStatus)->contains('exited') && $showRefreshButton) - - @endif -
diff --git a/resources/views/components/team/navbar.blade.php b/resources/views/components/team/navbar.blade.php deleted file mode 100644 index 332465dcbe..0000000000 --- a/resources/views/components/team/navbar.blade.php +++ /dev/null @@ -1,30 +0,0 @@ -@props([ - 'title' => 'Team', - 'subtitle' => 'Members, roles, and team settings', - // Hide family H1 only at xl+; keep create in the layer-2 bar. - 'titleOnDesktop' => false, -]) - - - @isset($titleActions) - - {{ $titleActions }} - - @endisset - - @isset($actions) - {{ $actions }} - @else - - - - - - - @endisset - - diff --git a/resources/views/components/use-magic-bar.blade.php b/resources/views/components/use-magic-bar.blade.php deleted file mode 100644 index 3991e59581..0000000000 --- a/resources/views/components/use-magic-bar.blade.php +++ /dev/null @@ -1,11 +0,0 @@ -
- @if (isset($link)) - Create a new one - - here. - - {{-- @else - Use the magic - bar (press /) to create a new one. --}} - @endif -
diff --git a/resources/views/livewire/destination/navbar.blade.php b/resources/views/livewire/destination/navbar.blade.php deleted file mode 100644 index 6c7315aa42..0000000000 --- a/resources/views/livewire/destination/navbar.blade.php +++ /dev/null @@ -1,17 +0,0 @@ -@props([ - 'destination', - 'title' => null, - 'subtitle' => null, -]) - -@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker') - -@elseif (filled($title)) -
-

{{ $title }}

- @if (filled($subtitle)) -

{{ $subtitle }}

- @endif -
-@endif diff --git a/resources/views/livewire/project/application/destination.blade.php b/resources/views/livewire/project/application/destination.blade.php deleted file mode 100644 index c5b2dd03a6..0000000000 --- a/resources/views/livewire/project/application/destination.blade.php +++ /dev/null @@ -1,8 +0,0 @@ -
-

Destination

-
The destination server / network where your application will be deployed to.
-
-

Server: {{ data_get($destination, 'server.name') }}

-

Destination Network: {{ $destination->network }}

-
-
diff --git a/resources/views/livewire/project/database/backup-now.blade.php b/resources/views/livewire/project/database/backup-now.blade.php deleted file mode 100644 index 6e91428573..0000000000 --- a/resources/views/livewire/project/database/backup-now.blade.php +++ /dev/null @@ -1,3 +0,0 @@ -Back up now diff --git a/resources/views/livewire/project/new/empty-project.blade.php b/resources/views/livewire/project/new/empty-project.blade.php deleted file mode 100644 index 7cc1a90bd7..0000000000 --- a/resources/views/livewire/project/new/empty-project.blade.php +++ /dev/null @@ -1 +0,0 @@ -Empty Project diff --git a/resources/views/livewire/project/service/edit-domain.blade.php b/resources/views/livewire/project/service/edit-domain.blade.php deleted file mode 100644 index 91518194cd..0000000000 --- a/resources/views/livewire/project/service/edit-domain.blade.php +++ /dev/null @@ -1,84 +0,0 @@ -
-
- @if($requiredPort) - - This service requires port {{ $requiredPort }} to function correctly. All domains must include this port number (or any other port if you know what you're doing). -

- Example: https://app.coolify.io:{{ $requiredPort }},https://www.app.coolify.io:{{ $requiredPort }} -
- @endif - - -
- Save domain -
- - - - -
    -
  • Only one service will be accessible at this domain
  • -
  • The routing behavior will be unpredictable
  • -
  • You may experience service disruptions
  • -
  • SSL certificates might not work correctly
  • -
-
-
- - @if ($showPortWarningModal) -
- -
- @endif -
diff --git a/resources/views/livewire/project/shared/upload-config.blade.php b/resources/views/livewire/project/shared/upload-config.blade.php deleted file mode 100644 index 6980d249e0..0000000000 --- a/resources/views/livewire/project/shared/upload-config.blade.php +++ /dev/null @@ -1,6 +0,0 @@ -
- - - Upload - - diff --git a/resources/views/livewire/tags/deployments.blade.php b/resources/views/livewire/tags/deployments.blade.php deleted file mode 100644 index 03eb633bc2..0000000000 --- a/resources/views/livewire/tags/deployments.blade.php +++ /dev/null @@ -1,48 +0,0 @@ -
- - @if (collect($deploymentsPerTagPerServer)->flatten(1)->isEmpty()) - - @else -
-
- Resource - Server - Status -
- - @foreach ($deploymentsPerTagPerServer as $serverName => $deployments) - @foreach ($deployments as $deployment) - @php - $status = data_get($deployment, 'status'); - $statusType = $status === 'in_progress' ? 'warning' : 'neutral'; - @endphp - - - - - - - {{ data_get($deployment, 'application_name') }} - - - {{ $serverName }} - - - @endforeach - @endforeach - -
- {{ collect($deploymentsPerTagPerServer)->flatten(1)->count() }} - {{ Str::plural('deployment', collect($deploymentsPerTagPerServer)->flatten(1)->count()) }} -
-
- @endif -
-
diff --git a/tests/Feature/AdvancedMenuIconConsistencyTest.php b/tests/Feature/AdvancedMenuIconConsistencyTest.php deleted file mode 100644 index f54e4b0601..0000000000 --- a/tests/Feature/AdvancedMenuIconConsistencyTest.php +++ /dev/null @@ -1,52 +0,0 @@ -toContain('Advanced'); - - // PHP menu arrays: label Advanced paired with icon grid - if (str_contains($contents, "'label' => 'Advanced'") || str_contains($contents, "'Advanced' =>")) { - expect($contents) - ->toMatch("/'label'\\s*=>\\s*'Advanced'[\\s\\S]{0,160}?'icon'\\s*=>\\s*'grid'|'Advanced'\\s*=>\\s*'grid'/") - ->not->toMatch("/'Advanced'\\s*=>\\s*'(?!grid)[^']+'/"); - } - - // Inline blade markup: reicon grid near Advanced label - if (str_contains($contents, 'menu-item-label">Advanced')) { - expect($contents) - ->toMatch('/name="grid"[\\s\\S]{0,120}menu-item-label">Advanced/') - ->not->toMatch('/name="(?!grid)[^"]+"[\\s\\S]{0,120}menu-item-label">Advanced/'); - } - } -}); - -test('advanced action dropdown menus use the grid icon', function () { - $files = [ - resource_path('views/components/applications/advanced.blade.php'), - resource_path('views/components/services/advanced.blade.php'), - resource_path('views/components/server/advanced.blade.php'), - ]; - - foreach ($files as $path) { - $contents = file_get_contents($path); - - expect($contents) - ->toContain('Advanced') - ->toContain('name="grid"') - ->not->toContain('name="sliders"') - ->not->toContain("name='sliders'"); - } -}); diff --git a/tests/Feature/ApiTokenPermissionsLayoutTest.php b/tests/Feature/ApiTokenPermissionsLayoutTest.php deleted file mode 100644 index 227cab4267..0000000000 --- a/tests/Feature/ApiTokenPermissionsLayoutTest.php +++ /dev/null @@ -1,18 +0,0 @@ -between( - '

Permissions

', - '' - )->toString(); - - expect($permissionsSection) - ->toContain('permissionsOpen') - ->toContain('Selected permissions') - ->toContain('toContain("in_array('root', \$permissions)") - ->toContain('Read sensitive data') - ->not->toContain('not->toContain("@if (!in_array('root', \$permissions))"); -}); diff --git a/tests/Feature/ApiTokensRevokeButtonTest.php b/tests/Feature/ApiTokensRevokeButtonTest.php deleted file mode 100644 index a728318a56..0000000000 --- a/tests/Feature/ApiTokensRevokeButtonTest.php +++ /dev/null @@ -1,9 +0,0 @@ -toContain('Revoke') - ->not->toMatch('/name="trash"[\s\S]{0,80}Revoke|Revoke[\s\S]{0,80}name="trash"/'); -}); diff --git a/tests/Feature/AppearanceThemeTest.php b/tests/Feature/AppearanceThemeTest.php deleted file mode 100644 index c4022c87a2..0000000000 --- a/tests/Feature/AppearanceThemeTest.php +++ /dev/null @@ -1,148 +0,0 @@ -toContain('and($accountMenu) - ->toContain('and($component) - ->toContain("['value' => 'custom', 'label' => 'Custom'") - ->toContain('and($picker) - ->toContain('type="color"') - ->toContain('@input="previewThemeColor($event.target.value)"') - ->toContain('@change="saveThemeColor($event.target.value)"') - ->toContain('aria-label="Custom theme color"') - ->toContain('setCustomMode') - ->and($layout) - ->toContain('window.themeControls') - ->toContain('requestAnimationFrame(() =>') - ->toContain("localStorage.setItem('themeColor', color)") - ->toContain("this.theme === 'custom'") - ->and($layout) - ->toContain("t === 'custom'") - ->toContain("localStorage.themeColor || '#6b16ed'") - ->toContain('--theme-accent-foreground') - ->toContain('window.applyStoredTheme') - ->toContain("document.addEventListener('livewire:navigated', window.applyStoredTheme)") - ->and($styles) - ->toContain('html[data-theme="custom"]') - ->toContain('--theme-base-color: oklch(49.65% 0.2709 289.33);') - ->toContain('--theme-bright-color: color-mix(in srgb, var(--theme-base-color) 85%, oklch(100% 0 0));') - ->toContain('--color-accent: var(--theme-bright-color);') - ->toContain('--color-coollabs: var(--theme-bright-color);') - ->toContain('--color-warning: var(--theme-bright-color);') - ->toContain('--color-accent-foreground: var(--theme-accent-foreground);') - ->toContain('--color-fg-dim: oklch(91.08% 0.0157 306.4);') - ->toContain('--color-fg-faint: oklch(87.24% 0.0188 306.63);') - ->toContain('html[data-theme="custom"] .control-selected') - ->toContain('--theme-scrollbar-thumb: color-mix(in srgb, var(--theme-bright-color) 70%, var(--theme-accent-foreground));') - ->toContain('--theme-border-color: color-mix(in oklab, var(--theme-base-color) 42%, oklch(44.19% 0.0146 285.79));') - ->toContain('--theme-fg-on-surface: oklch(from var(--theme-base-color) clamp(0.72, l, 0.86) min(c, 0.13) h);') - ->toContain('--theme-placeholder-color: oklch(from var(--theme-base-color) 0.62 min(c, 0.03) h);') - ->toContain('html[data-theme="custom"] *') - ->toContain('scrollbar-color: var(--theme-scrollbar-thumb) var(--color-panel);') - ->toContain('html[data-theme="custom"] *::-webkit-scrollbar-thumb') - ->toContain('html[data-theme="custom"] .application-settings-navigation') - ->toContain('scrollbar-gutter: stable;') - ->toContain('--color-panel: color-mix(in oklab, var(--theme-base-color) 14%, oklch(15.48% 0.0021 286.15));') - ->toContain('--color-surface: color-mix(in oklab, var(--theme-base-color) 18%, oklch(17.35% 0.0020 286.18));') - ->toContain('--coollabs-elevated: var(--color-surface);') - ->toContain('--coollabs-recessed: var(--color-raised);') - ->toContain('--coollabs-base: color-mix(in oklab, var(--theme-base-color) 22%, oklch(17.81% 0.0020 286.19));') - ->toContain('--color-content-surface: var(--coollabs-base);') - ->toContain('--coollabs-fill: color-mix(in oklab, var(--theme-base-color) 32%, oklch(20.99% 0.0039 286.06));') - ->toContain('--color-nav-text: oklch(91.08% 0.0157 306.4);') - ->toContain('--color-nav-muted: oklch(87.24% 0.0188 306.63);') - ->toContain('--color-log: color-mix(in oklab, var(--theme-base-color) 20%, oklch(13.49% 0.0024 286.07));') - ->toContain('--color-log-toolbar: color-mix(in oklab, var(--theme-base-color) 26%, oklch(17.35% 0.0020 286.18));') - ->toContain('html[data-theme="custom"] .logs-viewer-toolbar') - ->toContain('html[data-theme="custom"] .logs-viewer-timestamp') - ->toContain('html[data-theme="custom"] #nprogress .bar') - ->toContain('background: var(--theme-bright-color) !important;') - ->toContain('html[data-theme="custom"] #nprogress .spinner-icon') - ->toContain('html[data-theme="custom"] .loading-indicator') - ->toContain('html[data-theme="custom"] .data-table') - ->toContain('html[data-theme="custom"] [class~="dark:bg-white/[0.025]"]') - ->toContain('html[data-theme="custom"] [class~="dark:bg-surface"]') - ->toContain('background-color: var(--color-content-surface) !important;') - ->toContain('.dark [class~="dark:border-white/[0.08]"]') - ->toContain('.dark [class~="dark:border-white/[0.06]"]') - ->toContain('border-color: var(--coollabs-hairline) !important;') - ->toContain('html[data-theme="custom"] input::placeholder') - ->toContain('color: var(--theme-placeholder-color) !important;') - ->toMatch('/html\[data-theme="custom"\] input::placeholder,[^{]+\{[^}]*opacity: 1;/s') - ->toContain('html[data-theme="custom"] input:read-only') - ->and($deploymentLogs) - ->toContain('dark:bg-log') - ->not->toContain('dark:bg-[#0d0d0d]') - ->and($serverTiming) - ->toContain('html[data-theme="custom"] #server-timing-hud') - ->toContain('--sth-background: var(--color-surface);') - ->toContain('--sth-livewire: var(--theme-bright-color);') - ->and($utilities) - ->toContain('text-nav-text') - ->toContain('text-nav-muted') - ->toContain('text-nav-active') - ->and($terminal) - ->toContain("dataset.theme === 'custom'") - ->toContain("localStorage.getItem('themeColor')") - ->toContain("attributeFilter: ['class', 'data-theme', 'style']") - ->and($styles) - ->toContain('html[data-theme="custom"] .application-console-shell[data-console-theme="system"]') - ->toContain('--console-theme-background: var(--color-log);') - ->toContain('--console-theme-border: var(--coollabs-line);'); -}); - -test('navigation colors meet WCAG AA contrast requirements', function () { - $styles = file_get_contents(resource_path('css/app.css')); - - $contrastRatio = function (string $foreground, string $background): float { - $luminance = function (string $color): float { - $channels = array_map( - fn (string $channel): float => hexdec($channel) / 255, - str_split(ltrim($color, '#'), 2), - ); - - $channels = array_map( - fn (float $channel): float => $channel <= 0.04045 - ? $channel / 12.92 - : (($channel + 0.055) / 1.055) ** 2.4, - $channels, - ); - - return 0.2126 * $channels[0] + 0.7152 * $channels[1] + 0.0722 * $channels[2]; - }; - - [$lighter, $darker] = collect([$luminance($foreground), $luminance($background)]) - ->sortDesc() - ->values() - ->all(); - - return ($lighter + 0.05) / ($darker + 0.05); - }; - - $themes = [ - [['#525252', '#666666', '#171717'], '#ffffff'], - [['#a8a8b0', '#7a7a84', '#f2f2f2'], '#0c0c0d'], - [['#e4dfea', '#d8d2df', '#ffffff', '#e4dfea', '#d8d2df'], '#58585a'], - ]; - - foreach ($themes as [$colors, $background]) { - foreach ($colors as $color) { - expect($styles)->toContain($color); - expect($contrastRatio($color, $background))->toBeGreaterThanOrEqual(4.5); - } - } -}); diff --git a/tests/Feature/ApplicationDestinationStatusBadgeTest.php b/tests/Feature/ApplicationDestinationStatusBadgeTest.php deleted file mode 100644 index 43bac41486..0000000000 --- a/tests/Feature/ApplicationDestinationStatusBadgeTest.php +++ /dev/null @@ -1,19 +0,0 @@ -before('@else')->value(); - - expect($applicationSection) - ->toContain('') - ->not->toContain('toContain("route('server.show', ['server_uuid' => data_get(\$resource, 'destination.server.uuid')])") - ->toContain("route('server.show', ['server_uuid' => data_get(\$destination, 'server.uuid')])") - ->toContain('Open server'); -}); diff --git a/tests/Feature/ApplicationGeneralLayoutTest.php b/tests/Feature/ApplicationGeneralLayoutTest.php deleted file mode 100644 index 889f1318b2..0000000000 --- a/tests/Feature/ApplicationGeneralLayoutTest.php +++ /dev/null @@ -1,76 +0,0 @@ -after('class="application-details-card">') - ->before('') - ->toString(); - - expect($applicationDetails) - ->toContain('') - ->toContain('x-on:click="$wire.dispatch(\'loadCompose\', false)"') - ->toContain('Reload compose'); -}); - -test('compose file loading waits for the user to confirm the file location', function () { - $view = file_get_contents(resource_path('views/livewire/project/application/general.blade.php')); - - expect($view) - ->toContain('x-on:click="$wire.dispatch(\'loadCompose\', false)"') - ->not->toContain('x-init="$wire.dispatch(\'loadCompose\', true)"'); -}); - -test('traffic analytics is only shown on the dedicated analytics page', function () { - $view = file_get_contents(resource_path('views/livewire/project/application/general.blade.php')); - - expect($view)->not->toContain('toContain('
') - ->toContain('
'); -}); - -test('unsaved changes ignore compose initialization state', function () { - $view = file_get_contents(resource_path('views/livewire/project/application/general.blade.php')); - $unsavedBar = str($view) - ->after('before('/>') - ->toString(); - - expect($unsavedBar) - ->toContain('targets="') - ->toContain('name,description') - ->not->toContain('initLoadingCompose') - ->not->toContain('dockerComposeRaw') - ->not->toContain('dockerCompose,'); -}); - -test('onboarding uses the reusable advanced settings component', function () { - $view = file_get_contents(resource_path('views/livewire/project/application/general.blade.php')); - $onboarding = file_get_contents(resource_path('views/livewire/boarding/index.blade.php')); - - expect($view) - ->toContain('id="dockerComposeCustomBuildCommand"') - ->not->toContain('') - ->not->toContain('The following commands are for advanced use cases.') - ->and($onboarding) - ->toContain('after("@if (\$application->build_pack === 'dockerimage')") - ->after('@else') - ->before('toString(); - - expect($requiredImageCondition) - ->toContain('$application->settings->is_build_server_enabled') - ->toContain('! $application->destination->server->canBuildApplications()'); -}); diff --git a/tests/Feature/ApplicationInternalAccessSectionTest.php b/tests/Feature/ApplicationInternalAccessSectionTest.php deleted file mode 100644 index 8a5c7abd02..0000000000 --- a/tests/Feature/ApplicationInternalAccessSectionTest.php +++ /dev/null @@ -1,61 +0,0 @@ -toContain('id="access-section" title="Access"') - ->toContain('

Public access

') - ->toContain('$primaryDomain = null;') - ->toContain('$additionalDomainCount = max(0, $domainCount - 1);') - ->toContain("aria-label=\"{{ \$domainCount > 0 ? 'Manage application domains' : 'Add an application domain' }}\"") - ->toContain('{{ $primaryDomain }}') - ->toContain("+{{ \$additionalDomainCount }} more {{ Str::plural('domain', \$additionalDomainCount) }}") - ->toContain('No public domain configured') - ->toContain('Make this application available from a URL') - ->toContain("{{ \$domainCount > 0 ? 'Manage domains' : 'Add domain' }}") - ->toContain('class="button relative z-10 ml-auto shrink-0"') - ->toContain('aria-label="{{ $domainCount > 0 ? \'Manage application domains\' : \'Add an application domain\' }}"') - ->toContain('') - ->not->toContain('') - ->toContain('not->toContain('wire:init="loadCurrentInternalHostname"') - ->and($internalAccessSettings) - ->toContain('id="internal-access-section"') - ->toContain('

Internal access

') - ->not->toContain('toContain('Internal hostname') - ->toContain('Docker network') - ->toContain('Exposed ports') - ->toContain('Network aliases') - ->toContain('wire:init="loadCurrentInternalHostname"') - ->toContain('$currentInternalHostname') - ->toContain('class="input input-with-copy-button bg-white dark:bg-coolgray-100 dark:read-only:bg-coolgray-100 dark:read-only:text-white"') - ->not->toContain('Changes with each deployment') - ->toContain("window.scrollToSettingsSection?.('networking-section')") - ->and($generalComponent) - ->not->toContain('public function loadCurrentInternalHostname(): void') - ->and($internalAccessComponent) - ->toContain('public ?string $currentInternalHostname = null;') - ->toContain('public function loadCurrentInternalHostname(): void') - ->toContain('getCurrentApplicationContainerStatus(') - ->toContain("data_get(\$currentContainer, 'Names')") - ->and($configurationSidebar) - ->toContain("['id' => 'access-section', 'label' => 'Access']"); -}); - -it('does not show the internal access section for Docker Compose applications', function () { - $generalSettings = file_get_contents(resource_path('views/livewire/project/application/general.blade.php')); - $configurationSidebar = file_get_contents(resource_path('views/components/application/configuration-sidebar.blade.php')); - - expect($generalSettings) - ->toContain("@if (\$buildPack !== 'dockercompose')") - ->and($configurationSidebar) - ->toContain("['id' => 'access-section', 'label' => 'Access']") - ->not->toContain("['id' => 'public-access-section', 'label' => 'Public access']") - ->not->toContain("['id' => 'internal-access-section', 'label' => 'Internal access']"); -}); diff --git a/tests/Feature/ApplicationRollbackIconTest.php b/tests/Feature/ApplicationRollbackIconTest.php deleted file mode 100644 index 927c0b7cef..0000000000 --- a/tests/Feature/ApplicationRollbackIconTest.php +++ /dev/null @@ -1,16 +0,0 @@ -toMatch("/'Rollback'\\s*=>\\s*'time-back'/") - ->not->toMatch("/'Rollback'\\s*=>\\s*'logout'/"); - - expect($reicon) - ->toContain("'time-back' =>"); -}); diff --git a/tests/Feature/BoardingActionsLayoutTest.php b/tests/Feature/BoardingActionsLayoutTest.php deleted file mode 100644 index 795ae71ecc..0000000000 --- a/tests/Feature/BoardingActionsLayoutTest.php +++ /dev/null @@ -1,74 +0,0 @@ -toContain('class="mx-auto mt-6 flex w-full max-w-3xl flex-col items-center gap-3"'); -}); - -test('server validation opens in the centered process dialog', function () { - $view = file_get_contents(resource_path('views/livewire/boarding/index.blade.php')); - - expect($view) - ->toContain('') - ->toContain('@click="processDialogOpen = true"') - ->not->toContain(''); -}); - -test('DigitalOcean is available as an onboarding server provider', function () { - $view = file_get_contents(resource_path('views/livewire/boarding/index.blade.php')); - - expect($view) - ->toContain('') - ->toContain('') - ->toContain('Deploy servers directly from your DigitalOcean account.') - ->toContain(''); -}); - -test('server type details are shown on the relevant cards instead of a technical details panel', function () { - $view = file_get_contents(resource_path('views/livewire/boarding/index.blade.php')); - - expect($view) - ->toContain('aria-label="About this machine"') - ->toContain('aria-label="About remote servers"') - ->toContain('Not recommended for production workloads due to resource contention.') - ->toContain('Any SSH-accessible server, including cloud VPS, bare metal, and self-hosted infrastructure.') - ->not->toContain('') - ->not->toContain('') - ->not->toContain(''); -}); - -test('server type cards use the standard card hover treatment', function () { - $view = file_get_contents(resource_path('views/livewire/boarding/index.blade.php')); - $hoverClasses = 'shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:shadow-md'; - preg_match_all('/min-h-36[^\"]*'.preg_quote($hoverClasses, '/').'/', $view, $matches); - - expect($matches[0])->toHaveCount(5) - ->and(substr_count($view, 'group relative cursor-pointer'))->toBe(5) - ->and($view)->not->toContain('hover:border-coollabs/35 hover:bg-coollabs/[0.03]') - ->and($view)->not->toContain('dark:hover:border-warning/25 dark:hover:bg-warning/[0.04]'); -}); - -test('existing SSH key selection does not show a redundant value tooltip', function () { - $view = file_get_contents(resource_path('views/livewire/boarding/index.blade.php')); - - expect($view)->toContain('label="Existing SSH key" :options="$privateKeyOptions" :tooltip="false"'); -}); - -test('server connection step does not render a technical details panel', function () { - $view = file_get_contents(resource_path('views/livewire/boarding/index.blade.php')); - - expect($view) - ->not->toContain('') - ->not->toContain('') - ->not->toContain(''); -}); - -test('project step does not render a technical details panel', function () { - $view = file_get_contents(resource_path('views/livewire/boarding/index.blade.php')); - - expect($view) - ->not->toContain('') - ->not->toContain('') - ->not->toContain(''); -}); diff --git a/tests/Feature/BreadcrumbSelectionStyleTest.php b/tests/Feature/BreadcrumbSelectionStyleTest.php deleted file mode 100644 index d6a5978d3b..0000000000 --- a/tests/Feature/BreadcrumbSelectionStyleTest.php +++ /dev/null @@ -1,73 +0,0 @@ -map(fn (string $path): string => file_get_contents($path)); - - expect($views->implode("\n")) - ->not->toContain('name="check-circle"') - ->and($views->every(fn (string $view): bool => str_contains($view, 'bg-neutral-100'))) - ->toBeTrue(); -}); - -test('teleported resource breadcrumbs use the same spacing as application breadcrumbs', function () { - $layout = file_get_contents(resource_path('views/layouts/app.blade.php')); - - expect($layout) - ->toContain('class="flex items-center gap-0.5 min-w-0 flex-1 pl-3 pr-4"') - ->not->toContain('class="flex items-center gap-1.5 min-w-0 flex-1 pl-3 pr-4"'); -}); - -test('top breadcrumbs shrink and clip instead of overlapping on narrower screens', function () { - $breadcrumb = file_get_contents(resource_path('views/components/top-breadcrumb.blade.php')); - $layout = file_get_contents(resource_path('views/layouts/app.blade.php')); - - expect($breadcrumb) - ->toContain('class="flex w-full min-w-0 items-center gap-0.5 text-[13px]"') - ->not->toContain('overflow-hidden text-[13px]') - ->toContain('class="min-w-0 shrink" x-data="{ collapsed: false }"') - ->not->toContain('class="shrink-0" x-data="{ collapsed: false }"') - ->and($layout) - ->toContain('class="relative flex min-w-0 flex-1 items-center"') - ->not->toContain('flex min-w-0 flex-1 items-center overflow-hidden') - ->not->toContain('
'); -}); - -test('resource headings do not duplicate database and service breadcrumbs', function () { - $headings = collect([ - resource_path('views/livewire/project/database/heading.blade.php'), - resource_path('views/livewire/project/service/heading.blade.php'), - ])->map(fn (string $path): string => file_get_contents($path)); - - expect($headings->implode("\n"))->not->toContain("@teleport('#server-topbar-context')"); -}); - -test('top breadcrumb lets users switch between resources in the current environment', function () { - $breadcrumb = file_get_contents(resource_path('views/components/top-breadcrumb.blade.php')); - - expect($breadcrumb) - ->toContain('$currentEnvironment->applications') - ->toContain('$currentEnvironment->databases()') - ->toContain('$currentEnvironment->services') - ->toContain('toContain("'application' => route('project.application.configuration'") - ->toContain("'database' => route('project.database.configuration'") - ->toContain("'service' => route('project.service.configuration'"); -}); - -test('breadcrumb switchers let users search their items', function () { - $switcher = file_get_contents(resource_path('views/components/breadcrumb-switcher.blade.php')); - - expect($switcher) - ->toContain('x-model.debounce.150ms="search"') - ->toContain('type="search"') - ->toContain('placeholder="Search {{ strtolower($title) }}"') - ->toContain('class="searchable-listbox-search"') - ->toContain('class="searchable-listbox-search-input"') - ->toContain('toContain('left-4 size-3') - ->toContain('.includes(search.toLowerCase())'); -}); diff --git a/tests/Feature/BrowserTerminalIconConsistencyTest.php b/tests/Feature/BrowserTerminalIconConsistencyTest.php deleted file mode 100644 index a05951a549..0000000000 --- a/tests/Feature/BrowserTerminalIconConsistencyTest.php +++ /dev/null @@ -1,36 +0,0 @@ -not->toMatch('/name=["\']terminal["\']/') - ->not->toMatch('/icon-name=["\']terminal["\']/') - ->not->toMatch("/'icon'\\s*=>\\s*'terminal'/"); - } -}); - -test('reicon pack includes browser-terminal glyph', function () { - $path = resource_path('views/components/reicon.blade.php'); - $contents = file_get_contents($path); - - expect($contents)->toContain("'browser-terminal' =>"); -}); diff --git a/tests/Feature/ButtonDepthInteractionTest.php b/tests/Feature/ButtonDepthInteractionTest.php deleted file mode 100644 index 9e1e435a84..0000000000 --- a/tests/Feature/ButtonDepthInteractionTest.php +++ /dev/null @@ -1,33 +0,0 @@ -toContain('--button-depth-color: rgb(0 0 0 / 0.22);') - ->toContain('--button-depth: 0 2px 0 var(--button-depth-color);') - ->toContain('--button-depth-hover: 0 3px 0 var(--button-depth-color);') - ->toContain('--button-depth-color: var(--color-coollabs-300);') - ->toContain('.dark .button:not(.button-highlighted):not(.button-error):not([isHighlighted]):not(:disabled)') - ->not->toContain('.dark .button:not(.button-highlighted):not([isHighlighted]):not(:disabled)') - ->toContain('--button-depth-color: rgb(255 255 255 / 0.08);') - ->not->toContain('.dark .button.button-highlighted:not(:disabled)') - ->toContain('.button.button-error:not(:disabled)') - ->toContain('--button-depth-color: var(--color-red-300);') - ->toContain('.dark .button.button-error:not(:disabled)') - ->toContain('--button-depth-color: var(--color-red-800);') - ->toContain('.button-error:not(:disabled)') - ->toContain('.button.button-error {') - ->toContain('.button.button-error:disabled {') - ->toContain('hover:bg-red-100 hover:text-red-900 dark:hover:bg-red-700 dark:hover:text-white') - ->not->toContain('hover:bg-red-300 hover:text-white dark:hover:bg-red-800') - ->not->toContain('button[isError]:not(:disabled)') - ->toContain('.button:not(:disabled):hover') - ->toContain('transform: translateY(-1px);') - ->toContain('transition-duration: 120ms, 120ms, 120ms, 80ms, 80ms;') - ->toContain('.button:not(:disabled):active') - ->toContain('transform: translateY(2px);') - ->not->toContain('transform: translateY(1px);') - ->not->toContain('transform: translateY(3px);') - ->toContain('box-shadow: none;'); -}); diff --git a/tests/Feature/ButtonHeightConsistencyTest.php b/tests/Feature/ButtonHeightConsistencyTest.php deleted file mode 100644 index ac72cb2ba4..0000000000 --- a/tests/Feature/ButtonHeightConsistencyTest.php +++ /dev/null @@ -1,11 +0,0 @@ -toContain('px-2.5 h-8 min-h-8') - ->and($applicationStyles) - ->not->toContain('.application-settings-workspace .button') - ->not->toContain('.application-settings-form .button'); -}); diff --git a/tests/Feature/ButtonTextCapitalizationTest.php b/tests/Feature/ButtonTextCapitalizationTest.php deleted file mode 100644 index cdf41d1292..0000000000 --- a/tests/Feature/ButtonTextCapitalizationTest.php +++ /dev/null @@ -1,11 +0,0 @@ -[\s\S]*?)\n\}/', $utilities, $matches); - - expect($matches['styles'] ?? '') - ->toContain('capitalize') - ->not->toContain('normal-case'); -}); diff --git a/tests/Feature/CustomThemeContrastTest.php b/tests/Feature/CustomThemeContrastTest.php deleted file mode 100644 index 1f9050c842..0000000000 --- a/tests/Feature/CustomThemeContrastTest.php +++ /dev/null @@ -1,111 +0,0 @@ -after('html[data-theme="custom"] {') - ->before('}') - ->toString(); -} - -it('derives the custom theme entirely in oklch with no hex color literals', function () { - expect(customThemeBlock()) - ->not->toMatch('/#[0-9a-fA-F]{3,8}\b/') - ->toContain('--theme-base-color:') - ->toContain('oklch('); -}); - -it('adds clamped, readable foreground tokens for the custom theme', function () { - $block = customThemeBlock(); - - expect($block) - // Accent text/icons on dark surfaces: lightness forced into a readable band. - ->toContain('--theme-fg-on-surface:') - ->toMatch('/--theme-fg-on-surface:\s*oklch\(from var\(--theme-base-color\)[^;]*clamp\(/') - // Placeholder: light, near-neutral, guaranteed readable. - ->toContain('--theme-placeholder-color:') - ->toMatch('/--theme-placeholder-color:\s*oklch\(from var\(--theme-base-color\)/'); -}); - -it('routes custom-theme accent text utilities to the readable on-surface token', function () { - $css = file_get_contents(resource_path('css/app.css')); - - expect($css) - ->toMatch('/\[class~="text-warning"\][^{]*\{\s*color:\s*var\(--theme-fg-on-surface\)/') - ->toContain('[class~="text-coollabs"]'); -}); - -it('uses the clamped placeholder token for custom-theme inputs', function () { - $css = file_get_contents(resource_path('css/app.css')); - - expect($css) - ->toMatch('/html\[data-theme="custom"\] input::placeholder[^}]*var\(--theme-placeholder-color\)/'); -}); - -it('adds a light custom variant that tints surfaces toward white with dark foreground', function () { - $css = file_get_contents(resource_path('css/app.css')); - - expect($css) - ->toContain('html[data-theme="custom"]:not(.dark)') - // Surfaces hold a fixed high lightness with injected base-hue chroma, so the - // tint is visible (not a washed-out mix into white). - ->toMatch('/html\[data-theme="custom"\]:not\(\.dark\)[^}]*--color-app:\s*oklch\(from var\(--theme-base-color\) 9[0-9](\.[0-9]+)?% min\(c,/s') - // Accent text/icons clamp to a dark, readable band on light surfaces. - ->toMatch('/html\[data-theme="custom"\]:not\(\.dark\)[^}]*--theme-fg-on-surface:\s*oklch\(from var\(--theme-base-color\) clamp\(0\.3/s'); -}); - -it('lets the theme scripts toggle a custom light or dark mode via customMode', function () { - $base = file_get_contents(resource_path('views/layouts/base.blade.php')); - $component = file_get_contents(resource_path('views/components/theme-controls.blade.php')); - $picker = file_get_contents(resource_path('views/components/theme-controls/picker.blade.php')); - $appearance = file_get_contents(resource_path('views/livewire/profile/appearance.blade.php')); - $accountMenu = file_get_contents(resource_path('views/components/top-user-menu.blade.php')); - - expect($base) - // One shared Alpine factory holds the theme logic. - ->toContain('window.themeControls') - ->toContain('localStorage.customMode') - ->toContain('setCustomMode') - // The picker no longer force-adds dark; it follows customMode. - ->not->toContain("document.documentElement.classList.add('dark')") - // Light/dark lives inside the custom color picker popover. - ->and($picker) - ->toContain('customMode') - ->toContain('setCustomMode') - ->toContain('type="color"') - ->and($component) - // Both variants open the shared picker popover for Custom. - ->toContain('toContain('chooseCustom') - // Both the page and the dropdown reuse the one component. - ->and($appearance) - ->toContain('and($accountMenu) - ->toContain('after('window.themeAccentForeground') - ->before('window.applyStoredTheme') - ->toString(); - - expect($base) - ->toContain('window.hexToOklch') - ->and($accentFn) - ->toContain('oklch(0% 0 0)') - ->toContain('oklch(100% 0 0)') - ->not->toContain('#000000') - ->not->toContain('#ffffff'); -}); diff --git a/tests/Feature/DangerZoneLayoutTest.php b/tests/Feature/DangerZoneLayoutTest.php deleted file mode 100644 index 5395958c73..0000000000 --- a/tests/Feature/DangerZoneLayoutTest.php +++ /dev/null @@ -1,28 +0,0 @@ -toContain("@props(['title'])") - ->toContain('sm:flex-row sm:items-start sm:justify-between') - ->toContain('rounded-lg border border-red-200 bg-red-50') - ->toContain('dark:border-red-500/25 dark:bg-red-500/[0.08]') - ->toContain('Permanent') - ->toContain('@isset($action)'); - - foreach ([ - 'livewire/destination/show.blade.php', - 'livewire/project/database/backup-edit/danger.blade.php', - 'livewire/project/shared/danger.blade.php', - 'livewire/project/shared/storages/volume-backups/danger.blade.php', - 'livewire/server/delete.blade.php', - 'livewire/source/github/change.blade.php', - 'livewire/storage/show.blade.php', - 'livewire/team/danger-zone.blade.php', - ] as $view) { - expect(file_get_contents(resource_path('views/'.$view))) - ->toContain('not->toContain('rounded-lg border border-red-300 bg-red-50'); - } -}); diff --git a/tests/Feature/DashboardNavbarLayoutTest.php b/tests/Feature/DashboardNavbarLayoutTest.php deleted file mode 100644 index 5d613720f6..0000000000 --- a/tests/Feature/DashboardNavbarLayoutTest.php +++ /dev/null @@ -1,27 +0,0 @@ -toContain("['label' => 'General', 'route' => 'settings.index', 'active' => request()->routeIs('settings.*')]") - ->not->toContain("['label' => 'Email', 'route' => 'settings.email'"); -}); - -test('dashboard navbar keeps the original side-by-side tab and actions layout', function () { - $path = resource_path('views/components/dashboard/navbar.blade.php'); - $contents = file_get_contents($path); - - expect($contents) - ->toContain('flex w-full flex-col gap-2 sm:flex-row sm:items-center sm:justify-between') - ->toContain('flex min-w-0 w-full items-center gap-0.5 overflow-x-auto'); -}); - -test('dashboard view all links use the shared button style', function () { - $contents = file_get_contents(resource_path('views/components/section-heading.blade.php')); - - expect($contents) - ->toContain('class="button group"') - ->not->toContain('class="group inline-flex h-7'); -}); diff --git a/tests/Feature/DatabaseBackupSelectionInputTest.php b/tests/Feature/DatabaseBackupSelectionInputTest.php deleted file mode 100644 index 9a773ecb3b..0000000000 --- a/tests/Feature/DatabaseBackupSelectionInputTest.php +++ /dev/null @@ -1,19 +0,0 @@ -toContain('
') - ->toContain("['value' => true, 'label' => 'All databases']") - ->toContain("['value' => false, 'label' => 'Specific databases']") - ->toContain("value: @entangle('databasesToBackup').live") - ->toContain('class="chip-input"') - ->toContain('class="chip font-mono"') - ->toContain('class="chip-remove"') - ->toContain('addDatabase()') - ->toContain('removeDatabase(index)') - ->toContain('Type a database and press Enter'); - - expect(substr_count($view, 'toBe(1); -}); diff --git a/tests/Feature/DatabaseBackupsLayoutTest.php b/tests/Feature/DatabaseBackupsLayoutTest.php deleted file mode 100644 index 58c1e54478..0000000000 --- a/tests/Feature/DatabaseBackupsLayoutTest.php +++ /dev/null @@ -1,71 +0,0 @@ -toContain('title="Database backups"') - ->toContain('Schedules') - ->toContain('Enabled') - ->toContain('Total executions') - ->toContain('application-settings-form flex min-w-0 flex-col gap-6') - ->and($schedules) - ->toContain('placeholder="Search backups"') - ->toContain('data-table overflow-hidden rounded-xl border') - ->toContain('toContain("route('project.service.database.backup.show'") - ->toContain("route('project.database.backup.executions'") - ->toContain("route('project.service.database.backup.executions'") - ->toContain('toContain('Manage') - ->and($component)->toContain("withCount('executions')"); -}); - -it('returns from database backup settings to the database', function () { - $sidebar = file_get_contents(resource_path('views/components/backup-sidebar.blade.php')); - - expect($sidebar) - ->toContain("'back' => 'project.database.configuration'") - ->toContain("'Back to database'") - ->toContain("except('backup_uuid')"); -}); - -it('shows the backup path directly on every execution', function () { - $executions = file_get_contents(resource_path('views/livewire/project/database/backup-executions.blade.php')); - - expect($executions) - ->toContain('Backup path:') - ->toContain("data_get(\$execution, 'filename', 'N/A')") - ->toContain('Backup path') - ->toContain('class="select-all truncate font-mono text-[11px]') - ->not->toContain('backup-executions-table-grid border-t'); -}); - -it('keeps backup execution actions in the normal table flow', function () { - $executions = file_get_contents(resource_path('views/livewire/project/database/backup-executions.blade.php')); - - expect($executions) - ->not->toContain('sticky right-0'); -}); - -it('spaces instance backup settings sections', function () { - $edit = file_get_contents(resource_path('views/livewire/project/database/backup-edit.blade.php')); - - expect($edit) - ->toStartWith('
'); -}); - -it('shows database backup section descriptions from the shared title helper', function () { - $general = file_get_contents(resource_path('views/livewire/project/database/backup-edit/general.blade.php')); - $retention = file_get_contents(resource_path('views/livewire/project/database/backup-edit/retention.blade.php')); - - expect($general) - ->toContain('not->toContain('

Backup schedule

') - ->and($retention) - ->toContain('not->toContain('

Retention

'); -}); diff --git a/tests/Feature/DatabasePublicAccessLoadingTest.php b/tests/Feature/DatabasePublicAccessLoadingTest.php deleted file mode 100644 index 4b81d5f283..0000000000 --- a/tests/Feature/DatabasePublicAccessLoadingTest.php +++ /dev/null @@ -1,41 +0,0 @@ -toContain('') - ->toContain("'label' => blank(\$publicPort) ? 'Public through TCP proxy (set public port first)' : 'Public through TCP proxy'") - ->toContain("'disabled' => blank(\$publicPort)") - ->toContain('wire:key="public-access-{{ $publicPort ?: \'unset\' }}"') - ->not->toContain('x-data="{ publicPort:') - ->not->toContain('x-effect="options[1].disabled') - ->not->toContain('id="publicPort" x-on:input=') - ->not->toContain('id="publicPort" live'); - } -}); - -it('passes the selected PostgreSQL public access state to the save action', function () { - $generalSettings = file_get_contents(resource_path('views/livewire/project/database/postgresql/general.blade.php')); - - expect($generalSettings) - ->toContain('onChange="instantSave" :onChangeArgs="[]"'); - - $component = file_get_contents(app_path('Livewire/Project/Database/Postgresql/General.php')); - - expect($component) - ->toContain('public function instantSave(?bool $isPublic = null)') - ->toContain('$this->isPublic = $isPublic;'); -}); diff --git a/tests/Feature/DatabaseRestoreDialogTest.php b/tests/Feature/DatabaseRestoreDialogTest.php deleted file mode 100644 index 485ed2e26e..0000000000 --- a/tests/Feature/DatabaseRestoreDialogTest.php +++ /dev/null @@ -1,39 +0,0 @@ -toContain('') - ->toContain('') - ->not->toContain('toContain("@if (\$resourceDbType === 'standalone-postgresql' && \$dumpAll)") - ->toContain('Full restore overwrites administrator passwords') - ->toContain('The backup replaces PostgreSQL administrator role passwords, including the destination administrator password.') - ->toContain("If the administrator password changes, update it in Coolify's database configuration after the restore."); -}); - -test('postgresql single database restore offers explicit object replacement', function () { - $view = file_get_contents(resource_path('views/livewire/project/database/import-form.blade.php')); - - expect($view) - ->toContain('Replace objects that already exist') - ->toContain('wire:model="restoreCommandText"') - ->toContain('readonly label="Import command"'); -}); - -test('restore confirmation dialogs warn that existing objects can block the import', function () { - $view = file_get_contents(resource_path('views/livewire/project/database/import-form.blade.php')); - - expect($view) - ->toContain('submitAction="runImport"') - ->toContain('submitAction="restoreFromS3"') - ->toContain('Existing objects can cause the import to fail unless replacement is enabled.') - ->not->toContain('All existing data will be replaced.'); -}); diff --git a/tests/Feature/DefaultThemeTest.php b/tests/Feature/DefaultThemeTest.php deleted file mode 100644 index 8aa3543fab..0000000000 --- a/tests/Feature/DefaultThemeTest.php +++ /dev/null @@ -1,11 +0,0 @@ -toContain('toContain("const t = localStorage.theme || 'dark';") - ->toContain("localStorage.theme = 'dark';") - ->toContain(''); -}); diff --git a/tests/Feature/DeploymentLogToolbarStyleTest.php b/tests/Feature/DeploymentLogToolbarStyleTest.php deleted file mode 100644 index 24cf01376a..0000000000 --- a/tests/Feature/DeploymentLogToolbarStyleTest.php +++ /dev/null @@ -1,58 +0,0 @@ -toMatch('/\.logs-viewer-btn-active\s*\{[^}]*background:\s*var\(--color-coollabs\);[^}]*color:\s*#fff;/s') - ->toMatch('/\.dark \.logs-viewer-btn-active\s*\{[^}]*color:\s*#fff;/s') - ->not->toMatch('/\.logs-viewer-btn-active\s*\{[^}]*var\(--color-warning\)/s'); -}); - -test('deployment logs use light surfaces in light mode and dark surfaces in dark mode', function () { - $view = file_get_contents(resource_path('views/livewire/project/application/deployment/show.blade.php')); - $styles = file_get_contents(resource_path('css/app.css')); - - expect($view) - ->toContain('bg-white text-neutral-800') - ->toContain('dark:bg-log dark:text-neutral-100') - ->toContain('border-neutral-200 shadow-sm dark:border-coolgray-200') - ->toContain('border-neutral-200! bg-white!') - ->toContain('dark:border-white/[0.08]! dark:bg-white/[0.05]!') - ->and($styles) - ->toMatch('/\.logs-viewer\s*\{[^}]*background:\s*#fff;[^}]*color:\s*#262626;/s') - ->toMatch('/\.dark \.logs-viewer\s*\{[^}]*background:\s*var\(--color-log\);/s'); -}); - -test('log search hides lines that do not match', function () { - $styles = file_get_contents(resource_path('css/app.css')); - - expect($styles) - ->toMatch('/\.logs-viewer-line\.hidden\s*\{[^}]*display:\s*none;/s'); -}); - -test('turning off follow logs stays off at the bottom of the log viewer', function () { - $views = [ - resource_path('views/livewire/project/application/deployment/show.blade.php'), - resource_path('views/livewire/project/shared/get-logs.blade.php'), - ]; - - foreach ($views as $view) { - expect(file_get_contents($view)) - ->toContain('followManuallyDisabled: false') - ->toContain('!this.followManuallyDisabled && distanceFromBottom <= 10') - ->toContain('this.followManuallyDisabled = true') - ->toContain('this.followManuallyDisabled = false'); - } -}); - -test('runtime log follow loop is cleaned up when navigating away', function () { - $view = file_get_contents(resource_path('views/livewire/project/shared/get-logs.blade.php')); - - expect($view) - ->toContain('scrollTimeout: null') - ->toContain("this.\$root.querySelector('#logsContainer')") - ->toContain('destroy() {') - ->toContain('this.destroyed = true') - ->toContain('this.cancelScrollLoop()'); -}); diff --git a/tests/Feature/DeploymentsIndicatorLayoutTest.php b/tests/Feature/DeploymentsIndicatorLayoutTest.php deleted file mode 100644 index c1676dd817..0000000000 --- a/tests/Feature/DeploymentsIndicatorLayoutTest.php +++ /dev/null @@ -1,54 +0,0 @@ -toContain('transition-[left] duration-200') - ->toContain(":class=\"collapsed ? 'lg:left-16' : 'lg:left-56'\"") - ->not->toContain('fixed bottom-0 z-60 mb-4 left-0 lg:left-56 ml-4'); - - expect($layoutView) - ->toContain('
toBeLessThan(strpos($layoutView, '')); -}); - -it('uses the redesigned elevated surface and status badge patterns', function () { - $indicatorView = file_get_contents(resource_path('views/livewire/deployments-indicator.blade.php')); - - expect($indicatorView) - ->toContain('var(--coollabs-elevated)') - ->toContain('var(--coollabs-line)') - ->toContain('var(--shadow-modal)') - ->toContain('var(--coollabs-hairline)') - ->toContain('toContain('name="chevron-down"') - ->toContain("'In progress'") - ->toContain("'Queued'") - ->toContain('dark:text-fg') - ->toContain('dark:border-coolgray-300') - ->toContain('$this->shouldShow') - ->toContain('updateShouldShowFromPath') - ->toContain('livewire:navigated') - ->not->toContain('dark:bg-coolgray-100') - ->not->toContain('dark:bg-coolgray-200') - ->not->toContain('text-gray-800') - ->not->toContain("str_replace('_', ' ', \$deployment->status)"); -}); - -it('uses solid surfaces in every deployments indicator state', function () { - $indicatorView = file_get_contents(resource_path('views/livewire/deployments-indicator.blade.php')); - - expect($indicatorView) - ->not->toContain('reduceOpacity') - ->not->toContain('transition-opacity') - ->not->toContain('x-transition:enter-start="opacity-0') - ->not->toContain('x-transition:leave-end="opacity-0') - ->not->toContain('dark:bg-white/[') - ->not->toContain('dark:hover:bg-white/[') - ->not->toContain('dark:border-white/['); -}); diff --git a/tests/Feature/DestinationCreationUiTest.php b/tests/Feature/DestinationCreationUiTest.php deleted file mode 100644 index d5ac3e13cd..0000000000 --- a/tests/Feature/DestinationCreationUiTest.php +++ /dev/null @@ -1,12 +0,0 @@ -toContain("return redirectRoute(\$this, 'destination.show', [\$docker->uuid]);"); - expect($view) - ->toContain('wire:submit="submit"') - ->toContain('wire:target="submit"'); -}); diff --git a/tests/Feature/DestinationSettingsLayoutTest.php b/tests/Feature/DestinationSettingsLayoutTest.php deleted file mode 100644 index 0e3f5256fb..0000000000 --- a/tests/Feature/DestinationSettingsLayoutTest.php +++ /dev/null @@ -1,40 +0,0 @@ -toContain(':mobileTitleOnly="true"') - ->toContain("@include('livewire.destination.sidebar'"); - expect($resources) - ->toContain(':mobileTitleOnly="true"') - ->toContain("@include('livewire.destination.sidebar'"); - expect($sidebar) - ->toContain("'label' => 'General'") - ->toContain("'label' => 'Resources'") - ->toContain("'label' => 'Danger Zone'") - ->toContain('application-settings-navigation min-w-0 xl:self-start'); - expect($navbar) - ->toContain("request()->routeIs('destination.show', 'destination.resources', 'destination.danger')") - ->not->toContain("['label' => 'Resources', 'route' => 'destination.resources'"); - expect($breadcrumbs) - ->toContain('x-breadcrumb-switcher') - ->toContain('$currentDestination->name') - ->toContain("route('destination.show'") - ->toContain("? 'Docker' : 'Deprecated'"); - expect($breadcrumbs) - ->toContain('Pages') - ->toContain('Projects') - ->toContain('Environments') - ->not->toContain('M5 12l5 5 9-11'); - expect($show) - ->toContain('destination-danger-section') - ->toContain('title="Danger zone"') - ->not->toContain("actions=['This will delete the selected destination/network.']"); - expect(file_get_contents(base_path('routes/web.php'))) - ->toContain("->name('destination.danger')"); -}); diff --git a/tests/Feature/DockerCleanupExecutionStyleTest.php b/tests/Feature/DockerCleanupExecutionStyleTest.php deleted file mode 100644 index e220c8bc37..0000000000 --- a/tests/Feature/DockerCleanupExecutionStyleTest.php +++ /dev/null @@ -1,27 +0,0 @@ -toContain('bg-white text-neutral-700') - ->toContain('dark:bg-neutral-950 dark:text-neutral-300'); -}); - -test('docker cleanup executions show a loading state while opening an execution', function () { - $view = file_get_contents(resource_path('views/livewire/server/docker-cleanup-executions.blade.php')); - - expect($view) - ->toContain('wire:loading.attr="disabled"') - ->toContain('wire:target="selectExecution({{ data_get($execution, \'id\') }})"') - ->toContain('wire:loading.remove') - ->toContain('toContain('Choose Keep retained images to follow each application’s Images to keep setting under Rollback.') - ->toContain('Delete all old images ignores that setting and keeps only the running image.'); -}); diff --git a/tests/Feature/DockerComposeCreateViewTest.php b/tests/Feature/DockerComposeCreateViewTest.php deleted file mode 100644 index 4d21ca7981..0000000000 --- a/tests/Feature/DockerComposeCreateViewTest.php +++ /dev/null @@ -1,23 +0,0 @@ -toContain('class="application-settings-section-body"') - ->not->toContain('class="application-settings-section-body p-0!"'); -}); - -it('lets the Docker Compose editor use the available width', function () { - $view = file_get_contents(resource_path('views/livewire/project/new/docker-compose.blade.php')); - - expect($view) - ->toContain('class="mt-8 w-full lg:mt-3"') - ->not->toContain('max-w-[1180px]'); -}); - -it('shows a loading indicator while creating the service', function () { - $view = file_get_contents(resource_path('views/livewire/project/new/docker-compose.blade.php')); - - expect($view)->toContain(''); -}); diff --git a/tests/Feature/DockerImageCreateViewTest.php b/tests/Feature/DockerImageCreateViewTest.php deleted file mode 100644 index 226f369acc..0000000000 --- a/tests/Feature/DockerImageCreateViewTest.php +++ /dev/null @@ -1,9 +0,0 @@ -toContain('aria-label="Tag and SHA256 digest are mutually exclusive"') - ->toContain('>OR'); -}); diff --git a/tests/Feature/EmptyStateBackgroundTest.php b/tests/Feature/EmptyStateBackgroundTest.php deleted file mode 100644 index 7fe59a81c4..0000000000 --- a/tests/Feature/EmptyStateBackgroundTest.php +++ /dev/null @@ -1,21 +0,0 @@ -'); - $css = file_get_contents(resource_path('css/app.css')); - - expect($html) - ->toContain('empty-state') - ->not->toContain('bg-neutral-50') - ->not->toContain('dark:bg-white/[0.02]') - ->and($css) - ->toContain('.application-settings-section-body:has(> .empty-state:only-child)') - ->toContain('.application-settings-section-body.is-flush:has(> .empty-state:only-child)') - ->toContain('padding: 1rem;') - ->toContain('.empty-state') - ->toContain('background: var(--coollabs-base);') - ->toContain('.loading-state-card') - ->toContain('border: 1px dashed var(--coollabs-line);'); -}); diff --git a/tests/Feature/EnableButtonBrandingTest.php b/tests/Feature/EnableButtonBrandingTest.php deleted file mode 100644 index 1aa3603f18..0000000000 --- a/tests/Feature/EnableButtonBrandingTest.php +++ /dev/null @@ -1,17 +0,0 @@ -toContain('isHighlighted'); - } - - $terminalAccess = file_get_contents(resource_path('views/livewire/server/security/terminal-access.blade.php')); - - expect($terminalAccess)->toContain(':isHighlightedButton="!$isTerminalEnabled"'); -}); diff --git a/tests/Feature/EnvironmentEditUiTest.php b/tests/Feature/EnvironmentEditUiTest.php deleted file mode 100644 index 0f580eba70..0000000000 --- a/tests/Feature/EnvironmentEditUiTest.php +++ /dev/null @@ -1,23 +0,0 @@ -toContain('buttonTitle="Delete"') - ->not->toContain('buttonTitle="Delete Environment"'); -}); - -test('environment delete section stacks on small screens', function () { - $view = file_get_contents(resource_path('views/livewire/project/environment-edit.blade.php')); - - expect($view) - ->toContain('flex flex-col gap-4 px-5 py-4 sm:flex-row sm:items-start sm:justify-between') - ->toContain('livewire:project.delete-environment'); -}); - -test('app-tab utility includes icon text spacing', function () { - $css = file_get_contents(resource_path('css/utilities.css')); - - expect($css)->toMatch('/@utility app-tab \{[^}]*gap-1/s'); -}); diff --git a/tests/Feature/ExecutionViewStructureTest.php b/tests/Feature/ExecutionViewStructureTest.php deleted file mode 100644 index ce613608b1..0000000000 --- a/tests/Feature/ExecutionViewStructureTest.php +++ /dev/null @@ -1,26 +0,0 @@ -toContain('
') - ->toContain('

Executions

') - ->not->toContain('Executions toContain('class="flex flex-col gap-4 pt-2"'); -}); - -it('renders scheduled task executions as selectable status cards with expandable logs', function () { - $view = file_get_contents(resource_path('views/livewire/project/shared/scheduled-task/executions.blade.php')); - - expect($view) - ->toContain('
toContain('toContain('border-l-2 transition-colors p-4 cursor-pointer') - ->toContain("'success' => 'Success'") - ->toContain("'running' => 'In Progress'") - ->toContain("'failed' => 'Failed'") - ->toContain("data_get(\$execution, 'id') == \$selectedKey") - ->toContain('max-h-[600px] overflow-y-auto') - ->toContain('No executions found.'); -}); diff --git a/tests/Feature/FormControlHeightTest.php b/tests/Feature/FormControlHeightTest.php deleted file mode 100644 index 589112b5bf..0000000000 --- a/tests/Feature/FormControlHeightTest.php +++ /dev/null @@ -1,51 +0,0 @@ -toContain('@utility input-select {') - ->toContain('@utility button {'); - - preg_match('/@utility input-select \{[^}]*\}/s', $utilities, $inputSelect); - preg_match('/@utility button \{[^}]*\}/s', $utilities, $button); - - expect($inputSelect[0] ?? '') - ->toContain('h-8') - ->not->toContain('h-9') - ->and($button[0] ?? '')->toContain('h-8') - ->and($button[0] ?? '')->toContain('min-h-8') - ->and($button[0] ?? '')->toContain('whitespace-nowrap') - ->and($button[0] ?? '')->toContain('shrink-0') - ->and($button[0] ?? '')->not->toContain('h-9'); -}); - -test('settings form surfaces keep input and button heights equal', function () { - $css = file_get_contents(resource_path('css/app.css')); - - preg_match( - '/\.application-settings-workspace \.input,[\s\S]*?\.application-settings-form \.select \{[\s\S]*?\}/', - $css, - $inputs - ); - preg_match( - '/\.application-settings-workspace \.button,[\s\S]*?\.application-settings-form \.button \{[\s\S]*?\}/', - $css, - $buttons - ); - - expect($inputs[0] ?? '') - ->toContain('height: 2rem;') - ->and($buttons[0] ?? '')->toContain('height: 2rem;') - ->and($buttons[0] ?? '')->toContain('min-height: 2rem;') - ->and($buttons[0] ?? '')->toContain('white-space: nowrap;'); -}); - -test('directory storage actions wrap on narrow viewports instead of stacking uneven heights', function () { - $view = file_get_contents(resource_path('views/livewire/project/service/file-storage.blade.php')); - - expect($view) - ->toContain('flex flex-wrap items-center gap-2') - ->toContain('Convert to file') - ->toContain('Configure Backup'); -}); diff --git a/tests/Feature/FullWidthResourceLayoutTest.php b/tests/Feature/FullWidthResourceLayoutTest.php deleted file mode 100644 index 9cdfbdc52a..0000000000 --- a/tests/Feature/FullWidthResourceLayoutTest.php +++ /dev/null @@ -1,16 +0,0 @@ -toContain("pageWidth === 'centered' ? 'mx-auto max-w-[1400px]' : 'max-w-none'") - ->and($applicationConfiguration) - ->not->toContain('max-w-[1180px]') - ->toContain('xl:gap-8') - ->and($serverConfiguration) - ->not->toContain('max-w-[1180px]') - ->toContain('xl:gap-8'); -}); diff --git a/tests/Feature/GlobalIconTooltipTest.php b/tests/Feature/GlobalIconTooltipTest.php deleted file mode 100644 index 2cc9049299..0000000000 --- a/tests/Feature/GlobalIconTooltipTest.php +++ /dev/null @@ -1,46 +0,0 @@ -toContain('') - ->and($tooltip) - ->toContain("closest('button, a, [data-tooltip]')") - ->toContain("target.matches('[data-tooltip], .icon-button')") - ->toContain("target.hasAttribute('aria-label')") - ->toContain('target.childElementCount === 1') - ->toContain("target.firstElementChild?.matches('svg')") - ->not->toContain("target.querySelector('svg')") - ->toContain("target.matches('[data-icon-tooltip-ignore]')") - ->toContain("target.removeAttribute('title')") - ->toContain('role="tooltip"') - ->toContain('aria-label') - ->toContain('fixed z-[100]'); -}); - -it('does not reposition an already active tooltip during nested mouseover events', function () { - $tooltip = file_get_contents(resource_path('views/components/icon-tooltip.blade.php')); - - expect($tooltip) - ->toContain('if (target === this.activeTarget && this.visible) return;'); -}); - -it('keeps a tooltip hidden until its measured position is applied', function () { - $tooltip = file_get_contents(resource_path('views/components/icon-tooltip.blade.php')); - - expect($tooltip) - ->toContain('positioned: false') - ->toContain('this.positioned = false;') - ->toContain('this.$nextTick(() => this.positioned = true);') - ->toContain("positioned ? 'visible' : 'invisible'"); -}); - -it('centers tooltips on the trigger and keeps them within the viewport', function () { - $tooltip = file_get_contents(resource_path('views/components/icon-tooltip.blade.php')); - - expect($tooltip) - ->toContain('this.x = rect.left + rect.width / 2;') - ->toContain('Math.min(window.innerWidth - width - 8, this.x - width / 2)') - ->not->toContain('-translate-x-1/2'); -}); diff --git a/tests/Feature/GlobalSearchLoadingStateTest.php b/tests/Feature/GlobalSearchLoadingStateTest.php deleted file mode 100644 index 8737dde280..0000000000 --- a/tests/Feature/GlobalSearchLoadingStateTest.php +++ /dev/null @@ -1,104 +0,0 @@ -toContain('wire:loading.class="pointer-events-none opacity-40 blur-[2px]"') - ->toContain('wire:loading.flex') - ->toContain('selectServer,selectDestination,selectProject,selectEnvironment') - ->toContain('Loading selection…') - ->toContain('isPaletteTransitioning') - ->toContain('runPaletteTransition') - ->toContain('Loading…'); -}); - -it('uses a single Alpine result renderer for every command palette result type', function () { - $view = file_get_contents(resource_path('views/livewire/global-search.blade.php')); - - expect($view) - ->not->toContain('Create mode (server-rendered path)') - ->not->toContain('!$wire.isCreateMode') - ->toContain("\n
not->toContain("\n