Improve script error dialogs #304

- Include the script's directory path #304.
- Exclude Windows-specific instructions on non-Windows OS.
- Standardize language across dialogs for consistency.

Other supporting changes:

- Add script diagnostics data collection from main process.
- Document script file storage and execution tamper protection in
  SECURITY.md.
- Remove redundant comment in `NodeReadbackFileWriter`.
- Centralize error display for uniformity and simplicity.
- Simpify `WindowVariablesValidator` to omit checks when not on the
  renderer process.
- Improve and centralize Electron environment detection.
- Use more emphatic language (don't worry) in error messages.
This commit is contained in:
undergroundwires
2024-01-17 23:59:05 +01:00
parent f03fc24098
commit 6ada8d425c
34 changed files with 1182 additions and 450 deletions

View File

@@ -0,0 +1,149 @@
import { describe, it, expect } from 'vitest';
import { ScriptDiagnosticsCollector } from '@/application/ScriptDiagnostics/ScriptDiagnosticsCollector';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { Dialog } from '@/presentation/common/Dialog';
import { ScriptErrorDetails, createScriptErrorDialog } from '@/presentation/components/Code/CodeButtons/ScriptErrorDialog';
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
import { AllSupportedOperatingSystems } from '@tests/shared/TestCases/SupportedOperatingSystems';
import { ScriptDiagnosticsCollectorStub } from '@tests/unit/shared/Stubs/ScriptDiagnosticsCollectorStub';
describe('ScriptErrorDialog', () => {
describe('handles readback error type', () => {
it('handles file readback error', async () => {
// arrange
const errorDetails = createErrorDetails({ isFileReadbackError: true });
const context = new CreateScriptErrorDialogTestSetup()
.withDetails(errorDetails);
// act
const dialog = await context.createScriptErrorDialog();
// assert
assertValidDialog(dialog);
});
it('handles generic error', async () => {
// arrange
const errorDetails = createErrorDetails({ isFileReadbackError: false });
const context = new CreateScriptErrorDialogTestSetup()
.withDetails(errorDetails);
// act
const dialog = await context.createScriptErrorDialog();
// assert
assertValidDialog(dialog);
});
});
describe('handles supported operatingSystems', () => {
AllSupportedOperatingSystems.forEach((operatingSystem) => {
it(`${OperatingSystem[operatingSystem]}`, async () => {
// arrange
const diagnostics = new ScriptDiagnosticsCollectorStub()
.withOperatingSystem(operatingSystem);
const context = new CreateScriptErrorDialogTestSetup()
.withDiagnostics(diagnostics);
// act
const dialog = await context.createScriptErrorDialog();
// assert
assertValidDialog(dialog);
});
});
});
it('handles undefined diagnostics collector', async () => {
const diagnostics = undefined;
const context = new CreateScriptErrorDialogTestSetup()
.withDiagnostics(diagnostics);
// act
const dialog = await context.createScriptErrorDialog();
// assert
assertValidDialog(dialog);
});
it('handles undefined operating system', async () => {
// arrange
const undefinedOperatingSystem = undefined;
const diagnostics = new ScriptDiagnosticsCollectorStub()
.withOperatingSystem(undefinedOperatingSystem);
const context = new CreateScriptErrorDialogTestSetup()
.withDiagnostics(diagnostics);
// act
const dialog = await context.createScriptErrorDialog();
// assert
assertValidDialog(dialog);
});
it('handles directory path', async () => {
// arrange
const undefinedScriptsDirectory = undefined;
const diagnostics = new ScriptDiagnosticsCollectorStub()
.withScriptDirectoryPath(undefinedScriptsDirectory);
const context = new CreateScriptErrorDialogTestSetup()
.withDiagnostics(diagnostics);
// act
const dialog = await context.createScriptErrorDialog();
// assert
assertValidDialog(dialog);
});
describe('handles all contexts', () => {
const possibleContexts: ScriptErrorDetails['errorContext'][] = ['run', 'save'];
possibleContexts.forEach((dialogContext) => {
it(`${dialogContext} context`, async () => {
// arrange
const undefinedScriptsDirectory = undefined;
const diagnostics = new ScriptDiagnosticsCollectorStub()
.withScriptDirectoryPath(undefinedScriptsDirectory);
const context = new CreateScriptErrorDialogTestSetup()
.withDiagnostics(diagnostics);
// act
const dialog = await context.createScriptErrorDialog();
// assert
assertValidDialog(dialog);
});
});
});
});
function assertValidDialog(dialog: Parameters<Dialog['showError']>): void {
expectExists(dialog);
const [title, message] = dialog;
expectExists(title);
expect(title).to.have.length.greaterThan(1);
expectExists(message);
expect(message).to.have.length.greaterThan(1);
}
function createErrorDetails(partialDetails?: Partial<ScriptErrorDetails>): ScriptErrorDetails {
const defaultDetails: ScriptErrorDetails = {
errorContext: 'run',
errorType: 'test-error-type',
errorMessage: 'test error message',
isFileReadbackError: false,
};
return {
...defaultDetails,
...partialDetails,
};
}
class CreateScriptErrorDialogTestSetup {
private details: ScriptErrorDetails = createErrorDetails();
private diagnostics:
ScriptDiagnosticsCollector | undefined = new ScriptDiagnosticsCollectorStub();
public withDetails(details: ScriptErrorDetails): this {
this.details = details;
return this;
}
public withDiagnostics(diagnostics: ScriptDiagnosticsCollector | undefined): this {
this.diagnostics = diagnostics;
return this;
}
public createScriptErrorDialog() {
return createScriptErrorDialog(
this.details,
this.diagnostics,
);
}
}