feat: Tabellen per Spaltenkopf sortierbar (A11-T4)
Neues static/sort.js sortiert Tabellen clientseitig per Klick auf den Spaltenkopf (⇅/▲/▼): Datum TT.MM.JJJJ, deutsche Zahlen/Beträge, sonst Text (Intl.Collator 'de'). Sortierwert optional per td[data-sort] (Punkt-Dezimal/ISO) überschreibbar, th[data-nosort] bekommt keinen Button, table[data-sort-mode="server"] wird übersprungen (Buchungen sortiert serverseitig, folgt in Task 5). Bearbeiten-/Tilgungsplan- Zeilen (tr[data-sort-follow]) wandern mit ihrer Vorzeile mit. Initialisierung über htmx.onLoad, greift auch bei nachgeladenen Fragmenten. Templates entsprechend annotiert (Planung, Szenarien, Admin, Index, Salden, Import-Liste, Buchungen); Salden-Gesamtzeile dafür in ein tfoot verschoben. test_gui.py::test_gui_zeigt_deutsche_betragsformate angepasst, da das neue machinenlesbare data-sort-Attribut bewusst Punkt-Dezimal führt (Interface-Konvention) - die Prüfung auf deutsches Format im sichtbaren Text bleibt bestehen. JS-Unit-/DOM-Tests unter tests/js (node --test, DOM-Teil optional via jsdom), pytest-Wrapper skip ohne node (Fable-Gate führt sie per Podman aus). Suite: 248 passed, 3 skipped (vorher 242/2). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
let JSDOM = null;
|
||||
try { ({ JSDOM } = require('jsdom')); } catch (e) { /* jsdom fehlt -> skip */ }
|
||||
const SRC = fs.readFileSync(path.join(__dirname, '../../app/static/sort.js'), 'utf8');
|
||||
|
||||
function load(html) {
|
||||
const dom = new JSDOM(`<!doctype html><body>${html}</body>`, { runScripts: 'outside-only' });
|
||||
dom.window.eval(SRC);
|
||||
dom.window.FBSort.init(dom.window.document.body);
|
||||
return dom.window.document;
|
||||
}
|
||||
|
||||
const TABLE = `
|
||||
<table id="t"><thead><tr><th>Name</th><th>Betrag</th><th>Aktion</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr id="r1"><td>B</td><td>10,00 €</td><td>x</td><td></td></tr>
|
||||
<tr id="r1-edit" hidden data-sort-follow><td colspan="4">edit1</td></tr>
|
||||
<tr id="r2"><td>A</td><td>-5,00 €</td><td>x</td><td></td></tr>
|
||||
<tr id="r2-edit" hidden data-sort-follow><td colspan="4">edit2</td></tr>
|
||||
<tr id="r3"><td>C</td><td>–</td><td>x</td><td></td></tr>
|
||||
</tbody>
|
||||
<tfoot><tr id="sum"><td>Gesamt</td><td>5,00 €</td><td></td><td></td></tr></tfoot>
|
||||
</table>
|
||||
<table data-sort-mode="server" id="s"><thead><tr><th>Datum</th></tr></thead><tbody></tbody></table>`;
|
||||
|
||||
const ids = (doc) => [...doc.querySelectorAll('#t tbody tr')].map((r) => r.id);
|
||||
|
||||
test('Buttons nur an Spalten mit Text, nicht an server-Tabellen', { skip: !JSDOM }, () => {
|
||||
const doc = load(TABLE.replace('<th>Aktion</th>', '<th data-nosort>Aktion</th>'));
|
||||
assert.equal(doc.querySelectorAll('#t .sort-btn').length, 2);
|
||||
assert.equal(doc.querySelectorAll('#s .sort-btn').length, 0);
|
||||
assert.equal(doc.querySelector('#t .sort-btn').textContent, '⇅');
|
||||
});
|
||||
|
||||
test('Klick sortiert auf/ab, Folgezeilen wandern mit, Leerwert am Ende, tfoot bleibt', { skip: !JSDOM }, () => {
|
||||
const doc = load(TABLE);
|
||||
const btn = doc.querySelectorAll('#t .sort-btn')[1]; // Betrag
|
||||
btn.click();
|
||||
assert.equal(btn.textContent, '▲');
|
||||
assert.deepEqual(ids(doc), ['r2', 'r2-edit', 'r1', 'r1-edit', 'r3']);
|
||||
btn.click();
|
||||
assert.equal(btn.textContent, '▼');
|
||||
assert.deepEqual(ids(doc), ['r1', 'r1-edit', 'r2', 'r2-edit', 'r3']);
|
||||
assert.equal(doc.querySelector('#t tfoot tr').id, 'sum');
|
||||
const nameBtn = doc.querySelectorAll('#t .sort-btn')[0];
|
||||
nameBtn.click();
|
||||
assert.equal(btn.textContent, '⇅');
|
||||
assert.deepEqual(ids(doc), ['r2', 'r2-edit', 'r1', 'r1-edit', 'r3']);
|
||||
});
|
||||
|
||||
test('init zweimal erzeugt keine doppelten Buttons', { skip: !JSDOM }, () => {
|
||||
const doc = load(TABLE);
|
||||
doc.defaultView.FBSort.init(doc.body);
|
||||
assert.equal(doc.querySelectorAll('#t .sort-btn').length, 3);
|
||||
});
|
||||
Reference in New Issue
Block a user