diff --git a/data/mundo/mostRead/mundo.json b/data/mundo/mostRead/mundo.json new file mode 100644 index 00000000000..f4d64fd9d23 --- /dev/null +++ b/data/mundo/mostRead/mundo.json @@ -0,0 +1,150 @@ +{ + "data": { + "generated": "2023-06-15T14:59:17.801Z", + "lastRecordTimeStamp": "2023-06-15T14:57:00Z", + "firstRecordTimeStamp": "2023-06-15T14:42:00Z", + "items": [ + { + "id": "4adf2180-74e0-4ea9-836b-a00797fa0f48", + "rank": 1, + "title": "Quién es Valerii Zaluzhnyi, el popular \"general de hierro\" que está al mando de la contraofensiva ucraniana", + "href": "/mundo/noticias-internacional-65909204", + "timestamp": 1686821815000 + }, + { + "id": "7cd5b8ff-6587-4c92-917d-107aba6aa2d6", + "rank": 2, + "title": "El director de la morgue de la Universidad de Harvard es acusado de robar y vender partes de cadáveres humanos", + "href": "/mundo/noticias-65917580", + "timestamp": 1686832214000 + }, + { + "id": "b2c8768e-b7e9-448d-b163-e82f38d41f22", + "rank": 3, + "title": "Qué busca Bukele con la reducción de 262 a 44 municipios en El Salvador (y por qué causa polémica)", + "href": "/mundo/noticias-america-latina-65911008", + "timestamp": 1686826839000 + }, + { + "id": "781266b2-f28e-48f4-81a1-93fa29db1499", + "rank": 4, + "title": "Los estudios que muestran cómo la taurina puede alargar la vida", + "href": "/mundo/noticias-65863611", + "timestamp": 1686823420000 + }, + { + "id": "30ccf622-8542-40ed-b19d-81a77f38f0a1", + "rank": 5, + "title": "El inesperado peligro medioambiental que pueden generar los paneles solares", + "href": "/mundo/noticias-65815486", + "timestamp": 1686782133000 + }, + { + "id": "21331ae7-9b0b-4e93-a0ad-23b5ad254d32", + "rank": 6, + "title": "Qué es y qué queda del \"Tanomoshi\", el sistema que permitió prosperar a los japoneses que llegaron a Perú", + "href": "/mundo/noticias-america-latina-65851680", + "timestamp": 1686834397000 + }, + { + "id": "05a53fcc-a62e-4c25-95ad-0634e9969807", + "rank": 7, + "title": "Los contrastes de Colombia que revela la historia de los niños indígenas que estuvieron perdidos en la selva", + "href": "/mundo/noticias-america-latina-65899580", + "timestamp": 1686740548000 + }, + { + "id": "9bc341bf-308a-42d5-bb07-50c6cb7b90bf", + "rank": 8, + "title": "Muere la legendaria actriz inglesa Glenda Jackson, que inspiró al escritor argentino Julio Cortázar", + "href": "/mundo/noticias-65920533", + "timestamp": 1686839483000 + }, + { + "id": "0aa9876c-a4a4-4e88-9a5b-13ccea229a32", + "rank": 9, + "title": "Al menos 79 muertos y cientos de desaparecidos en el naufragio de un barco de migrantes frente a las costas de Grecia", + "href": "/mundo/noticias-internacional-65908697", + "timestamp": 1686801120000 + }, + { + "id": "1b2c3053-2fb3-4ffb-b0d0-ce1f868e47d2", + "rank": 10, + "title": "\"No se entiende que, con todo lo que recorrimos, encontráramos a los niños por donde ya habíamos pasado\"", + "href": "/mundo/noticias-america-latina-65886379", + "timestamp": 1686613016000 + }, + { + "id": "edbec380-d45e-491d-9d65-a7daa0eb28cd", + "rank": 11, + "title": "Quién es María Asunción Aramburuzabala, la mujer más rica de México", + "href": "/mundo/noticias-62897874", + "timestamp": 1664284329000 + }, + { + "id": "2a684580-b743-4180-8943-ca3f665e0f21", + "rank": 12, + "title": "\"Nos golpean día y noche sin parar\": las torturas y secuestros que sufren los migrantes que huyen del Talibán", + "href": "/mundo/noticias-internacional-65903588", + "timestamp": 1686797230000 + }, + { + "id": "0bd1529c-1339-48c1-9c06-c3d4a741e648", + "rank": 13, + "title": "Las incógnitas sobre quién heredará el imperio de US$6.500 millones de Silvio Berlusconi", + "href": "/mundo/noticias-internacional-65904977", + "timestamp": 1686748823000 + }, + { + "id": "30fa30ca-e382-4f92-b85d-9ffa56df4cc5", + "rank": 14, + "title": "3 áreas en las que la inteligencia artificial ya está mejorando nuestras vidas", + "href": "/mundo/noticias-65839988", + "timestamp": 1686735365000 + }, + { + "id": "e9301754-82ea-46e6-b8cc-e1cdaea0029f", + "rank": 15, + "title": "Investigación BBC | Los hombres que venden videos toqueteando a chicas en el metro de Japón (y cómo los están persiguiendo)", + "href": "/mundo/noticias-internacional-65861189", + "timestamp": 1686535346000 + }, + { + "id": "256c9c44-624a-47ac-8d48-e03908500b89", + "rank": 16, + "title": "\"La selva no era la amenaza, la selva los salvó\": ¿cómo pudieron sobrevivir los 4 niños que pasaron 40 días en la Amazonía colombiana?", + "href": "/mundo/noticias-america-latina-65869230", + "timestamp": 1686438275000 + }, + { + "id": "2534c64f-f53e-4af8-adcb-19d3eeeec700", + "rank": 17, + "title": "Françoise Gilot, la artista que amó y abandonó a Picasso (y que reveló el lado oscuro del genio)", + "href": "/mundo/noticias-65851130", + "timestamp": 1686482061000 + }, + { + "id": "fd1f78e8-9800-4905-b532-2dc5a6ad0b3b", + "rank": 18, + "title": "4 asombrosas historias de supervivencia y rescate que conmovieron al mundo", + "href": "/mundo/noticias-internacional-65885939", + "timestamp": 1686614917000 + }, + { + "id": "e1c294e0-90ba-425a-8d8e-2a89a1af5efa", + "rank": 19, + "title": "Por qué salir del clóset puede durar “toda la vida” para las personas LGBT+ (y cómo el apoyo familiar puede marcar la diferencia)", + "href": "/mundo/vert-fut-65891479", + "timestamp": 1686738366000 + }, + { + "id": "8edfba76-2d51-4261-aba1-fdb9d1800270", + "rank": 20, + "title": "Quién es el heredero de George Soros que tomará el control de su imperio de US$25.000 millones", + "href": "/mundo/noticias-internacional-65883449", + "timestamp": 1686588543000 + } + ] + }, + "contentType": "application/json; charset=utf-8" +} diff --git a/src/app/components/MostRead/Amp/__snapshots__/index.test.tsx.snap b/src/app/components/MostRead/Amp/__snapshots__/index.test.tsx.snap index 0ff2f50732b..4f905dfb623 100644 --- a/src/app/components/MostRead/Amp/__snapshots__/index.test.tsx.snap +++ b/src/app/components/MostRead/Amp/__snapshots__/index.test.tsx.snap @@ -227,7 +227,7 @@ exports[`AmpMostRead should render as expected 1`] = ` > - {{promo.headlines.shortHeadline}} + {{title}} diff --git a/src/app/components/MostRead/Amp/getRemoteDataScript/index.test.ts b/src/app/components/MostRead/Amp/getRemoteDataScript/index.test.ts new file mode 100644 index 00000000000..19984c8a588 --- /dev/null +++ b/src/app/components/MostRead/Amp/getRemoteDataScript/index.test.ts @@ -0,0 +1,32 @@ +/* eslint-disable no-eval */ +import { transformData } from '.'; +import { WesternArabic } from '../../../../legacy/psammead/psammead-locales/src/numerals'; +import { data as pidginMostRead } from '../../../../../../data/pidgin/mostRead/pidgin.json'; + +describe('getRemoteDataScript', () => { + it('transformData should append rankTranslation to each item', async () => { + const data = pidginMostRead; + + const translations = WesternArabic; + + eval(transformData()); + + data.items.forEach((item: object, index: number) => { + expect(item).toHaveProperty('rankTranslation'); + // @ts-expect-error required for testing purposes + expect(item.rankTranslation).toBe(translations[index + 1]); + }); + }); + + it('transformData should throw an error if there are empty items in the data response', async () => { + const data = { ...pidginMostRead, items: [] }; + expect(data.items).toHaveLength(0); + + const translations = WesternArabic; + expect(translations).toBeDefined(); + + expect(() => eval(transformData())).toThrowError( + 'Empty records from mostread endpoint', + ); + }); +}); diff --git a/src/app/components/MostRead/Amp/getRemoteDataScript/index.ts b/src/app/components/MostRead/Amp/getRemoteDataScript/index.ts new file mode 100644 index 00000000000..87a272cf10e --- /dev/null +++ b/src/app/components/MostRead/Amp/getRemoteDataScript/index.ts @@ -0,0 +1,42 @@ +import { Services } from '../../../../models/types/global'; +import { serviceNumerals } from '../../Canonical/Rank'; + +export const transformData = () => { + return ` + if (data.items.length === 0) { + throw new Error('Empty records from mostread endpoint'); + } + + data.items.forEach((item, index) => { + item.rankTranslation = translations[index + 1]; + }); + `; +}; + +export default ({ + endpoint, + service, +}: { + endpoint: string; + service: Services; +}) => { + return ` + const translations = ${JSON.stringify(serviceNumerals(service))} + + const getRemoteData = async () => { + try { + const response = await fetch("${endpoint}"); + const { data } = await response.json(); + + ${transformData()} + + return data; + } catch (error) { + console.warn(error); + return []; + } + } + + exportFunction('getRemoteData', getRemoteData); + `; +}; diff --git a/src/app/components/MostRead/Amp/index.test.tsx b/src/app/components/MostRead/Amp/index.test.tsx index 6f49b27c38f..ad161b79b3f 100644 --- a/src/app/components/MostRead/Amp/index.test.tsx +++ b/src/app/components/MostRead/Amp/index.test.tsx @@ -4,7 +4,7 @@ import { render, act } from '../../react-testing-library-with-providers'; import { ServiceContextProvider } from '../../../contexts/ServiceContext'; import AmpMostRead from '.'; import { Services } from '../../../models/types/global'; -import mundoMostReadResponse from '../../../../../data/mundo/mostRead/index.json'; +import { data as mundoMostReadResponse } from '../../../../../data/mundo/mostRead/mundo.json'; interface MostReadAmpWithContextProps { service: Services; @@ -45,6 +45,9 @@ describe('AmpMostRead', () => { const { container, getByText } = render( , + { + service: 'mundo', + }, ); await act(async () => { @@ -58,17 +61,19 @@ describe('AmpMostRead', () => { }); }); - it('should render fallback when fetch records are empty', async () => { + it('should render fallback when items are empty', async () => { fetchMock.mock('localhost:7080/mundo/mostread.json', { generated: '2022-05-03T14:44:35.496Z', lastRecordTimeStamp: '2022-05-03T14:42:00Z', firstRecordTimeStamp: '2022-05-03T14:27:00Z', - totalRecords: 20, - records: [], + items: [], }); const { container, getByText } = render( , + { + service: 'mundo', + }, ); await act(async () => { @@ -82,16 +87,18 @@ describe('AmpMostRead', () => { }); }); - it('should render fallback when fetch records is undefined', async () => { + it('should render fallback when items are undefined', async () => { fetchMock.mock('localhost:7080/mundo/mostread.json', { generated: '2022-05-03T14:44:35.496Z', lastRecordTimeStamp: '2022-05-03T14:42:00Z', firstRecordTimeStamp: '2022-05-03T14:27:00Z', - totalRecords: 20, }); const { container, getByText } = render( , + { + service: 'mundo', + }, ); await act(async () => { diff --git a/src/app/components/MostRead/Amp/index.tsx b/src/app/components/MostRead/Amp/index.tsx index 8d9a035d018..23ca0e10402 100644 --- a/src/app/components/MostRead/Amp/index.tsx +++ b/src/app/components/MostRead/Amp/index.tsx @@ -10,46 +10,11 @@ import { import pathOr from 'ramda/src/pathOr'; import { ServiceContext } from '../../../contexts/ServiceContext'; import { MostReadItemWrapper, MostReadLink } from '../Canonical/Item'; -import MostReadRank, { serviceNumerals } from '../Canonical/Rank'; +import MostReadRank from '../Canonical/Rank'; import generateCSPHash from '../utilities/generateCSPHash'; -import { Services } from '../../../models/types/global'; import { Size, Direction } from '../types'; import styles from './index.styles'; - -const rankTranslationScript = (endpoint: string, service: Services) => { - const translation = serviceNumerals(service); - return ` - const translations = ${JSON.stringify(translation)} - const getRemoteData = async () => { - try{ - const response = await fetch("${endpoint}"); - const data = await response.json(); - - if(data.records.length === 0){ - throw new Error("Empty records from mostread endpoint"); - } - - data.records.forEach((item, index) => { - item.rankTranslation = translations[index+1]; - - if (!item.promo.headlines.shortHeadline) { - item.promo.headlines.shortHeadline = item.promo.headlines.seoHeadline; - } - - if(!item.promo.locators.assetUri) { - item.promo.locators.assetUri = item.promo.locators.canonicalUrl; - } - - }); - - return data; - } catch(error){ - console.warn(error); - return []; - } - } - exportFunction('getRemoteData', getRemoteData);`; -}; +import getRemoteDataScript from './getRemoteDataScript'; interface AmpMostReadProps { endpoint: string; @@ -64,7 +29,7 @@ const AmpMostRead = ({ endpoint, size = 'default' }: AmpMostReadProps) => { translations, } = useContext(ServiceContext); - const onlyinnerscript = rankTranslationScript(endpoint, service); + const onlyinnerscript = getRemoteDataScript({ endpoint, service }); const fallbackText = pathOr( 'Content is not available', @@ -103,7 +68,7 @@ const AmpMostRead = ({ endpoint, size = 'default' }: AmpMostReadProps) => { { diff --git a/src/app/components/MostRead/index.test.tsx b/src/app/components/MostRead/index.test.tsx index 670e1b4fa52..b8a980f35f0 100644 --- a/src/app/components/MostRead/index.test.tsx +++ b/src/app/components/MostRead/index.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import isLive from '#app/lib/utilities/isLive'; import { RequestContextProvider } from '../../contexts/RequestContext'; import { ToggleContextProvider } from '../../contexts/ToggleContext'; -import pidginMostReadData from '../../../../data/pidgin/mostRead/pidgin.json'; +import { data as pidginMostReadData } from '../../../../data/pidgin/mostRead/pidgin.json'; import serbianLatMostReadData from '../../../../data/serbian/mostRead/lat.json'; import { FRONT_PAGE, @@ -18,9 +18,13 @@ import { PageTypes, Services, Variants } from '../../models/types/global'; import Canonical from './Canonical'; import Amp from './Amp'; import { MostReadData } from './types'; +import isLocal from '../../lib/utilities/isLocal'; jest.mock('./Canonical'); jest.mock('./Amp'); +jest.mock('../../lib/utilities/isLocal', () => + jest.fn().mockImplementation(() => true), +); interface MostReadProps { isAmp: boolean; @@ -245,6 +249,39 @@ describe('MostRead', () => { }); }, ); + + describe('endpoint', () => { + it.each` + service | variant | isLocalEnv | endpoint + ${'pidgin'} | ${null} | ${true} | ${'/pidgin/mostread.json'} + ${'pidgin'} | ${null} | ${false} | ${'/fd/simorgh-bff?pageType=mostRead&service=pidgin'} + ${'serbian'} | ${'cyr'} | ${true} | ${'/serbian/mostread/cyr.json'} + ${'serbian'} | ${'cyr'} | ${false} | ${'/fd/simorgh-bff?pageType=mostRead&service=serbian&variant=cyr'} + `( + 'should be $endpoint when service is $service, variant is $variant and isLocalEnv is $isLocalEnv', + async ({ service, variant, isLocalEnv, endpoint }) => { + (isLocal as jest.Mock).mockImplementationOnce(() => isLocalEnv); + + render( + , + ); + + expect(Amp).toHaveBeenCalledWith( + expect.objectContaining({ + endpoint: expect.stringContaining(endpoint), + }), + {}, + ); + }, + ); + }); }); describe('Presence on live environment', () => { @@ -263,7 +300,7 @@ describe('MostRead', () => { // if isLive is true, DO NOT show most read component const { container } = render( - , + , { toggles }, ); @@ -276,7 +313,7 @@ describe('MostRead', () => { // if isLive is false, show most read component const { container } = render( - , + , { toggles }, ); expect(container).not.toBeEmptyDOMElement(); diff --git a/src/app/components/MostRead/index.tsx b/src/app/components/MostRead/index.tsx index 1080f0644b8..546165ee16e 100644 --- a/src/app/components/MostRead/index.tsx +++ b/src/app/components/MostRead/index.tsx @@ -10,6 +10,7 @@ import { ColumnLayout, Size, MostReadData } from './types'; import MostReadSection from './Section'; import MostReadSectionLabel from './Label'; import { WHITE } from '../ThemeProvider/palette'; +import isLocal from '../../lib/utilities/isLocal'; import { STORY_PAGE, CORRESPONDENT_STORY_PAGE, @@ -61,9 +62,13 @@ const MostRead = ({ return null; } + // If not in local environment, use the BFF, otherwise use fixture data + const isBff = !isLocal(); + const endpoint = getMostReadEndpoint({ service, variant, + isBff, }); // We render amp on ONLY STY, CSP and ARTICLE pages using amp-list. diff --git a/src/app/lib/utilities/getUrlHelpers/getMostReadUrls/index.js b/src/app/lib/utilities/getUrlHelpers/getMostReadUrls/index.js index 9e249de79aa..4dcd577f9d6 100644 --- a/src/app/lib/utilities/getUrlHelpers/getMostReadUrls/index.js +++ b/src/app/lib/utilities/getUrlHelpers/getMostReadUrls/index.js @@ -1,7 +1,14 @@ -export const getMostReadEndpoint = ({ service, variant }) => - variant +export const getMostReadEndpoint = ({ service, variant, isBff = false }) => { + if (isBff) { + return variant + ? `/fd/simorgh-bff?pageType=mostRead&service=${service}&variant=${variant}` + : `/fd/simorgh-bff?pageType=mostRead&service=${service}`; + } + + return variant ? `/${service}/mostread/${variant}.json` : `/${service}/mostread.json`; +}; export const getLocalMostReadEndpoint = ({ service, variant = 'default' }) => `./data/${service}/mostRead/${ diff --git a/src/app/lib/utilities/getUrlHelpers/getMostReadUrls/index.test.js b/src/app/lib/utilities/getUrlHelpers/getMostReadUrls/index.test.js index 15764a79e57..234f52312c8 100644 --- a/src/app/lib/utilities/getUrlHelpers/getMostReadUrls/index.test.js +++ b/src/app/lib/utilities/getUrlHelpers/getMostReadUrls/index.test.js @@ -1,17 +1,20 @@ import { getMostReadEndpoint, getLocalMostReadEndpoint } from '.'; describe('getMostReadEndpoint', () => { - it('should return endpoint when passed service', () => { - expect(getMostReadEndpoint({ service: 'hausa' })).toBe( - '/hausa/mostread.json', - ); - }); - it('should return endpoint when passed service and variant', () => { - expect(getMostReadEndpoint({ service: 'serbian', variant: 'lat' })).toBe( - '/serbian/mostread/lat.json', - ); - }); + it.each` + service | variant | isBff | expected + ${'hausa'} | ${null} | ${false} | ${'/hausa/mostread.json'} + ${'hausa'} | ${null} | ${true} | ${'/fd/simorgh-bff?pageType=mostRead&service=hausa'} + ${'serbian'} | ${'lat'} | ${false} | ${'/serbian/mostread/lat.json'} + ${'serbian'} | ${'lat'} | ${true} | ${'/fd/simorgh-bff?pageType=mostRead&service=serbian&variant=lat'} + `( + 'should return the correct endpoint when service is $service, variant is $variant and isBff is $isBff', + ({ isBff, service, variant, expected }) => { + expect(getMostReadEndpoint({ service, variant, isBff })).toBe(expected); + }, + ); }); + describe('getLocalMostReadEndpoint', () => { it('should return endpoint when passed service', () => { expect(getLocalMostReadEndpoint({ service: 'hausa' })).toBe(