Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist
.env
*.db
.turbo
coverage
53 changes: 49 additions & 4 deletions packages/web/src/pages/Home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ beforeEach(() => {
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
global.fetch = vi.fn().mockImplementation((url: string) => {
if (url.includes('/meta/genres')) {
return Promise.resolve({ json: () => Promise.resolve(mockGenres) })
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockGenres) })
}
return Promise.resolve({ json: () => Promise.resolve(mockMovies) })
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockMovies) })
})
})

Expand Down Expand Up @@ -92,7 +92,7 @@ describe('Home — search and genre filter', () => {
const fetchError = new Error('Network error')
global.fetch = vi.fn().mockImplementation((url: string) => {
if (url.includes('/meta/genres')) {
return Promise.resolve({ json: () => Promise.resolve(mockGenres) })
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockGenres) })
}
return Promise.reject(fetchError)
})
Expand All @@ -107,10 +107,55 @@ describe('Home — search and genre filter', () => {
if (url.includes('/meta/genres')) {
return Promise.reject(fetchError)
}
return Promise.resolve({ json: () => Promise.resolve(mockMovies) })
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockMovies) })
})

renderHome()
await waitFor(() => expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to fetch genres:', fetchError))
})

it('does not log console.error when fetch is aborted (component unmount)', async () => {
const abortError = Object.assign(new Error('Aborted'), { name: 'AbortError' })
global.fetch = vi.fn().mockRejectedValue(abortError)

renderHome()
// Wait for promises to settle
await new Promise(r => setTimeout(r, 0))

expect(consoleErrorSpy).not.toHaveBeenCalled()
})

it('logs console.error when movies fetch returns a non-OK HTTP status', async () => {
global.fetch = vi.fn().mockImplementation((url: string) => {
if (url.includes('/meta/genres')) {
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockGenres) })
}
return Promise.resolve({ ok: false, status: 500 })
})

renderHome()
await waitFor(() =>
expect(consoleErrorSpy).toHaveBeenCalledWith(
'Failed to fetch movies:',
expect.objectContaining({ message: 'HTTP error! status: 500' })
)
)
})

it('logs console.error when genres fetch returns a non-OK HTTP status', async () => {
global.fetch = vi.fn().mockImplementation((url: string) => {
if (url.includes('/meta/genres')) {
return Promise.resolve({ ok: false, status: 404 })
}
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockMovies) })
})

renderHome()
await waitFor(() =>
expect(consoleErrorSpy).toHaveBeenCalledWith(
'Failed to fetch genres:',
expect.objectContaining({ message: 'HTTP error! status: 404' })
)
)
})
})
23 changes: 19 additions & 4 deletions packages/web/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,38 @@ function Home() {
const [genres, setGenres] = useState<string[]>([]);

useEffect(() => {
fetch('/api/movies')
.then(res => res.json())
const controller = new AbortController();
const { signal } = controller;

fetch('/api/movies', { signal })
.then(res => {
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
return res.json();
})
.then((data: Movie[]) => {
setMovies(data);
setLoading(false);
})
.catch((err: unknown) => {
if (err instanceof Error && err.name === 'AbortError') return;
console.error('Failed to fetch movies:', err);
setLoading(false);
});

fetch('/api/movies/meta/genres')
.then(res => res.json())
fetch('/api/movies/meta/genres', { signal })
.then(res => {
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
return res.json();
})
.then((data: string[]) => setGenres(data))
.catch((err: unknown) => {
if (err instanceof Error && err.name === 'AbortError') return;
console.error('Failed to fetch genres:', err);
});

return () => {
controller.abort();
};
}, []);

const filteredMovies = useMemo(() => {
Expand Down
Loading