Files
memory-infrastructure-palace/code/websites/pokedex.online/tests/unit/composables/useLineSelection.test.js

311 lines
11 KiB
JavaScript

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ref } from 'vue';
import { useLineSelection } from '@/composables/useLineSelection.js';
describe('useLineSelection', () => {
let displayLines;
let fileContent;
let selectedFile;
let composable;
beforeEach(() => {
displayLines = ref([
{ lineNumber: 1, content: 'line 1' },
{ lineNumber: 2, content: 'line 2' },
{ lineNumber: 3, content: 'line 3' },
{ lineNumber: 4, content: 'line 4' },
{ lineNumber: 5, content: 'line 5' }
]);
fileContent = ref('line 1\nline 2\nline 3\nline 4\nline 5');
selectedFile = ref('test-file');
composable = useLineSelection(displayLines, fileContent, selectedFile);
// Mock clipboard
global.navigator = {
clipboard: {
writeText: vi.fn()
}
};
// Mock URL methods
global.URL.createObjectURL = vi.fn(() => 'blob:test');
global.URL.revokeObjectURL = vi.fn();
// Mock document methods
document.body.appendChild = vi.fn();
document.body.removeChild = vi.fn();
});
describe('initialization', () => {
it('should initialize with empty selection', () => {
expect(composable.selectedLines.value.size).toBe(0);
expect(composable.hasSelection.value).toBe(false);
expect(composable.selectionCount.value).toBe(0);
});
});
describe('toggleLineSelection', () => {
it('should select single line with no modifiers', () => {
const event = { shiftKey: false, ctrlKey: false, metaKey: false };
composable.toggleLineSelection(1, event);
expect(composable.selectedLines.value.has(1)).toBe(true);
expect(composable.selectedLines.value.size).toBe(1);
});
it('should clear previous selection on single click', () => {
const event = { shiftKey: false, ctrlKey: false, metaKey: false };
composable.toggleLineSelection(1, event);
composable.toggleLineSelection(3, event);
expect(composable.selectedLines.value.has(1)).toBe(false);
expect(composable.selectedLines.value.has(3)).toBe(true);
expect(composable.selectedLines.value.size).toBe(1);
});
it('should toggle line with Ctrl modifier', () => {
const ctrlEvent = { shiftKey: false, ctrlKey: true, metaKey: false };
composable.toggleLineSelection(1, ctrlEvent);
expect(composable.selectedLines.value.has(1)).toBe(true);
composable.toggleLineSelection(1, ctrlEvent);
expect(composable.selectedLines.value.has(1)).toBe(false);
});
it('should toggle line with Cmd modifier (Mac)', () => {
const cmdEvent = { shiftKey: false, ctrlKey: false, metaKey: true };
composable.toggleLineSelection(1, cmdEvent);
expect(composable.selectedLines.value.has(1)).toBe(true);
composable.toggleLineSelection(1, cmdEvent);
expect(composable.selectedLines.value.has(1)).toBe(false);
});
it('should add multiple lines with Ctrl', () => {
const ctrlEvent = { shiftKey: false, ctrlKey: true, metaKey: false };
composable.toggleLineSelection(1, ctrlEvent);
composable.toggleLineSelection(3, ctrlEvent);
composable.toggleLineSelection(5, ctrlEvent);
expect(composable.selectedLines.value.size).toBe(3);
expect(composable.selectedLines.value.has(1)).toBe(true);
expect(composable.selectedLines.value.has(3)).toBe(true);
expect(composable.selectedLines.value.has(5)).toBe(true);
});
it('should select range with Shift modifier', () => {
const event = { shiftKey: false, ctrlKey: false, metaKey: false };
const shiftEvent = { shiftKey: true, ctrlKey: false, metaKey: false };
composable.toggleLineSelection(2, event); // Set starting point
composable.toggleLineSelection(4, shiftEvent); // Select range
expect(composable.selectedLines.value.has(2)).toBe(true);
expect(composable.selectedLines.value.has(3)).toBe(true);
expect(composable.selectedLines.value.has(4)).toBe(true);
expect(composable.selectedLines.value.size).toBe(3);
});
it('should select range in reverse order with Shift', () => {
const event = { shiftKey: false, ctrlKey: false, metaKey: false };
const shiftEvent = { shiftKey: true, ctrlKey: false, metaKey: false };
composable.toggleLineSelection(4, event); // Set starting point
composable.toggleLineSelection(2, shiftEvent); // Select range backwards
expect(composable.selectedLines.value.has(2)).toBe(true);
expect(composable.selectedLines.value.has(3)).toBe(true);
expect(composable.selectedLines.value.has(4)).toBe(true);
expect(composable.selectedLines.value.size).toBe(3);
});
});
describe('clearSelection', () => {
it('should clear all selections', () => {
const event = { shiftKey: false, ctrlKey: false, metaKey: false };
composable.toggleLineSelection(1, event);
composable.toggleLineSelection(3, event);
composable.clearSelection();
expect(composable.selectedLines.value.size).toBe(0);
expect(composable.hasSelection.value).toBe(false);
});
});
describe('getSelectedContent', () => {
it('should return empty string when no selection', () => {
expect(composable.getSelectedContent()).toBe('');
});
it('should return selected lines content', () => {
const event = { shiftKey: false, ctrlKey: false, metaKey: false };
const ctrlEvent = { shiftKey: false, ctrlKey: true, metaKey: false };
composable.toggleLineSelection(1, event);
composable.toggleLineSelection(3, ctrlEvent);
const content = composable.getSelectedContent();
expect(content).toContain('line 1');
expect(content).toContain('line 3');
expect(content).not.toContain('line 2');
});
it('should maintain line order in output', () => {
const ctrlEvent = { shiftKey: false, ctrlKey: true, metaKey: false };
composable.toggleLineSelection(5, ctrlEvent);
composable.toggleLineSelection(2, ctrlEvent);
composable.toggleLineSelection(4, ctrlEvent);
const content = composable.getSelectedContent();
const lines = content.split('\n');
expect(lines[0]).toBe('line 2');
expect(lines[1]).toBe('line 4');
expect(lines[2]).toBe('line 5');
});
});
describe('computed properties', () => {
it('should update hasSelection', () => {
expect(composable.hasSelection.value).toBe(false);
const event = { shiftKey: false, ctrlKey: false, metaKey: false };
composable.toggleLineSelection(1, event);
expect(composable.hasSelection.value).toBe(true);
});
it('should update selectionCount', () => {
const ctrlEvent = { shiftKey: false, ctrlKey: true, metaKey: false };
expect(composable.selectionCount.value).toBe(0);
composable.toggleLineSelection(1, ctrlEvent);
expect(composable.selectionCount.value).toBe(1);
composable.toggleLineSelection(3, ctrlEvent);
expect(composable.selectionCount.value).toBe(2);
});
it('should return sorted selected line numbers', () => {
const ctrlEvent = { shiftKey: false, ctrlKey: true, metaKey: false };
composable.toggleLineSelection(5, ctrlEvent);
composable.toggleLineSelection(2, ctrlEvent);
composable.toggleLineSelection(4, ctrlEvent);
expect(composable.selectedLineNumbers.value).toEqual([2, 4, 5]);
});
});
describe('selectAll', () => {
it('should select all available lines', () => {
composable.selectAll();
expect(composable.selectedLines.value.size).toBe(5);
expect(composable.hasSelection.value).toBe(true);
for (let i = 1; i <= 5; i++) {
expect(composable.selectedLines.value.has(i)).toBe(true);
}
});
});
describe('invertSelection', () => {
it('should invert selection', () => {
const ctrlEvent = { shiftKey: false, ctrlKey: true, metaKey: false };
composable.toggleLineSelection(2, ctrlEvent);
composable.toggleLineSelection(4, ctrlEvent);
composable.invertSelection();
expect(composable.selectedLines.value.has(1)).toBe(true);
expect(composable.selectedLines.value.has(2)).toBe(false);
expect(composable.selectedLines.value.has(3)).toBe(true);
expect(composable.selectedLines.value.has(4)).toBe(false);
expect(composable.selectedLines.value.has(5)).toBe(true);
});
it('should invert empty selection to select all', () => {
composable.invertSelection();
expect(composable.selectedLines.value.size).toBe(5);
});
});
describe('copySelected', () => {
it('should not copy if no selection', async () => {
await composable.copySelected();
expect(global.navigator.clipboard.writeText).not.toHaveBeenCalled();
});
it('should copy selected lines', async () => {
const event = { shiftKey: false, ctrlKey: false, metaKey: false };
composable.toggleLineSelection(1, event);
await composable.copySelected();
expect(global.navigator.clipboard.writeText).toHaveBeenCalledWith(
'line 1'
);
});
});
describe('copyAll', () => {
it('should copy all content', async () => {
await composable.copyAll();
expect(global.navigator.clipboard.writeText).toHaveBeenCalledWith(
fileContent.value
);
});
});
describe('exportSelected', () => {
it('should not export if no selection', () => {
const createElementSpy = vi.spyOn(document, 'createElement');
composable.exportSelected();
expect(createElementSpy).not.toHaveBeenCalledWith('a');
});
it('should export selected lines as file', () => {
const event = { shiftKey: false, ctrlKey: false, metaKey: false };
const ctrlEvent = { shiftKey: false, ctrlKey: true, metaKey: false };
composable.toggleLineSelection(1, event);
composable.toggleLineSelection(3, ctrlEvent);
const createElementSpy = vi.spyOn(document, 'createElement');
composable.exportSelected();
const linkElement = createElementSpy.mock.results.find(
r => r.value?.click
)?.value;
expect(linkElement?.download).toContain('test-file-selected');
});
});
describe('exportAll', () => {
it('should export all content as file', () => {
const createElementSpy = vi.spyOn(document, 'createElement');
composable.exportAll();
const linkElement = createElementSpy.mock.results.find(
r => r.value?.click
)?.value;
expect(linkElement?.download).toContain('test-file');
expect(linkElement?.download).not.toContain('selected');
});
});
describe('shareUrl', () => {
it('should copy current URL to clipboard', async () => {
await composable.shareUrl();
expect(global.navigator.clipboard.writeText).toHaveBeenCalled();
const call = global.navigator.clipboard.writeText.mock.calls[0][0];
expect(call).toContain(globalThis.location.href || '');
});
});
});