feat(points): show points result on every status save

Core now settles on GET /api/rewards/games/{appid}. The Frontend
shows a toast, refreshes the header balance, and lists the paid
rows and reasons on the game page. Deploy Core first.
This commit is contained in:
lobo committed 2026-10-09 15:25:04 +02:00
1 parent 9adc606c57
commit 814decc781
20 files changed
+429 -12

No files matched your search

+37 -1
View File
@@ -13,8 +13,12 @@ vi.mock('sonner', () => ({toast: Object.assign(vi.fn(), {success: vi.fn(), error
const card = (appid, question, name = `Game ${appid}`) => ({appid, name, imageUrl: null, question, status: 'PLAYING',
playtimeMinutes: 1500, normallyMinutes: null, lastActivity: null});
const booked = (appid) => ({balance: 30, missing: {}, paid: [{kind: 'FINISHED', appid, points: 30, paidAt: '2026-10-09T10:00:00Z', bookedNow: true}]});
const WALLET = {'GET /api/rewards/wallet': json(200, {balance: 30, catalog: [], owned: [], equipped: {}, history: []})};
const pointsCalls = () => globalThis.fetch.mock.calls.filter(([url]) => url.includes('/api/rewards/games/'));
const renderPage = (routes) => {
const mock = fetchRoutes(routes);
const mock = fetchRoutes({...WALLET, ...routes});
render(<MemoryRouter><PreferencesProvider><QuestionsPage/></PreferencesProvider></MemoryRouter>);
return mock;
};
@@ -45,6 +49,7 @@ describe('QuestionsPage', () => {
const mock = renderPage({
'GET /api/library/questions': json(200, {results: [card(1, 'START'), card(2, 'START')]}),
'POST /api/library/questions/1': json(200, {next: null}),
'GET /api/rewards/games/1': json(200, booked(1)),
});
await screen.findByText('Game 1');
@@ -73,6 +78,7 @@ describe('QuestionsPage', () => {
renderPage({
'GET /api/library/questions': json(200, {results: [card(1, 'FINISHED')]}),
'POST /api/library/questions/1': json(200, {next: card(1, 'RATE')}),
'GET /api/rewards/games/1': json(200, booked(1)),
});
await screen.findByText('QUESTIONS.FINISHED.ASK');
@@ -82,6 +88,36 @@ describe('QuestionsPage', () => {
expect(screen.queryByText('QUESTIONS.FINISHED.ASK')).toBeNull();
});
it('showsThePointsToastAfterAYesToFinished', async () => {
renderPage({
'GET /api/library/questions': json(200, {results: [card(1, 'FINISHED')]}),
'POST /api/library/questions/1': json(200, {next: null}),
'GET /api/rewards/games/1': json(200, booked(1)),
});
await screen.findByText('QUESTIONS.FINISHED.ASK');
fireEvent.click(screen.getByRole('button', {name: 'QUESTIONS.YES'}));
await waitFor(() => expect(toast.success).toHaveBeenCalledWith('POINTS.GAME.TOAST_BOOKED:30'));
expect(pointsCalls()).toHaveLength(1);
});
it('asksForNoPointsAfterANoOrTheRatingAnswer', async () => {
renderPage({
'GET /api/library/questions': json(200, {results: [card(1, 'FINISHED'), card(2, 'RATE')]}),
'POST /api/library/questions/1': json(200, {next: null}),
'POST /api/library/questions/2': json(200, {next: null}),
});
await screen.findByText('Game 1');
fireEvent.click(screen.getAllByRole('button', {name: 'QUESTIONS.NO'})[0]);
await waitFor(() => expect(screen.queryByText('Game 1')).toBeNull());
fireEvent.click(screen.getByRole('button', {name: 'BACKLOG.RATING_STARS:5'}));
await screen.findByText('QUESTIONS.EMPTY');
expect(pointsCalls()).toHaveLength(0);
});
it('sendsTheRatingOfTheStars', async () => {
const mock = renderPage({
'GET /api/library/questions': json(200, {results: [card(1, 'RATE')]}),