Skip to content

Commit 3c68e0f

Browse files
committed
fix: settings panel
1 parent 5467760 commit 3c68e0f

File tree

4 files changed

+246
-31
lines changed

4 files changed

+246
-31
lines changed

e2e/settings.spec.ts

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import { test, expect } from '@playwright/test';
2+
import { register, generateUniqueUser } from './helpers/auth';
3+
import { getToken, getCurrentUser } from './helpers/debug';
4+
5+
const API_BASE = 'https://api.realworld.show/api';
6+
7+
/**
8+
* Tests for the settings/profile update feature.
9+
* Each test updates a specific field and verifies the result.
10+
*/
11+
12+
test.describe('Settings - Profile Updates', () => {
13+
test.afterEach(async ({ context }) => {
14+
await context.close();
15+
await new Promise(resolve => setTimeout(resolve, 500));
16+
});
17+
18+
test('should update bio only', async ({ page }) => {
19+
const user = generateUniqueUser();
20+
await register(page, user.username, user.email, user.password);
21+
22+
// Go to settings
23+
await page.goto('/settings');
24+
await expect(page.locator('input[formControlName="username"]')).toHaveValue(user.username);
25+
26+
// Update bio
27+
const newBio = `Bio updated at ${Date.now()}`;
28+
await page.fill('textarea[formControlName="bio"]', newBio);
29+
30+
// Submit and wait for API response
31+
const responsePromise = page.waitForResponse(
32+
res => res.url().includes('/user') && res.request().method() === 'PUT',
33+
);
34+
await page.click('button[type="submit"]');
35+
const response = await responsePromise;
36+
37+
// Verify API response
38+
expect(response.status()).toBe(200);
39+
const responseBody = await response.json();
40+
expect(responseBody.user.bio).toBe(newBio);
41+
expect(responseBody.user.username).toBe(user.username);
42+
expect(responseBody.user.email).toBe(user.email);
43+
44+
// Verify navigation happened
45+
await expect(page).toHaveURL(new RegExp(`/profile/${user.username}`));
46+
47+
// Verify token is still present (auth not corrupted)
48+
const token = await getToken(page);
49+
expect(token).not.toBeNull();
50+
51+
// Verify current user is updated
52+
const currentUser = await getCurrentUser(page);
53+
expect(currentUser).not.toBeNull();
54+
expect(currentUser?.username).toBe(user.username);
55+
expect(currentUser?.bio).toBe(newBio);
56+
});
57+
58+
test('should update image only', async ({ page }) => {
59+
const user = generateUniqueUser();
60+
await register(page, user.username, user.email, user.password);
61+
62+
await page.goto('/settings');
63+
await expect(page.locator('input[formControlName="username"]')).toHaveValue(user.username);
64+
65+
// Update image
66+
const newImage = 'https://api.realworld.io/images/smiley-cyrus.jpeg';
67+
await page.fill('input[formControlName="image"]', newImage);
68+
69+
// Submit and wait for API response
70+
const responsePromise = page.waitForResponse(
71+
res => res.url().includes('/user') && res.request().method() === 'PUT',
72+
);
73+
await page.click('button[type="submit"]');
74+
const response = await responsePromise;
75+
76+
// Verify API response
77+
expect(response.status()).toBe(200);
78+
const responseBody = await response.json();
79+
expect(responseBody.user.image).toBe(newImage);
80+
expect(responseBody.user.username).toBe(user.username);
81+
82+
// Verify navigation happened
83+
await expect(page).toHaveURL(new RegExp(`/profile/${user.username}`));
84+
85+
// Verify token is still present
86+
const token = await getToken(page);
87+
expect(token).not.toBeNull();
88+
89+
// Verify current user is updated
90+
const currentUser = await getCurrentUser(page);
91+
expect(currentUser).not.toBeNull();
92+
expect(currentUser?.image).toBe(newImage);
93+
});
94+
95+
test('should update bio and image together', async ({ page }) => {
96+
const user = generateUniqueUser();
97+
await register(page, user.username, user.email, user.password);
98+
99+
await page.goto('/settings');
100+
101+
// Update both fields
102+
const newBio = `Multi-update bio ${Date.now()}`;
103+
const newImage = 'https://api.realworld.io/images/smiley-cyrus.jpeg';
104+
await page.fill('textarea[formControlName="bio"]', newBio);
105+
await page.fill('input[formControlName="image"]', newImage);
106+
107+
// Submit and wait for API response
108+
const responsePromise = page.waitForResponse(
109+
res => res.url().includes('/user') && res.request().method() === 'PUT',
110+
);
111+
await page.click('button[type="submit"]');
112+
const response = await responsePromise;
113+
114+
// Verify API response has both updates
115+
expect(response.status()).toBe(200);
116+
const responseBody = await response.json();
117+
expect(responseBody.user.bio).toBe(newBio);
118+
expect(responseBody.user.image).toBe(newImage);
119+
expect(responseBody.user.username).toBe(user.username);
120+
expect(responseBody.user.email).toBe(user.email);
121+
122+
// Verify navigation
123+
await expect(page).toHaveURL(new RegExp(`/profile/${user.username}`));
124+
125+
// Verify auth state
126+
const token = await getToken(page);
127+
expect(token).not.toBeNull();
128+
129+
const currentUser = await getCurrentUser(page);
130+
expect(currentUser?.bio).toBe(newBio);
131+
expect(currentUser?.image).toBe(newImage);
132+
});
133+
134+
test('should display updated bio on profile page', async ({ page }) => {
135+
const user = generateUniqueUser();
136+
await register(page, user.username, user.email, user.password);
137+
138+
await page.goto('/settings');
139+
140+
const newBio = `Visible bio ${Date.now()}`;
141+
await page.fill('textarea[formControlName="bio"]', newBio);
142+
143+
await Promise.all([
144+
page.waitForResponse(res => res.url().includes('/user') && res.request().method() === 'PUT'),
145+
page.click('button[type="submit"]'),
146+
]);
147+
148+
// Wait for profile page
149+
await expect(page).toHaveURL(new RegExp(`/profile/${user.username}`));
150+
151+
// Verify bio is displayed on profile
152+
await expect(page.locator('.user-info')).toContainText(newBio);
153+
});
154+
155+
test('should display updated image on profile page', async ({ page }) => {
156+
const user = generateUniqueUser();
157+
await register(page, user.username, user.email, user.password);
158+
159+
await page.goto('/settings');
160+
161+
const newImage = 'https://api.realworld.io/images/smiley-cyrus.jpeg';
162+
await page.fill('input[formControlName="image"]', newImage);
163+
164+
await Promise.all([
165+
page.waitForResponse(res => res.url().includes('/user') && res.request().method() === 'PUT'),
166+
page.click('button[type="submit"]'),
167+
]);
168+
169+
// Wait for profile page
170+
await expect(page).toHaveURL(new RegExp(`/profile/${user.username}`));
171+
172+
// Verify image is displayed on profile (use .user-img to avoid matching navbar)
173+
await expect(page.locator(`.user-img[src="${newImage}"]`)).toBeVisible();
174+
});
175+
176+
test('should preserve username in navbar after update', async ({ page }) => {
177+
const user = generateUniqueUser();
178+
await register(page, user.username, user.email, user.password);
179+
180+
// Verify navbar shows username before update
181+
await expect(page.locator(`a[href="/profile/${user.username}"]`)).toBeVisible();
182+
183+
await page.goto('/settings');
184+
185+
const newBio = `Navbar test ${Date.now()}`;
186+
await page.fill('textarea[formControlName="bio"]', newBio);
187+
188+
await Promise.all([
189+
page.waitForResponse(res => res.url().includes('/user') && res.request().method() === 'PUT'),
190+
page.click('button[type="submit"]'),
191+
]);
192+
193+
// Verify navbar STILL shows username after update (not corrupted)
194+
await expect(page.locator(`a[href="/profile/${user.username}"]`)).toBeVisible();
195+
});
196+
197+
test('should allow navigation to settings again after update', async ({ page }) => {
198+
const user = generateUniqueUser();
199+
await register(page, user.username, user.email, user.password);
200+
201+
await page.goto('/settings');
202+
203+
const bio1 = `First update ${Date.now()}`;
204+
await page.fill('textarea[formControlName="bio"]', bio1);
205+
206+
await Promise.all([
207+
page.waitForResponse(res => res.url().includes('/user') && res.request().method() === 'PUT'),
208+
page.click('button[type="submit"]'),
209+
]);
210+
211+
await expect(page).toHaveURL(new RegExp(`/profile/${user.username}`));
212+
213+
// Go back to settings
214+
await page.goto('/settings');
215+
await expect(page.locator('input[formControlName="username"]')).toHaveValue(user.username);
216+
217+
// Verify previous update persisted
218+
await expect(page.locator('textarea[formControlName="bio"]')).toHaveValue(bio1);
219+
220+
// Make another update
221+
const bio2 = `Second update ${Date.now()}`;
222+
await page.fill('textarea[formControlName="bio"]', bio2);
223+
224+
await Promise.all([
225+
page.waitForResponse(res => res.url().includes('/user') && res.request().method() === 'PUT'),
226+
page.click('button[type="submit"]'),
227+
]);
228+
229+
// Verify second update worked
230+
const currentUser = await getCurrentUser(page);
231+
expect(currentUser?.bio).toBe(bio2);
232+
});
233+
});

e2e/social.spec.ts

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { test, expect } from '@playwright/test';
22
import { register, generateUniqueUser } from './helpers/auth';
33
import { createArticle, generateUniqueArticle } from './helpers/articles';
4-
import { followUser, unfollowUser, updateProfile } from './helpers/profile';
4+
import { followUser, unfollowUser } from './helpers/profile';
55

66
test.describe('Social Features', () => {
77
test.afterEach(async ({ context }) => {
@@ -77,35 +77,6 @@ test.describe('Social Features', () => {
7777
await expect(page.locator('.article-preview').first()).toBeVisible();
7878
});
7979

80-
// SKIPPED: Profile edit feature is currently broken
81-
// Issue: After updating profile settings, the user state gets corrupted/wiped
82-
// Symptoms:
83-
// - Navigation to /profile/:username fails and redirects to home page
84-
// - Username disappears from navbar (profile link shows /profile/ with empty username)
85-
// - Angular's currentUser observable doesn't emit updated user data after settings save
86-
// Root Cause: Profile update functionality appears to wipe or corrupt the authentication state
87-
// TODO: Fix the settings/profile update feature before enabling this test
88-
test.skip('should update user profile', async ({ page }) => {
89-
const user = generateUniqueUser();
90-
await register(page, user.username, user.email, user.password);
91-
92-
const updates = {
93-
bio: 'This is my updated bio!',
94-
image: 'https://api.realworld.io/images/smiley-cyrus.jpeg',
95-
};
96-
97-
await updateProfile(page, updates);
98-
99-
// Navigate to profile page (updateProfile now waits for API to complete)
100-
await page.goto(`/profile/${user.username}`, { waitUntil: 'networkidle' });
101-
102-
// Should show updated bio
103-
await expect(page.locator('.user-info')).toContainText(updates.bio);
104-
105-
// Should show updated image
106-
await expect(page.locator('img[src="' + updates.image + '"]')).toBeVisible();
107-
});
108-
10980
test('should display user articles on profile', async ({ page }) => {
11081
const user = generateUniqueUser();
11182
await register(page, user.username, user.email, user.password);

src/app/core/auth/services/user.service.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ export class UserService {
5454

5555
public isAuthenticated = this.currentUser.pipe(map(user => !!user));
5656

57+
/**
58+
* Synchronously get the current cached user value.
59+
* Returns null if not authenticated or still loading.
60+
*/
61+
getCurrentUserSync(): User | null {
62+
return this.currentUserSubject.getValue();
63+
}
64+
5765
private retryAttempt = 0;
5866
private retrySubscription: Subscription | null = null;
5967

src/app/features/settings/settings.component.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ export default class SettingsComponent implements OnInit {
4343
) {}
4444

4545
ngOnInit(): void {
46-
this.settingsForm.patchValue(this.userService.getCurrentUser() as Partial<User>);
46+
const user = this.userService.getCurrentUserSync();
47+
if (user) {
48+
this.settingsForm.patchValue(user);
49+
}
4750
}
4851

4952
logout(): void {

0 commit comments

Comments
 (0)