From 03259d0515af18af191d398d9aa51dea9c132ce7 Mon Sep 17 00:00:00 2001 From: lobo Date: Thu, 24 Sep 2026 13:17:12 +0200 Subject: [PATCH] feat(library): sort by rating and priority --- public/i18n/de.json | 4 +++- public/i18n/en.json | 4 +++- src/pages/GameLibrary.jsx | 2 ++ src/pages/GameLibrary.test.jsx | 29 +++++++++++++++++++---------- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/public/i18n/de.json b/public/i18n/de.json index 33ed291..f01c24e 100644 --- a/public/i18n/de.json +++ b/public/i18n/de.json @@ -123,7 +123,9 @@ "LAST_SYNCED": "Zuletzt synchronisiert {{when}}", "FILTER_ALL": "Alle", "SEARCH_PLACEHOLDER": "Spiel suchen …", - "NO_MATCH": "Keine Spiele für diesen Filter." + "NO_MATCH": "Keine Spiele für diesen Filter.", + "SORT_RATING": "Beste Bewertung", + "SORT_PRIORITY": "Höchste Priorität" }, "GAME_DETAIL": { "BACK": "Zurück", diff --git a/public/i18n/en.json b/public/i18n/en.json index 2d297ba..fb8cefe 100644 --- a/public/i18n/en.json +++ b/public/i18n/en.json @@ -123,7 +123,9 @@ "LAST_SYNCED": "Last synced {{when}}", "FILTER_ALL": "All", "SEARCH_PLACEHOLDER": "Search games …", - "NO_MATCH": "No games match this filter." + "NO_MATCH": "No games match this filter.", + "SORT_RATING": "Best rated", + "SORT_PRIORITY": "Highest priority" }, "GAME_DETAIL": { "BACK": "Back", diff --git a/src/pages/GameLibrary.jsx b/src/pages/GameLibrary.jsx index 728009a..778d404 100644 --- a/src/pages/GameLibrary.jsx +++ b/src/pages/GameLibrary.jsx @@ -26,6 +26,8 @@ const SORT_OPTIONS = [ {value: 'playtimeMinutes,asc', labelKey: 'LIBRARY.SORT_PLAYTIME_ASC'}, {value: 'lastSeenAt,desc', labelKey: 'LIBRARY.SORT_LAST_SEEN'}, {value: 'firstSeenAt,desc', labelKey: 'LIBRARY.SORT_FIRST_SEEN'}, + {value: 'rating,desc', labelKey: 'LIBRARY.SORT_RATING'}, + {value: 'priority,desc', labelKey: 'LIBRARY.SORT_PRIORITY'}, ]; export default function GameLibrary() { diff --git a/src/pages/GameLibrary.test.jsx b/src/pages/GameLibrary.test.jsx index d386930..8f72848 100644 --- a/src/pages/GameLibrary.test.jsx +++ b/src/pages/GameLibrary.test.jsx @@ -39,17 +39,17 @@ function renderLibrary(initialEntry = '/library') { const typeInto = (value) => fireEvent.change(screen.getByRole('searchbox'), {target: {value}}); +beforeEach(() => { + localStorage.setItem('tbp_jwt', 'token'); + globalThis.fetch = vi.fn(async () => ({ok: true, status: 200, json: async () => page})); +}); + +afterEach(() => { + cleanup(); + localStorage.clear(); +}); + describe('GameLibrary search', () => { - beforeEach(() => { - localStorage.setItem('tbp_jwt', 'token'); - globalThis.fetch = vi.fn(async () => ({ok: true, status: 200, json: async () => page})); - }); - - afterEach(() => { - cleanup(); - localStorage.clear(); - }); - it('writes the trimmed search to the URL after the delay and resets the page', async () => { const lib = renderLibrary('/library?page=3'); @@ -93,3 +93,12 @@ describe('GameLibrary search', () => { await waitFor(() => expect(screen.getByRole('searchbox').value).toBe('')); }); }); + +describe('GameLibrary sort', () => { + it.each(['rating,desc', 'priority,desc'])('sends the sort %s from the URL', async (sort) => { + renderLibrary(`/library?sort=${encodeURIComponent(sort)}`); + + await waitFor(() => expect(fetch).toHaveBeenCalled()); + expect(new URL(fetch.mock.calls[0][0], 'http://x').searchParams.get('sort')).toBe(sort); + }); +});