Skip to content

Commit 178b456

Browse files
committed
Fix open-folder and create-folder error handling
- Add try/catch to Create Workspace and Open Folder button handlers so failures surface an alert instead of silently swallowing errors - Guard main-process dialog handlers against null/destroyed window - Guard create-new-file and create-new-folder against missing workspace - Show user message when New File/New Folder is invoked with no workspace - Add preload API availability check at renderer startup - Add 5 smoke tests covering the new guardrails (100 tests total)
1 parent 570b970 commit 178b456

3 files changed

Lines changed: 76 additions & 14 deletions

File tree

src/main.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ function handleWindowClose(event) {
163163
// ── IPC: folder operations ────────────────────────────────────────────────────
164164

165165
ipcMain.handle('open-folder', async () => {
166+
if (!mainWindow || mainWindow.isDestroyed()) {
167+
throw new Error('Application window is not ready. Please restart DocFoundry.');
168+
}
166169
const result = await dialog.showOpenDialog(mainWindow, {
167170
properties: ['openDirectory']
168171
});
@@ -204,17 +207,24 @@ ipcMain.handle('confirm-save-and-close', async (_event, didSave) => {
204207
});
205208

206209
ipcMain.handle('create-workspace', async () => {
210+
if (!mainWindow || mainWindow.isDestroyed()) {
211+
throw new Error('Application window is not ready. Please restart DocFoundry.');
212+
}
207213
const result = await dialog.showOpenDialog(mainWindow, {
208214
properties: ['openDirectory', 'createDirectory']
209215
});
210216
if (result.canceled || result.filePaths.length === 0) return null;
211217

212218
currentFolder = path.resolve(result.filePaths[0]);
213219
hasUnsavedChanges = false;
214-
const entries = fs.readdirSync(currentFolder);
215-
if (entries.length === 0) {
216-
const readme = path.join(currentFolder, 'README.md');
217-
fs.writeFileSync(readme, '# New Workspace\n\nStart writing here.\n', 'utf-8');
220+
try {
221+
const entries = fs.readdirSync(currentFolder);
222+
if (entries.length === 0) {
223+
const readme = path.join(currentFolder, 'README.md');
224+
fs.writeFileSync(readme, '# New Workspace\n\nStart writing here.\n', 'utf-8');
225+
}
226+
} catch (err) {
227+
console.error('Failed to initialise workspace folder:', err);
218228
}
219229
startFileWatcher(currentFolder);
220230
return readFolderTree(currentFolder);
@@ -223,6 +233,7 @@ ipcMain.handle('create-workspace', async () => {
223233
// ── IPC: file operations ──────────────────────────────────────────────────────
224234

225235
ipcMain.handle('create-new-file', async (_event, parentDir, fileName) => {
236+
if (!currentFolder) throw new Error('No workspace is open. Open or create a workspace first.');
226237
validateFileName(fileName);
227238
const resolvedDir = resolveWorkspacePath(currentFolder, parentDir);
228239
const filePath = path.join(resolvedDir, fileName);
@@ -234,6 +245,7 @@ ipcMain.handle('create-new-file', async (_event, parentDir, fileName) => {
234245
});
235246

236247
ipcMain.handle('create-new-folder', async (_event, parentDir, folderName) => {
248+
if (!currentFolder) throw new Error('No workspace is open. Open or create a workspace first.');
237249
validateFileName(folderName);
238250
const resolvedDir = resolveWorkspacePath(currentFolder, parentDir);
239251
const folderPath = path.join(resolvedDir, folderName);

src/renderer/renderer.js

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,20 @@ if (versionEl && api) {
9292
versionEl.textContent = `${api.appName} v${api.version}`;
9393
}
9494

95+
// ── API guard ─────────────────────────────────────────────────────────────────
96+
if (!api) {
97+
window.alert('DocFoundry failed to initialise. Please reinstall the application.');
98+
}
99+
95100
// ── Button wiring ─────────────────────────────────────────────────────────────
96101
btnCreate.addEventListener('click', async () => {
97-
const tree = await api.createWorkspace();
98-
if (tree) showWorkspace(tree);
102+
try {
103+
const tree = await api.createWorkspace();
104+
if (tree) showWorkspace(tree);
105+
} catch (err) {
106+
console.error('Create workspace failed:', err);
107+
window.alert(`Could not create workspace: ${err.message}`);
108+
}
99109
});
100110

101111
btnOpen.addEventListener('click', doOpenFolder);
@@ -414,14 +424,19 @@ function scheduleAutoSave() {
414424

415425
// ── Open folder ───────────────────────────────────────────────────────────────
416426
async function doOpenFolder() {
417-
// Save all dirty tabs before switching
418-
for (const tab of tabs) {
419-
if (tab.dirty && tab.path) {
420-
await saveTab(tab.id);
427+
try {
428+
// Save all dirty tabs before switching
429+
for (const tab of tabs) {
430+
if (tab.dirty && tab.path) {
431+
await saveTab(tab.id);
432+
}
421433
}
434+
const tree = await api.openFolder();
435+
if (tree) showWorkspace(tree);
436+
} catch (err) {
437+
console.error('Open folder failed:', err);
438+
window.alert(`Could not open folder: ${err.message}`);
422439
}
423-
const tree = await api.openFolder();
424-
if (tree) showWorkspace(tree);
425440
}
426441

427442
// ── Show workspace ────────────────────────────────────────────────────────────
@@ -666,7 +681,10 @@ function showContextMenu(event, entry) {
666681
// ── File CRUD ─────────────────────────────────────────────────────────────────
667682
async function promptNewFile(parentDir) {
668683
const dir = parentDir || workspaceRoot;
669-
if (!dir) return;
684+
if (!dir) {
685+
window.alert('Open a workspace first before creating files.');
686+
return;
687+
}
670688
const name = await showInputDialog({
671689
title: 'Create new file',
672690
label: 'File name',
@@ -692,7 +710,10 @@ async function promptNewFile(parentDir) {
692710

693711
async function promptNewFolder(parentDir) {
694712
const dir = parentDir || workspaceRoot;
695-
if (!dir) return;
713+
if (!dir) {
714+
window.alert('Open a workspace first before creating folders.');
715+
return;
716+
}
696717
const name = await showInputDialog({
697718
title: 'Create new folder',
698719
label: 'Folder name',

tests/smoke.test.mjs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,4 +300,33 @@ describe('Feature surface', () => {
300300
expect(renderer).toContain("../../node_modules/mermaid/dist/mermaid.min.js");
301301
expect(renderer).not.toContain('cdn.jsdelivr.net');
302302
});
303+
304+
it('renderer guards against missing preload API', () => {
305+
const renderer = fs.readFileSync(path.resolve('src/renderer/renderer.js'), 'utf-8');
306+
expect(renderer).toContain("if (!api)");
307+
expect(renderer).toContain("failed to initialise");
308+
});
309+
310+
it('renderer error-handles the create-workspace and open-folder flows', () => {
311+
const renderer = fs.readFileSync(path.resolve('src/renderer/renderer.js'), 'utf-8');
312+
expect(renderer).toContain("Could not create workspace:");
313+
expect(renderer).toContain("Could not open folder:");
314+
});
315+
316+
it('renderer warns when creating files or folders without a workspace', () => {
317+
const renderer = fs.readFileSync(path.resolve('src/renderer/renderer.js'), 'utf-8');
318+
expect(renderer).toContain('Open a workspace first before creating files.');
319+
expect(renderer).toContain('Open a workspace first before creating folders.');
320+
});
321+
322+
it('main process validates mainWindow before showing dialogs', () => {
323+
const main = fs.readFileSync(path.resolve('src/main.js'), 'utf-8');
324+
expect(main).toContain('mainWindow.isDestroyed()');
325+
expect(main).toContain('Application window is not ready');
326+
});
327+
328+
it('main process guards file/folder creation against missing workspace', () => {
329+
const main = fs.readFileSync(path.resolve('src/main.js'), 'utf-8');
330+
expect(main).toContain("if (!currentFolder) throw new Error('No workspace is open");
331+
});
303332
});

0 commit comments

Comments
 (0)