import frontPageMediaSets from '~/testing/front-page.json'; import path from 'path'; import { readFile } from 'fs/promises'; import { RT_URL } from '~/globals/rt-urls'; import { RtElTarget } from '~/models/rt-el-target'; import { DEFAULT_LIST_LENGTH, generateDynamicList, generateDynamicListItem } from '~/testing/rt-dom-construction'; import { describe, expect, test } from '@jest/globals'; import { extractBodyFromPage, extractMediaFromDynamicListItem, extractMediaFromFrontPageListItem, extractMediaSetFromDynamicList, extractMediaSetFromFrontPageList, extractMediaSetsFromFrontPageBody } from '~/modules/parser'; import type { Media, MediaSet } from '@rt_lite/common/models'; const expectedFrontPageMediaSets = frontPageMediaSets // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types .map(set => ({ ...set, // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types media: set.media.map(item => ({ ...item, url: new URL(item.url) })) })); // eslint-disable-next-line max-lines-per-function describe('Rotten Tomatoes Parser', () => { describe('extractBodyFromPage', () => { test('Body is extracted correctly from typical page', () => { const expectedOutput = '

Hello world!

'; const page = ` Dummy Page ${expectedOutput} `.trim() .replace(/\n|\t/gu, ''); const actualOutput = extractBodyFromPage(page); expect(actualOutput).toBe(expectedOutput); }); test('Body is extract correctly from page with only body', () => { const expectedOutput = '

Nobody

'; const page = ` ${expectedOutput} `.trim() .replace(/\n|\t/gu, ''); const actualOutput = extractBodyFromPage(page); expect(actualOutput).toBe(expectedOutput); }); }); describe('extractMediaFromFrontPageListItem', () => { test('Full record returns Media', () => { const listItem = document.createElement('li'); listItem.innerHTML = ` Planet of the Apes 100 `.trim(); const expectedOutput: Media = { title: 'Planet of the Apes', score: 100, url: new URL('/planet-of-the-apes', RT_URL) }; const actualOutput = extractMediaFromFrontPageListItem(listItem); expect(actualOutput).toStrictEqual(expectedOutput); }); test('Record without title returns null', () => { const listItem = document.createElement('li'); listItem.innerHTML = ` 100 `.trim(); expect(extractMediaFromFrontPageListItem(listItem)).toBeNull(); }); test('Record without anchor returns null', () => { const listItem = document.createElement('li'); listItem.innerHTML = ` Planet of the Apes 100 `.trim(); expect(extractMediaFromFrontPageListItem(listItem)).toBeNull(); }); test('Record without url returns null', () => { const listItem = document.createElement('li'); listItem.innerHTML = ` Planet of the Apes 100 `.trim(); expect(extractMediaFromFrontPageListItem(listItem)).toBeNull(); }); test('Record without score returns Media without score', () => { const listItem = document.createElement('li'); listItem.innerHTML = ` Planet of the Apes `.trim(); const expectedOutput: Media = { title: 'Planet of the Apes', url: new URL('/planet-of-the-apes', RT_URL) }; const actualOutput = extractMediaFromFrontPageListItem(listItem); expect(actualOutput).toStrictEqual(expectedOutput); }); test('Record with invalid score returns Media without score', () => { const listItem = document.createElement('li'); listItem.innerHTML = ` Planet of the Apes asdf `.trim(); const expectedOutput: Media = { title: 'Planet of the Apes', url: new URL('/planet-of-the-apes', RT_URL) }; const actualOutput = extractMediaFromFrontPageListItem(listItem); expect(actualOutput).toStrictEqual(expectedOutput); }); }); // eslint-disable-next-line max-lines-per-function describe('extractMediaSetFromFrontPageList', () => { // eslint-disable-next-line max-lines-per-function test('Complete record returns complete MediaSet', () => { const list = document.createElement('ul'); list.setAttribute('slot', 'list-items'); list.innerHTML = `

Planet of the Apes Collection

  • Planet of the Apes 86
  • Beneath the Planet of the Apes 39
  • Escape from the Planet of the Apes 77
  • Conquest of the Planet of the Apes 50
  • [ { "title": "Popular Streaming Movies", "movies": [ { "title": "The Tomorrow War", "url": "https://www.rottentomatoes.com/m/the_tomorrow_war", "score": 54 }, { "title": "Fear Street Part One: 1994", "url": "https://www.rottentomatoes.com/m/fear_street_part_one_1994", "score": 80 }, { "title": "No Sudden Move", "url": "https://www.rottentomatoes.com/m/no_sudden_move", "score": 90 }, { "title": "Luca", "url": "https://www.rottentomatoes.com/m/luca_2021", "score": 90 }, { "title": "In the Heights", "url": "https://www.rottentomatoes.com/m/in_the_heights_2021", "score": 95 }, { "title": "The Boss Baby: Family Business", "url": "https://www.rottentomatoes.com/m/the_boss_baby_family_business", "score": 47 }, { "title": "Till Death", "url": "https://www.rottentomatoes.com/m/till_death_2021", "score": 92 }, { "title": "Cruella", "url": "https://www.rottentomatoes.com/m/cruella", "score": 74 }, { "title": "The Ice Road", "url": "https://www.rottentomatoes.com/m/the_ice_road", "score": 41 }, { "title": "Werewolves Within", "url": "https://www.rottentomatoes.com/m/werewolves_within", "score": 86 } ] }, { "title": "Most Popular TV on RT ", "movies": [ { "title": "Loki: S01", "url": "https://www.rottentomatoes.com/tv/loki/s01", "score": 92 }, { "title": "Sex/Life: S01", "url": "https://www.rottentomatoes.com/tv/sex_life/s01", "score": 29 }, { "title": "Katla: S01", "url": "https://www.rottentomatoes.com/tv/katla/s01", "score": 100 }, { "title": "Blindspotting: S01", "url": "https://www.rottentomatoes.com/tv/blindspotting/s01", "score": 100 }, { "title": "Sweet Tooth: S01", "url": "https://www.rottentomatoes.com/tv/sweet_tooth/s01", "score": 98 }, { "title": "Black Summer: S02", "url": "https://www.rottentomatoes.com/tv/black_summer/s02", "score": 100 }, { "title": "Kevin Can F... Himself: S01", "url": "https://www.rottentomatoes.com/tv/kevin_can_f_k_himself/s01", "score": 83 }, { "title": "Manifest: S03", "url": "https://www.rottentomatoes.com/tv/manifest/s03" }, { "title": "Young Royals: S01", "url": "https://www.rottentomatoes.com/tv/young_royals/s01" }, { "title": "Sophie: A Murder in West Cork: S01", "url": "https://www.rottentomatoes.com/tv/sophie_a_murder_in_west_cork/s01", "score": 86 } ] }, { "title": "New TV This Week", "movies": [ { "title": "Monsters at Work: S01", "url": "https://www.rottentomatoes.com/tv/monsters_at_work/s01", "score": 75 }, { "title": "Gossip Girl: S01", "url": "https://www.rottentomatoes.com/tv/gossip_girl_2021/s01", "score": 34 }, { "title": "The Beast Must Die: S01", "url": "https://www.rottentomatoes.com/tv/the_beast_must_die/s01", "score": 100 }, { "title": "Resident Evil: Infinite Darkness: S01", "url": "https://www.rottentomatoes.com/tv/resident_evil_infinite_darkness/s01", "score": 53 }, { "title": "Shark Beach with Chris Hemsworth", "url": "https://www.rottentomatoes.com/m/shark_beach_with_chris_hemsworth" }, { "title": "grown-ish: S04", "url": "https://www.rottentomatoes.com/tv/grown_ish/s04" }, { "title": "I Think You Should Leave With Tim Robinson: S02", "url": "https://www.rottentomatoes.com/tv/i_think_you_should_leave_with_tim_robinson/s02", "score": 100 }, { "title": "Atypical: S04", "url": "https://www.rottentomatoes.com/tv/atypical/s04" }, { "title": "Virgin River: S03", "url": "https://www.rottentomatoes.com/tv/virgin_river/s03" }, { "title": "Leverage: Redemption", "url": "https://www.rottentomatoes.com/tv/leverage_redemption", "score": 88 } ] } ]
  • Battle for the Planet of the Apes 36
  • `.trim(); const expectedOutput: MediaSet = { title: 'Planet of the Apes Collection', media: [ { title: 'Planet of the Apes', url: new URL('planet-of-the-apes', RT_URL), score: 86 }, { title: 'Beneath the Planet of the Apes', url: new URL('beneath-the-planet-of-the-apes', RT_URL), score: 39 }, { title: 'Escape from the Planet of the Apes', url: new URL('escape-from-the-planet-of-the-apes', RT_URL), score: 77 }, { title: 'Conquest of the Planet of the Apes', url: new URL('conquest-of-the-planet-of-the-apes', RT_URL), score: 50 }, { title: 'Battle for the Planet of the Apes', url: new URL('battle-for-the-planet-of-the-apes', RT_URL), score: 36 } ] }; const actualOutput = extractMediaSetFromFrontPageList(list); expect(actualOutput).toStrictEqual(expectedOutput); }); test('Record without heading returns null', () => { const list = document.createElement('ul'); list.setAttribute('slot', 'list-items'); list.innerHTML = `
  • Planet of the Apes 86
  • Beneath the Planet of the Apes 39
  • Escape from the Planet of the Apes 77
  • Conquest of the Planet of the Apes 50
  • Battle for the Planet of the Apes 36
  • `.trim(); const actualOutput = extractMediaSetFromFrontPageList(list); expect(actualOutput).toBeNull(); }); test('Record without movies returns null', () => { const list = document.createElement('ul'); list.setAttribute('slot', 'list-items'); list.innerHTML = `

    Planet of the Apes Collection

    `.trim(); const actualOutput = extractMediaSetFromFrontPageList(list); expect(actualOutput).toBeNull(); }); test('Record without title returns null', () => { const list = document.createElement('ul'); list.setAttribute('slot', 'list-items'); list.innerHTML = `

  • Planet of the Apes 86
  • Beneath the Planet of the Apes 39
  • Escape from the Planet of the Apes 77
  • Conquest of the Planet of the Apes 50
  • Battle for the Planet of the Apes 36
  • `.trim(); const actualOutput = extractMediaSetFromFrontPageList(list); expect(actualOutput).toBeNull(); }); test('Record with invalid movies returns MovieSet with only valid movies', () => { const list = document.createElement('ul'); list.setAttribute('slot', 'list-items'); list.innerHTML = `

    Planet of the Apes Collection

  • Planet of the Apes 86
  • Beneath the Planet of the Apes 39
  • Escape from the Planet of the Apes 77
  • 50
  • Battle for the Planet of the Apes 36
  • `.trim(); const expectedOutput: MediaSet = { title: 'Planet of the Apes Collection', media: [ { title: 'Planet of the Apes', url: new URL('planet-of-the-apes', RT_URL), score: 86 }, { title: 'Escape from the Planet of the Apes', url: new URL('escape-from-the-planet-of-the-apes', RT_URL), score: 77 } ] }; const actualOutput = extractMediaSetFromFrontPageList(list); expect(actualOutput).toStrictEqual(expectedOutput); }); }); describe('extractMediaFromDynamicListItem', () => { test('Returns null when no title present', () => { const listItem = generateDynamicListItem({ title: false }); const media = extractMediaFromDynamicListItem(listItem); expect(media).toBeNull(); }); test('Returns null if no link to the item is present', () => { const listItem = generateDynamicListItem({ link: false }); const media = extractMediaFromDynamicListItem(listItem); expect(media).toBeNull(); }); test('Returns null when title element has no textContent', () => { const listItem = generateDynamicListItem({ titleTextContent: false }); const media = extractMediaFromDynamicListItem(listItem); expect(media).toBeNull(); }); test('Returns null if link has null href', () => { const listItem = generateDynamicListItem({ linkHref: false }); const media = extractMediaFromDynamicListItem(listItem); expect(media).toBeNull(); }); test('Returns Media without score if no score present', () => { const listItem = generateDynamicListItem({ score: false }); const media = extractMediaFromDynamicListItem(listItem); expect(media).not.toBeNull(); expect(media?.score).toBeUndefined(); }); test('Returns item without score if score element missing percentage attribute', () => { const listItem = generateDynamicListItem({ scorePercentage: false }); const media = extractMediaFromDynamicListItem(listItem); expect(media).not.toBeNull(); expect(media?.score).toBeUndefined(); }); test('Returns Media without score if score is not a number', () => { const listItem = generateDynamicListItem(); const scoreEl = listItem.querySelector(RtElTarget.DYNAMIC_LIST_ITEM_SCORE); if (!scoreEl) throw new Error('List item improperly constructed'); scoreEl.setAttribute('criticsscore', 'asdf'); const media = extractMediaFromDynamicListItem(listItem); expect(media).not.toBeNull(); expect(media?.score).toBeUndefined(); }); test('Returns Media with score if list item contains numeric score', () => { const listItem = generateDynamicListItem(); const media = extractMediaFromDynamicListItem(listItem); expect(media).not.toBeNull(); expect(typeof media?.title).toStrictEqual('string'); expect(media?.url).toBeInstanceOf(URL); expect(typeof media?.score).toStrictEqual('number'); }); }); describe('extractMediaSetFromDynamicList', () => { test('Returns null if no heading present', () => { const list = generateDynamicList({ heading: false }); const media = extractMediaSetFromDynamicList(list); expect(media).toBeNull(); }); test('Returns null if no movie list items present', () => { const list = generateDynamicList({ emptyList: true }); const media = extractMediaSetFromDynamicList(list); expect(media).toBeNull(); }); test('Returns null if heading contains no title', () => { const list = generateDynamicList({ titleTextContent: false }); const media = extractMediaSetFromDynamicList(list); expect(media).toBeNull(); }); test('Returns MediaSet with movies containing only valid movies', () => { const invalidCount = 3; const expectedCount = DEFAULT_LIST_LENGTH - invalidCount; const list = generateDynamicList({ invalidCount }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const mediaSet = extractMediaSetFromDynamicList(list)!; expect(mediaSet).not.toBeNull(); expect(mediaSet.media).toHaveLength(expectedCount); }); }); describe('extractMediaSetsFromFrontPageBody', () => { test('Returns empty array if no valid media sets on page', () => { const body = document.createElement('body'); const mediaSets = extractMediaSetsFromFrontPageBody(body); expect(Array.isArray(mediaSets)).toStrictEqual(true); expect(mediaSets).toHaveLength(0); }); test('Returns array containing all valid media sets on page', async () => { expect.assertions(1); const frontPagePath = path.resolve(__dirname, '../testing/front-page.html'); const frontPage = await readFile(frontPagePath, 'utf-8'); const bodyHtml = extractBodyFromPage(frontPage); const body = document.createElement('body'); body.innerHTML = bodyHtml; const mediaSets = extractMediaSetsFromFrontPageBody(body); expect(mediaSets).toStrictEqual(expectedFrontPageMediaSets); }); }); });