/** * 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', async () => { 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', async () => { 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', async () => { 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', async () => { 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'); }); });