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

+47 -3
View File
@@ -14,13 +14,13 @@ const library = {
finished: [{appid: 620, name: 'Portal 2', playtimeMinutes: 900, status: 'COMPLETED', rating: 5}],
};
function mockBackends({profileStatus = 200, libraryStatus = 200} = {}) {
function mockBackends({profileStatus = 200, libraryStatus = 200, style, answer = library} = {}) {
globalThis.fetch = vi.fn(async (url) => {
if (url.includes('/api/public/profiles/library')) {
return {ok: libraryStatus === 200, status: libraryStatus, json: async () => library};
return {ok: libraryStatus === 200, status: libraryStatus, json: async () => answer};
}
return {ok: profileStatus === 200, status: profileStatus,
json: async () => ({displayName: 'Lobo', memberSince: '2026-03-02', ticket: 'the-ticket'})};
json: async () => ({displayName: 'Lobo', memberSince: '2026-03-02', ticket: 'the-ticket', style})};
});
}
@@ -74,6 +74,50 @@ describe('PublicProfilePage', () => {
delete library.stats;
});
it('applies the owner\'s accent and corners to the page only, with banner and background', async () => {
mockBackends({style: {accent: 'red', corners: 'round', bannerAppid: 620, backgroundAppid: 220, sections: ['PLAYING']}});
const {container} = renderPage();
await screen.findByText('Half-Life 2');
const page = container.querySelector('[data-accent="red"]');
expect(page.dataset.corners).toBe('round');
expect(document.documentElement.dataset.accent).not.toBe('red');
expect([...container.querySelectorAll('img')].map((img) => img.getAttribute('src')))
.toEqual(expect.arrayContaining([expect.stringContaining('/620/'), expect.stringContaining('/220/')]));
});
it('shows the sections in the owner\'s order and leaves out hidden ones', async () => {
mockBackends({
style: {sections: ['FINISHED', 'PLAYING', 'SUMMARY']},
answer: {...library, byStatus: null, finishedGames: 60},
});
const {container} = renderPage();
await screen.findByText('Portal 2');
expect([...container.querySelectorAll('[data-section]')].map((el) => el.dataset.section))
.toEqual(['FINISHED', 'PLAYING', 'SUMMARY']);
expect(screen.getByText('60')).toBeTruthy();
expect(screen.queryByText('BACKLOG.STATUS.BACKLOG')).toBeNull();
});
it('leaves out the status bar of a profile without games', async () => {
mockBackends({answer: {...library, totalGames: 0, byStatus: {BACKLOG: 0, PLAYING: 0}, playing: [], finished: []}});
const {container} = renderPage();
await screen.findByText('PROFILE.GAMES');
expect([...container.querySelectorAll('[data-section]')].map((el) => el.dataset.section)).toEqual(['SUMMARY']);
});
it('shows every section for an Auth without a style', async () => {
mockBackends();
const {container} = renderPage();
await screen.findByText('Portal 2');
expect([...container.querySelectorAll('[data-section]')].map((el) => el.dataset.section))
.toEqual(['SUMMARY', 'STATUS', 'PLAYING', 'FINISHED']);
expect(container.querySelector('[data-accent="violet"]')).toBeTruthy();
});
it('says the profile is unknown or private on a 404', async () => {
mockBackends({profileStatus: 404});
renderPage();