Migrate to electron-vite and electron-builder
- Switch from deprecated Vue CLI plugin to `electron-vite` (see nklayman/vue-cli-plugin-electron-builder#1982) - Update main/preload scripts to use `index.cjs` filenames to support `"type": "module"`, resolving crash issue (#233). This crash was related to Electron not supporting ESM (see electron/asar#249, electron/electron#21457). - This commit completes migration to Vite from Vue CLI (#230). Structure changes: - Introduce separate folders for Electron's main and preload processes. - Move TypeHelpers to `src/` to mark tit as accessible by the rest of the code. Config changes: - Make `vite.config.ts` reusable by Electron configuration. - On electron-builder, use `--publish` flag instead of `-p` for clarity. Tests: - Add log for preload script loading verification. - Implement runtime environment sanity checks. - Enhance logging in `check-desktop-runtime-errors`.
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { CollectionData } from '@/application/collections/';
|
||||
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
||||
@@ -7,28 +6,28 @@ import { IAppMetadata } from '@/infrastructure/Metadata/IAppMetadata';
|
||||
import WindowsData from '@/application/collections/windows.yaml';
|
||||
import MacOsData from '@/application/collections/macos.yaml';
|
||||
import LinuxData from '@/application/collections/linux.yaml';
|
||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { getEnumValues } from '@/application/Common/Enum';
|
||||
import { CategoryCollectionStub } from '@tests/unit/shared/Stubs/CategoryCollectionStub';
|
||||
import { CollectionDataStub } from '@tests/unit/shared/Stubs/CollectionDataStub';
|
||||
import { getAbsentCollectionTestCases, AbsentObjectTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { AppMetadataStub } from '@tests/unit/shared/Stubs/AppMetadataStub';
|
||||
import { AppMetadataFactory } from '@/infrastructure/Metadata/AppMetadataFactory';
|
||||
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('parser', () => {
|
||||
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 CategoryCollectionParserSpy()
|
||||
.setUpReturnValue(data, expected)
|
||||
.mockParser();
|
||||
const parser = new CategoryCollectionParserStub()
|
||||
.withReturnValue(data, expected)
|
||||
.getStub();
|
||||
const sut = new ApplicationParserBuilder()
|
||||
.withCategoryCollectionParser(parser)
|
||||
.withCollectionsData([data]);
|
||||
@@ -39,20 +38,63 @@ describe('ApplicationParser', () => {
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
});
|
||||
describe('processEnv', () => {
|
||||
it('used to parse expected project information', () => {
|
||||
describe('project information', () => {
|
||||
it('informationParser is used to create application info', () => {
|
||||
// arrange
|
||||
const env = new AppMetadataStub();
|
||||
const expected = parseProjectInformation(env);
|
||||
const parserSpy = new CategoryCollectionParserSpy();
|
||||
const parserMock = parserSpy.mockParser();
|
||||
const expectedInformation = new ProjectInformationStub();
|
||||
const informationParserStub = new ProjectInformationParserStub()
|
||||
.withReturnValue(expectedInformation);
|
||||
const sut = new ApplicationParserBuilder()
|
||||
.withCategoryCollectionParser(parserMock);
|
||||
.withProjectInformationParser(informationParserStub.getStub());
|
||||
// act
|
||||
const app = sut.parseApplication();
|
||||
// assert
|
||||
expect(expected).to.deep.equal(app.info);
|
||||
expect(parserSpy.arguments.map((arg) => arg.info).every((info) => info === expected));
|
||||
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 = AppMetadataFactory.Current;
|
||||
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', () => {
|
||||
@@ -79,12 +121,13 @@ describe('ApplicationParser', () => {
|
||||
// act
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
let parserSpy = new CategoryCollectionParserSpy();
|
||||
let categoryParserStub = new CategoryCollectionParserStub();
|
||||
for (let i = 0; i < testCase.input.length; i++) {
|
||||
parserSpy = parserSpy.setUpReturnValue(testCase.input[i], testCase.output[i]);
|
||||
categoryParserStub = categoryParserStub
|
||||
.withReturnValue(testCase.input[i], testCase.output[i]);
|
||||
}
|
||||
const sut = new ApplicationParserBuilder()
|
||||
.withCategoryCollectionParser(parserSpy.mockParser())
|
||||
.withCategoryCollectionParser(categoryParserStub.getStub())
|
||||
.withCollectionsData(testCase.input);
|
||||
// act
|
||||
const app = sut.parseApplication();
|
||||
@@ -96,14 +139,14 @@ describe('ApplicationParser', () => {
|
||||
it('defaults to expected data', () => {
|
||||
// arrange
|
||||
const expected = [WindowsData, MacOsData, LinuxData];
|
||||
const parserSpy = new CategoryCollectionParserSpy();
|
||||
const categoryParserStub = new CategoryCollectionParserStub();
|
||||
const sut = new ApplicationParserBuilder()
|
||||
.withCollectionsData(undefined)
|
||||
.withCategoryCollectionParser(parserSpy.mockParser());
|
||||
.withCategoryCollectionParser(categoryParserStub.getStub());
|
||||
// act
|
||||
sut.parseApplication();
|
||||
// assert
|
||||
const actual = parserSpy.arguments.map((args) => args.data);
|
||||
const actual = categoryParserStub.arguments.map((args) => args.data);
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
describe('throws when data is invalid', () => {
|
||||
@@ -136,10 +179,13 @@ describe('ApplicationParser', () => {
|
||||
});
|
||||
|
||||
class ApplicationParserBuilder {
|
||||
private categoryCollectionParser: CategoryCollectionParserType = new CategoryCollectionParserSpy()
|
||||
.mockParser();
|
||||
private categoryCollectionParser
|
||||
: CategoryCollectionParserType = new CategoryCollectionParserStub().getStub();
|
||||
|
||||
private environment: IAppMetadata = new AppMetadataStub();
|
||||
private projectInformationParser
|
||||
: typeof parseProjectInformation = new ProjectInformationParserStub().getStub();
|
||||
|
||||
private metadata: IAppMetadata = new AppMetadataStub();
|
||||
|
||||
private collectionsData: CollectionData[] = [new CollectionDataStub()];
|
||||
|
||||
@@ -150,10 +196,17 @@ class ApplicationParserBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public withEnvironment(
|
||||
public withProjectInformationParser(
|
||||
projectInformationParser: typeof parseProjectInformation,
|
||||
): this {
|
||||
this.projectInformationParser = projectInformationParser;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withMetadata(
|
||||
environment: IAppMetadata,
|
||||
): this {
|
||||
this.environment = environment;
|
||||
this.metadata = environment;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -165,39 +218,9 @@ class ApplicationParserBuilder {
|
||||
public parseApplication(): ReturnType<typeof parseApplication> {
|
||||
return parseApplication(
|
||||
this.categoryCollectionParser,
|
||||
this.environment,
|
||||
this.projectInformationParser,
|
||||
this.metadata,
|
||||
this.collectionsData,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CategoryCollectionParserSpy {
|
||||
public arguments = new Array<{
|
||||
data: CollectionData,
|
||||
info: ProjectInformation,
|
||||
}>();
|
||||
|
||||
private returnValues = new Map<CollectionData, ICategoryCollection>();
|
||||
|
||||
public setUpReturnValue(
|
||||
data: CollectionData,
|
||||
collection: ICategoryCollection,
|
||||
): CategoryCollectionParserSpy {
|
||||
this.returnValues.set(data, collection);
|
||||
return this;
|
||||
}
|
||||
|
||||
public mockParser(): CategoryCollectionParserType {
|
||||
return (data: CollectionData, info: IProjectInformation) => {
|
||||
this.arguments.push({ data, info });
|
||||
if (this.returnValues.has(data)) {
|
||||
return this.returnValues.get(data);
|
||||
}
|
||||
// Get next OS with a unique OS so mock does not result in an invalid app due to duplicated OS
|
||||
// collections.
|
||||
const currentRun = this.arguments.length - 1;
|
||||
const nextOs = getEnumValues(OperatingSystem)[currentRun];
|
||||
return new CategoryCollectionStub().withOs(nextOs);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,111 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||
import { parseProjectInformation, ProjectInformationFactory } from '@/application/Parser/ProjectInformationParser';
|
||||
import { AppMetadataStub } from '@tests/unit/shared/Stubs/AppMetadataStub';
|
||||
import { PropertyKeys } from '@/TypeHelpers';
|
||||
import { ProjectInformationStub } from '@tests/unit/shared/Stubs/ProjectInformationStub';
|
||||
import { Version } from '@/domain/Version';
|
||||
|
||||
describe('ProjectInformationParser', () => {
|
||||
describe('parseProjectInformation', () => {
|
||||
interface IEnvironmentParsingTestCase {
|
||||
readonly testCaseName: string;
|
||||
readonly setMetadata: (appMetadataStub: AppMetadataStub, value: string) => AppMetadataStub;
|
||||
readonly expectedValue: string;
|
||||
readonly getActualValue: (info: IProjectInformation) => string;
|
||||
}
|
||||
const testCases: readonly IEnvironmentParsingTestCase[] = [
|
||||
{
|
||||
testCaseName: 'version',
|
||||
setMetadata: (metadata, value) => metadata.withVersion(value),
|
||||
expectedValue: '0.11.3',
|
||||
getActualValue: (info) => info.version.toString(),
|
||||
},
|
||||
{
|
||||
testCaseName: 'name',
|
||||
setMetadata: (metadata, value) => metadata.witName(value),
|
||||
expectedValue: 'expected-app-name',
|
||||
getActualValue: (info) => info.name,
|
||||
},
|
||||
{
|
||||
testCaseName: 'homepage',
|
||||
setMetadata: (metadata, value) => metadata.withHomepageUrl(value),
|
||||
expectedValue: 'https://expected.sexy',
|
||||
getActualValue: (info) => info.homepage,
|
||||
},
|
||||
{
|
||||
testCaseName: 'repositoryUrl',
|
||||
setMetadata: (metadata, value) => metadata.withRepositoryUrl(value),
|
||||
expectedValue: 'https://expected-repository.url',
|
||||
getActualValue: (info) => info.repositoryUrl,
|
||||
},
|
||||
{
|
||||
testCaseName: 'slogan',
|
||||
setMetadata: (metadata, value) => metadata.withSlogan(value),
|
||||
expectedValue: 'expected-slogan',
|
||||
getActualValue: (info) => info.slogan,
|
||||
},
|
||||
];
|
||||
for (const {
|
||||
expectedValue, testCaseName, setMetadata, getActualValue,
|
||||
} of testCases) {
|
||||
it(testCaseName, () => {
|
||||
it('returns expected information', () => {
|
||||
// arrange
|
||||
const expectedInformation = new ProjectInformationStub();
|
||||
const factoryMock = () => expectedInformation;
|
||||
// act
|
||||
const actualInformation = parseProjectInformation(new AppMetadataStub(), factoryMock);
|
||||
// assert
|
||||
expect(expectedInformation).to.equal(actualInformation);
|
||||
});
|
||||
describe('default behavior does not throw', () => {
|
||||
it('without metadataFactory', () => {
|
||||
// arrange
|
||||
const metadataFactory = undefined;
|
||||
const informationFactory = new ProjectInformationFactoryStub().getStub();
|
||||
// act
|
||||
const metadata = setMetadata(new AppMetadataStub(), expectedValue);
|
||||
// act
|
||||
const info = parseProjectInformation(metadata);
|
||||
// assert
|
||||
const actual = getActualValue(info);
|
||||
expect(actual).to.be.equal(expectedValue);
|
||||
const act = () => parseProjectInformation(metadataFactory, informationFactory);
|
||||
// expectS
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
}
|
||||
it('without projectInformationFactory', () => {
|
||||
// arrange
|
||||
const metadataFactory = new AppMetadataStub();
|
||||
const informationFactory = undefined;
|
||||
// act
|
||||
const act = () => parseProjectInformation(metadataFactory, informationFactory);
|
||||
// expect
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
describe('parses metadata to project information', () => {
|
||||
interface IMetadataTestCase {
|
||||
readonly setMetadata: (appMetadataStub: AppMetadataStub, value: string) => AppMetadataStub;
|
||||
readonly expectedValue: string;
|
||||
readonly getActualValue: (info: ProjectInformationFactoryStub) => string;
|
||||
}
|
||||
const testCases: { [K in PropertyKeys<ProjectInformationFactoryStub>]: IMetadataTestCase } = {
|
||||
name: {
|
||||
setMetadata: (metadata, value) => metadata.witName(value),
|
||||
expectedValue: 'expected-app-name',
|
||||
getActualValue: (info) => info.name,
|
||||
},
|
||||
version: {
|
||||
setMetadata: (metadata, value) => metadata.withVersion(value),
|
||||
expectedValue: '0.11.3',
|
||||
getActualValue: (info) => info.version.toString(),
|
||||
},
|
||||
slogan: {
|
||||
setMetadata: (metadata, value) => metadata.withSlogan(value),
|
||||
expectedValue: 'expected-slogan',
|
||||
getActualValue: (info) => info.slogan,
|
||||
},
|
||||
repositoryUrl: {
|
||||
setMetadata: (metadata, value) => metadata.withRepositoryUrl(value),
|
||||
expectedValue: 'https://expected-repository.url',
|
||||
getActualValue: (info) => info.repositoryUrl,
|
||||
},
|
||||
homepage: {
|
||||
setMetadata: (metadata, value) => metadata.withHomepageUrl(value),
|
||||
expectedValue: 'https://expected.sexy',
|
||||
getActualValue: (info) => info.homepage,
|
||||
},
|
||||
};
|
||||
Object.entries(testCases).forEach(([propertyName, {
|
||||
expectedValue, setMetadata, getActualValue,
|
||||
}]) => {
|
||||
it(propertyName, () => {
|
||||
// act
|
||||
const metadata = setMetadata(new AppMetadataStub(), expectedValue);
|
||||
const factoryStub = new ProjectInformationFactoryStub();
|
||||
// act
|
||||
parseProjectInformation(metadata, factoryStub.getStub());
|
||||
// assert
|
||||
const actual = getActualValue(factoryStub);
|
||||
expect(actual).to.be.equal(expectedValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ProjectInformationFactoryStub {
|
||||
public name: string;
|
||||
|
||||
public version: Version;
|
||||
|
||||
public slogan: string;
|
||||
|
||||
public repositoryUrl: string;
|
||||
|
||||
public homepage: string;
|
||||
|
||||
public getStub(): ProjectInformationFactory {
|
||||
return (name, version, slogan, repositoryUrl, homepage) => {
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
this.slogan = slogan;
|
||||
this.repositoryUrl = repositoryUrl;
|
||||
this.homepage = homepage;
|
||||
return new ProjectInformationStub();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user