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:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ describe('TreeNodeHierarchy', () => {
|
||||
|
||||
describe('depthInTree', () => {
|
||||
interface DepthTestScenario {
|
||||
readonly parentNode: TreeNode,
|
||||
readonly parentNode: TreeNode | undefined,
|
||||
readonly expectedDepth: number;
|
||||
}
|
||||
const testCases: readonly DepthTestScenario[] = [
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user