Bump to TypeScript 5.5 and enable noImplicitAny

This commit upgrades TypeScript from 5.4 to 5.5 and enables the
`noImplicitAny` option for stricter type checking. It refactors code to
comply with `noImplicitAny` and adapts to new TypeScript features and
limitations.

Key changes:

- Migrate from TypeScript 5.4 to 5.5
- Enable `noImplicitAny` for stricter type checking
- Refactor code to comply with new TypeScript features and limitations

Other supporting changes:

- Refactor progress bar handling for type safety
- Drop 'I' prefix from interfaces to align with new code convention
- Update TypeScript target from `ES2017` and `ES2018`.
  This allows named capturing groups. Otherwise, new TypeScript compiler
  does not compile the project and shows the following error:
  ```
  ...
  TimestampedFilenameGenerator.spec.ts:105:23 - error TS1503: Named capturing groups are only available when targeting 'ES2018' or later
  const pattern = /^(?<timestamp>\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})-(?<scriptName>[^.]+?)(?:\.(?<extension>[^.]+))?$/;// timestamp-scriptName.extension
  ...
  ```
- Refactor usage of `electron-progressbar` for type safety and
  less complexity.
This commit is contained in:
undergroundwires
2024-09-26 16:07:37 +02:00
parent a05a600071
commit e17744faf0
77 changed files with 656 additions and 332 deletions

View File

@@ -7,12 +7,14 @@ import { itIsSingletonFactory } from '@tests/unit/shared/TestCases/SingletonFact
import type { IApplicationContext } from '@/application/Context/IApplicationContext';
import { itIsTransientFactory } from '@tests/unit/shared/TestCases/TransientFactoryTests';
import { executeInComponentSetupContext } from '@tests/shared/Vue/ExecuteInComponentSetupContext';
import type { PropertyKeys } from '@/TypeHelpers';
type InjectionKeyType = PropertyKeys<typeof InjectionKeys>;
type DependencyInjectionTestFunction = (injectionKey: symbol) => void;
describe('DependencyProvider', () => {
describe('provideDependencies', () => {
const testCases: {
readonly [K in keyof typeof InjectionKeys]: (injectionKey: symbol) => void;
} = {
const testCases: Record<InjectionKeyType, DependencyInjectionTestFunction> = {
useCollectionState: createTransientTests(),
useApplication: createSingletonTests(),
useRuntimeEnvironment: createSingletonTests(),
@@ -27,7 +29,8 @@ describe('DependencyProvider', () => {
useAutoUnsubscribedEventListener: createTransientTests(),
};
Object.entries(testCases).forEach(([key, runTests]) => {
const registeredKey = InjectionKeys[key].key;
const injectionKey = key as InjectionKeyType;
const registeredKey = InjectionKeys[injectionKey].key;
describe(`Key: "${registeredKey.toString()}"`, () => {
runTests(registeredKey);
});
@@ -35,7 +38,7 @@ describe('DependencyProvider', () => {
});
});
function createTransientTests() {
function createTransientTests(): DependencyInjectionTestFunction {
return (injectionKey: symbol) => {
it('should register a function when transient dependency is resolved', () => {
// arrange
@@ -73,7 +76,7 @@ function createTransientTests() {
};
}
function createSingletonTests() {
function createSingletonTests(): DependencyInjectionTestFunction {
return (injectionKey: symbol) => {
it('should register an object when singleton dependency is resolved', () => {
// arrange

View File

@@ -1,20 +1,21 @@
import { describe, it, expect } from 'vitest';
import type { ISanityCheckOptions } from '@/infrastructure/RuntimeSanity/Common/ISanityCheckOptions';
import { RuntimeSanityValidator } from '@/presentation/bootstrapping/Modules/RuntimeSanityValidator';
import type { SanityCheckOptions } from '@/infrastructure/RuntimeSanity/Common/SanityCheckOptions';
import { RuntimeSanityBootstrapper } from '@/presentation/bootstrapping/Modules/RuntimeSanityBootstrapper';
import { expectDoesNotThrowAsync, expectThrowsAsync } from '@tests/shared/Assertions/ExpectThrowsAsync';
import type { RuntimeSanityValidator } from '@/infrastructure/RuntimeSanity/SanityChecks';
describe('RuntimeSanityValidator', () => {
describe('RuntimeSanityBootstrapper', () => {
it('calls validator with correct options upon bootstrap', async () => {
// arrange
const expectedOptions: ISanityCheckOptions = {
const expectedOptions: SanityCheckOptions = {
validateEnvironmentVariables: true,
validateWindowVariables: true,
};
let actualOptions: ISanityCheckOptions | undefined;
const validatorMock = (options) => {
let actualOptions: SanityCheckOptions | undefined;
const validatorMock: RuntimeSanityValidator = (options) => {
actualOptions = options;
};
const sut = new RuntimeSanityValidator(validatorMock);
const sut = new RuntimeSanityBootstrapper(validatorMock);
// act
await sut.bootstrap();
// assert
@@ -26,7 +27,7 @@ describe('RuntimeSanityValidator', () => {
const validatorMock = () => {
throw new Error(expectedMessage);
};
const sut = new RuntimeSanityValidator(validatorMock);
const sut = new RuntimeSanityBootstrapper(validatorMock);
// act
const act = async () => { await sut.bootstrap(); };
// assert
@@ -35,7 +36,7 @@ describe('RuntimeSanityValidator', () => {
it('runs successfully if validator passes', async () => {
// arrange
const validatorMock = () => { /* NOOP */ };
const sut = new RuntimeSanityValidator(validatorMock);
const sut = new RuntimeSanityBootstrapper(validatorMock);
// act
const act = async () => { await sut.bootstrap(); };
// assert

View File

@@ -17,7 +17,8 @@ describe('PlatformInstructionSteps', () => {
[OperatingSystem.macOS]: MacOsInstructions,
[OperatingSystem.Linux]: LinuxInstructions,
};
AllSupportedOperatingSystems.forEach((operatingSystem) => {
AllSupportedOperatingSystems.forEach((operatingSystemKey) => {
const operatingSystem = operatingSystemKey as SupportedOperatingSystem;
it(`renders the correct component for ${OperatingSystem[operatingSystem]}`, () => {
// arrange
const expectedComponent = testScenarios[operatingSystem];
@@ -47,7 +48,9 @@ describe('PlatformInstructionSteps', () => {
// assert
const componentWrapper = wrapper.findComponent(wrappedComponent);
expect(componentWrapper.props('filename')).to.equal(expectedFilename);
const propertyValues = componentWrapper.props();
const propertyValue = 'filename' in propertyValues ? propertyValues.filename : undefined;
expect(propertyValue).to.equal(expectedFilename);
});
});
});

View File

@@ -8,7 +8,7 @@ describe('CompositeMarkdownRenderer', () => {
it('throws error without renderers', () => {
// arrange
const expectedError = 'missing renderers';
const renderers = [];
const renderers = new Array<MarkdownRenderer>();
const context = new MarkdownRendererTestBuilder()
.withMarkdownRenderers(renderers);
// act

View File

@@ -33,7 +33,7 @@ describe('TreeNodeHierarchy', () => {
it('returns `true` without children', () => {
// arrange
const hierarchy = new TreeNodeHierarchy();
const children = [];
const children = new Array<TreeNode>();
// act
hierarchy.setChildren(children);
// assert
@@ -55,7 +55,7 @@ describe('TreeNodeHierarchy', () => {
it('returns `false` without children', () => {
// arrange
const hierarchy = new TreeNodeHierarchy();
const children = [];
const children = new Array<TreeNode>();
// act
hierarchy.setChildren(children);
// assert

View File

@@ -237,7 +237,7 @@ describe('useGradualNodeRendering', () => {
});
it('skips scheduling when no nodes to render', () => {
// arrange
const nodes = [];
const nodes = new Array<TreeNode>();
const nodesStub = new UseCurrentTreeNodesStub()
.withQueryableNodes(new QueryableNodesStub().withFlattenedNodes(nodes));
const delaySchedulerStub = new DelaySchedulerStub();

View File

@@ -17,7 +17,7 @@ describe('parseTreeInput', () => {
it('returns an empty array if given an empty array', () => {
// arrange
const input = [];
const input = new Array<TreeInputNodeData>();
// act
const nodes = parseTreeInput(input);
// assert

View File

@@ -1,7 +1,7 @@
import { SingleNodeCollectionFocusManager } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/Focus/SingleNodeCollectionFocusManager';
import type { TreeNodeCollection } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/NodeCollection/TreeNodeCollection';
import { TreeNodeInitializerAndUpdater } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/NodeCollection/TreeNodeInitializerAndUpdater';
import { TreeRootManager } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRootManager';
import { TreeRootManager, type FocusManagerFactory } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRootManager';
import { SingleNodeFocusManagerStub } from '@tests/unit/shared/Stubs/SingleNodeFocusManagerStub';
import { TreeNodeCollectionStub } from '@tests/unit/shared/Stubs/TreeNodeCollectionStub';
@@ -19,9 +19,12 @@ describe('TreeRootManager', () => {
it('set by constructor as expected', () => {
// arrange
const expectedCollection = new TreeNodeCollectionStub();
const sut = new TreeRootManager();
const context = new TestContext()
.withNodeCollection(expectedCollection);
// act
const actualCollection = sut.collection;
const actualCollection = context
.build()
.collection;
// assert
expect(actualCollection).to.equal(expectedCollection);
});
@@ -39,15 +42,41 @@ describe('TreeRootManager', () => {
it('creates with same collection it uses', () => {
// arrange
let usedCollection: TreeNodeCollection | undefined;
const factoryMock = (collection) => {
const factoryMock: FocusManagerFactory = (collection) => {
usedCollection = collection;
return new SingleNodeFocusManagerStub();
};
const sut = new TreeRootManager(new TreeNodeCollectionStub(), factoryMock);
const context = new TestContext()
.withFocusManagerFactory(factoryMock);
// act
const expected = sut.collection;
const expected = context
.build()
.collection;
// assert
expect(usedCollection).to.equal(expected);
});
});
});
class TestContext {
private nodeCollection: TreeNodeCollection = new TreeNodeCollectionStub();
private focusManagerFactory: FocusManagerFactory = () => new SingleNodeFocusManagerStub();
public withFocusManagerFactory(focusManagerFactory: FocusManagerFactory): this {
this.focusManagerFactory = focusManagerFactory;
return this;
}
public withNodeCollection(nodeCollection: TreeNodeCollection): this {
this.nodeCollection = nodeCollection;
return this;
}
public build(): TreeRootManager {
return new TreeRootManager(
this.nodeCollection,
this.focusManagerFactory,
);
}
}

View File

@@ -7,6 +7,7 @@ import { UseUserSelectionStateStub } from '@tests/unit/shared/Stubs/UseUserSelec
import { ScriptStub } from '@tests/unit/shared/Stubs/ScriptStub';
import type { TreeNodeId } from '@/presentation/components/Scripts/View/Tree/TreeView/Node/TreeNode';
import type { Executable } from '@/domain/Executables/Executable';
import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript';
describe('useSelectedScriptNodeIds', () => {
it('returns an empty array when no scripts are selected', () => {
@@ -44,7 +45,7 @@ describe('useSelectedScriptNodeIds', () => {
});
it('when the selection state changes', () => {
// arrange
const initialScripts = [];
const initialScripts = new Array<SelectedScript>();
const changedScripts = [
new SelectedScriptStub(new ScriptStub('id-1')),
new SelectedScriptStub(new ScriptStub('id-2')),

View File

@@ -67,7 +67,7 @@ function runSharedTestsForAnimation(
};
const element = document.createElement('div');
Object.entries(expectedStyleValues).forEach(([key, value]) => {
element.style[key] = value;
element.style[key as keyof MutatedStyleProperties] = value;
});
const timer = new TimerStub();
const hookResult = useExpandCollapseAnimation(timer);
@@ -78,7 +78,8 @@ function runSharedTestsForAnimation(
await promise;
// assert
Object.entries(expectedStyleValues).forEach(([key, expectedStyleValue]) => {
const actualStyleValue = element.style[key];
const styleProperty = key as keyof MutatedStyleProperties;
const actualStyleValue = element.style[styleProperty];
expect(actualStyleValue).to.equal(expectedStyleValue, formatAssertionMessage([
`Style key: ${key}`,
`Expected style value: ${expectedStyleValue}`,
@@ -86,7 +87,7 @@ function runSharedTestsForAnimation(
`Initial style value: ${expectedStyleValues}`,
'All styles:',
...Object.entries(expectedStyleValues)
.map(([k, value]) => indentText(`- ${k} > actual: "${element.style[k]}" | expected: "${value}"`)),
.map(([k, value]) => indentText(`- ${k} > actual: "${actualStyleValue}" | expected: "${value}"`)),
]));
});
});

View File

@@ -30,18 +30,19 @@ describe('useClipboard', () => {
} = {
copyText: ['text-arg'],
};
Object.entries(testScenarios).forEach(([functionName, testFunctionArgs]) => {
describe(functionName, () => {
Object.entries(testScenarios).forEach(([functionNameValue, testFunctionArgs]) => {
const functionName = functionNameValue as ClipboardFunction;
describe(functionNameValue, () => {
it('binds the method to the instance', () => {
// arrange
const expectedArgs = testFunctionArgs;
const clipboardStub = new ClipboardStub();
// act
const clipboard = useClipboard(clipboardStub);
const { [functionName as ClipboardFunction]: testFunction } = clipboard;
const { [functionName]: testFunction } = clipboard;
// assert
testFunction(...expectedArgs);
const call = clipboardStub.callHistory.find((c) => c.methodName === functionName);
const call = clipboardStub.callHistory.find((c) => c.methodName === functionNameValue);
expectExists(call);
expect(call.args).to.deep.equal(expectedArgs);
});
@@ -50,14 +51,15 @@ describe('useClipboard', () => {
const clipboardStub = new ClipboardStub();
const expectedThisContext = clipboardStub;
let actualThisContext: typeof expectedThisContext | undefined;
// eslint-disable-next-line func-names
clipboardStub[functionName] = function () {
// eslint-disable-next-line func-names, @typescript-eslint/no-unused-vars
clipboardStub[functionName] = function (_text) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
actualThisContext = this;
return Promise.resolve();
};
// act
const clipboard = useClipboard(clipboardStub);
const { [functionName as ClipboardFunction]: testFunction } = clipboard;
const { [functionNameValue as ClipboardFunction]: testFunction } = clipboard;
// assert
testFunction(...testFunctionArgs);
expect(expectedThisContext).to.equal(actualThisContext);

View File

@@ -9,6 +9,7 @@ import { ScriptStub } from '@tests/unit/shared/Stubs/ScriptStub';
import { CategoryCollectionStateStub } from '@tests/unit/shared/Stubs/CategoryCollectionStateStub';
import { SelectedScriptStub } from '@tests/unit/shared/Stubs/SelectedScriptStub';
import { ScriptSelectionStub } from '@tests/unit/shared/Stubs/ScriptSelectionStub';
import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript';
describe('useUserSelectionState', () => {
describe('currentSelection', () => {
@@ -170,8 +171,8 @@ describe('useUserSelectionState', () => {
describe('triggers change', () => {
it('with new selected scripts array reference', async () => {
// arrange
const oldSelectedScriptsArrayReference = [];
const newSelectedScriptsArrayReference = [];
const oldSelectedScriptsArrayReference = new Array<SelectedScript>();
const newSelectedScriptsArrayReference = new Array<SelectedScript>();
const scriptSelectionStub = new ScriptSelectionStub()
.withSelectedScripts(oldSelectedScriptsArrayReference);
const collectionStateStub = new UseCollectionStateStub()
@@ -191,7 +192,7 @@ describe('useUserSelectionState', () => {
});
it('with same selected scripts array reference', async () => {
// arrange
const sharedSelectedScriptsReference = [];
const sharedSelectedScriptsReference = new Array<SelectedScript>();
const scriptSelectionStub = new ScriptSelectionStub()
.withSelectedScripts(sharedSelectedScriptsReference);
const collectionStateStub = new UseCollectionStateStub()

View File

@@ -65,7 +65,7 @@ describe('IpcRegistration', () => {
// act
context.run();
// assert
const channel = IpcChannelDefinitions[key];
const channel = IpcChannelDefinitions[key as ChannelDefinitionKey] as IpcChannel<unknown>;
const actualInstance = getRegisteredInstance(channel);
expect(actualInstance).to.equal(expectedInstance);
});

View File

@@ -50,7 +50,7 @@ describe('RendererApiProvider', () => {
// act
const variables = testContext.provideWindowVariables();
// assert
const actualValue = variables[property];
const actualValue = variables[property as PropertyKeys<Required<WindowVariables>>];
expect(actualValue).to.equal(expectedValue);
});
});

View File

@@ -1,5 +1,4 @@
import { describe, it, expect } from 'vitest';
import type { IpcChannel } from '@/presentation/electron/shared/IpcBridging/IpcChannel';
import { type ChannelDefinitionKey, IpcChannelDefinitions } from '@/presentation/electron/shared/IpcBridging/IpcChannelDefinitions';
describe('IpcChannelDefinitions', () => {
@@ -25,7 +24,7 @@ describe('IpcChannelDefinitions', () => {
[definitionKey, { expectedNamespace, expectedAccessibleMembers }],
) => {
describe(`channel: "${definitionKey}"`, () => {
const ipcChannelUnderTest = IpcChannelDefinitions[definitionKey] as IpcChannel<unknown>;
const ipcChannelUnderTest = IpcChannelDefinitions[definitionKey as ChannelDefinitionKey];
it('has expected namespace', () => {
// act
const actualNamespace = ipcChannelUnderTest.namespace;