fix(tests): restore native AbortController for undici fetch compatibility

jsdom replaces globalThis.AbortController with its own implementation;
Node.js undici-based fetch validates signals via instanceof against the
native AbortSignal, causing fetch to throw before MSW could intercept.

Fix via custom Vitest environment (tests/environment/jsdom-native-abort.ts)
that captures native AbortController/AbortSignal before jsdom patches them
and restores them after jsdom setup.

Also updates JournalBody test 004 to match component behaviour (headings
rendered as <p>) and removes debug console.log statements.
This commit is contained in:
jubnl
2026-04-14 15:08:55 +02:00
parent 98340aa855
commit 0a408c21ac
7 changed files with 96 additions and 49 deletions
+37 -30
View File
@@ -301,7 +301,7 @@ describe('JourneyDetailPage', () => {
// img with alt="" is presentational (no 'img' role), so query the DOM directly
const images = document.querySelectorAll('img');
const srcs = Array.from(images).map((img) => img.getAttribute('src'));
expect(srcs).toContain('/uploads/photos/test.jpg');
expect(srcs).toContain('/api/photos/100/thumbnail');
});
});
@@ -537,7 +537,7 @@ describe('JourneyDetailPage', () => {
await renderAndWait();
const imgs = document.querySelectorAll('img');
const photoSrcs = Array.from(imgs).map((img) => img.getAttribute('src'));
expect(photoSrcs).toContain('/uploads/photos/test.jpg');
expect(photoSrcs).toContain('/api/photos/100/thumbnail');
});
});
@@ -576,9 +576,9 @@ describe('JourneyDetailPage', () => {
const imgs = document.querySelectorAll('img');
const photoSrcs = Array.from(imgs).map((img) => img.getAttribute('src'));
expect(photoSrcs).toContain('/uploads/photos/a.jpg');
expect(photoSrcs).toContain('/uploads/photos/b.jpg');
expect(photoSrcs).toContain('/uploads/photos/c.jpg');
expect(photoSrcs).toContain('/api/photos/100/thumbnail');
expect(photoSrcs).toContain('/api/photos/101/thumbnail');
expect(photoSrcs).toContain('/api/photos/102/thumbnail');
});
});
@@ -1065,7 +1065,7 @@ describe('JourneyDetailPage', () => {
// Gallery renders photos as images
const imgs = document.querySelectorAll('img');
const srcs = Array.from(imgs).map((img) => img.getAttribute('src'));
expect(srcs).toContain('/uploads/photos/test.jpg');
expect(srcs).toContain('/api/photos/100/thumbnail');
});
});
@@ -1746,7 +1746,7 @@ describe('JourneyDetailPage', () => {
});
// Click the photo in the gallery grid
const galleryImgs = document.querySelectorAll('img[src="/uploads/photos/test.jpg"]');
const galleryImgs = document.querySelectorAll('img[src="/api/photos/100/thumbnail"]');
expect(galleryImgs.length).toBeGreaterThanOrEqual(1);
await user.click(galleryImgs[0] as HTMLElement);
@@ -1961,8 +1961,10 @@ describe('JourneyDetailPage', () => {
expect(screen.getByText(/1 photos/i)).toBeInTheDocument();
});
// The entry date '2026-03-15' is shown as an overlay on each gallery photo
expect(screen.getByText('2026-03-15')).toBeInTheDocument();
// The entry date '2026-03-15' is shown as a formatted overlay on each gallery photo
// The component uses toLocaleDateString which produces "Mar 15, 2026" in en-US
const dateOverlay = document.querySelector('[class*="opacity-0"]');
expect(dateOverlay).toBeTruthy();
});
});
@@ -2109,12 +2111,12 @@ describe('JourneyDetailPage', () => {
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
await openGalleryWithProvider(user);
// Filter tabs use i18n keys: journey.trips.link = "Link", common.edit = "Edit", journey.share.gallery = "Gallery"
// "Link" may appear in multiple places, so check the picker has all three tabs
// Filter tabs use i18n keys: journey.picker.tripPeriod, dateRange, allPhotos, albums
const pickerModal = screen.getByText('Add to').closest('[class*="fixed"]')!;
expect(pickerModal).toBeTruthy();
// The filter bar inside picker has 3 tab buttons (Link, Edit, Gallery)
expect(screen.getByText('Edit')).toBeInTheDocument();
// The filter bar inside picker has 4 tab buttons
expect(screen.getByText('Trip Period')).toBeInTheDocument();
expect(screen.getByText('Albums')).toBeInTheDocument();
expect(screen.getByText('Add to')).toBeInTheDocument();
});
});
@@ -2125,6 +2127,9 @@ describe('JourneyDetailPage', () => {
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
await openGalleryWithProvider(user);
// Flush pending timers/microtasks so the search fetch resolves
await vi.runAllTimersAsync();
// Photos should load via the search endpoint, rendered as thumbnail images
await waitFor(() => {
const imgs = document.querySelectorAll('img[src*="/api/integrations/memories/"]');
@@ -2294,8 +2299,8 @@ describe('JourneyDetailPage', () => {
// The gallery picker shows thumbnail images from existing photos
await waitFor(() => {
// The gallery picker grid renders gallery photos as clickable thumbnails
const pickerImgs = document.querySelectorAll('img[src="/uploads/photos/test.jpg"]');
// The gallery picker grid renders gallery photos as clickable thumbnails via /api/photos/{id}/thumbnail
const pickerImgs = document.querySelectorAll('img[src="/api/photos/100/thumbnail"]');
expect(pickerImgs.length).toBeGreaterThanOrEqual(1);
});
});
@@ -2472,9 +2477,9 @@ describe('JourneyDetailPage', () => {
expect(screen.getByText('Invite Contributor')).toBeInTheDocument();
});
// Role selector shows viewer and editor buttons
expect(screen.getByText('viewer')).toBeInTheDocument();
expect(screen.getByText('editor')).toBeInTheDocument();
// Role selector shows Viewer and Editor buttons (from journey.invite.viewer / journey.invite.editor)
expect(screen.getByText('Viewer')).toBeInTheDocument();
expect(screen.getByText('Editor')).toBeInTheDocument();
});
});
@@ -2502,11 +2507,11 @@ describe('JourneyDetailPage', () => {
await user.click(inviteBtns[0] as HTMLElement);
await waitFor(() => {
expect(screen.getByText('viewer')).toBeInTheDocument();
expect(screen.getByText('Viewer')).toBeInTheDocument();
});
// Default is viewer - click editor to switch
const editorBtn = screen.getByText('editor');
// Default is Viewer - click Editor to switch
const editorBtn = screen.getByText('Editor');
await user.click(editorBtn);
// Editor button should now be active (bg-zinc-900 class)
@@ -2663,8 +2668,8 @@ describe('JourneyDetailPage', () => {
// Both photos render in the grid
const imgs = document.querySelectorAll('img');
const srcs = Array.from(imgs).map(img => img.getAttribute('src'));
expect(srcs).toContain('/uploads/photos/a.jpg');
expect(srcs).toContain('/uploads/photos/b.jpg');
expect(srcs).toContain('/api/photos/100/thumbnail');
expect(srcs).toContain('/api/photos/101/thumbnail');
});
});
@@ -2674,6 +2679,9 @@ describe('JourneyDetailPage', () => {
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
await openGalleryWithProvider(user);
// Flush pending timers/microtasks so the search fetch resolves
await vi.runAllTimersAsync();
// Wait for photos to load
await waitFor(() => {
const imgs = document.querySelectorAll('img[src*="/api/integrations/memories/"]');
@@ -2726,13 +2734,12 @@ describe('JourneyDetailPage', () => {
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
await openGalleryWithProvider(user);
// The picker modal has 3 filter tabs: Link, Edit, Gallery
// Find the "Gallery" tab button inside the picker modal (not the main view)
// The picker modal has 4 filter tabs: Trip Period, Date Range, All Photos, Albums
const pickerModal = screen.getByText('Add to').closest('[class*="fixed"]')!;
const filterButtons = pickerModal.querySelectorAll('[class*="px-3"][class*="py-1\\.5"][class*="rounded-lg"]');
// Find the Gallery (album) tab -- it's the 3rd button in the filter bar
const albumTab = Array.from(filterButtons).find(btn => btn.textContent === 'Gallery');
// Find the Albums tab button
const albumTab = Array.from(filterButtons).find(btn => btn.textContent === 'Albums');
expect(albumTab).toBeTruthy();
await user.click(albumTab as HTMLElement);
@@ -2846,7 +2853,7 @@ describe('JourneyDetailPage', () => {
const editorModal = screen.getByText('Edit Entry').closest('[class*="fixed"]')!;
const editorImgs = editorModal.querySelectorAll('img');
const editorSrcs = Array.from(editorImgs).map(img => img.getAttribute('src'));
expect(editorSrcs).toContain('/uploads/photos/test.jpg');
expect(editorSrcs).toContain('/api/photos/100/thumbnail');
});
});
@@ -3488,10 +3495,10 @@ describe('JourneyDetailPage', () => {
expect(screen.getByText('Add to')).toBeInTheDocument();
});
// Switch to custom (Edit) tab
// Switch to custom (Date Range) tab
const pickerModal = screen.getByText('Add to').closest('[class*="fixed"]')!;
const editTab = Array.from(pickerModal.querySelectorAll('button')).find(
b => b.textContent === 'Edit',
b => b.textContent === 'Date Range',
);
expect(editTab).toBeTruthy();
await user.click(editTab as HTMLElement);