diff --git a/code/websites/pokedex.online/tests/unit/components/challonge/TournamentGrid.test.js b/code/websites/pokedex.online/tests/unit/components/challonge/TournamentGrid.test.js new file mode 100644 index 0000000..1878859 --- /dev/null +++ b/code/websites/pokedex.online/tests/unit/components/challonge/TournamentGrid.test.js @@ -0,0 +1,426 @@ +/** + * Tests for TournamentGrid Component + * + * Verifies: + * - Loading state display + * - Error state display + * - Empty state display + * - Tournament list rendering + * - Search input functionality + * - Tournament card details + * - State badges rendering + * - Toggle details button + * - Load more pagination (v2.1) + * - Event emissions + */ + +import { describe, it, expect } from 'vitest'; +import { mount } from '@vue/test-utils'; +import TournamentGrid from '@/components/challonge/TournamentGrid.vue'; + +describe('TournamentGrid', () => { + const mockTournaments = [ + { + id: 'abc123', + name: 'Summer Championship', + state: 'pending', + url: 'summer-championship', + tournament_type: 'single elimination', + participants_count: 16, + started_at: '2024-06-01T10:00:00Z' + }, + { + tournament: { + id: 'def456', + name: 'Winter Tournament', + state: 'in_progress', + url: 'winter-tournament', + tournament_type: 'double elimination', + participants_count: 32, + started_at: '2024-12-15T14:00:00Z' + } + }, + { + id: 'ghi789', + name: 'Spring League', + state: 'ended', + url: 'spring-league', + tournament_type: 'round robin', + participants_count: 8, + started_at: null + } + ]; + + describe('Loading States', () => { + it('displays loading state', () => { + const wrapper = mount(TournamentGrid, { + props: { + loading: true, + tournaments: null + } + }); + + expect(wrapper.find('.status.loading').exists()).toBe(true); + expect(wrapper.text()).toContain('Loading tournaments'); + }); + + it('displays error state', () => { + const wrapper = mount(TournamentGrid, { + props: { + loading: false, + error: 'Network error', + tournaments: null + } + }); + + expect(wrapper.find('.status.error').exists()).toBe(true); + expect(wrapper.text()).toContain('Network error'); + }); + + it('displays empty state', () => { + const wrapper = mount(TournamentGrid, { + props: { + loading: false, + tournaments: [] + } + }); + + expect(wrapper.find('.status.empty').exists()).toBe(true); + expect(wrapper.text()).toContain('No tournaments found'); + expect(wrapper.find('a[href="https://challonge.com"]').exists()).toBe(true); + }); + }); + + describe('Tournament List Rendering', () => { + it('renders tournament cards', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: mockTournaments, + filteredTournaments: mockTournaments + } + }); + + const cards = wrapper.findAll('.tournament-card'); + expect(cards).toHaveLength(3); + }); + + it('displays tournament names correctly for both v1 and v2.1 formats', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: mockTournaments, + filteredTournaments: mockTournaments + } + }); + + expect(wrapper.text()).toContain('Summer Championship'); + expect(wrapper.text()).toContain('Winter Tournament'); + expect(wrapper.text()).toContain('Spring League'); + }); + + it('displays tournament details', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: [mockTournaments[0]], + filteredTournaments: [mockTournaments[0]] + } + }); + + expect(wrapper.text()).toContain('summer-championship'); + expect(wrapper.text()).toContain('single elimination'); + expect(wrapper.text()).toContain('16'); + }); + + it('displays state badges with correct classes', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: mockTournaments, + filteredTournaments: mockTournaments + } + }); + + expect(wrapper.find('.tournament-state.pending').exists()).toBe(true); + expect(wrapper.find('.tournament-state.in_progress').exists()).toBe(true); + expect(wrapper.find('.tournament-state.ended').exists()).toBe(true); + }); + + it('formats and displays started_at dates', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: [mockTournaments[0]], + filteredTournaments: [mockTournaments[0]] + } + }); + + expect(wrapper.text()).toContain('Started:'); + // Date formatting varies by locale, just check that something is displayed + const dateText = wrapper.text(); + expect(dateText).toMatch(/Started:.*\d/); + }); + + it('hides started date when null', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: [mockTournaments[2]], + filteredTournaments: [mockTournaments[2]] + } + }); + + const cardText = wrapper.find('.tournament-card').text(); + expect(cardText).not.toContain('Started:'); + }); + }); + + describe('Search Functionality', () => { + it('renders search input', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: mockTournaments, + filteredTournaments: mockTournaments + } + }); + + expect(wrapper.find('.search-input').exists()).toBe(true); + }); + + it('displays search query in input', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: mockTournaments, + filteredTournaments: mockTournaments, + searchQuery: 'Summer' + } + }); + + expect(wrapper.find('.search-input').element.value).toBe('Summer'); + }); + + it('emits update:searchQuery on input', async () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: mockTournaments, + filteredTournaments: mockTournaments + } + }); + + const input = wrapper.find('.search-input'); + await input.setValue('Winter'); + + expect(wrapper.emitted('update:searchQuery')).toBeTruthy(); + expect(wrapper.emitted('update:searchQuery')[0]).toEqual(['Winter']); + }); + + it('shows search info when query is present', () => { + const filtered = [mockTournaments[0]]; + const wrapper = mount(TournamentGrid, { + props: { + tournaments: mockTournaments, + filteredTournaments: filtered, + searchQuery: 'Summer' + } + }); + + expect(wrapper.find('.search-info').exists()).toBe(true); + expect(wrapper.text()).toContain('Showing 1 of 3 tournaments'); + }); + + it('hides search info when query is empty', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: mockTournaments, + filteredTournaments: mockTournaments, + searchQuery: '' + } + }); + + expect(wrapper.find('.search-info').exists()).toBe(false); + }); + }); + + describe('Tournament Details Toggle', () => { + it('renders details toggle button', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: [mockTournaments[0]], + filteredTournaments: [mockTournaments[0]] + } + }); + + expect(wrapper.find('.btn-small').exists()).toBe(true); + expect(wrapper.text()).toContain('Load Details'); + }); + + it('emits toggle-details event on button click', async () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: [mockTournaments[0]], + filteredTournaments: [mockTournaments[0]] + } + }); + + await wrapper.find('.btn-small').trigger('click'); + + expect(wrapper.emitted('toggle-details')).toBeTruthy(); + expect(wrapper.emitted('toggle-details')[0]).toEqual(['abc123']); + }); + + it('shows active state for expanded tournament', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: [mockTournaments[0]], + filteredTournaments: [mockTournaments[0]], + expandedTournamentId: 'abc123' + } + }); + + const button = wrapper.find('.btn-small'); + expect(button.classes()).toContain('btn-active'); + expect(button.text()).toBe('Hide Details'); + }); + + it('shows inactive state for non-expanded tournament', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: [mockTournaments[0]], + filteredTournaments: [mockTournaments[0]], + expandedTournamentId: 'other-id' + } + }); + + const button = wrapper.find('.btn-small'); + expect(button.classes()).not.toContain('btn-active'); + expect(button.text()).toBe('Load Details'); + }); + }); + + describe('Pagination (v2.1)', () => { + it('displays load more button for v2.1 with hasNextPage', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: mockTournaments, + filteredTournaments: mockTournaments, + apiVersion: 'v2.1', + hasNextPage: true + } + }); + + expect(wrapper.find('.load-more-section').exists()).toBe(true); + expect(wrapper.find('.btn-secondary').text()).toBe('Load More Tournaments'); + }); + + it('hides load more button when hasNextPage is false', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: mockTournaments, + filteredTournaments: mockTournaments, + apiVersion: 'v2.1', + hasNextPage: false + } + }); + + expect(wrapper.find('.load-more-section').exists()).toBe(false); + }); + + it('hides load more button for v1', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: mockTournaments, + filteredTournaments: mockTournaments, + apiVersion: 'v1', + hasNextPage: true + } + }); + + expect(wrapper.find('.load-more-section').exists()).toBe(false); + }); + + it('shows loading state on load more button', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: mockTournaments, + filteredTournaments: mockTournaments, + apiVersion: 'v2.1', + hasNextPage: true, + loadingMore: true + } + }); + + const button = wrapper.find('.btn-secondary'); + expect(button.text()).toBe('Loading...'); + expect(button.attributes('disabled')).toBeDefined(); + }); + + it('emits load-more event on button click', async () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: mockTournaments, + filteredTournaments: mockTournaments, + apiVersion: 'v2.1', + hasNextPage: true + } + }); + + await wrapper.find('.btn-secondary').trigger('click'); + + expect(wrapper.emitted('load-more')).toBeTruthy(); + expect(wrapper.emitted('load-more')).toHaveLength(1); + }); + }); + + describe('Slots', () => { + it('provides tournament-details slot', () => { + const wrapper = mount(TournamentGrid, { + props: { + tournaments: [mockTournaments[0]], + filteredTournaments: [mockTournaments[0]], + expandedTournamentId: 'abc123' + }, + slots: { + 'tournament-details': '
Custom Content
' + } + }); + + expect(wrapper.find('.custom-details').exists()).toBe(true); + expect(wrapper.text()).toContain('Custom Content'); + }); + }); + + describe('Helper Functions', () => { + it('handles v1 format tournaments (flat structure)', () => { + const v1Tournament = { + id: 'test123', + name: 'Test Tournament', + state: 'pending' + }; + + const wrapper = mount(TournamentGrid, { + props: { + tournaments: [v1Tournament], + filteredTournaments: [v1Tournament] + } + }); + + expect(wrapper.text()).toContain('Test Tournament'); + expect(wrapper.find('.tournament-state.pending').exists()).toBe(true); + }); + + it('handles v2.1 format tournaments (nested structure)', () => { + const v2Tournament = { + tournament: { + id: 'test456', + name: 'Test Tournament v2', + state: 'in_progress' + } + }; + + const wrapper = mount(TournamentGrid, { + props: { + tournaments: [v2Tournament], + filteredTournaments: [v2Tournament] + } + }); + + expect(wrapper.text()).toContain('Test Tournament v2'); + expect(wrapper.find('.tournament-state.in_progress').exists()).toBe(true); + }); + }); +});