Files
privacy.sexy/tests/unit/infrastructure/Dialog/LoggingDialogDecorator.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

164 lines
5.5 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { Logger } from '@/application/Common/Log/Logger';
import { decorateWithLogging } from '@/infrastructure/Dialog/LoggingDialogDecorator';
import { Dialog, FileType, SaveFileOutcome } from '@/presentation/common/Dialog';
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
import { DialogStub } from '@tests/unit/shared/Stubs/DialogStub';
import { LoggerStub } from '@tests/unit/shared/Stubs/LoggerStub';
describe('LoggingDialogDecorator', () => {
describe('decorateWithLogging', () => {
describe('saveFile', () => {
it('delegates call to dialog', async () => {
// arrange
const expectedArguments = createTestSaveFileArguments();
const dialog = new DialogStub();
const context = new LoggingDialogDecoratorTestSetup()
.withDialog(dialog);
// act
const decorator = context.decorateWithLogging();
await decorator.saveFile(...expectedArguments);
// assert
expect(dialog.callHistory).to.have.lengthOf(1);
const call = dialog.callHistory.find((c) => c.methodName === 'saveFile');
expectExists(call);
const actualArguments = call.args;
expect(expectedArguments).to.deep.equal(actualArguments);
});
it('returns dialog\'s response', async () => {
// arrange
const expectedResult: SaveFileOutcome = { success: true };
const dialog = new DialogStub();
dialog.saveFile = () => Promise.resolve(expectedResult);
const context = new LoggingDialogDecoratorTestSetup()
.withDialog(dialog);
// act
const decorator = context.decorateWithLogging();
const actualResult = await decorator.saveFile(...createTestSaveFileArguments());
// assert
expect(expectedResult).to.equal(actualResult);
});
it('logs information on invocation', async () => {
// arrange
const expectedLogMessagePart = 'Opening save file dialog';
const loggerStub = new LoggerStub();
const context = new LoggingDialogDecoratorTestSetup()
.withLogger(loggerStub);
// act
const decorator = context.decorateWithLogging();
await decorator.saveFile(...createTestSaveFileArguments());
// assert
loggerStub.assertLogsContainMessagePart('info', expectedLogMessagePart);
});
it('logs information on success', async () => {
// arrange
const expectedLogMessagePart = 'completed successfully';
const loggerStub = new LoggerStub();
const context = new LoggingDialogDecoratorTestSetup()
.withLogger(loggerStub);
// act
const decorator = context.decorateWithLogging();
await decorator.saveFile(...createTestSaveFileArguments());
// assert
loggerStub.assertLogsContainMessagePart('info', expectedLogMessagePart);
});
it('logs error on save failure', async () => {
// arrange
const expectedLogMessagePart = 'Error encountered';
const loggerStub = new LoggerStub();
const dialog = new DialogStub();
dialog.saveFile = () => Promise.resolve({ success: false, error: { message: 'error', type: 'DialogDisplayError' } });
const context = new LoggingDialogDecoratorTestSetup()
.withLogger(loggerStub);
// act
const decorator = context.decorateWithLogging();
await decorator.saveFile(...createTestSaveFileArguments());
// assert
loggerStub.assertLogsContainMessagePart('error', expectedLogMessagePart);
});
});
describe('showError', () => {
it('delegates call to the dialog', () => {
// arrange
const expectedArguments = createTestShowErrorArguments();
const dialog = new DialogStub();
const context = new LoggingDialogDecoratorTestSetup()
.withDialog(dialog);
// act
const decorator = context.decorateWithLogging();
decorator.showError(...expectedArguments);
// assert
expect(dialog.callHistory).to.have.lengthOf(1);
const call = dialog.callHistory.find((c) => c.methodName === 'showError');
expectExists(call);
const actualArguments = call.args;
expect(expectedArguments).to.deep.equal(actualArguments);
});
it('logs error message', () => {
// arrange
const expectedLogMessagePart = 'Showing error dialog';
const loggerStub = new LoggerStub();
const context = new LoggingDialogDecoratorTestSetup()
.withLogger(loggerStub);
// act
const decorator = context.decorateWithLogging();
decorator.showError(...createTestShowErrorArguments());
// assert
loggerStub.assertLogsContainMessagePart('error', expectedLogMessagePart);
});
});
});
});
class LoggingDialogDecoratorTestSetup {
private dialog: Dialog = new DialogStub();
private logger: Logger = new LoggerStub();
public withDialog(dialog: Dialog): this {
this.dialog = dialog;
return this;
}
public withLogger(logger: Logger): this {
this.logger = logger;
return this;
}
public decorateWithLogging() {
return decorateWithLogging(
this.dialog,
this.logger,
);
}
}
function createTestSaveFileArguments(): Parameters<Dialog['saveFile']> {
return [
'test-file-contents',
'test-default-filename',
FileType.BatchFile,
];
}
function createTestShowErrorArguments(): Parameters<Dialog['showError']> {
return [
'test-error-title',
'test-error-message',
];
}