// @vitest-environment jsdom import {afterEach, describe, expect, it, vi} from 'vitest'; import {cleanup, fireEvent, render, screen, waitFor} from '@testing-library/react'; import {toast} from 'sonner'; import FollowButton from './FollowButton.jsx'; import {fetchRoutes, json} from '../../test/fetchRoutes.js'; vi.mock('react-i18next', () => import('../../test/i18n-mock.js')); vi.mock('sonner', () => ({toast: {success: vi.fn(), error: vi.fn()}})); const noFollows = {following: [], followers: []}; const callsOf = (method) => fetch.mock.calls.filter(([, init]) => init?.method === method); afterEach(() => { cleanup(); vi.clearAllMocks(); }); describe('FollowButton', () => { it('offersFollowAndFollowsByNameThenOffersUnfollow', async () => { fetchRoutes({'GET /api/follows': json(200, noFollows), 'PUT /api/follows/by-name/Lobo%20Wolf': json(204)}); render(); fireEvent.click(await screen.findByRole('button', {name: 'FOLLOW.FOLLOW'})); expect(await screen.findByRole('button', {name: 'FOLLOW.UNFOLLOW'})).toBeTruthy(); expect(callsOf('PUT')).toHaveLength(1); }); it('offersUnfollowForANameAlreadyFollowedIgnoringCase', async () => { fetchRoutes({'GET /api/follows': json(200, {following: [{id: 1, displayName: 'lobo'}], followers: []}), 'DELETE /api/follows/by-name/Lobo': json(204)}); render(); fireEvent.click(await screen.findByRole('button', {name: 'FOLLOW.UNFOLLOW'})); expect(await screen.findByRole('button', {name: 'FOLLOW.FOLLOW'})).toBeTruthy(); expect(callsOf('DELETE')).toHaveLength(1); }); it('saysSoWhenTheProfileCannotBeFollowed', async () => { fetchRoutes({'GET /api/follows': json(200, noFollows), 'PUT /api/follows/by-name/Lobo': json(404, {code: 'PROFILE_NOT_FOUND'})}); render(); fireEvent.click(await screen.findByRole('button', {name: 'FOLLOW.FOLLOW'})); expect(await screen.findByText('FOLLOW.NOT_FOLLOWABLE')).toBeTruthy(); expect(screen.queryByRole('button')).toBeNull(); }); it('toastsOnAnOtherErrorAndKeepsTheButton', async () => { fetchRoutes({'GET /api/follows': json(200, noFollows), 'PUT /api/follows/by-name/Lobo': json(400, {code: 'FOLLOW_LIMIT'})}); render(); fireEvent.click(await screen.findByRole('button', {name: 'FOLLOW.FOLLOW'})); await waitFor(() => expect(toast.error).toHaveBeenCalledWith('FOLLOW.LIMIT')); expect(screen.getByRole('button', {name: 'FOLLOW.FOLLOW'})).toBeTruthy(); }); it('showsNoButtonWhenTheListDoesNotLoad', async () => { fetchRoutes({'GET /api/follows': json(500, {})}); render(); await waitFor(() => expect(fetch).toHaveBeenCalled()); expect(screen.queryByRole('button')).toBeNull(); }); });