254 lines
11 KiB
JavaScript
254 lines
11 KiB
JavaScript
// Generated, do not edit. sha256:768721f9fb6138508b5d90c78fcf36c2c24f85c154f27b169a0cb0109b7e699c
|
|
import { describe, it, afterEach } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { installDom } from '../helpers/mini-dom.mjs';
|
|
|
|
const dom = installDom();
|
|
const dlg = await import('../../client/ui/dialog.js');
|
|
|
|
const overlays = () => dom.document.querySelectorAll('.dialog-overlay');
|
|
const top = () => overlays().at(-1);
|
|
const button = kind => top().querySelector(`[data-dlg="${kind}"]`);
|
|
const escape = () => dom.key(dom.document.activeElement || dom.document.body, 'Escape');
|
|
|
|
afterEach(() => {
|
|
for (let i = 0; i < 3 && overlays().length; i++) escape();
|
|
dlg.configureDialog();
|
|
});
|
|
|
|
describe('confirm, alert, notice', () => {
|
|
it('confirm resolves true on OK and false on cancel; OK has the focus', async () => {
|
|
const p = dlg.confirm({ title: 'Delete?', message: 'Sure <b>?</b>' });
|
|
assert.ok(dom.document.activeElement === button('ok'), 'OK has the focus');
|
|
assert.equal(top().querySelector('.dialog-body').textContent, 'Sure <b>?</b>');
|
|
dom.click(button('ok'));
|
|
assert.equal(await p, true);
|
|
const q = dlg.confirm({ message: 'x' });
|
|
dom.click(button('cancel'));
|
|
assert.equal(await q, false);
|
|
assert.equal(overlays().length, 0);
|
|
});
|
|
|
|
it('danger makes the confirm button red; labels can be given per call', async () => {
|
|
const p = dlg.confirm({ message: 'x', danger: true, confirmLabel: 'Weg', cancelLabel: 'Nee' });
|
|
assert.ok(button('ok').classList.contains('btn-danger'));
|
|
assert.equal(button('ok').textContent, 'Weg');
|
|
assert.equal(button('cancel').textContent, 'Nee');
|
|
escape();
|
|
assert.equal(await p, false);
|
|
});
|
|
|
|
it('a dialog WITH a button does not close on the backdrop; one without does', async () => {
|
|
const p = dlg.alert({ message: 'x' });
|
|
dom.click(top());
|
|
assert.equal(overlays().length, 1);
|
|
dom.click(button('ok'));
|
|
await p;
|
|
const n = dlg.notice({ message: 'restarting' });
|
|
assert.equal(top().querySelector('.dialog-actions'), null, 'no empty action block');
|
|
dom.click(top());
|
|
assert.equal(await n, false);
|
|
});
|
|
|
|
it('a second dialog replaces the first, which resolves false', async () => {
|
|
const p = dlg.confirm({ message: 'one' });
|
|
const q = dlg.confirm({ message: 'two' });
|
|
assert.equal(await p, false);
|
|
assert.equal(overlays().length, 1);
|
|
escape();
|
|
await q;
|
|
});
|
|
});
|
|
|
|
describe('the page behind', () => {
|
|
it('stops scrolling while a dialog is open, keeps the scrollbar width, and gets its own values back', async () => {
|
|
const root = dom.document.documentElement;
|
|
root.style.overflow = 'auto';
|
|
Object.defineProperty(root, 'clientWidth', { value: 1265, configurable: true });
|
|
const p = dlg.alert({ message: 'x' });
|
|
assert.equal(root.style.overflow, 'hidden');
|
|
assert.equal(root.style.paddingRight, '15px');
|
|
dom.click(button('ok'));
|
|
await p;
|
|
assert.equal(root.style.overflow, 'auto');
|
|
assert.equal(root.style.paddingRight, '');
|
|
delete root.clientWidth;
|
|
});
|
|
});
|
|
|
|
describe('form', () => {
|
|
const fields = [
|
|
{ key: 'name', label: 'Naam', value: 'a"b' },
|
|
{ key: 'kind', label: 'Soort', type: 'select', value: 'y', options: [{ value: 'x', label: 'X' }, { value: 'y', label: 'Y' }] },
|
|
{ key: 'note', label: 'Notitie', type: 'textarea' },
|
|
];
|
|
|
|
it('gives the values on OK, with the first field focused and values escaped into the markup', async () => {
|
|
const p = dlg.form({ title: 'New', fields });
|
|
const name = top().querySelector('[data-dlg-field="name"]');
|
|
assert.ok(dom.document.activeElement === name);
|
|
assert.equal(name.value, 'a"b');
|
|
assert.equal(top().querySelector('[data-dlg-field="kind"]').value, 'y');
|
|
name.value = 'c';
|
|
dom.click(button('ok'));
|
|
assert.deepEqual(await p, { name: 'c', kind: 'y', note: '' });
|
|
});
|
|
|
|
it('Enter in an input confirms; Enter in a textarea does not; cancel gives a falsy value', async () => {
|
|
const p = dlg.form({ fields });
|
|
dom.key(top().querySelector('textarea'), 'Enter');
|
|
assert.equal(overlays().length, 1);
|
|
dom.key(top().querySelector('input'), 'Enter');
|
|
assert.ok(await p);
|
|
const q = dlg.form({ fields });
|
|
dom.click(top());
|
|
assert.equal(overlays().length, 1, 'the backdrop does not throw the input away');
|
|
dom.click(button('cancel'));
|
|
assert.equal(await q, null);
|
|
});
|
|
});
|
|
|
|
describe('detail and what stacks on it', () => {
|
|
it('a wide panel with a close cross, no actions, and the markup as given', async () => {
|
|
const p = dlg.detail({ title: 'Asset', html: '<p class="x">body</p>', size: 'xl', asideHtml: '<em>tag</em>' });
|
|
const d = top().querySelector('.dialog');
|
|
assert.ok(d.classList.contains('dialog--wide') && d.classList.contains('dialog--xl'));
|
|
assert.ok(d.querySelector('.dialog-close'));
|
|
assert.equal(d.querySelector('.dialog-actions'), null);
|
|
assert.equal(d.querySelector('p.x').textContent, 'body');
|
|
assert.equal(d.querySelector('[data-dialog-aside] em').textContent, 'tag');
|
|
dom.click(top());
|
|
assert.equal(overlays().length, 1, 'the cross is a way out, so the backdrop does not close');
|
|
dom.click(d.querySelector('.dialog-close'));
|
|
assert.equal(await p, false);
|
|
});
|
|
|
|
it('a confirmation from inside stacks on top; Escape closes only the top one', async () => {
|
|
const p = dlg.detail({ title: 'Asset', html: '' });
|
|
const q = dlg.confirm({ message: 'Delete this row?' });
|
|
assert.equal(overlays().length, 2);
|
|
assert.ok(top().classList.contains('dialog-overlay--stacked'));
|
|
assert.equal(dlg.isDialogOpen(overlays()[0].querySelector('.dialog')), true, 'the stacked one is in the way');
|
|
escape();
|
|
assert.equal(await q, false);
|
|
assert.equal(overlays().length, 1);
|
|
escape();
|
|
assert.equal(await p, false);
|
|
});
|
|
|
|
it('alert and form stack too, and a stacked form gives its values', async () => {
|
|
const p = dlg.detail({ title: 'x', html: '' });
|
|
const a = dlg.alert({ message: 'm' });
|
|
dom.click(button('ok'));
|
|
assert.equal(await a, true);
|
|
const f = dlg.form({ fields: [{ key: 'k', label: 'K', value: 'v' }] });
|
|
dom.key(top().querySelector('input'), 'Enter');
|
|
assert.deepEqual(await f, { k: 'v' });
|
|
assert.equal(overlays().length, 1);
|
|
escape();
|
|
await p;
|
|
});
|
|
|
|
it('Escape in a field of a detail panel belongs to that field, the next one closes the panel', async () => {
|
|
const p = dlg.detail({ title: 'x', html: '<input class="f">' });
|
|
const f = top().querySelector('input.f');
|
|
dom.key(f, 'Escape');
|
|
assert.equal(overlays().length, 1);
|
|
dom.key(top().querySelector('.dialog'), 'Escape');
|
|
assert.equal(await p, false);
|
|
});
|
|
|
|
it('isDialogOpen: an element inside the open dialog is not under it, one outside is', async () => {
|
|
assert.equal(dlg.isDialogOpen(), false);
|
|
const p = dlg.detail({ title: 'x', html: '<span class="in"></span>' });
|
|
assert.equal(dlg.isDialogOpen(top().querySelector('.in')), false);
|
|
assert.equal(dlg.isDialogOpen(dom.document.body), true);
|
|
escape();
|
|
await p;
|
|
});
|
|
|
|
it('onDetail gets the dialog element and the promise of its closing', async () => {
|
|
let got = null;
|
|
dlg.configureDialog({ onDetail: (d, done) => { got = { cls: d.className, done }; d.querySelector('.dialog-title').append(dom.document.createElement('b')); } });
|
|
const p = dlg.detail({ title: 'x', html: '' });
|
|
assert.equal(got.cls, 'dialog dialog--wide');
|
|
assert.ok(top().querySelector('.dialog-title b'));
|
|
assert.equal(got.done, p);
|
|
escape();
|
|
await p;
|
|
});
|
|
|
|
it('confirmOver without an open dialog is an ordinary confirm', async () => {
|
|
const p = dlg.confirmOver({ message: 'x' });
|
|
assert.equal(overlays().length, 1);
|
|
dom.click(button('ok'));
|
|
assert.equal(await p, true);
|
|
});
|
|
});
|
|
|
|
describe('stepper', () => {
|
|
const steps = [
|
|
{ title: 'Kind', html: () => '<button data-step-set="kind" data-value="btc">btc</button><button data-step-set="kind" data-value="all">all</button>', ready: s => !!s.kind },
|
|
{ title: 'Only bitcoin?', html: () => '<button data-step-toggle="tags" data-value="a">a</button><button data-step-toggle="tags" data-value="b">b</button><button data-step-toggle="tags" data-value="c">c</button>', skip: s => s.kind !== 'btc' },
|
|
{ title: 'Done', html: () => '<p>ok</p>' },
|
|
];
|
|
const next = () => top().querySelector('[data-step="next"]');
|
|
|
|
it('next stays disabled until the step is ready; a set choice fills the state', async () => {
|
|
const p = dlg.stepper({ title: 'Setup', steps });
|
|
assert.ok(next().hasAttribute('disabled'));
|
|
dom.click(next());
|
|
assert.equal(top().querySelector('.stepper-title').textContent, 'Kind', 'a disabled next does nothing');
|
|
dom.click(top().querySelector('[data-value="btc"]'));
|
|
assert.equal(next().hasAttribute('disabled'), false);
|
|
dom.click(next());
|
|
assert.equal(top().querySelector('.stepper-title').textContent, 'Only bitcoin?');
|
|
for (const v of ['a', 'b', 'c', 'a']) dom.click(top().querySelector(`[data-step-toggle][data-value="${v}"]`));
|
|
dom.click(next());
|
|
assert.equal(next().textContent, 'Finish');
|
|
dom.click(next());
|
|
assert.deepEqual(await p, { kind: 'btc', tags: ['b', 'c'] }, 'a toggle adds, a second click takes it out');
|
|
});
|
|
|
|
it('draws the hero once: a step change keeps the same element, so its animation does not replay', async () => {
|
|
const p = dlg.stepper({ steps, hero: '<div class="logo"></div>' });
|
|
const hero = top().querySelector('.stepper-hero .logo');
|
|
assert.ok(hero);
|
|
dom.click(top().querySelector('[data-value="btc"]'));
|
|
dom.click(next());
|
|
assert.equal(top().querySelector('.stepper-hero .logo'), hero);
|
|
dom.click(top().querySelector('[data-step="skip"]'));
|
|
assert.equal(await p, false);
|
|
});
|
|
|
|
it('a skipped step is left out, back goes back, and skip gives false', async () => {
|
|
const p = dlg.stepper({ steps, state: { kind: 'all' } });
|
|
dom.click(next());
|
|
assert.equal(top().querySelector('.stepper-title').textContent, 'Done');
|
|
dom.click(top().querySelector('[data-step="back"]'));
|
|
assert.equal(top().querySelector('.stepper-title').textContent, 'Kind');
|
|
dom.click(top().querySelector('[data-step="skip"]'));
|
|
assert.equal(await p, false);
|
|
});
|
|
});
|
|
|
|
describe('configureDialog', () => {
|
|
it('sets the button texts and the close icon; unset fields keep the default', async () => {
|
|
dlg.configureDialog({ labels: { ok: 'Goed', cancel: 'Annuleren', close: 'Sluiten' }, closeIcon: '<i class="x"></i>' });
|
|
const p = dlg.confirm({ message: 'x' });
|
|
assert.equal(button('ok').textContent, 'Goed');
|
|
assert.equal(button('cancel').textContent, 'Annuleren');
|
|
escape();
|
|
await p;
|
|
const d = dlg.detail({ title: 'x', html: '' });
|
|
assert.equal(top().querySelector('.dialog-close').getAttribute('aria-label'), 'Sluiten');
|
|
assert.ok(top().querySelector('.dialog-close i.x'));
|
|
escape();
|
|
await d;
|
|
const s = dlg.stepper({ steps: [{ html: () => '' }] });
|
|
assert.equal(top().querySelector('[data-step="skip"]').textContent, 'Skip');
|
|
escape();
|
|
await s;
|
|
});
|
|
});
|