- Move phase/plan docs into docs/ - Move legacy node:test files into tests/legacy/ with README - Remove .backup file, test audit artifacts, and unused AI prompt/skill files - Remove broken iOS GitHub workflows (reference missing MovieMapper-iOS/)
456 lines
13 KiB
JavaScript
456 lines
13 KiB
JavaScript
// Test edge cases and error handling for folder navigation
|
|
const { test } = require('node:test');
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const UIManager = require('./utils/renderer/UIManager');
|
|
|
|
test('Navigation handles non-existent folder gracefully', async () => {
|
|
const mockUIManager = {
|
|
currentDirectory: '/test/source',
|
|
selectedDirEl: { textContent: '' },
|
|
|
|
appState: {
|
|
navigationStack: ['/test/source'],
|
|
currentDepth: 0,
|
|
|
|
addToNavigationStack: function(dir) {
|
|
this.navigationStack.push(dir);
|
|
this.currentDepth = this.navigationStack.length - 1;
|
|
},
|
|
|
|
getNavigationStack: function() {
|
|
return this.navigationStack;
|
|
}
|
|
},
|
|
|
|
scanDirectory: async (dir) => {
|
|
return { success: true, files: [] };
|
|
},
|
|
|
|
_logAuditEvent: async () => {},
|
|
|
|
openDirectory: async (directory) => {
|
|
this.appState.addToNavigationStack(directory);
|
|
this.currentDirectory = directory;
|
|
},
|
|
|
|
handleFolderClick: async (folderPath, folderName) => {
|
|
console.log('Folder clicked:', folderName, 'at path:', folderPath);
|
|
|
|
// Validate folder path exists
|
|
if (!fs.existsSync(folderPath) || !fs.statSync(folderPath).isDirectory()) {
|
|
console.error('Invalid folder path:', folderPath);
|
|
return; // Don't navigate
|
|
}
|
|
|
|
// Open the folder
|
|
await this.openDirectory(folderPath);
|
|
}
|
|
};
|
|
|
|
// Mock fs.existsSync and fs.statSync
|
|
const originalExistsSync = fs.existsSync;
|
|
const originalStatSync = fs.statSync;
|
|
|
|
fs.existsSync = (path) => {
|
|
if (path === '/non/existent/path') return false;
|
|
return originalExistsSync(path);
|
|
};
|
|
|
|
fs.statSync = () => {
|
|
throw new Error('ENOENT: no such file or directory');
|
|
};
|
|
|
|
const consoleError = console.error;
|
|
console.error = () => {};
|
|
|
|
try {
|
|
mockUIManager.handleFolderClick('/non/existent/path', 'nonexistent');
|
|
|
|
// Verify navigation stack wasn't modified
|
|
assert.strictEqual(mockUIManager.appState.getNavigationStack().length, 1);
|
|
assert.strictEqual(mockUIManager.currentDirectory, '/test/source');
|
|
|
|
console.log('Non-existent folder test passed');
|
|
} finally {
|
|
fs.existsSync = originalExistsSync;
|
|
fs.statSync = originalStatSync;
|
|
console.error = consoleError;
|
|
}
|
|
});
|
|
|
|
test('Navigation handles permission errors gracefully', async () => {
|
|
const mockUIManager = {
|
|
currentDirectory: '/test/source',
|
|
selectedDirEl: { textContent: '' },
|
|
|
|
appState: {
|
|
navigationStack: ['/test/source'],
|
|
currentDepth: 0,
|
|
|
|
addToNavigationStack: function(dir) {
|
|
this.navigationStack.push(dir);
|
|
this.currentDepth = this.navigationStack.length - 1;
|
|
},
|
|
|
|
getNavigationStack: function() {
|
|
return this.navigationStack;
|
|
}
|
|
},
|
|
|
|
scanDirectory: async (dir) => {
|
|
return { success: true, files: [] };
|
|
},
|
|
|
|
_logAuditEvent: async () => {},
|
|
|
|
openDirectory: async (directory) => {
|
|
this.appState.addToNavigationStack(directory);
|
|
this.currentDirectory = directory;
|
|
},
|
|
|
|
handleFolderClick: async (folderPath, folderName) => {
|
|
console.log('Folder clicked:', folderName, 'at path:', folderPath);
|
|
|
|
// Validate folder path exists
|
|
if (!fs.existsSync(folderPath) || !fs.statSync(folderPath).isDirectory()) {
|
|
console.error('Invalid folder path:', folderPath);
|
|
return; // Don't navigate
|
|
}
|
|
|
|
// Open the folder
|
|
await this.openDirectory(folderPath);
|
|
}
|
|
};
|
|
|
|
// Mock fs.statSync to throw permission error
|
|
const originalStatSync = fs.statSync;
|
|
|
|
fs.statSync = (path) => {
|
|
if (path === '/permission/denied') {
|
|
const error = new Error('EACCES: permission denied');
|
|
error.code = 'EACCES';
|
|
throw error;
|
|
}
|
|
return originalStatSync(path);
|
|
};
|
|
|
|
const consoleError = console.error;
|
|
console.error = () => {};
|
|
|
|
try {
|
|
mockUIManager.handleFolderClick('/permission/denied', 'denied');
|
|
|
|
// Verify navigation stack wasn't modified
|
|
assert.strictEqual(mockUIManager.appState.getNavigationStack().length, 1);
|
|
|
|
console.log('Permission error handling test passed');
|
|
} finally {
|
|
fs.statSync = originalStatSync;
|
|
console.error = consoleError;
|
|
}
|
|
});
|
|
|
|
test('Breadcrumb navigation handles empty stack', () => {
|
|
const mockUIManager = {
|
|
selectedDirEl: { textContent: '' },
|
|
|
|
appState: {
|
|
getNavigationStack: () => [],
|
|
canGoBack: () => false
|
|
}
|
|
};
|
|
|
|
mockUIManager.updateBreadcrumbNavigation = function() {
|
|
const breadcrumbs = this.appState.getNavigationStack();
|
|
|
|
// Should not throw for empty stack
|
|
breadcrumbs.forEach(() => {
|
|
assert.ok(false, 'Should not iterate over empty stack');
|
|
});
|
|
|
|
return document.createElement('div');
|
|
};
|
|
|
|
try {
|
|
const container = mockUIManager.updateBreadcrumbNavigation();
|
|
assert.ok(true, 'Should handle empty stack');
|
|
} catch (error) {
|
|
assert.ok(false, 'Should not throw for empty stack');
|
|
}
|
|
});
|
|
|
|
test('Breadcrumb navigation handles null stack', () => {
|
|
const mockUIManager = {
|
|
selectedDirEl: { textContent: '' },
|
|
|
|
appState: {
|
|
getNavigationStack: () => null,
|
|
canGoBack: () => false
|
|
}
|
|
};
|
|
|
|
mockUIManager.updateBreadcrumbNavigation = function() {
|
|
// Should handle null stack gracefully
|
|
return document.createElement('div');
|
|
};
|
|
|
|
try {
|
|
const container = mockUIManager.updateBreadcrumbNavigation();
|
|
assert.ok(true, 'Should handle null stack');
|
|
} catch (error) {
|
|
assert.ok(false, 'Should not throw for null stack');
|
|
}
|
|
});
|
|
|
|
test('Go back from root does not cause errors', () => {
|
|
const mockUIManager = {
|
|
currentDirectory: '/test/source',
|
|
selectedDirEl: { textContent: '' },
|
|
|
|
appState: {
|
|
navigationStack: ['/test/source'],
|
|
currentDepth: 0,
|
|
|
|
goBack: function() {
|
|
if (this.currentDepth > 0) {
|
|
this.navigationStack.pop();
|
|
this.currentDepth--;
|
|
return this.navigationStack[this.currentDepth];
|
|
}
|
|
return null;
|
|
},
|
|
|
|
canGoBack: function() {
|
|
return this.currentDepth > 0;
|
|
}
|
|
},
|
|
|
|
scanDirectory: async (dir) => {
|
|
return { success: true, files: [] };
|
|
},
|
|
|
|
_logAuditEvent: async () => {},
|
|
|
|
goBack: async () => {
|
|
const parentDirectory = this.appState.goBack();
|
|
if (parentDirectory) {
|
|
this.currentDirectory = parentDirectory;
|
|
}
|
|
return parentDirectory;
|
|
},
|
|
|
|
updateBreadcrumbNavigation: function() {
|
|
// Mock breadcrumb update
|
|
}
|
|
};
|
|
|
|
// Try to go back when already at root
|
|
const result = mockUIManager.goBack();
|
|
assert.strictEqual(result, null, 'Should return null when at root');
|
|
assert.strictEqual(mockUIManager.currentDirectory, '/test/source', 'Should not change directory');
|
|
});
|
|
|
|
test('Special folder names are valid', () => {
|
|
const specialNames = ['extras', 'behind the scenes', 'delete', 'trailers', 'featurettes'];
|
|
|
|
specialNames.forEach(name => {
|
|
// Verify name doesn't contain invalid characters
|
|
assert.ok(!name.includes(path.sep), `Folder name "${name}" contains path separator`);
|
|
|
|
// Verify name is not too long
|
|
assert.ok(name.length <= 255, `Folder name "${name}" exceeds 255 characters`);
|
|
|
|
// Verify name is not empty
|
|
assert.ok(name.length > 0, `Folder name "${name}" should not be empty`);
|
|
});
|
|
|
|
console.log('Special folder names validation passed');
|
|
});
|
|
|
|
test('Navigation preserves file list state', async () => {
|
|
const testDir = path.join(__dirname, 'test_state_preservation');
|
|
|
|
if (!fs.existsSync(testDir)) {
|
|
fs.mkdirSync(testDir);
|
|
}
|
|
|
|
try {
|
|
const mockUIManager = {
|
|
currentDirectory: null,
|
|
selectedDirEl: { textContent: '' },
|
|
fileListEl: { querySelectorAll: () => [] },
|
|
|
|
appState: {
|
|
navigationStack: [],
|
|
currentDepth: 0,
|
|
|
|
addToNavigationStack: function(dir) {
|
|
this.navigationStack.push(dir);
|
|
this.currentDepth = this.navigationStack.length - 1;
|
|
}
|
|
},
|
|
|
|
scanDirectory: async (dir) => {
|
|
// Mock different file lists for different directories
|
|
if (dir === path.join(testDir, 'folder1')) {
|
|
return { success: true, files: [{ name: 'file1.mp4' }] };
|
|
} else if (dir === path.join(testDir, 'folder2')) {
|
|
return { success: true, files: [{ name: 'file2.mkv' }] };
|
|
}
|
|
return { success: true, files: [] };
|
|
},
|
|
|
|
_logAuditEvent: async () => {},
|
|
|
|
openDirectory: async (directory) => {
|
|
this.appState.addToNavigationStack(directory);
|
|
this.currentDirectory = directory;
|
|
const result = await this.scanDirectory(directory);
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// Navigate to folder1
|
|
const result1 = await mockUIManager.openDirectory(path.join(testDir, 'folder1'));
|
|
assert.strictEqual(result1.files.length, 1);
|
|
assert.strictEqual(result1.files[0].name, 'file1.mp4');
|
|
|
|
// Navigate to folder2
|
|
const result2 = await mockUIManager.openDirectory(path.join(testDir, 'folder2'));
|
|
assert.strictEqual(result2.files.length, 1);
|
|
assert.strictEqual(result2.files[0].name, 'file2.mkv');
|
|
|
|
// Go back to folder1
|
|
mockUIManager.appState.goBack();
|
|
assert.strictEqual(mockUIManager.currentDirectory, path.join(testDir, 'folder1'));
|
|
|
|
console.log('File list state preservation test passed');
|
|
} finally {
|
|
if (fs.existsSync(testDir)) {
|
|
fs.rmSync(testDir, { recursive: true });
|
|
}
|
|
}
|
|
});
|
|
|
|
test('Multiple rapid navigations', async () => {
|
|
const testDir = path.join(__dirname, 'test_rapid_nav');
|
|
|
|
if (!fs.existsSync(testDir)) {
|
|
fs.mkdirSync(testDir);
|
|
}
|
|
|
|
try {
|
|
const mockUIManager = {
|
|
currentDirectory: null,
|
|
selectedDirEl: { textContent: '' },
|
|
|
|
appState: {
|
|
navigationStack: [],
|
|
currentDepth: 0,
|
|
|
|
addToNavigationStack: function(dir) {
|
|
this.navigationStack.push(dir);
|
|
this.currentDepth = this.navigationStack.length - 1;
|
|
},
|
|
|
|
goBack: function() {
|
|
if (this.currentDepth > 0) {
|
|
this.navigationStack.pop();
|
|
this.currentDepth--;
|
|
return this.navigationStack[this.currentDepth];
|
|
}
|
|
return null;
|
|
}
|
|
},
|
|
|
|
scanDirectory: async (dir) => {
|
|
return { success: true, files: [] };
|
|
},
|
|
|
|
_logAuditEvent: async () => {},
|
|
|
|
openDirectory: async (directory) => {
|
|
this.appState.addToNavigationStack(directory);
|
|
this.currentDirectory = directory;
|
|
}
|
|
};
|
|
|
|
// Rapidly navigate between directories
|
|
for (let i = 0; i < 10; i++) {
|
|
await mockUIManager.openDirectory(path.join(testDir, `folder${i}`));
|
|
}
|
|
|
|
// Verify navigation stack
|
|
assert.strictEqual(mockUIManager.appState.getNavigationStack().length, 11); // testDir + 10 folders
|
|
assert.strictEqual(mockUIManager.currentDirectory, path.join(testDir, 'folder9'));
|
|
|
|
console.log('Multiple rapid navigations test passed');
|
|
} finally {
|
|
if (fs.existsSync(testDir)) {
|
|
fs.rmSync(testDir, { recursive: true });
|
|
}
|
|
}
|
|
});
|
|
|
|
test('Navigation with special characters in folder names', async () => {
|
|
const testDir = path.join(__dirname, 'test_special_chars');
|
|
|
|
if (!fs.existsSync(testDir)) {
|
|
fs.mkdirSync(testDir);
|
|
}
|
|
|
|
try {
|
|
// Create folders with special characters
|
|
const specialNames = ['folder with spaces', 'folder-dashes', 'folder_underscores'];
|
|
|
|
for (const name of specialNames) {
|
|
const folderPath = path.join(testDir, name);
|
|
if (!fs.existsSync(folderPath)) {
|
|
fs.mkdirSync(folderPath);
|
|
}
|
|
}
|
|
|
|
const mockUIManager = {
|
|
currentDirectory: null,
|
|
selectedDirEl: { textContent: '' },
|
|
|
|
appState: {
|
|
navigationStack: [],
|
|
currentDepth: 0,
|
|
|
|
addToNavigationStack: function(dir) {
|
|
this.navigationStack.push(dir);
|
|
this.currentDepth = this.navigationStack.length - 1;
|
|
}
|
|
},
|
|
|
|
scanDirectory: async (dir) => {
|
|
return { success: true, files: [] };
|
|
},
|
|
|
|
_logAuditEvent: async () => {},
|
|
|
|
openDirectory: async (directory) => {
|
|
this.appState.addToNavigationStack(directory);
|
|
this.currentDirectory = directory;
|
|
}
|
|
};
|
|
|
|
// Navigate to folders with special characters
|
|
for (const name of specialNames) {
|
|
const folderPath = path.join(testDir, name);
|
|
await mockUIManager.openDirectory(folderPath);
|
|
assert.strictEqual(mockUIManager.currentDirectory, folderPath);
|
|
}
|
|
|
|
console.log('Special characters in folder names test passed');
|
|
} finally {
|
|
if (fs.existsSync(testDir)) {
|
|
fs.rmSync(testDir, { recursive: true });
|
|
}
|
|
}
|
|
});
|
|
|
|
console.log('All edge case tests passed!'); |