// 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 ?' }); assert.ok(dom.document.activeElement === button('ok'), 'OK has the focus'); assert.equal(top().querySelector('.dialog-body').textContent, 'Sure ?'); 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: '
body
', size: 'xl', asideHtml: 'tag' }); 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: '' }); 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: '' }); 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: () => '', ready: s => !!s.kind }, { title: 'Only bitcoin?', html: () => '', skip: s => s.kind !== 'btc' }, { title: 'Done', html: () => 'ok
' }, ]; 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: '' }); 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: '' }); 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; }); });