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);
});
});
});