311 lines
11 KiB
JavaScript
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 || '');
|
|
});
|
|
});
|
|
});
|