From 773f59c4a10e9d75d24d99d45e760216132b9b20 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Sun, 5 Apr 2026 08:36:26 -0700 Subject: [PATCH 1/4] fix(ui): preserve root dir state when sibling dir is expanded - Recompute expanded state after toggling or auto-expanding directories - Only auto-expand ancestors for the top-level tree - Add regression coverage for sibling directory expansion --- app/components/Code/FileTree.vue | 2 +- app/composables/useFileTreeState.ts | 2 + test/nuxt/components/CodeFileTree.spec.ts | 89 +++++++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 test/nuxt/components/CodeFileTree.spec.ts diff --git a/app/components/Code/FileTree.vue b/app/components/Code/FileTree.vue index ae34449680..1959b83b1f 100644 --- a/app/components/Code/FileTree.vue +++ b/app/components/Code/FileTree.vue @@ -39,7 +39,7 @@ const { toggleDir, isExpanded, autoExpandAncestors } = useFileTreeState(props.ba watch( () => props.currentPath, path => { - if (path) { + if (depth.value === 0 && path) { autoExpandAncestors(path) } }, diff --git a/app/composables/useFileTreeState.ts b/app/composables/useFileTreeState.ts index 7c49eac5ca..7b27018e8a 100644 --- a/app/composables/useFileTreeState.ts +++ b/app/composables/useFileTreeState.ts @@ -7,6 +7,7 @@ export function useFileTreeState(baseUrl: string) { } else { expanded.value.add(path) } + expanded.value = new Set(expanded.value) } function isExpanded(path: string) { @@ -21,6 +22,7 @@ export function useFileTreeState(baseUrl: string) { prefix = prefix ? `${prefix}/${part}` : part expanded.value.add(prefix) } + expanded.value = new Set(expanded.value) } return { diff --git a/test/nuxt/components/CodeFileTree.spec.ts b/test/nuxt/components/CodeFileTree.spec.ts new file mode 100644 index 0000000000..c34dd93d03 --- /dev/null +++ b/test/nuxt/components/CodeFileTree.spec.ts @@ -0,0 +1,89 @@ +import { mountSuspended } from '@nuxt/test-utils/runtime' +import { describe, expect, it, vi } from 'vitest' +import CodeFileTree from '~/components/Code/FileTree.vue' + +const mockTree: PackageFileTree[] = [ + { + name: 'distribution', + type: 'directory', + path: 'distribution', + children: [ + { + name: 'core', + type: 'directory', + path: 'distribution/core', + children: [ + { + name: 'constants.d.ts', + type: 'file', + path: 'distribution/core/constants.d.ts', + }, + ], + }, + { + name: 'types', + type: 'directory', + path: 'distribution/types', + children: [ + { + name: 'common.d.ts', + type: 'file', + path: 'distribution/types/common.d.ts', + }, + ], + }, + ], + }, +] + +function findDirButton(wrapper: Awaited>, name: string) { + return wrapper.findAll('button').find(button => button.text().trim() === name) +} + +async function mountCodeFileTree() { + return mountSuspended(CodeFileTree, { + attachTo: document.body, + props: { + tree: mockTree, + currentPath: 'distribution/core/constants.d.ts', + baseUrl: '/package-code/ky/v/1.14.3/distribution/core/constants.d.ts?test=tree', + baseRoute: { + params: { + packageName: 'ky', + version: '1.14.3', + filePath: '', + }, + }, + }, + }) +} + +describe('CodeFileTree', () => { + it('keeps a collapsed sibling directory closed when another sibling expands', async () => { + const wrapper = await mountCodeFileTree() + + await vi.waitFor(() => { + expect(wrapper.text()).toContain('constants.d.ts') + expect(wrapper.text()).not.toContain('common.d.ts') + }) + + const coreButton = findDirButton(wrapper, 'core') + expect(coreButton).toBeDefined() + await coreButton!.trigger('click') + + await vi.waitFor(() => { + expect(wrapper.text()).not.toContain('constants.d.ts') + }) + + const typesButton = findDirButton(wrapper, 'types') + expect(typesButton).toBeDefined() + await typesButton!.trigger('click') + + await vi.waitFor(() => { + expect(wrapper.text()).toContain('common.d.ts') + expect(wrapper.text()).not.toContain('constants.d.ts') + }) + + wrapper.unmount() + }) +}) From 05dcb211cb1015ad2ce07ac02597932a8d4f55d0 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:58:14 -0700 Subject: [PATCH 2/4] test: rename to generic expand/collapse tests --- test/nuxt/components/CodeFileTree.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/nuxt/components/CodeFileTree.spec.ts b/test/nuxt/components/CodeFileTree.spec.ts index c34dd93d03..e0180b18f3 100644 --- a/test/nuxt/components/CodeFileTree.spec.ts +++ b/test/nuxt/components/CodeFileTree.spec.ts @@ -59,7 +59,7 @@ async function mountCodeFileTree() { } describe('CodeFileTree', () => { - it('keeps a collapsed sibling directory closed when another sibling expands', async () => { + it('expands and collapses a directory when clicked', async () => { const wrapper = await mountCodeFileTree() await vi.waitFor(() => { @@ -73,6 +73,7 @@ describe('CodeFileTree', () => { await vi.waitFor(() => { expect(wrapper.text()).not.toContain('constants.d.ts') + expect(wrapper.text()).not.toContain('common.d.ts') }) const typesButton = findDirButton(wrapper, 'types') From f13968d59b32474bd7b33d27352b7b8e1ed0d7d9 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:02:27 -0700 Subject: [PATCH 3/4] test: move unmount() to finally block --- test/nuxt/components/CodeFileTree.spec.ts | 43 ++++++++++++----------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/test/nuxt/components/CodeFileTree.spec.ts b/test/nuxt/components/CodeFileTree.spec.ts index e0180b18f3..20e067f952 100644 --- a/test/nuxt/components/CodeFileTree.spec.ts +++ b/test/nuxt/components/CodeFileTree.spec.ts @@ -61,30 +61,31 @@ async function mountCodeFileTree() { describe('CodeFileTree', () => { it('expands and collapses a directory when clicked', async () => { const wrapper = await mountCodeFileTree() + try { + await vi.waitFor(() => { + expect(wrapper.text()).toContain('constants.d.ts') + expect(wrapper.text()).not.toContain('common.d.ts') + }) - await vi.waitFor(() => { - expect(wrapper.text()).toContain('constants.d.ts') - expect(wrapper.text()).not.toContain('common.d.ts') - }) + const coreButton = findDirButton(wrapper, 'core') + expect(coreButton).toBeDefined() + await coreButton!.trigger('click') - const coreButton = findDirButton(wrapper, 'core') - expect(coreButton).toBeDefined() - await coreButton!.trigger('click') + await vi.waitFor(() => { + expect(wrapper.text()).not.toContain('constants.d.ts') + expect(wrapper.text()).not.toContain('common.d.ts') + }) - await vi.waitFor(() => { - expect(wrapper.text()).not.toContain('constants.d.ts') - expect(wrapper.text()).not.toContain('common.d.ts') - }) + const typesButton = findDirButton(wrapper, 'types') + expect(typesButton).toBeDefined() + await typesButton!.trigger('click') - const typesButton = findDirButton(wrapper, 'types') - expect(typesButton).toBeDefined() - await typesButton!.trigger('click') - - await vi.waitFor(() => { - expect(wrapper.text()).toContain('common.d.ts') - expect(wrapper.text()).not.toContain('constants.d.ts') - }) - - wrapper.unmount() + await vi.waitFor(() => { + expect(wrapper.text()).toContain('common.d.ts') + expect(wrapper.text()).not.toContain('constants.d.ts') + }) + } finally { + wrapper.unmount() + } }) }) From 35073d6548f1e9c66ec40f8041b99b5e146b2208 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:16:30 -0700 Subject: [PATCH 4/4] fix: remove redundant Set reassignments in file tree state --- app/composables/useFileTreeState.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/composables/useFileTreeState.ts b/app/composables/useFileTreeState.ts index 7b27018e8a..7c49eac5ca 100644 --- a/app/composables/useFileTreeState.ts +++ b/app/composables/useFileTreeState.ts @@ -7,7 +7,6 @@ export function useFileTreeState(baseUrl: string) { } else { expanded.value.add(path) } - expanded.value = new Set(expanded.value) } function isExpanded(path: string) { @@ -22,7 +21,6 @@ export function useFileTreeState(baseUrl: string) { prefix = prefix ? `${prefix}/${part}` : part expanded.value.add(prefix) } - expanded.value = new Set(expanded.value) } return {