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.
227 lines
9.1 KiB
TypeScript
227 lines
9.1 KiB
TypeScript
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/EnvironmentVariables/IAppMetadata';
|
|
import WindowsData from '@/application/collections/windows.yaml';
|
|
import MacOsData from '@/application/collections/macos.yaml';
|
|
import LinuxData from '@/application/collections/linux.yaml';
|
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
import { CategoryCollectionStub } from '@tests/unit/shared/Stubs/CategoryCollectionStub';
|
|
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 { 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';
|
|
|
|
describe('ApplicationParser', () => {
|
|
describe('parseApplication', () => {
|
|
describe('categoryParser', () => {
|
|
it('returns result from the parser', () => {
|
|
// arrange
|
|
const os = OperatingSystem.macOS;
|
|
const data = new CollectionDataStub();
|
|
const expected = new CategoryCollectionStub()
|
|
.withOs(os);
|
|
const parser = new CategoryCollectionParserStub()
|
|
.withReturnValue(data, expected)
|
|
.getStub();
|
|
const sut = new ApplicationParserBuilder()
|
|
.withCategoryCollectionParser(parser)
|
|
.withCollectionsData([data]);
|
|
// act
|
|
const app = sut.parseApplication();
|
|
// assert
|
|
const actual = app.getCollection(os);
|
|
expect(expected).to.equal(actual);
|
|
});
|
|
});
|
|
describe('project information', () => {
|
|
it('informationParser is used to create application info', () => {
|
|
// arrange
|
|
const expectedInformation = new ProjectInformationStub();
|
|
const informationParserStub = new ProjectInformationParserStub()
|
|
.withReturnValue(expectedInformation);
|
|
const sut = new ApplicationParserBuilder()
|
|
.withProjectInformationParser(informationParserStub.getStub());
|
|
// act
|
|
const app = sut.parseApplication();
|
|
// assert
|
|
const actualInformation = app.info;
|
|
expect(expectedInformation).to.deep.equal(actualInformation);
|
|
});
|
|
it('informationParser is used to parse collection info', () => {
|
|
// arrange
|
|
const expectedInformation = new ProjectInformationStub();
|
|
const informationParserStub = new ProjectInformationParserStub()
|
|
.withReturnValue(expectedInformation);
|
|
const collectionParserStub = new CategoryCollectionParserStub();
|
|
const sut = new ApplicationParserBuilder()
|
|
.withProjectInformationParser(informationParserStub.getStub())
|
|
.withCategoryCollectionParser(collectionParserStub.getStub());
|
|
// act
|
|
sut.parseApplication();
|
|
// assert
|
|
expect(collectionParserStub.arguments).to.have.length.above(0);
|
|
const actualyUsedInfos = collectionParserStub.arguments.map((arg) => arg.info);
|
|
expect(actualyUsedInfos.every((info) => info === expectedInformation));
|
|
});
|
|
});
|
|
describe('metadata', () => {
|
|
it('used to parse expected metadata', () => {
|
|
// arrange
|
|
const expectedMetadata = new AppMetadataStub();
|
|
const infoParserStub = new ProjectInformationParserStub();
|
|
// act
|
|
new ApplicationParserBuilder()
|
|
.withMetadata(expectedMetadata)
|
|
.withProjectInformationParser(infoParserStub.getStub())
|
|
.parseApplication();
|
|
// assert
|
|
expect(infoParserStub.arguments).to.have.lengthOf(1);
|
|
expect(infoParserStub.arguments[0]).to.equal(expectedMetadata);
|
|
});
|
|
it('defaults to metadata from factory', () => {
|
|
// arrange
|
|
const expectedMetadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance;
|
|
const infoParserStub = new ProjectInformationParserStub();
|
|
// act
|
|
new ApplicationParserBuilder()
|
|
.withMetadata(undefined) // force using default
|
|
.withProjectInformationParser(infoParserStub.getStub())
|
|
.parseApplication();
|
|
// assert
|
|
expect(infoParserStub.arguments).to.have.lengthOf(1);
|
|
expect(infoParserStub.arguments[0]).to.equal(expectedMetadata);
|
|
});
|
|
});
|
|
describe('collectionsData', () => {
|
|
describe('set as expected', () => {
|
|
// arrange
|
|
const testCases = [
|
|
{
|
|
name: 'single collection',
|
|
input: [new CollectionDataStub()],
|
|
output: [new CategoryCollectionStub().withOs(OperatingSystem.macOS)],
|
|
},
|
|
{
|
|
name: 'multiple collections',
|
|
input: [
|
|
new CollectionDataStub().withOs('windows'),
|
|
new CollectionDataStub().withOs('macos'),
|
|
],
|
|
output: [
|
|
new CategoryCollectionStub().withOs(OperatingSystem.macOS),
|
|
new CategoryCollectionStub().withOs(OperatingSystem.Windows),
|
|
],
|
|
},
|
|
];
|
|
// act
|
|
for (const testCase of testCases) {
|
|
it(testCase.name, () => {
|
|
let categoryParserStub = new CategoryCollectionParserStub();
|
|
for (let i = 0; i < testCase.input.length; i++) {
|
|
categoryParserStub = categoryParserStub
|
|
.withReturnValue(testCase.input[i], testCase.output[i]);
|
|
}
|
|
const sut = new ApplicationParserBuilder()
|
|
.withCategoryCollectionParser(categoryParserStub.getStub())
|
|
.withCollectionsData(testCase.input);
|
|
// act
|
|
const app = sut.parseApplication();
|
|
// assert
|
|
expect(app.collections).to.deep.equal(testCase.output);
|
|
});
|
|
}
|
|
});
|
|
it('defaults to expected data', () => {
|
|
// arrange
|
|
const expected = [WindowsData, MacOsData, LinuxData];
|
|
const categoryParserStub = new CategoryCollectionParserStub();
|
|
const sut = new ApplicationParserBuilder()
|
|
.withCollectionsData(undefined)
|
|
.withCategoryCollectionParser(categoryParserStub.getStub());
|
|
// act
|
|
sut.parseApplication();
|
|
// assert
|
|
const actual = categoryParserStub.arguments.map((args) => args.data);
|
|
expect(actual).to.deep.equal(expected);
|
|
});
|
|
describe('throws when data is invalid', () => {
|
|
// arrange
|
|
const testCases = [
|
|
...getAbsentCollectionTestCases<CollectionData>().map((testCase) => ({
|
|
name: `given absent collection "${testCase.valueName}"`,
|
|
value: testCase.absentValue,
|
|
expectedError: 'missing collections',
|
|
})).filter((test) => test.value !== undefined /* the default value is set */),
|
|
...getAbsentObjectTestCases().map((testCase) => ({
|
|
name: `given absent item "${testCase.valueName}"`,
|
|
value: [testCase.absentValue],
|
|
expectedError: 'missing collection provided',
|
|
})),
|
|
];
|
|
for (const testCase of testCases) {
|
|
it(testCase.name, () => {
|
|
const sut = new ApplicationParserBuilder()
|
|
.withCollectionsData(testCase.value);
|
|
// act
|
|
const act = () => sut.parseApplication();
|
|
// assert
|
|
expect(act).to.throw(testCase.expectedError);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
class ApplicationParserBuilder {
|
|
private categoryCollectionParser
|
|
: CategoryCollectionParserType = new CategoryCollectionParserStub().getStub();
|
|
|
|
private projectInformationParser
|
|
: typeof parseProjectInformation = new ProjectInformationParserStub().getStub();
|
|
|
|
private metadata: IAppMetadata = new AppMetadataStub();
|
|
|
|
private collectionsData: CollectionData[] = [new CollectionDataStub()];
|
|
|
|
public withCategoryCollectionParser(
|
|
categoryCollectionParser: CategoryCollectionParserType,
|
|
): this {
|
|
this.categoryCollectionParser = categoryCollectionParser;
|
|
return this;
|
|
}
|
|
|
|
public withProjectInformationParser(
|
|
projectInformationParser: typeof parseProjectInformation,
|
|
): this {
|
|
this.projectInformationParser = projectInformationParser;
|
|
return this;
|
|
}
|
|
|
|
public withMetadata(
|
|
metadata: IAppMetadata,
|
|
): this {
|
|
this.metadata = metadata;
|
|
return this;
|
|
}
|
|
|
|
public withCollectionsData(collectionsData: CollectionData[]): this {
|
|
this.collectionsData = collectionsData;
|
|
return this;
|
|
}
|
|
|
|
public parseApplication(): ReturnType<typeof parseApplication> {
|
|
return parseApplication(
|
|
this.categoryCollectionParser,
|
|
this.projectInformationParser,
|
|
this.metadata,
|
|
this.collectionsData,
|
|
);
|
|
}
|
|
}
|