Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
dca3e9e
Move cypress tests to ws-nextjs-app
hotinglok Apr 17, 2026
af894ff
WS-200: Initial commit
Isabella-Mitchell Apr 17, 2026
d0ff5ab
WS-200: Tidies. Adds toggle support and unit tests.
Isabella-Mitchell Apr 17, 2026
6cd9942
Merge branch 'latest' into WS-200-migrate-liveradio-simorgh
Isabella-Mitchell Apr 17, 2026
c98c92f
WS-200: Fix tests
Isabella-Mitchell Apr 17, 2026
6c0b601
WS-200: Tidies
Isabella-Mitchell Apr 17, 2026
8a9bc03
WS-200: Tidies. Adds unit test
Isabella-Mitchell Apr 17, 2026
92b9b1b
Merge branch 'latest' into WS-200-migrate-liveradio-simorgh
Isabella-Mitchell Apr 17, 2026
eb5c005
WS-200: Fix test
Isabella-Mitchell Apr 17, 2026
5d011f4
Merge branch 'latest' into WS-200-migrate-liveradio-simorgh
shayneahchoon Apr 20, 2026
a5b9fda
WS-200: Tidies
Isabella-Mitchell Apr 21, 2026
e2225b2
WS-200: Tidies
Isabella-Mitchell Apr 21, 2026
5dbd991
WS-2494: Migrates liveRadio Integration tests to NextJS app
Isabella-Mitchell Apr 21, 2026
698a103
WS-2494: TS test. Updates snapshots
Isabella-Mitchell Apr 21, 2026
23484b7
WS-2494: Define types
Isabella-Mitchell Apr 21, 2026
ca97e42
WS-2494: Adds passWithNoTests flag
Isabella-Mitchell Apr 21, 2026
0f60440
Merge branch 'WS-200-migrate-liveradio-simorgh' into ws-2495-migrate-…
hotinglok Apr 21, 2026
66b9af1
Fix failing test
hotinglok Apr 21, 2026
a4399d4
Re-add express tests
hotinglok Apr 21, 2026
dbd4e20
Merge branch 'latest' into WS-200-migrate-liveradio-simorgh
Isabella-Mitchell Apr 23, 2026
181e4c7
Merge branch 'WS-200-migrate-liveradio-simorgh' into WS-2494-LiveRadi…
Isabella-Mitchell Apr 23, 2026
dd527e4
Merge branch 'WS-200-migrate-liveradio-simorgh' into ws-2495-migrate-…
Isabella-Mitchell Apr 23, 2026
e5d073d
WS-2540: Skips tests
Isabella-Mitchell Apr 23, 2026
b290709
WS-2540: Skips missed test
Isabella-Mitchell Apr 23, 2026
e7b1800
WS-2540: Removes express pages e2e tests
Isabella-Mitchell Apr 23, 2026
0900698
Merge branch 'latest' into WS-200-migrate-liveradio-simorgh
Isabella-Mitchell Apr 23, 2026
c4e38de
Merge branch 'WS-200-migrate-liveradio-simorgh' into WS-2494-LiveRadi…
Isabella-Mitchell Apr 23, 2026
02488bd
Merge branch 'WS-200-migrate-liveradio-simorgh' into ws-2495-migrate-…
Isabella-Mitchell Apr 23, 2026
b5d24d7
Merge pull request #13931 from bbc/WS-2494-LiveRadio-Integration
Isabella-Mitchell Apr 23, 2026
dc6d4c0
Merge branch 'WS-200-migrate-liveradio-simorgh' into ws-2495-migrate-…
Isabella-Mitchell Apr 23, 2026
8ce38a9
Merge pull request #13926 from bbc/ws-2495-migrate-live-radio-e2es
Isabella-Mitchell Apr 23, 2026
2178537
Merge branch 'WS-200-migrate-liveradio-simorgh' into WS-2540-remove-e…
Isabella-Mitchell Apr 23, 2026
b20b75a
WS-2540: Updates package.json
Isabella-Mitchell Apr 23, 2026
95d2438
WS-200: Optimises test so that negative assertion does not rely on N…
Isabella-Mitchell Apr 23, 2026
1235125
Merge branch 'WS-200-migrate-liveradio-simorgh' into WS-2540-remove-e…
Isabella-Mitchell Apr 23, 2026
bd9a46f
Merge pull request #13948 from bbc/WS-2540-remove-express-pages-e2e-test
Isabella-Mitchell Apr 23, 2026
52adc51
Merge branch 'latest' into WS-200-migrate-liveradio-simorgh
Isabella-Mitchell Apr 28, 2026
914d2e3
WS-200: Removes isTest mock from tests
Isabella-Mitchell Apr 28, 2026
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
4 changes: 2 additions & 2 deletions src/app/pages/LiveRadioPage/LiveRadioPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import ATIAnalytics from '../../components/ATIAnalytics';
import ChartbeatAnalytics from '../../components/ChartbeatAnalytics';
import MetadataContainer from '../../components/Metadata';
import LinkedData from '../../components/LinkedData';
import { LiveRadioPageData } from './types';
import { LiveRadioPageProps } from './types';

const LiveRadioPage = ({ pageData }: { pageData: LiveRadioPageData }) => {
const LiveRadioPage = ({ pageData }: { pageData: LiveRadioPageProps }) => {
const {
language,
name,
Expand Down
16 changes: 8 additions & 8 deletions src/app/pages/LiveRadioPage/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { data as kyrgyzPageData } from '#data/kyrgyz/bbc_kyrgyz_radio/liveradio.
import { render } from '../../components/react-testing-library-with-providers';
import { ServiceContextProvider } from '../../contexts/ServiceContext';
import LiveRadioPage from './LiveRadioPage';
import { LiveRadioPageData } from './types';
import { LiveRadioPageProps } from './types';

type Props = {
pageData: LiveRadioPageData;
pageData: LiveRadioPageProps;
service: Services;
lang: string;
};
Expand Down Expand Up @@ -44,7 +44,7 @@ describe('Radio Page Main', () => {
it('should match snapshot for Canonical', () => {
const { container } = render(
<Page
pageData={afriquePageData.data as unknown as LiveRadioPageData}
pageData={afriquePageData.data as unknown as LiveRadioPageProps}
service="afrique"
lang="fr"
/>,
Expand All @@ -57,7 +57,7 @@ describe('Radio Page Main', () => {
it('should show the title for the Live Radio page', () => {
const { getByText } = render(
<Page
pageData={afriquePageData.data as unknown as LiveRadioPageData}
pageData={afriquePageData.data as unknown as LiveRadioPageProps}
service="afrique"
lang="fr"
/>,
Expand All @@ -70,7 +70,7 @@ describe('Radio Page Main', () => {
it('should show the summary for the Live Radio page', () => {
const { getByText } = render(
<Page
pageData={afriquePageData.data as unknown as LiveRadioPageData}
pageData={afriquePageData.data as unknown as LiveRadioPageProps}
service="afrique"
lang="fr"
/>,
Expand Down Expand Up @@ -115,7 +115,7 @@ describe('Radio Page Main', () => {
{
...afriquePageData.data,
mediaBlock: mockMediaBlock,
} as unknown as LiveRadioPageData
} as unknown as LiveRadioPageProps
}
service="afrique"
lang="fr"
Expand All @@ -132,7 +132,7 @@ describe('Radio Page Main', () => {
it('should show the radio schedule for the Live Radio page on canonical', () => {
const { getByText } = render(
<Page
pageData={afriquePageData.data as unknown as LiveRadioPageData}
pageData={afriquePageData.data as unknown as LiveRadioPageProps}
service="afrique"
lang="fr"
/>,
Expand All @@ -150,7 +150,7 @@ describe('Radio Page Main', () => {
it('should not show the radio schedule for services without a schedule', async () => {
const { container } = render(
<Page
pageData={kyrgyzPageData as unknown as LiveRadioPageData}
pageData={kyrgyzPageData as unknown as LiveRadioPageProps}
service="kyrgyz"
lang="ky"
/>,
Expand Down
2 changes: 1 addition & 1 deletion src/app/pages/LiveRadioPage/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ATIData } from '#app/components/ATIAnalytics/types';
import { ChartbeatProps } from '#app/components/ChartbeatAnalytics/types';
import { LiveRadioBlock } from '#app/models/types/media';

export type LiveRadioPageData = {
export type LiveRadioPageProps = {
language: string;
name: string;
summary: string;
Comment on lines +5 to 8
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exported type name LiveRadioPageProps describes the shape of pageData (language/name/summary/etc), not the React component props object. This is already causing confusion in the Next.js catch-all route types. Consider renaming this back to something like LiveRadioPageData (and, if needed, introducing a separate LiveRadioPageProps type that wraps { pageData: LiveRadioPageData }).

Copilot generated this review using guidance from repository custom instructions.
Expand Down
2 changes: 1 addition & 1 deletion src/app/routes/utils/constructPageFetchUrl/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ describe('constructPageFetchUrl', () => {
${TV_PAGE} | ${'hindi'} | ${null} | ${'test'} | ${'/hindi/bbc_hindi_tv/tv_programmes/w13xttlw'} | ${'https://mock-bff-path/?id=hindi%2Fbbc_hindi_tv%2Ftv_programmes%2Fw13xttlw&service=hindi&pageType=tv&serviceEnv=test'}
${TV_PAGE} | ${'hausa'} | ${null} | ${'live'} | ${'/hausa/bbc_hausa_tv/tv/w172yjj83ptptnj'} | ${'https://mock-bff-path/?id=hausa%2Fbbc_hausa_tv%2Ftv%2Fw172yjj83ptptnj&service=hausa&pageType=tv&serviceEnv=live'}
${TV_PAGE} | ${'hindi'} | ${null} | ${'live'} | ${'/hindi/bbc_hindi_tv/tv_programmes/w13xttlw'} | ${'https://mock-bff-path/?id=hindi%2Fbbc_hindi_tv%2Ftv_programmes%2Fw13xttlw&service=hindi&pageType=tv&serviceEnv=live'}
${LIVE_RADIO_PAGE} | ${'afrique'} | ${null} | ${'local'} | ${'/afrique/bbc_afrique_radio/liveradio'} | ${'http://localhost/afrique/bbc_afrique_radio/liveradio'}
${LIVE_RADIO_PAGE} | ${'afrique'} | ${null} | ${'local'} | ${'/afrique/bbc_afrique_radio/liveradio'} | ${'http://localhost/api/local/afrique/bbc_afrique_radio/liveradio'}
${LIVE_RADIO_PAGE} | ${'afrique'} | ${null} | ${'test'} | ${'/afrique/bbc_afrique_radio/liveradio'} | ${'https://mock-bff-path/?id=bbc_afrique_radio&service=afrique&pageType=liveRadio&serviceEnv=test'}
${LIVE_RADIO_PAGE} | ${'afrique'} | ${null} | ${'live'} | ${'/afrique/bbc_afrique_radio/liveradio'} | ${'https://mock-bff-path/?id=bbc_afrique_radio&service=afrique&pageType=liveRadio&serviceEnv=live'}
${LIVE_TV_PAGE} | ${'dari'} | ${null} | ${'local'} | ${'/dari/watch/bbc_afghan_tv/live'} | ${'http://localhost/api/local/dari/watch/bbc_afghan_tv/live'}
Expand Down
9 changes: 7 additions & 2 deletions src/app/routes/utils/constructPageFetchUrl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,14 @@ const constructPageFetchUrl = ({
);
break;
}
case LIVE_RADIO_PAGE:
fetchUrl = Url(`${pathname}`);
case LIVE_RADIO_PAGE: {
if (process.env?.NEXTJS) {
fetchUrl = Url(`${host}${port}/api/local${pathname}`);
} else {
fetchUrl = Url(`${pathname}`);
}
break;
}
case LIVE_TV_PAGE: {
fetchUrl = Url(`${host}${port}/api/local/${service}/watch/${id}/live`);
break;
Expand Down
14 changes: 13 additions & 1 deletion ws-nextjs-app/pages/[service]/[[...]].page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
HOME_PAGE,
AUDIO_PAGE,
TV_PAGE,
LIVE_RADIO_PAGE,
} from '#app/routes/utils/pageTypes';
import { PageTypes } from '#app/models/types/global';
import PageDataParams from '#app/models/types/pageDataParams';
Expand All @@ -22,6 +23,7 @@ import withOptimizelyProvider from '#app/legacy/containers/PageHandlers/withOpti
import { HomePageProps } from '#app/pages/HomePage/HomePage';
import { getEnvConfig } from '#app/lib/utilities/getEnvConfig';
import derivePageType from '#nextjs/utilities/derivePageType';
import { LiveRadioPageProps } from '#app/pages/LiveRadioPage/types';

// AV Embeds
import withMediaError from '#app/lib/utilities/episodeAvailability/withMediaError';
Expand All @@ -39,6 +41,8 @@ import handleOnDemandAudioRoute from './onDemandAudio/handleOnDemandAudioRoute';
import { OnDemandAudioProps } from './onDemandAudio/types';
// On Demand TV
import handleOnDemandTvRoute from './onDemandTv/handleOnDemandTvRoute';
// Live Radio
import handleLiveRadioRoute from './liveRadio/handleLiveRadioRoute';

// Dynamic imports of page layouts
const AvEmbedsPageLayout = dynamic(
Expand All @@ -56,6 +60,10 @@ const OnDemandTvPage = dynamic(
() => import('#app/pages/OnDemandTvPage/OnDemandTvPage'),
);

const LiveRadioPage = dynamic(
() => import('#app/pages/LiveRadioPage/LiveRadioPage'),
);

const getPageType = ({
resolvedUrl,
reqHeaders,
Expand Down Expand Up @@ -87,6 +95,7 @@ const ROUTE_HANDLERS = {
[HOME_PAGE]: handleHomepageRoute,
[AUDIO_PAGE]: handleOnDemandAudioRoute,
[TV_PAGE]: handleOnDemandTvRoute,
[LIVE_RADIO_PAGE]: handleLiveRadioRoute,
};

export const getServerSideProps: GetServerSideProps = async context => {
Expand Down Expand Up @@ -129,7 +138,8 @@ type PageProps = {
ArticlePageProps &
HomePageProps &
OnDemandAudioProps &
OnDemandTVProps;
OnDemandTVProps &
LiveRadioPageProps;
Comment on lines 139 to +142
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PageProps is intersected with LiveRadioPageProps, but that type currently represents the inner pageData fields (e.g. language, name, summary) rather than the page’s top-level props. This makes the Next.js page props type inaccurate and likely to mislead future changes. Define a wrapper type for the route (e.g. { pageData: LiveRadioPageData }) and use that in PageProps instead.

Copilot uses AI. Check for mistakes.

export default function PageTypeToRender({ pageType, ...props }: PageProps) {
switch (pageType) {
Expand All @@ -149,6 +159,8 @@ export default function PageTypeToRender({ pageType, ...props }: PageProps) {
return withMediaError(OnDemandAudioPage)({ ...props });
case TV_PAGE:
return withMediaError(OnDemandTvPage)({ ...props });
case LIVE_RADIO_PAGE:
return withMediaError(LiveRadioPage)({ ...props });
// Home Page
case HOME_PAGE:
return withOptimizelyProvider(HomePage)({ ...props });
Expand Down
194 changes: 194 additions & 0 deletions ws-nextjs-app/pages/[service]/liveRadio/handleLiveRadioRoute.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { GetServerSidePropsContext } from 'next';
import liveRadioJson from '#data/korean/bbc_korean_radio/liveradio.json';
import { LIVE_RADIO_PAGE } from '#app/routes/utils/pageTypes';
import { Toggles } from '#app/models/types/global';
import * as getTogglesModule from '#app/lib/utilities/getToggles/withCache';
import * as isTest from '#app/lib/utilities/isTest';
import * as getPageDataModule from '../../../utilities/pageRequests/getPageData';
import handleLiveRadioRoute from './handleLiveRadioRoute';

jest.mock('../../../utilities/pageRequests/getPageData');
jest.mock('#app/lib/utilities/getToggles/withCache');

jest.mock('#app/lib/utilities/isTest', () => {
const originalModule = jest.requireActual('#app/lib/utilities/isTest');
return {
__esModule: true,
...originalModule,
};
});
Comment thread
Isabella-Mitchell marked this conversation as resolved.
Outdated

describe('handleLiveRadioRoute', () => {
const mockSetHeader = jest.fn();
const mockGetServerSidePropsContext = {
req: {
headers: {},
} as unknown as GetServerSidePropsContext['req'],
res: {
setHeader: mockSetHeader,
removeHeader: jest.fn(),
} as unknown as GetServerSidePropsContext['res'],
resolvedUrl: '/korean/bbc_korean_radio/liveradio',
query: { service: 'korean' },
} satisfies GetServerSidePropsContext;

let getPageDataSpy: jest.SpyInstance;

beforeEach(() => {
jest.restoreAllMocks();
jest.clearAllMocks();
getPageDataSpy = jest
.spyOn(getPageDataModule, 'default')
.mockResolvedValue({
data: {
pageData: liveRadioJson.data,
status: 200,
},
});

jest.spyOn(getTogglesModule, 'default').mockResolvedValue({
liveRadioSchedule: { enabled: true },
} as Toggles);
});

it('returns expected props if data fetch succeeds', async () => {
const { props } = await handleLiveRadioRoute(mockGetServerSidePropsContext);
expect(props.status).toEqual(200);
expect(props.pageType).toEqual('liveRadio');
expect(props.pageData).toEqual(liveRadioJson.data);
});

it('should return essential data for a page to render', async () => {
const {
props: { pageData },
} = await handleLiveRadioRoute(mockGetServerSidePropsContext);

expect(pageData.name).toEqual('BBC 코리아 라디오');
expect(pageData.language).toEqual('ko');
expect(pageData.metadata.type).toEqual('Live Radio');
expect(pageData.summary).toEqual(
'세계와 한반도 뉴스를 공정하고 객관적으로 전달해 드립니다',
);
expect(pageData.heading).toEqual('BBC 코리아 라디오');
expect(pageData.bodySummary).toEqual(
'세계와 한반도 뉴스를 공정하고 객관적으로 전달해 드립니다',
);
expect(pageData.masterBrand).toEqual('bbc_korean_radio');
});

it('should call getPageData with the correct arguments', async () => {
await handleLiveRadioRoute(mockGetServerSidePropsContext);

expect(getPageDataSpy).toHaveBeenCalledWith(
expect.objectContaining({
id: '/korean/bbc_korean_radio/liveradio',
service: 'korean',
pageType: LIVE_RADIO_PAGE,
resolvedUrl: '/korean/bbc_korean_radio/liveradio',
}),
);
});

it('should pass disableRadioSchedule as true when toggle is disabled', async () => {
jest.spyOn(getTogglesModule, 'default').mockResolvedValue({
liveRadioSchedule: { enabled: false },
} as Toggles);

await handleLiveRadioRoute(mockGetServerSidePropsContext);

expect(getPageDataSpy).toHaveBeenCalledWith(
expect.objectContaining({
disableRadioSchedule: true,
}),
);
});

it('should pass disableRadioSchedule as false when toggle is enabled', async () => {
await handleLiveRadioRoute(mockGetServerSidePropsContext);

expect(getPageDataSpy).toHaveBeenCalledWith(
expect.objectContaining({
disableRadioSchedule: false,
}),
);
});

it('returns error props if data fetch returns 500', async () => {
jest.spyOn(getPageDataModule, 'default').mockResolvedValue({
data: {
pageData: liveRadioJson.data,
status: 500,
},
});

const result = await handleLiveRadioRoute(mockGetServerSidePropsContext);

expect(result).toEqual({
props: expect.objectContaining({
status: 500,
pageType: 'liveRadio',
pathname: '/korean/bbc_korean_radio/liveradio',
}),
});
});

it('returns error props if data fetch returns 404', async () => {
jest.spyOn(getPageDataModule, 'default').mockResolvedValue({
data: {
pageData: liveRadioJson.data,
status: 404,
},
});

const result = await handleLiveRadioRoute(mockGetServerSidePropsContext);

expect(result).toEqual({
props: expect.objectContaining({
status: 404,
pageType: 'liveRadio',
pathname: '/korean/bbc_korean_radio/liveradio',
}),
});
});

it('throws if pageData is missing', async () => {
jest.spyOn(getPageDataModule, 'default').mockResolvedValue({
data: { pageData: null, status: 200 },
});

await expect(
handleLiveRadioRoute(mockGetServerSidePropsContext),
).rejects.toThrow('LiveRadioPage data is malformed');
});

it('sets correct cache-control header', async () => {
await handleLiveRadioRoute(mockGetServerSidePropsContext);

expect(mockSetHeader).toHaveBeenCalledWith(
'Cache-Control',
expect.stringContaining('max-age=30'),
);
});

it('returns not found props when service is invalid', async () => {
const result = await handleLiveRadioRoute({
...mockGetServerSidePropsContext,
resolvedUrl: '/fakeservice',
});

expect(result).toEqual({
props: expect.objectContaining({ status: 404 }),
});
});

it('should render live assets on test environments', async () => {
jest.spyOn(isTest, 'default').mockReturnValueOnce(true);
const pageDataSpy = jest.spyOn(getPageDataModule, 'default');

await handleLiveRadioRoute(mockGetServerSidePropsContext);

expect(pageDataSpy).toHaveBeenCalledWith(
expect.objectContaining({ rendererEnv: 'live' }),
);
});
});
Loading
Loading