Files
new-markdown/tests/context-menus.test.tsx
Muse 2d1cd3045a feat(menus): 右键菜单与切换五项(docs/21)
屏蔽 WebView 自带右键菜单(输入框除外);新增 useDismiss 统一修复菜单
点别处不消失(标签菜单实测中招、文件树菜单一并加固);侧栏页签行加收起
按钮、收起后编辑区左上角有展开按钮;编辑区右下角加源码/即时渲染切换按钮;
标签与文件树右键补保存/另存为/新窗口打开/导出/打印(针对目标文件,
未打开先打开再执行)。vitest 97 通过 + 2 跳过。
2026-11-14 15:05:19 +08:00

114 lines
5.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 右键菜单回归(docs/21):标签菜单与文件树菜单的文件类动作、点别处关闭。
import { describe, it, expect, vi } from 'vitest'
import { createRoot } from 'react-dom/client'
import { act } from 'react-dom/test-utils'
vi.mock('../src/services/platform', async (importOriginal) => {
const orig = await importOriginal<typeof import('../src/services/platform')>()
return {
...orig,
listDir: async (dir: string) =>
dir === 'C:/ws' ? [{ name: 'a.md', path: 'C:/ws/a.md', isDir: false }] : [],
}
})
import TabBar from '../src/components/TabBar'
import Sidebar from '../src/components/Sidebar'
import { createTab } from '../src/stores/tabs'
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
function rightClick(el: Element) {
el.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, clientX: 10, clientY: 10 }))
}
function mouseDownOutside() {
document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))
}
function menuTexts(container: HTMLElement, selector: string) {
const menu = container.querySelector(selector)
return menu ? [...menu.querySelectorAll('button')].map((b) => b.textContent) : null
}
describe('标签右键菜单', () => {
it('含文件类动作;动作回调带标签 id;点别处关闭', async () => {
const container = document.createElement('div')
document.body.appendChild(container)
const root = createRoot(container)
const tab = createTab({ title: 'a', content: 'x', path: 'C:/a.md' })
const onSaveAsTab = vi.fn()
await act(async () => {
root.render(
<TabBar
tabs={[tab]} activeId={tab.id}
onActivate={() => undefined} onClose={() => undefined}
onCloseOthers={() => undefined} onCloseRight={() => undefined}
onNew={() => undefined} onMove={() => undefined}
onSaveAsTab={onSaveAsTab}
onOpenInNewWindowTab={() => undefined}
/>,
)
})
const tabEl = container.querySelector('.tab') as Element
await act(async () => { rightClick(tabEl) })
const texts = menuTexts(container, '.context-menu') ?? []
for (const label of ['另存为…', '在新窗口打开', '导出…', '打印 / 导出 PDF…', '关闭']) {
expect(texts.some((t) => t === label)).toBe(true)
}
// 点菜单外(文档任意处按下)→ 关闭
await act(async () => { mouseDownOutside() })
expect(container.querySelector('.context-menu')).toBeNull()
// 重新打开并点「另存为…」→ 回调触发且菜单关闭
await act(async () => { rightClick(tabEl) })
const saveAsBtn = [...container.querySelectorAll('.context-menu button')].find((b) => b.textContent === '另存为…')
await act(async () => { saveAsBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true })) })
expect(onSaveAsTab).toHaveBeenCalledWith(tab.id)
expect(container.querySelector('.context-menu')).toBeNull()
await act(async () => { root.unmount() })
container.remove()
})
})
describe('文件树右键菜单', () => {
it('文件条目含打开/新窗口/另存为/导出/打印;点别处关闭', async () => {
const container = document.createElement('div')
document.body.appendChild(container)
const root = createRoot(container)
const onExportEntry = vi.fn()
await act(async () => {
root.render(
<Sidebar
mode="files" onModeChange={() => undefined}
workspaceRoot="C:/ws" activePath={null}
onOpenFile={() => undefined} onOpenFolder={() => undefined}
outline={[]} onJumpHeading={() => undefined} tauriMode
onRenameEntry={() => undefined}
onOpenInNewWindowEntry={() => undefined}
onSaveAsEntry={() => undefined}
onExportEntry={onExportEntry}
onPrintEntry={() => undefined}
/>,
)
})
await act(async () => { await sleep(30) })
const item = [...container.querySelectorAll('.tree-item')].find((el) => el.textContent?.includes('a.md')) as Element
expect(item).toBeTruthy()
await act(async () => { rightClick(item) })
const texts = menuTexts(container, '.tree-context-menu') ?? []
for (const label of ['打开', '在新窗口打开', '另存为…', '导出…', '打印 / 导出 PDF…', '重命名…']) {
expect(texts.some((t) => t === label)).toBe(true)
}
const exportBtn = [...container.querySelectorAll('.tree-context-menu button')].find((b) => b.textContent === '导出…')
await act(async () => { exportBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true })) })
expect(onExportEntry).toHaveBeenCalledTimes(1)
expect(onExportEntry.mock.calls[0][0].path).toBe('C:/ws/a.md')
expect(container.querySelector('.tree-context-menu')).toBeNull()
// 再次打开后点别处关闭
await act(async () => { rightClick(item) })
expect(container.querySelector('.tree-context-menu')).not.toBeNull()
await act(async () => { mouseDownOutside() })
expect(container.querySelector('.tree-context-menu')).toBeNull()
await act(async () => { root.unmount() })
container.remove()
})
})