// @vitest-environment jsdom import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'; import {act, cleanup, fireEvent, screen, waitFor} from '@testing-library/react'; import {toast} from 'sonner'; import GameHeroCard from './GameHeroCard.jsx'; import {fetchRoutes, json} from '../test/fetchRoutes.js'; import {renderWithProviders} from '../test/renderWithProviders.jsx'; vi.mock('react-i18next', () => import('../test/i18n-mock.js')); vi.mock('sonner', () => ({toast: {success: vi.fn(), error: vi.fn()}})); const game = {appid: 220, name: 'Half-Life 2', status: 'BACKLOG', rating: null, hidden: false, playtimeMinutes: 10}; const routes = (extra = {}) => fetchRoutes({ 'GET /api/users/me': json(200, {settings: {}}), 'PUT /api/library/games/220/status': (url, init) => json(200, {...game, ...JSON.parse(init.body)}), 'PATCH /api/library/games/220/backlog': (url, init) => json(200, {...game, ...JSON.parse(init.body)}), 'PUT /api/library/games/220/hidden': (url, init) => json(200, JSON.parse(init.body)), ...extra, }); const renderCard = (props = {}) => renderWithProviders( ); const openMore = async () => fireEvent.keyDown(await screen.findByRole('button', {name: 'GAME_DETAIL.MORE'}), {key: 'Enter'}); const calls = (method) => fetch.mock.calls.filter(([, init]) => init?.method === method); beforeEach(() => { localStorage.setItem('tbp_session', '1'); }); afterEach(() => { cleanup(); vi.clearAllMocks(); localStorage.clear(); }); describe('GameHeroCard', () => { it('keepsTheMoreMenuForArtHideAndAuditorTools', async () => { routes(); renderCard({auditor: true, profileStyle: {}}); expect(screen.getByRole('link', {name: /GAME_DETAIL.OPEN_IN_STEAM/})).toBeTruthy(); await openMore(); const names = (await screen.findAllByRole('menuitem')).map((item) => item.textContent); expect(names).toEqual(['GAME_DETAIL.SET_BACKGROUND', 'GAME_DETAIL.SET_BANNER', 'GAME_DETAIL.HIDE', 'CORRECTION.OPEN', 'MERGE.OPEN']); expect(screen.getAllByRole('menuitemcheckbox')).toHaveLength(2); expect(screen.queryByRole('button', {name: 'CORRECTION.OPEN'})).toBeNull(); }); it('offersNoAuditorToolsToAUser', async () => { routes(); renderCard(); await openMore(); const names = (await screen.findAllByRole('menuitem')).map((item) => item.textContent); expect(names).not.toContain('CORRECTION.OPEN'); expect(names).not.toContain('MERGE.OPEN'); }); it('hidesWithAnUndoThatShowsTheGameAgain', async () => { routes(); const onHiddenChanged = vi.fn(); renderCard({onHiddenChanged}); await openMore(); fireEvent.click(await screen.findByRole('menuitem', {name: 'GAME_DETAIL.HIDE'})); await waitFor(() => expect(onHiddenChanged).toHaveBeenCalledWith(true)); const [message, options] = toast.success.mock.calls.at(-1); expect(message).toBe('GAME_DETAIL.HIDDEN_TOAST'); expect(options.action.label).toBe('MANUAL_GAME.UNDO'); await act(async () => options.action.onClick()); expect(onHiddenChanged).toHaveBeenLastCalledWith(false); expect(calls('PUT').map(([, init]) => JSON.parse(init.body))).toEqual([{hidden: true}, {hidden: false}]); // The undo is a plain toast. expect(toast.success.mock.calls.at(-1)).toEqual(['GAME_DETAIL.SHOWN_TOAST']); }); it('savesTheStatusFromTheMenuWithoutAskingThePointsItself', async () => { routes(); const onSaved = vi.fn(); renderCard({onSaved}); fireEvent.keyDown(screen.getByRole('button', {name: /BACKLOG.STATUS_LABEL/}), {key: 'Enter'}); fireEvent.click(await screen.findByRole('menuitemradio', {name: 'BACKLOG.STATUS.PLAYING'})); await waitFor(() => expect(onSaved).toHaveBeenCalledWith(expect.objectContaining({status: 'PLAYING'}))); expect(JSON.parse(calls('PUT')[0][1].body)).toEqual({status: 'PLAYING'}); // GameDetail's GamePointsLine does the points check when the status changes; none here (no route would fail). expect(toast.success.mock.calls.at(-1)[1].action.label).toBe('MANUAL_GAME.UNDO'); }); it('savesTheRatingAtOnce', async () => { routes(); const onSaved = vi.fn(); renderCard({onSaved}); fireEvent.click(screen.getByRole('button', {name: 'BACKLOG.RATING_STARS:4'})); await waitFor(() => expect(onSaved).toHaveBeenCalled()); expect(calls('PATCH').map(([, init]) => JSON.parse(init.body))).toEqual([{rating: 4}]); }); });