This commit introduces `useUserSelectionState` compositional hook. it centralizes and allows reusing the logic for tracking and mutation user selection state across the application. The change aims to increase code reusability, simplify the code, improve testability, and adhere to the single responsibility principle. It makes the code more reliable against race conditions and removes the need for tracking deep changes. Other supporting changes: - Introduce `CardStateIndicator` component for displaying the card state indicator icon, improving the testability and separation of concerns. - Refactor `SelectionTypeHandler` to use functional code with more clear interfaces to simplify the code. It reduces complexity, increases maintainability and increase readability by explicitly separating mutating functions. - Add new unit tests and extend improving ones to cover the new logic introduced in this commit. Remove the need to mount a wrapper component to simplify and optimize some tests, using parameter injection to inject dependencies intead.
167 lines
6.6 KiB
TypeScript
167 lines
6.6 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { useCollectionSelectionStateUpdater } from '@/presentation/components/Scripts/View/Tree/TreeViewAdapter/UseCollectionSelectionStateUpdater';
|
|
import { HierarchyAccessStub } from '@tests/unit/shared/Stubs/HierarchyAccessStub';
|
|
import { TreeNodeStateAccessStub } from '@tests/unit/shared/Stubs/TreeNodeStateAccessStub';
|
|
import { TreeNodeStub } from '@tests/unit/shared/Stubs/TreeNodeStub';
|
|
import { TreeNodeCheckState } from '@/presentation/components/Scripts/View/Tree/TreeView/Node/State/CheckState';
|
|
import { UserSelectionStub } from '@tests/unit/shared/Stubs/UserSelectionStub';
|
|
import { TreeNodeStateChangedEmittedEventStub } from '@tests/unit/shared/Stubs/TreeNodeStateChangedEmittedEventStub';
|
|
import { UseUserSelectionStateStub } from '@tests/unit/shared/Stubs/UseUserSelectionStateStub';
|
|
|
|
describe('useCollectionSelectionStateUpdater', () => {
|
|
describe('updateNodeSelection', () => {
|
|
describe('when node is a branch node', () => {
|
|
it('does nothing', () => {
|
|
// arrange
|
|
const { returnObject, useSelectionStateStub } = runHook();
|
|
const mockEvent = new TreeNodeStateChangedEmittedEventStub()
|
|
.withNode(
|
|
createTreeNodeStub({
|
|
isBranch: true,
|
|
currentState: TreeNodeCheckState.Checked,
|
|
}),
|
|
)
|
|
.withCheckStateChange({
|
|
oldState: TreeNodeCheckState.Checked,
|
|
newState: TreeNodeCheckState.Unchecked,
|
|
});
|
|
// act
|
|
returnObject.updateNodeSelection(mockEvent);
|
|
// assert
|
|
expect(useSelectionStateStub.isSelectionModified()).to.equal(false);
|
|
});
|
|
});
|
|
describe('when old and new check states are the same', () => {
|
|
it('does nothing', () => {
|
|
// arrange
|
|
const { returnObject, useSelectionStateStub } = runHook();
|
|
const mockEvent = new TreeNodeStateChangedEmittedEventStub()
|
|
.withNode(
|
|
createTreeNodeStub({
|
|
isBranch: false,
|
|
currentState: TreeNodeCheckState.Checked,
|
|
}),
|
|
)
|
|
.withCheckStateChange({
|
|
oldState: TreeNodeCheckState.Checked,
|
|
newState: TreeNodeCheckState.Checked,
|
|
});
|
|
// act
|
|
returnObject.updateNodeSelection(mockEvent);
|
|
// assert
|
|
expect(useSelectionStateStub.isSelectionModified()).to.equal(false);
|
|
});
|
|
});
|
|
describe('when checkState is checked', () => {
|
|
it('adds to selection if not already selected', () => {
|
|
// arrange
|
|
const { returnObject, useSelectionStateStub } = runHook();
|
|
const selectionStub = new UserSelectionStub([]);
|
|
selectionStub.isSelected = () => false;
|
|
useSelectionStateStub.withUserSelection(selectionStub);
|
|
const node = createTreeNodeStub({
|
|
isBranch: false,
|
|
currentState: TreeNodeCheckState.Checked,
|
|
});
|
|
const mockEvent = new TreeNodeStateChangedEmittedEventStub()
|
|
.withNode(node)
|
|
.withCheckStateChange({
|
|
oldState: TreeNodeCheckState.Unchecked,
|
|
newState: TreeNodeCheckState.Checked,
|
|
});
|
|
// act
|
|
returnObject.updateNodeSelection(mockEvent);
|
|
// assert
|
|
expect(useSelectionStateStub.isSelectionModified()).to.equal(true);
|
|
expect(selectionStub.isScriptAdded(node.id)).to.equal(true);
|
|
});
|
|
it('does nothing if already selected', () => {
|
|
// arrange
|
|
const { returnObject, useSelectionStateStub } = runHook();
|
|
const selectionStub = new UserSelectionStub([]);
|
|
selectionStub.isSelected = () => true;
|
|
useSelectionStateStub.withUserSelection(selectionStub);
|
|
const mockEvent = new TreeNodeStateChangedEmittedEventStub()
|
|
.withNode(
|
|
createTreeNodeStub({
|
|
isBranch: false,
|
|
currentState: TreeNodeCheckState.Checked,
|
|
}),
|
|
)
|
|
.withCheckStateChange({
|
|
oldState: TreeNodeCheckState.Unchecked,
|
|
newState: TreeNodeCheckState.Checked,
|
|
});
|
|
// act
|
|
returnObject.updateNodeSelection(mockEvent);
|
|
// assert
|
|
expect(useSelectionStateStub.isSelectionModified()).to.equal(false);
|
|
});
|
|
});
|
|
describe('when checkState is unchecked', () => {
|
|
it('removes from selection if already selected', () => {
|
|
// arrange
|
|
const { returnObject, useSelectionStateStub } = runHook();
|
|
const selectionStub = new UserSelectionStub([]);
|
|
selectionStub.isSelected = () => true;
|
|
useSelectionStateStub.withUserSelection(selectionStub);
|
|
const node = createTreeNodeStub({
|
|
isBranch: false,
|
|
currentState: TreeNodeCheckState.Unchecked,
|
|
});
|
|
const mockEvent = new TreeNodeStateChangedEmittedEventStub()
|
|
.withNode(node)
|
|
.withCheckStateChange({
|
|
oldState: TreeNodeCheckState.Checked,
|
|
newState: TreeNodeCheckState.Unchecked,
|
|
});
|
|
// act
|
|
returnObject.updateNodeSelection(mockEvent);
|
|
// assert
|
|
expect(useSelectionStateStub.isSelectionModified()).to.equal(true);
|
|
expect(selectionStub.isScriptRemoved(node.id)).to.equal(true);
|
|
});
|
|
it('does nothing if not already selected', () => {
|
|
// arrange
|
|
const { returnObject, useSelectionStateStub } = runHook();
|
|
const selectionStub = new UserSelectionStub([]);
|
|
selectionStub.isSelected = () => false;
|
|
useSelectionStateStub.withUserSelection(selectionStub);
|
|
const mockEvent = new TreeNodeStateChangedEmittedEventStub()
|
|
.withNode(
|
|
createTreeNodeStub({
|
|
isBranch: false,
|
|
currentState: TreeNodeCheckState.Unchecked,
|
|
}),
|
|
)
|
|
.withCheckStateChange({
|
|
oldState: TreeNodeCheckState.Checked,
|
|
newState: TreeNodeCheckState.Unchecked,
|
|
});
|
|
// act
|
|
returnObject.updateNodeSelection(mockEvent);
|
|
// assert
|
|
expect(useSelectionStateStub.isSelectionModified()).to.equal(false);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
function runHook() {
|
|
const useSelectionStateStub = new UseUserSelectionStateStub();
|
|
const returnObject = useCollectionSelectionStateUpdater(useSelectionStateStub.get());
|
|
return {
|
|
returnObject,
|
|
useSelectionStateStub,
|
|
};
|
|
}
|
|
|
|
function createTreeNodeStub(scenario: {
|
|
readonly isBranch: boolean,
|
|
readonly currentState: TreeNodeCheckState,
|
|
}) {
|
|
return new TreeNodeStub()
|
|
.withHierarchy(new HierarchyAccessStub().withIsBranchNode(scenario.isBranch))
|
|
.withState(new TreeNodeStateAccessStub().withCurrentCheckState(scenario.currentState));
|
|
}
|