Improve desktop runtime execution tests
Test improvements: - Capture titles for all macOS windows, not just the frontmost. - Incorporate missing application log files. - Improve log clarity with enriched context. - Improve application termination on macOS by reducing grace period. - Ensure complete application termination on macOS. - Validate Vue application loading through an initial log. - Support ignoring environment-specific `stderr` errors. - Do not fail the test if working directory cannot be deleted. - Use retry pattern when installing dependencies due to network errors. Refactorings: - Migrate the test code to TypeScript. - Replace deprecated `rmdir` with `rm` for error-resistant directory removal. - Improve sanity checking by shifting from App.vue to Vue bootstrapper. - Centralize environment variable management with `EnvironmentVariables` construct. - Rename infrastructure/Environment to RuntimeEnvironment for clarity. - Isolate WindowVariables and SystemOperations from RuntimeEnvironment. - Inject logging via preloader. - Correct mislabeled RuntimeSanity tests. Configuration: - Introduce `npm run check:desktop` for simplified execution. - Omit `console.log` override due to `nodeIntegration` restrictions and reveal logging functionality using context-bridging.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { EnvironmentStub } from '@tests/unit/shared/Stubs/EnvironmentStub';
|
||||
import { RuntimeEnvironmentStub } from '@tests/unit/shared/Stubs/RuntimeEnvironmentStub';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { CodeRunner } from '@/infrastructure/CodeRunner';
|
||||
import { expectThrowsAsync } from '@tests/unit/shared/Assertions/ExpectThrowsAsync';
|
||||
@@ -8,23 +8,10 @@ import { OperatingSystemOpsStub } from '@tests/unit/shared/Stubs/OperatingSystem
|
||||
import { LocationOpsStub } from '@tests/unit/shared/Stubs/LocationOpsStub';
|
||||
import { FileSystemOpsStub } from '@tests/unit/shared/Stubs/FileSystemOpsStub';
|
||||
import { CommandOpsStub } from '@tests/unit/shared/Stubs/CommandOpsStub';
|
||||
import { IFileSystemOps, ISystemOperations } from '@/infrastructure/Environment/SystemOperations/ISystemOperations';
|
||||
import { IFileSystemOps, ISystemOperations } from '@/infrastructure/SystemOperations/ISystemOperations';
|
||||
import { FunctionKeys } from '@/TypeHelpers';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
|
||||
describe('CodeRunner', () => {
|
||||
describe('ctor throws if system is missing', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing system operations';
|
||||
const environment = new EnvironmentStub()
|
||||
.withSystemOperations(absentValue);
|
||||
// act
|
||||
const act = () => new CodeRunner(environment);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('runCode', () => {
|
||||
it('creates temporary directory recursively', async () => {
|
||||
// arrange
|
||||
@@ -222,10 +209,9 @@ class TestContext {
|
||||
private systemOperations: ISystemOperations = new SystemOperationsStub();
|
||||
|
||||
public async runCode(): Promise<void> {
|
||||
const environment = new EnvironmentStub()
|
||||
.withOs(this.os)
|
||||
.withSystemOperations(this.systemOperations);
|
||||
const runner = new CodeRunner(environment);
|
||||
const environment = new RuntimeEnvironmentStub()
|
||||
.withOs(this.os);
|
||||
const runner = new CodeRunner(this.systemOperations, environment);
|
||||
await runner.runCode(this.code, this.folderName, this.fileExtension);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { validateWindowVariables } from '@/infrastructure/Environment/WindowVariablesValidator';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { SystemOperationsStub } from '@tests/unit/shared/Stubs/SystemOperationsStub';
|
||||
import { WindowVariables } from '@/infrastructure/Environment/WindowVariables';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
|
||||
describe('WindowVariablesValidator', () => {
|
||||
describe('validateWindowVariables', () => {
|
||||
describe('invalid types', () => {
|
||||
it('throws an error if variables is not an object', () => {
|
||||
// arrange
|
||||
const expectedError = 'window is not an object but string';
|
||||
const variablesAsString = 'not an object';
|
||||
// act
|
||||
const act = () => validateWindowVariables(variablesAsString as unknown);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
|
||||
it('throws an error if variables is an array', () => {
|
||||
// arrange
|
||||
const expectedError = 'window is not an object but object';
|
||||
const arrayVariables: unknown = [];
|
||||
// act
|
||||
const act = () => validateWindowVariables(arrayVariables as unknown);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
|
||||
describe('throws an error if variables is null', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing variables';
|
||||
const variables = absentValue;
|
||||
// act
|
||||
const act = () => validateWindowVariables(variables as unknown);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('property validations', () => {
|
||||
it('throws an error with a description of all invalid properties', () => {
|
||||
// arrange
|
||||
const expectedError = 'Unexpected os (string)\nUnexpected isDesktop (string)';
|
||||
const input = {
|
||||
os: 'invalid',
|
||||
isDesktop: 'not a boolean',
|
||||
};
|
||||
// act
|
||||
const act = () => validateWindowVariables(input as unknown);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
|
||||
describe('`os` property', () => {
|
||||
it('throws an error when os is not a number', () => {
|
||||
// arrange
|
||||
const expectedError = 'Unexpected os (string)';
|
||||
const input = {
|
||||
os: 'Linux',
|
||||
};
|
||||
// act
|
||||
const act = () => validateWindowVariables(input as unknown);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
|
||||
it('throws an error for an invalid numeric os value', () => {
|
||||
// arrange
|
||||
const expectedError = 'Unexpected os (number)';
|
||||
const input = {
|
||||
os: Number.MAX_SAFE_INTEGER,
|
||||
};
|
||||
// act
|
||||
const act = () => validateWindowVariables(input as unknown);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
|
||||
it('does not throw for a missing os value', () => {
|
||||
const input = {
|
||||
isDesktop: true,
|
||||
system: new SystemOperationsStub(),
|
||||
};
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('`isDesktop` property', () => {
|
||||
it('throws an error when only isDesktop is provided and it is true without a system object', () => {
|
||||
// arrange
|
||||
const expectedError = 'Unexpected system (undefined)';
|
||||
const input = {
|
||||
isDesktop: true,
|
||||
};
|
||||
// act
|
||||
const act = () => validateWindowVariables(input as unknown);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
|
||||
it('does not throw when isDesktop is true with a valid system object', () => {
|
||||
// arrange
|
||||
const input = {
|
||||
isDesktop: true,
|
||||
system: new SystemOperationsStub(),
|
||||
};
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
|
||||
it('does not throw when isDesktop is false without a system object', () => {
|
||||
// arrange
|
||||
const input = {
|
||||
isDesktop: false,
|
||||
};
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('`system` property', () => {
|
||||
it('throws an error if system is not an object', () => {
|
||||
// arrange
|
||||
const expectedError = 'Unexpected system (string)';
|
||||
const input = {
|
||||
isDesktop: true,
|
||||
system: 'invalid system',
|
||||
};
|
||||
// act
|
||||
const act = () => validateWindowVariables(input as unknown);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('does not throw for a valid object', () => {
|
||||
const input: WindowVariables = {
|
||||
os: OperatingSystem.Windows,
|
||||
isDesktop: true,
|
||||
system: new SystemOperationsStub(),
|
||||
};
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import {
|
||||
describe,
|
||||
} from 'vitest';
|
||||
import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
|
||||
import { EnvironmentVariablesFactory, EnvironmentVariablesValidator } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
|
||||
import { ViteEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/Vite/ViteEnvironmentVariables';
|
||||
import { IEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/IEnvironmentVariables';
|
||||
|
||||
describe('EnvironmentVariablesFactory', () => {
|
||||
describe('instance', () => {
|
||||
itIsSingleton({
|
||||
getter: () => EnvironmentVariablesFactory.Current.instance,
|
||||
expectedType: ViteEnvironmentVariables,
|
||||
});
|
||||
});
|
||||
it('creates the correct type', () => {
|
||||
// arrange
|
||||
const sut = new TestableEnvironmentVariablesFactory();
|
||||
// act
|
||||
const metadata = sut.instance;
|
||||
// assert
|
||||
expect(metadata).to.be.instanceOf(ViteEnvironmentVariables);
|
||||
});
|
||||
it('validates its instance', () => {
|
||||
// arrange
|
||||
let validatedInstance: IEnvironmentVariables;
|
||||
const validatorMock = (instanceToValidate: IEnvironmentVariables) => {
|
||||
validatedInstance = instanceToValidate;
|
||||
};
|
||||
// act
|
||||
const sut = new TestableEnvironmentVariablesFactory(validatorMock);
|
||||
const actualInstance = sut.instance;
|
||||
// assert
|
||||
expect(actualInstance).to.equal(validatedInstance);
|
||||
});
|
||||
it('throws error if validator fails', () => {
|
||||
// arrange
|
||||
const expectedError = 'validator failed';
|
||||
const failingValidator = () => {
|
||||
throw new Error(expectedError);
|
||||
};
|
||||
// act
|
||||
const act = () => new TestableEnvironmentVariablesFactory(failingValidator);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
|
||||
class TestableEnvironmentVariablesFactory extends EnvironmentVariablesFactory {
|
||||
public constructor(validator: EnvironmentVariablesValidator = () => { /* NO OP */ }) {
|
||||
super(validator);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { EnvironmentVariablesStub } from '@tests/unit/shared/Stubs/EnvironmentVariablesStub';
|
||||
import { validateEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesValidator';
|
||||
import { IEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/IEnvironmentVariables';
|
||||
|
||||
describe('EnvironmentVariablesValidator', () => {
|
||||
it('does not throw if all environment keys have values', () => {
|
||||
// arrange
|
||||
const environment = new EnvironmentVariablesStub();
|
||||
// act
|
||||
const act = () => validateEnvironmentVariables(environment);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
it('does not throw if a boolean key has false value', () => {
|
||||
// arrange
|
||||
const environmentWithFalseBoolean: Partial<IEnvironmentVariables> = {
|
||||
isNonProduction: false,
|
||||
};
|
||||
const environment: IEnvironmentVariables = {
|
||||
...new EnvironmentVariablesStub(),
|
||||
...environmentWithFalseBoolean,
|
||||
};
|
||||
// act
|
||||
const act = () => validateEnvironmentVariables(environment);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
describe('throws as expected', () => {
|
||||
describe('"missing environment" if environment is not provided', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing environment';
|
||||
const environment = absentValue;
|
||||
// act
|
||||
const act = () => validateEnvironmentVariables(environment);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
it('"missing keys" if environment has properties with missing values', () => {
|
||||
// arrange
|
||||
const expectedError = 'Environment keys missing: name, homepageUrl';
|
||||
const missingData: Partial<IEnvironmentVariables> = {
|
||||
name: undefined,
|
||||
homepageUrl: undefined,
|
||||
};
|
||||
const environment: IEnvironmentVariables = {
|
||||
...new EnvironmentVariablesStub(),
|
||||
...missingData,
|
||||
};
|
||||
// act
|
||||
const act = () => validateEnvironmentVariables(environment);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('"missing keys" if environment has getters with missing values', () => {
|
||||
// arrange
|
||||
const expectedError = 'Environment keys missing: name, homepageUrl';
|
||||
const stubWithGetters: Partial<IEnvironmentVariables> = {
|
||||
get name() {
|
||||
return undefined;
|
||||
},
|
||||
get homepageUrl() {
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
const environment: IEnvironmentVariables = {
|
||||
...new EnvironmentVariablesStub(),
|
||||
...stubWithGetters,
|
||||
};
|
||||
// act
|
||||
const act = () => validateEnvironmentVariables(environment);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws "unable to capture" if environment has no getters or properties', () => {
|
||||
// arrange
|
||||
const expectedError = 'Unable to capture key/value pairs';
|
||||
const environment = {} as IEnvironmentVariables;
|
||||
// act
|
||||
const act = () => validateEnvironmentVariables(environment);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import { VITE_ENVIRONMENT_KEYS } from '@/infrastructure/Metadata/Vite/ViteEnvironmentKeys';
|
||||
import { VITE_ENVIRONMENT_KEYS } from '@/infrastructure/EnvironmentVariables/Vite/ViteEnvironmentKeys';
|
||||
|
||||
describe('VITE_ENVIRONMENT_KEYS', () => {
|
||||
describe('each key should have a non-empty string', () => {
|
||||
@@ -1,11 +1,12 @@
|
||||
import {
|
||||
describe, beforeEach, afterEach, expect,
|
||||
} from 'vitest';
|
||||
import { ViteAppMetadata } from '@/infrastructure/Metadata/Vite/ViteAppMetadata';
|
||||
import { VITE_ENVIRONMENT_KEYS } from '@/infrastructure/Metadata/Vite/ViteEnvironmentKeys';
|
||||
import { VITE_ENVIRONMENT_KEYS } from '@/infrastructure/EnvironmentVariables/Vite/ViteEnvironmentKeys';
|
||||
import { PropertyKeys } from '@/TypeHelpers';
|
||||
import { IEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/IEnvironmentVariables';
|
||||
import { ViteEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/Vite/ViteEnvironmentVariables';
|
||||
|
||||
describe('ViteAppMetadata', () => {
|
||||
describe('ViteEnvironmentVariables', () => {
|
||||
describe('reads values from import.meta.env', () => {
|
||||
let originalMetaEnv;
|
||||
beforeEach(() => {
|
||||
@@ -15,13 +16,15 @@ describe('ViteAppMetadata', () => {
|
||||
Object.assign(import.meta.env, originalMetaEnv);
|
||||
});
|
||||
|
||||
interface ITestCase {
|
||||
readonly getActualValue: (sut: ViteAppMetadata) => string;
|
||||
interface ITestCase<T> {
|
||||
readonly getActualValue: (sut: IEnvironmentVariables) => T;
|
||||
readonly environmentVariable: typeof VITE_ENVIRONMENT_KEYS[
|
||||
keyof typeof VITE_ENVIRONMENT_KEYS];
|
||||
readonly expected: string;
|
||||
readonly expected: T;
|
||||
}
|
||||
const testCases: { readonly [K in PropertyKeys<ViteAppMetadata>]: ITestCase } = {
|
||||
const testCases: {
|
||||
readonly [K in PropertyKeys<IEnvironmentVariables>]: ITestCase<string | boolean>;
|
||||
} = {
|
||||
name: {
|
||||
environmentVariable: VITE_ENVIRONMENT_KEYS.NAME,
|
||||
expected: 'expected-name',
|
||||
@@ -47,6 +50,11 @@ describe('ViteAppMetadata', () => {
|
||||
expected: 'expected-homepageUrl',
|
||||
getActualValue: (sut) => sut.homepageUrl,
|
||||
},
|
||||
isNonProduction: {
|
||||
environmentVariable: VITE_ENVIRONMENT_KEYS.DEV,
|
||||
expected: false,
|
||||
getActualValue: (sut) => sut.isNonProduction,
|
||||
},
|
||||
};
|
||||
Object.values(testCases).forEach(({ environmentVariable, expected, getActualValue }) => {
|
||||
it(`should correctly get the value of ${environmentVariable}`, () => {
|
||||
@@ -54,7 +62,7 @@ describe('ViteAppMetadata', () => {
|
||||
import.meta.env[environmentVariable] = expected;
|
||||
|
||||
// act
|
||||
const sut = new ViteAppMetadata();
|
||||
const sut = new ViteEnvironmentVariables();
|
||||
const actualValue = getActualValue(sut);
|
||||
|
||||
// assert
|
||||
46
tests/unit/infrastructure/Log/ConsoleLogger.spec.ts
Normal file
46
tests/unit/infrastructure/Log/ConsoleLogger.spec.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { describe, expect } from 'vitest';
|
||||
import { StubWithObservableMethodCalls } from '@tests/unit/shared/Stubs/StubWithObservableMethodCalls';
|
||||
import { ConsoleLogger } from '@/infrastructure/Log/ConsoleLogger';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { itEachLoggingMethod } from './LoggerTestRunner';
|
||||
|
||||
describe('ConsoleLogger', () => {
|
||||
describe('throws if console is missing', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing console';
|
||||
const console = absentValue;
|
||||
// act
|
||||
const act = () => new ConsoleLogger(console);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}, { excludeUndefined: true });
|
||||
});
|
||||
describe('methods log the provided params', () => {
|
||||
itEachLoggingMethod((functionName, testParameters) => {
|
||||
// arrange
|
||||
const expectedParams = testParameters;
|
||||
const consoleMock = new MockConsole();
|
||||
const logger = new ConsoleLogger(consoleMock);
|
||||
|
||||
// act
|
||||
logger[functionName](...expectedParams);
|
||||
|
||||
// assert
|
||||
expect(consoleMock.callHistory).to.have.lengthOf(1);
|
||||
expect(consoleMock.callHistory[0].methodName).to.equal(functionName);
|
||||
expect(consoleMock.callHistory[0].args).to.deep.equal(expectedParams);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class MockConsole
|
||||
extends StubWithObservableMethodCalls<Partial<Console>>
|
||||
implements Partial<Console> {
|
||||
public info(...args: unknown[]) {
|
||||
this.registerMethodCall({
|
||||
methodName: 'info',
|
||||
args,
|
||||
});
|
||||
}
|
||||
}
|
||||
45
tests/unit/infrastructure/Log/ElectronLogger.spec.ts
Normal file
45
tests/unit/infrastructure/Log/ElectronLogger.spec.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { describe, expect } from 'vitest';
|
||||
import { ElectronLog } from 'electron-log';
|
||||
import { StubWithObservableMethodCalls } from '@tests/unit/shared/Stubs/StubWithObservableMethodCalls';
|
||||
import { createElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { itEachLoggingMethod } from './LoggerTestRunner';
|
||||
|
||||
describe('ElectronLogger', () => {
|
||||
describe('throws if logger is missing', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing logger';
|
||||
const electronLog = absentValue;
|
||||
// act
|
||||
const act = () => createElectronLogger(electronLog);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}, { excludeUndefined: true });
|
||||
});
|
||||
itEachLoggingMethod((functionName, testParameters) => {
|
||||
// arrange
|
||||
const expectedParams = testParameters;
|
||||
const electronLogMock = new MockElectronLog();
|
||||
const logger = createElectronLogger(electronLogMock);
|
||||
|
||||
// act
|
||||
logger[functionName](...expectedParams);
|
||||
|
||||
// assert
|
||||
expect(electronLogMock.callHistory).to.have.lengthOf(1);
|
||||
expect(electronLogMock.callHistory[0].methodName).to.equal(functionName);
|
||||
expect(electronLogMock.callHistory[0].args).to.deep.equal(expectedParams);
|
||||
});
|
||||
});
|
||||
|
||||
class MockElectronLog
|
||||
extends StubWithObservableMethodCalls<Partial<ElectronLog>>
|
||||
implements Partial<ElectronLog> {
|
||||
public info(...args: unknown[]) {
|
||||
this.registerMethodCall({
|
||||
methodName: 'info',
|
||||
args,
|
||||
});
|
||||
}
|
||||
}
|
||||
21
tests/unit/infrastructure/Log/LoggerTestRunner.ts
Normal file
21
tests/unit/infrastructure/Log/LoggerTestRunner.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { it } from 'vitest';
|
||||
import { FunctionKeys } from '@/TypeHelpers';
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
|
||||
export function itEachLoggingMethod(
|
||||
handler: (
|
||||
functionName: keyof ILogger,
|
||||
testParameters?: unknown[]
|
||||
) => void,
|
||||
) {
|
||||
const testParameters = ['test', 123, { some: 'object' }];
|
||||
const loggerMethods: Array<FunctionKeys<ILogger>> = [
|
||||
'info',
|
||||
];
|
||||
loggerMethods
|
||||
.forEach((functionKey) => {
|
||||
it(functionKey, () => {
|
||||
handler(functionKey, testParameters);
|
||||
});
|
||||
});
|
||||
}
|
||||
20
tests/unit/infrastructure/Log/NoopLogger.spec.ts
Normal file
20
tests/unit/infrastructure/Log/NoopLogger.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { describe, expect } from 'vitest';
|
||||
import { NoopLogger } from '@/infrastructure/Log/NoopLogger';
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { itEachLoggingMethod } from './LoggerTestRunner';
|
||||
|
||||
describe('NoopLogger', () => {
|
||||
describe('methods do not throw', () => {
|
||||
itEachLoggingMethod((functionName, testParameters) => {
|
||||
// arrange
|
||||
const randomParams = testParameters;
|
||||
const logger: ILogger = new NoopLogger();
|
||||
|
||||
// act
|
||||
const act = () => logger[functionName](...randomParams);
|
||||
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
50
tests/unit/infrastructure/Log/WindowInjectedLogger.spec.ts
Normal file
50
tests/unit/infrastructure/Log/WindowInjectedLogger.spec.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { describe } from 'vitest';
|
||||
import { LoggerStub } from '@tests/unit/shared/Stubs/LoggerStub';
|
||||
import { WindowVariablesStub } from '@tests/unit/shared/Stubs/WindowVariablesStub';
|
||||
import { WindowInjectedLogger } from '@/infrastructure/Log/WindowInjectedLogger';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { itEachLoggingMethod } from './LoggerTestRunner';
|
||||
|
||||
describe('WindowInjectedLogger', () => {
|
||||
describe('throws if log is absent', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing log';
|
||||
const windowVariables = new WindowVariablesStub()
|
||||
.withLog(absentValue);
|
||||
// act
|
||||
const act = () => new WindowInjectedLogger(windowVariables);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('throws if window is absent', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing window';
|
||||
const windowVariables = absentValue;
|
||||
// act
|
||||
const act = () => new WindowInjectedLogger(windowVariables);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}, { excludeUndefined: true });
|
||||
});
|
||||
describe('methods log the provided params', () => {
|
||||
itEachLoggingMethod((functionName) => {
|
||||
// arrange
|
||||
const expectedParams = ['test', 123, { some: 'object' }];
|
||||
const loggerMock = new LoggerStub();
|
||||
const windowVariables = new WindowVariablesStub()
|
||||
.withLog(loggerMock);
|
||||
const logger = new WindowInjectedLogger(windowVariables);
|
||||
|
||||
// act
|
||||
logger[functionName](...expectedParams);
|
||||
|
||||
// assert
|
||||
expect(loggerMock.callHistory).to.have.lengthOf(1);
|
||||
expect(loggerMock.callHistory[0].methodName).to.equal(functionName);
|
||||
expect(loggerMock.callHistory[0].args).to.deep.equal(expectedParams);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
import {
|
||||
describe,
|
||||
} from 'vitest';
|
||||
import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
|
||||
import { AppMetadataFactory, MetadataValidator } from '@/infrastructure/Metadata/AppMetadataFactory';
|
||||
import { ViteAppMetadata } from '@/infrastructure/Metadata/Vite/ViteAppMetadata';
|
||||
import { IAppMetadata } from '@/infrastructure/Metadata/IAppMetadata';
|
||||
|
||||
class TestableAppMetadataFactory extends AppMetadataFactory {
|
||||
public constructor(validator: MetadataValidator = () => { /* NO OP */ }) {
|
||||
super(validator);
|
||||
}
|
||||
}
|
||||
|
||||
describe('AppMetadataFactory', () => {
|
||||
describe('instance', () => {
|
||||
itIsSingleton({
|
||||
getter: () => AppMetadataFactory.Current.instance,
|
||||
expectedType: ViteAppMetadata,
|
||||
});
|
||||
});
|
||||
it('creates the correct type of metadata', () => {
|
||||
// arrange
|
||||
const sut = new TestableAppMetadataFactory();
|
||||
// act
|
||||
const metadata = sut.instance;
|
||||
// assert
|
||||
expect(metadata).to.be.instanceOf(ViteAppMetadata);
|
||||
});
|
||||
it('validates its instance', () => {
|
||||
// arrange
|
||||
let validatedMetadata: IAppMetadata;
|
||||
const validatorMock = (metadata: IAppMetadata) => {
|
||||
validatedMetadata = metadata;
|
||||
};
|
||||
// act
|
||||
const sut = new TestableAppMetadataFactory(validatorMock);
|
||||
const actualInstance = sut.instance;
|
||||
// assert
|
||||
expect(actualInstance).to.equal(validatedMetadata);
|
||||
});
|
||||
it('throws error if validator fails', () => {
|
||||
// arrange
|
||||
const expectedError = 'validator failed';
|
||||
const failingValidator = () => {
|
||||
throw new Error(expectedError);
|
||||
};
|
||||
// act
|
||||
const act = () => new TestableAppMetadataFactory(failingValidator);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { AppMetadataStub } from '@tests/unit/shared/Stubs/AppMetadataStub';
|
||||
import { IAppMetadata } from '@/infrastructure/Metadata/IAppMetadata';
|
||||
import { validateMetadata } from '@/infrastructure/Metadata/MetadataValidator';
|
||||
|
||||
describe('MetadataValidator', () => {
|
||||
it('does not throw if all metadata keys have values', () => {
|
||||
// arrange
|
||||
const metadata = new AppMetadataStub();
|
||||
// act
|
||||
const act = () => validateMetadata(metadata);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
describe('throws as expected', () => {
|
||||
describe('"missing metadata" if metadata is not provided', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing metadata';
|
||||
const metadata = absentValue;
|
||||
// act
|
||||
const act = () => validateMetadata(metadata);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
it('"missing keys" if metadata has properties with missing values', () => {
|
||||
// arrange
|
||||
const expectedError = 'Metadata keys missing: name, homepageUrl';
|
||||
const missingData: Partial<IAppMetadata> = {
|
||||
name: undefined,
|
||||
homepageUrl: undefined,
|
||||
};
|
||||
const metadata: IAppMetadata = {
|
||||
...new AppMetadataStub(),
|
||||
...missingData,
|
||||
};
|
||||
// act
|
||||
const act = () => validateMetadata(metadata);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('"missing keys" if metadata has getters with missing values', () => {
|
||||
// arrange
|
||||
const expectedError = 'Metadata keys missing: name, homepageUrl';
|
||||
const stubWithGetters: Partial<IAppMetadata> = {
|
||||
get name() {
|
||||
return undefined;
|
||||
},
|
||||
get homepageUrl() {
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
const metadata: IAppMetadata = {
|
||||
...new AppMetadataStub(),
|
||||
...stubWithGetters,
|
||||
};
|
||||
// act
|
||||
const act = () => validateMetadata(metadata);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('"unable to capture metadata" if metadata has no getters or properties', () => {
|
||||
// arrange
|
||||
const expectedError = 'Unable to capture metadata key/value pairs';
|
||||
const metadata = {} as IAppMetadata;
|
||||
// act
|
||||
const act = () => validateMetadata(metadata);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { BrowserOsDetector } from '@/infrastructure/Environment/BrowserOs/BrowserOsDetector';
|
||||
import { BrowserOsDetector } from '@/infrastructure/RuntimeEnvironment/BrowserOs/BrowserOsDetector';
|
||||
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { BrowserOsTestCases } from './BrowserOsTestCases';
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { IBrowserOsDetector } from '@/infrastructure/Environment/BrowserOs/IBrowserOsDetector';
|
||||
import { IBrowserOsDetector } from '@/infrastructure/RuntimeEnvironment/BrowserOs/IBrowserOsDetector';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { Environment, WindowValidator } from '@/infrastructure/Environment/Environment';
|
||||
import { SystemOperationsStub } from '@tests/unit/shared/Stubs/SystemOperationsStub';
|
||||
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { WindowVariables } from '@/infrastructure/Environment/WindowVariables';
|
||||
import { BrowserOsDetectorStub } from '@tests/unit/shared/Stubs/BrowserOsDetectorStub';
|
||||
import { IEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/IEnvironmentVariables';
|
||||
import { EnvironmentVariablesStub } from '@tests/unit/shared/Stubs/EnvironmentVariablesStub';
|
||||
|
||||
describe('Environment', () => {
|
||||
describe('RuntimeEnvironment', () => {
|
||||
describe('ctor', () => {
|
||||
describe('throws if window is absent', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
@@ -135,63 +135,21 @@ describe('Environment', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('system', () => {
|
||||
it('fetches system operations from window', () => {
|
||||
// arrange
|
||||
const expectedSystem = new SystemOperationsStub();
|
||||
const windowWithSystem = {
|
||||
system: expectedSystem,
|
||||
};
|
||||
// act
|
||||
const sut = createEnvironment({
|
||||
window: windowWithSystem,
|
||||
describe('isNonProduction', () => {
|
||||
[true, false].forEach((value) => {
|
||||
it(`sets ${value} from environment variables`, () => {
|
||||
// arrange
|
||||
const expectedValue = value;
|
||||
const environment = new EnvironmentVariablesStub()
|
||||
.withIsNonProduction(expectedValue);
|
||||
// act
|
||||
const sut = createEnvironment({
|
||||
environmentVariables: environment,
|
||||
});
|
||||
// assert
|
||||
const actualValue = sut.isNonProduction;
|
||||
expect(actualValue).to.equal(expectedValue);
|
||||
});
|
||||
// assert
|
||||
const actualSystem = sut.system;
|
||||
expect(actualSystem).to.equal(expectedSystem);
|
||||
});
|
||||
});
|
||||
describe('validateWindow', () => {
|
||||
it('throws when validator throws', () => {
|
||||
// arrange
|
||||
const expectedErrorMessage = 'expected error thrown from window validator';
|
||||
const mockValidator: WindowValidator = () => {
|
||||
throw new Error(expectedErrorMessage);
|
||||
};
|
||||
// act
|
||||
const act = () => createEnvironment({
|
||||
windowValidator: mockValidator,
|
||||
});
|
||||
// assert
|
||||
expect(act).to.throw(expectedErrorMessage);
|
||||
});
|
||||
it('does not throw when validator does not throw', () => {
|
||||
// arrange
|
||||
const expectedErrorMessage = 'expected error thrown from window validator';
|
||||
const mockValidator: WindowValidator = () => {
|
||||
// do not throw
|
||||
};
|
||||
// act
|
||||
const act = () => createEnvironment({
|
||||
windowValidator: mockValidator,
|
||||
});
|
||||
// assert
|
||||
expect(act).to.not.throw(expectedErrorMessage);
|
||||
});
|
||||
it('sends expected window to validator', () => {
|
||||
// arrange
|
||||
const expectedVariables: Partial<WindowVariables> = {};
|
||||
let actualVariables: Partial<WindowVariables>;
|
||||
const mockValidator: WindowValidator = (variables) => {
|
||||
actualVariables = variables;
|
||||
};
|
||||
// act
|
||||
createEnvironment({
|
||||
window: expectedVariables,
|
||||
windowValidator: mockValidator,
|
||||
});
|
||||
// assert
|
||||
expect(actualVariables).to.equal(expectedVariables);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -199,21 +157,21 @@ describe('Environment', () => {
|
||||
interface EnvironmentOptions {
|
||||
window: Partial<Window>;
|
||||
browserOsDetector?: IBrowserOsDetector;
|
||||
windowValidator?: WindowValidator;
|
||||
environmentVariables?: IEnvironmentVariables;
|
||||
}
|
||||
|
||||
function createEnvironment(options: Partial<EnvironmentOptions> = {}): TestableEnvironment {
|
||||
function createEnvironment(options: Partial<EnvironmentOptions> = {}): TestableRuntimeEnvironment {
|
||||
const defaultOptions: EnvironmentOptions = {
|
||||
window: {},
|
||||
browserOsDetector: new BrowserOsDetectorStub(),
|
||||
windowValidator: () => { /* NO OP */ },
|
||||
environmentVariables: new EnvironmentVariablesStub(),
|
||||
};
|
||||
|
||||
return new TestableEnvironment({ ...defaultOptions, ...options });
|
||||
return new TestableRuntimeEnvironment({ ...defaultOptions, ...options });
|
||||
}
|
||||
|
||||
class TestableEnvironment extends Environment {
|
||||
class TestableRuntimeEnvironment extends RuntimeEnvironment {
|
||||
public constructor(options: EnvironmentOptions) {
|
||||
super(options.window, options.browserOsDetector, options.windowValidator);
|
||||
super(options.window, options.environmentVariables, options.browserOsDetector);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { validateWindowVariables } from '@/infrastructure/WindowVariables/WindowVariablesValidator';
|
||||
import { WindowVariables } from '@/infrastructure/WindowVariables/WindowVariables';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { SystemOperationsStub } from '@tests/unit/shared/Stubs/SystemOperationsStub';
|
||||
import { getAbsentObjectTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { WindowVariablesStub } from '@tests/unit/shared/Stubs/WindowVariablesStub';
|
||||
|
||||
describe('WindowVariablesValidator', () => {
|
||||
describe('validateWindowVariables', () => {
|
||||
describe('validates window type', () => {
|
||||
itEachInvalidObjectValue((invalidObjectValue) => {
|
||||
// arrange
|
||||
const expectedError = 'window is not an object';
|
||||
const window: Partial<WindowVariables> = invalidObjectValue;
|
||||
// act
|
||||
const act = () => validateWindowVariables(window);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('property validations', () => {
|
||||
it('throws an error with a description of all invalid properties', () => {
|
||||
// arrange
|
||||
const invalidOs = 'invalid' as unknown as OperatingSystem;
|
||||
const invalidIsDesktop = 'not a boolean' as unknown as boolean;
|
||||
const expectedError = getExpectedError(
|
||||
{
|
||||
name: 'os',
|
||||
object: invalidOs,
|
||||
},
|
||||
{
|
||||
name: 'isDesktop',
|
||||
object: invalidIsDesktop,
|
||||
},
|
||||
);
|
||||
const input = new WindowVariablesStub()
|
||||
.withOs(invalidOs)
|
||||
.withIsDesktop(invalidIsDesktop);
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
|
||||
describe('`os` property', () => {
|
||||
it('throws an error when os is not a number', () => {
|
||||
// arrange
|
||||
const invalidOs = 'Linux' as unknown as OperatingSystem;
|
||||
const expectedError = getExpectedError(
|
||||
{
|
||||
name: 'os',
|
||||
object: invalidOs,
|
||||
},
|
||||
);
|
||||
const input = new WindowVariablesStub()
|
||||
.withOs(invalidOs);
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
|
||||
it('throws an error for an invalid numeric os value', () => {
|
||||
// arrange
|
||||
const invalidOs = Number.MAX_SAFE_INTEGER;
|
||||
const expectedError = getExpectedError(
|
||||
{
|
||||
name: 'os',
|
||||
object: invalidOs,
|
||||
},
|
||||
);
|
||||
const input = new WindowVariablesStub()
|
||||
.withOs(invalidOs);
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
|
||||
it('does not throw for a missing os value', () => {
|
||||
// arrange
|
||||
const input = new WindowVariablesStub()
|
||||
.withIsDesktop(true)
|
||||
.withOs(undefined);
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('`isDesktop` property', () => {
|
||||
it('throws an error when only isDesktop is provided and it is true without a system object', () => {
|
||||
// arrange
|
||||
const systemObject = undefined;
|
||||
const expectedError = getExpectedError(
|
||||
{
|
||||
name: 'system',
|
||||
object: systemObject,
|
||||
},
|
||||
);
|
||||
const input = new WindowVariablesStub()
|
||||
.withIsDesktop(true)
|
||||
.withSystem(systemObject);
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
|
||||
it('does not throw when isDesktop is true with a valid system object', () => {
|
||||
// arrange
|
||||
const validSystem = new SystemOperationsStub();
|
||||
const input = new WindowVariablesStub()
|
||||
.withIsDesktop(true)
|
||||
.withSystem(validSystem);
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
|
||||
it('does not throw when isDesktop is false without a system object', () => {
|
||||
// arrange
|
||||
const absentSystem = undefined;
|
||||
const input = new WindowVariablesStub()
|
||||
.withIsDesktop(false)
|
||||
.withSystem(absentSystem);
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('`system` property', () => {
|
||||
expectObjectOnDesktop('system');
|
||||
});
|
||||
|
||||
describe('`log` property', () => {
|
||||
expectObjectOnDesktop('log');
|
||||
});
|
||||
});
|
||||
|
||||
it('does not throw for a valid object', () => {
|
||||
const input = new WindowVariablesStub();
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function expectObjectOnDesktop<T>(key: keyof WindowVariables) {
|
||||
describe('validates object type on desktop', () => {
|
||||
itEachInvalidObjectValue((invalidObjectValue) => {
|
||||
// arrange
|
||||
const invalidObject = invalidObjectValue as T;
|
||||
const expectedError = getExpectedError({
|
||||
name: key,
|
||||
object: invalidObject,
|
||||
});
|
||||
const input: WindowVariables = {
|
||||
...new WindowVariablesStub(),
|
||||
isDesktop: true,
|
||||
[key]: invalidObject,
|
||||
};
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('does not object type when not on desktop', () => {
|
||||
itEachInvalidObjectValue((invalidObjectValue) => {
|
||||
// arrange
|
||||
const invalidObject = invalidObjectValue as T;
|
||||
const input: WindowVariables = {
|
||||
...new WindowVariablesStub(),
|
||||
isDesktop: undefined,
|
||||
[key]: invalidObject,
|
||||
};
|
||||
// act
|
||||
const act = () => validateWindowVariables(input);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function itEachInvalidObjectValue<T>(runner: (invalidObjectValue: T) => void) {
|
||||
const testCases: Array<{
|
||||
readonly name: string;
|
||||
readonly value: T;
|
||||
}> = [
|
||||
{
|
||||
name: 'given string',
|
||||
value: 'invalid object' as unknown as T,
|
||||
},
|
||||
{
|
||||
name: 'given array of objects',
|
||||
value: [{}, {}] as unknown as T,
|
||||
},
|
||||
...getAbsentObjectTestCases().map((testCase) => ({
|
||||
name: `given absent: ${testCase.valueName}`,
|
||||
value: testCase.absentValue as unknown as T,
|
||||
})),
|
||||
];
|
||||
testCases.forEach((testCase) => {
|
||||
it(testCase.name, () => {
|
||||
runner(testCase.value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getExpectedError(...unexpectedObjects: Array<{
|
||||
readonly name: keyof WindowVariables;
|
||||
readonly object: unknown;
|
||||
}>) {
|
||||
const errors = unexpectedObjects
|
||||
.map(({ name, object }) => `Unexpected ${name} (${typeof object})`);
|
||||
return errors.join('\n');
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { describe } from 'vitest';
|
||||
import { EnvironmentValidator } from '@/infrastructure/RuntimeSanity/Validators/EnvironmentValidator';
|
||||
import { itNoErrorsOnCurrentEnvironment } from './ValidatorTestRunner';
|
||||
|
||||
describe('EnvironmentValidator', () => {
|
||||
itNoErrorsOnCurrentEnvironment(() => new EnvironmentValidator());
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe } from 'vitest';
|
||||
import { EnvironmentVariablesValidator } from '@/infrastructure/RuntimeSanity/Validators/EnvironmentVariablesValidator';
|
||||
import { FactoryFunction } from '@/infrastructure/RuntimeSanity/Common/FactoryValidator';
|
||||
import { EnvironmentVariablesStub } from '@tests/unit/shared/Stubs/EnvironmentVariablesStub';
|
||||
import { IEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/IEnvironmentVariables';
|
||||
import { runFactoryValidatorTests } from './FactoryValidatorConcreteTestRunner';
|
||||
|
||||
describe('EnvironmentVariablesValidator', () => {
|
||||
runFactoryValidatorTests({
|
||||
createValidator: (
|
||||
factory?: FactoryFunction<IEnvironmentVariables>,
|
||||
) => new EnvironmentVariablesValidator(factory),
|
||||
enablingOptionProperty: 'validateEnvironmentVariables',
|
||||
factoryFunctionStub: () => new EnvironmentVariablesStub(),
|
||||
expectedValidatorName: 'environment variables',
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { PropertyKeys } from '@/TypeHelpers';
|
||||
import { FactoryFunction, FactoryValidator } from '@/infrastructure/RuntimeSanity/Common/FactoryValidator';
|
||||
import { ISanityCheckOptions } from '@/infrastructure/RuntimeSanity/Common/ISanityCheckOptions';
|
||||
import { SanityCheckOptionsStub } from '@tests/unit/shared/Stubs/SanityCheckOptionsStub';
|
||||
|
||||
interface ITestOptions<T> {
|
||||
createValidator: (factory?: FactoryFunction<T>) => FactoryValidator<T>;
|
||||
enablingOptionProperty: PropertyKeys<ISanityCheckOptions>;
|
||||
factoryFunctionStub: FactoryFunction<T>;
|
||||
expectedValidatorName: string;
|
||||
}
|
||||
|
||||
export function runFactoryValidatorTests<T>(
|
||||
testOptions: ITestOptions<T>,
|
||||
) {
|
||||
if (!testOptions) {
|
||||
throw new Error('missing options');
|
||||
}
|
||||
describe('shouldValidate', () => {
|
||||
it('returns true when option is true', () => {
|
||||
// arrange
|
||||
const expectedValue = true;
|
||||
const options: ISanityCheckOptions = {
|
||||
...new SanityCheckOptionsStub(),
|
||||
[testOptions.enablingOptionProperty]: true,
|
||||
};
|
||||
const validatorUnderTest = testOptions.createValidator(testOptions.factoryFunctionStub);
|
||||
// act
|
||||
const actualValue = validatorUnderTest.shouldValidate(options);
|
||||
// assert
|
||||
expect(actualValue).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
it('returns false when option is false', () => {
|
||||
// arrange
|
||||
const expectedValue = false;
|
||||
const options: ISanityCheckOptions = {
|
||||
...new SanityCheckOptionsStub(),
|
||||
[testOptions.enablingOptionProperty]: false,
|
||||
};
|
||||
const validatorUnderTest = testOptions.createValidator(testOptions.factoryFunctionStub);
|
||||
// act
|
||||
const actualValue = validatorUnderTest.shouldValidate(options);
|
||||
// assert
|
||||
expect(actualValue).to.equal(expectedValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('name', () => {
|
||||
it('returns as expected', () => {
|
||||
// arrange
|
||||
const expectedName = testOptions.expectedValidatorName;
|
||||
// act
|
||||
const validatorUnderTest = testOptions.createValidator(testOptions.factoryFunctionStub);
|
||||
// assert
|
||||
const actualName = validatorUnderTest.name;
|
||||
expect(actualName).to.equal(expectedName);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { describe } from 'vitest';
|
||||
import { MetadataValidator } from '@/infrastructure/RuntimeSanity/Validators/MetadataValidator';
|
||||
import { itNoErrorsOnCurrentEnvironment } from './ValidatorTestRunner';
|
||||
|
||||
describe('MetadataValidator', () => {
|
||||
itNoErrorsOnCurrentEnvironment(() => new MetadataValidator());
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import { it, expect } from 'vitest';
|
||||
import { ISanityValidator } from '@/infrastructure/RuntimeSanity/Common/ISanityValidator';
|
||||
|
||||
export function itNoErrorsOnCurrentEnvironment(
|
||||
factory: () => ISanityValidator,
|
||||
) {
|
||||
if (!factory) {
|
||||
throw new Error('missing factory');
|
||||
}
|
||||
it('it does report errors on current environment', () => {
|
||||
// arrange
|
||||
const validator = factory();
|
||||
// act
|
||||
const errors = [...validator.collectErrors()];
|
||||
// assert
|
||||
expect(errors).to.have.lengthOf(0);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe } from 'vitest';
|
||||
import { WindowVariablesValidator } from '@/infrastructure/RuntimeSanity/Validators/WindowVariablesValidator';
|
||||
import { FactoryFunction } from '@/infrastructure/RuntimeSanity/Common/FactoryValidator';
|
||||
import { WindowVariables } from '@/infrastructure/WindowVariables/WindowVariables';
|
||||
import { WindowVariablesStub } from '@tests/unit/shared/Stubs/WindowVariablesStub';
|
||||
import { runFactoryValidatorTests } from './FactoryValidatorConcreteTestRunner';
|
||||
|
||||
describe('WindowVariablesValidator', () => {
|
||||
runFactoryValidatorTests({
|
||||
createValidator: (
|
||||
factory?: FactoryFunction<WindowVariables>,
|
||||
) => new WindowVariablesValidator(factory),
|
||||
enablingOptionProperty: 'validateWindowVariables',
|
||||
factoryFunctionStub: () => new WindowVariablesStub(),
|
||||
expectedValidatorName: 'window variables',
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { getWindowInjectedSystemOperations } from '@/infrastructure/SystemOperations/WindowInjectedSystemOperations';
|
||||
import { WindowVariables } from '@/infrastructure/WindowVariables/WindowVariables';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { SystemOperationsStub } from '@tests/unit/shared/Stubs/SystemOperationsStub';
|
||||
|
||||
describe('WindowInjectedSystemOperations', () => {
|
||||
describe('getWindowInjectedSystemOperations', () => {
|
||||
describe('throws if window is absent', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing window';
|
||||
const window: WindowVariables = absentValue;
|
||||
// act
|
||||
const act = () => getWindowInjectedSystemOperations(window);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}, { excludeUndefined: true });
|
||||
});
|
||||
describe('throw if system is absent', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing system';
|
||||
const absentSystem = absentValue;
|
||||
const window: Partial<WindowVariables> = {
|
||||
system: absentSystem,
|
||||
};
|
||||
// act
|
||||
const act = () => getWindowInjectedSystemOperations(window);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
it('returns from window', () => {
|
||||
// arrange
|
||||
const expectedValue = new SystemOperationsStub();
|
||||
const window: Partial<WindowVariables> = {
|
||||
system: expectedValue,
|
||||
};
|
||||
// act
|
||||
const actualValue = getWindowInjectedSystemOperations(window);
|
||||
// assert
|
||||
expect(actualValue).to.equal(expectedValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user