Skip to content

Commit 312fb45

Browse files
esmcelroyCopilot
andcommitted
test: reach 86% coverage with 80% enforced thresholds
Coverage: 86% lines, 87% branches, 78% functions, 83% statements - heicUtils.ts: 20% → 96% (+10 tests for conversion & processing) - PaddingSettingsPanel.tsx: 33% → 73% (+19 tests for all controls) - PhotoGrid.tsx: 27% → 64% (+6 tests for clipboard, download, toggle) - App.tsx: 24% → 71% (+5 tests for theme, upload, process flow) - Thresholds raised to 80% lines/branches/statements, 70% functions - Total: 120 unit tests + 17 E2E tests = 137 tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 62fe80c commit 312fb45

5 files changed

Lines changed: 736 additions & 11 deletions

File tree

src/__tests__/App.test.tsx

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2-
import { render, screen } from '@testing-library/react';
2+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
34

45
// Mock dependencies before importing App
56
vi.mock('../lib/imageUtils', () => ({
@@ -82,4 +83,125 @@ describe('App', () => {
8283
render(<App />);
8384
expect(screen.queryByText('Clear all')).not.toBeInTheDocument();
8485
});
86+
87+
// --- Theme toggle ---
88+
it('clicking Dark theme button adds dark class to document', async () => {
89+
const user = userEvent.setup();
90+
render(<App />);
91+
92+
await user.click(screen.getByTitle('Dark'));
93+
expect(document.documentElement.classList.contains('dark')).toBe(true);
94+
});
95+
96+
it('clicking Light theme button removes dark class', async () => {
97+
const user = userEvent.setup();
98+
document.documentElement.classList.add('dark');
99+
render(<App />);
100+
101+
await user.click(screen.getByTitle('Light'));
102+
expect(document.documentElement.classList.contains('dark')).toBe(false);
103+
});
104+
105+
// Helper to set up FileReader mock and upload mocks
106+
function setupUploadMocks() {
107+
const origFileReader = global.FileReader;
108+
109+
class MockFileReader {
110+
result: string | null = 'data:image/png;base64,abc';
111+
onload: (() => void) | null = null;
112+
onerror: (() => void) | null = null;
113+
readAsDataURL() {
114+
setTimeout(() => this.onload?.(), 0);
115+
}
116+
}
117+
global.FileReader = MockFileReader as any;
118+
119+
return {
120+
restore: () => { global.FileReader = origFileReader; },
121+
};
122+
}
123+
124+
async function setupMocksAndUpload() {
125+
const { processFilesForHeic } = await import('../lib/heicUtils');
126+
const { getImageDimensions } = await import('../lib/imageUtils');
127+
128+
const mockFile = new File(['pixel'], 'test.png', { type: 'image/png' });
129+
(processFilesForHeic as ReturnType<typeof vi.fn>).mockResolvedValue({
130+
converted: [mockFile],
131+
errors: [],
132+
});
133+
(getImageDimensions as ReturnType<typeof vi.fn>).mockResolvedValue({ width: 200, height: 100 });
134+
135+
return mockFile;
136+
}
137+
138+
// --- Photo upload and stats bar ---
139+
it('shows stats bar and clear all after uploading photos', async () => {
140+
const mockFile = await setupMocksAndUpload();
141+
const { restore } = setupUploadMocks();
142+
143+
render(<App />);
144+
145+
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
146+
fireEvent.change(input, { target: { files: [mockFile] } });
147+
148+
await waitFor(() => {
149+
expect(screen.getByText(/1 photo/)).toBeInTheDocument();
150+
});
151+
expect(screen.getByText('Clear all')).toBeInTheDocument();
152+
153+
restore();
154+
});
155+
156+
// --- Clear all ---
157+
it('clicking Clear all removes all photos', async () => {
158+
const user = userEvent.setup();
159+
const mockFile = await setupMocksAndUpload();
160+
const { restore } = setupUploadMocks();
161+
162+
render(<App />);
163+
164+
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
165+
fireEvent.change(input, { target: { files: [mockFile] } });
166+
167+
await waitFor(() => {
168+
expect(screen.getByText(/1 photo/)).toBeInTheDocument();
169+
});
170+
171+
await user.click(screen.getByText('Clear all'));
172+
expect(screen.queryByText(/1 photo/)).not.toBeInTheDocument();
173+
expect(screen.queryByText('Clear all')).not.toBeInTheDocument();
174+
175+
restore();
176+
});
177+
178+
// --- Process flow ---
179+
it('processing photos shows Download All button after completion', async () => {
180+
const user = userEvent.setup();
181+
const mockFile = await setupMocksAndUpload();
182+
const { padImageToAspectRatio } = await import('../lib/imageUtils');
183+
(padImageToAspectRatio as ReturnType<typeof vi.fn>).mockResolvedValue('data:image/png;base64,padded');
184+
const { restore } = setupUploadMocks();
185+
186+
render(<App />);
187+
188+
// Upload a photo
189+
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
190+
fireEvent.change(input, { target: { files: [mockFile] } });
191+
192+
await waitFor(() => {
193+
expect(screen.getByText(/1 photo/)).toBeInTheDocument();
194+
});
195+
196+
// Process
197+
const processButton = screen.getByText('Process Images').closest('button')!;
198+
expect(processButton).not.toBeDisabled();
199+
await user.click(processButton);
200+
201+
await waitFor(() => {
202+
expect(screen.getByText('Download All as ZIP')).toBeInTheDocument();
203+
});
204+
205+
restore();
206+
});
85207
});

0 commit comments

Comments
 (0)