Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions app/shared/src/ui/CodeBlock.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import React from 'react';

// Mock react-syntax-highlighter so we don't load the heavy Prism bundle.
// CodeBlock lazy-imports PrismLight + oneDark; we stub them to render raw code.
vi.mock('react-syntax-highlighter', () => ({
PrismLight: ({ code }: { code: string }) =>
React.createElement('pre', null, React.createElement('code', null, code)),
}));
vi.mock('react-syntax-highlighter/dist/esm/styles/prism', () => ({
oneDark: {},
}));
vi.mock('./prismRegistry', () => ({}));

// Mock clipboard so the copy button can be exercised.
const writeText = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, 'clipboard', {
value: { writeText },
configurable: true,
});

import { CodeBlock } from './CodeBlock';

describe('CodeBlock', () => {
it('renders inline code (no language, no trailing newline) as <code>', () => {
render(<CodeBlock>inline snippet</CodeBlock>);
expect(screen.getByText('inline snippet')).toBeInTheDocument();
expect(screen.queryByRole('button')).not.toBeInTheDocument();
});

it('renders block code with language label and copy button', () => {
render(
<CodeBlock className="language-typescript">
{'const x = 1;\n'}
</CodeBlock>,
);
expect(screen.getByText('typescript')).toBeInTheDocument();
expect(screen.getByText('const x = 1;')).toBeInTheDocument();
const copyBtn = screen.getByRole('button', { name: 'code.copy' });
expect(copyBtn).toBeInTheDocument();
Comment thread
DeliciousBuding marked this conversation as resolved.
});

it('shows code.copied aria-label after clicking copy', async () => {
render(
<CodeBlock className="language-python">
{'print("hello")\n'}
</CodeBlock>,
);
const copyBtn = screen.getByRole('button', { name: 'code.copy' });
fireEvent.click(copyBtn);
await waitFor(() => {
expect(screen.getByRole('button', { name: 'code.copied' })).toBeInTheDocument();
});
expect(writeText).toHaveBeenCalledWith('print("hello")');
});

it('shows collapse/expand toggle for code longer than 20 lines', () => {
const longCode = Array.from({ length: 25 }, (_, i) => `line ${i}`).join('\n') + '\n';
render(
<CodeBlock className="language-text">
{longCode}
</CodeBlock>,
);
const toggle = screen.getByRole('button', { name: 'code.expand' });
expect(toggle).toBeInTheDocument();
});

it('does not show collapse toggle for short code', () => {
render(
<CodeBlock className="language-text">
{'short\n'}
</CodeBlock>,
);
expect(screen.queryByRole('button', { name: 'code.expand' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'code.collapse' })).not.toBeInTheDocument();
});

it('toggles between expand and collapse labels', () => {
const longCode = Array.from({ length: 25 }, (_, i) => `line ${i}`).join('\n') + '\n';
render(
<CodeBlock className="language-text">
{longCode}
</CodeBlock>,
);
const expandBtn = screen.getByRole('button', { name: 'code.expand' });
fireEvent.click(expandBtn);
expect(screen.getByRole('button', { name: 'code.collapse' })).toBeInTheDocument();
});
});
111 changes: 111 additions & 0 deletions app/shared/src/ui/DiffReviewPanelParts.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import React from 'react';

// Mock syntaxHighlight so we don't load the heavy Prism/refractor bundle.
vi.mock('./syntaxHighlight', () => ({
highlightLine: (content: string) => content,
highlightLineWithWordDiff: (content: string) => content,
}));
Comment thread
DeliciousBuding marked this conversation as resolved.

import {
DiffReviewFileTabs,
DiffReviewToolbar,
} from './DiffReviewPanelParts';
import type { DiffReviewFile } from './DiffReviewPanelTypes';

const mockFiles: DiffReviewFile[] = [
{ filePath: 'src/index.ts', status: 'modified', hunks: [] },
{ filePath: 'src/utils.ts', status: 'added', hunks: [] },
{ filePath: 'src/old.ts', status: 'deleted', hunks: [] },
];
Comment thread
DeliciousBuding marked this conversation as resolved.

describe('DiffReviewFileTabs', () => {
it('renders a tab for each file', () => {
render(
<DiffReviewFileTabs files={mockFiles} safeIndex={0} onSelectFile={vi.fn()} />,
);
expect(screen.getByText('src/index.ts')).toBeInTheDocument();
expect(screen.getByText('src/utils.ts')).toBeInTheDocument();
expect(screen.getByText('src/old.ts')).toBeInTheDocument();
});

it('marks the active tab with aria-selected', () => {
render(
<DiffReviewFileTabs files={mockFiles} safeIndex={1} onSelectFile={vi.fn()} />,
);
const tabs = screen.getAllByRole('tab');
expect(tabs[0]).toHaveAttribute('aria-selected', 'false');
expect(tabs[1]).toHaveAttribute('aria-selected', 'true');
expect(tabs[2]).toHaveAttribute('aria-selected', 'false');
});

it('calls onSelectFile with the correct index when a tab is clicked', () => {
const onSelectFile = vi.fn();
render(
<DiffReviewFileTabs files={mockFiles} safeIndex={0} onSelectFile={onSelectFile} />,
);
const tabs = screen.getAllByRole('tab');
fireEvent.click(tabs[2]);
expect(onSelectFile).toHaveBeenCalledWith(2);
});
});

describe('DiffReviewToolbar', () => {
it('renders file path and diff stats', () => {
render(
<DiffReviewToolbar
filePath="src/index.ts"
additions={5}
deletions={2}
modifiedCount={3}
acceptAllLabel="Accept all"
rejectAllLabel="Reject all"
onAcceptAll={vi.fn()}
onRejectAll={vi.fn()}
/>,
);
expect(screen.getByText('src/index.ts')).toBeInTheDocument();
expect(screen.getByText('+5')).toBeInTheDocument();
expect(screen.getByText('-2')).toBeInTheDocument();
expect(screen.getByText('~3')).toBeInTheDocument();
});

it('does not show modified count when zero', () => {
render(
<DiffReviewToolbar
filePath="src/index.ts"
additions={1}
deletions={1}
modifiedCount={0}
acceptAllLabel="Accept all"
rejectAllLabel="Reject all"
onAcceptAll={vi.fn()}
onRejectAll={vi.fn()}
/>,
);
expect(screen.queryByText('~0')).not.toBeInTheDocument();
});

it('calls onAcceptAll and onRejectAll when buttons are clicked', () => {
const onAcceptAll = vi.fn();
const onRejectAll = vi.fn();
render(
<DiffReviewToolbar
filePath="src/index.ts"
additions={1}
deletions={1}
modifiedCount={0}
acceptAllLabel="Accept all"
rejectAllLabel="Reject all"
onAcceptAll={onAcceptAll}
onRejectAll={onRejectAll}
/>,
);
fireEvent.click(screen.getByRole('button', { name: 'Accept all' }));
expect(onAcceptAll).toHaveBeenCalledTimes(1);
fireEvent.click(screen.getByRole('button', { name: 'Reject all' }));
expect(onRejectAll).toHaveBeenCalledTimes(1);
});
});
54 changes: 54 additions & 0 deletions app/shared/src/ui/PageErrorBoundary.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import { PageErrorBoundary } from './PageErrorBoundary';

function Thrower({ error }: { error: Error }): never {
throw error;
}

let consoleErrorSpy: ReturnType<typeof vi.spyOn>;

beforeEach(() => {
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
sessionStorage.clear();
});

afterEach(() => {
consoleErrorSpy.mockRestore();
});

describe('PageErrorBoundary', () => {
it('renders children when no error', () => {
render(
<PageErrorBoundary>
<p>Page content</p>
</PageErrorBoundary>,
);
expect(screen.getByText('Page content')).toBeInTheDocument();
});

it('catches render errors and shows error UI', () => {
render(
<PageErrorBoundary>
<Thrower error={new Error('page crashed')} />
</PageErrorBoundary>,
);
expect(screen.queryByText('Page content')).not.toBeInTheDocument();
expect(screen.getByRole('alert')).toBeInTheDocument();
expect(screen.getByText('Something went wrong')).toBeInTheDocument();
});

it('calls onReset when user retries', () => {
const onReset = vi.fn();
render(
<PageErrorBoundary onReset={onReset}>
<Thrower error={new Error('reset test')} />
</PageErrorBoundary>,
);
const retryButton = screen.getByText('Retry');
fireEvent.click(retryButton);
expect(onReset).toHaveBeenCalledTimes(1);
});
Comment thread
DeliciousBuding marked this conversation as resolved.
});
Loading
Loading