diora-web/e2e/navigation.spec.js
mrwnslz c8e05bdb0a
Some checks failed
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 9m15s
Test / e2e (push) Failing after 1m19s
Bücherliste liest nie mehr an den Dateien vorbei: Datum, gelesen, Cover aus abdeckenden Indizes (37 s → schnell; die Desktop-App gab nach 10 s auf)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 15:37:11 +02:00

145 lines
8.2 KiB
JavaScript

// The navigation (docs/navigation.md): Home, Lesen (Bücher, Zeitschriften, Nachrichten,
// Blogs), Hören (Hörbücher, Podcasts, Radio, Ambient), on the web page's own bar.
const { test, expect } = require('@playwright/test');
const path = require('path');
test.use({ storageState: path.join(__dirname, '.auth', 'user.json') });
const visiblePanel = page => page.evaluate(() =>
[...document.querySelectorAll('section.tab-panel')].filter(p => p.style.display !== 'none').map(p => p.id));
test('wide: every section in the bar under its group, each shows its panel and view', async ({ page }) => {
await page.setViewportSize({ width: 1200, height: 800 });
await page.goto('/#books');
const wide = page.locator('#tabs .nav-wide');
await expect(wide.locator('.nav-group-label')).toHaveText(['Lesen', 'Hören']);
await expect(wide.locator('[data-nav]')).toHaveText(
['Home', 'Bücher', 'Zeitschriften', 'Nachrichten', 'Blogs', 'Hörbücher', 'Podcasts', 'Radio', 'Ambient', 'Fokus', 'Abos & Quellen']);
await wide.locator('[data-nav="blogs"]').click();
await expect(page).toHaveURL(/#blogs$/);
expect(await visiblePanel(page)).toEqual(['tab-feeds']);
expect(await page.evaluate(() => localStorage.getItem('diora_feeds_view'))).toBe('blogs');
await expect(page.locator('#tab-feeds .fd-seg')).toBeHidden(); // the bar chooses, not the tab
await expect(page.locator('#nav-segments')).toBeHidden();
await wide.locator('[data-nav="audiobooks"]').click();
expect(await visiblePanel(page)).toEqual(['tab-audiobooks']);
await expect(page.locator('#audiobook-shelf')).toBeVisible();
await expect(wide.locator('[data-nav="audiobooks"]')).toHaveClass(/active/);
});
test('narrow: three groups, the sections as a row on top, each group remembers its last', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 800 });
await page.goto('/#news');
const narrow = page.locator('#tabs .nav-narrow');
await expect(narrow.locator('[data-nav]')).toHaveText(['Home', 'Lesen', 'Hören', '']);
await expect(narrow.locator('[data-nav="sources"] svg.dnav-ic')).toBeVisible();
await expect(narrow.locator('[data-nav="lesen"]')).toHaveClass(/active/);
const segs = page.locator('#nav-segments');
await expect(segs.locator('[data-nav]')).toHaveText(['Bücher', 'Zeitschriften', 'Nachrichten', 'Blogs']);
await expect(segs.locator('[data-nav="news"]')).toHaveClass(/active/);
await narrow.locator('[data-nav="hoeren"]').click();
await segs.locator('[data-nav="radio"]').click();
expect(await visiblePanel(page)).toEqual(['tab-radio']);
await narrow.locator('[data-nav="lesen"]').click();
await expect(page).toHaveURL(/#news$/); // back where Lesen was left
await narrow.locator('[data-nav="hoeren"]').click();
await expect(page).toHaveURL(/#radio/);
});
test('old links and direct panel switches land in their section', async ({ page }) => {
await page.setViewportSize({ width: 1200, height: 800 });
await page.goto('/#feeds');
expect(await visiblePanel(page)).toEqual(['tab-feeds']);
await expect(page.locator('#tabs .nav-wide [data-nav].active')).toHaveText(/Nachrichten|Blogs/);
await page.evaluate(() => showTab('podcasts'));
await expect(page.locator('#tabs [data-nav="podcasts"]')).toHaveClass(/active/);
await expect(page).toHaveURL(/#podcasts$/);
await page.reload();
expect(await visiblePanel(page)).toEqual(['tab-podcasts']); // remembered
});
test('Abos & Quellen: one page for every subscription, reached from the feeds too', async ({ page }) => {
await page.setViewportSize({ width: 1200, height: 800 });
await page.goto('/#news');
await page.locator('#tab-feeds [data-act="manage"]').first().click();
await expect(page).toHaveURL(/#sources$/);
const tab = page.locator('#tab-sources');
await expect(tab.locator('.src-h1')).toHaveText('Abos & Quellen');
await expect(tab.locator('.src-add-q')).toBeVisible();
await expect(tab.locator('.src-chip.on')).toContainText('Nachrichten'); // came from the news
await tab.locator('[data-filter="all"]').click();
await expect(tab.locator('.src-chip.on')).toContainText('Alle');
});
test('the library: a header with search, sort, layout and +, the add menu', async ({ page }) => {
await page.setViewportSize({ width: 1200, height: 800 });
await page.goto('/#books');
const list = page.locator('#book-list');
await expect(list.locator('.lib-h')).toHaveText('Bücher');
await expect(list.locator('.lib-q')).toBeVisible();
await expect(list.locator('.lib-sort')).toBeVisible();
await expect(page.locator('#book-drop-zone')).toBeHidden(); // adding is behind +
await list.locator('[data-lib="add"]').click();
await expect(page.locator('.dfo-sheet')).toContainText('E-Book-Datei');
await page.locator('.dfo-sheet [data-x]').click();
await expect(page.locator('.dfo-sheet')).toHaveCount(0);
await page.goto('/#magazines');
await expect(list.locator('.lib-h')).toHaveText('Zeitschriften');
});
test('several books at once; one already there is left out', async ({ page }) => {
const files = ['Eins', 'Zwei'].map(n => path.join(__dirname, '.auth', `many-${n}.epub`));
await page.goto('/#books');
await page.waitForFunction(() => typeof _importMany === 'function' && Array.isArray(_lastBookListData));
const status = page.locator('#book-upload-status');
await page.locator('#book-file-input').setInputFiles(files);
await expect(status).toContainText('2 Bücher hinzugefügt', { timeout: 30000 });
await page.waitForFunction(() => _lastBookListData.filter(b => /^Mehrfach/.test(b.title)).length === 2);
await page.locator('#book-file-input').setInputFiles(files);
await expect(status).toContainText('2 schon da', { timeout: 30000 });
// Out again, so the other tests see the library as before.
const books = await page.evaluate(() => _lastBookListData.filter(b => /^Mehrfach/.test(b.title)).map(b => b.id));
for (const id of books) await page.request.post(`/books/${id}/delete/`);
});
test('editions: two books joined show once with their formats, set apart show twice', async ({ page }) => {
await page.setViewportSize({ width: 1200, height: 800 });
const files = ['Eins', 'Zwei'].map(n => path.join(__dirname, '.auth', `many-${n}.epub`));
await page.goto('/#books');
await page.waitForFunction(() => typeof _importMany === 'function' && Array.isArray(_lastBookListData));
await page.locator('#book-file-input').setInputFiles(files);
await page.waitForFunction(() => _lastBookListData.filter(b => /^Mehrfach/.test(b.title) && b.id).length === 2, null, { timeout: 30000 });
const ids = await page.evaluate(() => _lastBookListData.filter(b => /^Mehrfach/.test(b.title)).map(b => b.id));
const list = page.locator('#book-list');
// New books: as cards under "Ungelesen" (not in the "Zuletzt gelesen" area on top).
const unread = () => page.evaluate(() => { _lib.status = 'unread'; renderBookList(_lastBookListData); });
await unread();
const cards = () => list.locator('.lib-grid [data-lib-book]').filter({ hasText: 'Mehrfach' });
await expect(cards()).toHaveCount(2);
// Choose both, join them.
await page.evaluate(ids => { _lib.selecting = new Set(ids); renderBookList(_lastBookListData); }, ids);
await list.locator('[data-lib="sel-editions"]').click();
await page.waitForFunction(ids => {
const w = ids.map(id => _lastBookListData.find(b => b.id === id).work);
return w[0] && w[0] === w[1];
}, ids);
await expect(cards()).toHaveCount(1);
await expect(cards().locator('.lib-ed')).toHaveText('EPUB');
// The work survives a reload (it is in the encrypted meta).
await page.reload();
await page.waitForFunction(() => Array.isArray(_lastBookListData) && _lastBookListData.length);
await unread();
await expect(cards()).toHaveCount(1);
// Ausgaben: both listed; one set apart, and the other is on its own again.
await cards().locator('.lib-more').click({ force: true });
await page.locator('.dfo-sheet .lib-sheet-item', { hasText: 'Ausgaben (2)' }).click();
await expect(page.locator('.dfo-sheet .lib-ed-row')).toHaveCount(2);
await page.locator('.dfo-sheet [data-unlink]').first().click();
await page.waitForFunction(ids => ids.every(id => !_lastBookListData.find(b => b.id === id).work), ids);
await expect(cards()).toHaveCount(2);
await page.evaluate(() => { _lib.status = 'all'; });
for (const id of ids) await page.request.post(`/books/${id}/delete/`);
});