diff --git a/apps/web/src/__tests__/components/DocumentPreviewModal.test.tsx b/apps/web/src/__tests__/components/DocumentPreviewModal.test.tsx
new file mode 100644
index 00000000..829dc922
--- /dev/null
+++ b/apps/web/src/__tests__/components/DocumentPreviewModal.test.tsx
@@ -0,0 +1,119 @@
+import { describe, it, expect, vi } from 'vitest';
+import { render, screen, fireEvent } from '@testing-library/react';
+import {
+ DocumentPreviewModal,
+ isPreviewableMimeType,
+} from '#/routes/_authenticated/applications/$applicationId/-components/DocumentPreviewModal';
+
+describe('isPreviewableMimeType', () => {
+ it('returns true for PDF', () => {
+ expect(isPreviewableMimeType('application/pdf')).toBe(true);
+ });
+
+ it('returns true for PNG', () => {
+ expect(isPreviewableMimeType('image/png')).toBe(true);
+ });
+
+ it('returns true for JPEG', () => {
+ expect(isPreviewableMimeType('image/jpeg')).toBe(true);
+ });
+
+ it('returns false for DOCX', () => {
+ expect(
+ isPreviewableMimeType(
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ ),
+ ).toBe(false);
+ });
+
+ it('returns false for plain text', () => {
+ expect(isPreviewableMimeType('text/plain')).toBe(false);
+ });
+});
+
+describe('DocumentPreviewModal', () => {
+ const pdfDoc = {
+ name: 'resume.pdf',
+ mimeType: 'application/pdf',
+ url: 'https://example.com/resume.pdf',
+ };
+ const imageDoc = {
+ name: 'photo.png',
+ mimeType: 'image/png',
+ url: 'https://example.com/photo.png',
+ };
+ const unsupportedDoc = {
+ name: 'report.docx',
+ mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ url: 'https://example.com/report.docx',
+ };
+
+ it('renders nothing when document is null', () => {
+ const { container } = render();
+ expect(container.firstChild).toBeNull();
+ });
+
+ it('renders the document name in the header', () => {
+ render();
+ expect(screen.getByText('resume.pdf')).toBeInTheDocument();
+ });
+
+ it('renders an iframe for PDF documents', () => {
+ render();
+ const iframe = screen.getByTitle('resume.pdf');
+ expect(iframe).toHaveAttribute('src', 'https://example.com/resume.pdf');
+ });
+
+ it('renders an img for PNG documents', () => {
+ render();
+ const img = screen.getByAltText('photo.png');
+ expect(img).toHaveAttribute('src', 'https://example.com/photo.png');
+ });
+
+ it('renders an img for JPEG documents', () => {
+ const jpegDoc = { ...imageDoc, name: 'photo.jpg', mimeType: 'image/jpeg' };
+ render();
+ const img = screen.getByAltText('photo.jpg');
+ expect(img).toHaveAttribute('src', 'https://example.com/photo.png');
+ });
+
+ it('renders fallback message for unsupported file types', () => {
+ render();
+ expect(screen.getByText(/preview isn't available/i)).toBeInTheDocument();
+ });
+
+ it('renders a download link for unsupported file types', () => {
+ render();
+ const link = screen.getByRole('link', { name: /open report\.docx/i });
+ expect(link).toHaveAttribute('href', 'https://example.com/report.docx');
+ expect(link).toHaveAttribute('target', '_blank');
+ });
+
+ it('calls onClose when the close button is clicked', () => {
+ const onClose = vi.fn();
+ render();
+ // The X button
+ const buttons = screen.getAllByRole('button');
+ const closeButton = buttons.find((b) => b.querySelector('svg'));
+ expect(closeButton).toBeDefined();
+ fireEvent.click(closeButton!);
+ expect(onClose).toHaveBeenCalledOnce();
+ });
+
+ it('calls onClose when the backdrop is clicked', () => {
+ const onClose = vi.fn();
+ const { container } = render();
+ // The backdrop is the fixed inset-0 div with bg-black/50
+ const backdrop = container.querySelector('.bg-black\\/50');
+ expect(backdrop).toBeInTheDocument();
+ fireEvent.click(backdrop!);
+ expect(onClose).toHaveBeenCalledOnce();
+ });
+
+ it('renders the "Open in new tab" link', () => {
+ render();
+ const link = screen.getByTitle('Open in new tab');
+ expect(link).toHaveAttribute('href', 'https://example.com/resume.pdf');
+ expect(link).toHaveAttribute('target', '_blank');
+ });
+});
diff --git a/apps/web/src/__tests__/components/ErrorState.test.tsx b/apps/web/src/__tests__/components/ErrorState.test.tsx
new file mode 100644
index 00000000..9bd3e16c
--- /dev/null
+++ b/apps/web/src/__tests__/components/ErrorState.test.tsx
@@ -0,0 +1,73 @@
+import { describe, it, expect, vi } from 'vitest';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { ErrorState } from '#/components/ErrorState';
+
+describe('ErrorState', () => {
+ it('renders the generic error message for a plain string error', () => {
+ render();
+ expect(screen.getByText('Something went wrong. Please try again.')).toBeInTheDocument();
+ });
+
+ it('renders the generic error message for an Error instance', () => {
+ render();
+ expect(screen.getByText('Something went wrong. Please try again.')).toBeInTheDocument();
+ });
+
+ it('renders the network error message for a TypeError', () => {
+ render();
+ expect(
+ screen.getByText("Can't reach the server. Check your connection and try again."),
+ ).toBeInTheDocument();
+ });
+
+ it('renders the error message from a GraphQL error with a known code', () => {
+ const gqlError = {
+ response: {
+ errors: [{ message: 'Rate limited', extensions: { code: 'RATE_LIMITED' } }],
+ },
+ };
+ render();
+ expect(screen.getByText('Rate limited')).toBeInTheDocument();
+ });
+
+ it('shows a NOT_FOUND override message', () => {
+ const gqlError = {
+ response: {
+ errors: [{ message: 'Application not found', extensions: { code: 'NOT_FOUND' } }],
+ },
+ };
+ render();
+ expect(
+ screen.getByText("That item couldn't be found — it may have been deleted."),
+ ).toBeInTheDocument();
+ });
+
+ it('shows a FORBIDDEN override message', () => {
+ const gqlError = {
+ response: {
+ errors: [{ message: 'Forbidden', extensions: { code: 'FORBIDDEN' } }],
+ },
+ };
+ render();
+ expect(screen.getByText("You don't have permission to do that.")).toBeInTheDocument();
+ });
+
+ it('renders the retry button when onRetry is provided', () => {
+ const onRetry = vi.fn();
+ render();
+ const btn = screen.getByRole('button', { name: /try again/i });
+ expect(btn).toBeInTheDocument();
+ fireEvent.click(btn);
+ expect(onRetry).toHaveBeenCalledOnce();
+ });
+
+ it('does not render the retry button when onRetry is not provided', () => {
+ render();
+ expect(screen.queryByRole('button', { name: /try again/i })).not.toBeInTheDocument();
+ });
+
+ it('renders the alert triangle icon', () => {
+ const { container } = render();
+ expect(container.querySelector('svg')).toBeInTheDocument();
+ });
+});
diff --git a/apps/web/src/__tests__/components/JdImportPanel.test.tsx b/apps/web/src/__tests__/components/JdImportPanel.test.tsx
new file mode 100644
index 00000000..2619f540
--- /dev/null
+++ b/apps/web/src/__tests__/components/JdImportPanel.test.tsx
@@ -0,0 +1,181 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { JdImportPanel } from '#/routes/_authenticated/applications/-components/JdImportPanel';
+
+const { mockGqlRequest } = vi.hoisted(() => ({
+ mockGqlRequest: vi.fn(),
+}));
+
+vi.mock('#/graphql/client', () => ({
+ gqlClient: { request: mockGqlRequest },
+}));
+
+vi.mock('@tanstack/react-router', () => ({
+ Link: ({ children, to }: { children: React.ReactNode; to: string }) => (
+ {children}
+ ),
+}));
+
+describe('JdImportPanel', () => {
+ const onFill = vi.fn();
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('renders the collapsed toggle button', () => {
+ render();
+ expect(screen.getByText('Auto-fill from job posting')).toBeInTheDocument();
+ });
+
+ it('expands the panel when toggle is clicked', () => {
+ render();
+ fireEvent.click(screen.getByText('Auto-fill from job posting'));
+ expect(screen.getByText('Paste text')).toBeInTheDocument();
+ expect(screen.getByText('From URL')).toBeInTheDocument();
+ });
+
+ it('shows the textarea in text mode by default', () => {
+ render();
+ fireEvent.click(screen.getByText('Auto-fill from job posting'));
+ expect(screen.getByPlaceholderText(/paste the job description/i)).toBeInTheDocument();
+ });
+
+ it('switches to URL mode when "From URL" is clicked', () => {
+ render();
+ fireEvent.click(screen.getByText('Auto-fill from job posting'));
+ fireEvent.click(screen.getByText('From URL'));
+ expect(screen.getByPlaceholderText(/https:\/\/company\.com/i)).toBeInTheDocument();
+ });
+
+ it('disables the auto-fill button when input is empty', () => {
+ render();
+ fireEvent.click(screen.getByText('Auto-fill from job posting'));
+ const btn = screen.getByRole('button', { name: /auto-fill fields/i });
+ expect(btn).toBeDisabled();
+ });
+
+ it('enables the auto-fill button when text is entered', () => {
+ render();
+ fireEvent.click(screen.getByText('Auto-fill from job posting'));
+ fireEvent.change(screen.getByPlaceholderText(/paste the job description/i), {
+ target: { value: 'Software Engineer at Acme' },
+ });
+ const btn = screen.getByRole('button', { name: /auto-fill fields/i });
+ expect(btn).toBeEnabled();
+ });
+
+ it('calls onFill with parsed data on successful auto-fill', async () => {
+ mockGqlRequest.mockResolvedValue({
+ parseJobDescription: {
+ company: 'Acme',
+ role: 'Engineer',
+ location: 'Remote',
+ salary: '$100k',
+ description: 'Build things',
+ },
+ });
+
+ render();
+ fireEvent.click(screen.getByText('Auto-fill from job posting'));
+ fireEvent.change(screen.getByPlaceholderText(/paste the job description/i), {
+ target: { value: 'Software Engineer at Acme' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: /auto-fill fields/i }));
+
+ await waitFor(() => {
+ expect(onFill).toHaveBeenCalledWith({
+ company: 'Acme',
+ role: 'Engineer',
+ location: 'Remote',
+ salary: '$100k',
+ description: 'Build things',
+ });
+ });
+ });
+
+ it('shows error message on mutation failure', async () => {
+ mockGqlRequest.mockRejectedValue(new Error('Network error'));
+
+ render();
+ fireEvent.click(screen.getByText('Auto-fill from job posting'));
+ fireEvent.change(screen.getByPlaceholderText(/paste the job description/i), {
+ target: { value: 'Some job description' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: /auto-fill fields/i }));
+
+ await waitFor(() => {
+ expect(screen.getByText('Network error')).toBeInTheDocument();
+ });
+ });
+
+ it('shows "Parsing..." while loading', async () => {
+ let resolvePromise: (value: unknown) => void;
+ mockGqlRequest.mockReturnValue(
+ new Promise((resolve) => {
+ resolvePromise = resolve;
+ }),
+ );
+
+ render();
+ fireEvent.click(screen.getByText('Auto-fill from job posting'));
+ fireEvent.change(screen.getByPlaceholderText(/paste the job description/i), {
+ target: { value: 'Job description' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: /auto-fill fields/i }));
+
+ await waitFor(() => {
+ expect(screen.getByText('Parsing…')).toBeInTheDocument();
+ });
+
+ resolvePromise!({
+ parseJobDescription: {
+ company: null,
+ role: null,
+ location: null,
+ salary: null,
+ description: null,
+ },
+ });
+ });
+
+ it('shows "Filled!" after successful auto-fill', async () => {
+ mockGqlRequest.mockResolvedValue({
+ parseJobDescription: {
+ company: null,
+ role: null,
+ location: null,
+ salary: null,
+ description: null,
+ },
+ });
+
+ render();
+ fireEvent.click(screen.getByText('Auto-fill from job posting'));
+ fireEvent.change(screen.getByPlaceholderText(/paste the job description/i), {
+ target: { value: 'Job description' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: /auto-fill fields/i }));
+
+ await waitFor(() => {
+ expect(screen.getByText('Filled!')).toBeInTheDocument();
+ });
+ });
+
+ it('shows AI not configured message when error code matches', async () => {
+ mockGqlRequest.mockRejectedValue({
+ response: { errors: [{ extensions: { code: 'AI_NOT_CONFIGURED' } }] },
+ });
+
+ render();
+ fireEvent.click(screen.getByText('Auto-fill from job posting'));
+ fireEvent.change(screen.getByPlaceholderText(/paste the job description/i), {
+ target: { value: 'Job description' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: /auto-fill fields/i }));
+
+ await waitFor(() => {
+ expect(screen.getByText(/add your ai api key/i)).toBeInTheDocument();
+ });
+ });
+});
diff --git a/apps/web/src/__tests__/components/LogoMark.test.tsx b/apps/web/src/__tests__/components/LogoMark.test.tsx
new file mode 100644
index 00000000..aea37365
--- /dev/null
+++ b/apps/web/src/__tests__/components/LogoMark.test.tsx
@@ -0,0 +1,45 @@
+import { describe, it, expect } from 'vitest';
+import { render } from '@testing-library/react';
+import { LogoMark } from '#/components/LogoMark';
+
+describe('LogoMark', () => {
+ it('renders an SVG element', () => {
+ const { container } = render();
+ const svg = container.querySelector('svg');
+ expect(svg).toBeInTheDocument();
+ });
+
+ it('uses default size of 24', () => {
+ const { container } = render();
+ const svg = container.querySelector('svg');
+ expect(svg).toHaveAttribute('width', '24');
+ expect(svg).toHaveAttribute('height', '24');
+ });
+
+ it('accepts a custom size prop', () => {
+ const { container } = render();
+ const svg = container.querySelector('svg');
+ expect(svg).toHaveAttribute('width', '48');
+ expect(svg).toHaveAttribute('height', '48');
+ });
+
+ it('applies a custom className', () => {
+ const { container } = render();
+ const svg = container.querySelector('svg');
+ expect(svg).toHaveClass('my-class');
+ });
+
+ it('has aria-hidden for decorative use', () => {
+ const { container } = render();
+ const svg = container.querySelector('svg');
+ expect(svg).toHaveAttribute('aria-hidden', 'true');
+ });
+
+ it('contains the brand blue rectangles and checkmark path', () => {
+ const { container } = render();
+ const rects = container.querySelectorAll('rect');
+ expect(rects.length).toBe(3);
+ const path = container.querySelector('path');
+ expect(path).toBeInTheDocument();
+ });
+});
diff --git a/apps/web/src/__tests__/components/NavigationProgressBar.test.tsx b/apps/web/src/__tests__/components/NavigationProgressBar.test.tsx
new file mode 100644
index 00000000..8304ce32
--- /dev/null
+++ b/apps/web/src/__tests__/components/NavigationProgressBar.test.tsx
@@ -0,0 +1,58 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { render, act } from '@testing-library/react';
+
+const { mockStart, mockDone } = vi.hoisted(() => ({
+ mockStart: vi.fn(),
+ mockDone: vi.fn(),
+}));
+
+vi.mock('nprogress', () => ({
+ default: {
+ configure: vi.fn(),
+ start: mockStart,
+ done: mockDone,
+ },
+}));
+
+let mockStatus: string;
+
+vi.mock('@tanstack/react-router', () => ({
+ useRouterState: ({ select }: { select: (s: { status: string }) => unknown }) =>
+ select({ status: mockStatus }),
+}));
+
+const NavigationProgressBar = (await import('#/components/NavigationProgressBar'))
+ .NavigationProgressBar;
+
+describe('NavigationProgressBar', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mockStatus = 'idle';
+ });
+
+ it('renders nothing (null)', async () => {
+ let result: ReturnType;
+ await act(async () => {
+ result = render();
+ });
+ expect(result!.container.firstChild).toBeNull();
+ });
+
+ it('does not call NProgress.start when status is idle', async () => {
+ await act(async () => {
+ render();
+ });
+ expect(mockStart).not.toHaveBeenCalled();
+ });
+
+ it('calls NProgress.done on unmount', async () => {
+ let unmount: () => void;
+ await act(async () => {
+ ({ unmount } = render());
+ });
+ await act(async () => {
+ unmount!();
+ });
+ expect(mockDone).toHaveBeenCalled();
+ });
+});
diff --git a/apps/web/src/__tests__/components/OAuthButtons.test.tsx b/apps/web/src/__tests__/components/OAuthButtons.test.tsx
new file mode 100644
index 00000000..dd5ead97
--- /dev/null
+++ b/apps/web/src/__tests__/components/OAuthButtons.test.tsx
@@ -0,0 +1,28 @@
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { OAuthButtons } from '#/components/OAuthButtons';
+
+describe('OAuthButtons', () => {
+ it('renders the "or" divider', () => {
+ render();
+ expect(screen.getByText('or')).toBeInTheDocument();
+ });
+
+ it('renders Google OAuth link with the label', () => {
+ render();
+ const link = screen.getByRole('link', { name: /sign up with google/i });
+ expect(link).toHaveAttribute('href', '/auth/oauth/google/start');
+ });
+
+ it('renders GitHub OAuth link with the label', () => {
+ render();
+ const link = screen.getByRole('link', { name: /log in with github/i });
+ expect(link).toHaveAttribute('href', '/auth/oauth/github/start');
+ });
+
+ it('renders both OAuth buttons', () => {
+ render();
+ expect(screen.getByRole('link', { name: /continue with google/i })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: /continue with github/i })).toBeInTheDocument();
+ });
+});