frontend: Warnungs-Modell vereinheitlichen (strukturiert: Typ + Zeile)
TASKS Phase 1 (abgeschlossen). Warnungen sind jetzt maschinenlesbare Objekte
{type, line, ...data} statt vorformatierter Strings: der Renderer emittiert
{type:'mixedGate', line, label}; src/warnings.js `formatWarning(w, t)` macht
daraus an EINER Stelle den lokalisierten, HTML-escapten Anzeigetext. Damit sind
Typ und Zeilennummer sortier-/filter-/testbar, und Phase 2 (unbekannte
Statuszeichen -> {type:'unknownStatus', line, code}) dockt ohne Formatstreuung an.
tests/warnings.test.js + Mixed-Gate-Test im Renderer (29 Tests grün).
Ende-zu-Ende im Browser verifiziert (lokalisierte Warnung mit Zeile + Label).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5905712aa5
commit
32a02021ed
+5
-1
@@ -24,7 +24,11 @@ Abhaken beim Erledigen; neue Aufgaben unten anfügen.
|
|||||||
`tests/render.test.js` (6 Tests, Snapshots). Anm.: der Modus
|
`tests/render.test.js` (6 Tests, Snapshots). Anm.: der Modus
|
||||||
(horizontal/vertikal/kompakt) ist reine CSS-Container-Klasse und ändert
|
(horizontal/vertikal/kompakt) ist reine CSS-Container-Klasse und ändert
|
||||||
den Renderer-String nicht — ein Snapshot deckt alle drei Modi ab.
|
den Renderer-String nicht — ein Snapshot deckt alle drei Modi ab.
|
||||||
- [ ] Warnungs-Modell vereinheitlichen (Zeilennummern, Typen).
|
- [x] Warnungs-Modell vereinheitlichen (Zeilennummern, Typen).
|
||||||
|
→ strukturierte Objekte `{type, line, ...}` (Renderer emittiert
|
||||||
|
`mixedGate`); `src/warnings.js` `formatWarning(w, t)` macht daraus den
|
||||||
|
lokalisierten, HTML-escapten Text an einer Stelle. Vorbereitet für
|
||||||
|
Phase 2 (`unknownStatus`). `tests/warnings.test.js`.
|
||||||
|
|
||||||
## Phase 2 — Qualität
|
## Phase 2 — Qualität
|
||||||
- [ ] Barrierefreiheit: Fokusreihenfolge, aria-Labels für Status/Größe/Tags.
|
- [ ] Barrierefreiheit: Fokusreihenfolge, aria-Labels für Status/Größe/Tags.
|
||||||
|
|||||||
+2
-1
@@ -2,6 +2,7 @@ import './style.css';
|
|||||||
import { parse } from './parser.js';
|
import { parse } from './parser.js';
|
||||||
import { computeCheapSet } from './model.js';
|
import { computeCheapSet } from './model.js';
|
||||||
import { esc, renderTreeHtml } from './render.js';
|
import { esc, renderTreeHtml } from './render.js';
|
||||||
|
import { formatWarning } from './warnings.js';
|
||||||
|
|
||||||
const INITIAL = `%% Project structure – Sprint 14
|
const INITIAL = `%% Project structure – Sprint 14
|
||||||
[~] Website relaunch (XL) https://wiki.example.com/relaunch
|
[~] Website relaunch (XL) https://wiki.example.com/relaunch
|
||||||
@@ -57,7 +58,7 @@ function render(){
|
|||||||
});
|
});
|
||||||
out.innerHTML = html;
|
out.innerHTML = html;
|
||||||
|
|
||||||
warnBox.innerHTML = warnings.map(w => `<div>⚠ ${w}</div>`).join('');
|
warnBox.innerHTML = warnings.map(w => `<div>⚠ ${formatWarning(w, t)}</div>`).join('');
|
||||||
drawCheapPath();
|
drawCheapPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ function renderChildren(node, warnings, opts){
|
|||||||
if(!kids.length) return '';
|
if(!kids.length) return '';
|
||||||
const types = new Set(kids.map(k => k.type));
|
const types = new Set(kids.map(k => k.type));
|
||||||
if(types.size > 1){
|
if(types.size > 1){
|
||||||
warnings.push(opts.t('mixedWarn', {line: kids[0].line, label: esc(node.label)}));
|
/* strukturierte Warnung (Typ + Zeile); Formatierung in warnings.js */
|
||||||
|
warnings.push({type: 'mixedGate', line: kids[0].line, label: node.label});
|
||||||
}
|
}
|
||||||
const gate = gateOf(kids);
|
const gate = gateOf(kids);
|
||||||
const items = kids.map(k => {
|
const items = kids.map(k => {
|
||||||
@@ -62,7 +63,8 @@ function renderChildren(node, warnings, opts){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Baut den inneren HTML-String für #out aus (bereits gefilterten) Wurzeln und
|
/* Baut den inneren HTML-String für #out aus (bereits gefilterten) Wurzeln und
|
||||||
sammelt Warnungen (gemischte Gates). Leere Wurzelliste ⇒ leerer String. */
|
sammelt strukturierte Warnungen ({type, line, ...}, siehe warnings.js).
|
||||||
|
Leere Wurzelliste ⇒ leerer String. */
|
||||||
export function renderTreeHtml(roots, opts){
|
export function renderTreeHtml(roots, opts){
|
||||||
const warnings = [];
|
const warnings = [];
|
||||||
const html = roots.map(root => {
|
const html = roots.map(root => {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/* Vereinheitlichtes Warnungs-Modell.
|
||||||
|
Producer (Renderer, künftig auch Parser) liefern strukturierte Objekte
|
||||||
|
{ type, line, ...data }
|
||||||
|
statt fertig formatierter Strings — so bleiben Typ und Zeilennummer
|
||||||
|
maschinenlesbar (sortierbar, filterbar, testbar). Die i18n-/HTML-Aufbereitung
|
||||||
|
passiert an genau einer Stelle: formatWarning(). Vgl. SPEC §3 (gemischte
|
||||||
|
Gates) und TASKS Phase 2 (unbekannte Statuszeichen).
|
||||||
|
|
||||||
|
Bekannte Typen:
|
||||||
|
- mixedGate { line, label } — Geschwister mit gemischtem Gate (SPEC §3)
|
||||||
|
- unknownStatus { line, code } — unbekanntes Statuszeichen (Phase 2) */
|
||||||
|
|
||||||
|
import { esc } from './render.js';
|
||||||
|
|
||||||
|
/* Strukturierte Warnung -> lokalisierter Anzeigetext (HTML-escaped Daten).
|
||||||
|
`t` ist die i18n-Funktion (key, vars) -> String. */
|
||||||
|
export function formatWarning(w, t){
|
||||||
|
switch(w.type){
|
||||||
|
case 'mixedGate':
|
||||||
|
return t('mixedWarn', {line: w.line, label: esc(w.label)});
|
||||||
|
case 'unknownStatus':
|
||||||
|
return t('unknownStatusWarn', {line: w.line, code: esc(w.code)});
|
||||||
|
default:
|
||||||
|
return `${esc(String(w.type))} (${w.line ?? '?'})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,6 +79,20 @@ describe('renderTreeHtml — „Untergliederung fehlt" (Geister-Knoten, SPEC §5
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('renderTreeHtml — strukturierte Warnungen', () => {
|
||||||
|
it('gemischte Gates ergeben eine {type:mixedGate, line, label}-Warnung', () => {
|
||||||
|
let {roots} = parse('- Eltern\n - und-Kind\n | oder-Kind');
|
||||||
|
const {warnings} = renderTreeHtml(roots, {t, showDiscarded: false, cheapPath: false, cheapSet: new Set()});
|
||||||
|
expect(warnings).toEqual([{type: 'mixedGate', line: 2, label: 'Eltern'}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('einheitliche Gates ergeben keine Warnung', () => {
|
||||||
|
let {roots} = parse('- Eltern\n - a\n - b');
|
||||||
|
const {warnings} = renderTreeHtml(roots, {t, showDiscarded: false, cheapPath: false, cheapSet: new Set()});
|
||||||
|
expect(warnings).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('renderTreeHtml — Moduswechsel ist CSS, nicht Renderer', () => {
|
describe('renderTreeHtml — Moduswechsel ist CSS, nicht Renderer', () => {
|
||||||
it('erzeugt nie eine Modus-Klasse (vertical/kompakt) — die setzt app.js am Container', () => {
|
it('erzeugt nie eine Modus-Klasse (vertical/kompakt) — die setzt app.js am Container', () => {
|
||||||
const {html} = renderExample();
|
const {html} = renderExample();
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { formatWarning } from '../src/warnings.js';
|
||||||
|
|
||||||
|
/* Stub bildet key + interpolierte Variablen sichtbar ab, damit wir prüfen
|
||||||
|
können, welche Werte (inkl. Escaping) formatWarning durchreicht. */
|
||||||
|
const t = (key, vars) => `${key}|${JSON.stringify(vars)}`;
|
||||||
|
|
||||||
|
describe('formatWarning — vereinheitlichtes Warnungs-Modell', () => {
|
||||||
|
it('mixedGate reicht Zeile durch und HTML-escaped das Label', () => {
|
||||||
|
const out = formatWarning({type: 'mixedGate', line: 7, label: 'A & <B>'}, t);
|
||||||
|
expect(out).toBe('mixedWarn|{"line":7,"label":"A & <B>"}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unknownStatus reicht Zeile durch und escaped den Code', () => {
|
||||||
|
const out = formatWarning({type: 'unknownStatus', line: 3, code: '<'}, t);
|
||||||
|
expect(out).toBe('unknownStatusWarn|{"line":3,"code":"<"}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unbekannter Typ fällt auf eine generische, escapte Meldung zurück', () => {
|
||||||
|
expect(formatWarning({type: 'was?', line: 9}, t)).toBe('was? (9)');
|
||||||
|
expect(formatWarning({type: 'x'}, t)).toBe('x (?)');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user