From 1564df434dad480369a134a690766645a053ef06 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sun, 30 Aug 2026 10:29:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=E4=BF=AE=E5=A4=8D=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=A0=91=E7=82=B9=E5=87=BB=E5=88=87=E6=8D=A2=E7=AB=9E?= =?UTF-8?q?=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/workspace/FileTreePanel.spec.ts | 57 +++++++++++++++++++ .../src/features/workspace/FileTreePanel.vue | 13 ++++- .../features/workspace/WorkspaceView.spec.ts | 32 +++++++++++ .../src/features/workspace/WorkspaceView.vue | 5 +- 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 frontend/src/features/workspace/FileTreePanel.spec.ts create mode 100644 frontend/src/features/workspace/WorkspaceView.spec.ts diff --git a/frontend/src/features/workspace/FileTreePanel.spec.ts b/frontend/src/features/workspace/FileTreePanel.spec.ts new file mode 100644 index 0000000..dfbd8d0 --- /dev/null +++ b/frontend/src/features/workspace/FileTreePanel.spec.ts @@ -0,0 +1,57 @@ +// @vitest-environment happy-dom +import { afterEach, beforeEach, describe, expect, it } from 'vitest' +import { mount, type VueWrapper } from '@vue/test-utils' +import { createPinia, setActivePinia } from 'pinia' +import { createMemoryHistory, createRouter } from 'vue-router' +import FileTreePanel from './FileTreePanel.vue' +import { useEditorStore } from '@/stores/editor' +import { useWorkspaceStore } from '@/stores/workspace' + +let wrapper: VueWrapper | null = null + +async function waitForPath(path: string) { + const editorStore = useEditorStore() + for (let attempt = 0; attempt < 100; attempt++) { + if (editorStore.currentFilePath === path) return + await new Promise((resolve) => setTimeout(resolve, 10)) + } + throw new Error(`Editor did not open ${path}`) +} + +beforeEach(() => { + localStorage.clear() + setActivePinia(createPinia()) +}) + +afterEach(() => { + wrapper?.unmount() + wrapper = null + document.body.innerHTML = '' +}) + +describe('FileTreePanel file switching', () => { + it('switches both workspace selection and editor content on consecutive clicks', async () => { + const router = createRouter({ + history: createMemoryHistory(), + routes: [{ path: '/workspace', component: { template: '
' } }], + }) + await router.push('/workspace') + await router.isReady() + + const workspaceStore = useWorkspaceStore() + const editorStore = useEditorStore() + await workspaceStore.openVault('/mock-vault') + wrapper = mount(FileTreePanel, { attachTo: document.body, global: { plugins: [router] } }) + + const findNode = (name: string) => wrapper!.findAll('.tree-node').find((node) => node.text().includes(name))! + await findNode('红黑树.md').trigger('click') + await waitForPath('/数据结构/红黑树.md') + expect(workspaceStore.activeFilePath).toBe('/数据结构/红黑树.md') + expect(editorStore.content).toContain('# 红黑树') + + await findNode('二叉搜索树.md').trigger('click') + await waitForPath('/数据结构/二叉搜索树.md') + expect(workspaceStore.activeFilePath).toBe('/数据结构/二叉搜索树.md') + expect(editorStore.content).toContain('# 二叉搜索树') + }) +}) diff --git a/frontend/src/features/workspace/FileTreePanel.vue b/frontend/src/features/workspace/FileTreePanel.vue index f7c109e..7139ea4 100644 --- a/frontend/src/features/workspace/FileTreePanel.vue +++ b/frontend/src/features/workspace/FileTreePanel.vue @@ -44,9 +44,18 @@ async function createItem() { async function openNode(node: FileNode) { if (node.type === 'folder') return workspaceStore.toggleFolder(node.path) - await editorStore.loadFile(node.path) + // 先同步活动文件,让真实点击立即生效;内容加载失败时再恢复原状态。 + const previousPath = workspaceStore.activeFilePath + const wasOpen = workspaceStore.openFiles.includes(node.path) workspaceStore.openFile(node.path) - await router.push('/workspace') + try { + await editorStore.loadFile(node.path) + await router.push('/workspace') + } catch (error) { + if (!wasOpen) workspaceStore.closeFile(node.path) + workspaceStore.setActiveFile(previousPath) + console.error(`打开文件失败:${node.path}`, error) + } } function openContextMenu(event: MouseEvent, node: FileNode) { diff --git a/frontend/src/features/workspace/WorkspaceView.spec.ts b/frontend/src/features/workspace/WorkspaceView.spec.ts new file mode 100644 index 0000000..15fdb86 --- /dev/null +++ b/frontend/src/features/workspace/WorkspaceView.spec.ts @@ -0,0 +1,32 @@ +// @vitest-environment happy-dom +import { afterEach, beforeEach, describe, expect, it } from 'vitest' +import { mount, type VueWrapper } from '@vue/test-utils' +import { createPinia, setActivePinia } from 'pinia' +import WorkspaceView from './WorkspaceView.vue' +import { useWorkspaceStore } from '@/stores/workspace' + +let wrapper: VueWrapper | null = null + +beforeEach(() => { + localStorage.clear() + setActivePinia(createPinia()) +}) + +afterEach(() => { + wrapper?.unmount() + wrapper = null +}) + +describe('WorkspaceView initial file', () => { + it('does not overwrite a file selected while the welcome note is loading', async () => { + const workspaceStore = useWorkspaceStore() + wrapper = mount(WorkspaceView, { + global: { stubs: { EditorHeader: true, EditorPane: true } }, + }) + + workspaceStore.openFile('/数据结构/红黑树.md') + await new Promise((resolve) => setTimeout(resolve, 0)) + + expect(workspaceStore.activeFilePath).toBe('/数据结构/红黑树.md') + }) +}) diff --git a/frontend/src/features/workspace/WorkspaceView.vue b/frontend/src/features/workspace/WorkspaceView.vue index f1976d1..82e2853 100644 --- a/frontend/src/features/workspace/WorkspaceView.vue +++ b/frontend/src/features/workspace/WorkspaceView.vue @@ -16,7 +16,10 @@ onMounted(() => { } if (!workspaceStore.activeFilePath && workspaceStore.fileTree.length === 0) { void editorStore.loadFile('/欢迎使用知笔知己.md').then(() => { - workspaceStore.openFile('/欢迎使用知笔知己.md') + // 默认文件加载期间用户可能已经点击了其他文件,不能覆盖用户的选择。 + if (!workspaceStore.activeFilePath && editorStore.currentFilePath === '/欢迎使用知笔知己.md') { + workspaceStore.openFile('/欢迎使用知笔知己.md') + } }) } })