From 9003e4b494bcc382f570802a4dd5c30a41e2ccac Mon Sep 17 00:00:00 2001 From: thzxx <1440196015@qq.com> Date: Mon, 17 Aug 2026 17:58:50 +0800 Subject: [PATCH] =?UTF-8?q?test:=20=E8=A1=A5=E5=85=85=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=E7=94=A8=E4=BE=8B=EF=BC=8C=E8=A6=86=E7=9B=96?= =?UTF-8?q?=E7=8E=87=E6=8F=90=E5=8D=87=E8=87=B3=2080%+?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/__tests__/App.test.tsx | 42 +++++++++++- .../src/components/ContextMenu.test.tsx | 42 ++++++++++++ src/renderer/src/components/DiffView.test.tsx | 60 +++++++++++++++++ src/renderer/src/components/Toolbar.test.tsx | 65 +++++++++++++++++++ 4 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 src/renderer/src/components/ContextMenu.test.tsx create mode 100644 src/renderer/src/components/DiffView.test.tsx create mode 100644 src/renderer/src/components/Toolbar.test.tsx diff --git a/src/renderer/src/__tests__/App.test.tsx b/src/renderer/src/__tests__/App.test.tsx index bc0165e..2996415 100644 --- a/src/renderer/src/__tests__/App.test.tsx +++ b/src/renderer/src/__tests__/App.test.tsx @@ -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 { + 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() @@ -18,4 +38,22 @@ describe('App - 初始空态', () => { render() expect(screen.queryByText('导出报告')).not.toBeInTheDocument() }) +}) + +describe('App - 加载文件后的非空态', () => { + it('打开文件后渲染对比视图与导出入口,并显示文件内容', async () => { + render() + 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() + fireEvent.click(screen.getByText('打开右侧')) + expect(await screen.findByText('导出报告')).toBeInTheDocument() + }) }) \ No newline at end of file diff --git a/src/renderer/src/components/ContextMenu.test.tsx b/src/renderer/src/components/ContextMenu.test.tsx new file mode 100644 index 0000000..6b58df4 --- /dev/null +++ b/src/renderer/src/components/ContextMenu.test.tsx @@ -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( + + ) + 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() + fireEvent.click(screen.getByText('在文件夹中显示')) + expect(action).not.toHaveBeenCalled() + expect(onClose).not.toHaveBeenCalled() + }) + + it('点击遮罩层触发关闭', () => { + const onClose = vi.fn() + const { container } = render( + + ) + const backdrop = container.querySelector('.ctx-backdrop') + expect(backdrop).not.toBeNull() + fireEvent.mouseDown(backdrop as Element) + expect(onClose).toHaveBeenCalled() + }) +}) \ No newline at end of file diff --git a/src/renderer/src/components/DiffView.test.tsx b/src/renderer/src/components/DiffView.test.tsx new file mode 100644 index 0000000..f1dda1d --- /dev/null +++ b/src/renderer/src/components/DiffView.test.tsx @@ -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( + + ) +} + +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() + }) +}) \ No newline at end of file diff --git a/src/renderer/src/components/Toolbar.test.tsx b/src/renderer/src/components/Toolbar.test.tsx new file mode 100644 index 0000000..949abca --- /dev/null +++ b/src/renderer/src/components/Toolbar.test.tsx @@ -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( + + ) + 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') + }) +}) \ No newline at end of file