Refactor watch sources for reliability
This commit changes `WatchSource` signatures into `Readonly<Ref>`s.
It provides two important benefits:
1. Eliminates the possibility of `undefined` states, that's result of
using `WatchSource`s. This previously required additional null checks.
By using `Readonly<Ref>`, the state handling becomes simpler and less
susceptible to null errors.
2. Optimizes performance by using references:
- Avoids the reactive layer of `computed` references when not needed.
- The `watch` syntax, such as `watch(() => ref.value)`, can introduce
side effects. For example, it does not account for `triggerRef` in
scenarios where the value remains unchanged, preventing the watcher
from running (vuejs/core#9579).
This commit is contained in:
@@ -17,7 +17,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent, toRef } from 'vue';
|
||||||
import { injectKey } from '@/presentation/injectionSymbols';
|
import { injectKey } from '@/presentation/injectionSymbols';
|
||||||
import TreeView from './TreeView/TreeView.vue';
|
import TreeView from './TreeView/TreeView.vue';
|
||||||
import NodeContent from './NodeContent/NodeContent.vue';
|
import NodeContent from './NodeContent/NodeContent.vue';
|
||||||
@@ -42,7 +42,7 @@ export default defineComponent({
|
|||||||
const useUserCollectionStateHook = injectKey((keys) => keys.useUserSelectionState);
|
const useUserCollectionStateHook = injectKey((keys) => keys.useUserSelectionState);
|
||||||
const { selectedScriptNodeIds } = useSelectedScriptNodeIds(useUserCollectionStateHook);
|
const { selectedScriptNodeIds } = useSelectedScriptNodeIds(useUserCollectionStateHook);
|
||||||
const { latestFilterEvent } = useTreeViewFilterEvent();
|
const { latestFilterEvent } = useTreeViewFilterEvent();
|
||||||
const { treeViewInputNodes } = useTreeViewNodeInput(() => props.categoryId);
|
const { treeViewInputNodes } = useTreeViewNodeInput(toRef(props, 'categoryId'));
|
||||||
const { updateNodeSelection } = useCollectionSelectionStateUpdater(useUserCollectionStateHook);
|
const { updateNodeSelection } = useCollectionSelectionStateUpdater(useUserCollectionStateHook);
|
||||||
|
|
||||||
function handleNodeChangedEvent(event: TreeNodeStateChangedEmittedEvent) {
|
function handleNodeChangedEvent(event: TreeNodeStateChangedEmittedEvent) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="wrapper" v-if="currentNode">
|
<div class="wrapper">
|
||||||
<div
|
<div
|
||||||
class="expansible-node"
|
class="expansible-node"
|
||||||
@click="toggleCheck"
|
@click="toggleCheck"
|
||||||
@@ -46,15 +46,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import { defineComponent, computed, toRef } from 'vue';
|
||||||
defineComponent, computed, PropType,
|
|
||||||
} from 'vue';
|
|
||||||
import { TreeRoot } from '../TreeRoot/TreeRoot';
|
import { TreeRoot } from '../TreeRoot/TreeRoot';
|
||||||
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
|
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
|
||||||
import { NodeRenderingStrategy } from '../Rendering/Scheduling/NodeRenderingStrategy';
|
import { NodeRenderingStrategy } from '../Rendering/Scheduling/NodeRenderingStrategy';
|
||||||
import { useNodeState } from './UseNodeState';
|
import { useNodeState } from './UseNodeState';
|
||||||
import { TreeNode } from './TreeNode';
|
import { TreeNode } from './TreeNode';
|
||||||
import LeafTreeNode from './LeafTreeNode.vue';
|
import LeafTreeNode from './LeafTreeNode.vue';
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'HierarchicalTreeNode', // Needed due to recursion
|
name: 'HierarchicalTreeNode', // Needed due to recursion
|
||||||
@@ -76,33 +75,32 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { nodes } = useCurrentTreeNodes(() => props.treeRoot);
|
const { nodes } = useCurrentTreeNodes(toRef(props, 'treeRoot'));
|
||||||
const currentNode = computed<TreeNode | undefined>(
|
const currentNode = computed<TreeNode>(
|
||||||
() => nodes.value?.getNodeById(props.nodeId),
|
() => nodes.value.getNodeById(props.nodeId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { state } = useNodeState(() => currentNode.value);
|
const { state } = useNodeState(currentNode);
|
||||||
const expanded = computed<boolean>(() => state.value?.isExpanded ?? false);
|
const expanded = computed<boolean>(() => state.value.isExpanded);
|
||||||
|
|
||||||
const renderedNodeIds = computed<readonly string[]>(
|
const renderedNodeIds = computed<readonly string[]>(
|
||||||
() => currentNode.value
|
() => currentNode.value
|
||||||
?.hierarchy
|
.hierarchy
|
||||||
.children
|
.children
|
||||||
.filter((child) => props.renderingStrategy.shouldRender(child))
|
.filter((child) => props.renderingStrategy.shouldRender(child))
|
||||||
.map((child) => child.id)
|
.map((child) => child.id),
|
||||||
?? [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
function toggleExpand() {
|
function toggleExpand() {
|
||||||
currentNode.value?.state.toggleExpand();
|
currentNode.value.state.toggleExpand();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleCheck() {
|
function toggleCheck() {
|
||||||
currentNode.value?.state.toggleCheck();
|
currentNode.value.state.toggleCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasChildren = computed<boolean>(
|
const hasChildren = computed<boolean>(
|
||||||
() => currentNode.value?.hierarchy.isBranchNode,
|
() => currentNode.value.hierarchy.isBranchNode,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<li
|
<li
|
||||||
v-if="currentNode"
|
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
@click.stop="toggleCheckState"
|
@click.stop="toggleCheckState"
|
||||||
>
|
>
|
||||||
@@ -31,15 +30,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import { defineComponent, computed, toRef } from 'vue';
|
||||||
defineComponent, computed, PropType,
|
|
||||||
} from 'vue';
|
|
||||||
import { TreeRoot } from '../TreeRoot/TreeRoot';
|
import { TreeRoot } from '../TreeRoot/TreeRoot';
|
||||||
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
|
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
|
||||||
import { useNodeState } from './UseNodeState';
|
import { useNodeState } from './UseNodeState';
|
||||||
import { useKeyboardInteractionState } from './UseKeyboardInteractionState';
|
import { useKeyboardInteractionState } from './UseKeyboardInteractionState';
|
||||||
import { TreeNode } from './TreeNode';
|
import { TreeNode } from './TreeNode';
|
||||||
import { TreeNodeCheckState } from './State/CheckState';
|
import { TreeNodeCheckState } from './State/CheckState';
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -54,16 +52,16 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { isKeyboardBeingUsed } = useKeyboardInteractionState();
|
const { isKeyboardBeingUsed } = useKeyboardInteractionState();
|
||||||
const { nodes } = useCurrentTreeNodes(() => props.treeRoot);
|
const { nodes } = useCurrentTreeNodes(toRef(props, 'treeRoot'));
|
||||||
const currentNode = computed<TreeNode | undefined>(
|
const currentNode = computed<TreeNode>(
|
||||||
() => nodes.value?.getNodeById(props.nodeId),
|
() => nodes.value.getNodeById(props.nodeId),
|
||||||
);
|
);
|
||||||
const { state } = useNodeState(() => currentNode.value);
|
const { state } = useNodeState(currentNode);
|
||||||
|
|
||||||
const hasFocus = computed<boolean>(() => state.value?.isFocused ?? false);
|
const hasFocus = computed<boolean>(() => state.value.isFocused);
|
||||||
const checked = computed<boolean>(() => state.value?.checkState === TreeNodeCheckState.Checked);
|
const checked = computed<boolean>(() => state.value.checkState === TreeNodeCheckState.Checked);
|
||||||
const indeterminate = computed<boolean>(
|
const indeterminate = computed<boolean>(
|
||||||
() => state.value?.checkState === TreeNodeCheckState.Indeterminate,
|
() => state.value.checkState === TreeNodeCheckState.Indeterminate,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasKeyboardFocus = computed<boolean>(() => {
|
const hasKeyboardFocus = computed<boolean>(() => {
|
||||||
@@ -74,14 +72,11 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onNodeFocus = () => {
|
const onNodeFocus = () => {
|
||||||
if (!currentNode.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
props.treeRoot.focus.setSingleFocus(currentNode.value);
|
props.treeRoot.focus.setSingleFocus(currentNode.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
function toggleCheckState() {
|
function toggleCheckState() {
|
||||||
currentNode.value?.state.toggleCheck();
|
currentNode.value.state.toggleCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
WatchSource, shallowRef, watch,
|
type Ref, shallowRef, watch, shallowReadonly,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { injectKey } from '@/presentation/injectionSymbols';
|
import { injectKey } from '@/presentation/injectionSymbols';
|
||||||
import { ReadOnlyTreeNode } from './TreeNode';
|
import { ReadOnlyTreeNode } from './TreeNode';
|
||||||
import { TreeNodeStateDescriptor } from './State/StateDescriptor';
|
import { TreeNodeStateDescriptor } from './State/StateDescriptor';
|
||||||
|
|
||||||
export function useNodeState(
|
export function useNodeState(
|
||||||
nodeWatcher: WatchSource<ReadOnlyTreeNode | undefined>,
|
nodeRef: Readonly<Ref<ReadOnlyTreeNode>>,
|
||||||
) {
|
) {
|
||||||
const { events } = injectKey((keys) => keys.useAutoUnsubscribedEvents);
|
const { events } = injectKey((keys) => keys.useAutoUnsubscribedEvents);
|
||||||
|
|
||||||
const state = shallowRef<TreeNodeStateDescriptor>();
|
const state = shallowRef<TreeNodeStateDescriptor>(nodeRef.value.state.current);
|
||||||
|
|
||||||
watch(nodeWatcher, (node: ReadOnlyTreeNode) => {
|
watch(nodeRef, (node: ReadOnlyTreeNode) => {
|
||||||
if (!node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state.value = node.state.current;
|
state.value = node.state.current;
|
||||||
events.unsubscribeAllAndRegister([
|
events.unsubscribeAllAndRegister([
|
||||||
node.state.changed.on((change) => {
|
node.state.changed.on((change) => {
|
||||||
@@ -25,6 +22,6 @@ export function useNodeState(
|
|||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state: shallowReadonly(state),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
WatchSource, shallowRef, triggerRef, watch,
|
type Ref, shallowRef, triggerRef, watch,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { ReadOnlyTreeNode } from '../Node/TreeNode';
|
import { ReadOnlyTreeNode } from '../Node/TreeNode';
|
||||||
import { useNodeStateChangeAggregator } from '../UseNodeStateChangeAggregator';
|
import { useNodeStateChangeAggregator } from '../UseNodeStateChangeAggregator';
|
||||||
@@ -15,7 +15,7 @@ import { CollapsedParentOrderer } from './Ordering/CollapsedParentOrderer';
|
|||||||
* Renders tree nodes gradually to prevent UI freeze when loading large amounts of nodes.
|
* Renders tree nodes gradually to prevent UI freeze when loading large amounts of nodes.
|
||||||
*/
|
*/
|
||||||
export function useGradualNodeRendering(
|
export function useGradualNodeRendering(
|
||||||
treeWatcher: WatchSource<TreeRoot>,
|
treeRootRef: Readonly<Ref<TreeRoot>>,
|
||||||
useChangeAggregator = useNodeStateChangeAggregator,
|
useChangeAggregator = useNodeStateChangeAggregator,
|
||||||
useTreeNodes = useCurrentTreeNodes,
|
useTreeNodes = useCurrentTreeNodes,
|
||||||
scheduler: DelayScheduler = new TimeoutDelayScheduler(),
|
scheduler: DelayScheduler = new TimeoutDelayScheduler(),
|
||||||
@@ -28,8 +28,8 @@ export function useGradualNodeRendering(
|
|||||||
let isRenderingInProgress = false;
|
let isRenderingInProgress = false;
|
||||||
const renderingDelayInMs = 50;
|
const renderingDelayInMs = 50;
|
||||||
|
|
||||||
const { onNodeStateChange } = useChangeAggregator(treeWatcher);
|
const { onNodeStateChange } = useChangeAggregator(treeRootRef);
|
||||||
const { nodes } = useTreeNodes(treeWatcher);
|
const { nodes } = useTreeNodes(treeRootRef);
|
||||||
|
|
||||||
function updateNodeRenderQueue(node: ReadOnlyTreeNode, isVisible: boolean) {
|
function updateNodeRenderQueue(node: ReadOnlyTreeNode, isVisible: boolean) {
|
||||||
if (isVisible
|
if (isVisible
|
||||||
@@ -48,7 +48,7 @@ export function useGradualNodeRendering(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => nodes.value, (newNodes) => {
|
watch(nodes, (newNodes) => {
|
||||||
nodesToRender.clear();
|
nodesToRender.clear();
|
||||||
nodesBeingRendered.value.clear();
|
nodesBeingRendered.value.clear();
|
||||||
if (!newNodes || newNodes.flattenedNodes.length === 0) {
|
if (!newNodes || newNodes.flattenedNodes.length === 0) {
|
||||||
|
|||||||
@@ -18,12 +18,13 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
defineComponent, computed, PropType,
|
defineComponent, computed, toRef,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import HierarchicalTreeNode from '../Node/HierarchicalTreeNode.vue';
|
import HierarchicalTreeNode from '../Node/HierarchicalTreeNode.vue';
|
||||||
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
|
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
|
||||||
import { NodeRenderingStrategy } from '../Rendering/Scheduling/NodeRenderingStrategy';
|
import { NodeRenderingStrategy } from '../Rendering/Scheduling/NodeRenderingStrategy';
|
||||||
import { TreeRoot } from './TreeRoot';
|
import { TreeRoot } from './TreeRoot';
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@@ -40,7 +41,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { nodes } = useCurrentTreeNodes(() => props.treeRoot);
|
const { nodes } = useCurrentTreeNodes(toRef(props, 'treeRoot'));
|
||||||
|
|
||||||
const renderedNodeIds = computed<string[]>(() => {
|
const renderedNodeIds = computed<string[]>(() => {
|
||||||
return nodes
|
return nodes
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
defineComponent, onMounted, watch,
|
defineComponent, onMounted, watch,
|
||||||
shallowRef, PropType,
|
shallowRef, toRef, shallowReadonly,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { TreeRootManager } from './TreeRoot/TreeRootManager';
|
import { TreeRootManager } from './TreeRoot/TreeRootManager';
|
||||||
import TreeRoot from './TreeRoot/TreeRoot.vue';
|
import TreeRoot from './TreeRoot/TreeRoot.vue';
|
||||||
@@ -28,6 +28,7 @@ import { TreeNodeStateChangedEmittedEvent } from './Bindings/TreeNodeStateChange
|
|||||||
import { useAutoUpdateParentCheckState } from './UseAutoUpdateParentCheckState';
|
import { useAutoUpdateParentCheckState } from './UseAutoUpdateParentCheckState';
|
||||||
import { useAutoUpdateChildrenCheckState } from './UseAutoUpdateChildrenCheckState';
|
import { useAutoUpdateChildrenCheckState } from './UseAutoUpdateChildrenCheckState';
|
||||||
import { useGradualNodeRendering } from './Rendering/UseGradualNodeRendering';
|
import { useGradualNodeRendering } from './Rendering/UseGradualNodeRendering';
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@@ -57,17 +58,16 @@ export default defineComponent({
|
|||||||
|
|
||||||
const tree = new TreeRootManager();
|
const tree = new TreeRootManager();
|
||||||
|
|
||||||
useTreeKeyboardNavigation(tree, treeContainerElement);
|
const treeRef = shallowReadonly(shallowRef(tree));
|
||||||
useTreeQueryFilter(
|
|
||||||
() => props.latestFilterEvent,
|
|
||||||
() => tree,
|
|
||||||
);
|
|
||||||
useLeafNodeCheckedStateUpdater(() => tree, () => props.selectedLeafNodeIds);
|
|
||||||
useAutoUpdateParentCheckState(() => tree);
|
|
||||||
useAutoUpdateChildrenCheckState(() => tree);
|
|
||||||
const nodeRenderingScheduler = useGradualNodeRendering(() => tree);
|
|
||||||
|
|
||||||
const { onNodeStateChange } = useNodeStateChangeAggregator(() => tree);
|
useTreeKeyboardNavigation(treeRef, treeContainerElement);
|
||||||
|
useTreeQueryFilter(toRef(props, 'latestFilterEvent'), treeRef);
|
||||||
|
useLeafNodeCheckedStateUpdater(treeRef, toRef(props, 'selectedLeafNodeIds'));
|
||||||
|
useAutoUpdateParentCheckState(treeRef);
|
||||||
|
useAutoUpdateChildrenCheckState(treeRef);
|
||||||
|
const nodeRenderingScheduler = useGradualNodeRendering(treeRef);
|
||||||
|
|
||||||
|
const { onNodeStateChange } = useNodeStateChangeAggregator(treeRef);
|
||||||
|
|
||||||
onNodeStateChange((change) => {
|
onNodeStateChange((change) => {
|
||||||
emit('nodeStateChanged', {
|
emit('nodeStateChanged', {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { WatchSource } from 'vue';
|
|
||||||
import { TreeRoot } from './TreeRoot/TreeRoot';
|
import { TreeRoot } from './TreeRoot/TreeRoot';
|
||||||
import { useNodeStateChangeAggregator } from './UseNodeStateChangeAggregator';
|
import { useNodeStateChangeAggregator } from './UseNodeStateChangeAggregator';
|
||||||
import { HierarchyAccess } from './Node/Hierarchy/HierarchyAccess';
|
import { HierarchyAccess } from './Node/Hierarchy/HierarchyAccess';
|
||||||
import { TreeNodeCheckState } from './Node/State/CheckState';
|
import { TreeNodeCheckState } from './Node/State/CheckState';
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
export function useAutoUpdateChildrenCheckState(
|
export function useAutoUpdateChildrenCheckState(
|
||||||
treeWatcher: WatchSource<TreeRoot>,
|
treeRootRef: Readonly<Ref<TreeRoot>>,
|
||||||
useChangeAggregator = useNodeStateChangeAggregator,
|
useChangeAggregator = useNodeStateChangeAggregator,
|
||||||
) {
|
) {
|
||||||
const { onNodeStateChange } = useChangeAggregator(treeWatcher);
|
const { onNodeStateChange } = useChangeAggregator(treeRootRef);
|
||||||
|
|
||||||
onNodeStateChange((change) => {
|
onNodeStateChange((change) => {
|
||||||
if (change.newState.checkState === change.oldState?.checkState) {
|
if (change.newState.checkState === change.oldState?.checkState) {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { WatchSource } from 'vue';
|
|
||||||
import { TreeRoot } from './TreeRoot/TreeRoot';
|
import { TreeRoot } from './TreeRoot/TreeRoot';
|
||||||
import { useNodeStateChangeAggregator } from './UseNodeStateChangeAggregator';
|
import { useNodeStateChangeAggregator } from './UseNodeStateChangeAggregator';
|
||||||
import { HierarchyAccess } from './Node/Hierarchy/HierarchyAccess';
|
import { HierarchyAccess } from './Node/Hierarchy/HierarchyAccess';
|
||||||
import { TreeNodeCheckState } from './Node/State/CheckState';
|
import { TreeNodeCheckState } from './Node/State/CheckState';
|
||||||
import { ReadOnlyTreeNode } from './Node/TreeNode';
|
import { ReadOnlyTreeNode } from './Node/TreeNode';
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
export function useAutoUpdateParentCheckState(
|
export function useAutoUpdateParentCheckState(
|
||||||
treeWatcher: WatchSource<TreeRoot>,
|
treeRef: Readonly<Ref<TreeRoot>>,
|
||||||
useChangeAggregator = useNodeStateChangeAggregator,
|
useChangeAggregator = useNodeStateChangeAggregator,
|
||||||
) {
|
) {
|
||||||
const { onNodeStateChange } = useChangeAggregator(treeWatcher);
|
const { onNodeStateChange } = useChangeAggregator(treeRef);
|
||||||
|
|
||||||
onNodeStateChange((change) => {
|
onNodeStateChange((change) => {
|
||||||
if (change.newState.checkState === change.oldState?.checkState) {
|
if (change.newState.checkState === change.oldState?.checkState) {
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
WatchSource, watch, shallowReadonly, shallowRef,
|
watch, shallowReadonly, shallowRef, type Ref,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { injectKey } from '@/presentation/injectionSymbols';
|
import { injectKey } from '@/presentation/injectionSymbols';
|
||||||
import { TreeRoot } from './TreeRoot/TreeRoot';
|
import { TreeRoot } from './TreeRoot/TreeRoot';
|
||||||
import { QueryableNodes } from './TreeRoot/NodeCollection/Query/QueryableNodes';
|
import { QueryableNodes } from './TreeRoot/NodeCollection/Query/QueryableNodes';
|
||||||
|
|
||||||
export function useCurrentTreeNodes(treeWatcher: WatchSource<TreeRoot>) {
|
export function useCurrentTreeNodes(treeRef: Readonly<Ref<TreeRoot>>) {
|
||||||
const { events } = injectKey((keys) => keys.useAutoUnsubscribedEvents);
|
const { events } = injectKey((keys) => keys.useAutoUnsubscribedEvents);
|
||||||
|
|
||||||
const tree = shallowRef<TreeRoot | undefined>();
|
const nodes = shallowRef<QueryableNodes>(treeRef.value.collection.nodes);
|
||||||
const nodes = shallowRef<QueryableNodes | undefined>();
|
|
||||||
|
|
||||||
watch(treeWatcher, (newTree) => {
|
watch(treeRef, (newTree) => {
|
||||||
tree.value = newTree;
|
|
||||||
nodes.value = newTree.collection.nodes;
|
nodes.value = newTree.collection.nodes;
|
||||||
events.unsubscribeAllAndRegister([
|
events.unsubscribeAllAndRegister([
|
||||||
newTree.collection.nodesUpdated.on((newNodes) => {
|
newTree.collection.nodesUpdated.on((newNodes) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { WatchSource, watch } from 'vue';
|
import { type Ref, watch } from 'vue';
|
||||||
import { TreeRoot } from './TreeRoot/TreeRoot';
|
import { TreeRoot } from './TreeRoot/TreeRoot';
|
||||||
import { TreeNode } from './Node/TreeNode';
|
import { TreeNode } from './Node/TreeNode';
|
||||||
import { QueryableNodes } from './TreeRoot/NodeCollection/Query/QueryableNodes';
|
import { QueryableNodes } from './TreeRoot/NodeCollection/Query/QueryableNodes';
|
||||||
@@ -6,13 +6,13 @@ import { useCurrentTreeNodes } from './UseCurrentTreeNodes';
|
|||||||
import { TreeNodeCheckState } from './Node/State/CheckState';
|
import { TreeNodeCheckState } from './Node/State/CheckState';
|
||||||
|
|
||||||
export function useLeafNodeCheckedStateUpdater(
|
export function useLeafNodeCheckedStateUpdater(
|
||||||
treeWatcher: WatchSource<TreeRoot>,
|
treeRootRef: Readonly<Ref<TreeRoot>>,
|
||||||
leafNodeIdsWatcher: WatchSource<readonly string[]>,
|
leafNodeIdsRef: Readonly<Ref<readonly string[]>>,
|
||||||
) {
|
) {
|
||||||
const { nodes } = useCurrentTreeNodes(treeWatcher);
|
const { nodes } = useCurrentTreeNodes(treeRootRef);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[leafNodeIdsWatcher, () => nodes.value],
|
[leafNodeIdsRef, nodes],
|
||||||
([nodeIds, actualNodes]) => {
|
([nodeIds, actualNodes]) => {
|
||||||
updateNodeSelections(actualNodes, nodeIds);
|
updateNodeSelections(actualNodes, nodeIds);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
WatchSource, watch, shallowRef,
|
watch, shallowRef, type Ref,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { injectKey } from '@/presentation/injectionSymbols';
|
import { injectKey } from '@/presentation/injectionSymbols';
|
||||||
import { IEventSubscription } from '@/infrastructure/Events/IEventSource';
|
import { IEventSubscription } from '@/infrastructure/Events/IEventSource';
|
||||||
@@ -11,31 +11,31 @@ import { TreeNodeStateDescriptor } from './Node/State/StateDescriptor';
|
|||||||
export type NodeStateChangeEventCallback = (args: NodeStateChangeEventArgs) => void;
|
export type NodeStateChangeEventCallback = (args: NodeStateChangeEventArgs) => void;
|
||||||
|
|
||||||
export function useNodeStateChangeAggregator(
|
export function useNodeStateChangeAggregator(
|
||||||
treeWatcher: WatchSource<TreeRoot>,
|
treeRootRef: Readonly<Ref<TreeRoot>>,
|
||||||
useTreeNodes = useCurrentTreeNodes,
|
useTreeNodes = useCurrentTreeNodes,
|
||||||
) {
|
) {
|
||||||
const { nodes } = useTreeNodes(treeWatcher);
|
const { nodes } = useTreeNodes(treeRootRef);
|
||||||
const { events } = injectKey((keys) => keys.useAutoUnsubscribedEvents);
|
const { events } = injectKey((keys) => keys.useAutoUnsubscribedEvents);
|
||||||
|
|
||||||
const onNodeChangeCallback = shallowRef<NodeStateChangeEventCallback>();
|
const onNodeChangeCallback = shallowRef<NodeStateChangeEventCallback>();
|
||||||
|
|
||||||
watch([
|
watch(
|
||||||
() => nodes.value,
|
[nodes, onNodeChangeCallback],
|
||||||
() => onNodeChangeCallback.value,
|
([newNodes, callback]) => {
|
||||||
], ([newNodes, callback]) => {
|
if (!callback) { // may not be registered yet
|
||||||
if (!callback) { // might not be registered yet
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (!newNodes || newNodes.flattenedNodes.length === 0) {
|
||||||
if (!newNodes || newNodes.flattenedNodes.length === 0) {
|
events.unsubscribeAll();
|
||||||
events.unsubscribeAll();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
const allNodes = newNodes.flattenedNodes;
|
||||||
const allNodes = newNodes.flattenedNodes;
|
events.unsubscribeAllAndRegister(
|
||||||
events.unsubscribeAllAndRegister(
|
subscribeToNotifyOnFutureNodeChanges(allNodes, callback),
|
||||||
subscribeToNotifyOnFutureNodeChanges(allNodes, callback),
|
);
|
||||||
);
|
notifyCurrentNodeState(allNodes, callback);
|
||||||
notifyCurrentNodeState(allNodes, callback);
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
function onNodeStateChange(
|
function onNodeStateChange(
|
||||||
callback: NodeStateChangeEventCallback,
|
callback: NodeStateChangeEventCallback,
|
||||||
|
|||||||
@@ -8,14 +8,16 @@ import { TreeNode } from './Node/TreeNode';
|
|||||||
type TreeNavigationKeyCodes = 'ArrowLeft' | 'ArrowUp' | 'ArrowRight' | 'ArrowDown' | ' ' | 'Enter';
|
type TreeNavigationKeyCodes = 'ArrowLeft' | 'ArrowUp' | 'ArrowRight' | 'ArrowDown' | ' ' | 'Enter';
|
||||||
|
|
||||||
export function useTreeKeyboardNavigation(
|
export function useTreeKeyboardNavigation(
|
||||||
treeRoot: TreeRoot,
|
treeRootRef: Readonly<Ref<TreeRoot>>,
|
||||||
treeElementRef: Ref<HTMLElement | undefined>,
|
treeElementRef: Readonly<Ref<HTMLElement | undefined>>,
|
||||||
) {
|
) {
|
||||||
useKeyboardListener(treeElementRef, (event) => {
|
useKeyboardListener(treeElementRef, (event) => {
|
||||||
if (!treeElementRef.value) {
|
if (!treeElementRef.value) {
|
||||||
return; // Not yet initialized?
|
return; // Not yet initialized?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const treeRoot = treeRootRef.value;
|
||||||
|
|
||||||
const keyCode = event.key as TreeNavigationKeyCodes;
|
const keyCode = event.key as TreeNavigationKeyCodes;
|
||||||
|
|
||||||
if (!treeRoot.focus.currentSingleFocusedNode) {
|
if (!treeRoot.focus.currentSingleFocusedNode) {
|
||||||
@@ -39,7 +41,7 @@ export function useTreeKeyboardNavigation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function useKeyboardListener(
|
function useKeyboardListener(
|
||||||
elementRef: Ref<HTMLElement | undefined>,
|
elementRef: Readonly<Ref<HTMLElement | undefined>>,
|
||||||
handleKeyboardEvent: (event: KeyboardEvent) => void,
|
handleKeyboardEvent: (event: KeyboardEvent) => void,
|
||||||
) {
|
) {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { WatchSource, watch } from 'vue';
|
import { type Ref, watch } from 'vue';
|
||||||
import { TreeViewFilterAction, TreeViewFilterEvent, TreeViewFilterPredicate } from './Bindings/TreeInputFilterEvent';
|
import { TreeViewFilterAction, TreeViewFilterEvent, TreeViewFilterPredicate } from './Bindings/TreeInputFilterEvent';
|
||||||
import { ReadOnlyTreeNode, TreeNode } from './Node/TreeNode';
|
import { ReadOnlyTreeNode, TreeNode } from './Node/TreeNode';
|
||||||
import { TreeRoot } from './TreeRoot/TreeRoot';
|
import { TreeRoot } from './TreeRoot/TreeRoot';
|
||||||
@@ -8,18 +8,18 @@ import { TreeNodeStateTransaction } from './Node/State/StateAccess';
|
|||||||
import { TreeNodeStateDescriptor } from './Node/State/StateDescriptor';
|
import { TreeNodeStateDescriptor } from './Node/State/StateDescriptor';
|
||||||
|
|
||||||
export function useTreeQueryFilter(
|
export function useTreeQueryFilter(
|
||||||
latestFilterEventWatcher: WatchSource<TreeViewFilterEvent | undefined>,
|
latestFilterEventRef: Readonly<Ref<TreeViewFilterEvent | undefined>>,
|
||||||
treeWatcher: WatchSource<TreeRoot>,
|
treeRootRef: Readonly<Ref<TreeRoot>>,
|
||||||
) {
|
) {
|
||||||
const { nodes } = useCurrentTreeNodes(treeWatcher);
|
const { nodes } = useCurrentTreeNodes(treeRootRef);
|
||||||
|
|
||||||
let isFiltering = false;
|
let isFiltering = false;
|
||||||
const statesBeforeFiltering = new NodeStateRestorer();
|
const statesBeforeFiltering = new NodeStateRestorer();
|
||||||
statesBeforeFiltering.saveStateBeforeFilter(nodes.value);
|
statesBeforeFiltering.saveStateBeforeFilter(nodes.value);
|
||||||
|
|
||||||
setupWatchers({
|
setupWatchers({
|
||||||
filterEventWatcher: latestFilterEventWatcher,
|
filterEventRef: latestFilterEventRef,
|
||||||
nodesWatcher: () => nodes.value,
|
nodesRef: nodes,
|
||||||
onFilterTrigger: (predicate, newNodes) => runFilter(
|
onFilterTrigger: (predicate, newNodes) => runFilter(
|
||||||
newNodes,
|
newNodes,
|
||||||
predicate,
|
predicate,
|
||||||
@@ -171,18 +171,18 @@ class NodeStateRestorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupWatchers(options: {
|
function setupWatchers(options: {
|
||||||
filterEventWatcher: WatchSource<TreeViewFilterEvent | undefined>,
|
readonly filterEventRef: Readonly<Ref<TreeViewFilterEvent | undefined>>,
|
||||||
nodesWatcher: WatchSource<QueryableNodes>,
|
readonly nodesRef: Readonly<Ref<QueryableNodes>>,
|
||||||
onFilterReset: () => void,
|
readonly onFilterReset: () => void,
|
||||||
onFilterTrigger: (
|
readonly onFilterTrigger: (
|
||||||
predicate: TreeViewFilterPredicate,
|
predicate: TreeViewFilterPredicate,
|
||||||
nodes: QueryableNodes,
|
nodes: QueryableNodes,
|
||||||
) => void,
|
) => void,
|
||||||
}) {
|
}) {
|
||||||
watch(
|
watch(
|
||||||
[
|
[
|
||||||
options.filterEventWatcher,
|
options.filterEventRef,
|
||||||
options.nodesWatcher,
|
options.nodesRef,
|
||||||
],
|
],
|
||||||
([filterEvent, nodes]) => {
|
([filterEvent, nodes]) => {
|
||||||
if (!filterEvent) {
|
if (!filterEvent) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
WatchSource, computed,
|
type Ref, computed, shallowReadonly,
|
||||||
ref, watch,
|
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
import { injectKey } from '@/presentation/injectionSymbols';
|
import { injectKey } from '@/presentation/injectionSymbols';
|
||||||
@@ -10,7 +9,7 @@ import { convertToNodeInput } from './TreeNodeMetadataConverter';
|
|||||||
import { parseSingleCategory, parseAllCategories } from './CategoryNodeMetadataConverter';
|
import { parseSingleCategory, parseAllCategories } from './CategoryNodeMetadataConverter';
|
||||||
|
|
||||||
export function useTreeViewNodeInput(
|
export function useTreeViewNodeInput(
|
||||||
categoryIdWatcher: WatchSource<number | undefined>,
|
categoryIdRef: Readonly<Ref<number | undefined>>,
|
||||||
parser: CategoryNodeParser = {
|
parser: CategoryNodeParser = {
|
||||||
parseSingle: parseSingleCategory,
|
parseSingle: parseSingleCategory,
|
||||||
parseAll: parseAllCategories,
|
parseAll: parseAllCategories,
|
||||||
@@ -19,20 +18,14 @@ export function useTreeViewNodeInput(
|
|||||||
) {
|
) {
|
||||||
const { currentState } = injectKey((keys) => keys.useCollectionState);
|
const { currentState } = injectKey((keys) => keys.useCollectionState);
|
||||||
|
|
||||||
const categoryId = ref<number | undefined>();
|
|
||||||
|
|
||||||
watch(categoryIdWatcher, (newCategoryId) => {
|
|
||||||
categoryId.value = newCategoryId;
|
|
||||||
}, { immediate: true });
|
|
||||||
|
|
||||||
const nodes = computed<readonly TreeInputNodeData[]>(() => {
|
const nodes = computed<readonly TreeInputNodeData[]>(() => {
|
||||||
const nodeMetadataList = parseNodes(categoryId.value, currentState.value.collection, parser);
|
const nodeMetadataList = parseNodes(categoryIdRef.value, currentState.value.collection, parser);
|
||||||
const nodeInputs = nodeMetadataList.map((node) => nodeConverter(node));
|
const nodeInputs = nodeMetadataList.map((node) => nodeConverter(node));
|
||||||
return nodeInputs;
|
return nodeInputs;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
treeViewInputNodes: nodes,
|
treeViewInputNodes: shallowReadonly(nodes),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { WatchSource, watch } from 'vue';
|
import { WatchSource, watch } from 'vue';
|
||||||
|
|
||||||
export function waitForValueChange<T>(valueWatcher: WatchSource<T>, timeoutMs = 2000): Promise<T> {
|
export function waitForValueChange<T>(
|
||||||
|
valueWatcher: WatchSource<T>,
|
||||||
|
timeoutMs = 2000,
|
||||||
|
): Promise<T> {
|
||||||
return new Promise<T>((resolve, reject) => {
|
return new Promise<T>((resolve, reject) => {
|
||||||
const unwatch = watch(valueWatcher, (newValue, oldValue) => {
|
const unwatch = watch(valueWatcher, (newValue, oldValue) => {
|
||||||
if (newValue !== oldValue) {
|
if (newValue !== oldValue) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import {
|
import {
|
||||||
shallowRef, defineComponent, WatchSource, nextTick,
|
shallowRef, defineComponent, nextTick, type Ref,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import { ReadOnlyTreeNode } from '@/presentation/components/Scripts/View/Tree/TreeView/Node/TreeNode';
|
import { ReadOnlyTreeNode } from '@/presentation/components/Scripts/View/Tree/TreeView/Node/TreeNode';
|
||||||
@@ -16,31 +16,22 @@ describe('useNodeState', () => {
|
|||||||
it('should set state on immediate invocation if node exists', () => {
|
it('should set state on immediate invocation if node exists', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expectedState = new TreeNodeStateDescriptorStub();
|
const expectedState = new TreeNodeStateDescriptorStub();
|
||||||
const nodeWatcher = shallowRef<ReadOnlyTreeNode | undefined>(undefined);
|
const nodeRef = shallowRef<ReadOnlyTreeNode>(
|
||||||
nodeWatcher.value = new TreeNodeStub()
|
new TreeNodeStub().withState(new TreeNodeStateAccessStub().withCurrent(expectedState)),
|
||||||
.withState(new TreeNodeStateAccessStub().withCurrent(expectedState));
|
);
|
||||||
// act
|
// act
|
||||||
const { returnObject } = mountWrapperComponent(nodeWatcher);
|
const { returnObject } = mountWrapperComponent(nodeRef);
|
||||||
// assert
|
// assert
|
||||||
expect(returnObject.state.value).to.equal(expectedState);
|
expect(returnObject.state.value).to.equal(expectedState);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not set state on immediate invocation if node is undefined', () => {
|
it('should update state when node changes', async () => {
|
||||||
// arrange
|
|
||||||
const nodeWatcher = shallowRef<ReadOnlyTreeNode | undefined>(undefined);
|
|
||||||
// act
|
|
||||||
const { returnObject } = mountWrapperComponent(nodeWatcher);
|
|
||||||
// assert
|
|
||||||
expect(returnObject.state.value).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update state when nodeWatcher changes', async () => {
|
|
||||||
// arrange
|
// arrange
|
||||||
const expectedNewState = new TreeNodeStateDescriptorStub();
|
const expectedNewState = new TreeNodeStateDescriptorStub();
|
||||||
const nodeWatcher = shallowRef<ReadOnlyTreeNode | undefined>(undefined);
|
const nodeRef = shallowRef<ReadOnlyTreeNode>(new TreeNodeStub());
|
||||||
const { returnObject } = mountWrapperComponent(nodeWatcher);
|
const { returnObject } = mountWrapperComponent(nodeRef);
|
||||||
// act
|
// act
|
||||||
nodeWatcher.value = new TreeNodeStub()
|
nodeRef.value = new TreeNodeStub()
|
||||||
.withState(new TreeNodeStateAccessStub().withCurrent(expectedNewState));
|
.withState(new TreeNodeStateAccessStub().withCurrent(expectedNewState));
|
||||||
await nextTick();
|
await nextTick();
|
||||||
// assert
|
// assert
|
||||||
@@ -49,30 +40,28 @@ describe('useNodeState', () => {
|
|||||||
|
|
||||||
it('should update state when node state changes', () => {
|
it('should update state when node state changes', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const nodeWatcher = shallowRef<ReadOnlyTreeNode | undefined>(undefined);
|
|
||||||
const stateAccessStub = new TreeNodeStateAccessStub();
|
const stateAccessStub = new TreeNodeStateAccessStub();
|
||||||
|
const nodeRef = shallowRef<ReadOnlyTreeNode>(
|
||||||
|
new TreeNodeStub().withState(stateAccessStub),
|
||||||
|
);
|
||||||
const expectedChangedState = new TreeNodeStateDescriptorStub();
|
const expectedChangedState = new TreeNodeStateDescriptorStub();
|
||||||
nodeWatcher.value = new TreeNodeStub()
|
|
||||||
.withState(stateAccessStub);
|
|
||||||
|
|
||||||
// act
|
// act
|
||||||
const { returnObject } = mountWrapperComponent(nodeWatcher);
|
const { returnObject } = mountWrapperComponent(nodeRef);
|
||||||
stateAccessStub.triggerStateChangedEvent(
|
stateAccessStub.triggerStateChangedEvent(
|
||||||
new NodeStateChangedEventStub()
|
new NodeStateChangedEventStub()
|
||||||
.withNewState(expectedChangedState),
|
.withNewState(expectedChangedState),
|
||||||
);
|
);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
expect(returnObject.state.value).to.equal(expectedChangedState);
|
expect(returnObject.state.value).to.equal(expectedChangedState);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function mountWrapperComponent(nodeWatcher: WatchSource<ReadOnlyTreeNode | undefined>) {
|
function mountWrapperComponent(nodeRef: Readonly<Ref<ReadOnlyTreeNode>>) {
|
||||||
let returnObject: ReturnType<typeof useNodeState>;
|
let returnObject: ReturnType<typeof useNodeState>;
|
||||||
const wrapper = shallowMount(
|
const wrapper = shallowMount(
|
||||||
defineComponent({
|
defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
returnObject = useNodeState(nodeWatcher);
|
returnObject = useNodeState(nodeRef);
|
||||||
},
|
},
|
||||||
template: '<div></div>',
|
template: '<div></div>',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { WatchSource } from 'vue';
|
import { type Ref, shallowRef } from 'vue';
|
||||||
import { useGradualNodeRendering } from '@/presentation/components/Scripts/View/Tree/TreeView/Rendering/UseGradualNodeRendering';
|
import { useGradualNodeRendering } from '@/presentation/components/Scripts/View/Tree/TreeView/Rendering/UseGradualNodeRendering';
|
||||||
import { TreeRoot } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRoot';
|
import { TreeRoot } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRoot';
|
||||||
import { TreeRootStub } from '@tests/unit/shared/Stubs/TreeRootStub';
|
import { TreeRootStub } from '@tests/unit/shared/Stubs/TreeRootStub';
|
||||||
@@ -17,18 +17,18 @@ import { RenderQueueOrdererStub } from '@tests/unit/shared/Stubs/RenderQueueOrde
|
|||||||
import { RenderQueueOrderer } from '@/presentation/components/Scripts/View/Tree/TreeView/Rendering/Ordering/RenderQueueOrderer';
|
import { RenderQueueOrderer } from '@/presentation/components/Scripts/View/Tree/TreeView/Rendering/Ordering/RenderQueueOrderer';
|
||||||
|
|
||||||
describe('useGradualNodeRendering', () => {
|
describe('useGradualNodeRendering', () => {
|
||||||
it('watches nodes on specified tree', () => {
|
it('tracks nodes on specified tree', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expectedWatcher = () => new TreeRootStub();
|
const expectedTreeRootRef = shallowRef(new TreeRootStub());
|
||||||
const currentTreeNodesStub = new UseCurrentTreeNodesStub();
|
const currentTreeNodesStub = new UseCurrentTreeNodesStub();
|
||||||
const builder = new UseGradualNodeRenderingBuilder()
|
const builder = new UseGradualNodeRenderingBuilder()
|
||||||
.withCurrentTreeNodes(currentTreeNodesStub)
|
.withCurrentTreeNodes(currentTreeNodesStub)
|
||||||
.withTreeWatcher(expectedWatcher);
|
.withTreeRootRef(expectedTreeRootRef);
|
||||||
// act
|
// act
|
||||||
builder.call();
|
builder.call();
|
||||||
// assert
|
// assert
|
||||||
const actualWatcher = currentTreeNodesStub.treeWatcher;
|
const actualTreeRootRef = currentTreeNodesStub.treeRootRef;
|
||||||
expect(actualWatcher).to.equal(expectedWatcher);
|
expect(actualTreeRootRef).to.equal(expectedTreeRootRef);
|
||||||
});
|
});
|
||||||
describe('shouldRender', () => {
|
describe('shouldRender', () => {
|
||||||
describe('on visibility toggle', () => {
|
describe('on visibility toggle', () => {
|
||||||
@@ -269,7 +269,7 @@ function createNodeWithVisibility(
|
|||||||
class UseGradualNodeRenderingBuilder {
|
class UseGradualNodeRenderingBuilder {
|
||||||
private changeAggregator = new UseNodeStateChangeAggregatorStub();
|
private changeAggregator = new UseNodeStateChangeAggregatorStub();
|
||||||
|
|
||||||
private treeWatcher: WatchSource<TreeRoot | undefined> = () => new TreeRootStub();
|
private treeRootRef: Readonly<Ref<TreeRoot>> = shallowRef(new TreeRootStub());
|
||||||
|
|
||||||
private currentTreeNodes = new UseCurrentTreeNodesStub();
|
private currentTreeNodes = new UseCurrentTreeNodesStub();
|
||||||
|
|
||||||
@@ -291,8 +291,8 @@ class UseGradualNodeRenderingBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public withTreeWatcher(treeWatcher: WatchSource<TreeRoot | undefined>): this {
|
public withTreeRootRef(treeRootRef: Readonly<Ref<TreeRoot>>): this {
|
||||||
this.treeWatcher = treeWatcher;
|
this.treeRootRef = treeRootRef;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ class UseGradualNodeRenderingBuilder {
|
|||||||
|
|
||||||
public call(): ReturnType<typeof useGradualNodeRendering> {
|
public call(): ReturnType<typeof useGradualNodeRendering> {
|
||||||
return useGradualNodeRendering(
|
return useGradualNodeRendering(
|
||||||
this.treeWatcher,
|
this.treeRootRef,
|
||||||
this.changeAggregator.get(),
|
this.changeAggregator.get(),
|
||||||
this.currentTreeNodes.get(),
|
this.currentTreeNodes.get(),
|
||||||
this.delayScheduler,
|
this.delayScheduler,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { WatchSource } from 'vue';
|
import { type Ref, shallowRef } from 'vue';
|
||||||
import { TreeRoot } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRoot';
|
import { TreeRoot } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRoot';
|
||||||
import { useAutoUpdateChildrenCheckState } from '@/presentation/components/Scripts/View/Tree/TreeView/UseAutoUpdateChildrenCheckState';
|
import { useAutoUpdateChildrenCheckState } from '@/presentation/components/Scripts/View/Tree/TreeView/UseAutoUpdateChildrenCheckState';
|
||||||
import { TreeNodeCheckState } from '@/presentation/components/Scripts/View/Tree/TreeView/Node/State/CheckState';
|
import { TreeNodeCheckState } from '@/presentation/components/Scripts/View/Tree/TreeView/Node/State/CheckState';
|
||||||
@@ -25,16 +25,16 @@ describe('useAutoUpdateChildrenCheckState', () => {
|
|||||||
});
|
});
|
||||||
it('aggregate changes on specified tree', () => {
|
it('aggregate changes on specified tree', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expectedWatcher = () => new TreeRootStub();
|
const expectedTreeRoot = shallowRef(new TreeRootStub());
|
||||||
const aggregatorStub = new UseNodeStateChangeAggregatorStub();
|
const aggregatorStub = new UseNodeStateChangeAggregatorStub();
|
||||||
const builder = new UseAutoUpdateChildrenCheckStateBuilder()
|
const builder = new UseAutoUpdateChildrenCheckStateBuilder()
|
||||||
.withChangeAggregator(aggregatorStub)
|
.withChangeAggregator(aggregatorStub)
|
||||||
.withTreeWatcher(expectedWatcher);
|
.withTreeRoot(expectedTreeRoot);
|
||||||
// act
|
// act
|
||||||
builder.call();
|
builder.call();
|
||||||
// assert
|
// assert
|
||||||
const actualWatcher = aggregatorStub.treeWatcher;
|
const actualTreeRoot = aggregatorStub.treeRootRef;
|
||||||
expect(actualWatcher).to.equal(expectedWatcher);
|
expect(actualTreeRoot).to.equal(expectedTreeRoot);
|
||||||
});
|
});
|
||||||
describe('skips event handling', () => {
|
describe('skips event handling', () => {
|
||||||
const scenarios: ReadonlyArray<{
|
const scenarios: ReadonlyArray<{
|
||||||
@@ -197,21 +197,21 @@ function getAllPossibleCheckStates() {
|
|||||||
class UseAutoUpdateChildrenCheckStateBuilder {
|
class UseAutoUpdateChildrenCheckStateBuilder {
|
||||||
private changeAggregator = new UseNodeStateChangeAggregatorStub();
|
private changeAggregator = new UseNodeStateChangeAggregatorStub();
|
||||||
|
|
||||||
private treeWatcher: WatchSource<TreeRoot | undefined> = () => new TreeRootStub();
|
private treeRoot: Readonly<Ref<TreeRoot>> = shallowRef(new TreeRootStub());
|
||||||
|
|
||||||
public withChangeAggregator(changeAggregator: UseNodeStateChangeAggregatorStub): this {
|
public withChangeAggregator(changeAggregator: UseNodeStateChangeAggregatorStub): this {
|
||||||
this.changeAggregator = changeAggregator;
|
this.changeAggregator = changeAggregator;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public withTreeWatcher(treeWatcher: WatchSource<TreeRoot | undefined>): this {
|
public withTreeRoot(treeRoot: Readonly<Ref<TreeRoot>>): this {
|
||||||
this.treeWatcher = treeWatcher;
|
this.treeRoot = treeRoot;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public call(): ReturnType<typeof useAutoUpdateChildrenCheckState> {
|
public call(): ReturnType<typeof useAutoUpdateChildrenCheckState> {
|
||||||
return useAutoUpdateChildrenCheckState(
|
return useAutoUpdateChildrenCheckState(
|
||||||
this.treeWatcher,
|
this.treeRoot,
|
||||||
this.changeAggregator.get(),
|
this.changeAggregator.get(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { WatchSource } from 'vue';
|
import { type Ref, shallowRef } from 'vue';
|
||||||
import { UseNodeStateChangeAggregatorStub } from '@tests/unit/shared/Stubs/UseNodeStateChangeAggregatorStub';
|
import { UseNodeStateChangeAggregatorStub } from '@tests/unit/shared/Stubs/UseNodeStateChangeAggregatorStub';
|
||||||
import { TreeRootStub } from '@tests/unit/shared/Stubs/TreeRootStub';
|
import { TreeRootStub } from '@tests/unit/shared/Stubs/TreeRootStub';
|
||||||
import { useAutoUpdateParentCheckState } from '@/presentation/components/Scripts/View/Tree/TreeView/UseAutoUpdateParentCheckState';
|
import { useAutoUpdateParentCheckState } from '@/presentation/components/Scripts/View/Tree/TreeView/UseAutoUpdateParentCheckState';
|
||||||
@@ -24,16 +24,16 @@ describe('useAutoUpdateParentCheckState', () => {
|
|||||||
});
|
});
|
||||||
it('aggregate changes on specified tree', () => {
|
it('aggregate changes on specified tree', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expectedWatcher = () => new TreeRootStub();
|
const expectedTreeRootRef = shallowRef(new TreeRootStub());
|
||||||
const aggregatorStub = new UseNodeStateChangeAggregatorStub();
|
const aggregatorStub = new UseNodeStateChangeAggregatorStub();
|
||||||
const builder = new UseAutoUpdateParentCheckStateBuilder()
|
const builder = new UseAutoUpdateParentCheckStateBuilder()
|
||||||
.withChangeAggregator(aggregatorStub)
|
.withChangeAggregator(aggregatorStub)
|
||||||
.withTreeWatcher(expectedWatcher);
|
.withTreeRootRef(expectedTreeRootRef);
|
||||||
// act
|
// act
|
||||||
builder.call();
|
builder.call();
|
||||||
// assert
|
// assert
|
||||||
const actualWatcher = aggregatorStub.treeWatcher;
|
const actualTreeRootRef = aggregatorStub.treeRootRef;
|
||||||
expect(actualWatcher).to.equal(expectedWatcher);
|
expect(actualTreeRootRef).to.equal(expectedTreeRootRef);
|
||||||
});
|
});
|
||||||
it('does not throw if node has no parent', () => {
|
it('does not throw if node has no parent', () => {
|
||||||
// arrange
|
// arrange
|
||||||
@@ -185,21 +185,21 @@ describe('useAutoUpdateParentCheckState', () => {
|
|||||||
class UseAutoUpdateParentCheckStateBuilder {
|
class UseAutoUpdateParentCheckStateBuilder {
|
||||||
private changeAggregator = new UseNodeStateChangeAggregatorStub();
|
private changeAggregator = new UseNodeStateChangeAggregatorStub();
|
||||||
|
|
||||||
private treeWatcher: WatchSource<TreeRoot | undefined> = () => new TreeRootStub();
|
private treeRootRef: Readonly<Ref<TreeRoot>> = shallowRef(new TreeRootStub());
|
||||||
|
|
||||||
public withChangeAggregator(changeAggregator: UseNodeStateChangeAggregatorStub): this {
|
public withChangeAggregator(changeAggregator: UseNodeStateChangeAggregatorStub): this {
|
||||||
this.changeAggregator = changeAggregator;
|
this.changeAggregator = changeAggregator;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public withTreeWatcher(treeWatcher: WatchSource<TreeRoot | undefined>): this {
|
public withTreeRootRef(treeRootRef: Readonly<Ref<TreeRoot>>): this {
|
||||||
this.treeWatcher = treeWatcher;
|
this.treeRootRef = treeRootRef;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public call(): ReturnType<typeof useAutoUpdateParentCheckState> {
|
public call(): ReturnType<typeof useAutoUpdateParentCheckState> {
|
||||||
return useAutoUpdateParentCheckState(
|
return useAutoUpdateParentCheckState(
|
||||||
this.treeWatcher,
|
this.treeRootRef,
|
||||||
this.changeAggregator.get(),
|
this.changeAggregator.get(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import {
|
import {
|
||||||
shallowRef, defineComponent, WatchSource, nextTick,
|
shallowRef, defineComponent, nextTick, type Ref,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import { TreeRoot } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRoot';
|
import { TreeRoot } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRoot';
|
||||||
@@ -15,25 +15,25 @@ describe('useCurrentTreeNodes', () => {
|
|||||||
it('should set nodes on immediate invocation', () => {
|
it('should set nodes on immediate invocation', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expectedNodes = new QueryableNodesStub();
|
const expectedNodes = new QueryableNodesStub();
|
||||||
const treeWatcher = shallowRef<TreeRoot>(new TreeRootStub().withCollection(
|
const treeRootRef = shallowRef(new TreeRootStub().withCollection(
|
||||||
new TreeNodeCollectionStub().withNodes(expectedNodes),
|
new TreeNodeCollectionStub().withNodes(expectedNodes),
|
||||||
));
|
));
|
||||||
// act
|
// act
|
||||||
const { returnObject } = mountWrapperComponent(treeWatcher);
|
const { returnObject } = mountWrapperComponent(treeRootRef);
|
||||||
// assert
|
// assert
|
||||||
expect(returnObject.nodes.value).to.deep.equal(expectedNodes);
|
expect(returnObject.nodes.value).to.deep.equal(expectedNodes);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update nodes when treeWatcher changes', async () => {
|
it('should update nodes when tree root changes', async () => {
|
||||||
// arrange
|
// arrange
|
||||||
const initialNodes = new QueryableNodesStub();
|
const initialNodes = new QueryableNodesStub();
|
||||||
const treeWatcher = shallowRef(
|
const treeRootRef = shallowRef(
|
||||||
new TreeRootStub().withCollection(new TreeNodeCollectionStub().withNodes(initialNodes)),
|
new TreeRootStub().withCollection(new TreeNodeCollectionStub().withNodes(initialNodes)),
|
||||||
);
|
);
|
||||||
const { returnObject } = mountWrapperComponent(treeWatcher);
|
const { returnObject } = mountWrapperComponent(treeRootRef);
|
||||||
const newExpectedNodes = new QueryableNodesStub();
|
const newExpectedNodes = new QueryableNodesStub();
|
||||||
// act
|
// act
|
||||||
treeWatcher.value = new TreeRootStub().withCollection(
|
treeRootRef.value = new TreeRootStub().withCollection(
|
||||||
new TreeNodeCollectionStub().withNodes(newExpectedNodes),
|
new TreeNodeCollectionStub().withNodes(newExpectedNodes),
|
||||||
);
|
);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
@@ -45,9 +45,9 @@ describe('useCurrentTreeNodes', () => {
|
|||||||
// arrange
|
// arrange
|
||||||
const initialNodes = new QueryableNodesStub();
|
const initialNodes = new QueryableNodesStub();
|
||||||
const treeCollectionStub = new TreeNodeCollectionStub().withNodes(initialNodes);
|
const treeCollectionStub = new TreeNodeCollectionStub().withNodes(initialNodes);
|
||||||
const treeWatcher = shallowRef(new TreeRootStub().withCollection(treeCollectionStub));
|
const treeRootRef = shallowRef(new TreeRootStub().withCollection(treeCollectionStub));
|
||||||
|
|
||||||
const { returnObject } = mountWrapperComponent(treeWatcher);
|
const { returnObject } = mountWrapperComponent(treeRootRef);
|
||||||
|
|
||||||
const newExpectedNodes = new QueryableNodesStub();
|
const newExpectedNodes = new QueryableNodesStub();
|
||||||
// act
|
// act
|
||||||
@@ -58,12 +58,12 @@ describe('useCurrentTreeNodes', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function mountWrapperComponent(treeWatcher: WatchSource<TreeRoot | undefined>) {
|
function mountWrapperComponent(treeRootRef: Ref<TreeRoot>) {
|
||||||
let returnObject: ReturnType<typeof useCurrentTreeNodes>;
|
let returnObject: ReturnType<typeof useCurrentTreeNodes>;
|
||||||
const wrapper = shallowMount(
|
const wrapper = shallowMount(
|
||||||
defineComponent({
|
defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
returnObject = useCurrentTreeNodes(treeWatcher);
|
returnObject = useCurrentTreeNodes(treeRootRef);
|
||||||
},
|
},
|
||||||
template: '<div></div>',
|
template: '<div></div>',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { WatchSource, defineComponent, nextTick } from 'vue';
|
import { defineComponent, nextTick, shallowRef } from 'vue';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import { TreeRoot } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRoot';
|
import { TreeRoot } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRoot';
|
||||||
import { useCurrentTreeNodes } from '@/presentation/components/Scripts/View/Tree/TreeView/UseCurrentTreeNodes';
|
import { useCurrentTreeNodes } from '@/presentation/components/Scripts/View/Tree/TreeView/UseCurrentTreeNodes';
|
||||||
@@ -17,20 +17,21 @@ import { TreeNodeStateDescriptorStub } from '@tests/unit/shared/Stubs/TreeNodeSt
|
|||||||
import { TreeNodeStateAccessStub } from '@tests/unit/shared/Stubs/TreeNodeStateAccessStub';
|
import { TreeNodeStateAccessStub } from '@tests/unit/shared/Stubs/TreeNodeStateAccessStub';
|
||||||
import { IEventSubscriptionCollection } from '@/infrastructure/Events/IEventSubscriptionCollection';
|
import { IEventSubscriptionCollection } from '@/infrastructure/Events/IEventSubscriptionCollection';
|
||||||
import { FunctionKeys } from '@/TypeHelpers';
|
import { FunctionKeys } from '@/TypeHelpers';
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
describe('useNodeStateChangeAggregator', () => {
|
describe('useNodeStateChangeAggregator', () => {
|
||||||
it('watches nodes on specified tree', () => {
|
it('tracks nodes on specified tree', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expectedWatcher = () => new TreeRootStub();
|
const expectedTreeRootRef = shallowRef(new TreeRootStub());
|
||||||
const currentTreeNodesStub = new UseCurrentTreeNodesStub();
|
const currentTreeNodesStub = new UseCurrentTreeNodesStub();
|
||||||
const builder = new UseNodeStateChangeAggregatorBuilder()
|
const builder = new UseNodeStateChangeAggregatorBuilder()
|
||||||
.withCurrentTreeNodes(currentTreeNodesStub.get())
|
.withCurrentTreeNodes(currentTreeNodesStub.get())
|
||||||
.withTreeWatcher(expectedWatcher);
|
.withTreeRootRef(expectedTreeRootRef);
|
||||||
// act
|
// act
|
||||||
builder.mountWrapperComponent();
|
builder.mountWrapperComponent();
|
||||||
// assert
|
// assert
|
||||||
const actualWatcher = currentTreeNodesStub.treeWatcher;
|
const actualTreeRootRef = currentTreeNodesStub.treeRootRef;
|
||||||
expect(actualWatcher).to.equal(expectedWatcher);
|
expect(actualTreeRootRef).to.equal(expectedTreeRootRef);
|
||||||
});
|
});
|
||||||
describe('onNodeStateChange', () => {
|
describe('onNodeStateChange', () => {
|
||||||
describe('throws if callback is absent', () => {
|
describe('throws if callback is absent', () => {
|
||||||
@@ -302,14 +303,14 @@ function createFlatCollection(nodes: readonly TreeNode[]): QueryableNodesStub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class UseNodeStateChangeAggregatorBuilder {
|
class UseNodeStateChangeAggregatorBuilder {
|
||||||
private treeWatcher: WatchSource<TreeRoot | undefined> = () => new TreeRootStub();
|
private treeRootRef: Readonly<Ref<TreeRoot>> = shallowRef(new TreeRootStub());
|
||||||
|
|
||||||
private currentTreeNodes: typeof useCurrentTreeNodes = new UseCurrentTreeNodesStub().get();
|
private currentTreeNodes: typeof useCurrentTreeNodes = new UseCurrentTreeNodesStub().get();
|
||||||
|
|
||||||
private events: UseAutoUnsubscribedEventsStub = new UseAutoUnsubscribedEventsStub();
|
private events: UseAutoUnsubscribedEventsStub = new UseAutoUnsubscribedEventsStub();
|
||||||
|
|
||||||
public withTreeWatcher(treeWatcher: WatchSource<TreeRoot | undefined>): this {
|
public withTreeRootRef(treeRootRef: Readonly<Ref<TreeRoot>>): this {
|
||||||
this.treeWatcher = treeWatcher;
|
this.treeRootRef = treeRootRef;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,11 +326,11 @@ class UseNodeStateChangeAggregatorBuilder {
|
|||||||
|
|
||||||
public mountWrapperComponent() {
|
public mountWrapperComponent() {
|
||||||
let returnObject: ReturnType<typeof useNodeStateChangeAggregator>;
|
let returnObject: ReturnType<typeof useNodeStateChangeAggregator>;
|
||||||
const { treeWatcher, currentTreeNodes } = this;
|
const { treeRootRef, currentTreeNodes } = this;
|
||||||
const wrapper = shallowMount(
|
const wrapper = shallowMount(
|
||||||
defineComponent({
|
defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
returnObject = useNodeStateChangeAggregator(treeWatcher, currentTreeNodes);
|
returnObject = useNodeStateChangeAggregator(treeRootRef, currentTreeNodes);
|
||||||
},
|
},
|
||||||
template: '<div></div>',
|
template: '<div></div>',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { WatchSource, ref, nextTick } from 'vue';
|
import { ref, nextTick, type Ref } from 'vue';
|
||||||
import { CategoryNodeParser, useTreeViewNodeInput } from '@/presentation/components/Scripts/View/Tree/TreeViewAdapter/UseTreeViewNodeInput';
|
import { CategoryNodeParser, useTreeViewNodeInput } from '@/presentation/components/Scripts/View/Tree/TreeViewAdapter/UseTreeViewNodeInput';
|
||||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||||
import { CategoryCollectionStateStub } from '@tests/unit/shared/Stubs/CategoryCollectionStateStub';
|
import { CategoryCollectionStateStub } from '@tests/unit/shared/Stubs/CategoryCollectionStateStub';
|
||||||
@@ -17,12 +17,10 @@ describe('useTreeViewNodeInput', () => {
|
|||||||
describe('when given categoryId', () => {
|
describe('when given categoryId', () => {
|
||||||
it('sets input nodes correctly', async () => {
|
it('sets input nodes correctly', async () => {
|
||||||
// arrange
|
// arrange
|
||||||
const testCategoryId = ref<number | undefined>();
|
const testCategoryIdRef = ref<number | undefined>();
|
||||||
const {
|
const {
|
||||||
useStateStub, returnObject, parserMock, converterMock,
|
useStateStub, returnObject, parserMock, converterMock,
|
||||||
} = mountWrapperComponent(
|
} = mountWrapperComponent(testCategoryIdRef);
|
||||||
() => testCategoryId.value,
|
|
||||||
);
|
|
||||||
const expectedCategoryId = 123;
|
const expectedCategoryId = 123;
|
||||||
const expectedCategoryCollection = new CategoryCollectionStub().withAction(
|
const expectedCategoryCollection = new CategoryCollectionStub().withAction(
|
||||||
new CategoryStub(expectedCategoryId),
|
new CategoryStub(expectedCategoryId),
|
||||||
@@ -45,7 +43,7 @@ describe('useTreeViewNodeInput', () => {
|
|||||||
);
|
);
|
||||||
// act
|
// act
|
||||||
const { treeViewInputNodes } = returnObject;
|
const { treeViewInputNodes } = returnObject;
|
||||||
testCategoryId.value = expectedCategoryId;
|
testCategoryIdRef.value = expectedCategoryId;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
// assert
|
// assert
|
||||||
const actualInputNodes = treeViewInputNodes.value;
|
const actualInputNodes = treeViewInputNodes.value;
|
||||||
@@ -60,9 +58,7 @@ describe('useTreeViewNodeInput', () => {
|
|||||||
const testCategoryId = ref<number | undefined>();
|
const testCategoryId = ref<number | undefined>();
|
||||||
const {
|
const {
|
||||||
useStateStub, returnObject, parserMock, converterMock,
|
useStateStub, returnObject, parserMock, converterMock,
|
||||||
} = mountWrapperComponent(
|
} = mountWrapperComponent(testCategoryId);
|
||||||
() => testCategoryId.value,
|
|
||||||
);
|
|
||||||
const expectedCategoryCollection = new CategoryCollectionStub().withAction(
|
const expectedCategoryCollection = new CategoryCollectionStub().withAction(
|
||||||
new CategoryStub(123),
|
new CategoryStub(123),
|
||||||
);
|
);
|
||||||
@@ -92,7 +88,7 @@ describe('useTreeViewNodeInput', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function mountWrapperComponent(categoryIdWatcher: WatchSource<number | undefined>) {
|
function mountWrapperComponent(categoryIdRef: Ref<number | undefined>) {
|
||||||
const useStateStub = new UseCollectionStateStub();
|
const useStateStub = new UseCollectionStateStub();
|
||||||
const parserMock = mockCategoryNodeParser();
|
const parserMock = mockCategoryNodeParser();
|
||||||
const converterMock = mockConverter();
|
const converterMock = mockConverter();
|
||||||
@@ -100,7 +96,7 @@ function mountWrapperComponent(categoryIdWatcher: WatchSource<number | undefined
|
|||||||
|
|
||||||
shallowMount({
|
shallowMount({
|
||||||
setup() {
|
setup() {
|
||||||
returnObject = useTreeViewNodeInput(categoryIdWatcher, parserMock.mock, converterMock.mock);
|
returnObject = useTreeViewNodeInput(categoryIdRef, parserMock.mock, converterMock.mock);
|
||||||
},
|
},
|
||||||
template: '<div></div>',
|
template: '<div></div>',
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
WatchSource, shallowReadonly, shallowRef, triggerRef,
|
type Ref, shallowReadonly, shallowRef, triggerRef,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { TreeRoot } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRoot';
|
import { TreeRoot } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRoot';
|
||||||
import { useCurrentTreeNodes } from '@/presentation/components/Scripts/View/Tree/TreeView/UseCurrentTreeNodes';
|
import { useCurrentTreeNodes } from '@/presentation/components/Scripts/View/Tree/TreeView/UseCurrentTreeNodes';
|
||||||
@@ -7,7 +7,7 @@ import { QueryableNodes } from '@/presentation/components/Scripts/View/Tree/Tree
|
|||||||
import { QueryableNodesStub } from './QueryableNodesStub';
|
import { QueryableNodesStub } from './QueryableNodesStub';
|
||||||
|
|
||||||
export class UseCurrentTreeNodesStub {
|
export class UseCurrentTreeNodesStub {
|
||||||
public treeWatcher: WatchSource<TreeRoot> | undefined;
|
public treeRootRef: Readonly<Ref<TreeRoot>> | undefined;
|
||||||
|
|
||||||
private nodes = shallowRef<QueryableNodes>(new QueryableNodesStub());
|
private nodes = shallowRef<QueryableNodes>(new QueryableNodesStub());
|
||||||
|
|
||||||
@@ -22,8 +22,8 @@ export class UseCurrentTreeNodesStub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get(): typeof useCurrentTreeNodes {
|
public get(): typeof useCurrentTreeNodes {
|
||||||
return (treeWatcher: WatchSource<TreeRoot>) => {
|
return (treeRootRef: Readonly<Ref<TreeRoot>>) => {
|
||||||
this.treeWatcher = treeWatcher;
|
this.treeRootRef = treeRootRef;
|
||||||
return {
|
return {
|
||||||
nodes: shallowReadonly(this.nodes),
|
nodes: shallowReadonly(this.nodes),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { WatchSource } from 'vue';
|
|
||||||
import { TreeRoot } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRoot';
|
import { TreeRoot } from '@/presentation/components/Scripts/View/Tree/TreeView/TreeRoot/TreeRoot';
|
||||||
import {
|
import {
|
||||||
NodeStateChangeEventArgs,
|
NodeStateChangeEventArgs,
|
||||||
NodeStateChangeEventCallback,
|
NodeStateChangeEventCallback,
|
||||||
useNodeStateChangeAggregator,
|
useNodeStateChangeAggregator,
|
||||||
} from '@/presentation/components/Scripts/View/Tree/TreeView/UseNodeStateChangeAggregator';
|
} from '@/presentation/components/Scripts/View/Tree/TreeView/UseNodeStateChangeAggregator';
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
export class UseNodeStateChangeAggregatorStub {
|
export class UseNodeStateChangeAggregatorStub {
|
||||||
public callback: NodeStateChangeEventCallback | undefined;
|
public callback: NodeStateChangeEventCallback | undefined;
|
||||||
|
|
||||||
public treeWatcher: WatchSource<TreeRoot> | undefined;
|
public treeRootRef: Readonly<Ref<TreeRoot>> | undefined;
|
||||||
|
|
||||||
public onNodeStateChange(callback: NodeStateChangeEventCallback) {
|
public onNodeStateChange(callback: NodeStateChangeEventCallback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
@@ -23,8 +23,8 @@ export class UseNodeStateChangeAggregatorStub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get(): typeof useNodeStateChangeAggregator {
|
public get(): typeof useNodeStateChangeAggregator {
|
||||||
return (treeWatcher: WatchSource<TreeRoot>) => {
|
return (treeRootRef: Readonly<Ref<TreeRoot>>) => {
|
||||||
this.treeWatcher = treeWatcher;
|
this.treeRootRef = treeRootRef;
|
||||||
return {
|
return {
|
||||||
onNodeStateChange: this.onNodeStateChange.bind(this),
|
onNodeStateChange: this.onNodeStateChange.bind(this),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user