feat(vorlagen): Editor, Liste und Versionsgeschichte
Der Redakteur baut eine Vorlage jetzt vollstaendig in der Oberflaeche: Liste mit Suche und Filtern, Editor mit Abschnitten, Schritten und Variablen, Vorschau mit Beispielwerten, Veroeffentlichen mit Aenderungsnotiz, Versionsgeschichte. Sortiert wird mit dem Finger, der Maus, der Tastatur **und** zwei Knoepfen an jeder Zeile — ein Touch-Drag gelingt auf einem 390-px-Geraet nicht jedem, und die Reihenfolge ist fachlich zu wichtig fuer nur einen Weg. Schritte, Variablen und Abschnitte klappen zu: dieselbe Vorlage misst am Handy 3 482 statt 16 986 px. - src/lib/platzhalter.ts als einzige Quelle der eingebauten Platzhalter; der Editor warnt vor unbekannten, blockiert aber nichts - Hilfetexte als Markdown ohne rohes HTML, Bilder als Alternativtext - Erste Playwright-Tests: der Editor wird durch die Oberflaeche gefahren Wichtigster Befund des Meilensteins: `next dev` hydriert in dieser Umgebung ueberhaupt nicht — die Seiten sehen richtig aus, aber kein Knopf tut etwas (2 von 1302 Elementen tragen React-Knoten, im gebauten Stand 1281 von 1296). Nicht an diesem Code: auch ohne proxy.ts und ohne output: standalone. Ein Screenshot beweist damit nichts ueber Bedienbarkeit. Oberflaechen werden ab sofort gegen `pnpm build && pnpm start` abgenommen, und was nur mit angehaengtem React gelingt, gehoert in einen Playwright-Test. Der unabhaengige Review fand zwei Blocker: eine abgelaufene Anmeldung haette im Editor jede ungespeicherte Arbeit vernichtet (der Rechtefehler lief bis in Nexts Fehlergrenze), und das Speichern der Kopfdaten loeste eine archivierte Kundenzuordnung stillschweigend auf. Dazu sechs weitere Befunde, alle behoben. 496 Tests gruen (vorher 428), dazu 5 E2E-Tests. Schliesst #010 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d8d5444e9f
commit
548c090562
@@ -0,0 +1,150 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
EINGEBAUTE_PLATZHALTER,
|
||||
QUELLEN_TEXT,
|
||||
istEingebaut,
|
||||
unbekanntePlatzhalter,
|
||||
unbenutzteVariablen,
|
||||
} from "@/lib/platzhalter";
|
||||
import { platzhalterDerStruktur, VariableSchema, type Struktur } from "@/lib/vorlagen-format";
|
||||
|
||||
/**
|
||||
* Die eingebauten Platzhalter (#010).
|
||||
*
|
||||
* Die Liste ist Daten, und Daten koennen still falsch sein: ein Schluessel mit
|
||||
* Bindestrich wuerde im Lauf nie ersetzt, weil `platzhalterFinden` ihn nicht als
|
||||
* Platzhalter erkennt. Deshalb wird die Liste hier gegen genau dieselbe Regel
|
||||
* geprueft, die auch fuer Variablen gilt.
|
||||
*/
|
||||
|
||||
describe("Liste der eingebauten Platzhalter", () => {
|
||||
it("haelt sich an die Schluesselregel der Variablen", () => {
|
||||
const schluesselRegel = VariableSchema.shape.schluessel;
|
||||
for (const platzhalter of EINGEBAUTE_PLATZHALTER) {
|
||||
const ergebnis = schluesselRegel.safeParse(platzhalter.schluessel);
|
||||
expect(ergebnis.success, `${platzhalter.schluessel}: ${ergebnis.error?.message}`).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("nennt jeden Schluessel nur einmal", () => {
|
||||
const schluessel = EINGEBAUTE_PLATZHALTER.map((p) => p.schluessel);
|
||||
expect(new Set(schluessel).size).toBe(schluessel.length);
|
||||
});
|
||||
|
||||
it("erklaert jeden Platzhalter und ordnet ihn einer benannten Quelle zu", () => {
|
||||
for (const platzhalter of EINGEBAUTE_PLATZHALTER) {
|
||||
expect(platzhalter.erklaerung.trim().length, platzhalter.schluessel).toBeGreaterThan(0);
|
||||
expect(QUELLEN_TEXT[platzhalter.quelle], platzhalter.schluessel).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it("enthaelt die im Auftrag genannten Platzhalter", () => {
|
||||
// Namentlich, damit das Verschwinden eines einzelnen auffaellt – eine
|
||||
// Vorlage, die ihn benutzt, bekaeme sonst plötzlich eine Warnung.
|
||||
for (const schluessel of [
|
||||
"hostname",
|
||||
"seriennummer",
|
||||
"inventarnummer",
|
||||
"ticket",
|
||||
"datum",
|
||||
"techniker",
|
||||
"kunde",
|
||||
"kundenkuerzel",
|
||||
"mitarbeiter",
|
||||
"benutzer",
|
||||
"vorname",
|
||||
"nachname",
|
||||
"upn",
|
||||
"sam",
|
||||
"mail",
|
||||
"abteilung",
|
||||
"titel",
|
||||
"vorgesetzter",
|
||||
"vorgesetzter_sam",
|
||||
"dn",
|
||||
"ou",
|
||||
"gruppen",
|
||||
"generiertes_passwort",
|
||||
]) {
|
||||
expect(istEingebaut(schluessel), schluessel).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("kennzeichnet die Platzhalter, die nur im Modus Dienstleistung gefuellt sind", () => {
|
||||
const nurDienstleistung = EINGEBAUTE_PLATZHALTER.filter((p) => p.nurDienstleistung).map(
|
||||
(p) => p.schluessel,
|
||||
);
|
||||
expect(nurDienstleistung.sort()).toEqual(["kunde", "kundenkuerzel"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Unbekannte Platzhalter", () => {
|
||||
it("meldet, was weder eingebaut noch als Variable definiert ist", () => {
|
||||
expect(unbekanntePlatzhalter(["hostname", "os_version", "tipfehler"], ["os_version"])).toEqual([
|
||||
"tipfehler",
|
||||
]);
|
||||
});
|
||||
|
||||
it("meldet nichts, wenn alles bekannt ist", () => {
|
||||
expect(unbekanntePlatzhalter(["hostname", "generiertes_passwort"], [])).toEqual([]);
|
||||
});
|
||||
|
||||
it("findet einen Tippfehler in einem echten Aufbau", () => {
|
||||
// Der praktische Fall: „{{hostnmae}}" im Befehl. Ohne Warnung merkt es erst
|
||||
// der Techniker, wenn der Befehl den Platzhalter im Klartext enthaelt.
|
||||
const struktur: Struktur = {
|
||||
variablen: [
|
||||
{
|
||||
schluessel: "os_version",
|
||||
beschriftung: "Betriebssystem",
|
||||
typ: "TEXT",
|
||||
optionen: [],
|
||||
pflicht: false,
|
||||
},
|
||||
],
|
||||
abschnitte: [
|
||||
{
|
||||
titel: "Installation",
|
||||
schritte: [
|
||||
{
|
||||
titel: "Rechner umbenennen",
|
||||
typ: "BEFEHL",
|
||||
pflicht: true,
|
||||
konfig: {
|
||||
shell: "powershell",
|
||||
befehl: 'Rename-Computer -NewName "{{hostnmae}}" # soll {{hostname}} sein',
|
||||
},
|
||||
},
|
||||
{
|
||||
titel: "{{os_version}} installieren",
|
||||
hilfetext: "Danach {{ datum }} im Ticket vermerken.",
|
||||
typ: "HAKEN",
|
||||
pflicht: true,
|
||||
konfig: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const verwendet = platzhalterDerStruktur(struktur);
|
||||
expect(verwendet).toContain("hostnmae");
|
||||
expect(
|
||||
unbekanntePlatzhalter(
|
||||
verwendet,
|
||||
struktur.variablen.map((v) => v.schluessel),
|
||||
),
|
||||
).toEqual(["hostnmae"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Unbenutzte Variablen", () => {
|
||||
it("meldet eine Variable, die nirgends vorkommt", () => {
|
||||
// Eine definierte Variable, die kein Schritt benutzt, fragt den Techniker
|
||||
// beim Start nach etwas, das nie irgendwo auftaucht.
|
||||
expect(unbenutzteVariablen(["hostname"], ["os_version", "join_typ"])).toEqual(["os_version", "join_typ"]);
|
||||
});
|
||||
|
||||
it("meldet nichts, wenn jede Variable benutzt wird", () => {
|
||||
expect(unbenutzteVariablen(["os_version"], ["os_version"])).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user