This commit introduces system-native error dialogs on desktop application for code save or execution failures, addressing user confusion described in issue #264. This commit adds informative feedback when script execution or saving fails. Changes: - Implement support for system-native error dialogs. - Refactor `CodeRunner` and `Dialog` interfaces and their implementations to improve error handling and provide better type safety. - Introduce structured error handling, allowing UI to display detailed error messages. - Replace error throwing with an error object interface for controlled handling. This ensures that errors are propagated to the renderer process without being limited by Electron's error object serialization limitations as detailed in electron/electron#24427. - Add logging for dialog actions to aid in troubleshooting. - Rename `fileName` to `defaultFilename` in `saveFile` functions to clarify its purpose. - Centralize message assertion in `LoggerStub` for consistency. - Introduce `expectTrue` in tests for clearer boolean assertions. - Standardize `filename` usage across the codebase. - Enhance existing test names and organization for clarity. - Update related documentation.
101 lines
3.4 KiB
TypeScript
101 lines
3.4 KiB
TypeScript
import { mkdtemp } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { exec } from 'node:child_process';
|
|
import { describe, it } from 'vitest';
|
|
import { ScriptDirectoryProvider } from '@/infrastructure/CodeRunner/Creation/Directory/ScriptDirectoryProvider';
|
|
import { ScriptFileCreationOrchestrator } from '@/infrastructure/CodeRunner/Creation/ScriptFileCreationOrchestrator';
|
|
import { ScriptFileCodeRunner } from '@/infrastructure/CodeRunner/ScriptFileCodeRunner';
|
|
import { CurrentEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironmentFactory';
|
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
import { LinuxTerminalEmulator } from '@/infrastructure/CodeRunner/Execution/VisibleTerminalScriptFileExecutor';
|
|
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
|
|
|
describe('ScriptFileCodeRunner', () => {
|
|
it('executes simple script correctly', async ({ skip }) => {
|
|
// arrange
|
|
const currentOperatingSystem = CurrentEnvironment.os;
|
|
if (await shouldSkipTest(currentOperatingSystem)) {
|
|
skip();
|
|
return;
|
|
}
|
|
const temporaryDirectoryProvider = createTemporaryDirectoryProvider();
|
|
const codeRunner = createCodeRunner(temporaryDirectoryProvider);
|
|
const args = getPlatformSpecificArguments(currentOperatingSystem);
|
|
// act
|
|
const { success, error } = await codeRunner.runCode(...args);
|
|
// assert
|
|
expect(success).to.equal(true, formatAssertionMessage([
|
|
'Failed to successfully execute the script.',
|
|
'Details:', JSON.stringify(error),
|
|
]));
|
|
});
|
|
});
|
|
|
|
function getPlatformSpecificArguments(
|
|
os: OperatingSystem | undefined,
|
|
): Parameters<ScriptFileCodeRunner['runCode']> {
|
|
switch (os) {
|
|
case undefined:
|
|
throw new Error('Operating system detection failed: Unable to identify the current platform.');
|
|
case OperatingSystem.Windows:
|
|
return [
|
|
[
|
|
'@echo off',
|
|
'echo Hello, World!',
|
|
'exit /b 0',
|
|
].join('\n'),
|
|
'bat',
|
|
];
|
|
case OperatingSystem.macOS:
|
|
case OperatingSystem.Linux:
|
|
return [
|
|
[
|
|
'#!/bin/bash',
|
|
'echo "Hello, World!"',
|
|
'exit 0',
|
|
].join('\n'),
|
|
'sh',
|
|
];
|
|
default:
|
|
throw new Error(`Platform not supported: The current operating system (${os}) is not compatible with this script execution.`);
|
|
}
|
|
}
|
|
|
|
function shouldSkipTest(
|
|
os: OperatingSystem | undefined,
|
|
): Promise<boolean> {
|
|
if (os !== OperatingSystem.Linux) {
|
|
return Promise.resolve(false);
|
|
}
|
|
return isLinuxTerminalEmulatorSupported();
|
|
}
|
|
|
|
function isLinuxTerminalEmulatorSupported(): Promise<boolean> {
|
|
return new Promise((resolve) => {
|
|
exec(`which ${LinuxTerminalEmulator}`).on('close', (exitCode) => {
|
|
resolve(exitCode !== 0);
|
|
});
|
|
});
|
|
}
|
|
|
|
function createCodeRunner(directoryProvider: ScriptDirectoryProvider): ScriptFileCodeRunner {
|
|
return new ScriptFileCodeRunner(
|
|
undefined,
|
|
new ScriptFileCreationOrchestrator(undefined, undefined, directoryProvider),
|
|
);
|
|
}
|
|
|
|
function createTemporaryDirectoryProvider(): ScriptDirectoryProvider {
|
|
return {
|
|
provideScriptDirectory: async () => {
|
|
const temporaryDirectoryPathPrefix = join(tmpdir(), 'privacy-sexy-tests-');
|
|
const temporaryDirectoryFullPath = await mkdtemp(temporaryDirectoryPathPrefix);
|
|
return {
|
|
success: true,
|
|
directoryAbsolutePath: temporaryDirectoryFullPath,
|
|
};
|
|
},
|
|
};
|
|
}
|