Add unit tests for useLineSelection composable

This commit is contained in:
2026-01-29 03:28:06 +00:00
parent b97d1c1f71
commit 12ea08a7e1
3 changed files with 560 additions and 0 deletions

View File

@@ -0,0 +1,209 @@
import { ref, computed } from 'vue';
import { useClipboard } from '@/composables/useClipboard.js';
/**
* useLineSelection - Composable for managing line selection operations
*
* Handles:
* - Single, range, and multi-line selection with Shift/Ctrl modifiers
* - Copy selected/all lines to clipboard
* - Export selected/all lines to JSON file
* - URL sharing
* - Selection state management
*
* @param {Ref<Set>} displayLines - Current displayed lines with metadata
* @param {Ref<string>} fileContent - Full file content for export
* @param {Ref<string>} selectedFile - Current file name for export naming
* @returns {Object} Line selection composable API
*/
export function useLineSelection(displayLines, fileContent, selectedFile) {
// Clipboard composable
const clipboard = useClipboard();
// Selection state - use Set for O(1) lookups
const selectedLines = ref(new Set());
/**
* Check if any lines are selected
*/
const hasSelection = computed(() => selectedLines.value.size > 0);
/**
* Get count of selected lines
*/
const selectionCount = computed(() => selectedLines.value.size);
/**
* Get selected line numbers as sorted array
*/
const selectedLineNumbers = computed(() => {
return [...selectedLines.value].sort((a, b) => a - b);
});
/**
* Handle line selection with modifiers
*
* Supports:
* - Single click: Select only that line
* - Shift+click: Select range from last selected to current
* - Ctrl/Cmd+click: Toggle individual line
*/
function toggleLineSelection(lineNumber, event) {
if (event.shiftKey && selectedLines.value.size > 0) {
// Range selection
const lastSelected = Math.max(...selectedLines.value);
const start = Math.min(lastSelected, lineNumber);
const end = Math.max(lastSelected, lineNumber);
for (let i = start; i <= end; i++) {
selectedLines.value.add(i);
}
} else if (event.ctrlKey || event.metaKey) {
// Toggle individual line
if (selectedLines.value.has(lineNumber)) {
selectedLines.value.delete(lineNumber);
} else {
selectedLines.value.add(lineNumber);
}
} else {
// Single selection
selectedLines.value.clear();
selectedLines.value.add(lineNumber);
}
}
/**
* Clear all selections
*/
function clearSelection() {
selectedLines.value.clear();
}
/**
* Get content of selected lines
*/
function getSelectedContent() {
if (selectedLines.value.size === 0) return '';
const lines = selectedLineNumbers.value;
const content = lines
.map(lineNum => {
return (
displayLines.value.find(l => l.lineNumber === lineNum)?.content || ''
);
})
.join('\n');
return content;
}
/**
* Copy selected lines to clipboard
*/
async function copySelected() {
if (selectedLines.value.size === 0) return;
const content = getSelectedContent();
await clipboard.copyToClipboard(content);
}
/**
* Copy all content to clipboard
*/
async function copyAll() {
await clipboard.copyToClipboard(fileContent.value);
}
/**
* Export selected lines to JSON file
*/
function exportSelected() {
if (selectedLines.value.size === 0) return;
const content = getSelectedContent();
downloadFile(
content,
`${selectedFile.value}-selected-${Date.now()}.json`
);
}
/**
* Export all content to JSON file
*/
function exportAll() {
downloadFile(fileContent.value, `${selectedFile.value}-${Date.now()}.json`);
}
/**
* Helper: Download content as file
*/
function downloadFile(content, filename) {
const blob = new Blob([content], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
/**
* Share current URL (copy to clipboard)
*/
async function shareUrl() {
const url = globalThis.location.href;
await clipboard.copyToClipboard(url);
}
/**
* Select all available lines
*/
function selectAll() {
displayLines.value.forEach(line => {
selectedLines.value.add(line.lineNumber);
});
}
/**
* Invert selection (select all not selected, deselect all selected)
*/
function invertSelection() {
const allLineNumbers = new Set(
displayLines.value.map(line => line.lineNumber)
);
const newSelection = new Set();
allLineNumbers.forEach(lineNum => {
if (!selectedLines.value.has(lineNum)) {
newSelection.add(lineNum);
}
});
selectedLines.value.clear();
newSelection.forEach(lineNum => selectedLines.value.add(lineNum));
}
return {
// State
selectedLines,
// Computed
hasSelection,
selectionCount,
selectedLineNumbers,
// Methods
toggleLineSelection,
clearSelection,
getSelectedContent,
copySelected,
copyAll,
exportSelected,
exportAll,
shareUrl,
selectAll,
invertSelection
};
}