Files

127 lines
5.5 KiB
JavaScript
Raw Permalink Normal View History

2026-10-30 12:28:48 +01:00
// Generated, do not edit. sha256:0196b96345c180a741780bad0241a094640e956c6972ad08f5b4f400181c36c5
2026-10-29 11:57:29 +01:00
import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { installDom } from '../helpers/mini-dom.mjs';
const dom = installDom();
const { setPageHint, clearPageHint, configureHint, SAMPLES } = await import('../../client/ui/hint.js');
const mount = dom.document.createElement('div');
mount.id = 'page-hint';
dom.document.body.appendChild(mount);
const page = dom.host();
const gone = dom.document.createElement('div');
describe('setPageHint', () => {
beforeEach(() => { configureHint(); mount.innerHTML = ''; });
it('puts down one icon with its panel', () => {
setPageHint(page, { title: 'What you see', rows: ['A row.'] });
assert.equal(mount.querySelectorAll('.hint-btn').length, 1);
assert.equal(mount.querySelector('.hint-panel .hint-row').textContent, 'A row.');
});
it('ignores a page that is no longer on screen, and a call without a host', () => {
setPageHint(gone, { title: 'Of the previous page', rows: ['Does not belong here.'] });
setPageHint(undefined, { title: 'No host', rows: [] });
assert.equal(mount.innerHTML, '');
});
it('escapes the title, in the attribute and in the heading', () => {
setPageHint(page, { title: 'A "title" & <more>', rows: [] });
assert.equal(mount.querySelector('.hint-btn').getAttribute('aria-label'), 'A "title" & <more>');
assert.equal(mount.querySelector('.hint-panel > .t-label').textContent, 'A "title" & <more>');
assert.equal(mount.querySelector('more'), null);
});
it('a row without a sample keeps the indent of a row with one', () => {
setPageHint(page, { title: 'x', rows: ['Bare.', { sample: SAMPLES.dot, text: 'With a dot.' }] });
assert.equal(mount.querySelectorAll('.hint-row .hint-sample').length, 2);
assert.ok(mount.querySelectorAll('.hint-row')[1].querySelector('.hint-dot'));
});
it('a heading is a heading and not a row', () => {
setPageHint(page, { title: 'x', rows: [{ heading: 'Box 3' }, 'A row.'] });
assert.equal(mount.querySelector('.hint-heading').textContent, 'Box 3');
assert.equal(mount.querySelectorAll('.hint-row').length, 1);
});
it('without rows there is still a panel', () => {
setPageHint(page, { title: 'x' });
assert.ok(mount.querySelector('.hint-panel'));
});
});
describe('clearPageHint', () => {
it('without configuration: empty', () => {
configureHint();
setPageHint(page, { title: 'x', rows: ['A row.'] });
clearPageHint();
assert.equal(mount.innerHTML, '');
});
});
describe('configureHint', () => {
beforeEach(() => { mount.innerHTML = ''; });
it('puts the lead on every legend, right under the title and before the rows', () => {
configureHint({ lead: '<div class="hint-row hint-warn">Lead</div>' });
setPageHint(page, { title: 'x', rows: ['A row.'] });
const panel = mount.querySelector('.hint-panel');
assert.deepEqual(panel.children.map(c => c.className), ['t-label', 'hint-row hint-warn', 'hint-row']);
configureHint();
});
it('with an empty legend configured, clearing leaves that legend with only the lead', () => {
configureHint({ lead: '<div class="hint-warn">Lead</div>', empty: { title: 'Missing', className: 'is-fallback' } });
setPageHint(page, { title: 'x', rows: ['A row.'] });
clearPageHint();
assert.ok(mount.querySelector('.hint.is-fallback'));
assert.equal(mount.querySelector('.hint-btn').getAttribute('aria-label'), 'Missing');
assert.ok(mount.querySelector('.hint-warn'));
assert.equal(mount.querySelectorAll('.hint-row').length, 0);
configureHint();
});
});
describe('a panel that would scroll gets wider on opening', () => {
it('is-wide on opening when the content is taller than the panel, and off again when it fits', () => {
configureHint();
setPageHint(page, { title: 'x', rows: ['A row.'] });
const panel = mount.querySelector('.hint-panel');
Object.defineProperty(panel, 'scrollHeight', { value: 500, configurable: true });
Object.defineProperty(panel, 'clientHeight', { value: 300, configurable: true });
dom.fire(mount, 'pointerenter');
assert.ok(panel.classList.contains('is-wide'));
Object.defineProperty(panel, 'scrollHeight', { value: 300, configurable: true });
dom.fire(mount, 'focusin');
assert.equal(panel.classList.contains('is-wide'), false);
});
2026-10-30 12:28:48 +01:00
it('a click on the icon pins the panel, and a click outside, Escape or the icon again closes it', () => {
configureHint();
setPageHint(page, { title: 'x', rows: ['<a href="#">link</a>'] });
const hint = () => mount.querySelector('.hint');
const btn = () => mount.querySelector('.hint-btn');
dom.click(btn());
assert.ok(hint().classList.contains('is-open'));
assert.equal(btn().getAttribute('aria-expanded'), 'true');
dom.click(mount.querySelector('.hint-panel a'));
assert.ok(hint().classList.contains('is-open'), 'a click inside the panel keeps it open');
dom.click(page);
assert.equal(hint().classList.contains('is-open'), false);
dom.click(btn());
dom.key(dom.document.body, 'Escape');
assert.equal(hint().classList.contains('is-open'), false);
dom.click(btn());
dom.click(btn());
assert.equal(hint().classList.contains('is-open'), false);
});
2026-10-29 11:57:29 +01:00
it('binds its listeners once per mount, however often a page announces', () => {
const before = mount.listeners.length;
setPageHint(page, { title: 'a', rows: [] });
setPageHint(page, { title: 'b', rows: [] });
assert.equal(mount.listeners.length, before);
});
});