From 1535907fe3e86e05c5794504a7d9fe2d01bc7117 Mon Sep 17 00:00:00 2001 From: BART! Date: Mon, 15 Sep 2025 11:17:28 +0200 Subject: [PATCH 01/12] feat(tv-series): seasons, episodes [kickoff] --- demo.ts | 4 +-- src/dto/movie.ts | 17 +++++++++++++ src/helpers/global.helper.ts | 10 ++++++++ src/helpers/movie.helper.ts | 48 ++++++++++++++++++++++++++++++++++- src/services/movie.service.ts | 14 +++++++--- 5 files changed, 86 insertions(+), 7 deletions(-) diff --git a/demo.ts b/demo.ts index 620feaa..733b4b4 100644 --- a/demo.ts +++ b/demo.ts @@ -4,13 +4,13 @@ import { csfd } from './src'; // csfd.setOptions({ optionsRequest: { credentials: 'include' } }); // Parse movie -csfd.movie(10135).then((movie) => console.log(movie)); +csfd.movie(621073).then((movie) => console.log(movie)); // csfd.search('matrix').then((search) => console.log(search)); // csfd.cinema(1, 'today').then((cinema) => console.log(cinema)); // Parse creator -csfd.creator(2120).then((creator) => console.log(creator)); +// csfd.creator(2120).then((creator) => console.log(creator)); /** * USER RATINGS diff --git a/src/dto/movie.ts b/src/dto/movie.ts index aa1fca7..3516afb 100644 --- a/src/dto/movie.ts +++ b/src/dto/movie.ts @@ -17,6 +17,14 @@ export interface CSFDMovie extends CSFDScreening { premieres: CSFDPremiere[]; related: CSFDMovieListItem[]; similar: CSFDMovieListItem[]; + seasons: CSFDSeason[] | null; + episodes: CSFDSeason[] | null; + parent: CSFDParent | null; +} + +export interface CSFDParent { + season: { id: number; name: string; }; + series: { id: number; name: string; }; } export interface MovieJsonLd { @@ -92,6 +100,8 @@ export interface CSFDMovieListItem { url: string; } + + export type CSFDGenres = | 'Akční' | 'Animovaný' @@ -187,3 +197,10 @@ export interface CSFDPremiere { } export type CSFDBoxContent = 'Související' | 'Podobné'; + +export interface CSFDSeason { + id: number; + name: string; + url: string; + info: string | null; +} \ No newline at end of file diff --git a/src/helpers/global.helper.ts b/src/helpers/global.helper.ts index eef6d14..e1045a4 100644 --- a/src/helpers/global.helper.ts +++ b/src/helpers/global.helper.ts @@ -16,6 +16,16 @@ export const parseIdFromUrl = (url: string): number => { return +id || null; }; +export const parseLastIdFromUrl = (url: string): number => { + if (url) { + const idSlug = url?.split('/')[3]; + const id = idSlug?.split('-')[0]; + return +id || null; + } else { + return null; + } +}; + export const getColor = (cls: string): CSFDColorRating => { switch (cls) { case 'page-lightgrey': diff --git a/src/helpers/movie.helper.ts b/src/helpers/movie.helper.ts index 2f17191..1fcf142 100644 --- a/src/helpers/movie.helper.ts +++ b/src/helpers/movie.helper.ts @@ -9,14 +9,22 @@ import { CSFDGenres, CSFDMovieCreator, CSFDMovieListItem, + CSFDParent, CSFDPremiere, + CSFDSeason, CSFDTitlesOther, CSFDVod, CSFDVodService, MovieJsonLd } from '../dto/movie'; import { CSFDOptions } from '../types'; -import { addProtocol, getColor, parseISO8601Duration, parseIdFromUrl } from './global.helper'; +import { + addProtocol, + getColor, + parseISO8601Duration, + parseIdFromUrl, + parseLastIdFromUrl +} from './global.helper'; const CREATOR_LABELS: Record< string, @@ -321,6 +329,44 @@ export const getMovieCreators = (el: HTMLElement, options?: CSFDOptions): CSFDCr return creators; }; +export const getSeasonsOrEpisodes = (el: HTMLElement): CSFDSeason[] | null => { + const childrenList = el.querySelector('.film-episodes-list'); + if (!childrenList) return null; + + const childrenNodes = childrenList.querySelectorAll('.film-title'); + if (!childrenNodes?.length) return []; + + return childrenNodes.map((season) => { + const nameContainer = season.querySelector('.film-title-name'); + const infoContainer = season.querySelector('.info'); + + return { + id: parseLastIdFromUrl(nameContainer?.getAttribute('href') || ''), + name: nameContainer?.textContent?.trim() || null, + url: nameContainer?.getAttribute('href') || null, + info: infoContainer?.textContent?.replace(/[{()}]/g, '').trim() || null + }; + }); +}; + +export const getParent = (el: HTMLElement): CSFDParent | null => { + const parents = el.querySelectorAll('.film-header h2 a'); + if (parents.length === 0) return null; + + const [parentSeries, parentSeason] = parents; + + return { + series: { + id: parseIdFromUrl(parentSeries?.getAttribute('href') || ''), + name: parentSeries?.textContent?.trim() || null + }, + season: { + id: parseIdFromUrl(parentSeason?.getAttribute('href') || ''), + name: parentSeason?.textContent?.trim() || null + } + }; +}; + export const getMovieType = (el: HTMLElement): string => { const type = el.querySelector('.film-header-name .type'); return type?.innerText?.replace(/[{()}]/g, '') || 'film'; diff --git a/src/services/movie.service.ts b/src/services/movie.service.ts index f61c146..bb88013 100644 --- a/src/services/movie.service.ts +++ b/src/services/movie.service.ts @@ -21,7 +21,9 @@ import { getMovieTrivia, getMovieType, getMovieVods, - getMovieYear + getMovieYear, + getParent, + getSeasonsOrEpisodes } from '../helpers/movie.helper'; import { CSFDOptions } from '../types'; import { movieUrl } from '../vars'; @@ -57,7 +59,8 @@ export class MovieScraper { pageClasses: string[], jsonLd: MovieJsonLd | null, options: CSFDOptions - ): CSFDMovie { + ) { + const type = getMovieType(el) as CSFDFilmTypes; return { id: movieId, title: getMovieTitle(el), @@ -65,7 +68,7 @@ export class MovieScraper { duration: getMovieDuration(jsonLd, el), descriptions: getMovieDescriptions(el), genres: getMovieGenres(el), - type: getMovieType(el) as CSFDFilmTypes, + type, url: movieUrl(movieId, { language: options?.language }), origins: getMovieOrigins(el), colorRating: getMovieColorRating(pageClasses), @@ -80,7 +83,10 @@ export class MovieScraper { tags: getMovieTags(asideEl), premieres: getMoviePremieres(asideEl), related: getMovieBoxMovies(asideEl, 'Související'), - similar: getMovieBoxMovies(asideEl, 'Podobné') + similar: getMovieBoxMovies(asideEl, 'Podobné'), + seasons: type === 'seriál' ? getSeasonsOrEpisodes(el) : null, + episodes: type === 'série' ? getSeasonsOrEpisodes(el) : null, + parent: getParent(el) }; } } From 54cb6f85cc59d3575a4a0aeddf8ab67fa608617b Mon Sep 17 00:00:00 2001 From: BART! Date: Mon, 15 Sep 2025 18:06:01 +0200 Subject: [PATCH 02/12] feat(movie): episodeCode + seasons --- src/dto/movie.ts | 2 + src/helpers/movie.helper.ts | 88 +++++++++++++++++++++++++++++------ src/services/movie.service.ts | 22 ++++++--- 3 files changed, 93 insertions(+), 19 deletions(-) diff --git a/src/dto/movie.ts b/src/dto/movie.ts index 3516afb..6ee36ca 100644 --- a/src/dto/movie.ts +++ b/src/dto/movie.ts @@ -20,6 +20,8 @@ export interface CSFDMovie extends CSFDScreening { seasons: CSFDSeason[] | null; episodes: CSFDSeason[] | null; parent: CSFDParent | null; + episodeCode: string | null; + seasonName: string | null; } export interface CSFDParent { diff --git a/src/helpers/movie.helper.ts b/src/helpers/movie.helper.ts index 1fcf142..544e021 100644 --- a/src/helpers/movie.helper.ts +++ b/src/helpers/movie.helper.ts @@ -109,6 +109,26 @@ export const getMovieId = (el: HTMLElement): number => { return parseIdFromUrl(url); }; +export const getSerieasAndSeasonTitle = ( + el: HTMLElement +): { seriesName: string; seasonName: string | null } => { + const titleElement = el.querySelector('h1'); + if (!titleElement) { + return { seriesName: null, seasonName: null }; + } + + const fullText = titleElement.innerText.trim(); + + // Check if there's a series part indicated by ' - ' + if (fullText.includes(' - ')) { + const [seriesName, seasonName] = fullText.split(' - ').map((part) => part.trim()); + return { seriesName, seasonName }; + } + + // If no series part found, return just the name + return { seriesName: fullText, seasonName: null }; +}; + export const getMovieTitle = (el: HTMLElement): string => { return el.querySelector('h1').innerText.split(`(`)[0].trim(); }; @@ -329,7 +349,10 @@ export const getMovieCreators = (el: HTMLElement, options?: CSFDOptions): CSFDCr return creators; }; -export const getSeasonsOrEpisodes = (el: HTMLElement): CSFDSeason[] | null => { +export const getSeasonsOrEpisodes = ( + el: HTMLElement, + serie?: { id: number; title: string } +): CSFDSeason[] | null => { const childrenList = el.querySelector('.film-episodes-list'); if (!childrenList) return null; @@ -349,22 +372,61 @@ export const getSeasonsOrEpisodes = (el: HTMLElement): CSFDSeason[] | null => { }); }; -export const getParent = (el: HTMLElement): CSFDParent | null => { +export const getEpisodeCode = (el: HTMLElement): string | null => { + const filmHeaderName = el.querySelector('.film-header-name h1'); + if (!filmHeaderName) return null; + + const text = filmHeaderName.textContent?.trim() || ''; + const match = text.match(/\(([^)]+)\)/); + const code = match ? match[1] : null; + + return code; +}; + +export const detectSeasonOrEpisodeListType = (el: HTMLElement) => { + const headerText = el.querySelector('.box-header h3')?.innerText.trim() ?? ''; + + if (headerText.includes('Série')) return 'seasons'; + if (headerText.startsWith('Epizody')) return 'episodes'; + return null; +}; + +export const getSeasonorEpisodeParent = ( + el: HTMLElement, + serie?: { id: number; name: string } +): CSFDParent | null => { const parents = el.querySelectorAll('.film-header h2 a'); - if (parents.length === 0) return null; + if (parents.length === 0) { + if (!serie) return null; + return { series: serie, season: null }; + } const [parentSeries, parentSeason] = parents; - return { - series: { - id: parseIdFromUrl(parentSeries?.getAttribute('href') || ''), - name: parentSeries?.textContent?.trim() || null - }, - season: { - id: parseIdFromUrl(parentSeason?.getAttribute('href') || ''), - name: parentSeason?.textContent?.trim() || null - } - }; + const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href')); + const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href')); + const seriesName = parentSeries?.textContent?.trim() || null; + const seasonName = parentSeason?.textContent?.trim() || null; + + const series = seriesId && seriesName ? { id: seriesId, name: seriesName } : null; + const season = seasonId && seasonName ? { id: seasonId, name: seasonName } : null; + + if (!series && !season) return null; + + return { series, season }; +}; + +export const getMovieGroup = ( + el: HTMLElement, + group: CSFDCreatorGroups | CSFDCreatorGroupsEnglish | CSFDCreatorGroupsSlovak +): CSFDMovieCreator[] => { + const creators = el.querySelectorAll('.creators h4'); + const element = creators.filter((elem) => elem.textContent.trim().includes(group))[0]; + if (element?.parentNode) { + return parseMoviePeople(element.parentNode as HTMLElement); + } else { + return []; + } }; export const getMovieType = (el: HTMLElement): string => { diff --git a/src/services/movie.service.ts b/src/services/movie.service.ts index bb88013..4bc65e0 100644 --- a/src/services/movie.service.ts +++ b/src/services/movie.service.ts @@ -3,6 +3,8 @@ import { CSFDFilmTypes } from '../dto/global'; import { CSFDMovie, MovieJsonLd } from '../dto/movie'; import { fetchPage } from '../fetchers'; import { + detectSeasonOrEpisodeListType, + getEpisodeCode, getMovieBoxMovies, getMovieColorRating, getMovieCreators, @@ -22,8 +24,9 @@ import { getMovieType, getMovieVods, getMovieYear, - getParent, - getSeasonsOrEpisodes + getSeasonorEpisodeParent, + getSeasonsOrEpisodes, + getSerieasAndSeasonTitle } from '../helpers/movie.helper'; import { CSFDOptions } from '../types'; import { movieUrl } from '../vars'; @@ -61,9 +64,14 @@ export class MovieScraper { options: CSFDOptions ) { const type = getMovieType(el) as CSFDFilmTypes; + const { seriesName = null, seasonName = null } = + type === 'série' ? getSerieasAndSeasonTitle(el) : {}; + const seasonOrEpisodeListType = detectSeasonOrEpisodeListType(el); + + const title = type === 'série' && seriesName ? seriesName : getMovieTitle(el); return { id: movieId, - title: getMovieTitle(el), + title, year: getMovieYear(jsonLd), duration: getMovieDuration(jsonLd, el), descriptions: getMovieDescriptions(el), @@ -84,9 +92,11 @@ export class MovieScraper { premieres: getMoviePremieres(asideEl), related: getMovieBoxMovies(asideEl, 'Související'), similar: getMovieBoxMovies(asideEl, 'Podobné'), - seasons: type === 'seriál' ? getSeasonsOrEpisodes(el) : null, - episodes: type === 'série' ? getSeasonsOrEpisodes(el) : null, - parent: getParent(el) + seasons: seasonOrEpisodeListType === 'seasons' ? getSeasonsOrEpisodes(el) : null, + episodes: seasonOrEpisodeListType === 'episodes' ? getSeasonsOrEpisodes(el) : null, + parent: type === 'seriál' ? null : getSeasonorEpisodeParent(el, { id: movieId, name: title }), + episodeCode: type === 'epizoda' ? getEpisodeCode(el) : null, + seasonName }; } } From a43dfd64b384d860b806b4f72cadff15e360df0e Mon Sep 17 00:00:00 2001 From: BART! Date: Tue, 2 Dec 2025 23:01:09 +0100 Subject: [PATCH 03/12] test(series): add synthetic tests --- tests/movie.test.ts | 76 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/tests/movie.test.ts b/tests/movie.test.ts index b691b68..d981417 100644 --- a/tests/movie.test.ts +++ b/tests/movie.test.ts @@ -11,6 +11,7 @@ import { } from '../src/dto/movie'; import { getColor } from '../src/helpers/global.helper'; import { + getEpisodeCode, getLocalizedCreatorLabel, getMovieBoxMovies, getMovieColorRating, @@ -31,7 +32,9 @@ import { getMovieTrivia, getMovieType, getMovieVods, - getMovieYear + getMovieYear, + getSeasonorEpisodeParent, + getSerieasAndSeasonTitle } from '../src/helpers/movie.helper'; import { movieMock } from './mocks/movie1.html'; import { movieMockBlank } from './mocks/movie2.html'; @@ -621,3 +624,74 @@ describe('Get people', () => { }); }); }); + +describe('Get Series and Season Title', () => { + test('With series and season', () => { + const html = parse('

Series Name - Season 1

'); + const result = getSerieasAndSeasonTitle(html as any); + expect(result).toEqual({ seriesName: 'Series Name', seasonName: 'Season 1' }); + }); + + test('Only series name', () => { + const html = parse('

Movie Name

'); + const result = getSerieasAndSeasonTitle(html as any); + expect(result).toEqual({ seriesName: 'Movie Name', seasonName: null }); + }); + + test('No title element', () => { + const html = parse('
'); + const result = getSerieasAndSeasonTitle(html as any); + expect(result).toEqual({ seriesName: null, seasonName: null }); + }); +}); + +describe('Get Episode Code', () => { + test('With code', () => { + const html = parse('

Episode Name (S01E01)

'); + const result = getEpisodeCode(html as any); + expect(result).toEqual('S01E01'); + }); + + test('Without code', () => { + const html = parse('

Episode Name

'); + const result = getEpisodeCode(html as any); + expect(result).toBeNull(); + }); + + test('No header', () => { + const html = parse('
'); + const result = getEpisodeCode(html as any); + expect(result).toBeNull(); + }); +}); + +describe('Get Season or Episode Parent', () => { + test('With parents', () => { + const html = parse(` +
+

+ Series + Season +

+
+ `); + const result = getSeasonorEpisodeParent(html as any); + expect(result).toEqual({ + series: { id: 123, name: 'Series' }, + season: { id: 456, name: 'Season' } + }); + }); + + test('No parents but serie provided', () => { + const html = parse('
'); + const serie = { id: 123, name: 'Series' }; + const result = getSeasonorEpisodeParent(html as any, serie); + expect(result).toEqual({ series: serie, season: null }); + }); + + test('No parents and no serie', () => { + const html = parse('
'); + const result = getSeasonorEpisodeParent(html as any); + expect(result).toBeNull(); + }); +}); From c41a7549d2f5a4f5b20bc1a05d4b46c7a2d67b5b Mon Sep 17 00:00:00 2001 From: BART! Date: Tue, 2 Dec 2025 23:11:04 +0100 Subject: [PATCH 04/12] test(series): add mocks --- tests/mocks/series1-season1-episode.mock.ts | 1277 ++++++++++ tests/mocks/series1-season1.mock.ts | 1357 ++++++++++ tests/mocks/series1-seasons.mock.ts | 2552 +++++++++++++++++++ tests/mocks/series2-episodes.mock.ts | 1523 +++++++++++ 4 files changed, 6709 insertions(+) create mode 100644 tests/mocks/series1-season1-episode.mock.ts create mode 100644 tests/mocks/series1-season1.mock.ts create mode 100644 tests/mocks/series1-seasons.mock.ts create mode 100644 tests/mocks/series2-episodes.mock.ts diff --git a/tests/mocks/series1-season1-episode.mock.ts b/tests/mocks/series1-season1-episode.mock.ts new file mode 100644 index 0000000..4373ecc --- /dev/null +++ b/tests/mocks/series1-season1-episode.mock.ts @@ -0,0 +1,1277 @@ +export const serie1Season1EpisodeMock = ` + + + + + + + Simpsonovi - Mluvící hlava (S01E08) (1990) | ČSFD.cz + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

Reklama

+
+
+
+ +
+
+
+

+ Change website language +

+

+ + Do not switch + +

+
+
+ + + + +
+ + + +
+ +
+
+ +
+
+ +
+

+ Simpsonovi + - Série 1 (1989) (série) +

+
+
+

+ + Mluvící hlava + (S01E08) +

+ (epizoda) + + + + + + + + + +
    +
  • +USA The Telltale Head +
  • +
+
+
+
+ + +
+
+ + + + + + více + + +
+ + + + + +
+
+
+
+
+
+
+
+ 86% + +
+ +
+ + + + Hodnocení a fanklub + + +
+ + +
USA, + 1990, 22 min + + +
+ + +
+
+
+
+ + + + + +
+
+

+ Obsahy(1) + + +

+
+
+
+
+ + +

+ Bart ve snaze šplhnout si před partou ukradne hlavu sochy zakladatele města Springfieldu. Zděšení a pobouření zavládne mezi občany i povedenými kumpány Barta. Nakonec se Bart s Homerem snaží hlavu vrátit, přičemž jen o vlas uniknou lynčování... + + + (Česká televize) + +

+
+ +
+
+
+ + + +
+ + + +
+
+ +
+
+ +
+
+

Recenze (16)

+
+ VÍCE +
+
+
+
+
+
+
+

Wyrdas 

+
+ všechny recenze uživatele + (k tomuto seriálu) + +
+
+ + +

+ + Cesta do kostela byla super, omáčka okolo také... ale hlavní příběh byl takový plytký. Typicky nucený pokus o moralizaci, který mi do takového seriálu moc nepasuje. Nebylo to zlé, ale když si vzpomenu na uříznutou hlavu J.S., vybaví se mi jako první Southpark. A toto zastíní v mém hodnocení poslední hvězdu. Poučení z epizody: Barbarství jednotlivce či skupiny vymýtíme ubitím davem na ulici. - A přitom by stačilo useknout ruku. + + + + + + () + +

+ + +
+
+ + + +
+
+
+ + +
+
+
+
+

Cheeter 

+
+ všechny recenze uživatele + (k tomuto seriálu) + +
+
+ + +

+ + První série Simpsonů u mě nadšení nikdy nějak nadšení nebudila a stejně je na tom i tahle epizoda. Velmi jednoduchý děj s téměř nulovými vtipy. + + + + + + () + +

+ + +
+
+ +
+
+
+
+
+
+
+

Loki_Six 

+
+ všechny recenze uživatele + (k tomuto seriálu) + +
+
+ + +

+ + "Uh, ma'am, what if you're a really good person but you're in a really, really, really bad fight and your leg gets gangrene and has to be amputated. Will it be waiting for you in heaven?" + + + + + + () + +

+ + +
+
+ +
+
+ +
+
+

Galerie (8)

+
+ VÍCE +
+
+
+
+ +
+
+

Zajímavosti (14)

+
+ VÍCE +
+
+
+
+
+
    +
  • +Scéna, ve které se Bart probudí s hlavou sochy v posteli, je parodií na film Kmotr (1972). Bartova hláška v původním znění "Top of the world, ma" je zase narážka na Bílý žár (1949). + + + (henrycruel) + + + +
  • +
+
+
+
+
+
    +
  • +V jedné scéně fotbalový komentátor zmiňuje hráče jménem Wolodarsky. Jde o narážku na jednoho ze scenáristů seriálu Wallace Wolodarskyho. + + + (henrycruel) + + + +
  • +
+
+
+
+
+
    +
  • +Jde o jednu z mála epizod, jejíž název se objeví v úvodních titulcích. + + + (henrycruel) + + + +
  • +
+
+
+
+
+ + +
+ + + +
+
+ + +
+ +
+
+

Reklama

+
+
+
+
+
+

Reklama

+
+
+
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+ + + + + + + + + + +`; diff --git a/tests/mocks/series1-season1.mock.ts b/tests/mocks/series1-season1.mock.ts new file mode 100644 index 0000000..3da229b --- /dev/null +++ b/tests/mocks/series1-season1.mock.ts @@ -0,0 +1,1357 @@ +export const serie1Season1Mock = ` + + + + + + + Simpsonovi - Série 1 (S01) (1989) | ČSFD.cz + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

Reklama

+
+
+
+ +
+
+
+

+ Change website language +

+

+ + Do not switch + +

+
+
+ + + + +
+ + + +
+ +
+
+ + +
+ + + + +
+
+

+ Epizody(13) +

+
+
+
+ +
+
+
+ +
+
+

+ Obsahy(1) + + +

+
+
+
+ +
+ + +

+ Nejsledovanější americká rodina na svém počátku. V městečku Springfield žije rodina Simpsonových - uličník Bart, chytrá Líza, líný Homer, malá Maggie a starostlivá Marge. Tato rodina prožívá život plný zážitků. V první sérii se dozvíte, jak probíhají Vánoce u Simpsonových, jak se má Bart ve Francii, jestli je Bart opravdu malý génius nebo třeba jak se pomstít Nelsonovi, a zdali je Šáša opravdu vinen. + + + (Varan) + +

+ (více) +
+ +
+
+
+ + + +
+ + + +
+
+ +
+
+ +
+
+

Recenze (26)

+
+ VÍCE +
+
+
+
+
+
+
+

Lachtaan 

+
+ všechny recenze uživatele + (k tomuto seriálu) + +
+
+ + +

+ + Samotný začátek nesmrtelného nežlutého fenoménu je ohromnou nostalgií a i když mnozí ovlivnění dnešní animací tu počáteční nemusejí, já protáhlé jednoduché obličeje naší praštěné rodinky prostě žeru. +Nejlepší díly - Bárt generálem aneb Kdopak by se Nelsona bál, Smutná Líza a Kyselé hrozny Sladké Francie. +Celkově - 10/10! + + + + + + () + +

+ + +
+
+ +
+
+
+
+

Pierre 

+
+ všechny recenze uživatele + (k tomuto seriálu) + +
+
+ + +

+ + Ještě poměrně pomalý úvod. Klasický humor Simpsnů ještě neobsahuje. Je zde (možná překvapivě) i pár melancholických dílů, u kterých se moc nenasmějete. Mám rád díl Ve víru vášně, ten je třeba moc milý a dojemný. A ještě Vánoce jsou solidní. Zbytek je spíš nuda. Hezkej večer je třeba dost primitivní epizoda. Hvězdné časy teprve měli přijít. + + + + + + () + +

+ + +
+
+

Reklama

+
+
+
+ +
+
+
+
+
+
+
+

MM_Ramone 

+
+ všechny recenze uživatele + (k tomuto seriálu) + +
+
+ + +

+ + 01. Vánoce u Simpsonových, 02. Malý génius, 03. Homerova odysea, 04. Taková nenormální rodinka, 05. Bart generálem aneb Kdopak by se Nelsona bál, 06. Smutná Líza, 07. Volání přírody, 08. Mluvící hlava, 09. Ve víru vášně, 10. Světák Homer, 11. Kyselé hrozny sladké Francie, 12. Je Šáša vinen?, 13. Hezkej večer ***** + + + + + + () + +

+ + +
+
+ +
+
+
+
+

KuceraJohny 

+
+ všechny recenze uživatele + (k tomuto seriálu) + +
+
+ + +

+ + Po mnoho letech jsem si zopakoval celou první sérii a musím říct, že rozhodně nebude tou mou nejoblíbenější. První díl mám velmi rád a pak asi další tři, ale většina je zkrátka průměr, který neurazí ani nenadchne. Nechci být divákem, který bude hodnotit jen na základě toho, že tohle jsou přece ty začátky, ty originální Simpsonovi. Pro mě ne, já mám nejraději ty o pár let novější. První série ještě není zajetá a spíše zkouší. Jednotlivá místa, občas nevypadají tak jak si je pamatuji, postavy se v podstatě ještě neznají a o co hůř i když se o pár epizod před tím poznají, tak najednou se zase neznají. Je to takové nejisté, ale né nejhorší. Vždycky jsem se celkem bavil, ale míň než jsem u Simpsonových zvyklý. + + + + + + () + +

+ + +
+
+ +
+
+
+
+
+
+
+

Wyrdas 

+
+ všechny recenze uživatele + (k tomuto seriálu) + +
+
+ + +

+ + Nu, průměr všech hodnocení z první sezóny nám dává silné čtyři hvězdy. Každý díl jsem okomentoval ohledně poučení, které z něj vychází a seriózně přemýšlel nad tím, proč už jsem to někde viděl. Občas jsem si musel připomenout, že to až další série přehrávají ty samé skeče a zde je vše ještě liliově čisté. Mnoho hlášek z první série se stalo legendárními a nikdy je nezapomenu. Už jen za to všechna čest a sláva, která jak již víme, pomalinku ale jistě bude jen blednout. Zatím super. + + + + + + () + +

+ + +
+
+ +
+
+ +
+
+

Galerie (69)

+
+ VÍCE +
+
+
+
+ +
+
+

Zajímavosti (261)

+
+ VÍCE +
+
+
+
+
+
    +
  • +V úvodní znělce této epizody chybí záběr s Bartem u tabule a závěrečný gaučový gag. + + + (henrycruel) + + + +
  • +
+
+
+
+
+
    +
  • +V této epizodě se poprvé objevili Bartovi spolužáci Milhouse Van Houten a Martin Prince, Martinovi rodiče, učitelka Edna Krabappelová a Dr. James Loren Pryor. + + + (Duoscop) + + + +
  • +
+
+
+
+
+
    +
  • +Když se Homer přijde zeptat Barta, jak se měl ve škole, tak za sebou nechá otevřené dveře. V pozdějším záběru jsou ale zavřené a v dalším opět otevřené. + + + (Duoscop) + + + +
  • +
+
+
+
+
+ + +
+ + + +
+
+ + +
+ +
+
+

Reklama

+
+
+
+
+
+

Reklama

+
+
+
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+ + + + + + + + + + + +`; diff --git a/tests/mocks/series1-seasons.mock.ts b/tests/mocks/series1-seasons.mock.ts new file mode 100644 index 0000000..0582ee1 --- /dev/null +++ b/tests/mocks/series1-seasons.mock.ts @@ -0,0 +1,2552 @@ +export const serie1SeasonsMock = ` + + + + + + + Simpsonovi (1989) | ČSFD.cz + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+

Reklama

+
+
+
+ +
+
+
+

+ Change website language +

+

+ + Do not switch + +

+
+
+ + + + +
+ + + +
+ +
+
+
+ +
+
+
+ +
+
+

+ + Simpsonovi + +

+ (seriál) + + + + + + + + +
    +
  • +USA The Simpsons + + (více) + +
  • + +
+
+
+
+
+ + všechny plakáty + +
+ +
+
+
+
+
+ + + + Trailer + + + + +
+ +
+
+
+
+
+
+ +
+
+ + + +
USA, + (1989–2025), 292 h 52 min + (Minutáž: 20–45 min) + +
+ +
+ + +
+

Scénář:

+John Swartzwelder, Dan Greaney, David X. Cohen, Richard Appel (více) +
+ +
+

Hrají:

+Dan Castellaneta, Julie Kavner, Nancy Cartwright, Yeardley Smith, Harry Shearer, Hank Azaria, Marcia Wallace, Pamela Hayden, Tress MacNeille, Russi Taylor (více) +
+ + + + + (další profese) +
+
+
+
+
+ +
+ + +
+ + + +
+
+

+ Série(37) / Epizody(809) +

+
+
+
+ +
+ +
+
+ +
+
+

+ Obsahy(1) + + +

+
+
+
+ +
+ + +

+ Natvrdlý lenoch Homer, pedantská puťka Marge, drzounský Bart, přechytralá Líza a roztomilá Maggie – to jsou členové rodiny nejslavnějšího a nejpopulárnějšího animovaného seriálu Simpsonovi. Už dvacet let se jich miliony fanoušků na celém světě nemohou nabažit a v pravidelných intervalech nakukují do zákulisí legendárního Springfieldu, aby byli svědky neuvěřitelných dobrodružství bláznivé, ale přesto vlastně normální americké rodinky. Simpsonovi si získali své obecenstvo především neotřelým a trochu drsným humorem, s nímž vykreslují typickou americkou rodinu z městečka Springfield. Ta se skládá z pěti naprosto odlišných lidí, pro které je občas pořádně těžké se sebou vyjít. Nemluvě o ostatních obyvatelích města, v němž to opravdu neustále žije – je tu vlastní upadlá kultura, zkorumpovaná politika, drsná mafie a lenivá policie, ale také prostí občané, kteří se každý týden chodí kát do místního kostela. V neposlední řadě si mohou diváci všimnout jednoho všudypřítomného detailu – Simpsonovi totiž téměř v každé scénce něco nebo někoho parodují – ať už jde o svět filmový, hudební, politický, ale i náboženský. Tvůrci seriálu se zkrátka s velkou odvahou pouštějí do neustálých provokací osob a institucí z reálného života. + + + (TV Prima) + +

+ (více) +
+ +
+
+
+ + + +
+ + + +
+
+
+

Videa (5)

+
+ VÍCE +
+
+
+
+
+
+
+ + + + Trailer + + + + +
+ +
+
+
+
+
+
+ +
+
+

Recenze (2 499)

+
+ VÍCE +
+
+
+
+
+
+
+

Jacinda 

+
+ všechny recenze uživatele + + +
+
+ + +

+ + Tento seriál mám opravdu ráda a skvěle se u něj jezdí na rotopedu. přesto existuje jeden díl, který považuju za odpad. Je to díl 21x06 o Andy Hamiltonovi. Podle mě už fakt trochu moc. Navíc mám pocit, že jak jde seriál dál, stává se z Houmera až příliš velký idiot, který spíš než mě pobaví tak mě znechutí. taktéž Bart dělá věci, za které bych ho jako matka zmlátila na kaši. Uvidím jak dopadne 21. řada, nevím zda mě nedonutí snížit hodnocení + + + + + + () + +

+ + +
+
+ +
+
+
+
+

Beckett51 

+
+ všechny recenze uživatele + + +
+
+ + +

+ + " Marge mohu si předplatit časopis Hádanky? - Houmí, ten je pro děti. - Jak jsi na to přišla? - Přečti si zbytek názvu. - Hádanky pro...ouch!" Pro mě jeden z nejlepších seriálů vůbec.... kdyby ovšem nebylo trapnosti třech posledních sezon. EDIT: Nakonec pátou hvězdu zase přidávám, protože tvůrci se poučili a už to zase konečně stojí zato. + + + + + + () + +

+ + +
+
+

Reklama

+
+
+
+ +
+
+
+
+
+
+
+

Jara.Cimrman.jr 

+
+ všechny recenze uživatele + (k tomuto seriálu) + +
+
+ + +

+ + Jako vážený springfieldský rodák, nemanželský potomek Neda Flenderse a jakési žluté potvory, mohu hrdě prohlásit, že jsem viděl všechna povyražení geniálního Homera Simpsona a jeho milované rodiny. A jelikož předcházející tvrzení patrně není pravda, tak dávám pět hvězd jako omluvu sám sobě za to, že jsem se pitomě okradl o nějakou tu minutu skvěle satirické zábavy. + + + + + + () + +

+ + +
+
+ +
+
+
+
+

CANNIBAL 

+
+ všechny recenze uživatele + + +
+
+ + +

+ + Postupem času u nových řad seriál víceméně skomírá a vůbec se dějově nevyvíjí. Furt mi připadá, že nemá seriál vůbec stanovené cíle, kam by měl děj směřovat. Je to jenom stále pokus omyl. Homer nejprve je čím dál víc hloupější a zničeho nic zase ne. Nebo když vidím jak jsou tvůrci zoufalí a snaží se Simpsonovi promíchat mizernou a stupidní Futuramu. Vtipy už často vyprchávají do ztracena. Když se navíc podívám, jaká že to armáda režisérů a scénáristů na tom maká. Musím být hodně kritický. Vyzařuji tedy z top a sundavám hvězdu dolů a doufám, že v tom nebudu muset dál pokračovat. Měli by si tvůrci uvědomit co vlastně chtějí, anebo ať včas "zavřou krám". + + + + + + () + +

+ + +
+
+ +
+
+
+
+
+
+
+

Djoker 

+
+ všechny recenze uživatele + + +
+
+ + +

+ + Simpsonovi se hodnotí těžko. Mají své zlaté období a většina hodnotících včetně mě na nich vyrostla, takže málokdo zohlední letitou stagnaci a v posledních sériích i naprostou absenci humoru. Po čase jsem zkusil jeden díl a bylo to k nevydržení. Pokud má někde na hodnocení vliv především nostalgie diváků, tak je to právě zde. + + + + + + () + +

+ + +
+
+ +
+
+ +
+
+

Galerie (3 648)

+
+ VÍCE +
+
+
+
+ +
+
+

Zajímavosti (4 450)

+
+ VÍCE +
+
+
+
+
+
    +
  • +V roce 2014 vyšla tzv. "Homerova kniha" o životě a názorech Homera Simpsona. Na ní navazovaly knihy v podobném duchu "Bartova kniha", "Lízina kniha" a "Margina kniha". Simpsonovi vychází již několik let také jako komiks a mají dokonce svůj vlastní časopis. + + + (Pierre) + + + +
  • +
+
+
+
+
+
    +
  • +Protože se některým divákům přestávalo líbit Homerovo škrcení Barta, rozhodli se tvůrci seriálu, že už Homer se škrcením přestane. Stalo se tak na sklonku roku 2023. + + + (User1854) + + + +
  • +
+
+
+
+
+
    +
  • +Na počátku seriálu Simpsonovi stál komiks "Život v Pekle" Matta Groeninga, který zaujal hollywoodského producenta Jamese L. Brookse. Ten se na Groeninga obrátil s návrhem vytvořit sérii krátkých animovaných skečů na motivy jeho komiksu. Groening ale nakonec místo toho vytvořil úplně nové postavy, a tak vznikl seriál Simpsonovi. + + + (francimor19) + + + +
  • +
+
+
+
+
+ + +
+ + + +
+
+

Související novinky

+
+ více +
+
+
+
+
+
+
+
+
+ + Simpsonovi opět míří do filmu + +
+
+

+ Simpsonovi opět míří do filmu +

+
+ 29.09.2025 +
+
+
+
+

+ Chyběla vám nejznámější animovaná televizní rodina? Budiž pro vás dobrou zprávou, že po dvaceti letech bude v kinech opět pořádně žluto! Legendární animovaní Simpsonovi z dílny Matta Groeninga se… + (více) +

+
+
+
+
+
+
+
+
+
+ + Disney+ odstartuje v Česku 14. června + +
+
+

+ Disney+ odstartuje v Česku 14. června +

+
+ 29.03.2022 +
+
+
+
+

+ A je to oficiální. Společnost The Walt Disney Company potvrdila, že streamovací služba Disney+ dorazí do Česka a na Slovensko 14. června, základní měsíční předplatné bude stát 199 Kč, roční pak vyjde… + (více) +

+
+
+
+
+
+
+
+
+
+ + Zemřela herečka Betty White + +
+
+

+ Zemřela herečka Betty White +

+
+ 01.01.2022 +
+
+
+
+

+ Zbožňovaná herečka a komediální ikona Betty White zemřela ve věku 99 let pár hodin před koncem roku 2021. Do stých narozenin jí přitom zbývalo už jen pár týdnů. Herečka byla poslední přeživší členkou… + (více) +

+
+
+
+
+
+
+
+
+
+ + Simpsonovi ztratí známý hlas + +
+
+

+ Simpsonovi ztratí známý hlas +

+
+ 21.01.2020 +
+
+
+
+

+ Postava Apua Nahasapeemapetilona z kultovního seriálu Simpsonovi prožila v posledních pár měsících svoji vlastní kontroverzi. Spousta lidí se totiž sekla na tom, že postava je de facto jen výčtem… + (více) +

+
+
+
+
+
+
+
+
+
+
+
+ + +
+ +
+
+

Reklama

+
+
+
+
+
+

Reklama

+
+
+
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+ + + + + + + + + + +`; diff --git a/tests/mocks/series2-episodes.mock.ts b/tests/mocks/series2-episodes.mock.ts new file mode 100644 index 0000000..348bfe8 --- /dev/null +++ b/tests/mocks/series2-episodes.mock.ts @@ -0,0 +1,1523 @@ +export const serie1Season1EpisodeMock = ` + + + + + + + The Curse (2023) | ČSFD.cz + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

Reklama

+
+
+
+ +
+
+
+

+ Change website language +

+

+ + Do not switch + +

+
+
+ + + + +
+ + + +
+ +
+
+ +
+
+ +
+
+

+ + The Curse + +

+ (seriál) + + + + + + + + +
+
+
+
+ + všechny plakáty + +
+ +
+
+
+
+
+ + + + Trailer + + + + +
+ +
+
+
+
+
+
+ +
+ +
+
+
+ +
+
+

VOD (1)

+ +
+ +
+ + + +
+
+

+ Epizody(10) +

+
+
+ +
+
+ +
+
+

+ Obsahy(1) + + +

+
+
+
+
+ + +

+ Údajná kletba naruší život novomanželského páru, který se snaží počít dítě a také natáčí nový pořad pro domácí kutily. + + + (SkyShowtime) + +

+
+ +
+
+
+ + + +
+ + + +
+
+
+

Videa (1)

+
+
+
+
+
+
+ + + + Trailer + + + + +
+ +
+
+
+
+
+
+ +
+
+

Recenze (28)

+
+ VÍCE +
+
+
+
+
+
+
+

mcb 

+
+ všechny recenze uživatele + + +
+
+ + +

+ + Více by tomu slušel půlhodinový formát, a tudíž poloviční stopáž, protože některé scény se přeci jen táhnou trochu déle, než by měly, ale za celý koncept seriál nemůžu jinak než plný počet. Prvotřídní zástupce (sub)subžánru „všednodenního“ hororu, který naposledy brilantně vytěžil Ari Aster v On se bojí, kombinuje prvotřídní cringe estetiku Fielderových non-fikčních pořadů, lynchovskou surreálně snovou poetiku Twin Peaks, hutné psychologické drama o manželské krizi ála Scény z manželského života a výstižnou kritiku současných hot topics (woke politika, diverzita, posedlost mediálním obrazem apod.), a ještě k tomu dokáže být silně návykový, i když se v něm toho moc po většinu stopáže neděje. The Curse nepustí hlavně díky neutuchající tenzi mezi jednotlivými záběry/scénami, která je vytvářena vychýlenou stísňující hudbou, autentickým herectvím a snímáním většiny akce v druhém plánu skrze objekty/odrazy, což vytváří nepříjemnou iluzi, jako bychom na veškeré dění nahlíželi z pozice skrytého pozorovatele/voyeura. Je také dalším úspěšným cvičením v zachycení trapnosti každodenního života, zde umocněného skrze téma konstruování falešných narativů skrze média, což jednak dělají postavy v seriálu, zároveň i jeho samotní tvůrci. Samotný konec a poslední epizoda je pak kapitola sama o sobě, přinášející ohlušující katarzi a zboření stěny polodokumentárního formátu. A24 skutečně jede neskutečné bomby a po loňském Ve při a několika nedávných filmech s velkou jistotou obhajuje pozici moderně smýšlejícího studia točícího áčkové, divácky přístupné tituly s vysokými production values, které však narušují a mnohdy překonávají veškerá žánrová očekávání a současné hollywoodské/televizní konvence. Nepopsatelně zdrcující zážitek. + + + + + + () + +

+ + +
+
+ +
+
+
+
+

M.i.k.e 

+
+ všechny recenze uživatele + + +
+
+ + +

+ + The Curse je sice přiznaná fikce, nicméně i tak zvládne obsahovat spoustu prvků typických pro Fieldrovu tvorbu a v mnoha ohledech je dokáže posouvat ještě mnohem dál. Žanry se tu míchají jak o život a díky tomu je tak tahle série chvilku vztahové drama, chvilku je vtipná, pak zase trapná nebo nepříjemná a celé to rámuje satirický tón beroucí si na paškál současné woke pokrytectví. The Curse rozhodně nesedne všem, ale ten kdo jí dá šanci a přistoupí na Fieldrovu a Safdieho hru, ten si nakonec odnese specifikcý zážitek a je jen na něm jak si ho po překvapivém a povedeném závěru interpretuje…80% + + + + + + () + +

+ + +
+
+

Reklama

+
+
+
+ +
+
+
+
+
+
+
+

Slasher 

+
+ všechny recenze uživatele + + +
+
+ + +

+ + Tak si říkám, že obsahově vzato jsem se mohl unudit už někde u 3. epizody, ale prostě ten experimentální styl, diskomfortní pocity a budování napětí, krásně dysfunkční vztahy, povrchní postavy odtržené od (ne-)reality a sympaticky paranormální finále – to vše vyústilo v unikátní, divácky zajímavý zážitek, co má svoje vlastní kouzlo (podobně jako loňský Beef). Snad jen záběrů přes zaprasený sklo bych něco ubral. 80% + + + + + + () + +

+ + +
+
+ +
+
+
+
+

J*A*S*M 

+
+ všechny recenze uživatele + + +
+
+ + +

+ + Bingnul jsem celou sérii po twitterových WTF ohlasech na poslední část, a převládajícím výsledným pocitem je asi zmatenost spojená s rozmrzelostí. Má to spoustu záblesků briliance, ale zcela divácky uspokojen se necítím. Nejlíp to na mne fungovalo v momentech komediální satiry na virtue signalling, přetvářku na kamery a pokrytectví boháčů, ale těch žánrů a motivů má The Curse mnohem více, přičemž se mi nezdá, že by se všechny dočkaly uspokojivého vyústění. Poslední epizoda je zhruba od půlky fajn full bizár, ale když jí předchází devět hodin relativně při zemi se držícího normálního dramatu ... přijde mi to jako odraz domýšlivosti tvůrců, té stejné domýšlivost, jakou trpí hlavní hrdinovů seriálu. + + + + + + () + +

+ + +
+
+ +
+
+
+
+
+
+
+

Oralfabet 

+
+ všechny recenze uživatele + (k tomuto seriálu) + +
+
+ + +

+ + Wow, včera jsem viděl poslední epizodu a pořád ještě zpracovávám své dojmy. Nathan Fielder a Benny Safdie společně stvořili něco naprosto unikátního. Má to v sobě jasně rozpoznatelné prvky jejich předchozích projektů, ale zároveň i něco úplně nového, stěží popsatelného... Je to nepříjemné, trapné, napínavé, tragické, vtipné i děsivé. Je to společenská satira, utahující si z fake woke liberalismu a pokrytectví celebrit na sociálních sítích, dekonstrukce reality TV i reality samotné, situační cringe komedie, Lynchovský surrealistický horor, vztahové drama a Bůh ví, co ještě... Je to padesát různých věcí najednou, ale zároveň soustředěný, koherentní projekt. Čemuž napomáhají i přesné herecké výkony, obzvlášť musím vyzdvihnout Emmu Stone, která mi v roce 2023 potvrdila, že je jednou z nejodvážnějších a nejtalentovanějších současných hereček. Bella z Poor Things je nejspíš její životní rolí, ale Whitney tady taky ztvárnila naprosto mistrně. Nathana musím mimochodem taky pochválit, sice se asi dá říct, že hraje nějakou postavu ve všech svých projektech, ale tady poprvé fakt HRAJE, a dovolím si trvdit, že za Emmou zase až tak výrazně nezaostává. Nesedne to každýmu, na to je to až moc specifické a nepříjemné, ale mě to přišlo absolutně fascinující a snad i geniální. 10/10 + + + + + + () + +

+ + +
+
+ +
+
+ +
+
+

Galerie (236)

+
+ VÍCE +
+
+
+
+ +
+
+

Zajímavosti (2)

+
+
+
+
+
    +
  • +Natáčanie prebiehalo v Novom Mexiku v meste Santa Fe a okolí, najmä v bare Dragon Room v Pink Adobe a v historických častiach Loretto Chapel a Santa Fe Plaza. + + + (F2003F) + + + +
  • +
+
+
+
+
+
    +
  • +Seriál byl inspirován incidentem, kdy Nathana Fieldera (Asher Siegel) oslovila žebračka v Los Angeles. Poté, co Fielder odmítl dát ženě peníze, řekla mu: „Proklínám tě.“ Fielder byl jejími slovy tak otřesen, že šel k bankomatu a vybral 20 dolarů. Poté, co dal ženě peníze, zeptal se, zda byla kletba zlomena. Řekla, že ano. + + + (pórek) + + + +
  • +
+
+
+
+
+ + +
+ + + +
+
+

Související novinky

+
+
+
+
+
+
+
+
+ + Co dalšího chystá Nolan a jaké filmy si loni oblíbil? + +
+
+

+ Co dalšího chystá Nolan a jaké filmy si loni oblíbil? +

+
+ 01.02.2024 +
+
+
+
+

+ Když jste jeden z nejúspěšnějších a zároveň nejoceňovanějších filmařů světa, máte tu výsadu, že vaše vyjádření ohledně kinematografie sleduje a dá na ně dost široká obec. V nedávné době tak tvůrce… + (více) +

+
+
+
+
+
+
+
+
+
+
+
+ + +
+ +
+
+

Reklama

+
+
+
+
+
+

Reklama

+
+
+
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+ + + + + + + + + + + +`; From b1068a646d8be3210554e00a034ea905b91fa7fa Mon Sep 17 00:00:00 2001 From: BART! Date: Tue, 2 Dec 2025 23:32:57 +0100 Subject: [PATCH 05/12] test(series): seasons and episodes [kickoff] --- tests/mocks/series2-episodes.mock.ts | 2 +- tests/series.test.ts | 362 +++++++++++++++++++++++++++ 2 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 tests/series.test.ts diff --git a/tests/mocks/series2-episodes.mock.ts b/tests/mocks/series2-episodes.mock.ts index 348bfe8..6eec8db 100644 --- a/tests/mocks/series2-episodes.mock.ts +++ b/tests/mocks/series2-episodes.mock.ts @@ -1,4 +1,4 @@ -export const serie1Season1EpisodeMock = ` +export const serie2EpisodesMock = ` diff --git a/tests/series.test.ts b/tests/series.test.ts new file mode 100644 index 0000000..2c93139 --- /dev/null +++ b/tests/series.test.ts @@ -0,0 +1,362 @@ +import { parse } from 'node-html-parser'; +import { beforeAll, describe, expect, test } from 'vitest'; +import { CSFDMovie } from '../src/dto/movie'; +import { MovieScraper } from '../src/services/movie.service'; +import { serie1Season1EpisodeMock } from './mocks/series1-season1-episode.mock'; +import { serie1Season1Mock } from './mocks/series1-season1.mock'; +import { serie1SeasonsMock } from './mocks/series1-seasons.mock'; +import { serie2EpisodesMock } from './mocks/series2-episodes.mock'; + +/** + * Helper function to parse mock HTML into CSFDMovie object + */ +function parseMovieMock(mockHtml: string, movieId: number): CSFDMovie { + const movieHtml = parse(mockHtml); + const pageClasses = movieHtml.querySelector('.page-content')?.classNames.split(' ') || []; + const asideNode = movieHtml.querySelector('.aside-movie-profile'); + const movieNode = movieHtml.querySelector('.main-movie-profile'); + const jsonLd = movieHtml.querySelector('script[type="application/ld+json"]')?.innerText || '{}'; + + if (!asideNode || !movieNode) { + throw new Error('Invalid mock HTML structure'); + } + + const scraper = new MovieScraper(); + // Access the private buildMovie method via reflection + return (scraper as any).buildMovie(movieId, movieNode, asideNode, pageClasses, jsonLd, {}); +} + +/** + * Series Pattern 1: Series with Seasons + * Structure: Series -> Seasons -> Episodes + * Example: The Simpsons (72489) + * - Main page shows seasons overview + * - Season page shows episodes list + * - Episode page shows individual episode details + */ +describe('Series Pattern 1: Series with Seasons (The Simpsons)', () => { + describe('Main Series Page - Seasons Overview', () => { + let movie: CSFDMovie; + + beforeAll(() => { + movie = parseMovieMock(serie1SeasonsMock, 72489); + }); + + test('Should have correct title', () => { + expect(movie.title).toBe('Simpsonovi'); + }); + + test('Should be type "seriál"', () => { + expect(movie.type).toBe('seriál'); + }); + + test('Should have year range', () => { + expect(movie.year).toBe(1989); + }); + + test('Should have seasons list', () => { + expect(movie.seasons).toBeDefined(); + expect(movie.seasons).not.toBeNull(); + expect(movie.seasons!.length).toBeGreaterThan(0); + }); + + test('Should NOT have episodes on main page', () => { + expect(movie.episodes).toBeNull(); + }); + + test('Should have correct season structure', () => { + const firstSeason = movie.seasons![0]; + expect(firstSeason).toHaveProperty('id'); + expect(firstSeason).toHaveProperty('name'); + expect(firstSeason).toHaveProperty('url'); + expect(firstSeason).toHaveProperty('info'); + }); + + test('Should have rating', () => { + expect(movie.rating).toBeGreaterThan(90); + }); + + test('Should have creators', () => { + expect(movie.creators.directors.length).toBeGreaterThan(0); + expect(movie.creators.actors.length).toBeGreaterThan(0); + }); + + test('Should have genres', () => { + expect(movie.genres).toContain('Animovaný'); + expect(movie.genres).toContain('Komedie'); + }); + + test('Should NOT have parent (it is the main series)', () => { + expect(movie.parent).toBeNull(); + }); + + test('Should NOT have episodeCode', () => { + expect(movie.episodeCode).toBeNull(); + }); + + test('Should NOT have seasonName', () => { + expect(movie.seasonName).toBeNull(); + }); + }); + + describe('Season Page - Episodes List', () => { + let movie: CSFDMovie; + + beforeAll(() => { + movie = parseMovieMock(serie1Season1Mock, 474212); + }); + + test('Should have correct title with season', () => { + expect(movie.title).toBe('Simpsonovi'); + }); + + test('Should have seasonName', () => { + expect(movie.seasonName).toBe('Série 1'); + }); + + test('Should be type "série"', () => { + expect(movie.type).toBe('série'); + }); + + test('Should have episodes list', () => { + expect(movie.episodes).toBeDefined(); + expect(movie.episodes).not.toBeNull(); + expect(movie.episodes!.length).toBeGreaterThan(0); + }); + + test('Should have correct episode structure', () => { + const firstEpisode = movie.episodes![0]; + expect(firstEpisode).toHaveProperty('id'); + expect(firstEpisode).toHaveProperty('name'); + expect(firstEpisode).toHaveProperty('url'); + expect(firstEpisode.name).toContain('Vánoce u Simpsonových'); + }); + + test('Should have parent series info', () => { + expect(movie.parent).toBeDefined(); + expect(movie.parent).not.toBeNull(); + expect(movie.parent!.series).toBeDefined(); + expect(movie.parent!.series.id).toBe(474212); + expect(movie.parent!.series.name).toBe('Simpsonovi'); + // Season page doesn't have a parent season, only parent series + expect(movie.parent!.season).toBeNull(); + }); + + test('Should NOT have seasons on season page', () => { + expect(movie.seasons).toBeNull(); + }); + + test('Should have rating', () => { + expect(movie.rating).toBeGreaterThan(85); + }); + + test('Should have year', () => { + expect(movie.year).toBe(1989); + }); + }); + + describe('Episode Page - Individual Episode', () => { + let movie: CSFDMovie; + + beforeAll(() => { + movie = parseMovieMock(serie1Season1EpisodeMock, 474220); + }); + + test('Should have correct title', () => { + expect(movie.title).toBe('Mluvící hlava'); + }); + + test('Should be type "epizoda"', () => { + expect(movie.type).toBe('epizoda'); + }); + + test('Should have episodeCode', () => { + expect(movie.episodeCode).toBe('S01E08'); + }); + + test('Should have parent with both series and season', () => { + expect(movie.parent).toBeDefined(); + expect(movie.parent).not.toBeNull(); + + // Series info + expect(movie.parent!.series).toBeDefined(); + expect(movie.parent!.series.id).toBe(72489); + expect(movie.parent!.series.name).toBe('Simpsonovi'); + + // Season info + expect(movie.parent!.season).toBeDefined(); + expect(movie.parent!.season.id).toBe(474212); + expect(movie.parent!.season.name).toBe('Série 1'); + }); + + test('Should have rating', () => { + expect(movie.rating).toBeGreaterThan(80); + }); + + test('Should have year', () => { + expect(movie.year).toBe(1990); + }); + + test('Should have duration', () => { + expect(movie.duration).toBe(22); + }); + + test('Should have creators', () => { + expect(movie.creators.directors.length).toBeGreaterThan(0); + expect(movie.creators.actors.length).toBeGreaterThan(0); + }); + + test('Should NOT have seasons', () => { + expect(movie.seasons).toBeNull(); + }); + + test('Should NOT have episodes', () => { + expect(movie.episodes).toBeNull(); + }); + + test('Should have descriptions', () => { + expect(movie.descriptions.length).toBeGreaterThan(0); + expect(movie.descriptions[0]).toContain('Bart'); + }); + }); +}); + +/** + * Series Pattern 2: Series with Direct Episodes + * Structure: Series -> Episodes (no season grouping) + * Example: The Curse (1431651) + * - Main page shows episodes directly + * - No intermediate season pages + */ +describe('Series Pattern 2: Series with Direct Episodes (The Curse)', () => { + let movie: CSFDMovie; + + beforeAll(() => { + movie = parseMovieMock(serie2EpisodesMock, 1431651); + }); + + test('Should have correct title', () => { + expect(movie.title).toBe('The Curse'); + }); + + test('Should be type "seriál"', () => { + expect(movie.type).toBe('seriál'); + }); + + test('Should have year', () => { + expect(movie.year).toBe(2023); + }); + + test('Should have episodes list directly', () => { + expect(movie.episodes).toBeDefined(); + expect(movie.episodes).not.toBeNull(); + expect(movie.episodes!.length).toBeGreaterThan(0); + }); + + test('Should have correct episode structure', () => { + const firstEpisode = movie.episodes![0]; + expect(firstEpisode).toHaveProperty('id'); + expect(firstEpisode).toHaveProperty('name'); + expect(firstEpisode).toHaveProperty('url'); + expect(firstEpisode.name).toContain('Kouzelná země'); + }); + + test('Should NOT have seasons', () => { + expect(movie.seasons).toBeNull(); + }); + + test('Should NOT have parent (it is the main series)', () => { + expect(movie.parent).toBeNull(); + }); + + test('Should NOT have episodeCode', () => { + expect(movie.episodeCode).toBeNull(); + }); + + test('Should NOT have seasonName', () => { + expect(movie.seasonName).toBeNull(); + }); + + test('Should have rating', () => { + expect(movie.rating).toBeGreaterThan(60); + }); + + test('Should have creators', () => { + expect(movie.creators.directors.length).toBeGreaterThan(0); + expect(movie.creators.actors.length).toBeGreaterThan(0); + }); + + test('Should have genres', () => { + expect(movie.genres).toContain('Komedie'); + expect(movie.genres).toContain('Drama'); + }); + + test('Should have VOD services', () => { + expect(movie.vod.length).toBeGreaterThan(0); + }); + + test('Should have descriptions', () => { + expect(movie.descriptions.length).toBeGreaterThan(0); + }); + + test('Episode info should include episode code', () => { + const episodes = movie.episodes!; + const firstEpisode = episodes[0]; + + // Check if info contains episode code pattern + if (firstEpisode.info) { + expect(firstEpisode.info).toMatch(/E\d+/); + } + }); +}); + +/** + * Cross-pattern validation tests + */ +describe('Series Patterns - Common Validations', () => { + test('All series should have valid IDs', () => { + const series1 = parseMovieMock(serie1SeasonsMock, 72489); + const series2 = parseMovieMock(serie2EpisodesMock, 1431651); + + expect(series1.id).toBeGreaterThan(0); + expect(series2.id).toBeGreaterThan(0); + }); + + test('All series should have valid URLs', () => { + const series1 = parseMovieMock(serie1SeasonsMock, 72489); + const series2 = parseMovieMock(serie2EpisodesMock, 1431651); + + expect(series1.url).toContain('/film/'); + expect(series2.url).toContain('/film/'); + }); + + test('Series with seasons should have mutually exclusive seasons/episodes', () => { + const mainSeries = parseMovieMock(serie1SeasonsMock, 72489); + const seasonPage = parseMovieMock(serie1Season1Mock, 474212); + + // Main series page should have seasons, not episodes + expect(mainSeries.seasons).not.toBeNull(); + expect(mainSeries.episodes).toBeNull(); + + // Season page should have episodes, not seasons + expect(seasonPage.episodes).not.toBeNull(); + expect(seasonPage.seasons).toBeNull(); + }); + + test('Episode should have parent references', () => { + const episode = parseMovieMock(serie1Season1EpisodeMock, 474220); + + expect(episode.parent).not.toBeNull(); + expect(episode.parent!.series).toBeDefined(); + expect(episode.parent!.season).toBeDefined(); + expect(episode.episodeCode).not.toBeNull(); + }); + + test('Series without seasons should have episodes directly', () => { + const series = parseMovieMock(serie2EpisodesMock, 1431651); + + expect(series.episodes).not.toBeNull(); + expect(series.seasons).toBeNull(); + expect(series.type).toBe('seriál'); + }); +}); From 7ba205b5dbff0cf148fe9b8423f51000f67284e9 Mon Sep 17 00:00:00 2001 From: BART! Date: Tue, 2 Dec 2025 23:45:45 +0100 Subject: [PATCH 06/12] fix(series): parse seasons --- src/helpers/movie.helper.ts | 9 +- tests/mocks/series2-episode.mock.ts | 1234 +++++++++++++++++++++++++++ 2 files changed, 1241 insertions(+), 2 deletions(-) create mode 100644 tests/mocks/series2-episode.mock.ts diff --git a/src/helpers/movie.helper.ts b/src/helpers/movie.helper.ts index 544e021..92334ce 100644 --- a/src/helpers/movie.helper.ts +++ b/src/helpers/movie.helper.ts @@ -395,7 +395,12 @@ export const getSeasonorEpisodeParent = ( el: HTMLElement, serie?: { id: number; name: string } ): CSFDParent | null => { - const parents = el.querySelectorAll('.film-header h2 a'); + // Try h2 first (for episodes), then h1 (for seasons) + let parents = el.querySelectorAll('.film-header h2 a'); + if (parents.length === 0) { + parents = el.querySelectorAll('.film-header h1 a'); + } + if (parents.length === 0) { if (!serie) return null; return { series: serie, season: null }; @@ -404,7 +409,7 @@ export const getSeasonorEpisodeParent = ( const [parentSeries, parentSeason] = parents; const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href')); - const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href')); + const seasonId = parseLastIdFromUrl(parentSeason?.getAttribute('href') || ''); const seriesName = parentSeries?.textContent?.trim() || null; const seasonName = parentSeason?.textContent?.trim() || null; diff --git a/tests/mocks/series2-episode.mock.ts b/tests/mocks/series2-episode.mock.ts new file mode 100644 index 0000000..c02aea3 --- /dev/null +++ b/tests/mocks/series2-episode.mock.ts @@ -0,0 +1,1234 @@ +export const serie2EpisodeMock = ` + + + + + + + The Curse - Kouzelná země (E01) (2023) | ČSFD.cz + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

Reklama

+
+
+
+ +
+
+
+

+ Change website language +

+

+ + Do not switch + +

+
+
+ + + + +
+ + + +
+ +
+
+ +
+
+ +
+

+ The Curse +

+
+
+

+ + Kouzelná země + (E01) +

+ (epizoda) + + +
+ další +
+ + + + + + +
    +
  • +USA Land of Enchantment +
  • +
+
+
+
+ + +
+
+ + + + + + více + + +
+ + + + + +
+
+
+
+
+
+
+
+ 70% + +
+ +
+ + + + Hodnocení a fanklub + + +
+ + +
USA, + 2023, 1 h 1 min + + +
+ + +
+
+
+
+ +
+
+

VOD (1)

+ +
+ +
+ + + + +
+
+

+ Obsahy(1) + + +

+
+
+
+
+ + +

+ Novomanželé Whitney a Asher Siegelovi se rozhodnou natočit pořad pro domácí kutily. + + + (SkyShowtime) + +

+
+ +
+
+
+ + + +
+ + + +
+
+ +
+
+ +
+
+

Recenze (3)

+
+
+
+
+
+
+

SpaceOdyseus 

+
+ všechny recenze uživatele + + +
+
+ + +

+ + Cringe on purpose? Ok, ale moc nevím, co z toho vyleze a jestli to chci zjišťovat v dalších epizodách. + + + + + + () + +

+ + +
+
+ +
+
+
+
+

William_ 

+
+ všechny recenze uživatele + (k tomuto seriálu) + +
+
+ + +

+ + Úvodní hodinka si mě teda získala. Otevírá to spoustu společenských témat a kritiku levičáckých elit, které se snaží hrát na otevřené lidi, ale přitom myslí jen samy na sebe. Tenhle seriál by měli pouštět v Holešovicích v místních podnicích. Nacházím v něm totiž spoustu podobného především v chování a myšlení tamních "elit". + + + + + + () + +

+ + +
+
+

Reklama

+
+
+
+ + + +
+
+ +
+
+

Galerie (42)

+
+ VÍCE +
+
+
+
+ + + +
+ + + +
+
+ + +
+ +
+
+

Reklama

+
+
+
+
+
+

Reklama

+
+
+
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+ + + + + + + + + + +`; From 849399a46ea1607cb683a1402912f8c2a8d1ad0e Mon Sep 17 00:00:00 2001 From: BART! Date: Tue, 2 Dec 2025 23:52:54 +0100 Subject: [PATCH 07/12] test(season): episode + seasons --- tests/movie.test.ts | 16 --------- tests/series.test.ts | 80 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 17 deletions(-) diff --git a/tests/movie.test.ts b/tests/movie.test.ts index d981417..697370a 100644 --- a/tests/movie.test.ts +++ b/tests/movie.test.ts @@ -666,22 +666,6 @@ describe('Get Episode Code', () => { }); describe('Get Season or Episode Parent', () => { - test('With parents', () => { - const html = parse(` -
-

- Series - Season -

-
- `); - const result = getSeasonorEpisodeParent(html as any); - expect(result).toEqual({ - series: { id: 123, name: 'Series' }, - season: { id: 456, name: 'Season' } - }); - }); - test('No parents but serie provided', () => { const html = parse('
'); const serie = { id: 123, name: 'Series' }; diff --git a/tests/series.test.ts b/tests/series.test.ts index 2c93139..92850bf 100644 --- a/tests/series.test.ts +++ b/tests/series.test.ts @@ -5,6 +5,7 @@ import { MovieScraper } from '../src/services/movie.service'; import { serie1Season1EpisodeMock } from './mocks/series1-season1-episode.mock'; import { serie1Season1Mock } from './mocks/series1-season1.mock'; import { serie1SeasonsMock } from './mocks/series1-seasons.mock'; +import { serie2EpisodeMock } from './mocks/series2-episode.mock'; import { serie2EpisodesMock } from './mocks/series2-episodes.mock'; /** @@ -136,7 +137,7 @@ describe('Series Pattern 1: Series with Seasons (The Simpsons)', () => { expect(movie.parent).toBeDefined(); expect(movie.parent).not.toBeNull(); expect(movie.parent!.series).toBeDefined(); - expect(movie.parent!.series.id).toBe(474212); + expect(movie.parent!.series.id).toBe(72489); expect(movie.parent!.series.name).toBe('Simpsonovi'); // Season page doesn't have a parent season, only parent series expect(movie.parent!.season).toBeNull(); @@ -310,6 +311,83 @@ describe('Series Pattern 2: Series with Direct Episodes (The Curse)', () => { }); }); +/** + * Series Pattern 2 Episode: Episode from Series without Seasons + * Structure: Series -> Episode (no season) + * Example: The Curse Episode 1 (1436408) + * - Episode has only series as parent, no season + */ +describe('Series Pattern 2 Episode: Episode without Season (The Curse Episode)', () => { + let movie: CSFDMovie; + + beforeAll(() => { + movie = parseMovieMock(serie2EpisodeMock, 1436408); + }); + + test('Should have correct title', () => { + expect(movie.title).toBe('Kouzelná země'); + }); + + test('Should be type "epizoda"', () => { + expect(movie.type).toBe('epizoda'); + }); + + test('Should have episodeCode', () => { + expect(movie.episodeCode).toBe('E01'); + }); + + test('Should have parent series but NO season', () => { + expect(movie.parent).toBeDefined(); + expect(movie.parent).not.toBeNull(); + + // Series info + expect(movie.parent!.series).toBeDefined(); + expect(movie.parent!.series.id).toBe(1431651); + expect(movie.parent!.series.name).toBe('The Curse'); + + // NO season info (this series doesn't have seasons) + expect(movie.parent!.season).toBeNull(); + }); + + test('Should have rating', () => { + expect(movie.rating).toBeGreaterThan(60); + }); + + test('Should have year', () => { + expect(movie.year).toBe(2023); + }); + + test('Should have duration', () => { + expect(movie.duration).toBe(61); + }); + + test('Should have creators', () => { + expect(movie.creators.directors.length).toBeGreaterThan(0); + expect(movie.creators.actors.length).toBeGreaterThan(0); + }); + + test('Should NOT have seasons', () => { + expect(movie.seasons).toBeNull(); + }); + + test('Should NOT have episodes', () => { + expect(movie.episodes).toBeNull(); + }); + + test('Should have descriptions', () => { + expect(movie.descriptions.length).toBeGreaterThan(0); + }); + + test('Should have VOD services', () => { + expect(movie.vod.length).toBeGreaterThan(0); + }); + + test('Should have genres', () => { + expect(movie.genres).toContain('Komedie'); + expect(movie.genres).toContain('Drama'); + }); +}); + /** * Cross-pattern validation tests */ From 3c7915a2f22a21c039e9a7089b1658145e9063aa Mon Sep 17 00:00:00 2001 From: BART! Date: Tue, 2 Dec 2025 23:59:06 +0100 Subject: [PATCH 08/12] test(season): live fetch seasons and episodes --- tests/fetchers.test.ts | 107 +++++++++++++++++++++++++++++++++++------ 1 file changed, 92 insertions(+), 15 deletions(-) diff --git a/tests/fetchers.test.ts b/tests/fetchers.test.ts index 714ce01..2f2f43b 100644 --- a/tests/fetchers.test.ts +++ b/tests/fetchers.test.ts @@ -159,25 +159,102 @@ describe('Live: Movie page. Fetch `10135-forrest-gump`', () => { }); }); -describe('Live: Tv series', () => { - let movie: CSFDMovie = {} as CSFDMovie; - beforeAll(async () => { - movie = await csfd.movie(71924); - }); - test('Year', () => { - expect(movie.year).toEqual(1994); +describe('Live: Series Patterns', () => { + // Pattern 1: Series with Seasons (The Simpsons) + describe('Series with Seasons (The Simpsons)', () => { + let movie: CSFDMovie; + beforeAll(async () => { + movie = await csfd.movie(72489); + }); + test('Type and Title', () => { + expect(movie.type).toEqual('seriál'); + expect(movie.title).toEqual('Simpsonovi'); + }); + test('Seasons', () => { + expect(movie.seasons).toBeDefined(); + expect(movie.seasons!.length).toBeGreaterThan(20); + }); + test('No Episodes on main page', () => { + expect(movie.episodes).toBeNull(); + }); }); - test('Type', () => { - expect(movie.type).toEqual('seriál'); + + // Pattern 1: Season Page (The Simpsons S01) + describe('Season Page (The Simpsons S01)', () => { + let movie: CSFDMovie; + beforeAll(async () => { + movie = await csfd.movie(474212); + }); + test('Type and Title', () => { + expect(movie.type).toEqual('série'); + expect(movie.title).toEqual('Simpsonovi'); + expect(movie.seasonName).toEqual('Série 1'); + }); + test('Parent Series', () => { + expect(movie.parent?.series?.id).toEqual(72489); + expect(movie.parent?.season).toBeNull(); + }); + test('Episodes', () => { + expect(movie.episodes).toBeDefined(); + expect(movie.episodes!.length).toBeGreaterThan(0); + }); }); - test('Title', () => { - expect(movie.title).toEqual('Království'); + + // Pattern 1: Episode Page (The Simpsons S01E08) + describe('Episode Page (The Simpsons S01E08)', () => { + let movie: CSFDMovie; + beforeAll(async () => { + movie = await csfd.movie(474220); + }); + test('Type and Title', () => { + expect(movie.type).toEqual('epizoda'); + expect(movie.title).toEqual('Mluvící hlava'); + }); + test('Episode Code', () => { + expect(movie.episodeCode).toEqual('S01E08'); + }); + test('Parents', () => { + expect(movie.parent?.series?.id).toEqual(72489); + expect(movie.parent?.season?.id).toEqual(474212); + }); }); - test('Duration', () => { - expect(movie.duration).toBeGreaterThan(50); + + // Pattern 2: Series without Seasons (The Curse) + describe('Series without Seasons (The Curse)', () => { + let movie: CSFDMovie; + beforeAll(async () => { + movie = await csfd.movie(1431651); + }); + test('Type and Title', () => { + expect(movie.type).toEqual('seriál'); + expect(movie.title).toEqual('The Curse'); + }); + test('Episodes directly', () => { + expect(movie.episodes).toBeDefined(); + expect(movie.episodes!.length).toBeGreaterThan(0); + }); + test('No Seasons', () => { + expect(movie.seasons).toBeNull(); + }); }); - test('Fetch not number', async () => { - await expect(csfd.movie('test' as any)).rejects.toThrow(Error); + + // Pattern 2: Episode without Season (The Curse E01) + describe('Episode without Season (The Curse E01)', () => { + let movie: CSFDMovie; + beforeAll(async () => { + movie = await csfd.movie(1436408); + }); + test('Type and Title', () => { + expect(movie.type).toEqual('epizoda'); + expect(movie.title).toEqual('Kouzelná země'); + }); + test('Episode Code', () => { + expect(movie.episodeCode).toEqual('E01'); + }); + test('Parents', () => { + expect(movie.parent?.series?.id).toEqual(1431651); + expect(movie.parent?.season).toBeNull(); + }); }); }); From d96f91ff607112c3bcf23a58e5b070f31ee7d024 Mon Sep 17 00:00:00 2001 From: BART! Date: Wed, 3 Dec 2025 00:06:04 +0100 Subject: [PATCH 09/12] fix(season): types --- src/dto/movie.ts | 9 +++------ src/helpers/movie.helper.ts | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/dto/movie.ts b/src/dto/movie.ts index 6ee36ca..d64a8ab 100644 --- a/src/dto/movie.ts +++ b/src/dto/movie.ts @@ -25,8 +25,8 @@ export interface CSFDMovie extends CSFDScreening { } export interface CSFDParent { - season: { id: number; name: string; }; - series: { id: number; name: string; }; + season: { id: number; name: string } | null; + series: { id: number; name: string } | null; } export interface MovieJsonLd { @@ -102,8 +102,6 @@ export interface CSFDMovieListItem { url: string; } - - export type CSFDGenres = | 'Akční' | 'Animovaný' @@ -163,7 +161,6 @@ export type CSFDCreatorGroups = | 'Scénografie' | 'Kostýmy'; - export type CSFDCreatorGroupsEnglish = | 'Directed by' | 'Screenplay' @@ -205,4 +202,4 @@ export interface CSFDSeason { name: string; url: string; info: string | null; -} \ No newline at end of file +} diff --git a/src/helpers/movie.helper.ts b/src/helpers/movie.helper.ts index 92334ce..a5bd881 100644 --- a/src/helpers/movie.helper.ts +++ b/src/helpers/movie.helper.ts @@ -111,7 +111,7 @@ export const getMovieId = (el: HTMLElement): number => { export const getSerieasAndSeasonTitle = ( el: HTMLElement -): { seriesName: string; seasonName: string | null } => { +): { seriesName: string | null; seasonName: string | null } => { const titleElement = el.querySelector('h1'); if (!titleElement) { return { seriesName: null, seasonName: null }; From fd9537428fad8890c70e613ed59e11a83f8dba1f Mon Sep 17 00:00:00 2001 From: BART! Date: Tue, 24 Feb 2026 10:49:36 +0100 Subject: [PATCH 10/12] test(series): update mocks --- tests/mocks/series1-season1-episode.mock.ts | 376 ++++++++----- tests/mocks/series1-season1.mock.ts | 330 +++++++----- tests/mocks/series1-seasons.mock.ts | 562 +++++++++++--------- tests/mocks/series2-episode.mock.ts | 253 +++++---- tests/mocks/series2-episodes.mock.ts | 347 ++++++------ 5 files changed, 1074 insertions(+), 794 deletions(-) diff --git a/tests/mocks/series1-season1-episode.mock.ts b/tests/mocks/series1-season1-episode.mock.ts index 4373ecc..8f936ec 100644 --- a/tests/mocks/series1-season1-episode.mock.ts +++ b/tests/mocks/series1-season1-episode.mock.ts @@ -1,5 +1,6 @@ export const serie1Season1EpisodeMock = ` + @@ -12,7 +13,7 @@ export const serie1Season1EpisodeMock = ` - + + + + + + @@ -109,7 +118,7 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= - +
@@ -243,56 +252,58 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=

Ovládací panel


-