import { describe, it, expect, } from 'vitest'; import { mount } from '@vue/test-utils'; import { defineComponent, shallowRef } from 'vue'; import TreeView from '@/presentation/components/Scripts/View/Tree/TreeView/TreeView.vue'; import type { TreeInputNodeData } from '@/presentation/components/Scripts/View/Tree/TreeView/Bindings/TreeInputNodeData'; import { provideDependencies } from '@/presentation/bootstrapping/DependencyProvider'; import { ApplicationContextStub } from '@tests/unit/shared/Stubs/ApplicationContextStub'; describe('TreeView', () => { it('renders all provided root nodes correctly', async () => { // arrange const nodes = createSampleNodes(); const { wrapper } = mountWrapperComponent({ initialNodeData: nodes, }); // act await waitForStableDom(wrapper.element); // assert const expectedTotalRootNodes = nodes.length; expect(wrapper.findAll('.node').length).to.equal(expectedTotalRootNodes, wrapper.html()); const rootNodeTexts = nodes.map((node) => (node.data as TreeInputMetadata).label); rootNodeTexts.forEach((label) => { expect(wrapper.text()).to.include(label); }); }); // Regression test for a bug where updating the nodes prop caused uncaught exceptions. it('updates nodes correctly when props change', async () => { // arrange const firstNodeLabel = 'Node 1'; const secondNodeLabel = 'Node 2'; const initialNodes: TreeInputNodeDataWithMetadata[] = [{ id: 'node1', data: { label: firstNodeLabel } }]; const updatedNodes: TreeInputNodeDataWithMetadata[] = [{ id: 'node2', data: { label: secondNodeLabel } }]; const { wrapper, nodes } = mountWrapperComponent({ initialNodeData: initialNodes, }); // act nodes.value = updatedNodes; await waitForStableDom(wrapper.element); // assert expect(wrapper.text()).toContain(secondNodeLabel); expect(wrapper.text()).not.toContain(firstNodeLabel); }); }); function mountWrapperComponent(options?: { readonly initialNodeData?: readonly TreeInputNodeDataWithMetadata[], }) { const nodes = shallowRef(options?.initialNodeData ?? createSampleNodes()); const wrapper = mount(defineComponent({ components: { TreeView, }, setup() { provideDependencies(new ApplicationContextStub()); const selectedLeafNodeIds = shallowRef([]); return { nodes, selectedLeafNodeIds, }; }, template: ` `, })); return { wrapper, nodes, }; } interface TreeInputMetadata { readonly label: string; } type TreeInputNodeDataWithMetadata = TreeInputNodeData & { readonly data?: TreeInputMetadata }; function createSampleNodes(): TreeInputNodeDataWithMetadata[] { return [ { id: 'root1', data: { label: 'Root 1', }, children: [ { id: 'child1', data: { label: 'Child 1', }, }, { id: 'child2', data: { label: 'Child 2', }, }, ], }, { id: 'root2', data: { label: 'Root 2', }, children: [ { id: 'child3', data: { label: 'Child 3', }, }, ], }, ]; } function waitForStableDom(rootElement, timeout = 3000, interval = 200): Promise { return new Promise((resolve, reject) => { let lastTimeoutId: ReturnType; const observer = new MutationObserver(() => { if (lastTimeoutId) { clearTimeout(lastTimeoutId); } lastTimeoutId = setTimeout(() => { observer.disconnect(); resolve(); }, interval); }); observer.observe(rootElement, { attributes: true, childList: true, subtree: true, characterData: true, }); setTimeout(() => { observer.disconnect(); reject(new Error('Timeout waiting for DOM to stabilize')); }, timeout); }); }