diff --git a/code/websites/pokedex.online/tests/unit/components/gamemaster/FilterPanel.test.js b/code/websites/pokedex.online/tests/unit/components/gamemaster/FilterPanel.test.js new file mode 100644 index 0000000..233fd7c --- /dev/null +++ b/code/websites/pokedex.online/tests/unit/components/gamemaster/FilterPanel.test.js @@ -0,0 +1,104 @@ +/** + * FilterPanel Component Tests + * Verifies filter UI and interactions + */ + +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { mount } from '@vue/test-utils'; +import { ref } from 'vue'; +import FilterPanel from '../../../../src/components/gamemaster/FilterPanel.vue'; +import useJsonFilter from '../../../../src/composables/useJsonFilter.js'; + +vi.mock('../../../../src/composables/useJsonFilter.js', () => ({ + default: vi.fn() +})); + +const createFilterMock = overrides => ({ + filterProperty: ref(''), + filterValue: ref(''), + filterMode: ref('equals'), + availablePaths: ref([]), + filterError: ref(null), + filterStats: ref({ total: 0, matched: 0, percentage: 0 }), + initializeFilter: vi.fn(), + extractPathsLazy: vi.fn(), + setFilter: vi.fn(), + clearFilters: vi.fn(), + getUniqueValues: vi.fn(() => []), + ...overrides +}); + +describe('FilterPanel Component', () => { + let filterMock; + + beforeEach(() => { + filterMock = createFilterMock(); + useJsonFilter.mockReturnValue(filterMock); + }); + + it('renders when data is provided', () => { + const wrapper = mount(FilterPanel, { + props: { data: [{ id: 1 }] } + }); + + expect(wrapper.find('.filter-panel').exists()).toBe(true); + }); + + it('initializes filter on data change', () => { + mount(FilterPanel, { + props: { data: [{ id: 1 }] } + }); + + expect(filterMock.initializeFilter).toHaveBeenCalled(); + }); + + it('renders property options', () => { + filterMock.availablePaths.value = [ + { path: 'stats.hp', breadcrumb: 'stats › hp' } + ]; + + const wrapper = mount(FilterPanel, { + props: { data: [{ id: 1 }] } + }); + + const options = wrapper.findAll('option'); + expect(options.length).toBeGreaterThan(1); + expect(options[1].text()).toContain('stats › hp'); + }); + + it('shows value input when property selected', async () => { + filterMock.filterProperty.value = 'stats.hp'; + + const wrapper = mount(FilterPanel, { + props: { data: [{ id: 1 }] } + }); + + await wrapper.vm.$nextTick(); + expect(wrapper.find('#filter-value').exists()).toBe(true); + }); + + it('applies filter on input change', async () => { + filterMock.filterProperty.value = 'stats.hp'; + + const wrapper = mount(FilterPanel, { + props: { data: [{ id: 1 }] } + }); + + const input = wrapper.find('#filter-value'); + await input.setValue('100'); + + expect(filterMock.setFilter).toHaveBeenCalled(); + }); + + it('shows stats and error message', () => { + filterMock.filterStats.value = { total: 10, matched: 4, percentage: 40 }; + filterMock.filterError.value = 'Invalid regex'; + + const wrapper = mount(FilterPanel, { + props: { data: [{ id: 1 }] } + }); + + expect(wrapper.find('.filter-stats').text()).toContain('4 / 10'); + expect(wrapper.find('.filter-error').text()).toContain('Invalid regex'); + }); +});