import { readFileSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; /* * Befunde 66, 67 und 38 zur Fassung 5.10.0. * * Drei Angaben der Werkbank, die sich am Baum nachmessen lassen und deshalb * nicht als blosser Fliesstext stehenbleiben duerfen: * * 66. `eslint.config.mjs` begruendet, welche Warnungen bewusst stehen * bleiben, und bezifferte `no-unnecessary-type-assertion` mit "67 Faelle, * ueberwiegend in tests/". Gemessen mit `npx eslint . -f json` sind es 36 * Warnungen insgesamt - 31 no-unnecessary-condition, 4 * switch-exhaustiveness-check und genau 1 no-unnecessary-type-assertion, * und die steht in `electron/karte.ts`, nicht in tests/. Der Kommentar * widerlegt sich sogar ohne Linterlauf: 67 plus 4 sind 71, waehrend * als offener Rueckstand 36 vermerkt waren - unter Verweis auf ebendiese * Begruendung. Dieselbe Grosszuegigkeit darf die neue Begruendung nicht * wiederholen: Von den 31 Faellen liegt einer in `e2e/rauchtest.spec.ts`. * `e2e/` faellt nicht unter den Ausnahmeblock fuer `tests/**`, gehoert * aber ebenso wenig zum Programm - "vollstaendig im Programm" waere um * einen Fall zu weit. * * NACHGEZOGEN NACH 5.16.0: Diese Fallzahlen gibt es nicht mehr, und die * vier Faelle, die sie nachrechneten, ebenso wenig. Der geduldete * Rueckstand ist abgetragen - die Regeln melden Fehler, jede beanstandete * Zeile traegt ihren eigenen Ausnahmevermerk samt Grund, `npm run lint` * laeuft mit `--max-warnings 0`. Der Befund bleibt hier als Bericht * stehen, weil er der Grund ist, warum es diese Datei gibt; was von ihm * dauerhaft galt - der Ausnahmeblock gilt fuer tests/ und nicht fuer * e2e/ -, prueft der Block unten weiter. Die Gegenrichtung haelt * `tests/tools/linterregelwerk.test.ts` fest, den Zustand * selbst `tests/tools/linterRueckstand.test.ts`. * * 67. `e2e/rauchtest.spec.ts` begruendete seine Daseinsberechtigung mit "Die * 1670 Modultests" - der Stand bei Einfuehrung der Datei. Der Baum steht * bei einem Vielfachen. Eine Wache auf die richtige Zahl waere der * falsche Weg: Die Zahl der Faelle laesst sich statisch nicht ermitteln. * Bewacht wird deshalb, dass dort ueberhaupt keine Zahl mehr steht - was keine * Zahl nennt, kann auch nicht stumm veralten. * * 38. `.githooks/pre-commit` setzt voraus, dass jemand einmal * `git config core.hooksPath .githooks` ausgefuehrt hat. Dieser Eintrag * steht in `.git/config` und damit ausserhalb des Baumes; `git clone`, * `git worktree add` und ein frisches Auschecken fuehren ihn nicht mit. * Solange das Projekt den Haken nicht selbst einrichtet, muss die Datei * das sagen, statt die Einrichtung als geschehen darzustellen. * * S4. Derselbe Kopf sagt unbedingt zu, der Haken fange ab, "was sich sonst * erst beim Anwender zeigt". Die vier Stufen der Schnellstufe lesen aber * die Dateien so, wie sie auf der Platte liegen, nicht so, wie sie im * Bereitstellungsbereich stehen. Bei teilweiser Bereitstellung beurteilt * der Lauf einen anderen Inhalt als den, der in den Commit geht - in * beide Richtungen. Ein Wegwerf-Arbeitsbaum je Commit ist die falsche * Antwort darauf: Er verlangt je Commit ein eigenes Verzeichnis samt * node_modules, und das faellt bei JEDEM Commit an. Die Grenze gehoert * aber benannt. NACHGEZOGEN AM 07.09.2026: Hier stand als Begruendung, * der Wegwerfbaum sprenge die "im selben Kopf zugesagten etwa zwanzig * Sekunden". Diese Zusage steht dort nicht mehr - der Haken nennt * seit der Fassung 5.16.0 gar keine Laufzeit mehr, weil sie um das * Vierfache daneben lag. Ein Kommentar, der eine gestrichene Stelle * zitiert, ist * selbst eine falsche Angabe. * * WARUM KEIN LINTERLAUF IM TESTFALL: `eslint .` braucht einen eigenen Lauf ueber * den ganzen Baum, und ein Testfall darf den nicht anwerfen. Das gilt * unveraendert - nur ist der Widerspruch, den es ohne Linterlauf nachzumessen * galt, mit den Fallzahlen weggefallen. `gate:schnell` faehrt den Linter * ohnehin bei jedem Commit, und seit `--max-warnings 0` haelt schon eine * einzige Warnung den Lauf an. */ const LINTERREGELWERK = readFileSync('eslint.config.mjs', 'utf8'); const RAUCHTEST = readFileSync('e2e/rauchtest.spec.ts', 'utf8'); const HAKEN = readFileSync('.githooks/pre-commit', 'utf8'); /** Kommentartext des Hakens ohne Rautenzeichen und Zeilenumbrueche - Saetze stehen dort umbrochen. */ const HAKENTEXT = HAKEN.replace(/^#+ ?/gm, '').replace(/\s+/g, ' '); const PAKET = JSON.parse(readFileSync('package.json', 'utf8')) as { scripts?: Record; }; describe('eslint.config.mjs - der Ausnahmeblock fuer tests/', () => { /* * WAS HIER FRUEHER STAND: vier weitere Faelle, die die Fallzahlen des * Begruendungsblocks nachrechneten - Summe gegen die vermerkte Zahl, * Aufteilung Programm gegen Rauchtest, eine Fallzahl je Regel auf 'warn'. * Alle vier setzten voraus, dass es einen geduldeten Rueckstand gibt. Den * gibt es nicht mehr: Die Regeln melden Fehler, jede beanstandete Zeile * traegt ihren eigenen Ausnahmevermerk samt Grund, und `npm run lint` laeuft * mit `--max-warnings 0`. Zahlen, die nicht mehr dastehen, lassen sich nicht * nachrechnen. * * WAS BLEIBT, weil es vom Rueckstand unabhaengig ist: Der Ausnahmeblock fuer * `tests/` gilt fuer tests/ und fuer nichts sonst. Der eine Fall in * `e2e/rauchtest.spec.ts` gehoert zum Programm - ihn stillschweigend * mitzunehmen waere dieselbe zu grosszuegige Selbstauskunft, die Befund 66 * abstellen sollte. * * Die Gegenrichtung - keine der drei Regeln wird woanders abgeschwaecht - * haelt `tests/tools/linterregelwerk.test.ts` fest. */ it('schaltet no-unnecessary-condition fuer tests/ ab', () => { const block = LINTERREGELWERK.slice(LINTERREGELWERK.lastIndexOf("files: ['tests/**/*.ts']")); expect(block, 'Block fuer tests/ nicht gefunden').not.toBe(''); expect(block).toContain("'@typescript-eslint/no-unnecessary-condition': 'off'"); }); it('nimmt e2e/ von der Regel nicht aus', () => { // Der Dateiabschnitt des Blocks, der die Regel abschaltet - nicht der des // Blocks, der sie setzt: `e2e/**/*.ts` steht dort sehr wohl, aber unter // den typgepruefen Ordnern, nicht unter den Ausnahmen. const aus = LINTERREGELWERK.indexOf("'@typescript-eslint/no-unnecessary-condition': 'off'"); expect(aus, 'Ausnahmeblock nicht gefunden').toBeGreaterThan(0); const ausnahme = LINTERREGELWERK.slice(LINTERREGELWERK.lastIndexOf('files:', aus), aus); expect(ausnahme, 'Ausnahme gilt fuer tests/').toContain('tests/**/*.ts'); expect(ausnahme, 'Ausnahme darf e2e/ nicht mitnehmen').not.toContain('e2e'); }); }); describe('e2e/rauchtest.spec.ts - der Kopf', () => { it('nennt keine Zahl von Modultests', () => { // Ziffern samt Tausendertrennern - auch schmales und geschuetztes // Leerzeichen, mit denen eine Zahl geschrieben sein koennte. const zahl = /\d[\d.,\u202f\u00a0 ]*Modultests/.exec(RAUCHTEST); expect(zahl?.[0] ?? null, 'Zahlenangabe vor "Modultests"').toBeNull(); }); it('behaelt die Abgrenzung, die den Rauchtest begruendet', () => { // Ohne die Zahl muss der Satz weiter tragen: Die Modultests starten das // Programm nicht, dieser Test tut es. const kopf = RAUCHTEST.replace(/\s+/g, ' '); expect(kopf).toContain('Modultests pruefen Fachlogik, Ausdruck und Oberflaechenfunktionen'); expect(kopf).toContain('aber keiner von ihnen startet das Programm'); }); }); describe('.githooks/pre-commit - die Einrichtung', () => { /** Richtet ein Skript aus package.json den Haken selbsttaetig ein? */ function richtetSelbstEin(): boolean { return Object.values(PAKET.scripts ?? {}).some((s) => s.includes('core.hooksPath')); } const WARNUNG = 'Dieser Befehl gehoert einmal in JEDE Arbeitskopie'; const KEINE_SELBSTEINRICHTUNG = 'Das Projekt fuehrt ihn nirgends selbsttaetig aus.'; it('sagt der Haken, wie es um seine Einrichtung wirklich steht', () => { if (richtetSelbstEin()) { // Sobald package.json den Haken einrichtet, waere der Vorbehalt falsch. expect(HAKENTEXT, 'Vorbehalt trotz Selbsteinrichtung').not.toContain(KEINE_SELBSTEINRICHTUNG); return; } expect(HAKENTEXT, 'Hinweis, dass der Befehl je Arbeitskopie faellig ist').toContain(WARNUNG); expect(HAKENTEXT, 'Hinweis, dass ihn nichts selbsttaetig ausfuehrt').toContain( KEINE_SELBSTEINRICHTUNG, ); }); it('nennt der Haken den Befehl, mit dem er eingerichtet wird', () => { expect(HAKENTEXT).toContain('git config core.hooksPath .githooks'); }); }); describe('.githooks/pre-commit - die Grenze der Zusage', () => { /* * Der Kopf sagt zu, der Lauf fange ab, "was sich sonst erst beim Anwender * zeigt". Das gilt fuer den Arbeitsbaum. Die vier Stufen von gate:schnell * bekommen keinen Pfad und lesen keinen Bereitstellungsbereich - bei * teilweiser Bereitstellung sagt der Lauf nichts ueber den Commit aus. Diese * Grenze gehoert daneben, sonst ist die Zusage zu weit gefasst. */ it('sagt, dass der Arbeitsbaum geprueft wird und nicht der bereitgestellte Stand', () => { expect(HAKENTEXT, 'Grenze der Zusage').toContain( 'Geprueft wird dabei der Arbeitsbaum, nicht der bereitgestellte Stand', ); }); it('nennt die Handgriffe, bei denen die Grenze greift', () => { expect(HAKENTEXT, 'Hinweis auf teilweise Bereitstellung').toContain('git add -p'); expect(HAKENTEXT, 'Hinweis auf git commit mit Pfad').toContain('git commit '); }); });