test: 补充组件交互用例,覆盖率提升至 80%+
This commit is contained in:
@@ -1,7 +1,27 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import App from '../App'
|
||||
|
||||
function mockApi(overrides?: Partial<typeof window.api>): typeof window.api {
|
||||
const base = {
|
||||
openFile: async () => ({
|
||||
path: '/tmp/a.txt',
|
||||
name: 'a.txt',
|
||||
text: 'line1\nline2',
|
||||
encoding: 'UTF-8'
|
||||
}),
|
||||
decodeBuffer: async () => ({ text: 'line1\nline2', encoding: 'UTF-8' }),
|
||||
saveReport: async () => ({ ok: false, path: null }),
|
||||
showInFolder: async () => true,
|
||||
setClipboard: async () => true
|
||||
}
|
||||
return { ...base, ...overrides } as typeof window.api
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
window.api = mockApi()
|
||||
})
|
||||
|
||||
describe('App - 初始空态', () => {
|
||||
it('渲染应用名称 DiffLens', () => {
|
||||
render(<App />)
|
||||
@@ -18,4 +38,22 @@ describe('App - 初始空态', () => {
|
||||
render(<App />)
|
||||
expect(screen.queryByText('导出报告')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('App - 加载文件后的非空态', () => {
|
||||
it('打开文件后渲染对比视图与导出入口,并显示文件内容', async () => {
|
||||
render(<App />)
|
||||
fireEvent.click(screen.getByText('打开左侧'))
|
||||
|
||||
expect(await screen.findByText('导出报告')).toBeInTheDocument()
|
||||
// 被对比的内容渲染(此处与空文件对比,内容出现在面板中)
|
||||
expect((await screen.findAllByText(/line1/)).length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('a.txt').length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('右侧打开文件后可导出报告', async () => {
|
||||
render(<App />)
|
||||
fireEvent.click(screen.getByText('打开右侧'))
|
||||
expect(await screen.findByText('导出报告')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import ContextMenu, { type ContextMenuItem } from './ContextMenu'
|
||||
|
||||
describe('ContextMenu', () => {
|
||||
it('渲染菜单项并触行动作后关闭', () => {
|
||||
const action = vi.fn()
|
||||
const onClose = vi.fn()
|
||||
const items: ContextMenuItem[] = [{ label: '复制此行内容', action }]
|
||||
const { container } = render(
|
||||
<ContextMenu x={10} y={10} items={items} onClose={onClose} />
|
||||
)
|
||||
fireEvent.click(screen.getByText('复制此行内容'))
|
||||
expect(action).toHaveBeenCalledTimes(1)
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
expect(container.firstChild).toBeTruthy()
|
||||
})
|
||||
|
||||
it('禁用项不触行动作', () => {
|
||||
const action = vi.fn()
|
||||
const onClose = vi.fn()
|
||||
const items: ContextMenuItem[] = [
|
||||
{ label: '在文件夹中显示', disabled: true, action },
|
||||
{ label: '复制文件名', action }
|
||||
]
|
||||
render(<ContextMenu x={10} y={10} items={items} onClose={onClose} />)
|
||||
fireEvent.click(screen.getByText('在文件夹中显示'))
|
||||
expect(action).not.toHaveBeenCalled()
|
||||
expect(onClose).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('点击遮罩层触发关闭', () => {
|
||||
const onClose = vi.fn()
|
||||
const { container } = render(
|
||||
<ContextMenu x={10} y={10} items={[]} onClose={onClose} />
|
||||
)
|
||||
const backdrop = container.querySelector('.ctx-backdrop')
|
||||
expect(backdrop).not.toBeNull()
|
||||
fireEvent.mouseDown(backdrop as Element)
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,60 @@
|
||||
import { describe, it, expect, vi, beforeAll } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import DiffView from './DiffView'
|
||||
import type { DiffRow } from '../diff/diffEngine'
|
||||
|
||||
beforeAll(() => {
|
||||
// jsdom 未实现 scrollIntoView,测试里打桩
|
||||
Element.prototype.scrollIntoView = vi.fn()
|
||||
})
|
||||
|
||||
function makeDiff(activeRowId: string | null): void {
|
||||
const rows: DiffRow[] = [
|
||||
{
|
||||
id: 'r0',
|
||||
rowKind: 'unchanged',
|
||||
isChanged: false,
|
||||
left: { lineNo: 1, text: 'hello', segs: null },
|
||||
right: { lineNo: 1, text: 'hello', segs: null }
|
||||
},
|
||||
{
|
||||
id: 'r1',
|
||||
rowKind: 'added',
|
||||
isChanged: true,
|
||||
left: { lineNo: null, text: null, segs: null },
|
||||
right: { lineNo: 2, text: 'world', segs: null }
|
||||
}
|
||||
]
|
||||
render(
|
||||
<DiffView
|
||||
diff={{ rows, summary: { changedLines: 1, inserted: 1, deleted: 0, modified: 0 } }}
|
||||
leftMeta={{ name: 'a.txt', encoding: 'UTF-8' }}
|
||||
rightMeta={{ name: 'b.txt', encoding: 'GBK' }}
|
||||
activeRowId={activeRowId}
|
||||
onOpen={vi.fn()}
|
||||
onRowContext={vi.fn()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
describe('DiffView', () => {
|
||||
it('渲染左右文件名与同/新增行内容', () => {
|
||||
makeDiff(null)
|
||||
expect(screen.getByText('a.txt')).toBeInTheDocument()
|
||||
expect(screen.getByText('b.txt')).toBeInTheDocument()
|
||||
expect(screen.getAllByText('hello').length).toBeGreaterThanOrEqual(2)
|
||||
expect(screen.getAllByText('world').length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('渲染行号', () => {
|
||||
makeDiff(null)
|
||||
// 两栏各自渲染行号 1,总共应有多个
|
||||
expect(screen.getAllByText('1').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('2').length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('设置活动行时调用 scrollIntoView 定位', () => {
|
||||
makeDiff('r1')
|
||||
expect(Element.prototype.scrollIntoView).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,65 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import Toolbar from './Toolbar'
|
||||
import { DiffSummary } from '../diff/diffEngine'
|
||||
|
||||
const summary: DiffSummary = { changedLines: 3, inserted: 1, deleted: 1, modified: 1 }
|
||||
|
||||
function setup(extra?: { canExport?: boolean }) {
|
||||
const onOptionsChange = vi.fn()
|
||||
const onNav = vi.fn()
|
||||
const onExport = vi.fn()
|
||||
const utils = render(
|
||||
<Toolbar
|
||||
options={{ trimWhitespace: false, ignoreCase: false }}
|
||||
onOptionsChange={onOptionsChange}
|
||||
summary={summary}
|
||||
navIndex={0}
|
||||
navCount={3}
|
||||
onNav={onNav}
|
||||
onExport={onExport}
|
||||
canExport={extra?.canExport ?? true}
|
||||
/>
|
||||
)
|
||||
return { onOptionsChange, onNav, onExport, ...utils }
|
||||
}
|
||||
|
||||
describe('Toolbar', () => {
|
||||
it('渲染差异统计徽章', () => {
|
||||
setup()
|
||||
expect(screen.getByText('+1')).toBeInTheDocument()
|
||||
expect(screen.getByText('−1')).toBeInTheDocument()
|
||||
expect(screen.getByText('~1')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('切换忽略大小写触发 onOptionsChange', () => {
|
||||
const { onOptionsChange } = setup()
|
||||
fireEvent.click(screen.getByLabelText('忽略大小写'))
|
||||
expect(onOptionsChange).toHaveBeenCalledWith({ trimWhitespace: false, ignoreCase: true })
|
||||
})
|
||||
|
||||
it('切换忽略行首尾空白触发 onOptionsChange', () => {
|
||||
const { onOptionsChange } = setup()
|
||||
fireEvent.click(screen.getByLabelText('忽略行首尾空白'))
|
||||
expect(onOptionsChange).toHaveBeenCalledWith({ trimWhitespace: true, ignoreCase: false })
|
||||
})
|
||||
|
||||
it('点击下一处差异调用 onNav(1)', () => {
|
||||
const { onNav } = setup()
|
||||
fireEvent.click(screen.getByTitle('下一处差异'))
|
||||
expect(onNav).toHaveBeenCalledWith(1)
|
||||
})
|
||||
|
||||
it('点击上一处差异调用 onNav(-1)', () => {
|
||||
const { onNav } = setup()
|
||||
fireEvent.click(screen.getByTitle('上一处差异'))
|
||||
expect(onNav).toHaveBeenCalledWith(-1)
|
||||
})
|
||||
|
||||
it('导出菜单选择 HTML 触发 onExport("html")', () => {
|
||||
const { onExport } = setup()
|
||||
fireEvent.click(screen.getByText('导出报告'))
|
||||
fireEvent.click(screen.getByText('HTML 报告'))
|
||||
expect(onExport).toHaveBeenCalledWith('html')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user