Refactor to enforce strictNullChecks

This commit applies `strictNullChecks` to the entire codebase to improve
maintainability and type safety. Key changes include:

- Remove some explicit null-checks where unnecessary.
- Add necessary null-checks.
- Refactor static factory functions for a more functional approach.
- Improve some test names and contexts for better debugging.
- Add unit tests for any additional logic introduced.
- Refactor `createPositionFromRegexFullMatch` to its own function as the
  logic is reused.
- Prefer `find` prefix on functions that may return `undefined` and
  `get` prefix for those that always return a value.
This commit is contained in:
undergroundwires
2023-11-12 22:54:00 +01:00
parent 7ab16ecccb
commit 949fac1a7c
294 changed files with 2477 additions and 2738 deletions

View File

@@ -14,38 +14,13 @@ describe('TreeViewFilterEvent', () => {
// expect
expect(event.action).to.equal(expectedAction);
});
describe('returns expected predicate', () => {
const testCases: ReadonlyArray<{
readonly name: string,
readonly givenPredicate: TreeViewFilterPredicate,
}> = [
{
name: 'given a real predicate',
givenPredicate: createPredicateStub(),
},
{
name: 'given undefined predicate',
givenPredicate: undefined,
},
];
testCases.forEach(({ name, givenPredicate }) => {
it(name, () => {
// arrange
const expectedPredicate = givenPredicate;
// act
const event = createFilterTriggeredEvent(expectedPredicate);
// assert
expect(event.predicate).to.equal(expectedPredicate);
});
});
});
it('returns event even without predicate', () => {
it('returns expected predicate', () => {
// arrange
const expectedPredicate = createPredicateStub();
// act
const predicate = null as TreeViewFilterPredicate;
const event = createFilterTriggeredEvent(expectedPredicate);
// assert
const event = createFilterTriggeredEvent(predicate);
// expect
expect(event.predicate).to.equal(predicate);
expect(event.predicate).to.equal(expectedPredicate);
});
});
@@ -58,14 +33,6 @@ describe('TreeViewFilterEvent', () => {
// expect
expect(event.action).to.equal(expectedAction);
});
it('returns without predicate', () => {
// arrange
const expected = undefined;
// act
const event = createFilterRemovedEvent();
// assert
expect(event.predicate).to.equal(expected);
});
});
});

View File

@@ -75,7 +75,7 @@ describe('TreeNodeHierarchy', () => {
describe('depthInTree', () => {
interface DepthTestScenario {
readonly parentNode: TreeNode,
readonly parentNode: TreeNode | undefined,
readonly expectedDepth: number;
}
const testCases: readonly DepthTestScenario[] = [

View File

@@ -4,6 +4,7 @@ import { TreeNodeStateDescriptor } from '@/presentation/components/Scripts/View/
import { TreeNodeCheckState } from '@/presentation/components/Scripts/View/Tree/TreeView/Node/State/CheckState';
import { NodeStateChangedEvent, TreeNodeStateTransaction } from '@/presentation/components/Scripts/View/Tree/TreeView/Node/State/StateAccess';
import { PropertyKeys } from '@/TypeHelpers';
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
describe('TreeNodeState', () => {
describe('beginTransaction', () => {
@@ -37,14 +38,14 @@ describe('TreeNodeState', () => {
const transaction = treeNodeState
.beginTransaction()
.withCheckState(TreeNodeCheckState.Checked);
let notifiedEvent: NodeStateChangedEvent;
let notifiedEvent: NodeStateChangedEvent | undefined;
// act
treeNodeState.changed.on((event) => {
notifiedEvent = event;
});
treeNodeState.commitTransaction(transaction);
// assert
expect(notifiedEvent).to.not.equal(undefined);
expectExists(notifiedEvent);
expect(notifiedEvent.oldState.checkState).to.equal(TreeNodeCheckState.Unchecked);
expect(notifiedEvent.newState.checkState).to.equal(TreeNodeCheckState.Checked);
});

View File

@@ -23,7 +23,7 @@ describe('TreeNodeManager', () => {
const act = () => new TreeNodeManager(absentId);
// assert
expect(act).to.throw(expectedError);
});
}, { excludeNull: true, excludeUndefined: true });
});
});
@@ -37,14 +37,14 @@ describe('TreeNodeManager', () => {
expect(node.metadata).to.equal(expectedMetadata);
});
describe('should accept absent metadata', () => {
itEachAbsentObjectValue((absentMetadata) => {
itEachAbsentObjectValue((absentValue) => {
// arrange
const expectedMetadata = absentMetadata;
const expectedMetadata = absentValue;
// act
const node = new TreeNodeManager('id', expectedMetadata);
// assert
expect(node.metadata).to.equal(absentMetadata);
});
expect(node.metadata).to.equal(undefined);
}, { excludeNull: true });
});
});

View File

@@ -74,13 +74,16 @@ describe('useKeyboardInteractionState', () => {
});
function mountWrapperComponent(window: WindowWithEventListeners) {
let returnObject: ReturnType<typeof useKeyboardInteractionState>;
let returnObject: ReturnType<typeof useKeyboardInteractionState> | undefined;
const wrapper = shallowMount(defineComponent({
setup() {
returnObject = useKeyboardInteractionState(window);
},
template: '<div></div>',
}));
if (!returnObject) {
throw new Error('missing hook result');
}
return {
returnObject,
wrapper,

View File

@@ -57,7 +57,7 @@ describe('useNodeState', () => {
});
function mountWrapperComponent(nodeRef: Readonly<Ref<ReadOnlyTreeNode>>) {
let returnObject: ReturnType<typeof useNodeState>;
let returnObject: ReturnType<typeof useNodeState> | undefined;
const wrapper = shallowMount(
defineComponent({
setup() {
@@ -74,6 +74,9 @@ function mountWrapperComponent(nodeRef: Readonly<Ref<ReadOnlyTreeNode>>) {
},
},
);
if (!returnObject) {
throw new Error('missing hook result');
}
return {
wrapper,
returnObject,

View File

@@ -3,7 +3,6 @@ import { parseTreeInput } from '@/presentation/components/Scripts/View/Tree/Tree
import { TreeInputNodeData } from '@/presentation/components/Scripts/View/Tree/TreeView/Bindings/TreeInputNodeData';
import { TreeNodeManager } from '@/presentation/components/Scripts/View/Tree/TreeView/Node/TreeNodeManager';
import { TreeInputNodeDataStub } from '@tests/unit/shared/Stubs/TreeInputNodeDataStub';
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
describe('parseTreeInput', () => {
it('throws if input data is not an array', () => {
@@ -16,18 +15,6 @@ describe('parseTreeInput', () => {
expect(act).to.throw(expectedError);
});
describe('throws if input data is absent', () => {
itEachAbsentObjectValue((absentValue) => {
// arrange
const expectedError = 'missing input';
const invalidInput = absentValue;
// act
const act = () => parseTreeInput(invalidInput);
// assert
expect(act).to.throw(expectedError);
});
});
it('returns an empty array if given an empty array', () => {
// arrange
const input = [];

View File

@@ -6,11 +6,12 @@ import { createTreeNodeParserStub } from '@tests/unit/shared/Stubs/TreeNodeParse
import { TreeNodeStub } from '@tests/unit/shared/Stubs/TreeNodeStub';
import { TreeInputNodeDataStub } from '@tests/unit/shared/Stubs/TreeInputNodeDataStub';
import { QueryableNodes } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/NodeCollection/Query/QueryableNodes';
import { TreeInputNodeData } from '@/presentation/components/Scripts/View/Tree/TreeView/Bindings/TreeInputNodeData';
describe('TreeNodeInitializerAndUpdater', () => {
describe('updateRootNodes', () => {
it('should throw an error if no data is provided', () => {
itEachAbsentCollectionValue((absentValue) => {
itEachAbsentCollectionValue<TreeInputNodeData>((absentValue) => {
// arrange
const expectedError = 'missing data';
const initializer = new TreeNodeInitializerAndUpdaterBuilder()
@@ -19,7 +20,7 @@ describe('TreeNodeInitializerAndUpdater', () => {
const act = () => initializer.updateRootNodes(absentValue);
// expect
expect(act).to.throw(expectedError);
});
}, { excludeUndefined: true, excludeNull: true });
});
it('should update nodes when valid data is provided', () => {

View File

@@ -4,13 +4,13 @@ import { TreeRoot } from '@/presentation/components/Scripts/View/Tree/TreeView/T
import { useAutoUpdateChildrenCheckState } from '@/presentation/components/Scripts/View/Tree/TreeView/UseAutoUpdateChildrenCheckState';
import { TreeNodeCheckState } from '@/presentation/components/Scripts/View/Tree/TreeView/Node/State/CheckState';
import { UseNodeStateChangeAggregatorStub } from '@tests/unit/shared/Stubs/UseNodeStateChangeAggregatorStub';
import { getAbsentObjectTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
import { TreeRootStub } from '@tests/unit/shared/Stubs/TreeRootStub';
import { TreeNodeStateDescriptorStub } from '@tests/unit/shared/Stubs/TreeNodeStateDescriptorStub';
import { TreeNodeStateAccessStub, createAccessStubsFromCheckStates } from '@tests/unit/shared/Stubs/TreeNodeStateAccessStub';
import { TreeNodeStateDescriptor } from '@/presentation/components/Scripts/View/Tree/TreeView/Node/State/StateDescriptor';
import { createChangeEvent } from '@tests/unit/shared/Stubs/NodeStateChangeEventArgsStub';
import { TreeNodeStub } from '@tests/unit/shared/Stubs/TreeNodeStub';
import { getAbsentObjectTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
describe('useAutoUpdateChildrenCheckState', () => {
it('registers change handler', () => {
@@ -153,7 +153,7 @@ describe('useAutoUpdateChildrenCheckState', () => {
.withCheckState(TreeNodeCheckState.Indeterminate),
newState: new TreeNodeStateDescriptorStub().withCheckState(TreeNodeCheckState.Checked),
},
...getAbsentObjectTestCases().map((testCase) => ({
...getAbsentObjectTestCases({ excludeNull: true }).map((testCase) => ({
description: `absent old state: "${testCase.valueName}"`,
oldState: testCase.absentValue,
newState: new TreeNodeStateDescriptorStub().withCheckState(TreeNodeCheckState.Unchecked),

View File

@@ -59,7 +59,7 @@ describe('useCurrentTreeNodes', () => {
});
function mountWrapperComponent(treeRootRef: Ref<TreeRoot>) {
let returnObject: ReturnType<typeof useCurrentTreeNodes>;
let returnObject: ReturnType<typeof useCurrentTreeNodes> | undefined;
const wrapper = shallowMount(
defineComponent({
setup() {
@@ -76,6 +76,9 @@ function mountWrapperComponent(treeRootRef: Ref<TreeRoot>) {
},
},
);
if (!returnObject) {
throw new Error('missing hook result');
}
return {
wrapper,
returnObject,

View File

@@ -6,7 +6,6 @@ import { useCurrentTreeNodes } from '@/presentation/components/Scripts/View/Tree
import { NodeStateChangeEventArgs, NodeStateChangeEventCallback, useNodeStateChangeAggregator } from '@/presentation/components/Scripts/View/Tree/TreeView/UseNodeStateChangeAggregator';
import { TreeRootStub } from '@tests/unit/shared/Stubs/TreeRootStub';
import { UseCurrentTreeNodesStub } from '@tests/unit/shared/Stubs/UseCurrentTreeNodesStub';
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
import { UseAutoUnsubscribedEventsStub } from '@tests/unit/shared/Stubs/UseAutoUnsubscribedEventsStub';
import { InjectionKeys } from '@/presentation/injectionSymbols';
import { TreeNodeStub } from '@tests/unit/shared/Stubs/TreeNodeStub';
@@ -34,18 +33,6 @@ describe('useNodeStateChangeAggregator', () => {
expect(actualTreeRootRef).to.equal(expectedTreeRootRef);
});
describe('onNodeStateChange', () => {
describe('throws if callback is absent', () => {
itEachAbsentObjectValue((absentValue) => {
// arrange
const expectedError = 'missing callback';
const { returnObject } = new UseNodeStateChangeAggregatorBuilder()
.mountWrapperComponent();
// act
const act = () => returnObject.onNodeStateChange(absentValue);
// assert
expect(act).to.throw(expectedError);
});
});
describe('notifies current node states', () => {
const scenarios: ReadonlyArray<{
readonly description: string;
@@ -325,7 +312,7 @@ class UseNodeStateChangeAggregatorBuilder {
}
public mountWrapperComponent() {
let returnObject: ReturnType<typeof useNodeStateChangeAggregator>;
let returnObject: ReturnType<typeof useNodeStateChangeAggregator> | undefined;
const { treeRootRef, currentTreeNodes } = this;
const wrapper = shallowMount(
defineComponent({
@@ -343,6 +330,11 @@ class UseNodeStateChangeAggregatorBuilder {
},
},
);
if (!returnObject) {
throw new Error('missing hook result');
}
return {
wrapper,
returnObject,