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);
|
|
|
|
|
});
|
|
|
|
|
});
|