// Generated, do not edit. sha256:2927734ac98d4252a0f01084377081712c0c677a75cd3c6e74946090a662fa75 import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { installDom } from '../helpers/mini-dom.mjs'; import { cellClasses, cellHtml, expCellHtml, headRowHtml, controlsHtml, actionsCellHtml, editActionsHtml, groupHeaderRowHtml, frameHtml, } from '../../client/ui/table-markup.js'; const dom = installDom(); const labels = { new: 'New', save: 'Save', cancel: 'Cancel', none: 'Ungrouped', search: 'Search', count: 'Records', edit: 'Edit' }; const parse = html => { const el = dom.host(); el.innerHTML = html; return el; }; const row = cells => parse(`${cells}
`); describe('cells', () => { it('classes: alignment and text, in that order, and nothing for a plain column', () => { assert.deepEqual(cellClasses({ align: 'num', text: true }), ['num', 'text']); assert.deepEqual(cellClasses({ align: 'center' }), ['center']); assert.deepEqual(cellClasses({}), []); }); it('a value without render is escaped; a render is markup; a text column clamps; data-label is escaped', () => { const td = row(cellHtml({ key: 'n', label: 'A "b"', text: true }, { n: 'x' })).querySelector('td'); assert.equal(td.querySelector('.cell-clamp').textContent, 'x'); assert.equal(td.querySelector('i'), null); assert.equal(td.dataset.label, 'A "b"'); const r = row(cellHtml({ key: 'n', render: v => `${v.n}` }, { n: 1 })).querySelector('td b'); assert.equal(r.textContent, '1'); }); it('a missing value is empty, not "undefined" or "null"', () => { assert.equal(row(cellHtml({ key: 'n' }, { n: null })).querySelector('td').textContent, ''); assert.equal(row(cellHtml({ key: 'n' }, {})).querySelector('td').textContent, ''); }); it('the chevron cell only in a table with a drawer, turned when open', () => { assert.equal(expCellHtml(false, true, 'v'), ''); const b = row(expCellHtml(true, true, '')).querySelector('.chev-btn'); assert.ok(b.classList.contains('is-open')); assert.equal(b.getAttribute('aria-hidden'), 'true'); }); }); describe('the head', () => { const columns = [{ key: 'a', label: 'A', sortable: true, align: 'num' }, { key: 'b', label: 'B' }]; it('sortable headers carry data-sort and th-sort; the active one an arrow per direction', () => { const head = parse(`${headRowHtml({ columns, sort: { key: 'a', dir: 'desc' }, hasExpansion: false, hasActions: false })}
`); const th = head.querySelector('th[data-sort="a"]'); assert.deepEqual(th.className.split(' '), ['num', 'th-sort']); assert.equal(th.querySelector('.sort-arrow').textContent, '▼'); assert.equal(head.querySelectorAll('th[data-sort]').length, 1); }); it('an empty header cell for the chevron and for the actions, only when there are', () => { const with_ = parse(`${headRowHtml({ columns, sort: null, hasExpansion: true, hasActions: true })}
`); assert.equal(with_.querySelectorAll('th').length, 4); const without = parse(`${headRowHtml({ columns, sort: null, hasExpansion: false, hasActions: false })}
`); assert.equal(without.querySelectorAll('th').length, 2); }); }); describe('the controls', () => { const groupCols = [{ key: 'g', label: 'G', group: {} }, { key: 'h', label: 'H', group: {} }, { key: 'i', label: 'I', group: {} }]; const base = { cfg: { rows: [1, 2], columns: groupCols }, labels, canCreate: true, groupCols, groupKey: null, groupKey2: null, groupKey3: null, search: '', editing: false }; const ctl = extra => parse(controlsHtml({ ...base, ...extra, cfg: { ...base.cfg, ...(extra.cfg || {}) } })); it('the plus, the search box and the count; no group select deeper than chosen', () => { const c = ctl({}); assert.ok(c.querySelector('[data-tbl-new]')); assert.equal(c.querySelector('[data-tbl-new]').title, 'New'); assert.equal(c.querySelector('[data-tbl-search]').getAttribute('placeholder'), 'Search'); assert.equal(c.querySelector('.tbl-count').title, 'Records'); assert.equal(c.querySelector('[id]'), null, 'no ids: a page may hold two tables'); assert.equal(c.querySelector('.tbl-count').textContent, '2'); assert.equal(c.querySelectorAll('.tbl-group-select').length, 1); assert.equal(c.querySelector('[data-gfilter]'), null); }); it('without canCreate no plus, and a toolbarNote function is read and escaped', () => { const c = ctl({ canCreate: false, cfg: { toolbarNote: () => '1 1' } }); assert.equal(c.querySelector('[data-tbl-new]'), null); assert.equal(c.querySelector('.tbl-note').textContent, '1 1'); }); it('three levels: the second excludes the first, the third excludes both, and the filter slot appears', () => { const c = ctl({ groupKey: 'g', groupKey2: 'h' }); assert.deepEqual(c.querySelector('[data-tbl-group="2"]').querySelectorAll('option').map(o => o.value), ['', 'h', 'i']); assert.deepEqual(c.querySelector('[data-tbl-group="3"]').querySelectorAll('option').map(o => o.value), ['', 'i']); assert.ok(c.querySelector('[data-gfilter]')); }); it('groupChoice false: no select and no filter slot', () => { const c = ctl({ groupKey: 'g', cfg: { groupChoice: false } }); assert.equal(c.querySelector('select'), null); assert.equal(c.querySelector('[data-gfilter]'), null); }); it('while editing everything is disabled; no style attribute anywhere', () => { const html = controlsHtml({ ...base, groupKey: 'g', editing: true, cfg: { ...base.cfg, toolbarActions: [{ id: 'x', label: 'X' }] } }); const c = parse(html); assert.ok(c.querySelectorAll('button, select, input').every(e => e.hasAttribute('disabled'))); assert.doesNotMatch(html, /style=/); }); it('a toolbar action with an icon has the shape of the plus; without one it is a ghost button', () => { const c = ctl({ cfg: { toolbarActions: [{ id: 'a', label: 'A', icon: '' }, { id: 'b', label: '' }] } }); assert.ok(c.querySelector('[data-tbl-act="a"].btn-new svg')); assert.equal(c.querySelector('[data-tbl-act="b"].btn-ghost').textContent, ''); }); }); describe('the action cell', () => { const cfg = { onSave() {}, onDelete() {}, extraActions: [{ key: 'x', label: 'X' }] }; const cellOf = args => row(actionsCellHtml({ cfg, labels, idKey: 'id', editing: false, isEditRow: false, ...args })).querySelector('td'); it('the edit row: an empty cell, save and cancel stand in the drawer (user, 26-10-2026)', () => { assert.equal(cellOf({ row: null, isEditRow: true }).children.length, 0); const bar = parse(editActionsHtml({ cfg: { saveLabel: '' }, labels })); assert.equal(bar.querySelector('[data-save]').textContent, ''); assert.equal(bar.querySelector('[data-cancel]').textContent, 'Cancel'); }); it('empty on a row that is not selected, and on every row while another is edited', () => { assert.equal(cellOf({ row: { id: 1 }, selectedId: 2 }).children.length, 0); assert.equal(cellOf({ row: { id: 1 }, selectedId: 1, editing: true }).children.length, 0); }); it('ids compare as text: a numeric selected id matches a string row id', () => { assert.ok(cellOf({ row: { id: '1' }, selectedId: 1 }).querySelector('[data-edit="1"]')); }); it('the selected row: extras, edit and delete, each only when the page allows it for this row', () => { const td = cellOf({ row: { id: 1 }, selectedId: 1 }); assert.ok(td.querySelector('[data-action="x"]') && td.querySelector('[data-edit="1"]') && td.querySelector('[data-del="1"]')); const locked = row(actionsCellHtml({ cfg: { ...cfg, rowEditable: () => false, rowDeletable: () => false }, labels, idKey: 'id', row: { id: 1 }, selectedId: 1, editing: false, isEditRow: false })).querySelector('td'); assert.equal(locked.querySelector('[data-edit]'), null); assert.equal(locked.querySelector('[data-del]'), null); }); }); describe('the group header and the frame', () => { it('escapes the label, keeps the summary as markup, and is a button only when collapsible', () => { const tr = parse(`${groupHeaderRowHtml({ token: 'a"b', label: '', count: 3, summaryHtml: 's', isCollapsed: true, collapsible: true, colCount: 4, chevron: '' })}
`).querySelector('tr'); assert.ok(tr.classList.contains('is-collapsed')); assert.equal(tr.querySelector('td').getAttribute('colspan'), '4'); assert.equal(tr.querySelector('.group-header > span').textContent, ''); assert.equal(tr.querySelector('.group-summary em').textContent, 's'); assert.equal(tr.querySelector('[data-gcollapse]').dataset.gcollapse, 'a"b'); const plain = parse(`${groupHeaderRowHtml({ token: 'a', label: 'A', count: 1, collapsible: false, colCount: 2, chevron: '' })}
`); assert.equal(plain.querySelector('[data-gcollapse]'), null); }); it('the frame: controls bar above the scrolling table, has-exp only with a drawer', () => { const f = parse(frameHtml({ controls: '', head: '', body: '', hasExpansion: true })); assert.ok(f.querySelector('.table-frame > .table-controls-bar > i')); assert.ok(f.querySelector('.table-wrap.sticky-sel.has-exp > table > thead')); assert.equal(parse(frameHtml({ controls: '', head: '', body: '', hasExpansion: false })).querySelector('.has-exp'), null); }); it('heads the bar with an escaped title above the controls, and draws nothing without one', () => { const html = frameHtml({ controls: '', head: '', body: '', hasExpansion: false, title: 'Coins & ' }); const bar = parse(html).querySelector('.table-controls-bar'); assert.ok(html.indexOf('tbl-title') < html.indexOf(''), 'the title comes first, the controls under it'); assert.equal(bar.querySelector('.tbl-title').textContent, 'Coins & '); assert.equal(bar.querySelector('.tbl-title b, .tbl-title bars'), null, 'markup in a title is text'); assert.equal(parse(frameHtml({ controls: '', head: '', body: '', hasExpansion: false })).querySelector('.tbl-title'), null); }); });