- £{p.total_price.toLocaleString()}
+ £{safeTotalPrice.toLocaleString()}
{p.listing_type !== 'BUY' && (
/mo
)}
@@ -180,12 +186,12 @@ export function SwipeCard({ feature, onSwipe, onTap, isTop, stackIndex }: SwipeC
{/* Key stats */}
- {p.rooms} bed
+ {safeRooms} bed
- {p.qm} m²
+ {safeQm} m²
- £{p.qmprice}/m²
+ £{safeQmprice}/m²
{/* Agency & availability */}
diff --git a/frontend/src/components/__tests__/PropertyCard.test.tsx b/frontend/src/components/__tests__/PropertyCard.test.tsx
index 676b184..ec6043f 100644
--- a/frontend/src/components/__tests__/PropertyCard.test.tsx
+++ b/frontend/src/components/__tests__/PropertyCard.test.tsx
@@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { PropertyCard } from '@/components/PropertyCard';
import { createMockProperty } from '@/__tests__/helpers';
+import type { PropertyProperties } from '@/types';
describe('PropertyCard', () => {
it('renders rent price with /mo suffix', () => {
@@ -88,4 +89,35 @@ describe('PropertyCard', () => {
const { container } = render(
);
expect(container.querySelector('.ring-2')).toBeInTheDocument();
});
+
+ it('renders without crashing when BUY listing has null numeric/date fields', () => {
+ // Regression: backend returns null for total_price/qm/qmprice/rooms/last_seen on some BUY listings.
+ const partial = {
+ ...createMockProperty({ listing_type: 'BUY' }),
+ total_price: null,
+ qm: null,
+ qmprice: null,
+ rooms: null,
+ last_seen: null,
+ } as unknown as PropertyProperties;
+
+ expect(() => render(
)).not.toThrow();
+ expect(screen.queryByText(/NaN/)).not.toBeInTheDocument();
+ expect(screen.queryByText(/d ago/)).not.toBeInTheDocument();
+ });
+
+ it('renders full variant without crashing when BUY listing has null fields', () => {
+ const partial = {
+ ...createMockProperty({ listing_type: 'BUY' }),
+ total_price: null,
+ qm: null,
+ qmprice: null,
+ rooms: null,
+ last_seen: null,
+ } as unknown as PropertyProperties;
+
+ expect(() => render(
)).not.toThrow();
+ expect(screen.queryByText(/NaN/)).not.toBeInTheDocument();
+ expect(screen.queryByText(/d ago/)).not.toBeInTheDocument();
+ });
});
diff --git a/frontend/tsconfig.app.tsbuildinfo b/frontend/tsconfig.app.tsbuildinfo
index ae7d696..2c80063 100644
--- a/frontend/tsconfig.app.tsbuildinfo
+++ b/frontend/tsconfig.app.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/auth/authservice.ts","./src/auth/config.ts","./src/auth/errors.ts","./src/auth/passkeyservice.ts","./src/auth/types.ts","./src/components/alerterror.tsx","./src/components/authcallback.tsx","./src/components/errorboundary.tsx","./src/components/favoritesview.tsx","./src/components/filterbar.tsx","./src/components/filterchips.tsx","./src/components/filterpanel.tsx","./src/components/header.tsx","./src/components/healthindicator.tsx","./src/components/listview.tsx","./src/components/listingdetail.tsx","./src/components/listingdetailsheet.tsx","./src/components/loginmodal.tsx","./src/components/map.tsx","./src/components/mobilebottomsheet.tsx","./src/components/mobilemenu.tsx","./src/components/poimanager.tsx","./src/components/photocarousel.tsx","./src/components/propertycard.tsx","./src/components/propertycardcompact.tsx","./src/components/spinner.tsx","./src/components/statsbar.tsx","./src/components/streamingprogressbar.tsx","./src/components/swipecard.tsx","./src/components/swipereviewmode.tsx","./src/components/swipeablecardrow.tsx","./src/components/swipeablepropertycard.tsx","./src/components/taskindicator.tsx","./src/components/taskprogressdrawer.tsx","./src/components/visualizationcard.tsx","./src/components/ui/datepicker.tsx","./src/components/ui/accordion.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/dialog.tsx","./src/components/ui/form.tsx","./src/components/ui/hover-card.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/range-slider-field.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/slider.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tooltip.tsx","./src/constants/colorschemes.ts","./src/constants/index.ts","./src/hooks/use-mobile.ts","./src/hooks/usedecisions.ts","./src/hooks/usefilterparams.ts","./src/hooks/uselistingdetail.ts","./src/hooks/usetaskprogress.ts","./src/lib/utils.ts","./src/services/apiclient.ts","./src/services/decisionservice.ts","./src/services/healthservice.ts","./src/services/index.ts","./src/services/listingcache.ts","./src/services/listingdetailservice.ts","./src/services/listingservice.ts","./src/services/perfcollector.ts","./src/services/poiservice.ts","./src/services/streamingservice.ts","./src/services/taskservice.ts","./src/types/index.ts","./src/utils/format.ts","./src/utils/maputils.ts","./src/utils/poiutils.ts","./src/utils/taskutils.ts","./src/workers/hexgridheatmapclient.ts","./src/workers/hexgrid.worker.ts","./src/workers/types.ts"],"version":"5.8.3"}
\ No newline at end of file
+{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/auth/authService.ts","./src/auth/config.ts","./src/auth/errors.ts","./src/auth/passkeyService.ts","./src/auth/types.ts","./src/components/AlertError.tsx","./src/components/AuthCallback.tsx","./src/components/ErrorBoundary.tsx","./src/components/FavoritesView.tsx","./src/components/FilterBar.tsx","./src/components/FilterChips.tsx","./src/components/FilterPanel.tsx","./src/components/Header.tsx","./src/components/HealthIndicator.tsx","./src/components/ListView.tsx","./src/components/ListingDetail.tsx","./src/components/ListingDetailSheet.tsx","./src/components/LoginModal.tsx","./src/components/Map.tsx","./src/components/MobileBottomSheet.tsx","./src/components/MobileMenu.tsx","./src/components/POIManager.tsx","./src/components/PhotoCarousel.tsx","./src/components/PropertyCard.tsx","./src/components/PropertyCardCompact.tsx","./src/components/Spinner.tsx","./src/components/StatsBar.tsx","./src/components/StreamingProgressBar.tsx","./src/components/SwipeCard.tsx","./src/components/SwipeReviewMode.tsx","./src/components/SwipeableCardRow.tsx","./src/components/SwipeablePropertyCard.tsx","./src/components/TaskIndicator.tsx","./src/components/TaskProgressDrawer.tsx","./src/components/VisualizationCard.tsx","./src/components/ui/DatePicker.tsx","./src/components/ui/accordion.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/dialog.tsx","./src/components/ui/form.tsx","./src/components/ui/hover-card.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/range-slider-field.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/slider.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tooltip.tsx","./src/constants/colorSchemes.ts","./src/constants/index.ts","./src/hooks/use-mobile.ts","./src/hooks/useDecisions.ts","./src/hooks/useFilterParams.ts","./src/hooks/useListingDetail.ts","./src/hooks/useTaskProgress.ts","./src/lib/utils.ts","./src/services/apiClient.ts","./src/services/decisionService.ts","./src/services/healthService.ts","./src/services/index.ts","./src/services/listingCache.ts","./src/services/listingDetailService.ts","./src/services/listingService.ts","./src/services/perfCollector.ts","./src/services/poiService.ts","./src/services/streamingService.ts","./src/services/taskService.ts","./src/types/index.ts","./src/utils/format.ts","./src/utils/mapUtils.ts","./src/utils/poiUtils.ts","./src/utils/taskUtils.ts","./src/workers/HexgridHeatmapClient.ts","./src/workers/hexgrid.worker.ts","./src/workers/types.ts"],"version":"5.8.3"}
\ No newline at end of file