Skip to content
Closed
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: 0 additions & 1 deletion __tests__/components/FilterPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ describe('FilterPanel', () => {
setup();
const select = screen.getByLabelText('Category:');
expect(select).toBeInTheDocument();
// Check a known category from the data file
expect(screen.getByRole('option', { name: /Food/i })).toBeInTheDocument();
});

Expand Down
1 change: 0 additions & 1 deletion __tests__/components/FindHelpResults.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { LocationProvider } from '@/contexts/LocationContext';
import { FilterContextProvider } from '@/contexts/FilterContext';
import { act as reactAct } from 'react';

// ✅ Use the manual mock located in __mocks__
jest.mock('@/components/FindHelp/FilterPanel', () =>
require('../../__mocks__/FilterPanel.tsx')
);
Expand Down
70 changes: 70 additions & 0 deletions __tests__/components/OrganisationPage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { render, screen } from '@testing-library/react';
import OrganisationOverview from '@/components/OrganisationPage/OrganisationOverview';
import OrganisationLocations from '@/components/OrganisationPage/OrganisationLocations';
import OrganisationServicesAccordion from '@/components/OrganisationPage/OrganisationServicesAccordion';
import OrganisationContactBlock from '@/components/OrganisationPage/OrganisationContactBlock';
import OrganisationFooter from '@/components/OrganisationPage/OrganisationFooter';

jest.mock('@/components/MapComponent/GoogleMap', () => (props: any) => {
(globalThis as any).mapProps = props;
return <div data-testid="map" />;
});

jest.mock('@/components/FindHelp/ServiceCard', () => (props: any) => (
<div data-testid="service-card">{props.service.name}</div>
));

const org = {
id: '1',
name: 'Test Org',
slug: 'test-org',
latitude: 53,
longitude: -2,
services: [
{ id: 's1', name: 'A', category: 'cat1', subCategory: 'sub', description: '' },
{ id: 's2', name: 'B', category: 'cat2', subCategory: 'sub', description: '' },
],
groupedServices: {
cat1: [{ id: 's1', name: 'A', category: 'cat1', subCategory: 'sub', description: '' }],
cat2: [{ id: 's2', name: 'B', category: 'cat2', subCategory: 'sub', description: '' }],
},
};

describe('OrganisationPage components', () => {
it('renders organisation overview with categories', () => {
render(<OrganisationOverview organisation={org} />);
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Test Org');
expect(screen.getByText('cat1')).toBeInTheDocument();
expect(screen.getByText('cat2')).toBeInTheDocument();
});

it('renders map when location available', () => {
render(<OrganisationLocations organisation={org} />);
expect(screen.getByTestId('map')).toBeInTheDocument();
expect((globalThis as any).mapProps.center).toEqual({ lat: 53, lng: -2 });
});

it('returns null when no location data', () => {
const { container } = render(
<OrganisationLocations organisation={{ ...org, latitude: undefined }} />
);
expect(container.firstChild).toBeNull();
});

it('renders services accordion with categories', () => {
render(<OrganisationServicesAccordion organisation={org} />);
const buttons = screen.getAllByRole('button');
expect(buttons).toHaveLength(2);
});

it('renders contact block and footer', () => {
render(
<>
<OrganisationContactBlock organisation={org} />
<OrganisationFooter />
</>
);
expect(screen.getByText(/We do not currently have public contact details/i)).toBeInTheDocument();
expect(screen.getByText(/Information provided by Street Support/)).toBeInTheDocument();
});
});
7 changes: 7 additions & 0 deletions __tests__/components/ServiceCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const mockService = {
],
clientGroups: ['age-18+', 'rough-sleepers'],
organisation: 'Mayer Inc',
organisationSlug: 'mayer-inc',
orgPostcode: 'LN4 2LE',
};

Expand All @@ -37,4 +38,10 @@ describe('ServiceCard', () => {
expect(screen.getByText('Monday: 09:00 – 17:00')).toBeInTheDocument();
expect(screen.getByText('Wednesday: 09:00 – 17:00')).toBeInTheDocument();
});

it('links to the organisation page when slug provided', () => {
render(<ServiceCard service={mockService} />);
const link = screen.getByRole('link', { name: /view details for/i });
expect(link).toHaveAttribute('href', '/find-help/organisation/mayer-inc');
});
});
28 changes: 28 additions & 0 deletions __tests__/organisation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { getOrganisationBySlug } from '@/utils/organisation';

jest.mock('@/data/service-providers.json', () => [
{
id: '1',
name: 'Test Org',
slug: 'test-org',
latitude: 53,
longitude: -2,
services: [
{ id: 's1', name: 'A', category: 'cat1', subCategory: 'sub', description: '' },
{ id: 's2', name: 'B', category: 'cat2', subCategory: 'sub', description: '' },
],
},
]);

describe('getOrganisationBySlug', () => {
it('returns organisation details with grouped services', () => {
const result = getOrganisationBySlug('test-org');
expect(result?.name).toBe('Test Org');
expect(result?.groupedServices.cat1).toHaveLength(1);
expect(result?.groupedServices.cat2).toHaveLength(1);
});

it('returns null for unknown slug', () => {
expect(getOrganisationBySlug('unknown')).toBeNull();
});
});
6 changes: 3 additions & 3 deletions e2e/find-help.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ async function enterPostcode(page) {
await page.goto('/find-help');
await page.getByLabel(/postcode/i).fill(postcode);
await page.locator('button:has-text("Continue")').click();
await page.waitForTimeout(500); // allow location context update
await page.waitForTimeout(500);
}

test.describe('Find Help Page', () => {
Expand All @@ -29,7 +29,7 @@ test.describe('Find Help Page', () => {
});

test('should toggle map visibility', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 }); // Simulate mobile
await page.setViewportSize({ width: 375, height: 812 });
await enterPostcode(page);

const toggleBtn = page.getByRole('button', { name: /show map/i });
Expand All @@ -41,7 +41,7 @@ test.describe('Find Help Page', () => {
await enterPostcode(page);

await expect(page.getByText(/services near you/i)).toBeVisible();
await expect(page.locator('#category')).toBeVisible(); // changed from ambiguous getByLabel
await expect(page.locator('#category')).toBeVisible();
await expect(page.getByRole('button', { name: /show map/i })).toBeVisible();
});
});
3 changes: 0 additions & 3 deletions e2e/homepage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ test.describe('Homepage Map', () => {
test('should render the map and show location markers', async ({ page }) => {
await page.goto('/');

// Wait for the map container
const mapContainer = page.locator('div[role="region"] >> nth=0');
await expect(mapContainer).toBeVisible();

// Wait for at least one marker icon to appear
const markerIcons = page.locator('img[src$="map-pin.png"]');
await expect(markerIcons.first()).toBeVisible({ timeout: 5000 });

Expand Down
24 changes: 24 additions & 0 deletions e2e/organisation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { test, expect } from '@playwright/test';

const slug = 'manchester-organisation-8d0eba';

async function enterPostcode(page) {
await page.goto('/find-help');
await page.getByLabel(/postcode/i).fill('LN4 2LE');
await page.locator('button:has-text("Continue")').click();
await page.waitForTimeout(500);
}

test.describe('Organisation pages', () => {
test('loads organisation details directly', async ({ page }) => {
await page.goto(`/find-help/organisation/${slug}`);
await expect(page.getByRole('heading', { level: 1 })).toContainText('Organisation');
});

test('service card links to organisation page', async ({ page }) => {
await enterPostcode(page);
await page.locator('a[href^="/find-help/organisation/"]').first().click();
await expect(page).toHaveURL(/\/find-help\/organisation\//);
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
});
});
1 change: 0 additions & 1 deletion jest.config.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/** @type {import('jest').Config} */
console.log('[JEST CONFIG] Using CJS config');

module.exports = {
testPathIgnorePatterns: [
Expand Down
1 change: 0 additions & 1 deletion jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,4 @@ beforeAll(() => {
});
});

// ✅ Define global alert as jest mock to avoid jsdom crash
global.alert = jest.fn();
1 change: 0 additions & 1 deletion src/app/api/get-service-providers/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { NextResponse } from 'next/server';
import services from '@/data/service-providers.json';

export function GET() {
// Flatten out all services into a flat list with org data
const allServices = services.flatMap((provider) => {
return provider.services.map((service) => ({
...service,
Expand Down
7 changes: 2 additions & 5 deletions src/app/find-help/organisation/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@ import OrganisationContactBlock from '@/components/OrganisationPage/Organisation
import OrganisationFooter from '@/components/OrganisationPage/OrganisationFooter';
import { notFound } from 'next/navigation';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default async function OrganisationPage(props: any) {
const { params }: { params: { slug: string } } = props;

const organisation = await getOrganisationBySlug(params.slug);
export default function OrganisationPage({ params }: { params: { slug: string } }) {
const organisation = getOrganisationBySlug(params.slug);

if (!organisation) return notFound();

Expand Down
3 changes: 1 addition & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { ReactNode } from 'react';
import Script from 'next/script'; // import Script component
import Script from 'next/script';
import './globals.css';

import Nav from '../components/partials/Nav';
Expand All @@ -12,7 +12,6 @@ export default function RootLayout({ children }: { children: ReactNode }) {
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Street Support</title>
{/* Load Google Maps JS API */}
<Script
src={`https://maps.googleapis.com/maps/api/js?key=${process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY}&libraries=marker&map_ids=8364b1415f1ab88dc38e401b`}
strategy="beforeInteractive"
Expand Down
2 changes: 1 addition & 1 deletion src/components/FindHelp/FilterPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function FilterPanel({
value={selectedCategory}
onChange={(e) => {
setSelectedCategory(e.target.value);
setSelectedSubCategory(''); // reset subcategory when changing category
setSelectedSubCategory('');
}}
>
<option value="">All</option>
Expand Down
4 changes: 2 additions & 2 deletions src/components/MapComponent/GoogleMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface Marker {
lat: number;
lng: number;
title: string;
organisationSlug: string; // ✅ must match field used in ServiceCard
organisationSlug: string;
icon?: string;
organisation?: string;
serviceName?: string;
Expand Down Expand Up @@ -61,7 +61,7 @@ export default function GoogleMap({ center, markers, zoom }: Props) {
organisation,
serviceName,
distanceKm,
organisationSlug, // ✅ corrected
organisationSlug,
} = markerData;

const gMarker = new google.maps.Marker({
Expand Down
2 changes: 1 addition & 1 deletion src/components/OrganisationPage/OrganisationLocations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function OrganisationLocations({ organisation }: Props) {
lat: organisation.latitude,
lng: organisation.longitude,
title: organisation.name,
organisationSlug: organisation.slug || 'org-loc-default' // ✅ dummy fallback slug
organisationSlug: organisation.slug || 'org-loc-default'
},
];

Expand Down
1 change: 0 additions & 1 deletion src/contexts/LocationContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export function LocationProvider({ children }: { children: ReactNode }) {
});
},
() => {
// quietly ignore location errors
},
{
enableHighAccuracy: true,
Expand Down
Loading