From eac58ee6bacdf412e0d0e527de33d3cd0f4405d3 Mon Sep 17 00:00:00 2001 From: Sluijsens Date: Sun, 2 Aug 2026 16:31:30 +0200 Subject: [PATCH] Voeg loading-skeleton toe voor pakketten-sectie Toont 3 pulserende placeholder-kaarten (zelfde afmetingen/structuur als PackageCard) zolang de offerings-fetch nog bezig is, zodat de sectie niet leeg oogt en de layout niet verspringt zodra de echte data binnenkomt. --- .../__tests__/PackagesSection.test.tsx | 26 ++++++++++++++++++- .../components/PackageCardSkeleton.tsx | 26 +++++++++++++++++++ .../landing/components/PackagesSection.tsx | 9 ++++++- 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/features/landing/components/PackageCardSkeleton.tsx diff --git a/src/features/landing/__tests__/PackagesSection.test.tsx b/src/features/landing/__tests__/PackagesSection.test.tsx index 04ba12c..0c14b66 100644 --- a/src/features/landing/__tests__/PackagesSection.test.tsx +++ b/src/features/landing/__tests__/PackagesSection.test.tsx @@ -40,10 +40,11 @@ describe('PackagesSection', () => { renderWithQueryClient(); await waitFor(() => { - expect(screen.getByTestId('packages-grid').children.length).toBe(packages.length); + expect(screen.queryByTestId('package-card-skeleton')).not.toBeInTheDocument(); }); expect(fetch).toHaveBeenCalledWith('/api/v1/offerings'); + expect(screen.getByTestId('packages-grid').children.length).toBe(packages.length); for (const pkg of packages) { expect(screen.getByTestId(`package-card-${pkg.id}`)).toBeInTheDocument(); expect(screen.getByText(pkg.title)).toBeInTheDocument(); @@ -60,6 +61,29 @@ describe('PackagesSection', () => { }); }); + it('shows skeleton placeholders while the request is in flight, reserving the grid space', async () => { + let resolveFetch: (response: Response) => void; + vi.stubGlobal( + 'fetch', + vi.fn().mockReturnValue( + new Promise((resolve) => { + resolveFetch = resolve; + }), + ), + ); + + renderWithQueryClient(); + + expect(screen.getAllByTestId('package-card-skeleton')).toHaveLength(packages.length); + + resolveFetch!(new Response(JSON.stringify(packages), { status: 200 })); + + await waitFor(() => { + expect(screen.queryAllByTestId('package-card-skeleton')).toHaveLength(0); + }); + expect(screen.getByTestId('packages-grid').children.length).toBe(packages.length); + }); + it('shows an error message when the API request fails', async () => { mockFetchOnce(new Response(null, { status: 500, statusText: 'Internal Server Error' })); diff --git a/src/features/landing/components/PackageCardSkeleton.tsx b/src/features/landing/components/PackageCardSkeleton.tsx new file mode 100644 index 0000000..0fa4d37 --- /dev/null +++ b/src/features/landing/components/PackageCardSkeleton.tsx @@ -0,0 +1,26 @@ +// Mirrors PackageCard's structure/spacing 1-to-1 so the grid doesn't reflow once +// real data arrives — only the content becomes pulsing placeholder blocks. +export function PackageCardSkeleton() { + return ( +