110 lines
3.7 KiB
TypeScript
110 lines
3.7 KiB
TypeScript
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
import { render, screen, fireEvent } from '@testing-library/svelte';
|
|
import { mockQuery } from '../../test-utils/query';
|
|
|
|
const invalidateMock = vi.fn();
|
|
vi.mock('@tanstack/svelte-query', async (orig) => {
|
|
const actual = (await orig()) as Record<string, unknown>;
|
|
return { ...actual, useQueryClient: () => ({ invalidateQueries: invalidateMock }) };
|
|
});
|
|
|
|
vi.mock('$lib/api/suggestions', () => ({
|
|
createSuggestionsQuery: vi.fn()
|
|
}));
|
|
|
|
vi.mock('$lib/api/requests', () => ({
|
|
createRequest: vi.fn().mockResolvedValue({})
|
|
}));
|
|
|
|
import SuggestionFeed from './SuggestionFeed.svelte';
|
|
import { createSuggestionsQuery } from '$lib/api/suggestions';
|
|
import { createRequest } from '$lib/api/requests';
|
|
import type { ArtistSuggestion } from '$lib/api/types';
|
|
|
|
const oneSeed: ArtistSuggestion = {
|
|
mbid: 'mb1',
|
|
name: 'Outsider',
|
|
score: 1.0,
|
|
attribution: [
|
|
{ artist_id: 'a1', name: 'Seed', contribution: 0.9, is_liked: true, play_count: 0 }
|
|
]
|
|
};
|
|
|
|
const twoSeeds: ArtistSuggestion = {
|
|
mbid: 'mb2',
|
|
name: 'Outsider Two',
|
|
score: 2.0,
|
|
attribution: [
|
|
{ artist_id: 'a1', name: 'A', contribution: 0.8, is_liked: true, play_count: 0 },
|
|
{ artist_id: 'a2', name: 'B', contribution: 0.5, is_liked: false, play_count: 3 }
|
|
]
|
|
};
|
|
|
|
const threeSeeds: ArtistSuggestion = {
|
|
mbid: 'mb3',
|
|
name: 'Outsider Three',
|
|
score: 3.0,
|
|
attribution: [
|
|
{ artist_id: 'a1', name: 'X', contribution: 0.9, is_liked: true, play_count: 0 },
|
|
{ artist_id: 'a2', name: 'Y', contribution: 0.6, is_liked: false, play_count: 5 },
|
|
{ artist_id: 'a3', name: 'Z', contribution: 0.3, is_liked: false, play_count: 1 }
|
|
]
|
|
};
|
|
|
|
afterEach(() => vi.clearAllMocks());
|
|
|
|
describe('SuggestionFeed', () => {
|
|
test('renders one card per suggestion', () => {
|
|
(createSuggestionsQuery as ReturnType<typeof vi.fn>).mockReturnValue(
|
|
mockQuery({ data: [oneSeed, twoSeeds] })
|
|
);
|
|
render(SuggestionFeed);
|
|
expect(screen.getByText('Outsider')).toBeInTheDocument();
|
|
expect(screen.getByText('Outsider Two')).toBeInTheDocument();
|
|
});
|
|
|
|
test('attribution copy: 1 seed → "Because you liked X."', () => {
|
|
(createSuggestionsQuery as ReturnType<typeof vi.fn>).mockReturnValue(
|
|
mockQuery({ data: [oneSeed] })
|
|
);
|
|
render(SuggestionFeed);
|
|
expect(screen.getByText(/because you liked seed\./i)).toBeInTheDocument();
|
|
});
|
|
|
|
test('attribution copy: 2 seeds → "Because you liked A and played B."', () => {
|
|
(createSuggestionsQuery as ReturnType<typeof vi.fn>).mockReturnValue(
|
|
mockQuery({ data: [twoSeeds] })
|
|
);
|
|
render(SuggestionFeed);
|
|
expect(screen.getByText(/because you liked a and played b\./i)).toBeInTheDocument();
|
|
});
|
|
|
|
test('attribution copy: 3 seeds → Oxford comma', () => {
|
|
(createSuggestionsQuery as ReturnType<typeof vi.fn>).mockReturnValue(
|
|
mockQuery({ data: [threeSeeds] })
|
|
);
|
|
render(SuggestionFeed);
|
|
expect(screen.getByText(/because you liked x, played y, and played z\./i)).toBeInTheDocument();
|
|
});
|
|
|
|
test('Request button calls createRequest with artist-kind body', async () => {
|
|
(createSuggestionsQuery as ReturnType<typeof vi.fn>).mockReturnValue(
|
|
mockQuery({ data: [oneSeed] })
|
|
);
|
|
render(SuggestionFeed);
|
|
await fireEvent.click(screen.getByRole('button', { name: /request outsider/i }));
|
|
expect(createRequest).toHaveBeenCalledWith({
|
|
kind: 'artist',
|
|
lidarr_artist_mbid: 'mb1',
|
|
artist_name: 'Outsider'
|
|
});
|
|
expect(invalidateMock).toHaveBeenCalled();
|
|
});
|
|
|
|
test('empty state when data is []', () => {
|
|
(createSuggestionsQuery as ReturnType<typeof vi.fn>).mockReturnValue(mockQuery({ data: [] }));
|
|
render(SuggestionFeed);
|
|
expect(screen.getByText(/listen to something or like an artist/i)).toBeInTheDocument();
|
|
});
|
|
});
|