Files
privacy.sexy/tests/unit/presentation/components/Shared/Hooks/Dialog/ClientDialogFactory.spec.ts
undergroundwires e09db0f1bd Show save/execution error dialogs on desktop #264
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.
2024-01-14 22:35:53 +01:00

139 lines
5.1 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
createEnvironmentSpecificLoggedDialog, WindowDialogCreationFunction,
BrowserDialogCreationFunction, DialogLoggingDecorator,
} from '@/presentation/components/Shared/Hooks/Dialog/ClientDialogFactory';
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
import { RuntimeEnvironmentStub } from '@tests/unit/shared/Stubs/RuntimeEnvironmentStub';
import { DialogStub } from '@tests/unit/shared/Stubs/DialogStub';
import { collectExceptionMessage } from '@tests/unit/shared/ExceptionCollector';
import { Dialog } from '@/presentation/common/Dialog';
describe('ClientDialogFactory', () => {
describe('createEnvironmentSpecificLoggedDialog', () => {
describe('dialog selection based on environment', () => {
describe('when in non-desktop environment', () => {
it('provides a browser dialog', () => {
// arrange
const expectedDialog = new DialogStub();
const context = new DialogCreationTestSetup()
.withEnvironment(new RuntimeEnvironmentStub().withIsRunningAsDesktopApplication(false))
.withBrowserDialogFactory(() => expectedDialog);
// act
const actualDialog = context.createDialogForTest();
// assert
expect(expectedDialog).to.equal(actualDialog);
});
});
describe('when in desktop environment', () => {
it('provides a window-injected dialog', () => {
// arrange
const expectedDialog = new DialogStub();
const context = new DialogCreationTestSetup()
.withEnvironment(new RuntimeEnvironmentStub().withIsRunningAsDesktopApplication(true))
.withWindowInjectedDialogFactory(() => expectedDialog);
// act
const actualDialog = context.createDialogForTest();
// assert
expect(expectedDialog).to.equal(actualDialog);
});
it('throws error if window-injected dialog is not available', () => {
// arrange
const expectedError = 'Failed to retrieve Dialog API from window object in desktop environment.';
const context = new DialogCreationTestSetup()
.withEnvironment(new RuntimeEnvironmentStub().withIsRunningAsDesktopApplication(true))
.withWindowInjectedDialogFactory(() => undefined);
// act
const act = () => context.createDialogForTest();
// assert
const actualError = collectExceptionMessage(act);
expect(actualError).to.include(expectedError);
});
});
});
describe('dialog decoration with logging', () => {
it('returns a dialog decorated with logging', () => {
// arrange
const expectedLoggingDialogStub = new DialogStub();
const decoratorStub: DialogLoggingDecorator = () => expectedLoggingDialogStub;
const context = new DialogCreationTestSetup()
.withDialogLoggingDecorator(decoratorStub);
// act
const actualDialog = context.createDialogForTest();
// assert
expect(expectedLoggingDialogStub).to.equal(actualDialog);
});
it('applies logging decorator to the provided dialog', () => {
// arrange
const expectedDialog = new DialogStub();
let actualDecoratedDialog: Dialog | undefined;
const decoratorStub: DialogLoggingDecorator = (dialog) => {
actualDecoratedDialog = dialog;
return new DialogStub();
};
const context = new DialogCreationTestSetup()
.withEnvironment(new RuntimeEnvironmentStub().withIsRunningAsDesktopApplication(false))
.withBrowserDialogFactory(() => expectedDialog)
.withDialogLoggingDecorator(decoratorStub);
// act
context.createDialogForTest();
// assert
expect(expectedDialog).to.equal(actualDecoratedDialog);
});
});
});
});
class DialogCreationTestSetup {
private environment: RuntimeEnvironment = new RuntimeEnvironmentStub();
private browserDialogFactory: BrowserDialogCreationFunction = () => new DialogStub();
private windowInjectedDialogFactory: WindowDialogCreationFunction = () => new DialogStub();
private dialogLoggingDecorator: DialogLoggingDecorator = (dialog) => dialog;
public withEnvironment(environment: RuntimeEnvironment): this {
this.environment = environment;
return this;
}
public withBrowserDialogFactory(browserDialogFactory: BrowserDialogCreationFunction): this {
this.browserDialogFactory = browserDialogFactory;
return this;
}
public withWindowInjectedDialogFactory(
windowInjectedDialogFactory: WindowDialogCreationFunction,
): this {
this.windowInjectedDialogFactory = windowInjectedDialogFactory;
return this;
}
public withDialogLoggingDecorator(
dialogLoggingDecorator: DialogLoggingDecorator,
): this {
this.dialogLoggingDecorator = dialogLoggingDecorator;
return this;
}
public createDialogForTest() {
return createEnvironmentSpecificLoggedDialog(
this.environment,
this.dialogLoggingDecorator,
this.windowInjectedDialogFactory,
this.browserDialogFactory,
);
}
}