Haal pakketten-data op via /api/v1/packages i.p.v. hardcoded content
Continuous Integration / config (pull_request) Successful in 3m1s
Continuous Integration / prepare (pull_request) Successful in 1m13s
Continuous Integration / build-production (pull_request) Skipped
Continuous Integration / build (pull_request) Successful in 2m14s
Continuous Integration / test (pull_request) Successful in 1m52s
Continuous Integration / deploy-production (pull_request) Skipped
Deploy / deploy (pull_request) Successful in 43s
Continuous Integration / deploy-test (pull_request) Successful in 44s
Continuous Integration / config (pull_request) Successful in 3m1s
Continuous Integration / prepare (pull_request) Successful in 1m13s
Continuous Integration / build-production (pull_request) Skipped
Continuous Integration / build (pull_request) Successful in 2m14s
Continuous Integration / test (pull_request) Successful in 1m52s
Continuous Integration / deploy-production (pull_request) Skipped
Deploy / deploy (pull_request) Successful in 43s
Continuous Integration / deploy-test (pull_request) Successful in 44s
PackagesSection gebruikte tot nu toe statische data via een placeholder query-hook (FR-5). usePackagesQuery haalt nu echt op bij /api/v1/packages (relatief pad, zelfde domein op test en productie), met een foutmelding in de UI als het request faalt. Voegt ook een hand-off-document toe voor de agent die de CMS/API-kant gaat bouwen: het endpoint-contract, per veld het type/gebruik, en de huidige statische content als seed-data.
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { PackagesSection } from '../components/PackagesSection';
|
||||
import { packages } from '../data/content';
|
||||
|
||||
function renderWithQueryClient() {
|
||||
const queryClient = new QueryClient();
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
});
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<PackagesSection />
|
||||
@@ -13,14 +15,28 @@ function renderWithQueryClient() {
|
||||
);
|
||||
}
|
||||
|
||||
function mockFetchOnce(response: Response) {
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue(response),
|
||||
);
|
||||
}
|
||||
|
||||
describe('PackagesSection', () => {
|
||||
it('renders one PackageCard per package once loaded', async () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('fetches packages from /api/v1/packages and renders one PackageCard per package', async () => {
|
||||
mockFetchOnce(new Response(JSON.stringify(packages), { status: 200 }));
|
||||
|
||||
renderWithQueryClient();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('packages-grid').children.length).toBe(packages.length);
|
||||
});
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith('/api/v1/packages');
|
||||
for (const pkg of packages) {
|
||||
expect(screen.getByTestId(`package-card-${pkg.id}`)).toBeInTheDocument();
|
||||
expect(screen.getByText(pkg.title)).toBeInTheDocument();
|
||||
@@ -28,10 +44,22 @@ describe('PackagesSection', () => {
|
||||
});
|
||||
|
||||
it('marks the featured package as "Meest gekozen"', async () => {
|
||||
mockFetchOnce(new Response(JSON.stringify(packages), { status: 200 }));
|
||||
|
||||
renderWithQueryClient();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Meest gekozen')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows an error message when the API request fails', async () => {
|
||||
mockFetchOnce(new Response(null, { status: 500, statusText: 'Internal Server Error' }));
|
||||
|
||||
renderWithQueryClient();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('packages-error')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { usePackagesQuery } from '../hooks/usePackagesQuery';
|
||||
import { PackageCard } from './PackageCard';
|
||||
|
||||
export function PackagesSection() {
|
||||
const { data: packages, isLoading } = usePackagesQuery();
|
||||
const { data: packages, isLoading, isError } = usePackagesQuery();
|
||||
|
||||
return (
|
||||
<section
|
||||
@@ -23,9 +23,13 @@ export function PackagesSection() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-[22px] sm:grid-cols-3" data-testid="packages-grid">
|
||||
{isLoading || !packages
|
||||
? null
|
||||
: packages.map((pkg) => <PackageCard key={pkg.id} pkg={pkg} />)}
|
||||
{isError ? (
|
||||
<p className="col-span-full text-center text-muted" data-testid="packages-error">
|
||||
Pakketten konden niet worden geladen. Probeer het later opnieuw.
|
||||
</p>
|
||||
) : isLoading || !packages ? null : (
|
||||
packages.map((pkg) => <PackageCard key={pkg.id} pkg={pkg} />)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
* Shapes mirror the domain entities defined in
|
||||
* aidlc-docs/features/react-frontend/construction/react-frontend-app/functional-design/domain-entities.md
|
||||
*
|
||||
* This module is the current source of truth for content (requirements FR-1/FR-6).
|
||||
* It is deliberately structured so it can be swapped for TanStack Query data later
|
||||
* (see src/features/landing/hooks/usePackagesQuery.ts) with minimal changes (requirements FR-5).
|
||||
* This module is the source of truth for content that isn't (yet) API-backed
|
||||
* (requirements FR-1/FR-6). `packages` is the exception: it now also doubles as
|
||||
* the reference/seed data for the `/api/v1/packages` contract consumed via
|
||||
* src/features/landing/hooks/usePackagesQuery.ts (requirements FR-5) — see
|
||||
* aidlc-docs/features/react-frontend/construction/react-frontend-app/functional-design/packages-api-handoff.md.
|
||||
*/
|
||||
|
||||
export interface NavLink {
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { packages, type PackageCardData } from '../data/content';
|
||||
import type { PackageCardData } from '../data/content';
|
||||
|
||||
// Relative path so it resolves against whichever domain served the app
|
||||
// (test.slpsoftware.nl / slpsoftware.nl) — see aidlc-docs/features/react-frontend/
|
||||
// construction/react-frontend-app/functional-design/packages-api-handoff.md
|
||||
// for the API contract this endpoint must implement.
|
||||
const PACKAGES_ENDPOINT = '/api/v1/packages';
|
||||
|
||||
/**
|
||||
* Placeholder query hook (requirements FR-5). The queryFn currently just resolves the
|
||||
* static package data, wrapped in a Promise so the calling component already consumes
|
||||
* it exactly the way it will once a real API exists — only this queryFn's body will
|
||||
* need to change in a future iteration.
|
||||
*/
|
||||
async function fetchPackages(): Promise<PackageCardData[]> {
|
||||
return Promise.resolve(packages);
|
||||
const response = await fetch(PACKAGES_ENDPOINT);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch packages: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export function usePackagesQuery() {
|
||||
|
||||
Reference in New Issue
Block a user