diff --git a/code/websites/pokedex.online/tests/unit/views/GamemasterExplorer.test.js b/code/websites/pokedex.online/tests/unit/views/GamemasterExplorer.test.js new file mode 100644 index 0000000..29075fb --- /dev/null +++ b/code/websites/pokedex.online/tests/unit/views/GamemasterExplorer.test.js @@ -0,0 +1,279 @@ +/** + * GamemasterExplorer View Tests + * Integration tests verifying the refactored component works correctly + */ + +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { mount } from '@vue/test-utils'; +import { ref } from 'vue'; +import GamemasterExplorer from '../../../src/views/GamemasterExplorer.vue'; + +// Mock composables +vi.mock('../../../src/composables/useGamemasterFiles.js', () => ({ + useGamemasterFiles: vi.fn(() => ({ + selectedFile: ref('pokemon.json'), + fileContent: ref('{"test": "data"}'), + fileLines: ref(['{', ' "test": "data"', '}']), + displayLines: ref(['{', ' "test": "data"', '}']), + isLoading: ref(false), + fileError: ref(null), + preferences: ref({ + lineWrap: false, + darkMode: false, + showLineNumbers: true, + performanceMode: 'auto' + }), + hasFiles: ref(true), + fileTooLarge: ref(false), + loadStatus: vi.fn(), + formatSize: vi.fn(size => `${size} B`), + formatFileName: vi.fn(name => name), + getFileType: vi.fn(name => name.replace('.json', '')) + })) +})); + +vi.mock('../../../src/composables/useGamemasterSearch.js', () => ({ + useGamemasterSearch: vi.fn(() => ({ + searchQuery: ref(''), + searchResults: ref([]), + currentResultIndex: ref(-1), + isSearching: ref(false), + searchError: ref(null), + executeSearch: vi.fn(), + clearSearch: vi.fn(), + goToNextResult: vi.fn(), + goToPrevResult: vi.fn() + })) +})); + +vi.mock('../../../src/composables/useLineSelection.js', () => ({ + useLineSelection: vi.fn(() => ({ + selectedLines: ref(new Set()), + hasSelection: ref(false), + selectionCount: ref(0), + toggleLineSelection: vi.fn(), + clearSelection: vi.fn(), + selectAll: vi.fn(), + copySelected: vi.fn(), + exportSelected: vi.fn() + })) +})); + +vi.mock('../../../src/composables/useJsonFilter.js', () => ({ + default: vi.fn(() => ({ + filterProperty: ref(''), + filterValue: ref(''), + filterMode: ref('equals'), + filteredData: ref([]), + setFilter: vi.fn(), + clearFilters: vi.fn(), + getUniqueValues: vi.fn(() => []) + })) +})); + +vi.mock('../../../src/composables/useKeyboardShortcuts.js', () => ({ + useKeyboardShortcuts: vi.fn() +})); + +vi.mock('../../../src/composables/useUrlState.js', () => ({ + useUrlState: vi.fn() +})); + +vi.mock('../../../src/composables/useClipboard.js', () => ({ + useClipboard: vi.fn(() => ({ + copied: ref(false), + error: ref(null), + copy: vi.fn() + })) +})); + +vi.mock('../../../src/utilities/gamemaster-client.js', () => ({ + GamemasterClient: vi.fn(() => ({ + getStatus: vi.fn(), + getFile: vi.fn() + })) +})); + +describe('GamemasterExplorer', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('renders successfully', () => { + const wrapper = mount(GamemasterExplorer); + expect(wrapper.exists()).toBe(true); + }); + + it('shows loading state when isLoading is true', () => { + const { useGamemasterFiles } = await import( + '../../../src/composables/useGamemasterFiles.js' + ); + useGamemasterFiles.mockReturnValueOnce({ + selectedFile: ref(''), + fileContent: ref(''), + fileLines: ref([]), + displayLines: ref([]), + isLoading: ref(true), + fileError: ref(null), + preferences: ref({}), + hasFiles: ref(false), + fileTooLarge: ref(false), + loadStatus: vi.fn() + }); + + const wrapper = mount(GamemasterExplorer); + expect(wrapper.find('.loading-state').exists()).toBe(true); + expect(wrapper.text()).toContain('Loading Gamemaster Explorer'); + }); + + it('shows error state when fileError exists', () => { + const { useGamemasterFiles } = await import( + '../../../src/composables/useGamemasterFiles.js' + ); + useGamemasterFiles.mockReturnValueOnce({ + selectedFile: ref(''), + fileContent: ref(''), + fileLines: ref([]), + displayLines: ref([]), + isLoading: ref(false), + fileError: ref('Failed to load files'), + preferences: ref({}), + hasFiles: ref(false), + fileTooLarge: ref(false), + loadStatus: vi.fn() + }); + + const wrapper = mount(GamemasterExplorer); + expect(wrapper.find('.error-state').exists()).toBe(true); + expect(wrapper.text()).toContain('Failed to load files'); + }); + + it('shows no files state when hasFiles is false', () => { + const { useGamemasterFiles } = await import( + '../../../src/composables/useGamemasterFiles.js' + ); + useGamemasterFiles.mockReturnValueOnce({ + selectedFile: ref(''), + fileContent: ref(''), + fileLines: ref([]), + displayLines: ref([]), + isLoading: ref(false), + fileError: ref(null), + preferences: ref({}), + hasFiles: ref(false), + fileTooLarge: ref(false), + loadStatus: vi.fn() + }); + + const wrapper = mount(GamemasterExplorer); + expect(wrapper.find('.no-files-state').exists()).toBe(true); + expect(wrapper.text()).toContain('No Gamemaster Files Available'); + }); + + it('renders main explorer interface when files are loaded', () => { + const wrapper = mount(GamemasterExplorer); + expect(wrapper.find('.explorer-container').exists()).toBe(true); + expect(wrapper.find('.explorer-header').exists()).toBe(true); + expect(wrapper.text()).toContain('Gamemaster Explorer'); + }); + + it('includes all child components', () => { + const wrapper = mount(GamemasterExplorer); + + // Check for FileSelector component + expect(wrapper.findComponent({ name: 'FileSelector' }).exists()).toBe(true); + + // Check for SearchBar component + expect(wrapper.findComponent({ name: 'SearchBar' }).exists()).toBe(true); + + // Check for FilterPanel component + expect(wrapper.findComponent({ name: 'FilterPanel' }).exists()).toBe(true); + + // Check for JsonViewer component + expect(wrapper.findComponent({ name: 'JsonViewer' }).exists()).toBe(true); + + // Check for ActionToolbar component + expect(wrapper.findComponent({ name: 'ActionToolbar' }).exists()).toBe(true); + }); + + it('toggles help panel', async () => { + const wrapper = mount(GamemasterExplorer); + + expect(wrapper.find('.help-panel').exists()).toBe(false); + + // Click help button + const helpButton = wrapper.findAll('.btn-icon')[0]; + await helpButton.trigger('click'); + + expect(wrapper.find('.help-panel').exists()).toBe(true); + expect(wrapper.text()).toContain('Keyboard Shortcuts'); + }); + + it('toggles settings panel', async () => { + const wrapper = mount(GamemasterExplorer); + + expect(wrapper.find('.settings-panel').exists()).toBe(false); + + // Click settings button + const settingsButton = wrapper.findAll('.btn-icon')[1]; + await settingsButton.trigger('click'); + + expect(wrapper.find('.settings-panel').exists()).toBe(true); + expect(wrapper.text()).toContain('Settings'); + }); + + it('has back to home link', () => { + const wrapper = mount(GamemasterExplorer); + const backLink = wrapper.find('.back-button'); + + expect(backLink.exists()).toBe(true); + expect(backLink.attributes('to')).toBe('/'); + }); + + it('calls loadStatus on mount', () => { + const { useGamemasterFiles } = await import( + '../../../src/composables/useGamemasterFiles.js' + ); + const mockLoadStatus = vi.fn(); + useGamemasterFiles.mockReturnValueOnce({ + selectedFile: ref(''), + fileContent: ref(''), + fileLines: ref([]), + displayLines: ref([]), + isLoading: ref(false), + fileError: ref(null), + preferences: ref({}), + hasFiles: ref(true), + fileTooLarge: ref(false), + loadStatus: mockLoadStatus + }); + + mount(GamemasterExplorer); + + // loadStatus should be called on mount + expect(mockLoadStatus).toHaveBeenCalled(); + }); + + it('computes filterData from fileContent', () => { + const wrapper = mount(GamemasterExplorer); + + // Component should parse JSON content for filtering + const filterData = wrapper.vm.filterData; + expect(filterData).toBeDefined(); + }); + + it('displays toast messages for clipboard operations', async () => { + const { useClipboard } = await import( + '../../../src/composables/useClipboard.js' + ); + useClipboard.mockReturnValueOnce({ + copied: ref(true), + error: ref(null), + copy: vi.fn() + }); + + const wrapper = mount(GamemasterExplorer); + expect(wrapper.find('.toast.success').exists()).toBe(true); + expect(wrapper.text()).toContain('Copied to clipboard'); + }); +});