get(route('profile.edit')); $response->assertRedirect(route('login')); }); test('guests are redirected to the login page for profile update', function () { $response = $this->patch(route('profile.update'), [ 'email' => 'test@example.com', 'username' => 'testuser', 'full_name' => 'Test User', ]); $response->assertRedirect(route('login')); }); /* |-------------------------------------------------------------------------- | PROFILE EDIT PAGE |-------------------------------------------------------------------------- */ test('profile page is displayed', function () { $user = User::factory()->create(); $response = $this ->actingAs($user) ->get(route('profile.edit')); $response->assertOk(); }); test('profile page renders correct inertia component', function () { $user = User::factory()->create(); $this->actingAs($user) ->get(route('profile.edit')) ->assertInertia(fn (Assert $page) => $page ->component('settings/profile') ); }); test('profile page passes user data to view', function () { $user = User::factory()->create([ 'email' => 'john@example.com', 'username' => 'johndoe', ]); $user->userProfile()->create([ 'full_name' => 'John Doe', 'phone_number' => '08123456789', 'gender' => 'male', 'birth_date' => '1990-05-15', 'address' => 'Jl. Sudirman No. 1', ]); $this->actingAs($user) ->get(route('profile.edit')) ->assertInertia(fn (Assert $page) => $page ->where('user.id', $user->id) ->where('user.email', 'john@example.com') ->where('user.username', 'johndoe') ->where('user.userProfile.full_name', 'John Doe') ->where('user.userProfile.phone_number', '08123456789') ->where('user.userProfile.gender', 'male') ->where('user.userProfile.birth_date', '1990-05-15') ->where('user.userProfile.address', 'Jl. Sudirman No. 1') ); }); test('profile page works when user has no profile', function () { $user = User::factory()->create(); $this->actingAs($user) ->get(route('profile.edit')) ->assertInertia(fn (Assert $page) => $page ->where('user.id', $user->id) ->where('user.email', $user->email) ->where('user.username', $user->username) ->where('user.userProfile', null) ); }); test('profile page passes mustVerifyEmail flag', function () { $user = User::factory()->create(); $this->actingAs($user) ->get(route('profile.edit')) ->assertInertia(fn (Assert $page) => $page ->has('mustVerifyEmail') ); }); /* |-------------------------------------------------------------------------- | PROFILE UPDATE - SUCCESS |-------------------------------------------------------------------------- */ test('profile information can be updated with all fields', function () { $user = User::factory()->create(['username' => 'originaluser']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'updateduser', 'full_name' => 'Updated Name', 'phone_number' => '08987654321', 'gender' => 'female', 'birth_date' => '1995-03-20', 'address' => 'Jl. Thamrin No. 2', ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('profile.edit')); $user->refresh(); expect($user->username)->toBe('updateduser'); expect($user->userProfile->full_name)->toBe('Updated Name'); expect($user->userProfile->phone_number)->toBe('08987654321'); expect($user->userProfile->gender->value)->toBe('female'); expect($user->userProfile->birth_date->format('Y-m-d'))->toBe('1995-03-20'); expect($user->userProfile->address)->toBe('Jl. Thamrin No. 2'); }); test('profile can be updated with only required fields', function () { $user = User::factory()->create(['username' => 'minimaluser']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'minimaluser', 'full_name' => 'Minimal Update', ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('profile.edit')); expect($user->refresh()->userProfile->full_name)->toBe('Minimal Update'); }); test('profile update creates user profile if not exists', function () { $user = User::factory()->create(['username' => 'newprofile']); expect($user->userProfile)->toBeNull(); $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'newprofile', 'full_name' => 'New Profile', 'phone_number' => '08111111111', 'gender' => 'male', 'birth_date' => '2000-01-01', 'address' => 'New Address', ]); $user->refresh(); expect($user->userProfile)->not->toBeNull(); expect($user->userProfile->full_name)->toBe('New Profile'); expect($user->userProfile->phone_number)->toBe('08111111111'); }); test('profile update updates existing user profile', function () { $user = User::factory()->create(['username' => 'existingprofile']); $user->userProfile()->create([ 'full_name' => 'Old Name', 'phone_number' => '08000000000', 'gender' => 'male', 'address' => 'Old Address', ]); $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'existingprofile', 'full_name' => 'New Name', 'phone_number' => '08999999999', 'gender' => 'female', 'address' => 'New Address', ]); $user->refresh(); expect($user->userProfile->full_name)->toBe('New Name'); expect($user->userProfile->phone_number)->toBe('08999999999'); expect($user->userProfile->gender->value)->toBe('female'); expect($user->userProfile->address)->toBe('New Address'); $this->assertDatabaseCount('user_profiles', 1); }); test('profile update with null optional fields saves nulls', function () { $user = User::factory()->create(['username' => 'nulltest']); $user->userProfile()->create([ 'full_name' => 'Has Data', 'phone_number' => '08123456789', 'gender' => 'male', 'birth_date' => '1990-01-01', 'address' => 'Some Address', ]); $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'nulltest', 'full_name' => 'Still Has Name', 'phone_number' => null, 'gender' => null, 'birth_date' => null, 'address' => null, ]); $user->refresh(); expect($user->userProfile->full_name)->toBe('Still Has Name'); expect($user->userProfile->phone_number)->toBeNull(); expect($user->userProfile->gender)->toBeNull(); expect($user->userProfile->birth_date)->toBeNull(); expect($user->userProfile->address)->toBeNull(); }); /* |-------------------------------------------------------------------------- | PROFILE UPDATE - VALIDATION: EMAIL |-------------------------------------------------------------------------- */ test('email is required', function () { $user = User::factory()->create(['username' => 'emailreq']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'username' => 'emailreq', 'full_name' => 'Test', ]); $response->assertSessionHasErrors('email'); }); test('email must be valid format', function () { $user = User::factory()->create(['username' => 'emailfmt']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => 'not-an-email', 'username' => 'emailfmt', 'full_name' => 'Test', ]); $response->assertSessionHasErrors('email'); }); test('email must not exceed 255 characters', function () { $user = User::factory()->create(['username' => 'emailmax']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => str_repeat('a', 246).'@example.com', 'username' => 'emailmax', 'full_name' => 'Test', ]); $response->assertSessionHasErrors('email'); }); test('email must be unique excluding self', function () { $user = User::factory()->create(['email' => 'user1@example.com', 'username' => 'uniqueemail1']); User::factory()->create(['email' => 'user2@example.com', 'username' => 'uniqueemail2']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => 'user2@example.com', 'username' => 'uniqueemail1', 'full_name' => 'Test', ]); $response->assertSessionHasErrors('email'); }); test('user can keep their own email on update', function () { $user = User::factory()->create(['email' => 'keep@example.com', 'username' => 'keepemail']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => 'keep@example.com', 'username' => 'keepemail', 'full_name' => 'Test', ]); $response->assertSessionHasNoErrors(); }); /* |-------------------------------------------------------------------------- | PROFILE UPDATE - VALIDATION: USERNAME |-------------------------------------------------------------------------- */ test('username is required', function () { $user = User::factory()->create(['username' => 'usernamereq']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'full_name' => 'Test', ]); $response->assertSessionHasErrors('username'); }); test('username must not exceed 20 characters', function () { $user = User::factory()->create(['username' => 'usernamemax']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => str_repeat('a', 21), 'full_name' => 'Test', ]); $response->assertSessionHasErrors('username'); }); test('username must be alpha_dash', function () { $user = User::factory()->create(['username' => 'alphauser']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'has spaces!', 'full_name' => 'Test', ]); $response->assertSessionHasErrors('username'); }); test('username allows dashes and underscores', function () { $user = User::factory()->create(['username' => 'dashuser']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'user-name_123', 'full_name' => 'Test', ]); $response->assertSessionHasNoErrors(); }); test('username must be unique excluding self', function () { $user = User::factory()->create(['username' => 'uniqueuser1']); User::factory()->create(['username' => 'uniqueuser2']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'uniqueuser2', 'full_name' => 'Test', ]); $response->assertSessionHasErrors('username'); }); test('user can keep their own username on update', function () { $user = User::factory()->create(['username' => 'keepuser']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'keepuser', 'full_name' => 'Test', ]); $response->assertSessionHasNoErrors(); }); /* |-------------------------------------------------------------------------- | PROFILE UPDATE - VALIDATION: FULL NAME |-------------------------------------------------------------------------- */ test('full_name is required', function () { $user = User::factory()->create(['username' => 'fullnamereq']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'fullnamereq', ]); $response->assertSessionHasErrors('full_name'); }); test('full_name must not exceed 200 characters', function () { $user = User::factory()->create(['username' => 'fullnamemax']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'fullnamemax', 'full_name' => str_repeat('a', 201), ]); $response->assertSessionHasErrors('full_name'); }); /* |-------------------------------------------------------------------------- | PROFILE UPDATE - VALIDATION: OPTIONAL FIELDS |-------------------------------------------------------------------------- */ test('phone_number is optional', function () { $user = User::factory()->create(['username' => 'phonetest']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'phonetest', 'full_name' => 'Test', 'phone_number' => null, ]); $response->assertSessionHasNoErrors(); }); test('phone_number must not exceed 20 characters', function () { $user = User::factory()->create(['username' => 'phonemax']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'phonemax', 'full_name' => 'Test', 'phone_number' => str_repeat('1', 21), ]); $response->assertSessionHasErrors('phone_number'); }); test('gender must be valid enum value', function () { $user = User::factory()->create(['username' => 'genderbad']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'genderbad', 'full_name' => 'Test', 'gender' => 'other', ]); $response->assertSessionHasErrors('gender'); }); test('gender accepts male', function () { $user = User::factory()->create(['username' => 'gendermale']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'gendermale', 'full_name' => 'Test', 'gender' => 'male', ]); $response->assertSessionHasNoErrors(); }); test('gender accepts female', function () { $user = User::factory()->create(['username' => 'genderfemale']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'genderfemale', 'full_name' => 'Test', 'gender' => 'female', ]); $response->assertSessionHasNoErrors(); }); test('birth_date is optional', function () { $user = User::factory()->create(['username' => 'birthnull']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'birthnull', 'full_name' => 'Test', 'birth_date' => null, ]); $response->assertSessionHasNoErrors(); }); test('birth_date must be valid date', function () { $user = User::factory()->create(['username' => 'birthbad']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'birthbad', 'full_name' => 'Test', 'birth_date' => 'not-a-date', ]); $response->assertSessionHasErrors('birth_date'); }); test('address is optional', function () { $user = User::factory()->create(['username' => 'addrnull']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'addrnull', 'full_name' => 'Test', 'address' => null, ]); $response->assertSessionHasNoErrors(); }); /* |-------------------------------------------------------------------------- | PROFILE UPDATE - VALIDATION: EMPTY REQUEST |-------------------------------------------------------------------------- */ test('user cannot update profile without submitting any data', function () { $user = User::factory()->create(['username' => 'emptyreq']); $response = $this ->actingAs($user) ->patch(route('profile.update'), []); $response->assertSessionHasErrors(['email', 'username', 'full_name']); }); /* |-------------------------------------------------------------------------- | PROFILE UPDATE - TOAST / FLASH |-------------------------------------------------------------------------- */ test('profile update flashes success toast via inertia', function () { $user = User::factory()->create(['username' => 'toastuser']); $response = $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'toastuser', 'full_name' => 'Toast Test', ]); $response->assertRedirect(); }); /* |-------------------------------------------------------------------------- | PROFILE UPDATE - DATA INTEGRITY |-------------------------------------------------------------------------- */ test('profile update preserves other user fields', function () { $user = User::factory()->create(['username' => 'preservetest']); $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'preservetest', 'full_name' => 'New Name', ]); $user->refresh(); expect(Hash::check('password', $user->password))->toBeTrue(); }); test('profile update with realistic indonesian data', function () { $user = User::factory()->create(['email' => 'budi.santoso@gmail.com', 'username' => 'budisan']); $this ->actingAs($user) ->patch(route('profile.update'), [ 'email' => 'budi.santoso@gmail.com', 'username' => 'budisan', 'full_name' => 'Budi Santoso', 'phone_number' => '08123456789', 'gender' => 'male', 'birth_date' => '1990-05-15', 'address' => 'Jl. Sudirman No. 123, Jakarta Selatan', ]); $user->refresh(); expect($user->email)->toBe('budi.santoso@gmail.com'); expect($user->username)->toBe('budisan'); expect($user->userProfile->full_name)->toBe('Budi Santoso'); expect($user->userProfile->phone_number)->toBe('08123456789'); expect($user->userProfile->gender->value)->toBe('male'); expect($user->userProfile->birth_date->format('Y-m-d'))->toBe('1990-05-15'); expect($user->userProfile->address)->toBe('Jl. Sudirman No. 123, Jakarta Selatan'); }); /* |-------------------------------------------------------------------------- | ROUTE TESTS |-------------------------------------------------------------------------- */ test('settings redirect goes to profile edit', function () { $user = User::factory()->create(['username' => 'redirtest']); $response = $this ->actingAs($user) ->get('/settings'); $response->assertRedirect('/settings/profile'); }); /* |-------------------------------------------------------------------------- | REALISTIC SCENARIOS |-------------------------------------------------------------------------- */ test('user updates profile then views it again', function () { $user = User::factory()->create(['username' => 'scenariouser']); $this->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'scenariouser', 'full_name' => 'Scenario User', 'phone_number' => '08123456789', 'gender' => 'female', 'birth_date' => '1995-08-20', 'address' => 'Jl. Gatot Subroto No. 45', ]); $this->actingAs($user->fresh()) ->get(route('profile.edit')) ->assertInertia(fn (Assert $page) => $page ->where('user.userProfile.full_name', 'Scenario User') ->where('user.userProfile.phone_number', '08123456789') ->where('user.userProfile.gender', 'female') ->where('user.userProfile.birth_date', '1995-08-20') ->where('user.userProfile.address', 'Jl. Gatot Subroto No. 45') ); }); test('user updates profile multiple times keeps only latest data', function () { $user = User::factory()->create(['username' => 'multitimes']); $this->actingAs($user) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'multitimes', 'full_name' => 'First Update', 'phone_number' => '08111111111', ]); $this->actingAs($user->fresh()) ->patch(route('profile.update'), [ 'email' => $user->email, 'username' => 'multitimes', 'full_name' => 'Second Update', 'phone_number' => '08222222222', ]); $user->refresh(); expect($user->userProfile->full_name)->toBe('Second Update'); expect($user->userProfile->phone_number)->toBe('08222222222'); $this->assertDatabaseCount('user_profiles', 1); });