diff --git a/server/changelogs/2026-10-09.json b/server/changelogs/2026-10-09.json new file mode 100644 index 0000000..b0d6eba --- /dev/null +++ b/server/changelogs/2026-10-09.json @@ -0,0 +1,3 @@ +[ + "Opraveno načítání jídelního lístku TechTower po změně jejich webu" +] diff --git a/server/src/restaurants.ts b/server/src/restaurants.ts index f5ca1f7..9a3aa55 100644 --- a/server/src/restaurants.ts +++ b/server/src/restaurants.ts @@ -31,6 +31,11 @@ const SOUP_NAMES = [ 'bramboračka', 'dýňový krém', 'ovarová', + 'fazolačka', + 'kyselica', + 'rajčatová s ', + 'hrášková s ', + 'hrachová s ', ]; const DAYS_IN_WEEK = ['pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', 'sobota', 'neděle']; @@ -334,6 +339,13 @@ export const getMenuTechTower = async (firstDayOfWeek: Date, mock: boolean = fal if (!font) { throw new Error('Chyba: nenalezen pro obědy v HTML Techtower.'); } + // Šestý pokus - formát z 10/2026: hlavička je

, + // jednotlivé dny a položky jsou sourozenecké

v témže bloku. Třetí pokus sice najde + // hlavičku, ale nemá žádné sourozenecké

, proto se použije parsing pátého pokusu. + if (thirdTry && $(font).parent().siblings('p').length === 0) { + thirdTry = false; + fifthTry = true; + } const result: Food[][] = []; @@ -371,7 +383,11 @@ export const getMenuTechTower = async (firstDayOfWeek: Date, mock: boolean = fal if (fifthTry) { let parsing = false; let currentDayIndex = 0; - const container = $(font).parent().parent(); + let container = $(font).parent().parent(); + if (container.find('p').length === 0) { + // Formát 10/2026 - hlavička je přímo v

, kontejnerem je jeho rodič + container = $(font).closest('p').parent(); + } container.find('p').each((_, p) => { const pHtml = $(p).html() ?? ''; const lines = pHtml.split(//i) @@ -379,7 +395,15 @@ export const getMenuTechTower = async (firstDayOfWeek: Date, mock: boolean = fal .filter(line => line.length > 0); for (const text of lines) { const lower = text.toLocaleLowerCase(); + // Za aktuálním týdnem může následovat menu na další týden - to již nezpracováváme + if (parsing && lower.startsWith('následující týden')) { + return false; + } if (DAYS_IN_WEEK.includes(lower)) { + if (result[DAYS_IN_WEEK.indexOf(lower)]) { + // Opakující se den = začátek dalšího týdne + return false; + } currentDayIndex = DAYS_IN_WEEK.indexOf(lower); parsing = true; result[currentDayIndex] ??= []; diff --git a/server/src/tests/fixtures/techtower-2026-10.html b/server/src/tests/fixtures/techtower-2026-10.html new file mode 100644 index 0000000..d21a00d --- /dev/null +++ b/server/src/tests/fixtures/techtower-2026-10.html @@ -0,0 +1,305 @@ + + + + + Restaurace :: Equifarm + + + + + + + + + + + +

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

Restaurace 

+
+
+
+
+
+
+

Otevírací doba

po – pá 10.00 - 14.00

obědy 10.00 - 13:45

+
+

Jídelní lístek 5. 10.– 9. 10.

Pondělí

Mexická fazolačka s kukuřicí a masem 1 — 45 Kč

Špagety s kuřecím masem ve smetanové omáčce, pórek, sýr 1,7 — 140 Kč

Wrap se zeleninou a grilovaným halloumi sýrem 1,7

Vepřové medailonky na zeleném pepři, fazolky se slaninou, šťouchané brambory 1,7

Kuřecí paličky se sladkokyselou omáčkou, jasmínová rýže, barevný sezam

Úterý

Rajčatová s cizrnou a bazalkou 1 — 45 Kč

Maďarský hovězí guláš, houskový knedlík 1,3,7 — 140 Kč

Tagliatelle s dýňovou omáčkou a semínky, trhaná burrata 1,7

Dušená šunka od kosti s čerstvým křenem s jablky, hrubozrnná hořčice, podmáslový chléb 1,10

Cordon bleu, hranolky, pikantní dip 1,3,7

Středa

Uzený vývar s rýží a zeleninou 9 — 45 Kč

Rozlítaný vepřový ptáček, rýže 1,3 — 140 Kč

Halušky s pečeným ajvarem a feta sýrem, bylinkový olej 1,7

Krůtí medailonky s grilovanou zeleninou, silná omáčka 1

Smažený karbanátek, bramborové pyré, řepový salát s jablky 1,3,7

Čtvrtek

Valašská kyselica s klobásou 1,7 — 45 Kč

Rizoto z vepřového masa se zeleninou, sypané sýrem Gouda 1,7 — 140 Kč

Žemlovka s jablky a tvarohem, cukr, máslo 1,3,7

Zapečené bramborové plátky s kuřecím masem a smetanovo-máslovou omáčkou, mozzarella 1,3,7

Hovězí roštěná na česneku s jasmínovou rýží 1

Pátek

Hovězí vývar se zeleninou a masem, vaječné kapání 1,3,7,9 — 45 Kč

Zapečené těstoviny s uzeným masem, okurka 1,3,7 — 140 Kč

Houbový kuba, naše čalamáda 1

Pečené kachní stehýnko s dušeným zelím, bramborové knedlíky s bylinkami a špekem 1,3,7

Smažený sýr se šunkou v bulce, hranolky, tatarská omáčka 1,3,7


Následující týden

Pondělí

Hrášková s krutonem 1,7 — 45 Kč

Čevabčiči, brambory, cibule, hořčice 1,3,7 — 140 Kč

Sýrová pizza, čerstvá bazalka 1,7

Vepřová panenka s žampionovou omáčkou, jasmínová rýže 1,7

Pečený kuřecí gyros, hranolky, tzatziki 7

Úterý

Telecí vývar se zeleninou a játrovými knedlíčky 1,3,7,9 — 45 Kč

Francouzské brambory, okurka 1,3,7 — 140 Kč

Halušky s brynzou a řepou, smažená cibulka 1,7

Quesadilla s kuřecím masem a jalapeňos, červené fazole, salát coleslaw 1,3

Pečená selečí roláda, baby mrkev na másle, bramborový knedlík 1,3,7

Středa

Krémová slepičí polévka se zeleninou 1,3,7,9 — 45 Kč

Játra po anglicku, opečené brambory 1 — 140 Kč

Těstoviny Alfredo, pažitka 1,3,7

Hovězí roštěná po cikánsku, jasmínová rýže 1,7

Smažený sekaný řízek s cheddarem, bramborové pyré 1,3,7

Čtvrtek

Hrachová s klobásou 1,7 — 45 Kč

Houbová omáčka, hovězí maso, houskový knedlík 1,3,7 — 140 Kč

Tvarohové knedlíky s ovocem, cukr, máslo, tvaroh 1,3,7

Vepřové plátky na japonský způsob, jasmínová rýže, barevný sezam 1,6,11

Steak z tuňáka, pečené brambory, koprový dip, citron 3,4,7

Pátek

Hovězí vývar se zeleninou a masem, vaječné strouhání 1,3,7,9 — 45 Kč

Vepřový Stroganoff, rýže 1,7 — 140 Kč

Smažená sýrová jehla, máslové brambory, tatarská omáčka 1,3,7

Bramborové knedlíky plněné uzeným masem, dušené bílé zelí, restovaná cibulka 1,3,7

Zapečené lasagne s mletým ragú, mozzarellou, bazalkové pesto 1,7

+
+

+
+

www.equifarm.cz

techtower@equifarm.cz

+420 608 180 311

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+ © 2022 Equifarm, s.r.o. Všechna práva vyhrazena. +
+
+ +Vytvořeno službou Webnode + + + Cookies + + + + +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+ + + + + + + diff --git a/server/src/tests/scrapers.test.ts b/server/src/tests/scrapers.test.ts index b6840f3..b862051 100644 --- a/server/src/tests/scrapers.test.ts +++ b/server/src/tests/scrapers.test.ts @@ -129,6 +129,43 @@ describe('TechTower parser – nový formát 07/2026', () => { }); }); +describe('TechTower parser – nový formát 10/2026', () => { + // Pondělí 5.10.2026, stránka obsahuje i menu na následující týden + const MONDAY_2026_10 = new Date('2026-10-05'); + + beforeEach(() => { + mockedAxios.get = jest.fn().mockResolvedValue({ data: loadFixture('techtower-2026-10.html') }); + }); + + test('vrátí pole o délce 5 s jídly pro každý den', async () => { + const menu = await getMenuTechTower(MONDAY_2026_10); + expect(menu).toHaveLength(5); + for (const den of menu) { + expect(den).toHaveLength(5); + } + }); + + test('nezahrne menu následujícího týdne', async () => { + const menu = await getMenuTechTower(MONDAY_2026_10); + expect(menu[0][0].name).toBe('Mexická fazolačka s kukuřicí a masem'); + expect(menu.flat().some(f => f.name.startsWith('Hrášková'))).toBe(false); + }); + + test('každý den začíná polévkou s cenou a alergeny', async () => { + const menu = await getMenuTechTower(MONDAY_2026_10); + for (const den of menu) { + expect(den[0].isSoup).toBe(true); + expect(den[0].price).toBe('45\xA0Kč'); + expect(den[0].allergens?.length).toBeGreaterThan(0); + expect(den.slice(1).some(f => f.isSoup)).toBe(false); + } + }); + + test('hodí StaleWeekError pro jiný týden', async () => { + await expect(getMenuTechTower(new Date('2026-10-12'))).rejects.toBeInstanceOf(StaleWeekError); + }); +}); + describe('SenkSerikova parser', () => { beforeEach(() => { // SenkSerikova parsuje arraybuffer – musíme vrátit Buffer, ne string