name = 'Test User';
$this->email = 'test@example.com';
}
public function updateProfile(): void
{
$this->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255'],
]);
$this->dispatched[] = 'profile-updated';
$this->toasts[] = ['text' => 'Profile updated successfully.', 'variant' => 'success'];
}
public function updatePreferences(): void
{
$this->validate([
'locale' => ['required', 'string'],
'timezone' => ['required', 'timezone'],
'time_format' => ['required', 'in:12,24'],
'week_starts_on' => ['required', 'in:0,1'],
]);
$this->dispatched[] = 'preferences-updated';
$this->toasts[] = ['text' => 'Preferences updated.', 'variant' => 'success'];
}
public function updatePassword(): void
{
$this->validate([
'current_password' => ['required'],
'new_password' => ['required', 'confirmed', 'min:8'],
]);
$this->current_password = '';
$this->new_password = '';
$this->new_password_confirmation = '';
$this->dispatched[] = 'password-updated';
$this->toasts[] = ['text' => 'Password updated.', 'variant' => 'success'];
}
public function enableTwoFactor(): void
{
$this->toasts[] = ['text' => 'Two-factor upgrading.', 'variant' => 'warning'];
}
public function requestAccountDeletion(): void
{
$this->showDeleteConfirmation = false;
$this->deleteReason = '';
$this->toasts[] = ['text' => 'Deletion scheduled.', 'variant' => 'warning'];
}
public function cancelAccountDeletion(): void
{
$this->toasts[] = ['text' => 'Deletion cancelled.', 'variant' => 'success'];
}
public function render(): string
{
return <<<'HTML'
Section: {{ $activeSection }}
@if($showDeleteConfirmation)
Delete Confirmation
@endif
HTML;
}
}
// =============================================================================
// Tab Navigation Tests
// =============================================================================
describe('Settings tab navigation', function () {
it('defaults to profile section', function () {
Livewire::test(SettingsModalDouble::class)
->assertSet('activeSection', 'profile');
});
it('switches to preferences section', function () {
Livewire::test(SettingsModalDouble::class)
->set('activeSection', 'preferences')
->assertSet('activeSection', 'preferences');
});
it('switches to security section', function () {
Livewire::test(SettingsModalDouble::class)
->set('activeSection', 'security')
->assertSet('activeSection', 'security');
});
it('switches to danger zone section', function () {
Livewire::test(SettingsModalDouble::class)
->set('activeSection', 'danger')
->assertSet('activeSection', 'danger');
});
it('renders active section label', function () {
Livewire::test(SettingsModalDouble::class)
->set('activeSection', 'preferences')
->assertSee('Section: preferences');
});
it('cycles through all sections', function () {
$component = Livewire::test(SettingsModalDouble::class);
foreach (['profile', 'preferences', 'security', 'danger'] as $section) {
$component->set('activeSection', $section)
->assertSet('activeSection', $section);
}
});
});
// =============================================================================
// Profile Update Tests
// =============================================================================
describe('Settings profile update', function () {
it('loads initial profile data on mount', function () {
Livewire::test(SettingsModalDouble::class)
->assertSet('name', 'Test User')
->assertSet('email', 'test@example.com');
});
it('validates required name', function () {
Livewire::test(SettingsModalDouble::class)
->set('name', '')
->call('updateProfile')
->assertHasErrors(['name']);
});
it('validates required email', function () {
Livewire::test(SettingsModalDouble::class)
->set('email', '')
->call('updateProfile')
->assertHasErrors(['email']);
});
it('validates email format', function () {
Livewire::test(SettingsModalDouble::class)
->set('email', 'not-an-email')
->call('updateProfile')
->assertHasErrors(['email']);
});
it('validates name max length', function () {
Livewire::test(SettingsModalDouble::class)
->set('name', str_repeat('A', 256))
->call('updateProfile')
->assertHasErrors(['name']);
});
it('succeeds with valid profile data', function () {
Livewire::test(SettingsModalDouble::class)
->set('name', 'Jane Doe')
->set('email', 'jane@example.com')
->call('updateProfile')
->assertHasNoErrors()
->assertSet('dispatched', ['profile-updated']);
});
it('records success toast on profile update', function () {
Livewire::test(SettingsModalDouble::class)
->set('name', 'Jane Doe')
->set('email', 'jane@example.com')
->call('updateProfile')
->assertSet('toasts', fn ($toasts) => count($toasts) === 1
&& $toasts[0]['variant'] === 'success');
});
});
// =============================================================================
// Preferences Update Tests
// =============================================================================
describe('Settings preferences update', function () {
it('has correct default preferences', function () {
Livewire::test(SettingsModalDouble::class)
->assertSet('locale', 'en_GB')
->assertSet('timezone', 'Europe/London')
->assertSet('time_format', 12)
->assertSet('week_starts_on', 1);
});
it('validates timezone', function () {
Livewire::test(SettingsModalDouble::class)
->set('timezone', 'Invalid/Zone')
->call('updatePreferences')
->assertHasErrors(['timezone']);
});
it('validates time_format values', function () {
Livewire::test(SettingsModalDouble::class)
->set('time_format', 99)
->call('updatePreferences')
->assertHasErrors(['time_format']);
});
it('validates week_starts_on values', function () {
Livewire::test(SettingsModalDouble::class)
->set('week_starts_on', 5)
->call('updatePreferences')
->assertHasErrors(['week_starts_on']);
});
it('accepts valid 24-hour format', function () {
Livewire::test(SettingsModalDouble::class)
->set('time_format', 24)
->call('updatePreferences')
->assertHasNoErrors(['time_format']);
});
it('accepts Monday start (1) and Sunday start (0)', function () {
Livewire::test(SettingsModalDouble::class)
->set('week_starts_on', 0)
->call('updatePreferences')
->assertHasNoErrors(['week_starts_on']);
Livewire::test(SettingsModalDouble::class)
->set('week_starts_on', 1)
->call('updatePreferences')
->assertHasNoErrors(['week_starts_on']);
});
it('dispatches preferences-updated on success', function () {
Livewire::test(SettingsModalDouble::class)
->set('locale', 'en_US')
->set('timezone', 'America/New_York')
->set('time_format', 24)
->set('week_starts_on', 0)
->call('updatePreferences')
->assertHasNoErrors()
->assertSet('dispatched', ['preferences-updated']);
});
});
// =============================================================================
// Password Change Tests
// =============================================================================
describe('Settings password change', function () {
it('validates current password required', function () {
Livewire::test(SettingsModalDouble::class)
->set('current_password', '')
->set('new_password', 'newpass123')
->set('new_password_confirmation', 'newpass123')
->call('updatePassword')
->assertHasErrors(['current_password']);
});
it('validates new password required', function () {
Livewire::test(SettingsModalDouble::class)
->set('current_password', 'oldpass')
->set('new_password', '')
->call('updatePassword')
->assertHasErrors(['new_password']);
});
it('validates password confirmation matches', function () {
Livewire::test(SettingsModalDouble::class)
->set('current_password', 'oldpass')
->set('new_password', 'newpass123')
->set('new_password_confirmation', 'different')
->call('updatePassword')
->assertHasErrors(['new_password']);
});
it('validates minimum password length', function () {
Livewire::test(SettingsModalDouble::class)
->set('current_password', 'oldpass')
->set('new_password', 'short')
->set('new_password_confirmation', 'short')
->call('updatePassword')
->assertHasErrors(['new_password']);
});
it('clears password fields on success', function () {
Livewire::test(SettingsModalDouble::class)
->set('current_password', 'oldpassword')
->set('new_password', 'newpassword123')
->set('new_password_confirmation', 'newpassword123')
->call('updatePassword')
->assertHasNoErrors()
->assertSet('current_password', '')
->assertSet('new_password', '')
->assertSet('new_password_confirmation', '');
});
it('dispatches password-updated event on success', function () {
Livewire::test(SettingsModalDouble::class)
->set('current_password', 'oldpassword')
->set('new_password', 'newpassword123')
->set('new_password_confirmation', 'newpassword123')
->call('updatePassword')
->assertSet('dispatched', ['password-updated']);
});
});
// =============================================================================
// Account Deletion Tests
// =============================================================================
describe('Settings account deletion', function () {
it('shows delete confirmation modal', function () {
Livewire::test(SettingsModalDouble::class)
->set('showDeleteConfirmation', true)
->assertSee('Delete Confirmation');
});
it('hides delete confirmation by default', function () {
Livewire::test(SettingsModalDouble::class)
->assertSet('showDeleteConfirmation', false)
->assertDontSee('Delete Confirmation');
});
it('resets state when deletion requested', function () {
Livewire::test(SettingsModalDouble::class)
->set('showDeleteConfirmation', true)
->set('deleteReason', 'No longer needed')
->call('requestAccountDeletion')
->assertSet('showDeleteConfirmation', false)
->assertSet('deleteReason', '');
});
it('records warning toast for deletion', function () {
Livewire::test(SettingsModalDouble::class)
->call('requestAccountDeletion')
->assertSet('toasts', fn ($toasts) => $toasts[0]['variant'] === 'warning');
});
it('records success toast for deletion cancellation', function () {
Livewire::test(SettingsModalDouble::class)
->call('cancelAccountDeletion')
->assertSet('toasts', fn ($toasts) => $toasts[0]['variant'] === 'success');
});
it('has delete account feature enabled by default', function () {
Livewire::test(SettingsModalDouble::class)
->assertSet('isDeleteAccountEnabled', true);
});
});
// =============================================================================
// Two-Factor Authentication Tests
// =============================================================================
describe('Settings two-factor authentication', function () {
it('has two-factor disabled by default', function () {
Livewire::test(SettingsModalDouble::class)
->assertSet('isTwoFactorEnabled', false)
->assertSet('userHasTwoFactorEnabled', false)
->assertSet('showTwoFactorSetup', false);
});
it('records warning toast when enabling two-factor (not yet implemented)', function () {
Livewire::test(SettingsModalDouble::class)
->call('enableTwoFactor')
->assertSet('toasts', fn ($toasts) => $toasts[0]['variant'] === 'warning');
});
});