feat(play-next): add FRIENDS row and reason text

Shows games from followed users. Reason displays friend name and finish
or playtime status. Supports reroll without mood. Added English and
German translations.
This commit is contained in:
lobo committed 2026-10-07 09:42:10 +02:00
1 parent e041c71cf8
commit 6ae2abee07
5 files changed
+37 -7

No files matched your search

+21 -2
View File
@@ -21,6 +21,9 @@ const ALL = () => [
row('SERIES', game(6, {reason: {source: 'SERIES', seriesName: 'Mass Effect'}})),
row('SIMILAR', game(3, {reason: {source: 'SIMILAR', anchorAppid: 9, anchorName: 'Celeste', anchorPlaytimeMinutes: 600}})),
row('STUDIO', game(8, {reason: {source: 'STUDIO', studioName: 'BioWare'}})),
row('FRIENDS',
game(11, {reason: {source: 'FRIENDS', friendName: 'Max', friendFinished: true, friendPlaytimeMinutes: null}}),
game(12, {reason: {source: 'FRIENDS', friendName: 'Anna', friendFinished: false, friendPlaytimeMinutes: 2400}})),
row('LONGEST', game(4, {reason: {source: 'LONGEST', firstSeenAt: '2024-03-12T10:00:00Z'}})),
row('RANDOM', game(5, {reason: {source: 'RANDOM'}})),
];
@@ -78,7 +81,7 @@ describe('PlayNextPage', () => {
await screen.findByText('Game 1');
expect(screen.getAllByRole('heading', {level: 2}).map((h) => h.textContent)).toEqual([
'PLAY_NEXT.CATEGORY.NEXT_UP', 'PLAY_NEXT.CATEGORY.SERIES', 'PLAY_NEXT.CATEGORY.SIMILAR',
'PLAY_NEXT.CATEGORY.STUDIO', 'PLAY_NEXT.CATEGORY.LONGEST', 'PLAY_NEXT.CATEGORY.RANDOM']);
'PLAY_NEXT.CATEGORY.STUDIO', 'PLAY_NEXT.CATEGORY.FRIENDS', 'PLAY_NEXT.CATEGORY.LONGEST', 'PLAY_NEXT.CATEGORY.RANDOM']);
const params = pickerUrls()[0];
expect(params.has('count')).toBe(false);
expect(params.has('category')).toBe(false);
@@ -191,6 +194,20 @@ describe('PlayNextPage', () => {
expect(pickerUrls()[2].has('mood')).toBe(false);
});
it('rerolls the friends row with category=FRIENDS and without a mood', async () => {
mockRows(ALL(), {FRIENDS: [row('FRIENDS', game(13))]});
renderPage();
await screen.findByText('Game 1');
fireEvent.click(screen.getByRole('button', {name: 'PLAY_NEXT.MOOD.STORY'}));
await screen.findByText('Game 5');
fireEvent.click(rerollOf('PLAY_NEXT.CATEGORY.FRIENDS'));
await screen.findByText('Game 13');
expect(pickerUrls()[2].get('category')).toBe('FRIENDS');
expect(pickerUrls()[2].has('mood')).toBe(false);
});
it('starts a row over when it has no unseen game left', async () => {
mockBackend((params) => {
if (!params.get('category')) return ALL();
@@ -244,7 +261,7 @@ describe('PlayNextPage', () => {
expect(await screen.findByText('PLAY_NEXT.EMPTY_MOOD')).toBeTruthy();
});
it('shows a reason line for the series, similar, studio and longest rows only', async () => {
it('shows a reason line for the series, similar, studio, friends and longest rows only', async () => {
mockRows();
renderPage();
@@ -252,6 +269,8 @@ describe('PlayNextPage', () => {
expect(await screen.findByText('PLAY_NEXT.REASON.SIMILAR')).toBeTruthy();
expect(screen.getByText('PLAY_NEXT.REASON.SERIES')).toBeTruthy();
expect(screen.getByText('PLAY_NEXT.REASON.STUDIO')).toBeTruthy();
expect(screen.getByText('PLAY_NEXT.REASON.FRIENDS_FINISHED')).toBeTruthy();
expect(screen.getByText('PLAY_NEXT.REASON.FRIENDS_PLAYED')).toBeTruthy();
expect(screen.getByText('PLAY_NEXT.REASON.LONGEST')).toBeTruthy();
expect(screen.queryByText(/PLAY_NEXT\.REASON\.(RANDOM|NEXT_UP)/)).toBeNull();
});