test: 补充组件交互用例,覆盖率提升至 80%+
This commit is contained in:
@@ -1,7 +1,27 @@
|
|||||||
import { describe, it, expect } from 'vitest'
|
import { describe, it, expect, beforeEach } from 'vitest'
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen, fireEvent } from '@testing-library/react'
|
||||||
import App from '../App'
|
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 - 初始空态', () => {
|
describe('App - 初始空态', () => {
|
||||||
it('渲染应用名称 DiffLens', () => {
|
it('渲染应用名称 DiffLens', () => {
|
||||||
render(<App />)
|
render(<App />)
|
||||||
@@ -19,3 +39,21 @@ describe('App - 初始空态', () => {
|
|||||||
expect(screen.queryByText('导出报告')).not.toBeInTheDocument()
|
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