feat(profile): let users design their public profile

This commit is contained in:
lobo committed 2026-09-28 14:20:14 +02:00
1 parent a5458b3f86
commit b55fcfec92
20 files changed
+674 -138

No files matched your search

+64
View File
@@ -242,6 +242,70 @@ describe('GameDetail of a game added by hand', () => {
expect(screen.queryByText('MANUAL_GAME.TITLE')).toBeNull();
});
it('keeps a section hidden after the page loaded when it sets the profile art', async () => {
let sections = ['PLAYING'];
globalThis.fetch = vi.fn(async (url, init) => ({ok: true, status: init?.method === 'PUT' ? 204 : 200,
json: async () => (url.includes('/api/users/me')
? {role: 'USER', settings: {profileStyle: {accent: 'red', corners: 'round', sections}}}
: {...game, source: 'STEAM'})}));
renderDetail();
const menu = await screen.findByRole('button', {name: 'GAME_DETAIL.PROFILE_ART'});
sections = []; // the owner hides PLAYING in Settings, e.g. in a second tab
fireEvent.keyDown(menu, {key: 'Enter'});
fireEvent.click(await screen.findByRole('menuitemcheckbox', {name: 'GAME_DETAIL.PROFILE_BANNER'}));
await waitFor(() => expect(fetch.mock.calls.some(([u, init]) => init?.method === 'PUT' && u.endsWith('/profile-style'))).toBe(true));
const sent = fetch.mock.calls.filter(([u, init]) => init?.method === 'PUT' && u.endsWith('/profile-style'))
.map(([, init]) => JSON.parse(init.body));
expect(sent.filter((body) => body.sections?.includes('PLAYING'))).toEqual([]);
});
it('keeps a banner picked in another tab when it sets this game as banner', async () => {
let bannerAppid = 220;
globalThis.fetch = vi.fn(async (url, init) => ({ok: true, status: init?.method === 'PUT' ? 204 : 200,
json: async () => (url.includes('/api/users/me')
? {role: 'USER', settings: {profileStyle: {bannerAppid, sections: ['PLAYING']}}}
: {...game, source: 'STEAM'})}));
renderDetail();
const menu = await screen.findByRole('button', {name: 'GAME_DETAIL.PROFILE_ART'});
bannerAppid = 620; // another tab picked another game
fireEvent.keyDown(menu, {key: 'Enter'});
fireEvent.click(await screen.findByRole('menuitemcheckbox', {name: 'GAME_DETAIL.PROFILE_BANNER'}));
await waitFor(() => expect(fetch.mock.calls.some(([u, init]) => init?.method === 'PUT' && u.endsWith('/profile-style'))).toBe(true));
const [, init] = fetch.mock.calls.find(([u, i]) => i?.method === 'PUT' && u.endsWith('/profile-style'));
expect(JSON.parse(init.body).bannerAppid).toBe(220);
});
it('uses the art as profile banner and removes it on the second click', async () => {
let stored = {accent: 'red', corners: 'round', sections: ['PLAYING']};
globalThis.fetch = vi.fn(async (url, init) => {
if (init?.method === 'PUT') stored = JSON.parse(init.body);
return {ok: true, status: init?.method === 'PUT' ? 204 : 200,
json: async () => (url.includes('/api/users/me')
? {role: 'USER', settings: {profileStyle: stored}}
: {...game, source: 'STEAM'})};
});
const styleBodies = () => fetch.mock.calls.filter(([url, init]) => init?.method === 'PUT' && url.endsWith('/profile-style'))
.map(([, init]) => JSON.parse(init.body));
const openMenu = async () => fireEvent.keyDown(await screen.findByRole('button', {name: 'GAME_DETAIL.PROFILE_ART'}), {key: 'Enter'});
renderDetail();
await openMenu();
fireEvent.click(await screen.findByRole('menuitemcheckbox', {name: 'GAME_DETAIL.PROFILE_BANNER'}));
await waitFor(() => expect(styleBodies()).toEqual([{accent: 'red', corners: 'round', bannerAppid: 220,
backgroundAppid: null, sections: ['PLAYING']}]));
await openMenu();
const banner = await screen.findByRole('menuitemcheckbox', {name: 'GAME_DETAIL.PROFILE_BANNER'});
expect(banner.getAttribute('aria-checked')).toBe('true');
fireEvent.click(banner);
await waitFor(() => expect(styleBodies()).toHaveLength(2));
expect(styleBodies()[1].bannerAppid).toBeNull();
});
describe('corrections', () => {
const loggedInAs = (me) => {
globalThis.fetch = vi.fn(async (url) => ({ok: true, status: 200,