414 lines
13 KiB
JavaScript
414 lines
13 KiB
JavaScript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import { ref } from 'vue';
|
|
import { useGamemasterFiles } from '@/composables/useGamemasterFiles.js';
|
|
|
|
describe('useGamemasterFiles', () => {
|
|
let mockClient;
|
|
let composable;
|
|
|
|
beforeEach(() => {
|
|
// Mock GamemasterClient
|
|
mockClient = {
|
|
getStatus: vi.fn(() =>
|
|
Promise.resolve({
|
|
available: [
|
|
{ filename: 'pokemon.json', size: 5000 },
|
|
{ filename: 'moves.json', size: 3000 },
|
|
{ filename: 'allForms.json', size: 8000 },
|
|
{ filename: 'raw.json', size: 20000 }
|
|
]
|
|
})
|
|
),
|
|
getPokemon: vi.fn(() =>
|
|
Promise.resolve({
|
|
pokemon: [{ name: 'pikachu', id: 25 }]
|
|
})
|
|
),
|
|
getMoves: vi.fn(() =>
|
|
Promise.resolve({
|
|
moves: [{ name: 'thunderbolt', id: 24 }]
|
|
})
|
|
),
|
|
getAllForms: vi.fn(() =>
|
|
Promise.resolve({
|
|
forms: [{ name: 'pikachu-gmax' }]
|
|
})
|
|
),
|
|
getRaw: vi.fn(() =>
|
|
Promise.resolve({
|
|
raw: [{ data: 'raw content' }]
|
|
})
|
|
)
|
|
};
|
|
|
|
composable = useGamemasterFiles(mockClient);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('initialization', () => {
|
|
it('should initialize with empty state', () => {
|
|
expect(composable.selectedFile.value).toBe('');
|
|
expect(composable.fileContent.value).toBe('');
|
|
expect(composable.fileLines.value).toEqual([]);
|
|
expect(composable.displayLines.value).toEqual([]);
|
|
expect(composable.isLoading.value).toBe(false);
|
|
expect(composable.fileError.value).toBeNull();
|
|
});
|
|
|
|
it('should have default preferences', () => {
|
|
expect(composable.preferences.value).toBeDefined();
|
|
expect(composable.preferences.value.darkMode).toBe(false);
|
|
expect(composable.preferences.value.showLineNumbers).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('loadStatus', () => {
|
|
it('should fetch file list from server', async () => {
|
|
await composable.loadStatus();
|
|
expect(mockClient.getStatus).toHaveBeenCalled();
|
|
expect(composable.status.value.available).toHaveLength(4);
|
|
});
|
|
|
|
it('should set error on fetch failure', async () => {
|
|
mockClient.getStatus.mockRejectedValueOnce(new Error('Network error'));
|
|
await composable.loadStatus();
|
|
expect(composable.fileError.value).toBe('Network error');
|
|
});
|
|
|
|
it('should auto-load last file if preference set', async () => {
|
|
composable.preferences.value.lastFile = 'pokemon';
|
|
mockClient.getPokemon.mockResolvedValueOnce({ pokemon: [] });
|
|
|
|
await composable.loadStatus();
|
|
|
|
expect(composable.selectedFile.value).toBe('pokemon');
|
|
expect(mockClient.getPokemon).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should set loading state during fetch', async () => {
|
|
const loadingStates = [];
|
|
const originalSetTimeout = setTimeout;
|
|
|
|
await composable.loadStatus();
|
|
|
|
expect(composable.isLoading.value).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('loadFile', () => {
|
|
beforeEach(async () => {
|
|
await composable.loadStatus();
|
|
});
|
|
|
|
it('should return early if no file selected', async () => {
|
|
composable.selectedFile.value = '';
|
|
await composable.loadFile();
|
|
expect(mockClient.getPokemon).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should load pokemon file', async () => {
|
|
composable.selectedFile.value = 'pokemon';
|
|
await composable.loadFile();
|
|
|
|
expect(mockClient.getPokemon).toHaveBeenCalled();
|
|
expect(composable.fileContent.value).toContain('pikachu');
|
|
expect(composable.fileLines.value.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should load moves file', async () => {
|
|
composable.selectedFile.value = 'moves';
|
|
await composable.loadFile();
|
|
|
|
expect(mockClient.getMoves).toHaveBeenCalled();
|
|
expect(composable.fileContent.value).toContain('thunderbolt');
|
|
});
|
|
|
|
it('should load allForms file', async () => {
|
|
composable.selectedFile.value = 'allForms';
|
|
await composable.loadFile();
|
|
|
|
expect(mockClient.getAllForms).toHaveBeenCalled();
|
|
expect(composable.fileContent.value).toContain('pikachu-gmax');
|
|
});
|
|
|
|
it('should load raw file', async () => {
|
|
composable.selectedFile.value = 'raw';
|
|
await composable.loadFile();
|
|
|
|
expect(mockClient.getRaw).toHaveBeenCalled();
|
|
expect(composable.fileContent.value).toContain('raw content');
|
|
});
|
|
|
|
it('should reject raw file if too large', async () => {
|
|
composable.status.value.available = [
|
|
{
|
|
filename: 'raw.json',
|
|
size: 100 * 1024 * 1024 // 100MB
|
|
}
|
|
];
|
|
composable.selectedFile.value = 'raw';
|
|
|
|
await composable.loadFile();
|
|
|
|
expect(composable.fileError.value).toContain('very large');
|
|
expect(composable.fileContent.value).toBe('');
|
|
});
|
|
|
|
it('should set display lines for small files', async () => {
|
|
composable.selectedFile.value = 'pokemon';
|
|
await composable.loadFile();
|
|
|
|
expect(composable.displayLines.value.length).toBeGreaterThan(0);
|
|
expect(composable.displayLines.value[0]).toHaveProperty('lineNumber');
|
|
expect(composable.displayLines.value[0]).toHaveProperty('content');
|
|
expect(composable.displayLines.value[0]).toHaveProperty('hasMatch');
|
|
});
|
|
|
|
it('should limit displayed lines to 10000', async () => {
|
|
composable.selectedFile.value = 'pokemon';
|
|
await composable.loadFile();
|
|
|
|
expect(composable.displayLines.value.length).toBeLessThanOrEqual(
|
|
composable.LINES_TO_DISPLAY
|
|
);
|
|
});
|
|
|
|
it('should save last file preference', async () => {
|
|
await composable.loadStatus(); // Load status first
|
|
composable.selectedFile.value = 'moves';
|
|
await composable.loadFile();
|
|
|
|
expect(composable.preferences.value.lastFile).toBe('moves');
|
|
});
|
|
|
|
it('should handle unknown file type', async () => {
|
|
composable.selectedFile.value = 'unknown';
|
|
await composable.loadFile();
|
|
|
|
expect(composable.fileError.value).toContain('Unknown file type');
|
|
});
|
|
|
|
it('should handle file loading correctly', async () => {
|
|
await composable.loadStatus();
|
|
composable.selectedFile.value = 'pokemon';
|
|
await composable.loadFile();
|
|
|
|
expect(composable.fileContent.value).toBeDefined();
|
|
expect(composable.fileLines.value.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('clearFileSelection', () => {
|
|
beforeEach(async () => {
|
|
composable.selectedFile.value = 'pokemon';
|
|
composable.fileContent.value = 'test content';
|
|
composable.fileLines.value = ['line1', 'line2'];
|
|
composable.jsonPaths.value = ['/path1'];
|
|
composable.fileError.value = 'Some error';
|
|
});
|
|
|
|
it('should clear all file state', () => {
|
|
composable.clearFileSelection();
|
|
|
|
expect(composable.selectedFile.value).toBe('');
|
|
expect(composable.fileContent.value).toBe('');
|
|
expect(composable.fileLines.value).toEqual([]);
|
|
expect(composable.displayLines.value).toEqual([]);
|
|
expect(composable.jsonPaths.value).toEqual([]);
|
|
expect(composable.fileError.value).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('formatSize', () => {
|
|
it('should format bytes correctly', () => {
|
|
expect(composable.formatSize(0)).toBe('0 B');
|
|
expect(composable.formatSize(1024)).toContain('KB');
|
|
expect(composable.formatSize(1024 * 1024)).toContain('MB');
|
|
});
|
|
|
|
it('should handle null/undefined', () => {
|
|
expect(composable.formatSize(null)).toBe('0 B');
|
|
expect(composable.formatSize(undefined)).toBe('0 B');
|
|
});
|
|
});
|
|
|
|
describe('getFileType', () => {
|
|
it('should identify pokemon file', () => {
|
|
expect(composable.getFileType('pokemon.json')).toBe('pokemon');
|
|
});
|
|
|
|
it('should identify moves file', () => {
|
|
expect(composable.getFileType('moves.json')).toBe('moves');
|
|
});
|
|
|
|
it('should identify allForms file', () => {
|
|
expect(composable.getFileType('allForms.json')).toBe('allForms');
|
|
expect(composable.getFileType('AllForms.json')).toBe('allForms');
|
|
});
|
|
|
|
it('should identify raw file', () => {
|
|
expect(composable.getFileType('raw.json')).toBe('raw');
|
|
});
|
|
|
|
it('should return empty string for unknown type', () => {
|
|
expect(composable.getFileType('unknown.json')).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('formatFileName', () => {
|
|
it('should format pokemon file name', () => {
|
|
expect(composable.formatFileName('pokemon.json')).toBe('Pokemon');
|
|
});
|
|
|
|
it('should format moves file name', () => {
|
|
expect(composable.formatFileName('moves.json')).toBe('Moves');
|
|
});
|
|
|
|
it('should format allForms file name', () => {
|
|
expect(composable.formatFileName('allForms.json')).toBe(
|
|
'Pokemon All Forms'
|
|
);
|
|
});
|
|
|
|
it('should format raw file name', () => {
|
|
expect(composable.formatFileName('raw.json')).toBe('Raw Gamemaster');
|
|
});
|
|
});
|
|
|
|
describe('computed properties', () => {
|
|
describe('availableFiles', () => {
|
|
it('should return files from status', async () => {
|
|
await composable.loadStatus();
|
|
expect(composable.availableFiles.value).toHaveLength(4);
|
|
});
|
|
|
|
it('should return empty array initially', () => {
|
|
expect(composable.availableFiles.value).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('uniqueFiles', () => {
|
|
it('should return unique files sorted by type', async () => {
|
|
await composable.loadStatus();
|
|
expect(composable.uniqueFiles.value.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should sort files by type order', async () => {
|
|
await composable.loadStatus();
|
|
const types = composable.uniqueFiles.value.map(f =>
|
|
composable.getFileType(f.filename)
|
|
);
|
|
expect(types.indexOf('pokemon')).toBeLessThan(
|
|
types.indexOf('allForms')
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('hasFiles', () => {
|
|
it('should be false initially', () => {
|
|
expect(composable.hasFiles.value).toBe(false);
|
|
});
|
|
|
|
it('should be true after loading status', async () => {
|
|
await composable.loadStatus();
|
|
expect(composable.hasFiles.value).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('fileTooLarge', () => {
|
|
it('should be false for small files', async () => {
|
|
composable.selectedFile.value = 'pokemon';
|
|
await composable.loadFile();
|
|
expect(composable.fileTooLarge.value).toBe(false);
|
|
});
|
|
|
|
it('should compute correctly based on line count', () => {
|
|
// Manually set up a large line array
|
|
composable.fileLines.value = Array(15000).fill('line');
|
|
expect(composable.fileTooLarge.value).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('updateDisplayLines', () => {
|
|
beforeEach(async () => {
|
|
composable.selectedFile.value = 'pokemon';
|
|
await composable.loadFile();
|
|
});
|
|
|
|
it('should update display lines range', () => {
|
|
const originalCount = composable.displayLines.value.length;
|
|
composable.updateDisplayLines(0, 100);
|
|
|
|
expect(composable.displayLines.value.length).toBeLessThanOrEqual(100);
|
|
});
|
|
|
|
it('should set correct line numbers', () => {
|
|
// Create enough lines for this test
|
|
composable.fileLines.value = Array(200).fill('test line');
|
|
composable.updateDisplayLines(50, 150);
|
|
|
|
expect(composable.displayLines.value.length).toBeGreaterThan(0);
|
|
if (composable.displayLines.value.length > 0) {
|
|
expect(composable.displayLines.value[0].lineNumber).toBe(51);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('expandDisplayLinesToInclude', () => {
|
|
it('should not expand if line is visible', () => {
|
|
composable.fileLines.value = Array(500).fill('line');
|
|
composable.updateDisplayLines(0, 100);
|
|
const originalLength = composable.displayLines.value.length;
|
|
|
|
composable.expandDisplayLinesToInclude(50);
|
|
|
|
expect(composable.displayLines.value.length).toBe(originalLength);
|
|
});
|
|
|
|
it('should expand to include requested line', () => {
|
|
composable.fileLines.value = Array(500).fill('line');
|
|
composable.updateDisplayLines(0, 100);
|
|
|
|
composable.expandDisplayLinesToInclude(300);
|
|
|
|
expect(composable.displayLines.value.length).toBeGreaterThan(100);
|
|
});
|
|
|
|
it('should not exceed total file lines', () => {
|
|
composable.fileLines.value = Array(500).fill('line');
|
|
composable.expandDisplayLinesToInclude(999999);
|
|
|
|
expect(composable.displayLines.value.length).toBeLessThanOrEqual(
|
|
composable.fileLines.value.length
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('file selection watch', () => {
|
|
it('should load file when selectedFile changes', async () => {
|
|
composable.selectedFile.value = 'pokemon';
|
|
await new Promise(r => setTimeout(r, 50));
|
|
|
|
expect(mockClient.getPokemon).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not load when selectedFile is empty', async () => {
|
|
composable.selectedFile.value = '';
|
|
await new Promise(r => setTimeout(r, 50));
|
|
|
|
expect(mockClient.getPokemon).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('constants', () => {
|
|
it('should have correct constants', () => {
|
|
expect(composable.LINES_TO_DISPLAY).toBe(10000);
|
|
expect(composable.MAX_RAW_FILE_SIZE).toBe(50 * 1024 * 1024);
|
|
});
|
|
});
|
|
});
|