- Created comprehensive tests for CollaboratorManagement component - Created comprehensive tests for PlaylistHeader component - Created comprehensive tests for AddCollaboratorModal component - Created comprehensive tests for PlaylistFollowButton component All 51 tests pass. These components are essential for playlist detail and collaboration functionality. Phase: PHASE-5 Priority: P2 Progress: 244/267 (91.39%)
375 lines
9.6 KiB
TypeScript
375 lines
9.6 KiB
TypeScript
/**
|
|
* Tests for PlaylistFollowButton Component
|
|
* FE-TEST-007: Test playlist follow button component
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import { PlaylistFollowButton } from './PlaylistFollowButton';
|
|
import {
|
|
followPlaylist,
|
|
unfollowPlaylist,
|
|
getPlaylist,
|
|
getPlaylistFollowStatus,
|
|
} from '../services/playlistService';
|
|
import { useAuthStore } from '@/stores/auth';
|
|
import { useToast } from '@/hooks/useToast';
|
|
|
|
// Mock dependencies
|
|
vi.mock('../services/playlistService', () => ({
|
|
followPlaylist: vi.fn(),
|
|
unfollowPlaylist: vi.fn(),
|
|
getPlaylist: vi.fn(),
|
|
getPlaylistFollowStatus: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('@/stores/auth', () => ({
|
|
useAuthStore: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('@/hooks/useToast', () => ({
|
|
useToast: vi.fn(),
|
|
}));
|
|
|
|
const createTestQueryClient = () =>
|
|
new QueryClient({
|
|
defaultOptions: {
|
|
queries: { retry: false },
|
|
mutations: { retry: false },
|
|
},
|
|
});
|
|
|
|
const TestWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
const queryClient = createTestQueryClient();
|
|
return (
|
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
);
|
|
};
|
|
|
|
describe('PlaylistFollowButton', () => {
|
|
const mockUser = {
|
|
id: '2',
|
|
username: 'testuser',
|
|
email: 'test@example.com',
|
|
};
|
|
|
|
const mockShowSuccess = vi.fn();
|
|
const mockShowError = vi.fn();
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(useAuthStore).mockReturnValue({
|
|
user: mockUser,
|
|
} as any);
|
|
vi.mocked(useToast).mockReturnValue({
|
|
success: mockShowSuccess,
|
|
error: mockShowError,
|
|
} as any);
|
|
vi.mocked(getPlaylist).mockResolvedValue({
|
|
id: '1',
|
|
user_id: '1', // Different from mockUser.id
|
|
} as any);
|
|
vi.mocked(getPlaylistFollowStatus).mockResolvedValue({
|
|
is_following: false,
|
|
follower_count: 10,
|
|
});
|
|
});
|
|
|
|
it('should render follow button when not following', async () => {
|
|
render(
|
|
<TestWrapper>
|
|
<PlaylistFollowButton playlistId="1" initialFollowing={false} />
|
|
</TestWrapper>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Suivre')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should render unfollow button when following', async () => {
|
|
vi.mocked(getPlaylistFollowStatus).mockResolvedValue({
|
|
is_following: true,
|
|
follower_count: 11,
|
|
});
|
|
|
|
render(
|
|
<TestWrapper>
|
|
<PlaylistFollowButton playlistId="1" initialFollowing={true} />
|
|
</TestWrapper>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Abonné')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should not render for playlist owner', async () => {
|
|
vi.mocked(getPlaylist).mockResolvedValue({
|
|
id: '1',
|
|
user_id: '2', // Same as mockUser.id
|
|
} as any);
|
|
vi.mocked(getPlaylistFollowStatus).mockResolvedValue({
|
|
is_following: false,
|
|
follower_count: 0,
|
|
});
|
|
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<PlaylistFollowButton playlistId="1" />
|
|
</TestWrapper>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
// Button should not be rendered for owner
|
|
// Component returns null, so no buttons should be found
|
|
const buttons = screen.queryAllByRole('button');
|
|
expect(buttons.length).toBe(0);
|
|
}, { timeout: 3000 });
|
|
});
|
|
|
|
it('should not render when user is not logged in', async () => {
|
|
vi.mocked(useAuthStore).mockReturnValue({
|
|
user: null,
|
|
} as any);
|
|
vi.mocked(getPlaylist).mockResolvedValue({
|
|
id: '1',
|
|
user_id: '1',
|
|
} as any);
|
|
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<PlaylistFollowButton playlistId="1" />
|
|
</TestWrapper>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
// Component should return null when user is not logged in
|
|
const buttons = screen.queryAllByRole('button');
|
|
expect(buttons.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
it('should call followPlaylist when follow button is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
vi.mocked(followPlaylist).mockResolvedValue({});
|
|
|
|
render(
|
|
<TestWrapper>
|
|
<PlaylistFollowButton playlistId="1" initialFollowing={false} />
|
|
</TestWrapper>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Suivre')).toBeInTheDocument();
|
|
});
|
|
|
|
const followButton = screen.getByText('Suivre');
|
|
await user.click(followButton);
|
|
|
|
await waitFor(() => {
|
|
expect(followPlaylist).toHaveBeenCalledWith('1');
|
|
});
|
|
});
|
|
|
|
it('should call unfollowPlaylist when unfollow button is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
vi.mocked(unfollowPlaylist).mockResolvedValue({});
|
|
vi.mocked(getPlaylistFollowStatus).mockResolvedValue({
|
|
is_following: true,
|
|
follower_count: 11,
|
|
});
|
|
|
|
render(
|
|
<TestWrapper>
|
|
<PlaylistFollowButton playlistId="1" initialFollowing={true} />
|
|
</TestWrapper>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Abonné')).toBeInTheDocument();
|
|
});
|
|
|
|
const unfollowButton = screen.getByText('Abonné');
|
|
await user.click(unfollowButton);
|
|
|
|
await waitFor(() => {
|
|
expect(unfollowPlaylist).toHaveBeenCalledWith('1');
|
|
});
|
|
});
|
|
|
|
it('should show success message on follow', async () => {
|
|
const user = userEvent.setup();
|
|
vi.mocked(followPlaylist).mockResolvedValue({});
|
|
|
|
render(
|
|
<TestWrapper>
|
|
<PlaylistFollowButton playlistId="1" initialFollowing={false} />
|
|
</TestWrapper>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Suivre')).toBeInTheDocument();
|
|
});
|
|
|
|
const followButton = screen.getByText('Suivre');
|
|
await user.click(followButton);
|
|
|
|
await waitFor(() => {
|
|
expect(mockShowSuccess).toHaveBeenCalledWith(
|
|
'Vous suivez maintenant cette playlist',
|
|
);
|
|
});
|
|
});
|
|
|
|
it('should show success message on unfollow', async () => {
|
|
const user = userEvent.setup();
|
|
vi.mocked(unfollowPlaylist).mockResolvedValue({});
|
|
vi.mocked(getPlaylistFollowStatus).mockResolvedValue({
|
|
is_following: true,
|
|
follower_count: 11,
|
|
});
|
|
|
|
render(
|
|
<TestWrapper>
|
|
<PlaylistFollowButton playlistId="1" initialFollowing={true} />
|
|
</TestWrapper>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Abonné')).toBeInTheDocument();
|
|
});
|
|
|
|
const unfollowButton = screen.getByText('Abonné');
|
|
await user.click(unfollowButton);
|
|
|
|
await waitFor(() => {
|
|
expect(mockShowSuccess).toHaveBeenCalledWith(
|
|
'Vous ne suivez plus cette playlist',
|
|
);
|
|
});
|
|
});
|
|
|
|
it('should show error message on follow failure', async () => {
|
|
const user = userEvent.setup();
|
|
vi.mocked(followPlaylist).mockRejectedValue(new Error('Follow failed'));
|
|
|
|
render(
|
|
<TestWrapper>
|
|
<PlaylistFollowButton playlistId="1" initialFollowing={false} />
|
|
</TestWrapper>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Suivre')).toBeInTheDocument();
|
|
});
|
|
|
|
const followButton = screen.getByText('Suivre');
|
|
await user.click(followButton);
|
|
|
|
await waitFor(() => {
|
|
expect(mockShowError).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it('should show follower count when showCount is true', async () => {
|
|
render(
|
|
<TestWrapper>
|
|
<PlaylistFollowButton
|
|
playlistId="1"
|
|
initialFollowing={false}
|
|
initialFollowerCount={25}
|
|
showCount={true}
|
|
/>
|
|
</TestWrapper>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('(25)')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should update follower count optimistically', async () => {
|
|
const user = userEvent.setup();
|
|
vi.mocked(followPlaylist).mockResolvedValue({});
|
|
|
|
render(
|
|
<TestWrapper>
|
|
<PlaylistFollowButton
|
|
playlistId="1"
|
|
initialFollowing={false}
|
|
initialFollowerCount={10}
|
|
showCount={true}
|
|
/>
|
|
</TestWrapper>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Suivre')).toBeInTheDocument();
|
|
});
|
|
|
|
const followButton = screen.getByText('Suivre');
|
|
await user.click(followButton);
|
|
|
|
// Follower count should increase optimistically
|
|
await waitFor(() => {
|
|
expect(screen.getByText('(11)')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should call onFollowChange callback', async () => {
|
|
const user = userEvent.setup();
|
|
const mockOnFollowChange = vi.fn();
|
|
vi.mocked(followPlaylist).mockResolvedValue({});
|
|
|
|
render(
|
|
<TestWrapper>
|
|
<PlaylistFollowButton
|
|
playlistId="1"
|
|
initialFollowing={false}
|
|
onFollowChange={mockOnFollowChange}
|
|
/>
|
|
</TestWrapper>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Suivre')).toBeInTheDocument();
|
|
});
|
|
|
|
const followButton = screen.getByText('Suivre');
|
|
await user.click(followButton);
|
|
|
|
await waitFor(() => {
|
|
expect(mockOnFollowChange).toHaveBeenCalledWith(true);
|
|
});
|
|
});
|
|
|
|
it('should be disabled during update', async () => {
|
|
const user = userEvent.setup();
|
|
vi.mocked(followPlaylist).mockImplementation(
|
|
() => new Promise((resolve) => setTimeout(resolve, 100)),
|
|
);
|
|
|
|
render(
|
|
<TestWrapper>
|
|
<PlaylistFollowButton playlistId="1" initialFollowing={false} />
|
|
</TestWrapper>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Suivre')).toBeInTheDocument();
|
|
});
|
|
|
|
const followButton = screen.getByText('Suivre');
|
|
await user.click(followButton);
|
|
|
|
// Button should show loading state
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/abonnement/i)).toBeInTheDocument();
|
|
expect(followButton).toBeDisabled();
|
|
});
|
|
});
|
|
});
|
|
|