Refactor: Enhance test robustness by using form submission in ProfileManager and improve logging in FlyerUploader tests
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 23m6s

This commit is contained in:
2025-12-16 11:24:05 -08:00
parent 9f9aa884ea
commit 9be01a9dc2
3 changed files with 95 additions and 75 deletions

View File

@@ -48,20 +48,19 @@ describe('FlyerUploader', () => {
const navigateSpy = vi.fn();
beforeEach(() => {
console.log(`\n--- [TEST LOG] ---: Starting test: "${expect.getState().currentTestName}" ---`);
vi.resetAllMocks();
console.log(`\n--- [TEST LOG] ---: Starting test: "${expect.getState().currentTestName}"`);
// Use the 'modern' implementation of fake timers to handle promise microtasks correctly.
vi.useFakeTimers({ toFake: ['setTimeout'], shouldAdvanceTime: true });
console.log('--- [TEST LOG] ---: MODERN fake timers enabled.');
vi.resetAllMocks(); // Resets mock implementations AND call history.
console.log('--- [TEST LOG] ---: Mocks reset.');
vi.useFakeTimers();
console.log('--- [TEST LOG] ---: Fake timers enabled.');
mockedChecksumModule.generateFileChecksum.mockResolvedValue('mock-checksum');
(useNavigate as Mock).mockReturnValue(navigateSpy);
});
afterEach(() => {
vi.useRealTimers();
console.log('--- [TEST LOG] ---: Real timers restored.');
console.log(`--- [TEST LOG] ---: Finished test: "${expect.getState().currentTestName}" ---\n`);
console.log(`--- [TEST LOG] ---: Finished test: "${expect.getState().currentTestName}"\n`);
});
it('should render the initial state correctly', () => {
@@ -86,36 +85,43 @@ describe('FlyerUploader', () => {
console.log('--- [TEST LOG] ---: 3. Firing file change event.');
fireEvent.change(input, { target: { files: [file] } });
console.log('--- [TEST LOG] ---: 4. File change event fired.');
console.log('--- [TEST LOG] ---: 4. File change event fired. Now AWAITING UI update.');
console.log('--- [TEST LOG] ---: 5. Waiting for UI to update to polling state ("Checking...").');
try {
await screen.findByText('Checking...');
console.log('--- [TEST LOG] ---: 6. SUCCESS: UI updated to polling state.');
console.log('--- [TEST LOG] ---: 5. SUCCESS: UI updated to polling state.');
console.log('--- [DEBUG] ---: DOM after findByText success:');
screen.debug();
} catch (error) {
console.error('--- [TEST LOG] ---: 6. ERROR: findByText("Checking...") timed out.');
console.error('--- [TEST LOG] ---: 5. ERROR: findByText("Checking...") timed out.');
console.log('--- [DEBUG] ---: DOM at time of failure:');
screen.debug(); // Print the DOM when the error occurs
throw error; // Re-throw the error to fail the test
}
console.log('--- [TEST LOG] ---: 6. Asserting mock calls after UI update.');
expect(mockedAiApiClient.uploadAndProcessFlyer).toHaveBeenCalledWith(file, 'mock-checksum');
expect(mockedAiApiClient.getJobStatus).toHaveBeenCalledTimes(1);
console.log('--- [TEST LOG] ---: 7. Verified upload and initial poll calls.');
console.log('--- [TEST LOG] ---: 7. Verified upload and initial poll calls. Now AWAITING timer advancement.');
console.log(`--- [TEST LOG] ---: 8. Advancing timers by 3000ms.`);
await act(async () => {
await vi.advanceTimersByTimeAsync(3000);
console.log('--- [TEST LOG] ---: 8. Advancing timers by 3000ms...');
vi.advanceTimersByTime(3000);
});
console.log(`--- [TEST LOG] ---: 9. Timers advanced.`);
console.log('--- [TEST LOG] ---: 9. Timers advanced. Now AWAITING second poll.');
console.log('--- [TEST LOG] ---: 10. Waiting for getJobStatus to be called a second time.');
await waitFor(() => {
expect(mockedAiApiClient.getJobStatus).toHaveBeenCalledTimes(2);
});
console.log('--- [TEST LOG] ---: 11. Test finished successfully.');
try {
await waitFor(() => {
console.log('--- [TEST LOG] ---: 10. waitFor check: checking for 2 getJobStatus calls...');
expect(mockedAiApiClient.getJobStatus).toHaveBeenCalledTimes(2);
});
console.log('--- [TEST LOG] ---: 11. SUCCESS: Second poll confirmed.');
} catch(error) {
console.error('--- [TEST LOG] ---: 11. ERROR: waitFor for second poll timed out.');
console.log('--- [DEBUG] ---: DOM at time of failure:');
screen.debug();
throw error;
}
});
it('should poll for status, complete successfully, and redirect', async () => {
@@ -134,41 +140,48 @@ describe('FlyerUploader', () => {
const input = screen.getByLabelText(/click to select a file/i);
fireEvent.change(input, { target: { files: [file] } });
console.log('--- [TEST LOG] ---: 3. Waiting for UI to update to "Analyzing...".');
console.log('--- [TEST LOG] ---: 3. Fired event. Now AWAITING UI update to "Analyzing...".');
try {
await screen.findByText('Analyzing...');
console.log('--- [TEST LOG] ---: 4. SUCCESS: UI is showing "Analyzing...".');
await screen.findByText('Analyzing...');
console.log('--- [TEST LOG] ---: 4. SUCCESS: UI is showing "Analyzing...".');
} catch(error) {
console.error('--- [TEST LOG] ---: 4. ERROR: findByText("Analyzing...") timed out.');
console.log('--- [DEBUG] ---: DOM at time of failure:');
screen.debug();
throw error;
console.error('--- [TEST LOG] ---: 4. ERROR: findByText("Analyzing...") timed out.');
console.log('--- [DEBUG] ---: DOM at time of failure:');
screen.debug();
throw error;
}
expect(mockedAiApiClient.getJobStatus).toHaveBeenCalledTimes(1);
console.log('--- [TEST LOG] ---: 5. First poll confirmed.');
console.log('--- [TEST LOG] ---: 5. First poll confirmed. Now AWAITING timer advancement.');
console.log(`--- [TEST LOG] ---: 6. Advancing timers by 4000ms for the second poll.`);
await act(async () => {
await vi.advanceTimersByTimeAsync(4000);
console.log(`--- [TEST LOG] ---: 6. Advancing timers by 4000ms for the second poll...`);
vi.advanceTimersByTime(4000);
});
console.log(`--- [TEST LOG] ---: 7. Timers advanced.`);
console.log(`--- [TEST LOG] ---: 7. Timers advanced. Now AWAITING completion message.`);
console.log('--- [TEST LOG] ---: 8. Waiting for completion message.');
await waitFor(() => {
expect(mockedAiApiClient.getJobStatus).toHaveBeenCalledTimes(2);
expect(screen.getByText('Processing complete! Redirecting to flyer 42...')).toBeInTheDocument();
});
console.log('--- [TEST LOG] ---: 9. Completion message found.');
try {
await waitFor(() => {
console.log('--- [TEST LOG] ---: 8. waitFor check: checking for completion message...');
expect(screen.getByText('Processing complete! Redirecting to flyer 42...')).toBeInTheDocument();
});
console.log('--- [TEST LOG] ---: 9. SUCCESS: Completion message found.');
} catch (error) {
console.error('--- [TEST LOG] ---: 9. ERROR: waitFor for completion message timed out.');
console.log('--- [DEBUG] ---: DOM at time of failure:');
screen.debug();
throw error;
}
expect(mockedAiApiClient.getJobStatus).toHaveBeenCalledTimes(2);
console.log(`--- [TEST LOG] ---: 10. Advancing timers by 2000ms for redirect.`);
await act(async () => {
await vi.advanceTimersByTimeAsync(2000);
console.log(`--- [TEST LOG] ---: 10. Advancing timers by 2000ms for redirect...`);
vi.advanceTimersByTime(2000);
});
console.log(`--- [TEST LOG] ---: 11. Timers advanced.`);
console.log(`--- [TEST LOG] ---: 11. Timers advanced. Now asserting navigation.`);
expect(onProcessingComplete).toHaveBeenCalled();
expect(navigateSpy).toHaveBeenCalledWith('/flyers/42');
console.log('--- [TEST LOG] ---: 12. Callback and navigation confirmed. Test finished successfully.');
console.log('--- [TEST LOG] ---: 12. Callback and navigation confirmed.');
});
it('should handle a failed job', async () => {
@@ -185,22 +198,23 @@ describe('FlyerUploader', () => {
const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
const input = screen.getByLabelText(/click to select a file/i);
console.log('--- [TEST LOG] ---: 3. Firing file change event.');
fireEvent.change(input, { target: { files: [file] } });
console.log('--- [TEST LOG] ---: 3. File upload triggered.');
console.log('--- [TEST LOG] ---: 4. Waiting for failure message.');
try {
expect(await screen.findByText(/Processing failed: AI model exploded/i)).toBeInTheDocument();
console.log('--- [TEST LOG] ---: 5. SUCCESS: Failure message found.');
console.log('--- [TEST LOG] ---: 4. AWAITING failure message...');
expect(await screen.findByText(/Processing failed: AI model exploded/i)).toBeInTheDocument();
console.log('--- [TEST LOG] ---: 5. SUCCESS: Failure message found.');
} catch (error) {
console.error('--- [TEST LOG] ---: 5. ERROR: findByText for failure message timed out.');
console.log('--- [DEBUG] ---: DOM at time of failure:');
screen.debug();
throw error;
console.error('--- [TEST LOG] ---: 5. ERROR: findByText for failure message timed out.');
console.log('--- [DEBUG] ---: DOM at time of failure:');
screen.debug();
throw error;
}
expect(screen.getByText('Upload Another Flyer')).toBeInTheDocument();
console.log('--- [TEST LOG] ---: 6. "Upload Another" button confirmed. Test finished successfully.');
console.log('--- [TEST LOG] ---: 6. "Upload Another" button confirmed.');
});
it('should handle a duplicate flyer error (409)', async () => {
@@ -209,27 +223,29 @@ describe('FlyerUploader', () => {
new Response(JSON.stringify({ flyerId: 99, message: 'Duplicate' }), { status: 409 })
);
console.log('--- [TEST LOG] ---: 2. Rendering and uploading.');
renderComponent();
const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
const input = screen.getByLabelText(/click to select a file/i);
console.log('--- [TEST LOG] ---: 3. Firing file change event.');
fireEvent.change(input, { target: { files: [file] } });
console.log('--- [TEST LOG] ---: 3. File upload triggered.');
console.log('--- [TEST LOG] ---: 4. Waiting for duplicate flyer message.');
try {
expect(await screen.findByText('This flyer has already been processed. You can view it here:')).toBeInTheDocument();
console.log('--- [TEST LOG] ---: 5. SUCCESS: Duplicate message found.');
console.log('--- [TEST LOG] ---: 4. AWAITING duplicate flyer message...');
expect(await screen.findByText('This flyer has already been processed. You can view it here:')).toBeInTheDocument();
console.log('--- [TEST LOG] ---: 5. SUCCESS: Duplicate message found.');
} catch(error) {
console.error('--- [TEST LOG] ---: 5. ERROR: findByText for duplicate message timed out.');
console.log('--- [DEBUG] ---: DOM at time of failure:');
screen.debug();
throw error;
console.error('--- [TEST LOG] ---: 5. ERROR: findByText for duplicate message timed out.');
console.log('--- [DEBUG] ---: DOM at time of failure:');
screen.debug();
throw error;
}
const link = screen.getByRole('link', { name: /Flyer #99/i });
expect(link).toHaveAttribute('href', '/flyers/99');
console.log('--- [TEST LOG] ---: 6. Duplicate link confirmed. Test finished successfully.');
console.log('--- [TEST LOG] ---: 6. Duplicate link confirmed.');
});
it('should allow the user to stop watching progress', async () => {
@@ -246,37 +262,37 @@ describe('FlyerUploader', () => {
const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
const input = screen.getByLabelText(/click to select a file/i);
console.log('--- [TEST LOG] ---: 3. Firing file change event.');
fireEvent.change(input, { target: { files: [file] } });
console.log('--- [TEST LOG] ---: 3. File upload triggered.');
console.log('--- [TEST LOG] ---: 4. Waiting for polling UI.');
let stopButton;
try {
stopButton = await screen.findByRole('button', { name: 'Stop Watching Progress' });
console.log('--- [TEST LOG] ---: 5. SUCCESS: Polling UI is visible.');
console.log('--- [TEST LOG] ---: 4. AWAITING polling UI...');
stopButton = await screen.findByRole('button', { name: 'Stop Watching Progress' });
console.log('--- [TEST LOG] ---: 5. SUCCESS: Polling UI is visible.');
} catch(error) {
console.error('--- [TEST LOG] ---: 5. ERROR: findByRole for stop button timed out.');
console.log('--- [DEBUG] ---: DOM at time of failure:');
screen.debug();
throw error;
console.error('--- [TEST LOG] ---: 5. ERROR: findByRole for stop button timed out.');
console.log('--- [DEBUG] ---: DOM at time of failure:');
screen.debug();
throw error;
}
console.log('--- [TEST LOG] ---: 6. Clicking "Stop Watching Progress" button.');
fireEvent.click(stopButton);
console.log('--- [TEST LOG] ---: 7. Click event fired.');
console.log('--- [TEST LOG] ---: 8. Waiting for UI to reset to idle state.');
try {
expect(await screen.findByText(/click to select a file/i)).toBeInTheDocument();
console.log('--- [TEST LOG] ---: 9. SUCCESS: UI has reset.');
console.log('--- [TEST LOG] ---: 8. AWAITING UI reset to idle state...');
expect(await screen.findByText(/click to select a file/i)).toBeInTheDocument();
console.log('--- [TEST LOG] ---: 9. SUCCESS: UI has reset.');
} catch(error) {
console.error('--- [TEST LOG] ---: 9. ERROR: findByText for idle state timed out.');
console.log('--- [DEBUG] ---: DOM at time of failure:');
screen.debug();
throw error;
console.error('--- [TEST LOG] ---: 9. ERROR: findByText for idle state timed out.');
console.log('--- [DEBUG] ---: DOM at time of failure:');
screen.debug();
throw error;
}
expect(screen.queryByText('Analyzing...')).not.toBeInTheDocument();
console.log('--- [TEST LOG] ---: 10. UI has reset. Test finished successfully.');
});
});

View File

@@ -195,8 +195,9 @@ describe('ProfileManager Authenticated User Features', () => {
fireEvent.change(screen.getByLabelText(/city/i), { target: { value: 'NewCity' } });
console.log('[TEST LOG] City input value after change:', (cityInput as HTMLInputElement).value);
console.log('[TEST LOG] Firing click event on "Save Profile" button.');
fireEvent.click(screen.getByRole('button', { name: /save profile/i }));
console.log('[TEST LOG] Firing SUBMIT event on the form.');
// Use fireEvent.submit on the form for more robust testing of submission logic.
fireEvent.submit(screen.getByTestId('profile-form'));
console.log('[TEST LOG] Waiting for notifyError to be called...');
// Since only the address changed and it failed, we expect an error notification (handled by useApi)

View File

@@ -82,7 +82,10 @@ describe('generateFileChecksum', () => {
const checksum = await generateFileChecksum(file);
// Correct SHA-256 for "error fallback" (without a trailing newline).
expect(checksum).toBe('786aaab58cd94f0d74de6895575dcfa7ace2733c9713121e1fac197eb02ec1c2');
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('file.arrayBuffer() threw an error'));
expect(logger.warn).toHaveBeenCalledWith(
expect.stringContaining('file.arrayBuffer() threw an error'),
expect.any(Object)
);
});
it('should throw an error if crypto.subtle is not available', async () => {