Change slogan and refactor project info naming

The project's slagon has been updated back to "Privacy is sexy" from
"Now you have the choice" for enhanced brand clarity and memorability.

This change also reflects the community's preference and aligns with the
project's established identity.

This commit also refactors naming and structure of project information
(metadata) struct to enhance clarity and maintainability in relation to
changing the slogan.

Key changes include:

- Update UI components to display the revised slogan.
- Remove period from project slogan in code area for consistency with a
  explanatory comment for future maintainability.
- Refactor header container and class names for clarity.
- Standardize project metadata usage in `TheCodeArea.vue` to ensure
  consistency.
- Improve code clarity by renaming `IProjectInformation` to
  `ProjectDetails` and `ProjectInformation` to `GitHubProjectDetails`.
- Organize `ProjectDetails` under a dedicated `Project` directory within
  the domain layer for better structure.

These changes are expected to improve the project's appeal and
streamline future maintenance and development efforts.
This commit is contained in:
undergroundwires
2024-02-10 18:50:56 +01:00
parent b9c89b701f
commit a54e16488c
38 changed files with 273 additions and 256 deletions

View File

@@ -1,4 +1,4 @@
# privacy.sexy — Now you have the choice
# privacy.sexy — Privacy is sexy
> Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy.

View File

@@ -2,7 +2,7 @@
"name": "privacy.sexy",
"version": "0.12.10",
"private": true,
"slogan": "Now you have the choice",
"slogan": "Privacy is sexy",
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy.",
"author": "undergroundwires",
"type": "module",

View File

@@ -1,11 +1,11 @@
import type { CollectionData } from '@/application/collections/';
import { IApplication } from '@/domain/IApplication';
import { IProjectInformation } from '@/domain/IProjectInformation';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import WindowsData from '@/application/collections/windows.yaml';
import MacOsData from '@/application/collections/macos.yaml';
import LinuxData from '@/application/collections/linux.yaml';
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
import { parseProjectDetails } from '@/application/Parser/ProjectDetailsParser';
import { Application } from '@/domain/Application';
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
@@ -13,19 +13,21 @@ import { parseCategoryCollection } from './CategoryCollectionParser';
export function parseApplication(
categoryParser = parseCategoryCollection,
informationParser = parseProjectInformation,
projectDetailsParser = parseProjectDetails,
metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance,
collectionsData = PreParsedCollections,
): IApplication {
validateCollectionsData(collectionsData);
const information = informationParser(metadata);
const collections = collectionsData.map((collection) => categoryParser(collection, information));
const app = new Application(information, collections);
const projectDetails = projectDetailsParser(metadata);
const collections = collectionsData.map(
(collection) => categoryParser(collection, projectDetails),
);
const app = new Application(projectDetails, collections);
return app;
}
export type CategoryCollectionParserType
= (file: CollectionData, info: IProjectInformation) => ICategoryCollection;
= (file: CollectionData, projectDetails: ProjectDetails) => ICategoryCollection;
const PreParsedCollections: readonly CollectionData [] = [
WindowsData, MacOsData, LinuxData,

View File

@@ -2,7 +2,7 @@ import type { CollectionData } from '@/application/collections/';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { CategoryCollection } from '@/domain/CategoryCollection';
import { IProjectInformation } from '@/domain/IProjectInformation';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { createEnumParser } from '../Common/Enum';
import { parseCategory } from './CategoryParser';
import { CategoryCollectionParseContext } from './Script/CategoryCollectionParseContext';
@@ -10,12 +10,12 @@ import { ScriptingDefinitionParser } from './ScriptingDefinition/ScriptingDefini
export function parseCategoryCollection(
content: CollectionData,
info: IProjectInformation,
projectDetails: ProjectDetails,
osParser = createEnumParser(OperatingSystem),
): ICategoryCollection {
validate(content);
const scripting = new ScriptingDefinitionParser()
.parse(content.scripting, info);
.parse(content.scripting, projectDetails);
const context = new CategoryCollectionParseContext(content.functions, scripting);
const categories = content.actions.map((action) => parseCategory(action, context));
const os = osParser.parseEnum(content.os, 'os');

View File

@@ -1,21 +1,21 @@
import { IProjectInformation } from '@/domain/IProjectInformation';
import { ProjectInformation } from '@/domain/ProjectInformation';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { GitHubProjectDetails } from '@/domain/Project/GitHubProjectDetails';
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
import { Version } from '@/domain/Version';
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
import { ConstructorArguments } from '@/TypeHelpers';
export function
parseProjectInformation(
parseProjectDetails(
metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance,
createProjectInformation: ProjectInformationFactory = (
createProjectDetails: ProjectDetailsFactory = (
...args
) => new ProjectInformation(...args),
): IProjectInformation {
) => new GitHubProjectDetails(...args),
): ProjectDetails {
const version = new Version(
metadata.version,
);
return createProjectInformation(
return createProjectDetails(
metadata.name,
version,
metadata.slogan,
@@ -24,6 +24,6 @@ parseProjectInformation(
);
}
export type ProjectInformationFactory = (
...args: ConstructorArguments<typeof ProjectInformation>
) => IProjectInformation;
export type ProjectDetailsFactory = (
...args: ConstructorArguments<typeof GitHubProjectDetails>
) => ProjectDetails;

View File

@@ -2,7 +2,7 @@ import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expre
import { ParameterSubstitutionParser } from '@/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser';
import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser';
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
import { IProjectInformation } from '@/domain/IProjectInformation';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import { ICodeSubstituter } from './ICodeSubstituter';
@@ -15,13 +15,13 @@ export class CodeSubstituter implements ICodeSubstituter {
}
public substitute(code: string, info: IProjectInformation): string {
public substitute(code: string, projectDetails: ProjectDetails): string {
if (!code) { throw new Error('missing code'); }
const args = new FunctionCallArgumentCollection();
const substitute = (name: string, value: string) => args
.addArgument(new FunctionCallArgument(name, value));
substitute('homepage', info.homepage);
substitute('version', info.version.toString());
substitute('homepage', projectDetails.homepage);
substitute('version', projectDetails.version.toString());
substitute('date', this.date.toUTCString());
const compiledCode = this.compiler.compileExpressions(code, args);
return compiledCode;

View File

@@ -1,5 +1,5 @@
import { IProjectInformation } from '@/domain/IProjectInformation';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
export interface ICodeSubstituter {
substitute(code: string, info: IProjectInformation): string;
substitute(code: string, projectDetails: ProjectDetails): string;
}

View File

@@ -2,7 +2,7 @@ import type { ScriptingDefinitionData } from '@/application/collections/';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ScriptingDefinition } from '@/domain/ScriptingDefinition';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IProjectInformation } from '@/domain/IProjectInformation';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { createEnumParser } from '../../Common/Enum';
import { ICodeSubstituter } from './ICodeSubstituter';
import { CodeSubstituter } from './CodeSubstituter';
@@ -16,11 +16,11 @@ export class ScriptingDefinitionParser {
public parse(
definition: ScriptingDefinitionData,
info: IProjectInformation,
projectDetails: ProjectDetails,
): IScriptingDefinition {
const language = this.languageParser.parseEnum(definition.language, 'language');
const startCode = this.codeSubstituter.substitute(definition.startCode, info);
const endCode = this.codeSubstituter.substitute(definition.endCode, info);
const startCode = this.codeSubstituter.substitute(definition.startCode, projectDetails);
const endCode = this.codeSubstituter.substitute(definition.endCode, projectDetails);
return new ScriptingDefinition(
language,
startCode,

View File

@@ -1,11 +1,11 @@
import { IApplication } from './IApplication';
import { ICategoryCollection } from './ICategoryCollection';
import { IProjectInformation } from './IProjectInformation';
import { ProjectDetails } from './Project/ProjectDetails';
import { OperatingSystem } from './OperatingSystem';
export class Application implements IApplication {
constructor(
public info: IProjectInformation,
public projectDetails: ProjectDetails,
public collections: readonly ICategoryCollection[],
) {
validateCollections(collections);

View File

@@ -1,9 +1,9 @@
import { ICategoryCollection } from './ICategoryCollection';
import { IProjectInformation } from './IProjectInformation';
import { ProjectDetails } from './Project/ProjectDetails';
import { OperatingSystem } from './OperatingSystem';
export interface IApplication {
readonly info: IProjectInformation;
readonly projectDetails: ProjectDetails;
readonly collections: readonly ICategoryCollection[];
getSupportedOsList(): OperatingSystem[];

View File

@@ -1,9 +1,9 @@
import { assertInRange } from '@/application/Common/Enum';
import { IProjectInformation } from './IProjectInformation';
import { OperatingSystem } from './OperatingSystem';
import { Version } from './Version';
import { OperatingSystem } from '../OperatingSystem';
import { Version } from '../Version';
import type { ProjectDetails } from './ProjectDetails';
export class ProjectInformation implements IProjectInformation {
export class GitHubProjectDetails implements ProjectDetails {
public readonly repositoryWebUrl: string;
constructor(

View File

@@ -1,7 +1,7 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
import { Version } from '@/domain/Version';
export interface IProjectInformation {
export interface ProjectDetails {
readonly name: string;
readonly version: Version;

View File

@@ -44,14 +44,14 @@ export default defineComponent({
setup() {
const { currentState } = injectKey((keys) => keys.useCollectionState);
const { info } = injectKey((keys) => keys.useApplication);
const { projectDetails } = injectKey((keys) => keys.useApplication);
const operatingSystem = computed<OperatingSystem>(() => currentState.value.os);
const appName = computed<string>(() => info.name);
const appName = computed<string>(() => projectDetails.name);
const downloadUrl = computed<string>(
() => info.getDownloadUrl(operatingSystem.value),
() => projectDetails.getDownloadUrl(operatingSystem.value),
);
const osName = computed<string>(

View File

@@ -24,6 +24,7 @@ import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/IC
import { CodeBuilderFactory } from '@/application/Context/State/Code/Generation/CodeBuilderFactory';
import SizeObserver from '@/presentation/components/Shared/SizeObserver.vue';
import { NonCollapsing } from '@/presentation/components/Scripts/View/Cards/NonCollapsingDirective';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import ace from './ace-importer';
export default defineComponent({
@@ -41,6 +42,7 @@ export default defineComponent({
},
setup(props) {
const { onStateChange, currentState } = injectKey((keys) => keys.useCollectionState);
const { projectDetails } = injectKey((keys) => keys.useApplication);
const { events } = injectKey((keys) => keys.useAutoUnsubscribedEvents);
const editorId = 'codeEditor';
@@ -74,7 +76,7 @@ export default defineComponent({
}
function updateCode(code: string, language: ScriptingLanguage) {
const innerCode = code || getDefaultCode(language);
const innerCode = code || getDefaultCode(language, projectDetails);
editor?.setValue(innerCode, 1);
}
@@ -171,10 +173,14 @@ function getLanguage(language: ScriptingLanguage) {
}
}
function getDefaultCode(language: ScriptingLanguage): string {
function getDefaultCode(language: ScriptingLanguage, project: ProjectDetails): string {
return new CodeBuilderFactory()
.create(language)
.appendCommentLine('privacy.sexy — Now you have the choice.')
.appendCommentLine(`${project.name}${project.slogan}`)
/*
Keep the slogan without a period for impact and continuity.
Slogans should be punchy and memorable, not punctuated like full sentences.
*/
.appendCommentLine(' 🔐 Enforce privacy & security best-practices on Windows, macOS and Linux.')
.appendLine()
.appendCommentLine('-- 🤔 How to use')
@@ -183,7 +189,7 @@ function getDefaultCode(language: ScriptingLanguage): string {
.appendCommentLine(' 📙 After you choose any tweak, you can download or copy to execute your script.')
.appendCommentLine(' 📙 Come back regularly to apply latest version for stronger privacy and security.')
.appendLine()
.appendCommentLine('-- 🧐 Why privacy.sexy')
.appendCommentLine(`-- 🧐 Why ${project.name}`)
.appendCommentLine(' ✔️ Rich tweak pool to harden security & privacy of the OS and other software on it.')
.appendCommentLine(' ✔️ No need to run any compiled software on your system, just run the generated scripts.')
.appendCommentLine(' ✔️ Have full visibility into what the tweaks do as you enable them.')

View File

@@ -62,9 +62,9 @@ export default defineComponent({
setup() {
const { modifyCurrentState, onStateChange } = injectKey((keys) => keys.useCollectionState);
const { events } = injectKey((keys) => keys.useAutoUnsubscribedEvents);
const { info } = injectKey((keys) => keys.useApplication);
const { projectDetails } = injectKey((keys) => keys.useApplication);
const repositoryUrl = computed<string>(() => info.repositoryWebUrl);
const repositoryUrl = computed<string>(() => projectDetails.repositoryWebUrl);
const searchQuery = ref<string | undefined>();
const isSearching = computed(() => Boolean(searchQuery.value));
const searchHasMatches = ref(false);

View File

@@ -3,6 +3,6 @@ import { IApplication } from '@/domain/IApplication';
export function useApplication(application: IApplication) {
return {
application,
info: application.info,
projectDetails: application.projectDetails,
};
}

View File

@@ -26,7 +26,7 @@ export default defineComponent({
},
},
setup(props) {
const { info } = injectKey((keys) => keys.useApplication);
const { projectDetails } = injectKey((keys) => keys.useApplication);
const { os: currentOs } = injectKey((keys) => keys.useRuntimeEnvironment);
const isCurrentOs = computed<boolean>(() => {
@@ -42,7 +42,7 @@ export default defineComponent({
});
const downloadUrl = computed<string>(() => {
return info.getDownloadUrl(props.operatingSystem);
return projectDetails.getDownloadUrl(props.operatingSystem);
});
return {

View File

@@ -59,11 +59,11 @@ import { injectKey } from '@/presentation/injectionSymbols';
export default defineComponent({
setup() {
const { info } = injectKey((keys) => keys.useApplication);
const { projectDetails } = injectKey((keys) => keys.useApplication);
const { isRunningAsDesktopApplication } = injectKey((keys) => keys.useRuntimeEnvironment);
const repositoryUrl = computed<string>(() => info.repositoryUrl);
const feedbackUrl = computed<string>(() => info.feedbackUrl);
const repositoryUrl = computed<string>(() => projectDetails.repositoryUrl);
const feedbackUrl = computed<string>(() => projectDetails.feedbackUrl);
return {
repositoryUrl,

View File

@@ -67,20 +67,20 @@ export default defineComponent({
FlatButton,
},
setup() {
const { info } = injectKey((keys) => keys.useApplication);
const { projectDetails } = injectKey((keys) => keys.useApplication);
const { isRunningAsDesktopApplication } = injectKey((keys) => keys.useRuntimeEnvironment);
const isPrivacyDialogVisible = ref(false);
const version = computed<string>(() => info.version.toString());
const version = computed<string>(() => projectDetails.version.toString());
const homepageUrl = computed<string>(() => info.homepage);
const homepageUrl = computed<string>(() => projectDetails.homepage);
const repositoryUrl = computed<string>(() => info.repositoryWebUrl);
const repositoryUrl = computed<string>(() => projectDetails.repositoryWebUrl);
const releaseUrl = computed<string>(() => info.releaseUrl);
const releaseUrl = computed<string>(() => projectDetails.releaseUrl);
const feedbackUrl = computed<string>(() => info.feedbackUrl);
const feedbackUrl = computed<string>(() => projectDetails.feedbackUrl);
function showPrivacyDialog() {
isPrivacyDialogVisible.value = true;

View File

@@ -1,9 +1,13 @@
<template>
<div id="container">
<h1 class="child title">
<div class="container">
<h1 class="child brand">
{{ title }}
</h1>
<h2 class="child subtitle">
<h2 class="child slogan">
<!--
Keep the slogan without a period for impact and continuity.
Slogans should be punchy and memorable, not punctuated like full sentences.
-->
{{ subtitle }}
</h2>
</div>
@@ -15,10 +19,10 @@ import { injectKey } from '@/presentation/injectionSymbols';
export default defineComponent({
setup() {
const { info } = injectKey((keys) => keys.useApplication);
const { projectDetails } = injectKey((keys) => keys.useApplication);
const title = computed(() => info.name);
const subtitle = computed(() => info.slogan);
const title = computed(() => projectDetails.name);
const subtitle = computed(() => projectDetails.slogan);
return {
title,
@@ -32,7 +36,7 @@ export default defineComponent({
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
#container {
.container {
display: flex;
align-items: center;
flex-direction: column;
@@ -42,13 +46,13 @@ export default defineComponent({
text-align: center;
}
.title {
.brand {
margin: 0;
text-transform: uppercase;
font-family: $font-main;
font-size: $font-size-absolute-xx-large;
}
.subtitle {
.slogan {
margin: 0;
font-size: $font-size-absolute-x-large;
color: $color-primary;

View File

@@ -1,9 +1,9 @@
import { shell } from 'electron';
import { UpdateInfo } from 'electron-updater';
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
import { ProjectInformation } from '@/domain/ProjectInformation';
import { GitHubProjectDetails } from '@/domain/Project/GitHubProjectDetails';
import { Version } from '@/domain/Version';
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
import { parseProjectDetails } from '@/application/Parser/ProjectDetailsParser';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { UpdateProgressBar } from '../UpdateProgressBar';
import {
@@ -139,8 +139,8 @@ interface UpdateUrls {
}
function getRemoteUpdateUrls(targetVersion: string): UpdateUrls {
const existingProject = parseProjectInformation();
const targetProject = new ProjectInformation(
const existingProject = parseProjectDetails();
const targetProject = new GitHubProjectDetails(
existingProject.name,
new Version(targetVersion),
existingProject.slogan,

View File

@@ -14,7 +14,7 @@ const requestOptions: IBatchRequestOptions = {
requestOptions: {
retryExponentialBaseInMs: 3 /* sec */ * 1000,
requestTimeoutInMs: 60 /* sec */ * 1000,
additionalHeaders: { referer: app.info.homepage },
additionalHeaders: { referer: app.projectDetails.homepage },
},
};
const testTimeoutInMs = urls.length * 60 /* seconds */ * 1000;

View File

@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest';
import type { CollectionData } from '@/application/collections/';
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
import { parseProjectDetails } from '@/application/Parser/ProjectDetailsParser';
import { CategoryCollectionParserType, parseApplication } from '@/application/Parser/ApplicationParser';
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
import WindowsData from '@/application/collections/windows.yaml';
@@ -13,8 +13,8 @@ import { getAbsentCollectionTestCases } from '@tests/unit/shared/TestCases/Absen
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';
import { ProjectDetailsParserStub } from '@tests/unit/shared/Stubs/ProjectDetailsParserStub';
import { ProjectDetailsStub } from '@tests/unit/shared/Stubs/ProjectDetailsStub';
describe('ApplicationParser', () => {
describe('parseApplication', () => {
@@ -38,63 +38,65 @@ describe('ApplicationParser', () => {
expect(expected).to.equal(actual);
});
});
describe('project information', () => {
it('informationParser is used to create application info', () => {
describe('projectDetails', () => {
it('projectDetailsParser is used to create the instance', () => {
// arrange
const expectedInformation = new ProjectInformationStub();
const informationParserStub = new ProjectInformationParserStub()
.withReturnValue(expectedInformation);
const expectedProjectDetails = new ProjectDetailsStub();
const projectDetailsParserStub = new ProjectDetailsParserStub()
.withReturnValue(expectedProjectDetails);
const sut = new ApplicationParserBuilder()
.withProjectInformationParser(informationParserStub.getStub());
.withProjectDetailsParser(projectDetailsParserStub.getStub());
// act
const app = sut.parseApplication();
// assert
const actualInformation = app.info;
expect(expectedInformation).to.deep.equal(actualInformation);
const actualProjectDetails = app.projectDetails;
expect(expectedProjectDetails).to.deep.equal(actualProjectDetails);
});
it('informationParser is used to parse collection info', () => {
it('projectDetailsParser is used to parse collection', () => {
// arrange
const expectedInformation = new ProjectInformationStub();
const informationParserStub = new ProjectInformationParserStub()
.withReturnValue(expectedInformation);
const expectedProjectDetails = new ProjectDetailsStub();
const projectDetailsParserStub = new ProjectDetailsParserStub()
.withReturnValue(expectedProjectDetails);
const collectionParserStub = new CategoryCollectionParserStub();
const sut = new ApplicationParserBuilder()
.withProjectInformationParser(informationParserStub.getStub())
.withProjectDetailsParser(projectDetailsParserStub.getStub())
.withCategoryCollectionParser(collectionParserStub.getStub());
// act
sut.parseApplication();
// assert
expect(collectionParserStub.arguments).to.have.length.above(0);
const actuallyUsedInfos = collectionParserStub.arguments.map((arg) => arg.info);
expect(actuallyUsedInfos.every((info) => info === expectedInformation));
const actuallyUsedInfos = collectionParserStub.arguments.map((arg) => arg.projectDetails);
expect(actuallyUsedInfos.every(
(actualProjectDetails) => actualProjectDetails === expectedProjectDetails,
)).to.equal(true);
});
});
describe('metadata', () => {
it('used to parse expected metadata', () => {
// arrange
const expectedMetadata = new AppMetadataStub();
const infoParserStub = new ProjectInformationParserStub();
const projectDetailsParser = new ProjectDetailsParserStub();
// act
new ApplicationParserBuilder()
.withMetadata(expectedMetadata)
.withProjectInformationParser(infoParserStub.getStub())
.withProjectDetailsParser(projectDetailsParser.getStub())
.parseApplication();
// assert
expect(infoParserStub.arguments).to.have.lengthOf(1);
expect(infoParserStub.arguments[0]).to.equal(expectedMetadata);
expect(projectDetailsParser.arguments).to.have.lengthOf(1);
expect(projectDetailsParser.arguments[0]).to.equal(expectedMetadata);
});
it('defaults to metadata from factory', () => {
// arrange
const expectedMetadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance;
const infoParserStub = new ProjectInformationParserStub();
const projectDetailsParser = new ProjectDetailsParserStub();
// act
new ApplicationParserBuilder()
.withMetadata(undefined) // force using default
.withProjectInformationParser(infoParserStub.getStub())
.withProjectDetailsParser(projectDetailsParser.getStub())
.parseApplication();
// assert
expect(infoParserStub.arguments).to.have.lengthOf(1);
expect(infoParserStub.arguments[0]).to.equal(expectedMetadata);
expect(projectDetailsParser.arguments).to.have.lengthOf(1);
expect(projectDetailsParser.arguments[0]).to.equal(expectedMetadata);
});
});
describe('collectionsData', () => {
@@ -182,8 +184,8 @@ class ApplicationParserBuilder {
private categoryCollectionParser
: CategoryCollectionParserType = new CategoryCollectionParserStub().getStub();
private projectInformationParser
: typeof parseProjectInformation = new ProjectInformationParserStub().getStub();
private projectDetailsParser
: typeof parseProjectDetails = new ProjectDetailsParserStub().getStub();
private metadata: IAppMetadata | undefined = new AppMetadataStub();
@@ -196,10 +198,10 @@ class ApplicationParserBuilder {
return this;
}
public withProjectInformationParser(
projectInformationParser: typeof parseProjectInformation,
public withProjectDetailsParser(
projectDetailsParser: typeof parseProjectDetails,
): this {
this.projectInformationParser = projectInformationParser;
this.projectDetailsParser = projectDetailsParser;
return this;
}
@@ -218,7 +220,7 @@ class ApplicationParserBuilder {
public parseApplication(): ReturnType<typeof parseApplication> {
return parseApplication(
this.categoryCollectionParser,
this.projectInformationParser,
this.projectDetailsParser,
this.metadata,
this.collectionsData,
);

View File

@@ -6,7 +6,7 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { ScriptingDefinitionParser } from '@/application/Parser/ScriptingDefinition/ScriptingDefinitionParser';
import { EnumParserStub } from '@tests/unit/shared/Stubs/EnumParserStub';
import { ProjectInformationStub } from '@tests/unit/shared/Stubs/ProjectInformationStub';
import { ProjectDetailsStub } from '@tests/unit/shared/Stubs/ProjectDetailsStub';
import { getCategoryStub, CollectionDataStub } from '@tests/unit/shared/Stubs/CollectionDataStub';
import { CategoryCollectionParseContextStub } from '@tests/unit/shared/Stubs/CategoryCollectionParseContextStub';
import { CategoryDataStub } from '@tests/unit/shared/Stubs/CategoryDataStub';
@@ -25,9 +25,9 @@ describe('CategoryCollectionParser', () => {
const expectedError = 'content does not define any action';
const collection = new CollectionDataStub()
.withActions(absentValue);
const info = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
// act
const act = () => parseCategoryCollection(collection, info);
const act = () => parseCategoryCollection(collection, projectDetails);
// assert
expect(act).to.throw(expectedError);
}, { excludeUndefined: true, excludeNull: true });
@@ -39,9 +39,9 @@ describe('CategoryCollectionParser', () => {
const expected = [parseCategory(actions[0], context), parseCategory(actions[1], context)];
const collection = new CollectionDataStub()
.withActions(actions);
const info = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
// act
const actual = parseCategoryCollection(collection, info).actions;
const actual = parseCategoryCollection(collection, projectDetails).actions;
// assert
expect(excludingId(actual)).to.be.deep.equal(excludingId(expected));
function excludingId<TId>(array: ReadonlyArray<IEntity<TId>>) {
@@ -57,11 +57,11 @@ describe('CategoryCollectionParser', () => {
it('parses scripting definition as expected', () => {
// arrange
const collection = new CollectionDataStub();
const information = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
const expected = new ScriptingDefinitionParser()
.parse(collection.scripting, information);
.parse(collection.scripting, projectDetails);
// act
const actual = parseCategoryCollection(collection, information).scripting;
const actual = parseCategoryCollection(collection, projectDetails).scripting;
// assert
expect(expected).to.deep.equal(actual);
});
@@ -76,9 +76,9 @@ describe('CategoryCollectionParser', () => {
.withOs(osText);
const parserMock = new EnumParserStub<OperatingSystem>()
.setup(expectedName, osText, expectedOs);
const info = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
// act
const actual = parseCategoryCollection(collection, info, parserMock);
const actual = parseCategoryCollection(collection, projectDetails, parserMock);
// assert
expect(actual.os).to.equal(expectedOs);
});
@@ -106,9 +106,9 @@ describe('CategoryCollectionParser', () => {
const collection = new CollectionDataStub()
.withActions([category])
.withFunctions([func]);
const info = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
// act
const actual = parseCategoryCollection(collection, info);
const actual = parseCategoryCollection(collection, projectDetails);
// assert
const actualScript = actual.getScript(scriptName);
const actualCode = actualScript.code.execute;

View File

@@ -1,85 +1,87 @@
import { describe, it, expect } from 'vitest';
import { parseProjectInformation, ProjectInformationFactory } from '@/application/Parser/ProjectInformationParser';
import { parseProjectDetails, ProjectDetailsFactory } from '@/application/Parser/ProjectDetailsParser';
import { AppMetadataStub } from '@tests/unit/shared/Stubs/AppMetadataStub';
import { PropertyKeys } from '@/TypeHelpers';
import { ProjectInformationStub } from '@tests/unit/shared/Stubs/ProjectInformationStub';
import { ProjectDetailsStub } from '@tests/unit/shared/Stubs/ProjectDetailsStub';
import { Version } from '@/domain/Version';
describe('ProjectInformationParser', () => {
describe('parseProjectInformation', () => {
it('returns expected information', () => {
describe('ProjectDetailsParser', () => {
describe('parseProjectDetails', () => {
it('returns expected instance', () => {
// arrange
const expectedInformation = new ProjectInformationStub();
const expectedInformation = new ProjectDetailsStub();
const factoryMock = () => expectedInformation;
// act
const actualInformation = parseProjectInformation(new AppMetadataStub(), factoryMock);
const actualInformation = parseProjectDetails(new AppMetadataStub(), factoryMock);
// assert
expect(expectedInformation).to.equal(actualInformation);
});
describe('default behavior does not throw', () => {
it('without metadataFactory', () => {
it('without metadata', () => {
// arrange
const metadataFactory = undefined;
const informationFactory = new ProjectInformationFactoryStub().getStub();
const projectDetailsFactory = new ProjectDetailsFactoryStub().getStub();
// act
const act = () => parseProjectInformation(metadataFactory, informationFactory);
const act = () => parseProjectDetails(metadataFactory, projectDetailsFactory);
// expectS
expect(act).to.not.throw();
});
it('without projectInformationFactory', () => {
it('without projectDetailsFactory', () => {
// arrange
const metadataFactory = new AppMetadataStub();
const informationFactory = undefined;
const projectDetailsFactory = undefined;
// act
const act = () => parseProjectInformation(metadataFactory, informationFactory);
const act = () => parseProjectDetails(metadataFactory, projectDetailsFactory);
// expect
expect(act).to.not.throw();
});
});
describe('parses metadata to project information', () => {
interface IMetadataTestCase {
describe('parses metadata correctly', () => {
interface MetadataTestScenario {
readonly setMetadata: (appMetadataStub: AppMetadataStub, value: string) => AppMetadataStub;
readonly expectedValue: string;
readonly getActualValue: (info: ProjectInformationFactoryStub) => string;
readonly getActualValue: (projectDetailsFactory: ProjectDetailsFactoryStub) => string;
}
const testCases: { [K in PropertyKeys<ProjectInformationFactoryStub>]: IMetadataTestCase } = {
const testScenarios: {
[K in PropertyKeys<ProjectDetailsFactoryStub>]: MetadataTestScenario
} = {
name: {
setMetadata: (metadata, value) => metadata.witName(value),
expectedValue: 'expected-app-name',
getActualValue: (info) => info.name,
getActualValue: (projectDetailsFactory) => projectDetailsFactory.name,
},
version: {
setMetadata: (metadata, value) => metadata.withVersion(value),
expectedValue: '0.11.3',
getActualValue: (info) => info.version.toString(),
getActualValue: (projectDetailsFactory) => projectDetailsFactory.version.toString(),
},
slogan: {
setMetadata: (metadata, value) => metadata.withSlogan(value),
expectedValue: 'expected-slogan',
getActualValue: (info) => info.slogan,
getActualValue: (projectDetailsFactory) => projectDetailsFactory.slogan,
},
repositoryUrl: {
setMetadata: (metadata, value) => metadata.withRepositoryUrl(value),
expectedValue: 'https://expected-repository.url',
getActualValue: (info) => info.repositoryUrl,
getActualValue: (projectDetailsFactory) => projectDetailsFactory.repositoryUrl,
},
homepage: {
setMetadata: (metadata, value) => metadata.withHomepageUrl(value),
expectedValue: 'https://expected.sexy',
getActualValue: (info) => info.homepage,
getActualValue: (projectDetailsFactory) => projectDetailsFactory.homepage,
},
};
Object.entries(testCases).forEach(([propertyName, {
Object.entries(testScenarios).forEach(([propertyName, {
expectedValue, setMetadata, getActualValue,
}]) => {
it(propertyName, () => {
// act
const metadata = setMetadata(new AppMetadataStub(), expectedValue);
const factoryStub = new ProjectInformationFactoryStub();
const projectDetailsFactoryStub = new ProjectDetailsFactoryStub();
// act
parseProjectInformation(metadata, factoryStub.getStub());
parseProjectDetails(metadata, projectDetailsFactoryStub.getStub());
// assert
const actual = getActualValue(factoryStub);
const actual = getActualValue(projectDetailsFactoryStub);
expect(actual).to.be.equal(expectedValue);
});
});
@@ -87,7 +89,7 @@ describe('ProjectInformationParser', () => {
});
});
class ProjectInformationFactoryStub {
class ProjectDetailsFactoryStub {
public name: string;
public version: Version;
@@ -98,14 +100,14 @@ class ProjectInformationFactoryStub {
public homepage: string;
public getStub(): ProjectInformationFactory {
public getStub(): ProjectDetailsFactory {
return (name, version, slogan, repositoryUrl, homepage) => {
this.name = name;
this.version = version;
this.slogan = slogan;
this.repositoryUrl = repositoryUrl;
this.homepage = homepage;
return new ProjectInformationStub();
return new ProjectDetailsStub();
};
}
}

View File

@@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest';
import { CodeSubstituter } from '@/application/Parser/ScriptingDefinition/CodeSubstituter';
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
import { ProjectInformationStub } from '@tests/unit/shared/Stubs/ProjectInformationStub';
import { ProjectDetailsStub } from '@tests/unit/shared/Stubs/ProjectDetailsStub';
import { ExpressionsCompilerStub } from '@tests/unit/shared/Stubs/ExpressionsCompilerStub';
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
@@ -11,26 +11,26 @@ describe('CodeSubstituter', () => {
// arrange
const expectedError = 'missing code';
const code = emptyCode;
const info = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
const sut = new CodeSubstituterBuilder().build();
// act
const act = () => sut.substitute(code, info);
const act = () => sut.substitute(code, projectDetails);
// assert
expect(act).to.throw(expectedError);
}, { excludeNull: true, excludeUndefined: true });
});
describe('substitutes parameters as expected values', () => {
// arrange
const info = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
const date = new Date();
const testCases: Array<{ parameter: string, argument: string }> = [
{
parameter: 'homepage',
argument: info.homepage,
argument: projectDetails.homepage,
},
{
parameter: 'version',
argument: info.version.toString(),
argument: projectDetails.version.toString(),
},
{
parameter: 'date',
@@ -45,7 +45,7 @@ describe('CodeSubstituter', () => {
.withDate(date)
.build();
// act
sut.substitute('non empty code', info);
sut.substitute('non empty code', projectDetails);
// assert
expect(compilerStub.callHistory).to.have.lengthOf(1);
const parameters = compilerStub.callHistory[0].args[1];
@@ -63,7 +63,7 @@ describe('CodeSubstituter', () => {
.withCompiler(compilerStub)
.build();
// act
sut.substitute(expected, new ProjectInformationStub());
sut.substitute(expected, new ProjectDetailsStub());
// assert
expect(compilerStub.callHistory).to.have.lengthOf(1);
expect(compilerStub.callHistory[0].args[0]).to.equal(expected);

View File

@@ -4,7 +4,7 @@ import { ScriptingDefinitionParser } from '@/application/Parser/ScriptingDefinit
import { IEnumParser } from '@/application/Common/Enum';
import { ICodeSubstituter } from '@/application/Parser/ScriptingDefinition/ICodeSubstituter';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ProjectInformationStub } from '@tests/unit/shared/Stubs/ProjectInformationStub';
import { ProjectDetailsStub } from '@tests/unit/shared/Stubs/ProjectDetailsStub';
import { EnumParserStub } from '@tests/unit/shared/Stubs/EnumParserStub';
import { ScriptingDefinitionDataStub } from '@tests/unit/shared/Stubs/ScriptingDefinitionDataStub';
import { CodeSubstituterStub } from '@tests/unit/shared/Stubs/CodeSubstituterStub';
@@ -17,7 +17,7 @@ describe('ScriptingDefinitionParser', () => {
const expectedLanguage = ScriptingLanguage.batchfile;
const languageText = 'batchfile';
const expectedName = 'language';
const info = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
const definition = new ScriptingDefinitionDataStub()
.withLanguage(languageText);
const parserMock = new EnumParserStub<ScriptingLanguage>()
@@ -26,7 +26,7 @@ describe('ScriptingDefinitionParser', () => {
.withParser(parserMock)
.build();
// act
const actual = sut.parse(definition, info);
const actual = sut.parse(definition, projectDetails);
// assert
expect(actual.language).to.equal(expectedLanguage);
});
@@ -51,14 +51,14 @@ describe('ScriptingDefinitionParser', () => {
];
for (const testCase of testCases) {
it(testCase.name, () => {
const info = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
const substituterMock = new CodeSubstituterStub()
.setup(code, info, expected);
.setup(code, projectDetails, expected);
const sut = new ScriptingDefinitionParserBuilder()
.withSubstituter(substituterMock)
.build();
// act
const definition = sut.parse(testCase.data, info);
const definition = sut.parse(testCase.data, projectDetails);
// assert
const actual = testCase.getActualValue(definition);
expect(actual).to.equal(expected);

View File

@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
import { Application } from '@/domain/Application';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { CategoryCollectionStub } from '@tests/unit/shared/Stubs/CategoryCollectionStub';
import { ProjectInformationStub } from '@tests/unit/shared/Stubs/ProjectInformationStub';
import { ProjectDetailsStub } from '@tests/unit/shared/Stubs/ProjectDetailsStub';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { getAbsentCollectionTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
@@ -12,10 +12,10 @@ describe('Application', () => {
// arrange
const missingOs = OperatingSystem.Android;
const expectedError = `Operating system "${OperatingSystem[missingOs]}" is not defined in application`;
const info = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
const collections = [new CategoryCollectionStub().withOs(OperatingSystem.Windows)];
// act
const sut = new Application(info, collections);
const sut = new Application(projectDetails, collections);
const act = () => sut.getCollection(missingOs);
// assert
expect(act).to.throw(expectedError);
@@ -24,25 +24,25 @@ describe('Application', () => {
// arrange
const os = OperatingSystem.Windows;
const expected = new CategoryCollectionStub().withOs(os);
const info = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
const collections = [expected, new CategoryCollectionStub().withOs(OperatingSystem.Android)];
// act
const sut = new Application(info, collections);
const sut = new Application(projectDetails, collections);
const actual = sut.getCollection(os);
// assert
expect(actual).to.equals(expected);
});
});
describe('ctor', () => {
describe('info', () => {
describe('projectDetails', () => {
it('sets as expected', () => {
// arrange
const expected = new ProjectInformationStub();
const expectedProjectDetails = new ProjectDetailsStub();
const collections = [new CategoryCollectionStub()];
// act
const sut = new Application(expected, collections);
const sut = new Application(expectedProjectDetails, collections);
// assert
expect(sut.info).to.equal(expected);
expect(sut.projectDetails).to.equal(expectedProjectDetails);
});
});
describe('collections', () => {
@@ -75,10 +75,10 @@ describe('Application', () => {
];
for (const testCase of testCases) {
it(testCase.name, () => {
const info = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
const collections = testCase.value;
// act
const act = () => new Application(info, collections);
const act = () => new Application(projectDetails, collections);
// assert
expect(act).to.throw(testCase.expectedError);
});
@@ -86,10 +86,10 @@ describe('Application', () => {
});
it('sets as expected', () => {
// arrange
const info = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
const expected = [new CategoryCollectionStub()];
// act
const sut = new Application(info, expected);
const sut = new Application(projectDetails, expected);
// assert
expect(sut.collections).to.equal(expected);
});
@@ -99,10 +99,10 @@ describe('Application', () => {
it('returns expected', () => {
// arrange
const expected = [OperatingSystem.Windows, OperatingSystem.macOS];
const info = new ProjectInformationStub();
const projectDetails = new ProjectDetailsStub();
const collections = expected.map((os) => new CategoryCollectionStub().withOs(os));
// act
const sut = new Application(info, collections);
const sut = new Application(projectDetails, collections);
const actual = sut.getSupportedOsList();
// assert
expect(actual).to.deep.equal(expected);

View File

@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { ProjectInformation } from '@/domain/ProjectInformation';
import { GitHubProjectDetails } from '@/domain/Project/GitHubProjectDetails';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
import { VersionStub } from '@tests/unit/shared/Stubs/VersionStub';
@@ -7,19 +7,19 @@ import { Version } from '@/domain/Version';
import { PropertyKeys } from '@/TypeHelpers';
import { SupportedOperatingSystem, AllSupportedOperatingSystems } from '@tests/shared/TestCases/SupportedOperatingSystems';
describe('ProjectInformation', () => {
describe('GitHubProjectDetails', () => {
describe('retrieval of property values', () => {
interface IInformationParsingTestCase {
interface PropertyTestScenario {
readonly description?: string;
readonly expectedValue: string;
readonly buildWithExpectedValue: (
builder: ProjectInformationBuilder,
builder: ProjectDetailsBuilder,
expected: string,
) => ProjectInformationBuilder;
readonly getActualValue: (sut: ProjectInformation) => string;
) => ProjectDetailsBuilder;
readonly getActualValue: (sut: GitHubProjectDetails) => string;
}
const propertyTestCases: {
readonly [K in PropertyKeys<ProjectInformation>]: readonly IInformationParsingTestCase[];
const propertyTestScenarios: {
readonly [K in PropertyKeys<GitHubProjectDetails>]: readonly PropertyTestScenario[];
} = {
name: [{
expectedValue: 'expected-app-name',
@@ -100,13 +100,13 @@ describe('ProjectInformation', () => {
getActualValue: (sut) => sut.releaseUrl,
}],
};
Object.entries(propertyTestCases).forEach(([propertyName, testList]) => {
Object.entries(propertyTestScenarios).forEach(([propertyName, testList]) => {
testList.forEach(({
description, buildWithExpectedValue, expectedValue, getActualValue,
}) => {
it(`${propertyName}${description ? ` (${description})` : ''}`, () => {
// arrange
const builder = new ProjectInformationBuilder();
const builder = new ProjectDetailsBuilder();
const sut = buildWithExpectedValue(builder, expectedValue).build();
// act
@@ -144,7 +144,7 @@ describe('ProjectInformation', () => {
it(`should return the expected download URL for ${OperatingSystem[operatingSystem]}`, () => {
// arrange
const { expected, version, repositoryUrl } = testScenarios[operatingSystem];
const sut = new ProjectInformationBuilder()
const sut = new ProjectDetailsBuilder()
.withVersion(new VersionStub(version))
.withRepositoryUrl(repositoryUrl)
.build();
@@ -156,7 +156,7 @@ describe('ProjectInformation', () => {
});
describe('should throw an error when provided with an invalid operating system', () => {
// arrange
const sut = new ProjectInformationBuilder()
const sut = new ProjectDetailsBuilder()
.build();
// act
const act = (os: OperatingSystem) => sut.getDownloadUrl(os);
@@ -168,7 +168,7 @@ describe('ProjectInformation', () => {
});
});
class ProjectInformationBuilder {
class ProjectDetailsBuilder {
private name = 'default-name';
private version: Version = new VersionStub();
@@ -179,33 +179,33 @@ class ProjectInformationBuilder {
private slogan = 'default-slogan';
public withName(name: string): ProjectInformationBuilder {
public withName(name: string): this {
this.name = name;
return this;
}
public withVersion(version: VersionStub): ProjectInformationBuilder {
public withVersion(version: VersionStub): this {
this.version = version;
return this;
}
public withSlogan(slogan: string): ProjectInformationBuilder {
public withSlogan(slogan: string): this {
this.slogan = slogan;
return this;
}
public withRepositoryUrl(repositoryUrl: string): ProjectInformationBuilder {
public withRepositoryUrl(repositoryUrl: string): this {
this.repositoryUrl = repositoryUrl;
return this;
}
public withHomepage(homepage: string): ProjectInformationBuilder {
public withHomepage(homepage: string): this {
this.homepage = homepage;
return this;
}
public build(): ProjectInformation {
return new ProjectInformation(
public build(): GitHubProjectDetails {
return new GitHubProjectDetails(
this.name,
this.version,
this.slogan,

View File

@@ -185,7 +185,7 @@ describe('NodeElectronSaveFileDialog', () => {
const saveFileCall = fileWriterStub.callHistory.find((c) => c.methodName === 'writeAndVerifyFile');
expect(saveFileCall).to.equal(undefined);
});
it('logs cancelation info', async () => {
it('logs cancelation', async () => {
// arrange
const expectedLogMessagePart = 'File save cancelled';
const logger = new LoggerStub();

View File

@@ -1,26 +1,26 @@
import { describe, it, expect } from 'vitest';
import { useApplication } from '@/presentation/components/Shared/Hooks/UseApplication';
import { ApplicationStub } from '@tests/unit/shared/Stubs/ApplicationStub';
import { ProjectInformationStub } from '@tests/unit/shared/Stubs/ProjectInformationStub';
import { ProjectDetailsStub } from '@tests/unit/shared/Stubs/ProjectDetailsStub';
describe('UseApplication', () => {
it('should return expected info', () => {
it('should return expected projectDetails', () => {
// arrange
const expectedInfo = new ProjectInformationStub()
.withName('expected-project-information');
const expectedProjectDetails = new ProjectDetailsStub()
.withName(`expected-${ProjectDetailsStub.name}`);
const application = new ApplicationStub()
.withProjectInformation(expectedInfo);
.withProjectDetails(expectedProjectDetails);
// act
const { info } = useApplication(application);
const { projectDetails } = useApplication(application);
// assert
expect(info).to.equal(expectedInfo);
expect(projectDetails).to.equal(expectedProjectDetails);
});
it('should return expected application', () => {
// arrange
const expectedApp = new ApplicationStub()
.withProjectInformation(
new ProjectInformationStub().withName('expected-application'),
.withProjectDetails(
new ProjectDetailsStub().withName('expected-application'),
);
// act
const { application } = useApplication(expectedApp);

View File

@@ -1,12 +1,12 @@
import { IApplication } from '@/domain/IApplication';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { IProjectInformation } from '@/domain/IProjectInformation';
import { ProjectInformationStub } from './ProjectInformationStub';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { ProjectDetailsStub } from './ProjectDetailsStub';
import { CategoryCollectionStub } from './CategoryCollectionStub';
export class ApplicationStub implements IApplication {
public info: IProjectInformation = new ProjectInformationStub();
public projectDetails: ProjectDetails = new ProjectDetailsStub();
public collections: ICategoryCollection[] = [];
@@ -24,8 +24,8 @@ export class ApplicationStub implements IApplication {
return this;
}
public withProjectInformation(info: IProjectInformation): this {
this.info = info;
public withProjectDetails(info: ProjectDetails): this {
this.projectDetails = info;
return this;
}

View File

@@ -1,5 +1,4 @@
import { IProjectInformation } from '@/domain/IProjectInformation';
import { ProjectInformation } from '@/domain/ProjectInformation';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { getEnumValues } from '@/application/Common/Enum';
import type { CollectionData } from '@/application/collections/';
@@ -10,7 +9,7 @@ import { CategoryCollectionStub } from './CategoryCollectionStub';
export class CategoryCollectionParserStub {
public readonly arguments = new Array<{
data: CollectionData,
info: ProjectInformation,
projectDetails: ProjectDetails,
}>();
private readonly returnValues = new Map<CollectionData, ICategoryCollection>();
@@ -24,8 +23,8 @@ export class CategoryCollectionParserStub {
}
public getStub(): CategoryCollectionParserType {
return (data: CollectionData, info: IProjectInformation): ICategoryCollection => {
this.arguments.push({ data, info });
return (data: CollectionData, projectDetails: ProjectDetails): ICategoryCollection => {
this.arguments.push({ data, projectDetails });
const foundReturnValue = this.returnValues.get(data);
if (foundReturnValue) {
return foundReturnValue;

View File

@@ -1,20 +1,22 @@
import { IProjectInformation } from '@/domain/IProjectInformation';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { ICodeSubstituter } from '@/application/Parser/ScriptingDefinition/ICodeSubstituter';
export class CodeSubstituterStub implements ICodeSubstituter {
private readonly scenarios = new Array<{
code: string, info: IProjectInformation, result: string }>();
code: string, projectDetails: ProjectDetails, result: string }>();
public substitute(code: string, info: IProjectInformation): string {
const scenario = this.scenarios.find((s) => s.code === code && s.info === info);
public substitute(code: string, projectDetails: ProjectDetails): string {
const scenario = this.scenarios.find(
(s) => s.code === code && s.projectDetails === projectDetails,
);
if (scenario) {
return scenario.result;
}
return `[CodeSubstituterStub] - code: ${code}`;
}
public setup(code: string, info: IProjectInformation, result: string) {
this.scenarios.push({ code, info, result });
public setup(code: string, projectDetails: ProjectDetails, result: string) {
this.scenarios.push({ code, projectDetails, result });
return this;
}
}

View File

@@ -0,0 +1,22 @@
import { parseProjectDetails } from '@/application/Parser/ProjectDetailsParser';
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { ProjectDetailsStub } from './ProjectDetailsStub';
export class ProjectDetailsParserStub {
public readonly arguments = new Array<IAppMetadata | undefined>();
private returnValue: ProjectDetails = new ProjectDetailsStub();
public withReturnValue(value: ProjectDetails): this {
this.returnValue = value;
return this;
}
public getStub(): typeof parseProjectDetails {
return (metadata) => {
this.arguments.push(metadata);
return this.returnValue;
};
}
}

View File

@@ -1,8 +1,8 @@
import { IProjectInformation } from '@/domain/IProjectInformation';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { Version } from '@/domain/Version';
import { VersionStub } from './VersionStub';
export class ProjectInformationStub implements IProjectInformation {
export class ProjectDetailsStub implements ProjectDetails {
public name = 'stub-name';
public version = new VersionStub();
@@ -21,37 +21,37 @@ export class ProjectInformationStub implements IProjectInformation {
public slogan = 'stub-slogan';
public withName(name: string): ProjectInformationStub {
public withName(name: string): this {
this.name = name;
return this;
}
public withVersion(version: Version): ProjectInformationStub {
public withVersion(version: Version): this {
this.version = version;
return this;
}
public withRepositoryUrl(repositoryUrl: string): ProjectInformationStub {
public withRepositoryUrl(repositoryUrl: string): this {
this.repositoryUrl = repositoryUrl;
return this;
}
public withHomepageUrl(homepageUrl: string): ProjectInformationStub {
public withHomepageUrl(homepageUrl: string): this {
this.homepage = homepageUrl;
return this;
}
public withFeedbackUrl(feedbackUrl: string): ProjectInformationStub {
public withFeedbackUrl(feedbackUrl: string): this {
this.feedbackUrl = feedbackUrl;
return this;
}
public withReleaseUrl(releaseUrl: string): ProjectInformationStub {
public withReleaseUrl(releaseUrl: string): this {
this.releaseUrl = releaseUrl;
return this;
}
public withRepositoryWebUrl(repositoryWebUrl: string): ProjectInformationStub {
public withRepositoryWebUrl(repositoryWebUrl: string): this {
this.repositoryWebUrl = repositoryWebUrl;
return this;
}

View File

@@ -1,22 +0,0 @@
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
import { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
import { IProjectInformation } from '@/domain/IProjectInformation';
import { ProjectInformationStub } from './ProjectInformationStub';
export class ProjectInformationParserStub {
public readonly arguments = new Array<IAppMetadata | undefined>();
private returnValue: IProjectInformation = new ProjectInformationStub();
public withReturnValue(value: IProjectInformation): this {
this.returnValue = value;
return this;
}
public getStub(): typeof parseProjectInformation {
return (metadata) => {
this.arguments.push(metadata);
return this.returnValue;
};
}
}

View File

@@ -13,7 +13,7 @@ export class UseApplicationStub {
public get(): ReturnType<typeof useApplication> {
return {
application: this.application,
info: this.application.info,
projectDetails: this.application.projectDetails,
};
}
}