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:
@@ -0,0 +1,15 @@
|
||||
import { describe, it } from 'vitest';
|
||||
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
|
||||
|
||||
describe('EnvironmentVariablesFactory', () => {
|
||||
it('can read current environment', () => {
|
||||
const environmentVariables = EnvironmentVariablesFactory.Current.instance;
|
||||
Object.entries(environmentVariables).forEach(([key, value]) => {
|
||||
it(`${key} value is defined`, () => {
|
||||
expect(value).to.not.equal(undefined);
|
||||
expect(value).to.not.equal(null);
|
||||
expect(value).to.not.equal(Number.NaN);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,16 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ViteAppMetadata } from '@/infrastructure/Metadata/Vite/ViteAppMetadata';
|
||||
import packageJson from '@/../package.json' assert { type: 'json' };
|
||||
import { PropertyKeys } from '@/TypeHelpers';
|
||||
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
|
||||
import { ViteEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/Vite/ViteEnvironmentVariables';
|
||||
|
||||
describe('ViteAppMetadata', () => {
|
||||
describe('populates from package.json', () => {
|
||||
describe('ViteEnvironmentVariables', () => {
|
||||
describe('populates metadata from package.json', () => {
|
||||
interface ITestCase {
|
||||
readonly getActualValue: (sut: ViteAppMetadata) => string;
|
||||
readonly getActualValue: (sut: IAppMetadata) => string;
|
||||
readonly expected: string;
|
||||
}
|
||||
const testCases: { readonly [K in PropertyKeys<ViteAppMetadata>]: ITestCase } = {
|
||||
const testCases: { readonly [K in PropertyKeys<IAppMetadata>]: ITestCase } = {
|
||||
name: {
|
||||
expected: packageJson.name,
|
||||
getActualValue: (sut) => sut.name,
|
||||
@@ -34,7 +35,7 @@ describe('ViteAppMetadata', () => {
|
||||
Object.entries(testCases).forEach(([propertyName, { expected, getActualValue }]) => {
|
||||
it(`should correctly get the value of ${propertyName}`, () => {
|
||||
// arrange
|
||||
const sut = new ViteAppMetadata();
|
||||
const sut = new ViteEnvironmentVariables();
|
||||
|
||||
// act
|
||||
const actualValue = getActualValue(sut);
|
||||
@@ -22,8 +22,8 @@ describe('SanityChecks', () => {
|
||||
|
||||
function generateTestOptions(): ISanityCheckOptions[] {
|
||||
const defaultOptions: ISanityCheckOptions = {
|
||||
validateMetadata: true,
|
||||
validateEnvironment: true,
|
||||
validateEnvironmentVariables: true,
|
||||
validateWindowVariables: true,
|
||||
};
|
||||
return generateBooleanPermutations(defaultOptions);
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { describe } from 'vitest';
|
||||
import { EnvironmentValidator } from '@/infrastructure/RuntimeSanity/Validators/EnvironmentValidator';
|
||||
import { FactoryFunction } from '@/infrastructure/RuntimeSanity/Common/FactoryValidator';
|
||||
import { EnvironmentStub } from '@tests/unit/shared/Stubs/EnvironmentStub';
|
||||
import { IEnvironment } from '@/infrastructure/Environment/IEnvironment';
|
||||
import { runFactoryValidatorTests } from './FactoryValidatorConcreteTestRunner';
|
||||
|
||||
describe('EnvironmentValidator', () => {
|
||||
runFactoryValidatorTests({
|
||||
createValidator: (factory?: FactoryFunction<IEnvironment>) => new EnvironmentValidator(factory),
|
||||
enablingOptionProperty: 'validateEnvironment',
|
||||
factoryFunctionStub: () => new EnvironmentStub(),
|
||||
expectedValidatorName: 'environment',
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { describe } from 'vitest';
|
||||
import { EnvironmentVariablesValidator } from '@/infrastructure/RuntimeSanity/Validators/EnvironmentVariablesValidator';
|
||||
import { itNoErrorsOnCurrentEnvironment } from './ValidatorTestRunner';
|
||||
|
||||
describe('EnvironmentVariablesValidator', () => {
|
||||
itNoErrorsOnCurrentEnvironment(() => new EnvironmentVariablesValidator());
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import { describe } from 'vitest';
|
||||
import { MetadataValidator } from '@/infrastructure/RuntimeSanity/Validators/MetadataValidator';
|
||||
import { FactoryFunction } from '@/infrastructure/RuntimeSanity/Common/FactoryValidator';
|
||||
import { AppMetadataStub } from '@tests/unit/shared/Stubs/AppMetadataStub';
|
||||
import { IAppMetadata } from '@/infrastructure/Metadata/IAppMetadata';
|
||||
import { runFactoryValidatorTests } from './FactoryValidatorConcreteTestRunner';
|
||||
|
||||
describe('MetadataValidator', () => {
|
||||
runFactoryValidatorTests({
|
||||
createValidator: (factory?: FactoryFunction<IAppMetadata>) => new MetadataValidator(factory),
|
||||
enablingOptionProperty: 'validateMetadata',
|
||||
factoryFunctionStub: () => new AppMetadataStub(),
|
||||
expectedValidatorName: 'metadata',
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { describe } from 'vitest';
|
||||
import { WindowVariablesValidator } from '@/infrastructure/RuntimeSanity/Validators/WindowVariablesValidator';
|
||||
import { itNoErrorsOnCurrentEnvironment } from './ValidatorTestRunner';
|
||||
|
||||
describe('WindowVariablesValidator', () => {
|
||||
itNoErrorsOnCurrentEnvironment(() => new WindowVariablesValidator());
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { it, describe, expect } from 'vitest';
|
||||
import { provideWindowVariables } from '@/presentation/electron/preload/WindowVariablesProvider';
|
||||
|
||||
describe('WindowVariablesProvider', () => {
|
||||
describe('provideWindowVariables', () => {
|
||||
describe('conforms to Electron\'s context bridging requirements', () => {
|
||||
// https://www.electronjs.org/docs/latest/api/context-bridge
|
||||
const variables = provideWindowVariables();
|
||||
Object.entries(variables).forEach(([key, value]) => {
|
||||
it(`\`${key}\` conforms to allowed types for context bridging`, () => {
|
||||
expect(checkAllowedType(value)).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function checkAllowedType(value: unknown) {
|
||||
const type = typeof value;
|
||||
if (['string', 'number', 'boolean', 'function'].includes(type)) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.every(checkAllowedType);
|
||||
}
|
||||
if (type === 'object' && value !== null) {
|
||||
return (
|
||||
// Every key should be a string
|
||||
Object.keys(value).every((key) => typeof key === 'string')
|
||||
// Every value should be of allowed type
|
||||
&& Object.values(value).every(checkAllowedType)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import { buildContext } from '@/application/Context/ApplicationContextFactory';
|
||||
import { IApplicationFactory } from '@/application/IApplicationFactory';
|
||||
import { IApplication } from '@/domain/IApplication';
|
||||
import { EnvironmentStub } from '@tests/unit/shared/Stubs/EnvironmentStub';
|
||||
import { RuntimeEnvironmentStub } from '@tests/unit/shared/Stubs/RuntimeEnvironmentStub';
|
||||
import { ApplicationStub } from '@tests/unit/shared/Stubs/ApplicationStub';
|
||||
import { CategoryCollectionStub } from '@tests/unit/shared/Stubs/CategoryCollectionStub';
|
||||
import { expectThrowsAsync } from '@tests/unit/shared/Assertions/ExpectThrowsAsync';
|
||||
@@ -38,7 +38,7 @@ describe('ApplicationContextFactory', () => {
|
||||
it('returns currentOs if it is supported', async () => {
|
||||
// arrange
|
||||
const expected = OperatingSystem.Windows;
|
||||
const environment = new EnvironmentStub().withOs(expected);
|
||||
const environment = new RuntimeEnvironmentStub().withOs(expected);
|
||||
const collection = new CategoryCollectionStub().withOs(expected);
|
||||
const factoryMock = mockFactoryWithCollection(collection);
|
||||
// act
|
||||
@@ -51,7 +51,7 @@ describe('ApplicationContextFactory', () => {
|
||||
// arrange
|
||||
const expected = OperatingSystem.Windows;
|
||||
const currentOs = OperatingSystem.macOS;
|
||||
const environment = new EnvironmentStub().withOs(currentOs);
|
||||
const environment = new RuntimeEnvironmentStub().withOs(currentOs);
|
||||
const collection = new CategoryCollectionStub().withOs(expected);
|
||||
const factoryMock = mockFactoryWithCollection(collection);
|
||||
// act
|
||||
@@ -68,7 +68,7 @@ describe('ApplicationContextFactory', () => {
|
||||
new CategoryCollectionStub().withOs(expectedOs).withTotalScripts(5),
|
||||
new CategoryCollectionStub().withOs(OperatingSystem.Windows).withTotalScripts(4),
|
||||
];
|
||||
const environment = new EnvironmentStub().withOs(OperatingSystem.macOS);
|
||||
const environment = new RuntimeEnvironmentStub().withOs(OperatingSystem.macOS);
|
||||
const app = new ApplicationStub().withCollections(...allCollections);
|
||||
const factoryMock = mockFactoryWithApp(app);
|
||||
// act
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
|
||||
import type { CollectionData } from '@/application/collections/';
|
||||
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
||||
import { CategoryCollectionParserType, parseApplication } from '@/application/Parser/ApplicationParser';
|
||||
import { IAppMetadata } from '@/infrastructure/Metadata/IAppMetadata';
|
||||
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
|
||||
import WindowsData from '@/application/collections/windows.yaml';
|
||||
import MacOsData from '@/application/collections/macos.yaml';
|
||||
import LinuxData from '@/application/collections/linux.yaml';
|
||||
@@ -11,7 +11,7 @@ import { CategoryCollectionStub } from '@tests/unit/shared/Stubs/CategoryCollect
|
||||
import { CollectionDataStub } from '@tests/unit/shared/Stubs/CollectionDataStub';
|
||||
import { getAbsentCollectionTestCases, getAbsentObjectTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { AppMetadataStub } from '@tests/unit/shared/Stubs/AppMetadataStub';
|
||||
import { AppMetadataFactory } from '@/infrastructure/Metadata/AppMetadataFactory';
|
||||
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
|
||||
import { CategoryCollectionParserStub } from '@tests/unit/shared/Stubs/CategoryCollectionParserStub';
|
||||
import { ProjectInformationParserStub } from '@tests/unit/shared/Stubs/ProjectInformationParserStub';
|
||||
import { ProjectInformationStub } from '@tests/unit/shared/Stubs/ProjectInformationStub';
|
||||
@@ -85,7 +85,7 @@ describe('ApplicationParser', () => {
|
||||
});
|
||||
it('defaults to metadata from factory', () => {
|
||||
// arrange
|
||||
const expectedMetadata: IAppMetadata = AppMetadataFactory.Current.instance;
|
||||
const expectedMetadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance;
|
||||
const infoParserStub = new ProjectInformationParserStub();
|
||||
// act
|
||||
new ApplicationParserBuilder()
|
||||
@@ -204,9 +204,9 @@ class ApplicationParserBuilder {
|
||||
}
|
||||
|
||||
public withMetadata(
|
||||
environment: IAppMetadata,
|
||||
metadata: IAppMetadata,
|
||||
): this {
|
||||
this.metadata = environment;
|
||||
this.metadata = metadata;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
import { describe } from 'vitest';
|
||||
import { MetadataValidator } from '@/infrastructure/RuntimeSanity/Validators/MetadataValidator';
|
||||
import { itNoErrorsOnCurrentEnvironment } from './ValidatorTestRunner';
|
||||
|
||||
describe('MetadataValidator', () => {
|
||||
itNoErrorsOnCurrentEnvironment(() => new MetadataValidator());
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
describe, it, beforeEach, afterEach,
|
||||
} from 'vitest';
|
||||
import { IRuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/IRuntimeEnvironment';
|
||||
import { ClientLoggerFactory } from '@/presentation/bootstrapping/ClientLoggerFactory';
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { WindowInjectedLogger } from '@/infrastructure/Log/WindowInjectedLogger';
|
||||
import { ConsoleLogger } from '@/infrastructure/Log/ConsoleLogger';
|
||||
import { NoopLogger } from '@/infrastructure/Log/NoopLogger';
|
||||
import { RuntimeEnvironmentStub } from '@tests/unit/shared/Stubs/RuntimeEnvironmentStub';
|
||||
import { Constructible } from '@/TypeHelpers';
|
||||
import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
|
||||
import { LoggerStub } from '@tests/unit/shared/Stubs/LoggerStub';
|
||||
|
||||
describe('ClientLoggerFactory', () => {
|
||||
describe('Current', () => {
|
||||
itIsSingleton({
|
||||
getter: () => ClientLoggerFactory.Current,
|
||||
expectedType: ClientLoggerFactory,
|
||||
});
|
||||
});
|
||||
describe('logger instantiation based on environment', () => {
|
||||
const originalWindow = { ...window };
|
||||
beforeEach(() => {
|
||||
Object.assign(window, { log: new LoggerStub() });
|
||||
});
|
||||
afterEach(() => {
|
||||
Object.assign(window, originalWindow);
|
||||
});
|
||||
const testCases: Array<{
|
||||
readonly description: string,
|
||||
readonly expectedType: Constructible<ILogger>,
|
||||
readonly environment: IRuntimeEnvironment,
|
||||
}> = [
|
||||
{
|
||||
description: 'desktop environment',
|
||||
expectedType: WindowInjectedLogger,
|
||||
environment: new RuntimeEnvironmentStub()
|
||||
.withIsDesktop(true),
|
||||
},
|
||||
{
|
||||
description: 'non-production and desktop environment',
|
||||
expectedType: WindowInjectedLogger,
|
||||
environment: new RuntimeEnvironmentStub()
|
||||
.withIsDesktop(true)
|
||||
.withIsNonProduction(true),
|
||||
},
|
||||
{
|
||||
description: 'non-production without desktop',
|
||||
expectedType: ConsoleLogger,
|
||||
environment: new RuntimeEnvironmentStub()
|
||||
.withIsDesktop(false)
|
||||
.withIsNonProduction(true),
|
||||
},
|
||||
{
|
||||
description: 'production without desktop',
|
||||
expectedType: NoopLogger,
|
||||
environment: new RuntimeEnvironmentStub()
|
||||
.withIsDesktop(false)
|
||||
.withIsNonProduction(false),
|
||||
},
|
||||
];
|
||||
testCases.forEach(({ description, expectedType, environment }) => {
|
||||
it(`instantiates ${expectedType.name} for ${description}`, () => {
|
||||
// arrange
|
||||
const factory = new TestableClientLoggerFactory(environment);
|
||||
// act
|
||||
const { logger } = factory;
|
||||
// assert
|
||||
expect(logger).to.be.instanceOf(expectedType);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class TestableClientLoggerFactory extends ClientLoggerFactory {
|
||||
public constructor(environment: IRuntimeEnvironment) {
|
||||
super(environment);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { AppInitializationLogger } from '@/presentation/bootstrapping/Modules/AppInitializationLogger';
|
||||
import { LoggerStub } from '@tests/unit/shared/Stubs/LoggerStub';
|
||||
|
||||
describe('AppInitializationLogger', () => {
|
||||
it('logs the app initialization marker upon bootstrap', () => {
|
||||
// arrange
|
||||
const marker = '[APP_INIT]';
|
||||
const loggerStub = new LoggerStub();
|
||||
const sut = new AppInitializationLogger(loggerStub);
|
||||
// act
|
||||
sut.bootstrap();
|
||||
// assert
|
||||
expect(loggerStub.callHistory).to.have.lengthOf(1);
|
||||
expect(loggerStub.callHistory[0].args).to.have.lengthOf(1);
|
||||
expect(loggerStub.callHistory[0].args[0]).to.include(marker);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ISanityCheckOptions } from '@/infrastructure/RuntimeSanity/Common/ISanityCheckOptions';
|
||||
import { RuntimeSanityValidator } from '@/presentation/bootstrapping/Modules/RuntimeSanityValidator';
|
||||
|
||||
describe('RuntimeSanityValidator', () => {
|
||||
it('calls validator with correct options upon bootstrap', () => {
|
||||
// arrange
|
||||
const expectedOptions: ISanityCheckOptions = {
|
||||
validateEnvironmentVariables: true,
|
||||
validateWindowVariables: true,
|
||||
};
|
||||
let actualOptions: ISanityCheckOptions;
|
||||
const validatorMock = (options) => {
|
||||
actualOptions = options;
|
||||
};
|
||||
const sut = new RuntimeSanityValidator(validatorMock);
|
||||
// act
|
||||
sut.bootstrap();
|
||||
// assert
|
||||
expect(actualOptions).to.deep.equal(expectedOptions);
|
||||
});
|
||||
it('propagates the error if validator fails', () => {
|
||||
// arrange
|
||||
const expectedMessage = 'message thrown from validator';
|
||||
const validatorMock = () => {
|
||||
throw new Error(expectedMessage);
|
||||
};
|
||||
const sut = new RuntimeSanityValidator(validatorMock);
|
||||
// act
|
||||
const act = () => sut.bootstrap();
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
it('runs successfully if validator passes', () => {
|
||||
// arrange
|
||||
const validatorMock = () => { /* NOOP */ };
|
||||
const sut = new RuntimeSanityValidator(validatorMock);
|
||||
// act
|
||||
const act = () => sut.bootstrap();
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,16 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { useEnvironment } from '@/presentation/components/Shared/Hooks/UseEnvironment';
|
||||
import { useRuntimeEnvironment } from '@/presentation/components/Shared/Hooks/UseRuntimeEnvironment';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { EnvironmentStub } from '@tests/unit/shared/Stubs/EnvironmentStub';
|
||||
import { RuntimeEnvironmentStub } from '@tests/unit/shared/Stubs/RuntimeEnvironmentStub';
|
||||
|
||||
describe('UseEnvironment', () => {
|
||||
describe('UseRuntimeEnvironment', () => {
|
||||
describe('environment is absent', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing environment';
|
||||
const environmentValue = absentValue;
|
||||
// act
|
||||
const act = () => useEnvironment(environmentValue);
|
||||
const act = () => useRuntimeEnvironment(environmentValue);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
@@ -18,9 +18,9 @@ describe('UseEnvironment', () => {
|
||||
|
||||
it('returns expected environment', () => {
|
||||
// arrange
|
||||
const expectedEnvironment = new EnvironmentStub();
|
||||
const expectedEnvironment = new RuntimeEnvironmentStub();
|
||||
// act
|
||||
const actualEnvironment = useEnvironment(expectedEnvironment);
|
||||
const actualEnvironment = useRuntimeEnvironment(expectedEnvironment);
|
||||
// assert
|
||||
expect(actualEnvironment).to.equal(expectedEnvironment);
|
||||
});
|
||||
@@ -2,11 +2,13 @@ import { describe, it, expect } from 'vitest';
|
||||
import { provideWindowVariables } from '@/presentation/electron/preload/WindowVariablesProvider';
|
||||
import { SystemOperationsStub } from '@tests/unit/shared/Stubs/SystemOperationsStub';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { ISystemOperations } from '@/infrastructure/Environment/SystemOperations/ISystemOperations';
|
||||
import { ISystemOperations } from '@/infrastructure/SystemOperations/ISystemOperations';
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { LoggerStub } from '@tests/unit/shared/Stubs/LoggerStub';
|
||||
|
||||
describe('WindowVariablesProvider', () => {
|
||||
describe('provideWindowVariables', () => {
|
||||
it('returns expected system', () => {
|
||||
it('returns expected `system`', () => {
|
||||
// arrange
|
||||
const expectedValue = new SystemOperationsStub();
|
||||
// act
|
||||
@@ -16,7 +18,7 @@ describe('WindowVariablesProvider', () => {
|
||||
// assert
|
||||
expect(variables.system).to.equal(expectedValue);
|
||||
});
|
||||
it('returns expected os', () => {
|
||||
it('returns expected `os`', () => {
|
||||
// arrange
|
||||
const expectedValue = OperatingSystem.WindowsPhone;
|
||||
// act
|
||||
@@ -26,6 +28,16 @@ describe('WindowVariablesProvider', () => {
|
||||
// assert
|
||||
expect(variables.os).to.equal(expectedValue);
|
||||
});
|
||||
it('returns expected `log`', () => {
|
||||
// arrange
|
||||
const expectedValue = new LoggerStub();
|
||||
// act
|
||||
const variables = new TestContext()
|
||||
.withLogger(expectedValue)
|
||||
.provideWindowVariables();
|
||||
// assert
|
||||
expect(variables.log).to.equal(expectedValue);
|
||||
});
|
||||
it('`isDesktop` is true', () => {
|
||||
// arrange
|
||||
const expectedValue = true;
|
||||
@@ -43,6 +55,8 @@ class TestContext {
|
||||
|
||||
private os: OperatingSystem = OperatingSystem.Android;
|
||||
|
||||
private log: ILogger = new LoggerStub();
|
||||
|
||||
public withSystem(system: ISystemOperations): this {
|
||||
this.system = system;
|
||||
return this;
|
||||
@@ -53,9 +67,15 @@ class TestContext {
|
||||
return this;
|
||||
}
|
||||
|
||||
public withLogger(log: ILogger): this {
|
||||
this.log = log;
|
||||
return this;
|
||||
}
|
||||
|
||||
public provideWindowVariables() {
|
||||
return provideWindowVariables(
|
||||
() => this.system,
|
||||
() => this.log,
|
||||
() => this.os,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IAppMetadata } from '@/infrastructure/Metadata/IAppMetadata';
|
||||
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
|
||||
|
||||
export class AppMetadataStub implements IAppMetadata {
|
||||
public version = '0.12.2';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { IBrowserOsDetector } from '@/infrastructure/Environment/BrowserOs/IBrowserOsDetector';
|
||||
import { IBrowserOsDetector } from '@/infrastructure/RuntimeEnvironment/BrowserOs/IBrowserOsDetector';
|
||||
|
||||
export class BrowserOsDetectorStub implements IBrowserOsDetector {
|
||||
public detect(): OperatingSystem {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ICommandOps } from '@/infrastructure/Environment/SystemOperations/ISystemOperations';
|
||||
import { ICommandOps } from '@/infrastructure/SystemOperations/ISystemOperations';
|
||||
import { StubWithObservableMethodCalls } from './StubWithObservableMethodCalls';
|
||||
|
||||
export class CommandOpsStub
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { IEnvironment } from '@/infrastructure/Environment/IEnvironment';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { ISystemOperations } from '@/infrastructure/Environment/SystemOperations/ISystemOperations';
|
||||
import { SystemOperationsStub } from './SystemOperationsStub';
|
||||
|
||||
export class EnvironmentStub implements IEnvironment {
|
||||
public isDesktop = true;
|
||||
|
||||
public os = OperatingSystem.Windows;
|
||||
|
||||
public system: ISystemOperations = new SystemOperationsStub();
|
||||
|
||||
public withOs(os: OperatingSystem): this {
|
||||
this.os = os;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSystemOperations(system: ISystemOperations): this {
|
||||
this.system = system;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
11
tests/unit/shared/Stubs/EnvironmentVariablesStub.ts
Normal file
11
tests/unit/shared/Stubs/EnvironmentVariablesStub.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { IEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/IEnvironmentVariables';
|
||||
import { AppMetadataStub } from './AppMetadataStub';
|
||||
|
||||
export class EnvironmentVariablesStub extends AppMetadataStub implements IEnvironmentVariables {
|
||||
public isNonProduction = true;
|
||||
|
||||
public withIsNonProduction(isNonProduction: boolean): this {
|
||||
this.isNonProduction = isNonProduction;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IFileSystemOps } from '@/infrastructure/Environment/SystemOperations/ISystemOperations';
|
||||
import { IFileSystemOps } from '@/infrastructure/SystemOperations/ISystemOperations';
|
||||
import { StubWithObservableMethodCalls } from './StubWithObservableMethodCalls';
|
||||
|
||||
export class FileSystemOpsStub
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ILocationOps } from '@/infrastructure/Environment/SystemOperations/ISystemOperations';
|
||||
import { ILocationOps } from '@/infrastructure/SystemOperations/ISystemOperations';
|
||||
import { StubWithObservableMethodCalls } from './StubWithObservableMethodCalls';
|
||||
|
||||
export class LocationOpsStub
|
||||
|
||||
12
tests/unit/shared/Stubs/LoggerStub.ts
Normal file
12
tests/unit/shared/Stubs/LoggerStub.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { StubWithObservableMethodCalls } from './StubWithObservableMethodCalls';
|
||||
|
||||
export class LoggerStub extends StubWithObservableMethodCalls<ILogger> implements ILogger {
|
||||
public info(...params: unknown[]): void {
|
||||
this.registerMethodCall({
|
||||
methodName: 'info',
|
||||
args: params,
|
||||
});
|
||||
console.log(...params);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IOperatingSystemOps } from '@/infrastructure/Environment/SystemOperations/ISystemOperations';
|
||||
import { IOperatingSystemOps } from '@/infrastructure/SystemOperations/ISystemOperations';
|
||||
import { StubWithObservableMethodCalls } from './StubWithObservableMethodCalls';
|
||||
|
||||
export class OperatingSystemOpsStub
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
||||
import { IAppMetadata } from '@/infrastructure/Metadata/IAppMetadata';
|
||||
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
|
||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||
import { ProjectInformationStub } from './ProjectInformationStub';
|
||||
|
||||
|
||||
25
tests/unit/shared/Stubs/RuntimeEnvironmentStub.ts
Normal file
25
tests/unit/shared/Stubs/RuntimeEnvironmentStub.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { IRuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/IRuntimeEnvironment';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
export class RuntimeEnvironmentStub implements IRuntimeEnvironment {
|
||||
public isNonProduction = true;
|
||||
|
||||
public isDesktop = true;
|
||||
|
||||
public os = OperatingSystem.Windows;
|
||||
|
||||
public withOs(os: OperatingSystem): this {
|
||||
this.os = os;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withIsDesktop(isDesktop: boolean): this {
|
||||
this.isDesktop = isDesktop;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withIsNonProduction(isNonProduction: boolean): this {
|
||||
this.isNonProduction = isNonProduction;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import { ISanityCheckOptions } from '@/infrastructure/RuntimeSanity/Common/ISanityCheckOptions';
|
||||
|
||||
export class SanityCheckOptionsStub implements ISanityCheckOptions {
|
||||
public validateEnvironment = false;
|
||||
public validateWindowVariables = false;
|
||||
|
||||
public validateMetadata = false;
|
||||
public validateEnvironmentVariables = false;
|
||||
|
||||
public withValidateMetadata(value: boolean): this {
|
||||
this.validateMetadata = value;
|
||||
public withvalidateEnvironmentVariables(value: boolean): this {
|
||||
this.validateEnvironmentVariables = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withValidateEnvironment(value: boolean): this {
|
||||
this.validateEnvironment = value;
|
||||
this.validateWindowVariables = value;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
IOperatingSystemOps,
|
||||
ILocationOps,
|
||||
ISystemOperations,
|
||||
} from '@/infrastructure/Environment/SystemOperations/ISystemOperations';
|
||||
} from '@/infrastructure/SystemOperations/ISystemOperations';
|
||||
import { CommandOpsStub } from './CommandOpsStub';
|
||||
import { FileSystemOpsStub } from './FileSystemOpsStub';
|
||||
import { LocationOpsStub } from './LocationOpsStub';
|
||||
|
||||
36
tests/unit/shared/Stubs/WindowVariablesStub.ts
Normal file
36
tests/unit/shared/Stubs/WindowVariablesStub.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { ISystemOperations } from '@/infrastructure/SystemOperations/ISystemOperations';
|
||||
import { WindowVariables } from '@/infrastructure/WindowVariables/WindowVariables';
|
||||
import { SystemOperationsStub } from './SystemOperationsStub';
|
||||
import { LoggerStub } from './LoggerStub';
|
||||
|
||||
export class WindowVariablesStub implements WindowVariables {
|
||||
public system: ISystemOperations = new SystemOperationsStub();
|
||||
|
||||
public isDesktop = false;
|
||||
|
||||
public os: OperatingSystem = OperatingSystem.BlackBerryOS;
|
||||
|
||||
public log: ILogger = new LoggerStub();
|
||||
|
||||
public withLog(log: ILogger): this {
|
||||
this.log = log;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withIsDesktop(value: boolean): this {
|
||||
this.isDesktop = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withOs(value: OperatingSystem): this {
|
||||
this.os = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSystem(value: ISystemOperations): this {
|
||||
this.system = value;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user