InstanceSettings::query()->updateOrCreate(['id' => 0], ['fqdn' => null])); $this->team = Team::factory()->create(); $this->owner = User::factory()->create(); $this->team->members()->attach($this->owner->id, ['role' => 'owner']); $this->actingAs($this->owner); session(['currentTeam' => $this->team]); }); it('shows generate link with loading state targeting viaLink', function () { $view = file_get_contents(resource_path('views/livewire/team/invite-link.blade.php')); expect($view) ->toContain('wire:submit="viaLink"') ->toContain('wire:target="viaLink"') ->toContain('toContain('Generate link'); Livewire::test(InviteLink::class) ->assertSee('Generate link') ->assertSeeHtml('wire:target="viaLink"') ->assertSeeHtml('wire:loading.attr="disabled"') ->assertSeeHtml('wire:loading.class="is-loading"'); }); it('renders a real copy button for pending invitation links', function () { $invitation = TeamInvitation::create([ 'team_id' => $this->team->id, 'uuid' => 'test-invitation-uuid-123456789012', 'email' => 'invitee@example.com', 'role' => 'member', 'link' => 'http://example.test/invitations/test-invitation-uuid-123456789012', 'via' => 'link', ]); $view = file_get_contents(resource_path('views/livewire/team/invitations.blade.php')); expect($view) ->toContain('aria-label="Copy invitation link"') ->toContain('window.copyToClipboard(@js($invite->link))') ->toContain('class="button h-7! shrink-0 px-2!"'); Livewire::test(Invitations::class, [ 'invitations' => TeamInvitation::ownedByCurrentTeam()->get(), ]) ->assertSee($invitation->link) ->assertSeeHtml('aria-label="Copy invitation link"') ->assertSeeHtml('window.copyToClipboard(') ->assertSeeHtml('type="button"'); }); it('exposes a resilient global copyToClipboard helper', function () { $layout = file_get_contents(resource_path('views/layouts/base.blade.php')); expect($layout) ->toContain('async function copyToClipboard(text)') ->toContain('window.copyToClipboard = copyToClipboard') ->toContain('document.execCommand(\'copy\')') ->toContain('window.isSecureContext'); }); it('preserves a provisional user when revoking their invitation fails', function () { $provisionalUser = User::factory()->create([ 'email' => 'provisional@example.com', 'email_verified_at' => null, 'force_password_reset' => true, ]); $invitation = TeamInvitation::create([ 'team_id' => $this->team->id, 'uuid' => 'failing-invitation-delete', 'email' => $provisionalUser->email, 'role' => 'member', 'link' => 'http://example.test/invitations/failing-invitation-delete', 'via' => 'link', ]); TeamInvitation::deleting(function (): void { throw new RuntimeException('Invitation deletion failed.'); }); Livewire::test(Invitations::class, [ 'invitations' => collect([$invitation]), ]) ->call('deleteInvitation', $invitation->id) ->assertDispatched('error'); $this->assertDatabaseHas('users', ['id' => $provisionalUser->id]); $this->assertDatabaseHas('team_invitations', ['id' => $invitation->id]); });