feat(frontend): 完善工作区导航与笔记属性并适配手帐主题

This commit is contained in:
2026-09-05 19:11:45 +08:00
parent d5b1050a86
commit a63f6c57e0
13 changed files with 484 additions and 23 deletions
@@ -48,6 +48,67 @@ afterEach(() => {
})
describe('FileTreePanel file switching', () => {
it('switches full-height panels using tabs and preserves the file search', async () => {
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/workspace', component: { template: '<div />' } }] })
await router.push('/workspace')
wrapper = mount(FileTreePanel, { attachTo: document.body, global: { plugins: [router] } })
expect(wrapper.get('#workspace-files-panel').isVisible()).toBe(true)
expect(wrapper.get('#workspace-outline-panel').isVisible()).toBe(false)
await wrapper.get('.file-tree-panel').trigger('wheel', { deltaY: -50 })
await wrapper.get('.file-search input').setValue('笔记')
await wrapper.get('#workspace-outline-tab').trigger('click')
expect(wrapper.get('#workspace-files-panel').isVisible()).toBe(false)
expect(wrapper.get('#workspace-outline-panel').isVisible()).toBe(true)
expect(wrapper.get('#workspace-outline-tab').attributes('aria-selected')).toBe('true')
await wrapper.get('#workspace-outline-tab').trigger('keydown', { key: 'ArrowLeft' })
expect(wrapper.get('#workspace-files-panel').isVisible()).toBe(true)
expect((wrapper.get('.file-search input').element as HTMLInputElement).value).toBe('笔记')
})
it('reveals search on upward wheel and filters without changing folder state', async () => {
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/workspace', component: { template: '<div />' } }] })
await router.push('/workspace')
const store = useWorkspaceStore()
await store.openVault('C:/vault')
store.toggleFolder('/数据结构')
wrapper = mount(FileTreePanel, { global: { plugins: [router] } })
expect(wrapper.find('.file-search').exists()).toBe(false)
await wrapper.get('.file-tree-panel').trigger('wheel', { deltaY: -50 })
await wrapper.get('.file-search input').setValue('红黑')
expect(wrapper.findAll('.tree-node').map(node => node.text())).toEqual(['数据结构', '红黑树.md'])
expect(store.fileTree[0]!.is_open).toBe(false)
await wrapper.get('.file-tree-panel').trigger('wheel', { deltaY: 50 })
expect(wrapper.find('.file-search').exists()).toBe(true)
})
it('creates a folder through the file context menu in its containing directory', async () => {
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/workspace', component: { template: '<div />' } }] })
await router.push('/workspace')
await useWorkspaceStore().openVault('C:/vault')
const create = vi.spyOn(workspaceService, 'createFolder').mockResolvedValue({ id: 'new', name: '子目录', path: '/数据结构/子目录', type: 'folder' })
wrapper = mount(FileTreePanel, { attachTo: document.body, global: { plugins: [router] } })
await wrapper.findAll('.tree-node').find(node => node.text().includes('红黑树'))!.trigger('contextmenu')
const button = [...document.querySelectorAll<HTMLButtonElement>('.context-menu button')].find(item => item.textContent === '新建文件夹')!
button.click()
await wrapper.vm.$nextTick()
await wrapper.get('.new-item input').setValue('子目录')
await wrapper.get('.new-item').trigger('submit')
expect(create).toHaveBeenCalledWith('/数据结构', '子目录')
})
it('collapses nested headings and requests navigation to a duplicate heading', async () => {
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/workspace', component: { template: '<div />' } }] })
await router.push('/workspace')
const store = useEditorStore()
store.currentFilePath = '/note.md'
store.content = '# 标题\n\n## 子标题\n\n# 标题\n'
wrapper = mount(FileTreePanel, { global: { plugins: [router] } })
await wrapper.get('#workspace-outline-tab').trigger('click')
expect(wrapper.findAll('.outline-title')).toHaveLength(3)
await wrapper.get('.outline-row button[aria-expanded]').trigger('click')
expect(wrapper.findAll('.outline-title')).toHaveLength(2)
await wrapper.findAll('.outline-title')[1]!.trigger('click')
expect(store.headingRequest).toEqual({ index: 2, offset: store.content.lastIndexOf('# 标题'), path: '/note.md' })
})
it('switches both workspace selection and editor content on consecutive clicks', async () => {
const router = createRouter({
history: createMemoryHistory(),