112 lines
6.6 KiB
JavaScript
112 lines
6.6 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs').promises;
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { detectVerticalStrip, resolveReadingDirection, fallbackDirection } = require('../../reading-direction');
|
|
const { publishManifest } = require('../lib/publisher');
|
|
const { scanLibrary } = require('../../library');
|
|
|
|
const strip = (w = 800) => ({ w, h: 12000 });
|
|
|
|
test('strip detection tolerates a landscape title card and rejects weak evidence', () => {
|
|
const pages = [{ w: 1200, h: 800 }, strip(), strip(), strip()];
|
|
assert.equal(detectVerticalStrip(pages).value, 'ttb');
|
|
assert.equal(detectVerticalStrip(pages).evidence.tallPages, 3);
|
|
assert.equal(detectVerticalStrip([strip()]), null);
|
|
assert.equal(detectVerticalStrip([strip(), strip(), { w: 800, h: 1200 }, { w: 800, h: 1200 }]), null);
|
|
assert.equal(detectVerticalStrip([strip(), strip(), strip(1400), { w: 1200, h: 800 }]), null);
|
|
assert.equal(detectVerticalStrip([{ w: 800, h: 12000, r: 90 }, strip(), strip()]), null);
|
|
assert.equal(detectVerticalStrip([{ w: 12000, h: 800, r: 90 }, strip(), strip()]).value, 'ttb');
|
|
});
|
|
|
|
test('manual choices at chapter, book or manga level beat detection and fallback', () => {
|
|
const pages = [strip(), strip(), strip()];
|
|
const automatic = fallbackDirection();
|
|
assert.equal(resolveReadingDirection([automatic], pages).value, 'ttb');
|
|
assert.equal(resolveReadingDirection([automatic]).source, 'fallback');
|
|
assert.equal(resolveReadingDirection([{ direction: 'ltr' }, automatic], pages).value, 'ltr');
|
|
assert.equal(resolveReadingDirection([{}, { direction: 'rtl' }, automatic], pages).value, 'rtl');
|
|
assert.equal(resolveReadingDirection([{ directionOverride: 'rtl', ...automatic }], pages).value, 'rtl');
|
|
assert.equal(resolveReadingDirection([{ ...automatic, direction: 'ltr' }], pages).value, 'ltr');
|
|
assert.equal(resolveReadingDirection([{ direction: 'rtl' }], pages).source, 'manual');
|
|
assert.equal(resolveReadingDirection([], pages).value, 'ttb');
|
|
});
|
|
|
|
function png(w, h) {
|
|
const b = Buffer.alloc(24);
|
|
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]).copy(b);
|
|
b.writeUInt32BE(w, 16); b.writeUInt32BE(h, 20);
|
|
return b;
|
|
}
|
|
|
|
async function json(file) { return JSON.parse(await fs.readFile(file, 'utf8')); }
|
|
|
|
test('publisher detects new strips, preserves updates and manual metadata, and scanner agrees', async t => {
|
|
const temp = await fs.mkdtemp(path.join(os.tmpdir(), 'mcscraper-direction-'));
|
|
t.after(() => fs.rm(temp, { recursive: true, force: true }));
|
|
const libraryRoot = path.join(temp, 'library');
|
|
const sizes = [[1200, 800], [800, 12000], [800, 12500], [800, 14210]];
|
|
const files = [];
|
|
for (const [i, size] of sizes.entries()) {
|
|
const file = path.join(temp, `page${i}.png`);
|
|
await fs.writeFile(file, png(...size)); files.push(file);
|
|
}
|
|
const series = { title: 'Strip Work', ...fallbackDirection(),
|
|
sources: [{ name: 'Test', url: 'https://example.test/series' }] };
|
|
const chapter = number => ({ number, source: { name: 'Test', url: `https://example.test/${number}` },
|
|
book: { number: 1, title: 'Season 1' }, pages: files.map(file => ({ path: file })) });
|
|
const first = await publishManifest({ version: 1, series, chapters: [chapter('1')] }, { libraryRoot });
|
|
const mangaDir = first.mangaDir, bookDir = path.join(mangaDir, 'v01 Season 1');
|
|
const mangaFile = path.join(mangaDir, 'manga.json');
|
|
assert.equal((await json(mangaFile)).direction, 'ttb');
|
|
assert.equal((await json(mangaFile))['x-reading-direction'].source, 'detected');
|
|
const scan = (await scanLibrary(libraryRoot)).index.mangas[0];
|
|
assert.equal(scan.chapters[0].versions[0].direction, 'ttb');
|
|
assert.equal(scan.chapters[0].versions[0].pages[0].w, 1200);
|
|
|
|
const same = await publishManifest({ version: 1, series, chapters: [chapter('2')] }, { libraryRoot });
|
|
assert.equal(same.mangaDir, mangaDir);
|
|
assert.equal((await json(mangaFile)).direction, 'ttb');
|
|
|
|
const bookFile = path.join(bookDir, 'book.json');
|
|
await fs.writeFile(bookFile, JSON.stringify({ title: 'Season 1', direction: 'ltr', note: 'human' }));
|
|
const chapterDir = path.join(bookDir, first.results[0].folder);
|
|
const chapterFile = path.join(chapterDir, 'chapter.json');
|
|
await fs.writeFile(chapterFile, JSON.stringify({ ...await json(chapterFile), directionOverride: 'rtl', note: 'human' }));
|
|
await publishManifest({ version: 1, series, chapters: [chapter('1')] }, { libraryRoot, replace: true });
|
|
assert.equal((await json(bookFile)).direction, 'ltr');
|
|
assert.equal((await json(bookFile)).note, 'human');
|
|
assert.equal((await json(chapterFile)).direction, 'rtl');
|
|
assert.equal((await json(chapterFile)).directionOverride, 'rtl');
|
|
assert.equal((await json(chapterFile)).note, 'human');
|
|
const rescanned = (await scanLibrary(libraryRoot)).index.mangas[0];
|
|
assert.equal(rescanned.chapters[0].versions[0].direction, 'rtl');
|
|
assert.equal(rescanned.chapters[1].versions[0].direction, 'ltr');
|
|
|
|
await fs.writeFile(mangaFile, JSON.stringify({ ...await json(mangaFile), direction: 'rtl' }));
|
|
await publishManifest({ version: 1, series, chapters: [chapter('3')] }, { libraryRoot });
|
|
assert.equal((await json(mangaFile)).direction, 'rtl');
|
|
assert.equal((await json(mangaFile))['x-reading-direction'].source, 'manual');
|
|
assert.equal((await scanLibrary(libraryRoot)).index.mangas[0].chapters[2].versions[0].direction, 'ltr');
|
|
});
|
|
|
|
test('legacy plain RTL stays protected across automatic imports', async t => {
|
|
const temp = await fs.mkdtemp(path.join(os.tmpdir(), 'mcscraper-legacy-direction-'));
|
|
t.after(() => fs.rm(temp, { recursive: true, force: true }));
|
|
const libraryRoot = path.join(temp, 'library');
|
|
const page = path.join(temp, 'tall.png'); await fs.writeFile(page, png(800, 12000));
|
|
const series = { title: 'Legacy', ...fallbackDirection(), sources: [{ name: 'Test', url: 'https://example.test/legacy' }] };
|
|
const chapter = { number: '1', source: { name: 'Test', url: 'https://example.test/legacy/1' },
|
|
pages: [1, 2, 3].map(() => ({ path: page })) };
|
|
const first = await publishManifest({ version: 1, series, chapters: [chapter] }, { libraryRoot });
|
|
const file = path.join(first.mangaDir, 'manga.json');
|
|
const meta = await json(file); delete meta['x-reading-direction']; meta.direction = 'rtl';
|
|
await fs.writeFile(file, JSON.stringify(meta));
|
|
await publishManifest({ version: 1, series, chapters: [{ ...chapter, number: '2', source: { name: 'Test', url: 'https://example.test/legacy/2' } }] }, { libraryRoot });
|
|
assert.equal((await json(file)).direction, 'rtl');
|
|
assert.equal((await json(file))['x-reading-direction'].source, 'manual');
|
|
});
|