CI / Generate TypeScript types (push) Successful in 11s
CI / Server unit tests (push) Successful in 30s
CI / Build server (push) Successful in 29s
CI / Build client (push) Successful in 41s
CI / Playwright E2E tests (push) Successful in 2m0s
CI / Build and push Docker image (push) Successful in 47s
CI / Notify (push) Successful in 2s
197 lines
7.6 KiB
TypeScript
197 lines
7.6 KiB
TypeScript
import * as fs from 'fs';
|
||
import * as path from 'path';
|
||
import axios from 'axios';
|
||
import { getMenuSladovnicka, getMenuTechTower, getMenuSenkSerikova, StaleWeekError } from '../restaurants';
|
||
|
||
jest.mock('axios');
|
||
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
||
|
||
const fixturesDir = path.join(__dirname, 'fixtures');
|
||
const loadFixture = (name: string) => fs.readFileSync(path.join(fixturesDir, name), 'utf-8');
|
||
|
||
// Pondělí 12.5.2025
|
||
const MONDAY = new Date('2025-05-12');
|
||
|
||
beforeEach(() => {
|
||
jest.resetAllMocks();
|
||
});
|
||
|
||
describe('Sladovnicka parser', () => {
|
||
beforeEach(() => {
|
||
mockedAxios.get = jest.fn().mockResolvedValue({ data: loadFixture('sladovnicka.html') });
|
||
});
|
||
|
||
test('vrátí pole o délce 5 (jeden záznam na každý pracovní den)', async () => {
|
||
const menu = await getMenuSladovnicka(MONDAY);
|
||
expect(menu).toHaveLength(5);
|
||
});
|
||
|
||
test('pondělní menu obsahuje aspoň jedno jídlo', async () => {
|
||
const menu = await getMenuSladovnicka(MONDAY);
|
||
expect(menu[0].length).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('první položka pondělního dne je polévka (isSoup=true)', async () => {
|
||
const menu = await getMenuSladovnicka(MONDAY);
|
||
expect(menu[0][0].isSoup).toBe(true);
|
||
});
|
||
|
||
test('jídla mají name, price a amount', async () => {
|
||
const menu = await getMenuSladovnicka(MONDAY);
|
||
const jidlo = menu[0][1];
|
||
expect(jidlo.name).toBeTruthy();
|
||
expect(jidlo.price).toBeTruthy();
|
||
expect(jidlo.amount).toBeTruthy();
|
||
});
|
||
|
||
test('alergeny jsou naparsovány jako čísla', async () => {
|
||
const menu = await getMenuSladovnicka(MONDAY);
|
||
const polievka = menu[0][0];
|
||
expect(Array.isArray(polievka.allergens)).toBe(true);
|
||
expect(polievka.allergens!.length).toBeGreaterThan(0);
|
||
expect(typeof polievka.allergens![0]).toBe('number');
|
||
});
|
||
});
|
||
|
||
describe('TechTower parser', () => {
|
||
beforeEach(() => {
|
||
mockedAxios.get = jest.fn().mockResolvedValue({ data: loadFixture('techtower.html') });
|
||
});
|
||
|
||
test('vrátí pole o délce 5', async () => {
|
||
const menu = await getMenuTechTower(MONDAY);
|
||
expect(menu).toHaveLength(5);
|
||
});
|
||
|
||
test('pondělní menu obsahuje polévku a hlavní jídla', async () => {
|
||
const menu = await getMenuTechTower(MONDAY);
|
||
expect(menu[0].some(f => f.isSoup)).toBe(true);
|
||
expect(menu[0].some(f => !f.isSoup)).toBe(true);
|
||
});
|
||
|
||
test('TechTower hodí StaleWeekError, pokud datum v hlavičce neodpovídá', async () => {
|
||
// Fixture obsahuje "12.5.-16.5.2025" – jiný týden = stale
|
||
const jinaStreda = new Date('2025-04-14');
|
||
await expect(getMenuTechTower(jinaStreda)).rejects.toBeInstanceOf(StaleWeekError);
|
||
});
|
||
|
||
test('StaleWeekError obsahuje naparsovaná data', async () => {
|
||
const jinaStreda = new Date('2025-04-14');
|
||
try {
|
||
await getMenuTechTower(jinaStreda);
|
||
} catch (e) {
|
||
expect(e).toBeInstanceOf(StaleWeekError);
|
||
const err = e as StaleWeekError;
|
||
expect(err.food).toHaveLength(5);
|
||
}
|
||
});
|
||
});
|
||
|
||
describe('TechTower parser – nový formát 07/2026', () => {
|
||
// Pondělí 6.7.2026 (pondělí je svátek, hlavička začíná úterým "7. 7.-10. 7.")
|
||
const MONDAY_2026 = new Date('2026-07-06');
|
||
|
||
beforeEach(() => {
|
||
mockedAxios.get = jest.fn().mockResolvedValue({ data: loadFixture('techtower-2026-07.html') });
|
||
});
|
||
|
||
test('vrátí pole o délce 5', async () => {
|
||
const menu = await getMenuTechTower(MONDAY_2026);
|
||
expect(menu).toHaveLength(5);
|
||
});
|
||
|
||
test('středeční menu obsahuje polévku a hlavní jídla s cenou', async () => {
|
||
const menu = await getMenuTechTower(MONDAY_2026);
|
||
// index 2 = středa (Boršč + hlavní jídla)
|
||
expect(menu[2].some(f => f.isSoup)).toBe(true);
|
||
expect(menu[2].some(f => !f.isSoup && f.price.includes('Kč'))).toBe(true);
|
||
});
|
||
|
||
test('cena i alergeny jsou správně odděleny od názvu jídla', async () => {
|
||
const menu = await getMenuTechTower(MONDAY_2026);
|
||
const polevka = menu[2].find(f => f.isSoup);
|
||
expect(polevka).toBeDefined();
|
||
// "Boršč 1,7,9 — 45 Kč" → název bez ceny a bez alergenů, cena a alergeny zvlášť
|
||
expect(polevka!.name).not.toMatch(/Kč/);
|
||
expect(polevka!.name).not.toMatch(/\d/);
|
||
expect(polevka!.price).toContain('Kč');
|
||
expect(Array.isArray(polevka!.allergens)).toBe(true);
|
||
expect(polevka!.allergens!.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('nehodí StaleWeekError pro aktuální týden, i když pondělí je svátek', async () => {
|
||
await expect(getMenuTechTower(MONDAY_2026)).resolves.toHaveLength(5);
|
||
});
|
||
|
||
test('hodí StaleWeekError pro jiný týden', async () => {
|
||
const jinyTyden = new Date('2026-06-01');
|
||
await expect(getMenuTechTower(jinyTyden)).rejects.toBeInstanceOf(StaleWeekError);
|
||
});
|
||
});
|
||
|
||
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
|
||
mockedAxios.get = jest.fn().mockResolvedValue({
|
||
data: Buffer.from(loadFixture('senkserikova.html')),
|
||
headers: {}
|
||
});
|
||
});
|
||
|
||
test('parser provede HTTP request a vrátí pole', async () => {
|
||
const menu = await getMenuSenkSerikova(MONDAY);
|
||
expect(mockedAxios.get).toHaveBeenCalledTimes(1);
|
||
expect(Array.isArray(menu)).toBe(true);
|
||
});
|
||
|
||
test('výsledné dny s obsahem mají správnou strukturu (name, price, isSoup)', async () => {
|
||
const menu = await getMenuSenkSerikova(MONDAY);
|
||
// Protože MONDAY je v minulosti, parser vrátí placeholdery pro všechny pracovní
|
||
// dny a .menicka elementy přidá za ně – hledáme aspoň jeden den s reálnými daty
|
||
const denSJidlem = menu.find(den =>
|
||
den.length > 0 && den[0].name !== 'Pro tento den není uveřejněna nabídka jídel'
|
||
);
|
||
if (denSJidlem) {
|
||
expect(typeof denSJidlem[0].name).toBe('string');
|
||
expect(typeof denSJidlem[0].isSoup).toBe('boolean');
|
||
}
|
||
});
|
||
});
|