This commit introduces native operating system file dialogs in the desktop application replacing the existing web-based dialogs. It lays the foundation for future enhancements such as: - Providing error messages when saving or executing files, addressing #264. - Creating system restore points, addressing #50. Documentation updates: - Update `desktop-vs-web-features.md` with added functionality. - Update `README.md` with security feature highlights. - Update home page documentation to emphasize security features. Other supporting changes include: - Integrate IPC communication channels for secure Electron dialog API interactions. - Refactor `IpcRegistration` for more type-safety and simplicity. - Introduce a Vue hook to encapsulate dialog functionality. - Improve errors during IPC registration for easier troubleshooting. - Move `ClientLoggerFactory` for consistency in hooks organization and remove `LoggerFactory` interface for simplicity. - Add tests for the save file dialog in the browser context. - Add `Blob` polyfill in tests to compensate for the missing `blob.text()` function in `jsdom` (see jsdom/jsdom#2555). Improve environment detection logic: - Treat test environment as browser environments to correctly activate features based on the environment. This resolves issues where the environment is misidentified as desktop, but Electron preloader APIs are missing. - Rename `isDesktop` environment identification variable to `isRunningAsDesktopApplication` for better clarity and to avoid confusion with desktop environments in web/browser/test environments. - Simplify `BrowserRuntimeEnvironment` to consistently detect non-desktop application environments. - Improve environment detection for Electron main process (electron/electron#2288).
95 lines
3.2 KiB
TypeScript
95 lines
3.2 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 { expectDoesNotThrowAsync } from '@tests/shared/Assertions/ExpectThrowsAsync';
|
|
import { CurrentEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironmentFactory';
|
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
import { LinuxTerminalEmulator } from '@/infrastructure/CodeRunner/Execution/VisibleTerminalScriptFileExecutor';
|
|
|
|
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 act = () => codeRunner.runCode(...args);
|
|
// assert
|
|
await expectDoesNotThrowAsync(act);
|
|
});
|
|
});
|
|
|
|
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 temporaryDirectoryFullPath;
|
|
},
|
|
};
|
|
}
|