The new "Book Downloads" group in Settings holds a Calibre template for the file name of a direct download. Without a template, the name stays the title. The name is also the title if the template fails or gives no name for a book.
94 lines
4.0 KiB
JavaScript
94 lines
4.0 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import { test } from 'node:test';
|
|
import { install, nodes, server, settle } from './flow-helpers.mjs';
|
|
|
|
const ui = install();
|
|
const { stopPolling } = await import('../../bindery/static/js/jobs.js');
|
|
|
|
const title = 'A very long story title that the tab cuts off with an ellipsis';
|
|
const story = (id, storyTitle) => ({
|
|
story: { id, title: storyTitle, author: 'Writer', url: 'https://example.com/story', site: 'example.com',
|
|
locked_fields: [], pinned: {}, site_values: {}, auto_update: true, status: 'ok' },
|
|
chapters: [{ id: 2, title: 'One', url: 'chapter-1', included: true, on_site: true, current_rev: 1, revisions: 1 }],
|
|
recent_chapters: 0,
|
|
});
|
|
const diff = { chapter: { id: 2, title: 'One', url: 'https://example.com/c/1', current_rev: 1 }, story_title: title,
|
|
a: 1, b: 1, identical: true, html: '', changed: 0, added: 0, removed: 0, revisions: [{ rev: 1, size: 10 }] };
|
|
const rules = { rules: [], paragraphs: null, global_paragraphs: false };
|
|
|
|
const tabs = () => nodes(ui.app).filter((el) => el.tagName === 'nav').flatMap((nav) => nav.children);
|
|
const storyTabs = () => tabs().filter((a) => a.className === 'story-tab');
|
|
const current = () => tabs().filter((a) => a.attributes['aria-current'] === 'page').map((a) => a.textContent);
|
|
const go = async (hash) => {
|
|
window.location.hash = hash;
|
|
ui.windowEvents.hashchange();
|
|
await settle();
|
|
};
|
|
|
|
test('an open story has a tab with its title, also on its chapter pages', async (t) => {
|
|
t.mock.timers.enable({ apis: ['setTimeout'] });
|
|
t.after(stopPolling);
|
|
const backend = server(t);
|
|
backend.on('GET', 'auth/check', { authenticated: true, mode: 'none', user: '' });
|
|
backend.on('GET', 'config', { metadata_fields: {}, mode_labels: {}, library_available: false, download_fields: {} });
|
|
backend.on('GET', 'jobs', []);
|
|
backend.on('GET', 'stories', []);
|
|
backend.on('GET', 'settings', { recent_chapters: 0, personal_ini: '' });
|
|
backend.on('GET', 'rules', rules);
|
|
backend.on('GET', 'stories/1', story(1, title));
|
|
backend.on('GET', 'stories/1/rules', rules);
|
|
backend.on('GET', 'stories/2', story(2, 'Second'));
|
|
backend.on('GET', 'stories/2/rules', rules);
|
|
backend.on('GET', 'chapters/2/diff?', diff);
|
|
await import('../../bindery/static/js/main.js');
|
|
await settle();
|
|
assert.deepEqual(storyTabs(), []);
|
|
|
|
await go('#/story/1');
|
|
const [tab] = storyTabs();
|
|
assert.equal(tab.textContent, title);
|
|
assert.equal(tab.attributes.title, title);
|
|
assert.equal(tab.attributes.href, '#/story/1');
|
|
assert.deepEqual(current(), [title]);
|
|
|
|
await go('#/story/1/chapter/2');
|
|
assert.deepEqual(storyTabs(), [tab]);
|
|
assert.deepEqual(current(), [title]);
|
|
|
|
await go('#/story/2');
|
|
assert.equal(storyTabs().length, 1);
|
|
assert.deepEqual(current(), ['Second']);
|
|
|
|
await go('#/settings');
|
|
assert.deepEqual(storyTabs(), []);
|
|
assert.deepEqual(current(), ['Settings']);
|
|
});
|
|
|
|
test('a story that loads after the user left it gets no tab', async (t) => {
|
|
t.mock.timers.enable({ apis: ['setTimeout'] });
|
|
t.after(stopPolling);
|
|
const backend = server(t);
|
|
let release;
|
|
backend.on('GET', 'stories', []);
|
|
backend.on('GET', 'jobs', []);
|
|
backend.on('GET', 'stories/3', () => new Promise((resolve) => { release = () => resolve(story(3, 'Late')); }));
|
|
backend.on('GET', 'stories/3/rules', rules);
|
|
await go('#/story/3');
|
|
await go('#/');
|
|
release();
|
|
await settle();
|
|
assert.deepEqual(storyTabs(), []);
|
|
assert.deepEqual(current(), ['Stories']);
|
|
});
|
|
|
|
test('a long title ends in an ellipsis', () => {
|
|
const css = readFileSync(new URL('../../bindery/static/style.css', import.meta.url), 'utf8');
|
|
const rule = css.match(/header nav a\.story-tab \{([^}]*)\}/)[1];
|
|
for (const declaration of ['max-width: 24ch', 'overflow: hidden', 'text-overflow: ellipsis', 'white-space: nowrap']) {
|
|
assert.ok(rule.includes(`${declaration};`), declaration);
|
|
}
|
|
// Without it, the nav keeps the width of the whole title and runs past the header on a phone.
|
|
assert.ok(css.match(/header nav \{([^}]*)\}/)[1].includes('min-width: 0;'));
|
|
});
|