From 4dd8e1158eefd40414fbc3e3f046be3f9a0ad606 Mon Sep 17 00:00:00 2001 From: undergroundwires Date: Thu, 12 Dec 2024 11:35:14 +0100 Subject: [PATCH] Bump to TypeScript 5.6 /w strictPropertyInit This commit migrates from TypeScript 5.5 to 5.6 with `strictPropertyInitialization` flag enabled for stricter quality controls. Supporting changes: - Move GitHubProjectDetails to application layer for better SRP - Convert mutable fields to immutable states with getters - Initialize test stubs with default values - Split complex state updates into pure functions - Improve error messages in test assertions - Upgrade `nanoid` dependency to avoid security issues --- package-lock.json | 59 ++++----- package.json | 2 +- src/application/Context/ApplicationContext.ts | 13 +- .../Context/State/Code/ApplicationCode.ts | 44 +++++-- src/application/Parser/ApplicationParser.ts | 2 +- .../Function/SharedFunctionsParser.ts | 5 +- .../Project/GitHubProjectDetailsFactory.ts | 58 +++++++++ .../Parser/Project/ProjectDetailsFactory.ts | 14 +++ .../Parser/Project/ProjectDetailsParser.ts | 30 +++++ .../Parser/ProjectDetailsParser.ts | 33 ------ src/domain/Project/GitHubProjectDetails.ts | 64 ---------- .../Node/Hierarchy/TreeNodeHierarchy.ts | 2 +- .../ManualUpdater/ManualUpdateCoordinator.ts | 18 +-- .../Context/ApplicationContext.spec.ts | 10 +- .../Generation/UserScriptGenerator.spec.ts | 62 +++++++--- .../Parser/ApplicationParser.spec.ts | 2 +- .../GitHubProjectDetailsFactory.spec.ts} | 112 ++++++------------ .../Parser/ProjectDetailsParser.spec.ts | 58 ++++----- .../unit/shared/Stubs/CodeChangedEventStub.ts | 15 ++- tests/unit/shared/Stubs/EnumParserStub.ts | 4 +- .../unit/shared/Stubs/FunctionCallDataStub.ts | 2 +- tests/unit/shared/Stubs/FunctionCallStub.ts | 2 +- tests/unit/shared/Stubs/FunctionDataStub.ts | 15 ++- .../Stubs/ParameterDefinitionDataStub.ts | 2 +- .../Stubs/ProjectDetailsParametersStub.ts | 41 +++++++ .../shared/Stubs/ProjectDetailsParserStub.ts | 2 +- tests/unit/shared/Stubs/ProjectDetailsStub.ts | 5 + tests/unit/shared/Stubs/ScriptDataStub.ts | 13 +- tests/unit/shared/Stubs/SelectedScriptStub.ts | 1 + .../TreeNodeStateChangedEmittedEventStub.ts | 7 +- .../Stubs/TreeNodeStateTransactionStub.ts | 3 +- tsconfig.json | 1 + 32 files changed, 391 insertions(+), 310 deletions(-) create mode 100644 src/application/Parser/Project/GitHubProjectDetailsFactory.ts create mode 100644 src/application/Parser/Project/ProjectDetailsFactory.ts create mode 100644 src/application/Parser/Project/ProjectDetailsParser.ts delete mode 100644 src/application/Parser/ProjectDetailsParser.ts delete mode 100644 src/domain/Project/GitHubProjectDetails.ts rename tests/unit/{domain/Project/GitHubProjectDetails.spec.ts => application/Parser/Project/GitHubProjectDetailsFactory.spec.ts} (69%) create mode 100644 tests/unit/shared/Stubs/ProjectDetailsParametersStub.ts diff --git a/package-lock.json b/package-lock.json index c8881329b..802acd6b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "privacy.sexy", - "version": "0.13.6", + "version": "0.13.7", "hasInstallScript": true, "dependencies": { "@floating-ui/vue": "^1.1.5", @@ -53,7 +53,7 @@ "start-server-and-test": "^2.0.8", "terser": "^5.36.0", "tslib": "^2.8.1", - "typescript": "~5.5.4", + "typescript": "~5.6.3", "vite": "^5.4.11", "vitest": "^2.1.8", "vue-tsc": "^2.1.10", @@ -6526,10 +6526,11 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -12771,12 +12772,13 @@ ] }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -13096,15 +13098,16 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -16792,9 +16795,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -22548,9 +22551,9 @@ } }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -27009,12 +27012,12 @@ "dev": true }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, @@ -27250,9 +27253,9 @@ "dev": true }, "nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==" }, "natural-compare": { "version": "1.4.0", @@ -29893,9 +29896,9 @@ "dev": true }, "typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "devOptional": true }, "uc.micro": { diff --git a/package.json b/package.json index 993a24b17..e5eab3707 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "start-server-and-test": "^2.0.8", "terser": "^5.36.0", "tslib": "^2.8.1", - "typescript": "~5.5.4", + "typescript": "~5.6.3", "vite": "^5.4.11", "vitest": "^2.1.8", "vue-tsc": "^2.1.10", diff --git a/src/application/Context/ApplicationContext.ts b/src/application/Context/ApplicationContext.ts index 7c9cd289b..000cfcb20 100644 --- a/src/application/Context/ApplicationContext.ts +++ b/src/application/Context/ApplicationContext.ts @@ -14,7 +14,9 @@ export class ApplicationContext implements IApplicationContext { public collection: ICategoryCollection; - public currentOs: OperatingSystem; + public get currentOs(): OperatingSystem { + return this.collection.os; + } public get state(): ICategoryCollectionState { return this.getState(this.collection.os); @@ -26,7 +28,7 @@ export class ApplicationContext implements IApplicationContext { public readonly app: IApplication, initialContext: OperatingSystem, ) { - this.setContext(initialContext); + this.collection = this.getCollection(initialContext); this.states = initializeStates(app); } @@ -38,14 +40,13 @@ export class ApplicationContext implements IApplicationContext { newState: this.getState(os), oldState: this.getState(this.currentOs), }; - this.setContext(os); + this.collection = this.getCollection(os); this.contextChanged.notify(event); } - private setContext(os: OperatingSystem): void { + private getCollection(os: OperatingSystem): ICategoryCollection { validateOperatingSystem(os, this.app); - this.collection = this.app.getCollection(os); - this.currentOs = os; + return this.app.getCollection(os); } private getState(os: OperatingSystem): ICategoryCollectionState { diff --git a/src/application/Context/State/Code/ApplicationCode.ts b/src/application/Context/State/Code/ApplicationCode.ts index e376aadb4..75d16ea02 100644 --- a/src/application/Context/State/Code/ApplicationCode.ts +++ b/src/application/Context/State/Code/ApplicationCode.ts @@ -12,27 +12,51 @@ import type { IApplicationCode } from './IApplicationCode'; export class ApplicationCode implements IApplicationCode { public readonly changed = new EventSource(); - public current: string; + private state: CodeGenerationResult; - private scriptPositions = new Map(); + public get current(): string { + return this.state.code; + } + + private get scriptPositions(): Map { + return this.state.scriptPositions; + } constructor( selection: ReadonlyScriptSelection, private readonly scriptingDefinition: IScriptingDefinition, private readonly generator: IUserScriptGenerator = new UserScriptGenerator(), ) { - this.setCode(selection.selectedScripts); - selection.changed.on((scripts) => { - this.setCode(scripts); + this.state = this.generateCode(selection.selectedScripts); + selection.changed.on((newScripts) => { + const oldScripts = Array.from(this.scriptPositions.keys()); + this.state = this.generateCode(newScripts); + this.notifyCodeChange(oldScripts, this.state); }); } - private setCode(scripts: ReadonlyArray): void { - const oldScripts = Array.from(this.scriptPositions.keys()); + private generateCode(scripts: readonly SelectedScript[]): CodeGenerationResult { const code = this.generator.buildCode(scripts, this.scriptingDefinition); - this.current = code.code; - this.scriptPositions = code.scriptPositions; - const event = new CodeChangedEvent(code.code, oldScripts, code.scriptPositions); + return { + code: code.code, + scriptPositions: code.scriptPositions, + }; + } + + private notifyCodeChange( + oldScripts: readonly SelectedScript[], + newCode: CodeGenerationResult, + ): void { + const event = new CodeChangedEvent( + newCode.code, + oldScripts, + newCode.scriptPositions, + ); this.changed.notify(event); } } + +interface CodeGenerationResult { + readonly code: string; + readonly scriptPositions: Map; +} diff --git a/src/application/Parser/ApplicationParser.ts b/src/application/Parser/ApplicationParser.ts index 27c4c08ec..80512dd3d 100644 --- a/src/application/Parser/ApplicationParser.ts +++ b/src/application/Parser/ApplicationParser.ts @@ -3,7 +3,7 @@ import type { IApplication } from '@/domain/IApplication'; import WindowsData from '@/application/collections/windows.yaml'; import MacOsData from '@/application/collections/macos.yaml'; import LinuxData from '@/application/collections/linux.yaml'; -import { parseProjectDetails, type ProjectDetailsParser } from '@/application/Parser/ProjectDetailsParser'; +import { parseProjectDetails, type ProjectDetailsParser } from '@/application/Parser/Project/ProjectDetailsParser'; import { Application } from '@/domain/Application'; import { parseCategoryCollection, type CategoryCollectionParser } from './CategoryCollectionParser'; import { createTypeValidator, type TypeValidator } from './Common/TypeValidator'; diff --git a/src/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser.ts b/src/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser.ts index 23f7f05cb..02128c0f8 100644 --- a/src/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser.ts +++ b/src/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser.ts @@ -126,11 +126,12 @@ function parseParameterWithContextualError( } function hasCode(data: FunctionData): data is CodeFunctionData { - return (data as CodeInstruction).code !== undefined; + const { code } = (data as CodeInstruction); + return !isNullOrUndefined(code) && code !== ''; } function hasCall(data: FunctionData): data is CallFunctionData { - return (data as CallInstruction).call !== undefined; + return !isNullOrUndefined((data as CallInstruction).call); } function ensureValidFunctions(functions: readonly FunctionData[]) { diff --git a/src/application/Parser/Project/GitHubProjectDetailsFactory.ts b/src/application/Parser/Project/GitHubProjectDetailsFactory.ts new file mode 100644 index 000000000..ba345bf24 --- /dev/null +++ b/src/application/Parser/Project/GitHubProjectDetailsFactory.ts @@ -0,0 +1,58 @@ +import { assertInRange } from '@/application/Common/Enum'; +import { OperatingSystem } from '@/domain/OperatingSystem'; +import type { ProjectDetailsParameters, ProjectDetailsFactory } from './ProjectDetailsFactory'; + +export const createGitHubProjectDetails: ProjectDetailsFactory = (parameters) => { + validateParameters(parameters); + const githubRepositoryWebUrl = getWebUrl(parameters.repositoryUrl); + return { + name: parameters.name, + version: parameters.version, + slogan: parameters.slogan, + repositoryUrl: parameters.repositoryUrl, + homepage: parameters.homepage, + repositoryWebUrl: githubRepositoryWebUrl, + feedbackUrl: `${githubRepositoryWebUrl}/issues`, + releaseUrl: `${githubRepositoryWebUrl}/releases/tag/${parameters.version}`, + getDownloadUrl: (os) => { + const fileName = getFileName(os, parameters.version.toString()); + return `${githubRepositoryWebUrl}/releases/download/${parameters.version}/${fileName}`; + }, + }; +}; + +function validateParameters(parameters: ProjectDetailsParameters) { + if (!parameters.name) { + throw new Error('name is undefined'); + } + if (!parameters.slogan) { + throw new Error('undefined slogan'); + } + if (!parameters.repositoryUrl) { + throw new Error('repositoryUrl is undefined'); + } + if (!parameters.homepage) { + throw new Error('homepage is undefined'); + } +} + +function getWebUrl(gitUrl: string) { + if (gitUrl.endsWith('.git')) { + return gitUrl.substring(0, gitUrl.length - 4); + } + return gitUrl; +} + +function getFileName(os: OperatingSystem, version: string): string { + assertInRange(os, OperatingSystem); + switch (os) { + case OperatingSystem.Linux: + return `privacy.sexy-${version}.AppImage`; + case OperatingSystem.macOS: + return `privacy.sexy-${version}.dmg`; + case OperatingSystem.Windows: + return `privacy.sexy-Setup-${version}.exe`; + default: + throw new RangeError(`Unsupported os: ${OperatingSystem[os]}`); + } +} diff --git a/src/application/Parser/Project/ProjectDetailsFactory.ts b/src/application/Parser/Project/ProjectDetailsFactory.ts new file mode 100644 index 000000000..5912bfb15 --- /dev/null +++ b/src/application/Parser/Project/ProjectDetailsFactory.ts @@ -0,0 +1,14 @@ +import type { ProjectDetails } from '@/domain/Project/ProjectDetails'; +import type { Version } from '@/domain/Version'; + +export interface ProjectDetailsParameters { + readonly name: string, + readonly version: Version, + readonly slogan: string, + readonly repositoryUrl: string, + readonly homepage: string, +} + +export type ProjectDetailsFactory = ( + args: ProjectDetailsParameters, +) => ProjectDetails; diff --git a/src/application/Parser/Project/ProjectDetailsParser.ts b/src/application/Parser/Project/ProjectDetailsParser.ts new file mode 100644 index 000000000..5ebe29f81 --- /dev/null +++ b/src/application/Parser/Project/ProjectDetailsParser.ts @@ -0,0 +1,30 @@ +import type { ProjectDetails } from '@/domain/Project/ProjectDetails'; +import type { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata'; +import { Version } from '@/domain/Version'; +import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory'; +import { createGitHubProjectDetails } from './GitHubProjectDetailsFactory'; +import type { ProjectDetailsFactory } from './ProjectDetailsFactory'; + +export const parseProjectDetails: MetadataProjectDetailsParser = ( + metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance, + createProjectDetails: ProjectDetailsFactory = createGitHubProjectDetails, +) => { + return createProjectDetails({ + name: metadata.name, + version: new Version( + metadata.version, + ), + slogan: metadata.slogan, + repositoryUrl: metadata.repositoryUrl, + homepage: metadata.homepageUrl, + }); +}; + +export type MetadataProjectDetailsParser = ProjectDetailsParser & (( + metadata?: IAppMetadata, + createProjectDetails?: ProjectDetailsFactory, +) => ProjectDetails); + +export interface ProjectDetailsParser { + (): ProjectDetails; +} diff --git a/src/application/Parser/ProjectDetailsParser.ts b/src/application/Parser/ProjectDetailsParser.ts deleted file mode 100644 index 97749b4e2..000000000 --- a/src/application/Parser/ProjectDetailsParser.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { ProjectDetails } from '@/domain/Project/ProjectDetails'; -import { GitHubProjectDetails } from '@/domain/Project/GitHubProjectDetails'; -import type { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata'; -import { Version } from '@/domain/Version'; -import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory'; -import type { ConstructorArguments } from '@/TypeHelpers'; - -export function -parseProjectDetails( - metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance, - createProjectDetails: ProjectDetailsFactory = ( - ...args - ) => new GitHubProjectDetails(...args), -): ProjectDetails { - const version = new Version( - metadata.version, - ); - return createProjectDetails( - metadata.name, - version, - metadata.slogan, - metadata.repositoryUrl, - metadata.homepageUrl, - ); -} - -export interface ProjectDetailsParser { - (): ProjectDetails; -} - -export type ProjectDetailsFactory = ( - ...args: ConstructorArguments -) => ProjectDetails; diff --git a/src/domain/Project/GitHubProjectDetails.ts b/src/domain/Project/GitHubProjectDetails.ts deleted file mode 100644 index 05dae56ea..000000000 --- a/src/domain/Project/GitHubProjectDetails.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { assertInRange } from '@/application/Common/Enum'; -import { OperatingSystem } from '../OperatingSystem'; -import { Version } from '../Version'; -import type { ProjectDetails } from './ProjectDetails'; - -export class GitHubProjectDetails implements ProjectDetails { - public readonly repositoryWebUrl: string; - - constructor( - public readonly name: string, - public readonly version: Version, - public readonly slogan: string, - public readonly repositoryUrl: string, - public readonly homepage: string, - ) { - if (!name) { - throw new Error('name is undefined'); - } - if (!slogan) { - throw new Error('undefined slogan'); - } - if (!repositoryUrl) { - throw new Error('repositoryUrl is undefined'); - } - if (!homepage) { - throw new Error('homepage is undefined'); - } - this.repositoryWebUrl = getWebUrl(this.repositoryUrl); - } - - public getDownloadUrl(os: OperatingSystem): string { - const fileName = getFileName(os, this.version.toString()); - return `${this.repositoryWebUrl}/releases/download/${this.version}/${fileName}`; - } - - public get feedbackUrl(): string { - return `${this.repositoryWebUrl}/issues`; - } - - public get releaseUrl(): string { - return `${this.repositoryWebUrl}/releases/tag/${this.version}`; - } -} - -function getWebUrl(gitUrl: string) { - if (gitUrl.endsWith('.git')) { - return gitUrl.substring(0, gitUrl.length - 4); - } - return gitUrl; -} - -function getFileName(os: OperatingSystem, version: string): string { - assertInRange(os, OperatingSystem); - switch (os) { - case OperatingSystem.Linux: - return `privacy.sexy-${version}.AppImage`; - case OperatingSystem.macOS: - return `privacy.sexy-${version}.dmg`; - case OperatingSystem.Windows: - return `privacy.sexy-Setup-${version}.exe`; - default: - throw new RangeError(`Unsupported os: ${OperatingSystem[os]}`); - } -} diff --git a/src/presentation/components/Scripts/View/Tree/TreeView/Node/Hierarchy/TreeNodeHierarchy.ts b/src/presentation/components/Scripts/View/Tree/TreeView/Node/Hierarchy/TreeNodeHierarchy.ts index c15fe48ab..6315dc403 100644 --- a/src/presentation/components/Scripts/View/Tree/TreeView/Node/Hierarchy/TreeNodeHierarchy.ts +++ b/src/presentation/components/Scripts/View/Tree/TreeView/Node/Hierarchy/TreeNodeHierarchy.ts @@ -19,7 +19,7 @@ export class TreeNodeHierarchy implements HierarchyAccess { return this.children.length > 0; } - public children: readonly TreeNode[]; + public children: readonly TreeNode[] = []; public setChildren(children: readonly TreeNode[]): void { this.children = children; diff --git a/src/presentation/electron/main/Update/ManualUpdater/ManualUpdateCoordinator.ts b/src/presentation/electron/main/Update/ManualUpdater/ManualUpdateCoordinator.ts index a0396d65f..54e0a9494 100644 --- a/src/presentation/electron/main/Update/ManualUpdater/ManualUpdateCoordinator.ts +++ b/src/presentation/electron/main/Update/ManualUpdater/ManualUpdateCoordinator.ts @@ -1,8 +1,7 @@ import { shell } from 'electron'; import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger'; -import { GitHubProjectDetails } from '@/domain/Project/GitHubProjectDetails'; import { Version } from '@/domain/Version'; -import { parseProjectDetails } from '@/application/Parser/ProjectDetailsParser'; +import { parseProjectDetails } from '@/application/Parser/Project/ProjectDetailsParser'; import { OperatingSystem } from '@/domain/OperatingSystem'; import { UpdateProgressBar } from '../ProgressBar/UpdateProgressBar'; import { @@ -16,6 +15,7 @@ import { checkIntegrity } from './Integrity'; import { startInstallation } from './Installer'; import { clearUpdateInstallationFiles } from './InstallationFiles/InstallationFileCleaner'; import type { UpdateInfo } from 'electron-updater'; +import { createGitHubProjectDetails } from '@/application/Parser/Project/GitHubProjectDetailsFactory'; export function requiresManualUpdate( nodePlatform: string = process.platform, @@ -157,13 +157,13 @@ interface UpdateUrls { function getRemoteUpdateUrls(targetVersion: string): UpdateUrls { const existingProject = parseProjectDetails(); - const targetProject = new GitHubProjectDetails( - existingProject.name, - new Version(targetVersion), - existingProject.slogan, - existingProject.repositoryUrl, - existingProject.homepage, - ); + const targetProject = createGitHubProjectDetails({ + name: existingProject.name, + version: new Version(targetVersion), + slogan: existingProject.slogan, + repositoryUrl: existingProject.repositoryUrl, + homepage: existingProject.homepage, + }); return { releaseUrl: targetProject.releaseUrl, downloadUrl: targetProject.getDownloadUrl(OperatingSystem.macOS), diff --git a/tests/unit/application/Context/ApplicationContext.spec.ts b/tests/unit/application/Context/ApplicationContext.spec.ts index 03f28406d..92612cb76 100644 --- a/tests/unit/application/Context/ApplicationContext.spec.ts +++ b/tests/unit/application/Context/ApplicationContext.spec.ts @@ -247,16 +247,16 @@ describe('ApplicationContext', () => { class ObservableApplicationContextFactory { private static DefaultOs = OperatingSystem.Windows; - public app: IApplication; + public app: IApplication = new ApplicationStub() + .withCollection( + new CategoryCollectionStub() + .withOs(ObservableApplicationContextFactory.DefaultOs), + ); public firedEvents = new Array(); private initialOs = ObservableApplicationContextFactory.DefaultOs; - public constructor() { - this.withAppContainingCollections(ObservableApplicationContextFactory.DefaultOs); - } - public withAppContainingCollections( ...oses: OperatingSystem[] ): this { diff --git a/tests/unit/application/Context/State/Code/Generation/UserScriptGenerator.spec.ts b/tests/unit/application/Context/State/Code/Generation/UserScriptGenerator.spec.ts index 440dc112c..e04b65024 100644 --- a/tests/unit/application/Context/State/Code/Generation/UserScriptGenerator.spec.ts +++ b/tests/unit/application/Context/State/Code/Generation/UserScriptGenerator.spec.ts @@ -8,6 +8,8 @@ import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTest import { expectExists } from '@tests/shared/Assertions/ExpectExists'; import { SelectedScriptStub } from '@tests/unit/shared/Stubs/SelectedScriptStub'; import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript'; +import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage'; +import { indentText } from '@/application/Common/Text/IndentText'; describe('UserScriptGenerator', () => { describe('scriptingDefinition', () => { @@ -158,21 +160,25 @@ describe('UserScriptGenerator', () => { const definition = new ScriptingDefinitionStub() .withStartCode('First line\nSecond line'); describe('single script', () => { - const testCases = [ + const testCases: readonly { + readonly description: string; + readonly scriptCode: string; + readonly codeLines: number; + }[] = [ { - name: 'single-lined', + description: 'single-lined', scriptCode: 'only line', codeLines: 1, }, { - name: 'multi-lined', + description: 'multi-lined', scriptCode: 'first line\nsecond line', codeLines: 2, }, ]; const sut = new UserScriptGenerator(); for (const testCase of testCases) { - it(testCase.name, () => { + it(testCase.description, () => { const expectedStartLine = totalStartCodeLines + 1 // empty line code begin + 1; // code begin @@ -200,32 +206,54 @@ describe('UserScriptGenerator', () => { const selectedScripts = [ new ScriptStub('1').withCode('only line'), new ScriptStub('2').withCode('first line\nsecond line'), - ].map((s) => s.toSelectedScript()); - const expectedFirstScriptStart = totalStartCodeLines + ] + .map((s) => s.toSelectedScript()) + .map((s) => s.withRevert(false)); + const expectedScript1Start = totalStartCodeLines + 1 // empty line code begin + 1; // code begin - const expectedFirstScriptEnd = expectedFirstScriptStart + const expectedScript1End = expectedScript1Start + totalFunctionNameLines + 1; // total code lines - const expectedSecondScriptStart = expectedFirstScriptEnd + const expectedScript2Start = expectedScript1End + 1 // code end hyphens + 1 // new line + 1; // code begin - const expectedSecondScriptEnd = expectedSecondScriptStart + const expectedScript2End = expectedScript2Start + totalFunctionNameLines + 2; // total lines of second script // act const actual = sut.buildCode(selectedScripts, definition); // assert - const firstPosition = actual.scriptPositions.get(selectedScripts[0]); - const secondPosition = actual.scriptPositions.get(selectedScripts[1]); + const actualPosition1 = actual.scriptPositions.get(selectedScripts[0]); + const actualPosition2 = actual.scriptPositions.get(selectedScripts[1]); + const assertionContext: readonly string[] = [ + 'Given scripts:', + indentText(JSON.stringify(selectedScripts)), + 'Generated code:', + indentText(actual.code), + 'Actual script positions:', + indentText(JSON.stringify(Array.from(actual.scriptPositions.values()))), + ]; expect(actual.scriptPositions.size).to.equal(2); - expectExists(firstPosition); - expect(expectedFirstScriptStart).to.equal(firstPosition.startLine, 'Unexpected start line position (first script)'); - expect(expectedFirstScriptEnd).to.equal(firstPosition.endLine, 'Unexpected end line position (first script)'); - expectExists(secondPosition); - expect(expectedSecondScriptStart).to.equal(secondPosition.startLine, 'Unexpected start line position (second script)'); - expect(expectedSecondScriptEnd).to.equal(secondPosition.endLine, 'Unexpected end line position (second script)'); + expectExists(actualPosition1); + expect(expectedScript1Start).to.equal(actualPosition1.startLine, formatAssertionMessage([ + 'Unexpected start line position (first script)', + ...assertionContext, + ])); + expect(expectedScript1End).to.equal(actualPosition1.endLine, formatAssertionMessage([ + 'Unexpected end line position (first script)', + ...assertionContext, + ])); + expectExists(actualPosition2); + expect(expectedScript2Start).to.equal(actualPosition2.startLine, formatAssertionMessage([ + 'Unexpected start line position (second script)', + ...assertionContext, + ])); + expect(expectedScript2End).to.equal(actualPosition2.endLine, formatAssertionMessage([ + 'Unexpected end line position (second script)', + ...assertionContext, + ])); }); }); }); diff --git a/tests/unit/application/Parser/ApplicationParser.spec.ts b/tests/unit/application/Parser/ApplicationParser.spec.ts index 58bdbd07b..efdf933d4 100644 --- a/tests/unit/application/Parser/ApplicationParser.spec.ts +++ b/tests/unit/application/Parser/ApplicationParser.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import type { CollectionData } from '@/application/collections/'; -import { parseProjectDetails } from '@/application/Parser/ProjectDetailsParser'; +import { parseProjectDetails } from '@/application/Parser/Project/ProjectDetailsParser'; import { parseApplication } from '@/application/Parser/ApplicationParser'; import WindowsData from '@/application/collections/windows.yaml'; import MacOsData from '@/application/collections/macos.yaml'; diff --git a/tests/unit/domain/Project/GitHubProjectDetails.spec.ts b/tests/unit/application/Parser/Project/GitHubProjectDetailsFactory.spec.ts similarity index 69% rename from tests/unit/domain/Project/GitHubProjectDetails.spec.ts rename to tests/unit/application/Parser/Project/GitHubProjectDetailsFactory.spec.ts index aca02eb99..55e7b736d 100644 --- a/tests/unit/domain/Project/GitHubProjectDetails.spec.ts +++ b/tests/unit/application/Parser/Project/GitHubProjectDetailsFactory.spec.ts @@ -1,100 +1,103 @@ import { describe, it, expect } from 'vitest'; -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'; -import { Version } from '@/domain/Version'; import type { PropertyKeys } from '@/TypeHelpers'; import { type SupportedOperatingSystem, AllSupportedOperatingSystems } from '@tests/shared/TestCases/SupportedOperatingSystems'; +import type { ProjectDetailsParameters } from '@/application/Parser/Project/ProjectDetailsFactory'; +import { createGitHubProjectDetails } from '@/application/Parser/Project/GitHubProjectDetailsFactory'; +import { ProjectDetailsParametersStub } from '@tests/unit/shared/Stubs/ProjectDetailsParametersStub'; +import type { ProjectDetails } from '@/domain/Project/ProjectDetails'; -describe('GitHubProjectDetails', () => { +describe('GitHubProjectDetailsFactory', () => { describe('retrieval of property values', () => { interface PropertyTestScenario { readonly description?: string; readonly expectedValue: string; - readonly buildWithExpectedValue: ( - builder: ProjectDetailsBuilder, + readonly prepareParams: ( + params: ProjectDetailsParametersStub, expected: string, - ) => ProjectDetailsBuilder; - readonly getActualValue: (sut: GitHubProjectDetails) => string; + ) => ProjectDetailsParameters; + readonly getActualValue: (sut: ProjectDetails) => string; } const propertyTestScenarios: { - readonly [K in PropertyKeys]: readonly PropertyTestScenario[]; + readonly [K in PropertyKeys]: + readonly PropertyTestScenario[]; } = { name: [{ expectedValue: 'expected-app-name', - buildWithExpectedValue: (builder, expected) => builder + prepareParams: (params, expected) => params .withName(expected), getActualValue: (sut) => sut.name, }], version: [{ expectedValue: '0.11.3', - buildWithExpectedValue: (builder, expected) => builder + prepareParams: (params, expected) => params .withVersion(new VersionStub(expected)), getActualValue: (sut) => sut.version.toString(), }], slogan: [{ expectedValue: 'expected-slogan', - buildWithExpectedValue: (builder, expected) => builder + prepareParams: (params, expected) => params .withSlogan(expected), getActualValue: (sut) => sut.slogan, }], repositoryUrl: [{ description: 'without `.git` suffix', expectedValue: 'expected-repository-url', - buildWithExpectedValue: (builder, expected) => builder + prepareParams: (builder, expected) => builder .withRepositoryUrl(expected), getActualValue: (sut) => sut.repositoryUrl, }, { description: 'with `.git` suffix', expectedValue: 'expected-repository-url', - buildWithExpectedValue: (builder, expected) => builder + prepareParams: (builder, expected) => builder .withRepositoryUrl(expected), getActualValue: (sut) => sut.repositoryUrl, }], repositoryWebUrl: [{ description: 'without `.git` suffix', expectedValue: 'expected-repository-url', - buildWithExpectedValue: (builder, expected) => builder + prepareParams: (params, expected) => params .withRepositoryUrl(expected), getActualValue: (sut) => sut.repositoryWebUrl, }, { description: 'with `.git` suffix', expectedValue: 'expected-repository-url', - buildWithExpectedValue: (builder, expected) => builder + prepareParams: (params, expected) => params .withRepositoryUrl(`${expected}.git`), getActualValue: (sut) => sut.repositoryWebUrl, }], homepage: [{ expectedValue: 'expected-homepage', - buildWithExpectedValue: (builder, expected) => builder + prepareParams: (params, expected) => params .withHomepage(expected), getActualValue: (sut) => sut.homepage, }], feedbackUrl: [{ description: 'without `.git` suffix', expectedValue: 'https://github.com/undergroundwires/privacy.sexy/issues', - buildWithExpectedValue: (builder) => builder + prepareParams: (params) => params .withRepositoryUrl('https://github.com/undergroundwires/privacy.sexy'), getActualValue: (sut) => sut.feedbackUrl, }, { description: 'with `.git` suffix', expectedValue: 'https://github.com/undergroundwires/privacy.sexy/issues', - buildWithExpectedValue: (builder) => builder + prepareParams: (params) => params .withRepositoryUrl('https://github.com/undergroundwires/privacy.sexy.git'), getActualValue: (sut) => sut.feedbackUrl, }], releaseUrl: [{ description: 'without `.git` suffix', expectedValue: 'https://github.com/undergroundwires/privacy.sexy/releases/tag/0.7.2', - buildWithExpectedValue: (builder) => builder + prepareParams: (params) => params .withRepositoryUrl('https://github.com/undergroundwires/privacy.sexy') .withVersion(new VersionStub('0.7.2')), getActualValue: (sut) => sut.releaseUrl, }, { description: 'with `.git` suffix', expectedValue: 'https://github.com/undergroundwires/privacy.sexy/releases/tag/0.7.2', - buildWithExpectedValue: (builder) => builder + prepareParams: (params) => params .withRepositoryUrl('https://github.com/undergroundwires/privacy.sexy.git') .withVersion(new VersionStub('0.7.2')), getActualValue: (sut) => sut.releaseUrl, @@ -102,14 +105,17 @@ describe('GitHubProjectDetails', () => { }; Object.entries(propertyTestScenarios).forEach(([propertyName, testList]) => { testList.forEach(({ - description, buildWithExpectedValue, expectedValue, getActualValue, + description, prepareParams, expectedValue, getActualValue, }) => { it(`${propertyName}${description ? ` (${description})` : ''}`, () => { // arrange - const builder = new ProjectDetailsBuilder(); - const sut = buildWithExpectedValue(builder, expectedValue).build(); + const params = prepareParams( + new ProjectDetailsParametersStub(), + expectedValue, + ); // act + const sut = create(() => params); const actual = getActualValue(sut); // assert @@ -144,10 +150,9 @@ describe('GitHubProjectDetails', () => { it(`should return the expected download URL for ${OperatingSystem[operatingSystem]}`, () => { // arrange const { expected, version, repositoryUrl } = testScenarios[operatingSystem]; - const sut = new ProjectDetailsBuilder() + const sut = create((params) => params .withVersion(new VersionStub(version)) - .withRepositoryUrl(repositoryUrl) - .build(); + .withRepositoryUrl(repositoryUrl)); // act const actual = sut.getDownloadUrl(operatingSystem); // assert @@ -156,8 +161,7 @@ describe('GitHubProjectDetails', () => { }); describe('should throw an error when provided with an invalid operating system', () => { // arrange - const sut = new ProjectDetailsBuilder() - .build(); + const sut = create(); // act const act = (os: OperatingSystem) => sut.getDownloadUrl(os); // assert @@ -168,49 +172,11 @@ describe('GitHubProjectDetails', () => { }); }); -class ProjectDetailsBuilder { - private name = 'default-name'; - - private version: Version = new VersionStub(); - - private repositoryUrl = 'default-repository-url'; - - private homepage = 'default-homepage'; - - private slogan = 'default-slogan'; - - public withName(name: string): this { - this.name = name; - return this; - } - - public withVersion(version: VersionStub): this { - this.version = version; - return this; - } - - public withSlogan(slogan: string): this { - this.slogan = slogan; - return this; - } - - public withRepositoryUrl(repositoryUrl: string): this { - this.repositoryUrl = repositoryUrl; - return this; - } - - public withHomepage(homepage: string): this { - this.homepage = homepage; - return this; - } - - public build(): GitHubProjectDetails { - return new GitHubProjectDetails( - this.name, - this.version, - this.slogan, - this.repositoryUrl, - this.homepage, - ); - } +function create( + prepareParams?: (params: ProjectDetailsParametersStub) => ProjectDetailsParameters, +) { + const params: ProjectDetailsParameters = prepareParams + ? prepareParams(new ProjectDetailsParametersStub()) + : new ProjectDetailsParametersStub(); + return createGitHubProjectDetails(params); } diff --git a/tests/unit/application/Parser/ProjectDetailsParser.spec.ts b/tests/unit/application/Parser/ProjectDetailsParser.spec.ts index 7b54cb553..ae5acfcf7 100644 --- a/tests/unit/application/Parser/ProjectDetailsParser.spec.ts +++ b/tests/unit/application/Parser/ProjectDetailsParser.spec.ts @@ -1,9 +1,10 @@ import { describe, it, expect } from 'vitest'; -import { parseProjectDetails, type ProjectDetailsFactory } from '@/application/Parser/ProjectDetailsParser'; +import { parseProjectDetails } from '@/application/Parser/Project/ProjectDetailsParser'; import { AppMetadataStub } from '@tests/unit/shared/Stubs/AppMetadataStub'; import type { PropertyKeys } from '@/TypeHelpers'; import { ProjectDetailsStub } from '@tests/unit/shared/Stubs/ProjectDetailsStub'; -import { Version } from '@/domain/Version'; +import type { ProjectDetails } from '@/domain/Project/ProjectDetails'; +import type { ProjectDetailsParameters, ProjectDetailsFactory } from '@/application/Parser/Project/ProjectDetailsFactory'; describe('ProjectDetailsParser', () => { describe('parseProjectDetails', () => { @@ -20,7 +21,7 @@ describe('ProjectDetailsParser', () => { it('without metadata', () => { // arrange const metadataFactory = undefined; - const projectDetailsFactory = new ProjectDetailsFactoryStub().getStub(); + const projectDetailsFactory = createProjectDetailsFactoryStub(); // act const act = () => parseProjectDetails(metadataFactory, projectDetailsFactory); // expectS @@ -40,35 +41,35 @@ describe('ProjectDetailsParser', () => { interface MetadataTestScenario { readonly setMetadata: (appMetadataStub: AppMetadataStub, value: string) => AppMetadataStub; readonly expectedValue: string; - readonly getActualValue: (projectDetailsFactory: ProjectDetailsFactoryStub) => string; + readonly getActualValue: (projectDetails: ProjectDetails) => string; } const testScenarios: { - [K in PropertyKeys]: MetadataTestScenario + [K in PropertyKeys]: MetadataTestScenario } = { name: { setMetadata: (metadata, value) => metadata.witName(value), expectedValue: 'expected-app-name', - getActualValue: (projectDetailsFactory) => projectDetailsFactory.name, + getActualValue: (projectDetails) => projectDetails.name, }, version: { setMetadata: (metadata, value) => metadata.withVersion(value), expectedValue: '0.11.3', - getActualValue: (projectDetailsFactory) => projectDetailsFactory.version.toString(), + getActualValue: (projectDetails) => projectDetails.version.toString(), }, slogan: { setMetadata: (metadata, value) => metadata.withSlogan(value), expectedValue: 'expected-slogan', - getActualValue: (projectDetailsFactory) => projectDetailsFactory.slogan, + getActualValue: (projectDetails) => projectDetails.slogan, }, repositoryUrl: { setMetadata: (metadata, value) => metadata.withRepositoryUrl(value), expectedValue: 'https://expected-repository.url', - getActualValue: (projectDetailsFactory) => projectDetailsFactory.repositoryUrl, + getActualValue: (projectDetails) => projectDetails.repositoryUrl, }, homepage: { setMetadata: (metadata, value) => metadata.withHomepageUrl(value), expectedValue: 'https://expected.sexy', - getActualValue: (projectDetailsFactory) => projectDetailsFactory.homepage, + getActualValue: (projectDetails) => projectDetails.homepage, }, }; Object.entries(testScenarios).forEach(([propertyName, { @@ -77,11 +78,11 @@ describe('ProjectDetailsParser', () => { it(propertyName, () => { // act const metadata = setMetadata(new AppMetadataStub(), expectedValue); - const projectDetailsFactoryStub = new ProjectDetailsFactoryStub(); + const projectDetailsFactoryStub = createProjectDetailsFactoryStub(); // act - parseProjectDetails(metadata, projectDetailsFactoryStub.getStub()); + const projectDetails = parseProjectDetails(metadata, projectDetailsFactoryStub); // assert - const actual = getActualValue(projectDetailsFactoryStub); + const actual = getActualValue(projectDetails); expect(actual).to.be.equal(expectedValue); }); }); @@ -89,25 +90,14 @@ describe('ProjectDetailsParser', () => { }); }); -class ProjectDetailsFactoryStub { - public name: string; - - public version: Version; - - public slogan: string; - - public repositoryUrl: string; - - public homepage: string; - - 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 ProjectDetailsStub(); - }; - } +function createProjectDetailsFactoryStub(): ProjectDetailsFactory { + return (params) => { + const details = new ProjectDetailsStub() + .withName(params.name) + .withVersion(params.version) + .withSlogan(params.slogan) + .withRepositoryUrl(params.repositoryUrl) + .withHomepageUrl(params.homepage); + return details; + }; } diff --git a/tests/unit/shared/Stubs/CodeChangedEventStub.ts b/tests/unit/shared/Stubs/CodeChangedEventStub.ts index 4f1e884c3..c8fdf77d4 100644 --- a/tests/unit/shared/Stubs/CodeChangedEventStub.ts +++ b/tests/unit/shared/Stubs/CodeChangedEventStub.ts @@ -1,15 +1,22 @@ import type { ICodeChangedEvent } from '@/application/Context/State/Code/Event/ICodeChangedEvent'; import type { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition'; import type { Script } from '@/domain/Executables/Script/Script'; +import { ScriptStub } from './ScriptStub'; export class CodeChangedEventStub implements ICodeChangedEvent { - public code: string; + public code: string = `[${CodeChangedEventStub.name}]code`; - public addedScripts: readonly Script[]; + public addedScripts: readonly Script[] = [ + new ScriptStub(`[${CodeChangedEventStub.name}]added-script`), + ]; - public removedScripts: readonly Script[]; + public removedScripts: readonly Script[] = [ + new ScriptStub(`[${CodeChangedEventStub.name}]removed-script`), + ]; - public changedScripts: readonly Script[]; + public changedScripts: readonly Script[] = [ + new ScriptStub(`[${CodeChangedEventStub.name}]changed-script`), + ]; public isEmpty(): boolean { throw new Error('Method not implemented.'); diff --git a/tests/unit/shared/Stubs/EnumParserStub.ts b/tests/unit/shared/Stubs/EnumParserStub.ts index 1afe1e44d..0961ceb82 100644 --- a/tests/unit/shared/Stubs/EnumParserStub.ts +++ b/tests/unit/shared/Stubs/EnumParserStub.ts @@ -4,7 +4,7 @@ export class EnumParserStub implements EnumParser { private readonly scenarios = new Array<{ inputName: string, inputValue: string, outputValue: T }>(); - private defaultValue: T; + private defaultValue: T | null = null; public setup(inputName: string, inputValue: string, outputValue: T): this { this.scenarios.push({ inputName, inputValue, outputValue }); @@ -23,7 +23,7 @@ export class EnumParserStub implements EnumParser { if (scenario) { return scenario.outputValue; } - if (this.defaultValue !== undefined) { + if (this.defaultValue !== null) { return this.defaultValue; } throw new Error(`Don't know now what to return from ${EnumParserStub.name}, forgot to set-up?`); diff --git a/tests/unit/shared/Stubs/FunctionCallDataStub.ts b/tests/unit/shared/Stubs/FunctionCallDataStub.ts index 65a36d2f8..13b99e062 100644 --- a/tests/unit/shared/Stubs/FunctionCallDataStub.ts +++ b/tests/unit/shared/Stubs/FunctionCallDataStub.ts @@ -1,7 +1,7 @@ import type { FunctionCallData, FunctionCallParametersData } from '@/application/collections/'; export class FunctionCallDataStub implements FunctionCallData { - public function = 'callDatStubCalleeFunction'; + public function = `[${FunctionCallDataStub.name}]callee-function`; public parameters: { [index: string]: string } = { testParameter: 'testArgument' }; diff --git a/tests/unit/shared/Stubs/FunctionCallStub.ts b/tests/unit/shared/Stubs/FunctionCallStub.ts index adf37b024..4a6812c18 100644 --- a/tests/unit/shared/Stubs/FunctionCallStub.ts +++ b/tests/unit/shared/Stubs/FunctionCallStub.ts @@ -2,7 +2,7 @@ import type { FunctionCall } from '@/application/Parser/Executable/Script/Compil import { FunctionCallArgumentCollectionStub } from './FunctionCallArgumentCollectionStub'; export class FunctionCallStub implements FunctionCall { - public functionName = 'functionCallStub'; + public functionName = `[${FunctionCallStub.name}]name`; public args = new FunctionCallArgumentCollectionStub(); diff --git a/tests/unit/shared/Stubs/FunctionDataStub.ts b/tests/unit/shared/Stubs/FunctionDataStub.ts index 11098187c..0b747fe72 100644 --- a/tests/unit/shared/Stubs/FunctionDataStub.ts +++ b/tests/unit/shared/Stubs/FunctionDataStub.ts @@ -5,7 +5,7 @@ import type { import { FunctionCallDataStub } from './FunctionCallDataStub'; export function createFunctionDataWithCode(): FunctionDataStub { - const instance = new FunctionDataStub() + const instance = createFunctionDataWithoutCallOrCode() .withCode('stub-code') .withRevertCode('stub-revert-code'); return instance; @@ -14,7 +14,7 @@ export function createFunctionDataWithCode(): FunctionDataStub { export function createFunctionDataWithCall( call?: FunctionCallsData, ): FunctionDataStub { - let instance = new FunctionDataStub(); + let instance = createFunctionDataWithoutCallOrCode(); if (call) { instance = instance.withCall(call); } else { @@ -24,7 +24,10 @@ export function createFunctionDataWithCall( } export function createFunctionDataWithoutCallOrCode(): FunctionDataStub { - return new FunctionDataStub(); + return new FunctionDataStub() + .withCall(undefined) + .withCode(undefined as unknown as string) + .withRevertCode(undefined); } interface FunctionDataBuilder { @@ -45,9 +48,9 @@ interface CallFunctionDataBuilder extends FunctionDataBuilder; + public updatedState: Partial = new TreeNodeStateDescriptorStub(); } diff --git a/tsconfig.json b/tsconfig.json index 456ee38b4..2fb3ab994 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "moduleResolution": "Bundler", "strictNullChecks": true, "noImplicitAny": true, + "strictPropertyInitialization": true, "experimentalDecorators": true, "esModuleInterop": true, "resolveJsonModule": true,