toContain('.listbox-trigger {') ->toContain('min-width: 0;') ->toContain('overflow: hidden;') ->toContain('.listbox-trigger-label {') ->toContain('text-overflow: ellipsis;') ->toContain('white-space: nowrap;'); }); test('listbox trigger height matches shared inputs', function () { $css = file_get_contents(resource_path('css/app.css')); expect($css) ->toMatch('/\.listbox-trigger \{[^}]*height: 2\.25rem;/s') ->toMatch('/\.application-settings-workspace \.listbox-trigger[^}]*height: 2rem;/s'); }); test('listbox component uses shared trigger label truncation', function () { $html = Blade::render(<<<'BLADE' BLADE); expect($html) ->toContain('class="w-full min-w-0"') ->toContain('relative min-w-0') ->toContain('listbox-trigger') ->toContain('listbox-trigger-label') ->toContain(':title="current"') ->not->toContain('class="truncate" x-text="current"'); }); test('searchable listbox component uses shared trigger label truncation', function () { $html = Blade::render(<<<'BLADE' BLADE); expect($html) ->toContain('class="w-full min-w-0"') ->toContain('relative min-w-0') ->toContain('listbox-trigger-label') ->toContain(':title="current"'); }); test('listbox shows an empty state when it has no options', function () { $listbox = file_get_contents(resource_path('views/components/forms/listbox.blade.php')); $operations = file_get_contents(resource_path('views/livewire/project/shared/resource-operations.blade.php')); expect($listbox) ->toContain("'emptyText' => 'No options available.'") ->toContain('x-show="options.length === 0"'); expect($operations)->toContain('No network destinations are available on this server.'); }); test('listbox forwards dynamic disabled state to its trigger', function () { $listbox = file_get_contents(resource_path('views/components/forms/listbox.blade.php')); $operations = file_get_contents(resource_path('views/livewire/project/shared/resource-operations.blade.php')); expect($listbox)->toContain("\$attributes->whereStartsWith('x-bind:disabled')"); expect($operations) ->toContain('selectedCloneServer: null') ->toContain('selectedCloneProject: null') ->toContain('selectedCloneEnvironment: null') ->toContain('currentServerId: @js($resource->destination->server->id)') ->toContain('currentDestinationUuid: @js($resource->destination->uuid)') ->toContain("server.id == this.currentServerId ? ' (current)' : ''") ->toContain("destination.uuid == this.currentDestinationUuid ? ' (current)' : ''") ->toContain('selectedCloneServer = null;') ->toContain('placeholder="Choose a server…"') ->toContain("this.selectedCloneServer === null || this.selectedCloneServer === ''") ->toContain("x-bind:disabled=\"selectedCloneServer === null || selectedCloneServer === ''\"") ->toContain('x-model="selectedCloneProject"') ->toContain('x-model="selectedCloneEnvironment"') ->toContain('$wire.cloneTo(selectedCloneDestination)') ->toContain('$wire.cloneTo(@js($resource->destination->uuid), selectedCloneEnvironment)') ->toContain('x-bind:disabled="!selectedMoveProject || availableEnvironments.length === 0"'); }); test('notification event multiselect truncates long selected summaries', function () { $html = Blade::render(<<<'BLADE' BLADE); expect($html) ->toContain('class="w-full min-w-0"') ->toContain('relative min-w-0') ->toContain('listbox-trigger') ->toContain('listbox-trigger-label') ->toContain('Docker cleanup failure, Disk usage warning, Server unreachable, Server patching') ->toContain('title="Docker cleanup failure, Disk usage warning, Server unreachable, Server patching"') ->toContain('4/4'); });