Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
146 lines
5.8 KiB
JavaScript
146 lines
5.8 KiB
JavaScript
'use strict';
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { JSDOM } = require('jsdom');
|
|
|
|
const SOURCE = fs.readFileSync(path.join(__dirname, '../../static/read-more.js'), 'utf8');
|
|
|
|
// jsdom has no layout: heights come from data-h on each .text element.
|
|
const measure = (el) => Number(el.dataset.h || 0);
|
|
|
|
function entry(id, height, notRead = false) {
|
|
return `<div class="flux${notRead ? ' not_read' : ''}" id="flux_${id}">
|
|
<article class="flux_content"><div class="content">
|
|
<div class="text" data-h="${height}"><p>Article ${id}</p><img src="x.png"></div>
|
|
</div></article>
|
|
</div>`;
|
|
}
|
|
|
|
function setup(entries) {
|
|
const dom = new JSDOM(`<!DOCTYPE html><body><main id="stream">${entries.join('')}</main></body>`,
|
|
{ runScripts: 'outside-only' });
|
|
dom.window.eval(SOURCE);
|
|
return { window: dom.window, document: dom.window.document, ReadMore: dom.window.ReadMore };
|
|
}
|
|
|
|
const OPTS = { collapsedHeight: 250, label: 'Lire plus', measure };
|
|
|
|
function setContext(window, view) {
|
|
window.context = {
|
|
current_view: view,
|
|
extensions: { read_more: { collapsed_height: 250, label: 'Lire plus' } },
|
|
};
|
|
}
|
|
|
|
test('shouldCollapse thresholds', () => {
|
|
const { ReadMore } = setup([]);
|
|
assert.equal(ReadMore.THRESHOLD_RATIO, 1.5);
|
|
assert.equal(ReadMore.shouldCollapse(374, 250), false);
|
|
assert.equal(ReadMore.shouldCollapse(375, 250), true);
|
|
assert.equal(ReadMore.shouldCollapse(1000, 250), true);
|
|
});
|
|
|
|
test('collapses tall entry', () => {
|
|
const { document, ReadMore } = setup([entry(1, 1000)]);
|
|
ReadMore.processEntries(document, OPTS);
|
|
const flux = document.getElementById('flux_1');
|
|
const text = flux.querySelector('.text');
|
|
assert.ok(text.classList.contains('read-more-collapsed'));
|
|
assert.equal(text.style.getPropertyValue('--read-more-height'), '250px');
|
|
const buttons = flux.querySelectorAll('.read-more-btn');
|
|
assert.equal(buttons.length, 1);
|
|
assert.equal(buttons[0].textContent, 'Lire plus');
|
|
assert.equal(buttons[0].type, 'button');
|
|
assert.equal(text.nextElementSibling, buttons[0]);
|
|
assert.equal(flux.dataset.readMore, 'collapsed');
|
|
});
|
|
|
|
test('skips short entry', () => {
|
|
const { document, ReadMore } = setup([entry(1, 300)]);
|
|
ReadMore.processEntries(document, OPTS);
|
|
const flux = document.getElementById('flux_1');
|
|
assert.ok(!flux.querySelector('.text').classList.contains('read-more-collapsed'));
|
|
assert.equal(flux.querySelectorAll('.read-more-btn').length, 0);
|
|
assert.equal(flux.dataset.readMore, 'short');
|
|
});
|
|
|
|
test('is idempotent', () => {
|
|
const { document, ReadMore } = setup([entry(1, 1000), entry(2, 1000)]);
|
|
ReadMore.processEntries(document, OPTS);
|
|
ReadMore.processEntries(document, OPTS);
|
|
for (const flux of document.querySelectorAll('.flux')) {
|
|
assert.equal(flux.querySelectorAll('.read-more-btn').length, 1);
|
|
}
|
|
});
|
|
|
|
test('click expands', () => {
|
|
const { window, document, ReadMore } = setup([entry(1, 1000)]);
|
|
ReadMore.processEntries(document, OPTS);
|
|
const flux = document.getElementById('flux_1');
|
|
let parentClicks = 0;
|
|
flux.addEventListener('click', () => { parentClicks++; });
|
|
const event = new window.MouseEvent('click', { bubbles: true, cancelable: true });
|
|
flux.querySelector('.read-more-btn').dispatchEvent(event);
|
|
assert.ok(!flux.querySelector('.text').classList.contains('read-more-collapsed'));
|
|
assert.equal(flux.querySelectorAll('.read-more-btn').length, 0);
|
|
assert.equal(flux.dataset.readMore, 'expanded');
|
|
assert.equal(event.defaultPrevented, true);
|
|
assert.equal(parentClicks, 0);
|
|
});
|
|
|
|
test('does not re-collapse expanded entry', () => {
|
|
const { document, ReadMore } = setup([entry(1, 1000)]);
|
|
ReadMore.processEntries(document, OPTS);
|
|
document.querySelector('.read-more-btn').click();
|
|
ReadMore.processEntries(document, OPTS);
|
|
const flux = document.getElementById('flux_1');
|
|
assert.ok(!flux.querySelector('.text').classList.contains('read-more-collapsed'));
|
|
assert.equal(flux.querySelectorAll('.read-more-btn').length, 0);
|
|
assert.equal(flux.dataset.readMore, 'expanded');
|
|
});
|
|
|
|
test('does not touch read state', () => {
|
|
const { document, ReadMore } = setup([entry(1, 1000, true)]);
|
|
ReadMore.processEntries(document, OPTS);
|
|
const flux = document.getElementById('flux_1');
|
|
assert.ok(flux.classList.contains('not_read'));
|
|
flux.querySelector('.read-more-btn').click();
|
|
assert.ok(flux.classList.contains('not_read'));
|
|
});
|
|
|
|
test('reevaluates short entry after image load', () => {
|
|
const { window, document, ReadMore } = setup([entry(1, 300)]);
|
|
setContext(window, 'reader');
|
|
ReadMore.init(window, measure);
|
|
const flux = document.getElementById('flux_1');
|
|
assert.equal(flux.dataset.readMore, 'short');
|
|
const text = flux.querySelector('.text');
|
|
text.dataset.h = '1000';
|
|
text.querySelector('img').dispatchEvent(new window.Event('load'));
|
|
assert.ok(text.classList.contains('read-more-collapsed'));
|
|
assert.equal(flux.dataset.readMore, 'collapsed');
|
|
assert.equal(flux.querySelectorAll('.read-more-btn').length, 1);
|
|
});
|
|
|
|
test('init ignores non-reader view', () => {
|
|
const { window, document, ReadMore } = setup([entry(1, 1000)]);
|
|
setContext(window, 'normal');
|
|
ReadMore.init(window, measure);
|
|
assert.equal(document.querySelectorAll('.read-more-btn').length, 0);
|
|
assert.equal(document.getElementById('flux_1').dataset.readMore, undefined);
|
|
});
|
|
|
|
test('init handles load-more', () => {
|
|
const { window, document, ReadMore } = setup([entry(1, 1000)]);
|
|
setContext(window, 'reader');
|
|
ReadMore.init(window, measure);
|
|
assert.equal(document.querySelectorAll('.read-more-btn').length, 1);
|
|
document.getElementById('stream').insertAdjacentHTML('beforeend', entry(2, 1000));
|
|
document.body.dispatchEvent(new window.Event('freshrss:load-more', { bubbles: true }));
|
|
assert.equal(document.querySelectorAll('.read-more-btn').length, 2);
|
|
assert.equal(document.getElementById('flux_2').dataset.readMore, 'collapsed');
|
|
});
|