diff --git a/src/features/flyer/FlyerUploader.test.tsx b/src/features/flyer/FlyerUploader.test.tsx
index ad0d142d..34c9329e 100644
--- a/src/features/flyer/FlyerUploader.test.tsx
+++ b/src/features/flyer/FlyerUploader.test.tsx
@@ -84,14 +84,22 @@ 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 inside act().');
- // fireEvent is already wrapped in act(). No need for an explicit, outer act().
+ 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] ---: 5. Waiting for UI to update to polling state ("Checking...").');
- await screen.findByText('Checking...');
- console.log('--- [TEST LOG] ---: 6. UI updated to polling state.');
+ try {
+ await screen.findByText('Checking...');
+ console.log('--- [TEST LOG] ---: 6. 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.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
+ }
expect(mockedAiApiClient.uploadAndProcessFlyer).toHaveBeenCalledWith(file, 'mock-checksum');
expect(mockedAiApiClient.getJobStatus).toHaveBeenCalledTimes(1);
@@ -107,6 +115,7 @@ describe('FlyerUploader', () => {
await waitFor(() => {
expect(mockedAiApiClient.getJobStatus).toHaveBeenCalledTimes(2);
});
+ console.log('--- [TEST LOG] ---: 11. Test finished successfully.');
});
it('should poll for status, complete successfully, and redirect', async () => {
@@ -126,31 +135,40 @@ describe('FlyerUploader', () => {
fireEvent.change(input, { target: { files: [file] } });
console.log('--- [TEST LOG] ---: 3. Waiting for UI to update to "Analyzing...".');
- await screen.findByText('Analyzing...');
+ try {
+ 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;
+ }
expect(mockedAiApiClient.getJobStatus).toHaveBeenCalledTimes(1);
- console.log('--- [TEST LOG] ---: 4. UI is showing "Analyzing...", first poll confirmed.');
+ console.log('--- [TEST LOG] ---: 5. First poll confirmed.');
- console.log(`--- [TEST LOG] ---: 5. Advancing timers by 4000ms for the second poll.`);
+ 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. Timers advanced.`);
+ console.log(`--- [TEST LOG] ---: 7. Timers advanced.`);
- console.log('--- [TEST LOG] ---: 7. Waiting for 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] ---: 8. Completion message found.');
+ console.log('--- [TEST LOG] ---: 9. Completion message found.');
- console.log(`--- [TEST LOG] ---: 9. Advancing timers by 2000ms for redirect.`);
+ console.log(`--- [TEST LOG] ---: 10. Advancing timers by 2000ms for redirect.`);
await act(async () => {
await vi.advanceTimersByTimeAsync(2000);
});
- console.log(`--- [TEST LOG] ---: 10. Timers advanced.`);
+ console.log(`--- [TEST LOG] ---: 11. Timers advanced.`);
expect(onProcessingComplete).toHaveBeenCalled();
expect(navigateSpy).toHaveBeenCalledWith('/flyers/42');
+ console.log('--- [TEST LOG] ---: 12. Callback and navigation confirmed. Test finished successfully.');
});
it('should handle a failed job', async () => {
@@ -166,12 +184,23 @@ describe('FlyerUploader', () => {
renderComponent();
const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
const input = screen.getByLabelText(/click to select a file/i);
+
fireEvent.change(input, { target: { files: [file] } });
console.log('--- [TEST LOG] ---: 3. File upload triggered.');
console.log('--- [TEST LOG] ---: 4. Waiting for failure message.');
- expect(await screen.findByText(/Processing failed: AI model exploded/i)).toBeInTheDocument();
- console.log('--- [TEST LOG] ---: 5. Failure message found.');
+ try {
+ 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;
+ }
+
+ expect(screen.getByText('Upload Another Flyer')).toBeInTheDocument();
+ console.log('--- [TEST LOG] ---: 6. "Upload Another" button confirmed. Test finished successfully.');
});
it('should handle a duplicate flyer error (409)', async () => {
@@ -180,17 +209,27 @@ 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);
+
fireEvent.change(input, { target: { files: [file] } });
console.log('--- [TEST LOG] ---: 3. File upload triggered.');
console.log('--- [TEST LOG] ---: 4. Waiting for duplicate flyer message.');
- expect(await screen.findByText('This flyer has already been processed. You can view it here:')).toBeInTheDocument();
+ 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.');
+ } 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;
+ }
+
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.');
});
it('should allow the user to stop watching progress', async () => {
@@ -206,19 +245,38 @@ describe('FlyerUploader', () => {
renderComponent();
const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
const input = screen.getByLabelText(/click to select a file/i);
+
fireEvent.change(input, { target: { files: [file] } });
console.log('--- [TEST LOG] ---: 3. File upload triggered.');
console.log('--- [TEST LOG] ---: 4. Waiting for polling UI.');
- const stopButton = await screen.findByRole('button', { name: 'Stop Watching Progress' });
- console.log('--- [TEST LOG] ---: 5. Polling UI is visible.');
-
+ let stopButton;
+ try {
+ 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.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.');
- expect(await screen.findByText(/click to select a file/i)).toBeInTheDocument();
+ try {
+ 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;
+ }
+
expect(screen.queryByText('Analyzing...')).not.toBeInTheDocument();
+ console.log('--- [TEST LOG] ---: 10. UI has reset. Test finished successfully.');
});
});
\ No newline at end of file
diff --git a/src/pages/admin/components/ProfileManager.Authenticated.test.tsx b/src/pages/admin/components/ProfileManager.Authenticated.test.tsx
index ee915845..debf36d2 100644
--- a/src/pages/admin/components/ProfileManager.Authenticated.test.tsx
+++ b/src/pages/admin/components/ProfileManager.Authenticated.test.tsx
@@ -169,24 +169,36 @@ describe('ProfileManager Authenticated User Features', () => {
});
it('should show an error if updating the address fails', async () => {
+ // --- TEST SETUP & LOGGING ---
+ console.log('[TEST LOG] Setting up mocks for "should show an error if updating the address fails" test.');
+ // Explicitly mock the successful initial address fetch for this test to ensure it resolves.
+ vi.mocked(mockedApiClient.getUserAddress).mockResolvedValue(
+ new Response(JSON.stringify(mockAddress), { status: 200 })
+ );
// Ensure profile update succeeds so we isolate the address update failure
vi.mocked(mockedApiClient.updateUserProfile).mockResolvedValueOnce(
new Response(JSON.stringify(authenticatedProfile), { status: 200 })
);
-
// Mock the failing promise for the address update.
vi.mocked(mockedApiClient.updateUserAddress).mockRejectedValueOnce(new Error('Address update failed'));
+ console.log('[TEST LOG] Mocks are set. Rendering component...');
+ // --- END TEST SETUP ---
render();
// Wait for initial data fetch (getUserAddress) to complete
console.log('[TEST LOG] Waiting for initial address data to load...');
await waitFor(() => expect(screen.getByLabelText(/city/i)).toHaveValue(mockAddress.city));
+ console.log('[TEST LOG] Initial address data loaded successfully.');
+ const cityInput = screen.getByLabelText(/city/i);
console.log('[TEST LOG] Firing change event on city input.');
fireEvent.change(screen.getByLabelText(/city/i), { target: { value: 'NewCity' } });
- // Use fireEvent.submit on the form for more robust testing of submission logic.
- fireEvent.submit(screen.getByTestId('profile-form'));
+ 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] Waiting for notifyError to be called...');
// Since only the address changed and it failed, we expect an error notification (handled by useApi)
// and NOT a success message.
await waitFor(() => {
diff --git a/src/pages/admin/components/ProfileManager.tsx b/src/pages/admin/components/ProfileManager.tsx
index 4dc54109..6ed370fb 100644
--- a/src/pages/admin/components/ProfileManager.tsx
+++ b/src/pages/admin/components/ProfileManager.tsx
@@ -77,24 +77,32 @@ export const ProfileManager: React.FC = ({ isOpen, onClose,
const { execute: fetchAddress } = useApi(fetchAddressWrapper);
const handleAddressFetch = useCallback(async (addressId: number) => {
+ logger.debug(`[handleAddressFetch] Starting fetch for addressId: ${addressId}`);
const fetchedAddress = await fetchAddress(addressId);
if (fetchedAddress) {
+ logger.debug('[handleAddressFetch] Successfully fetched address:', fetchedAddress);
setAddress(fetchedAddress);
setInitialAddress(fetchedAddress); // Set initial address on fetch
+ } else {
+ logger.warn(`[handleAddressFetch] Fetch returned null or undefined for addressId: ${addressId}. This might indicate a network error caught by useApi.`);
}
}, [fetchAddress]);
useEffect(() => {
// Only reset state when the modal is opened.
// Do not reset on profile changes, which can happen during sign-out.
+ logger.debug('[useEffect] Running effect due to change in isOpen or profile.', { isOpen, profileExists: !!profile });
if (isOpen && profile) { // Ensure profile exists before setting state
+ logger.debug('[useEffect] Modal is open with a valid profile. Resetting component state.');
setFullName(profile?.full_name || '');
setAvatarUrl(profile?.avatar_url || '');
// If the user has an address, fetch its details
if (profile.address_id) {
+ logger.debug(`[useEffect] Profile has address_id: ${profile.address_id}. Calling handleAddressFetch.`);
handleAddressFetch(profile.address_id);
} else {
// Reset address form if user has no address
+ logger.debug('[useEffect] Profile has no address_id. Resetting address form.');
setAddress({});
setInitialAddress({});
}
@@ -102,6 +110,7 @@ export const ProfileManager: React.FC = ({ isOpen, onClose,
setIsConfirmingDelete(false);
setPasswordForDelete('');
} else {
+ logger.debug('[useEffect] Modal is closed or profile is null. Resetting address state only.');
setAddress({});
setInitialAddress({});
}
@@ -171,16 +180,11 @@ export const ProfileManager: React.FC = ({ isOpen, onClose,
}
onClose();
} else {
- // A failure occurred. The specific error notification has already been
- // displayed by the failed useApi hook. We simply log it and keep the
- // modal open for the user to correct any issues.
- // A failure occurred. The specific error notification has already been displayed by the failed useApi hook. We simply log it and keep the modal open for the user to correct any issues.
- logger.warn('handleProfileSave completed with one or more failures.');
+ logger.warn('[handleProfileSave] One or more operations failed. The useApi hook should have shown an error notification.');
}
} catch (error) {
// This catch block is a safeguard. In normal operation, the useApi hook
// should prevent any promises from rejecting.
- // This catch block is a safeguard. In normal operation, the useApi hook should prevent any promises from rejecting.
logger.error({ err: error }, 'An unexpected error occurred in handleProfileSave:');
}
};
diff --git a/src/utils/checksum.test.ts b/src/utils/checksum.test.ts
index eb279041..d0e9fd2f 100644
--- a/src/utils/checksum.test.ts
+++ b/src/utils/checksum.test.ts
@@ -52,6 +52,8 @@ describe('generateFileChecksum', () => {
describe('with fallback mechanisms', () => {
beforeEach(() => {
+ // Clear mock history before each test to ensure isolation
+ vi.clearAllMocks();
// Mock logger.warn to prevent logs from appearing in test output
vi.spyOn(logger, 'warn').mockImplementation(() => {});
});