Skip to content

Commit

Permalink
fix: Improved tests for updating profile details
Browse files Browse the repository at this point in the history
  • Loading branch information
lewislarsen committed Aug 2, 2024
1 parent 8edb409 commit d851fb1
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -1,48 +1,110 @@
<?php
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Session;
use Illuminate\Validation\Rule;
use Livewire\Volt\Component;
use Masmerise\Toaster\Toaster;
new class extends Component {
new class extends Component
{
public string $name = '';
public string $email = '';
public ?string $gravatar_email;
public ?string $gravatar_email = null;
public string $timezone = '';
public ?int $preferred_backup_destination_id = null;
public string $language = 'en'; // Default language is english.
public ?bool $receiving_weekly_summary_email = false;
public int $pagination_count;
public string $language = 'en';
public bool $receiving_weekly_summary_email = false;
public int $pagination_count = 15;
public Collection $pagination_options;
public string $lastFriday = '';
public string $lastMonday = '';
/**
* Mount the component.
*/
public function mount(): void
{
$this->name = Auth::user()->name;
$this->email = Auth::user()->email;
$this->gravatar_email = Auth::user()->gravatar_email;
$this->timezone = Auth::user()->timezone;
$this->preferred_backup_destination_id = Auth::user()->preferred_backup_destination_id ?? null;
$this->language = Auth::user()->language;
$this->receiving_weekly_summary_email = Auth::user()->isOptedInForWeeklySummary();
$this->pagination_count = Auth::user()->getAttribute('pagination_count');
$this->pagination_options = collect([15, 30, 50, 100])->mapWithKeys(fn($value) => [$value => "{$value} per page"]);
$user = Auth::user();
if (!$user) {
return;
}
$this->name = $user->name;
$this->email = $user->email;
$this->gravatar_email = $user->gravatar_email;
$this->timezone = $user->timezone;
$this->preferred_backup_destination_id = $user->preferred_backup_destination_id;
$this->language = $user->language;
$this->receiving_weekly_summary_email = $user->isOptedInForWeeklySummary();
$this->pagination_count = $user->pagination_count ?? 15; // Fallback to 15 if null
$this->pagination_options = $this->getPaginationOptions();
$this->setFormattedDates();
}
/**
* Update the user's profile information.
*/
public function updateProfileInformation(): void
{
$user = Auth::user();
if (!$user) {
return;
}
$validated = $this->validate();
$user->fill($validated);
$user->weekly_summary_opt_in_at = $validated['receiving_weekly_summary_email'] ? now() : null;
if ($user->isDirty('email')) {
$user->email_verified_at = null;
}
$user->save();
Toaster::success(__('Profile details saved.'));
}
/**
* Send email verification link.
*/
public function sendVerification(): void
{
$user = Auth::user();
if (!$user) {
return;
}
if ($user->hasVerifiedEmail()) {
$this->redirectIntended(route('overview', absolute: false));
return;
}
$user->sendEmailVerificationNotification();
Session::flash('status', 'verification-link-sent');
}
$validated = $this->validate([
/**
* Get the validation rules.
*
* @return array<string, array<int, string|Rule>>
*/
protected function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($user->id)],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore(Auth::id())],
'gravatar_email' => ['nullable', 'string', 'lowercase', 'email'],
'timezone' => ['required', 'string', 'max:255', Rule::in(timezone_identifiers_list())],
'preferred_backup_destination_id' => ['nullable', 'integer', Rule::exists('backup_destinations', 'id')->where('user_id', $user->id)],
'receiving_weekly_summary_email' => ['required', 'boolean'],
'preferred_backup_destination_id' => ['nullable', 'integer', Rule::exists('backup_destinations', 'id')->where('user_id', Auth::id())],
'receiving_weekly_summary_email' => ['boolean'],
'pagination_count' => ['required', 'integer', 'min:1', 'max:100', 'in:15,30,50,100'],
'language' => [
'required',
Expand All @@ -53,53 +115,95 @@ public function updateProfileInformation(): void
'alpha',
Rule::in(array_keys(config('app.available_languages')))
],
], $this->messages());
$user->fill($validated);
$user->weekly_summary_opt_in_at = $validated['receiving_weekly_summary_email'] ? now() : null;
if ($user->isDirty('email')) {
$user->email_verified_at = null;
}
$user->save();
Toaster::success(__('Profile details saved.'));
];
}
public function messages(): array
/**
* Get the error messages for the defined validation rules.
*
* @return array<string, string>
*/
protected function messages(): array
{
return [
'name.required' => __('Please enter a name.'),
'name.max' => __('Your name is too long. Please try a shorter name.'),
'email.required' => __('Please enter an email address.'),
'email.email' => __('Please enter an email address.'),
'gravatar_email.email' => __('Please enter an email address.'),
'email.email' => __('Please enter a valid email address.'),
'gravatar_email.email' => __('Please enter a valid email address for Gravatar.'),
'timezone.required' => __('Please specify a timezone.'),
'language.required' => __('Please specify a language for your account.'),
'pagination_count.required' => __('Please specify a valid pagination value for your account.'),
'pagination.count_integer' => __('The count must be an integer.')
'pagination_count.required' => __('The pagination count is required.'),
'pagination_count.integer' => __('The pagination count must be an integer.'),
'pagination_count.min' => __('The pagination count must be at least 1.'),
'pagination_count.max' => __('The pagination count may not be greater than 100.'),
'pagination_count.in' => __('The pagination count must be 15, 30, 50, or 100.'),
];
}
public function sendVerification(): void
/**
* Get the pagination options.
*
* @return Collection
*/
private function getPaginationOptions(): Collection
{
$user = Auth::user();
return collect([15, 30, 50, 100])->mapWithKeys(fn($value) => [$value => "{$value} per page"]);
}
if ($user->hasVerifiedEmail()) {
$this->redirectIntended(default: route('overview', absolute: false));
/**
* Set the formatted dates for last Friday and Monday.
*/
private function setFormattedDates(): void
{
$userLanguage = $this->getUserLanguage();
Carbon::setLocale($userLanguage);
return;
}
$this->lastFriday = $this->formatDate(Carbon::FRIDAY);
$this->lastMonday = $this->formatDate(Carbon::MONDAY);
}
$user->sendEmailVerificationNotification();
/**
* Get the user's language or fall back to the application default.
*
* @return string
*/
private function getUserLanguage(): string
{
$user = Auth::user();
Session::flash('status', 'verification-link-sent');
if (!$user || !$user->language) {
return App::getLocale();
}
return $this->isValidLanguage($user->language)
? $user->language
: App::getLocale();
}
}; ?>
/**
* Check if the given language is supported by the application.
*
* @param string $language
* @return bool
*/
private function isValidLanguage(string $language): bool
{
$availableLanguages = config('app.available_languages', ['en' => 'English']);
return array_key_exists($language, $availableLanguages);
}
/**
* Format the date for the given day of the week.
*
* @param int $day
* @return string
*/
private function formatDate(int $day): string
{
return Carbon::now()->previous($day)->isoFormat('dddd Do');
}
}?>
<x-form-wrapper>
<x-slot name="title">
{{ __('Your Profile') }}
Expand Down Expand Up @@ -214,21 +318,22 @@ class="mt-1 block w-full" autofocus autocomplete="gravatar_email"/>
model="receiving_weekly_summary_email"
/>
<x-input-explain>
{{ __('Get a summary of your weekly backup tasks every Monday morning.') }}
{{ __('Get a summary of your weekly backup tasks every Monday morning, this will be the task logs from :lastMonday to :lastFriday.', ['lastMonday' => $lastMonday, 'lastFriday' => $lastFriday]) }}
</x-input-explain>
</div>

<div>
<x-input-label for="pagination_count" :value="__('Items per Page')" />
<x-select wire:model="pagination_count" id="pagination_count" name="pagination_count" class="mt-1 block w-full">
<x-input-label for="pagination_count" :value="__('Items per Page')"/>
<x-select wire:model="pagination_count" id="pagination_count" name="pagination_count"
class="mt-1 block w-full">
@foreach ($pagination_options as $value => $label)
<option value="{{ $value }}">{{ $label }}</option>
@endforeach
</x-select>
<x-input-explain>
{{ __('Select the number of items you want to see per page in lists throughout the application. This setting affects how many backup tasks, servers, and other items are displayed at once.') }}
</x-input-explain>
<x-input-error class="mt-2" :messages="$errors->get('pagination_count')" />
<x-input-error class="mt-2" :messages="$errors->get('pagination_count')"/>
</div>

</div>
Expand Down
91 changes: 84 additions & 7 deletions tests/Feature/Profile/ProfileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -338,18 +338,95 @@
->assertNoRedirect();
});

test('pagination count is required', function (): void {
test('formatted dates are localized correctly', function (): void {
Config::set('app.available_languages', [
'en' => 'English',
'fr' => 'French',
]);

$user = User::factory()->create(['language' => 'fr']);

$this->actingAs($user);

$testable = Volt::test('profile.update-profile-information-form');

// Assert that the lastMonday and lastFriday are in French
expect($testable->get('lastMonday'))->toContain('lundi');
expect($testable->get('lastFriday'))->toContain('vendredi');
});

test('getUserLanguage returns correct language', function (): void {
Config::set('app.available_languages', [
'en' => 'English',
'fr' => 'French',
]);

$user = User::factory()->create(['language' => 'fr']);

$this->actingAs($user);

$testable = Volt::test('profile.update-profile-information-form');

$reflectionClass = new ReflectionClass($testable->instance());
$reflectionMethod = $reflectionClass->getMethod('getUserLanguage');

expect($reflectionMethod->invoke($testable->instance()))->toBe('fr');
});

test('isValidLanguage correctly validates language codes', function (): void {
Config::set('app.available_languages', [
'en' => 'English',
'fr' => 'French',
]);

$user = User::factory()->create();

$this->actingAs($user);

$testable = Volt::test('profile.update-profile-information-form');

$reflectionClass = new ReflectionClass($testable->instance());
$reflectionMethod = $reflectionClass->getMethod('isValidLanguage');

expect($reflectionMethod->invoke($testable->instance(), 'en'))->toBeTrue();
expect($reflectionMethod->invoke($testable->instance(), 'fr'))->toBeTrue();
expect($reflectionMethod->invoke($testable->instance(), 'de'))->toBeFalse();
});

test('weekly summary email can be opted out', function (): void {
$user = User::factory()->create([
'weekly_summary_opt_in_at' => now(),
]);

$this->actingAs($user);

$component = Volt::test('profile.update-profile-information-form')
->set('name', $user->name)
->set('email', $user->email)
->set('pagination_count', null)
->set('receiving_weekly_summary_email', false)
->set('pagination_count', 15)
->call('updateProfileInformation');

$component
->assertHasErrors(['pagination_count' => 'required'])
->assertNoRedirect();
$component->assertHasNoErrors();

$user->refresh();

expect($user->weekly_summary_opt_in_at)->toBeNull();
});

test('getPaginationOptions returns correct options', function (): void {
$user = User::factory()->create();

$this->actingAs($user);

$testable = Volt::test('profile.update-profile-information-form');

$reflectionClass = new ReflectionClass($testable->instance());
$reflectionMethod = $reflectionClass->getMethod('getPaginationOptions');

$options = $reflectionMethod->invoke($testable->instance());

expect($options)->toHaveCount(4)
->and($options->get(15))->toBe('15 per page')
->and($options->get(30))->toBe('30 per page')
->and($options->get(50))->toBe('50 per page')
->and($options->get(100))->toBe('100 per page');
});

0 comments on commit d851fb1

Please sign in to comment.