import { render, screen, fireEvent } from '@testing-library/react' import { DiffViewer, parseDiffFiles, diffLineClass } from '../diff/DiffViewer' const SAMPLE_DIFF = `diff --git a/src/foo.ts b/src/foo.ts index 1234567..89abcde 100644 --- a/src/foo.ts +++ b/src/foo.ts @@ -1,3 +1,3 @@ const a = 1 -const b = 2 +const b = 3 diff --git a/src/bar.ts b/src/bar.ts new file mode 100644 index 0000000..1111111 --- /dev/null +++ b/src/bar.ts @@ -0,0 +1,2 @@ +export const bar = 'baz' +export default bar ` describe('parseDiffFiles', () => { it('splits a unified diff into one block per file with the b/ path', () => { const files = parseDiffFiles(SAMPLE_DIFF) expect(files).toHaveLength(2) expect(files[0].filePath).toBe('src/foo.ts') expect(files[1].filePath).toBe('src/bar.ts') // the block carries its own lines (including the hunk header) expect(files[0].lines.some((l) => l.startsWith('@@'))).toBe(true) }) it('returns an empty array for empty / whitespace input', () => { expect(parseDiffFiles('')).toEqual([]) expect(parseDiffFiles(' \n ')).toEqual([]) }) }) describe('diffLineClass', () => { it('colourises added / removed / hunk / context lines', () => { expect(diffLineClass('+added')).toContain('emerald') expect(diffLineClass('-removed')).toContain('red') expect(diffLineClass('@@ -1 +1 @@')).toContain('cyan') expect(diffLineClass(' context')).toContain('muted-foreground') }) }) describe('DiffViewer', () => { it('renders per-file blocks and colourised +/-/@@ lines', () => { render() // both file headers render expect(screen.getByText('src/foo.ts')).toBeInTheDocument() expect(screen.getByText('src/bar.ts')).toBeInTheDocument() // an added line is present and coloured; metadata lines are hidden expect(screen.getByText('+const b = 3')).toHaveClass('bg-emerald-500/10') expect(screen.getByText('-const b = 2')).toHaveClass('bg-red-500/10') expect(screen.queryByText(/^index 1234567/)).not.toBeInTheDocument() // base ref shown in header expect(screen.getByText('main')).toBeInTheDocument() }) it('renders a summary line with staged/unstaged counts', () => { render() const summary = screen.getByTestId('diff-viewer-summary') expect(summary).toHaveTextContent('2 files changed') expect(summary).toHaveTextContent('1 staged') expect(summary).toHaveTextContent('1 unstaged') }) it('shows the empty state (with custom label) when the diff is empty', () => { render() expect(screen.getByTestId('diff-viewer-empty')).toBeInTheDocument() expect(screen.getByText('Nothing to review')).toBeInTheDocument() }) it('shows a loading state', () => { render() expect(screen.getByTestId('diff-viewer-loading')).toBeInTheDocument() }) it('shows an error state and fires onRefresh from Try again', () => { const onRefresh = jest.fn() render() expect(screen.getByTestId('diff-viewer-error')).toBeInTheDocument() expect(screen.getByText('boom')).toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: /try again/i })) expect(onRefresh).toHaveBeenCalledTimes(1) }) it('fires onRefresh from the header refresh button', () => { const onRefresh = jest.fn() render() fireEvent.click(screen.getByRole('button', { name: /refresh diff/i })) expect(onRefresh).toHaveBeenCalledTimes(1) }) it('renders a collapsible working-tree status block', () => { render() expect(screen.getByText('Working tree status')).toBeInTheDocument() expect(screen.getByText('?? untracked.ts')).toBeInTheDocument() }) })