test(frontend): Wächter gegen deutschen Oberflächentext – und fünf Reste
Konsolidierung: Bestandsaufnahme auf den Stand vom 24.09. gebracht (Punkte 1, 2, 2b und die Tests für documents/tags sind erledigt; sharing, rescan, audit und thumbnails bleiben offen; jsdom ist seit dem 18.09. drin). Beim Nachsehen fielen fünf Übersetzungsreste auf: label="Ganztägig", aria-label="Kalendermenü", label="Bearbeiten" und zweimal "Löschen". Alle in **doppelt** gequoteten JSX-Attributen – die Ersetzungsliste hatte nur die einfach gequoteten, und das damalige Prüfskript hielt Einzelwörter ohne Leerzeichen für Bezeichner. Die Prüfung steht deshalb jetzt als Test, nicht als Skript. Sie läuft bei jedem npm test mit. Der erste Wächter taugte nichts, und das kam nur durch die Mutation heraus: Er prüfte allein auf Umlaute und liess "Papierkorb ist leer" durch. Wer einen Wächter baut, muss ihn einmal angreifen. Jetzt Umlaute plus eine Wortliste mit Wortgrenzen, nachgewiesen an vier Mutationen. Kommentare werden vorher entfernt – mit einem kleinen Durchlauf statt zeilenweise: Blockkommentare haben Fortsetzungszeilen ohne führenden Stern, die beim ersten Versuch fünf Fehlalarme erzeugten. Ehrlich dazu: Das ist ein Netz, kein Beweis. Ein deutscher Satz ohne Umlaut und ohne gelistetes Wort käme durch. Steht so im Test. @types/node kam dazu, weil der Test den Quelltext von der Platte liest. Geprüft: tsc sauber, 48 Vitest-Tests, alle 234 Prüfungen der sieben Selbsttest-Suiten grün. Gebautes Bündel gegengeprüft – deutsche Texte weg, englische da. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e1259dd55c
commit
a7528d7365
8 files changed
+261
-15
No files matched your search
+33
-9
@@ -1,5 +1,21 @@
|
||||
# Bestandsaufnahme, 12.09.2026
|
||||
|
||||
> **Stand 24.09.2026.** Dieses Dokument ist die Aufnahme vom 12.09. Was seither
|
||||
> erledigt wurde, steht an Ort und Stelle durchgestrichen oder vermerkt; die
|
||||
> Kurzfassung:
|
||||
>
|
||||
> | | |
|
||||
> |---|---|
|
||||
> | Punkte 1, 2, 2b, 3 (teilweise) | ✅ erledigt |
|
||||
> | Go-Tests `documents`, `tags` | ✅ 12 Testfunktionen |
|
||||
> | Go-Tests `sharing`, `rescan`, `audit`, `thumbnails` | **offen** |
|
||||
> | Frontend-DOM-Tests | ✅ jsdom seit 18.09., 46 Tests |
|
||||
>
|
||||
> Dazwischen lag eine Woche Oberflächenarbeit, die hier nicht auftaucht, weil
|
||||
> sie keine Befunde abarbeitete: Rückmeldungen mit Rückgängig, Platzhalter,
|
||||
> Leerzustände, Sprache vereinheitlicht, `Intl`. Steht in
|
||||
> [TESTING.md](TESTING.md).
|
||||
|
||||
Einmal durch die ganze Cloud: Code, Sicherheit, offene Enden. Geordnet nach
|
||||
dem, was passieren kann — nicht nach Aufwand.
|
||||
|
||||
@@ -160,14 +176,14 @@ Zwei Tests in `pkg/middleware/logger_test.go` halten es fest.
|
||||
|
||||
**Schwere: mittel. Nicht was heute kaputt ist, sondern was morgen still kaputtgeht.**
|
||||
|
||||
| Paket | Zeilen | Test | Aussenpruefung |
|
||||
| Paket | Zeilen | Test (24.09.) | Aussenpruefung |
|
||||
|---|---:|---:|---|
|
||||
| `internal/tags` | 1132 | **0** | keine |
|
||||
| `internal/documents` (OCR, Volltext) | 1008 | **0** | keine |
|
||||
| `internal/sharing` | 382 | **0** | Python-Selbsttest, 24 Prüfungen |
|
||||
| `internal/rescan` | 479 | **0** | Python-Selbsttest, 20 Prüfungen |
|
||||
| `internal/audit` | 207 | **0** | keine |
|
||||
| `internal/thumbnails` | 113 | **0** | keine |
|
||||
| `internal/tags` | 1338 | **4** ✅ | keine |
|
||||
| `internal/documents` (OCR, Volltext) | 1243 | **8** ✅ | keine |
|
||||
| `internal/sharing` | 347 | **0** | Python-Selbsttest, 24 Prüfungen |
|
||||
| `internal/rescan` | 435 | **0** | Python-Selbsttest, 20 Prüfungen |
|
||||
| `internal/audit` | 180 | **0** | keine |
|
||||
| `internal/thumbnails` | 95 | **0** | keine |
|
||||
|
||||
Bei `sharing` und `rescan` deckt wenigstens ein Selbsttest die Außenseite ab.
|
||||
Bei **`documents` und `tags` gar nichts** — und `documents` ist das Stück, das
|
||||
@@ -251,8 +267,16 @@ gibt:
|
||||
* **Die Fehlversuchssperren liegen im Arbeitsspeicher.** Ein Neustart des
|
||||
Containers hebt jede Sperre auf. Für den Hausgebrauch vertretbar; wer es
|
||||
anders will, braucht eine Tabelle.
|
||||
* **Frontend ohne Komponententests.** `vitest` läuft jetzt (37 Tests), aber nur
|
||||
über reine Funktionen — für DOM-Tests fehlt `jsdom` in den devDependencies.
|
||||
* ~~**Frontend ohne Komponententests.**~~ ✅ **18.09.2026:** `jsdom` ist in den
|
||||
devDependencies, die Umgebung bleibt `node` (schnell); wer ein DOM braucht,
|
||||
schreibt `// @vitest-environment jsdom` an den Anfang seiner Datei. 46 Tests,
|
||||
davon vier gegen ein DOM. Der erste davon hat sofort einen Fehler gefunden –
|
||||
in der Behebung vom Vortag (`useBackClose`).
|
||||
|
||||
**Was er nicht leistet, steht dabei:** Den ursprünglichen Fehler bildet jsdom
|
||||
nicht ab; es verwirft einen Verlaufssprung, sobald `pushState` folgt. Dafür
|
||||
bräuchte es einen echten Browser (Playwright). Nachgeprüft, nicht vermutet:
|
||||
mit der alten, kaputten Fassung blieben alle Tests grün.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+40
@@ -1562,6 +1562,46 @@ bleibt `node` (schnell); wer ein DOM braucht, schreibt
|
||||
|
||||
---
|
||||
|
||||
## Ein Wächter für die Sprache (24.09.2026)
|
||||
|
||||
Sechs Tage nach der Übersetzung fielen beim Nachsehen **fünf Reste** auf:
|
||||
`label="Ganztägig"`, `aria-label="Kalendermenü"`, `label="Bearbeiten"` und
|
||||
zweimal `"Löschen"`. Alle in **doppelt** gequoteten JSX-Attributen – die
|
||||
Ersetzungsliste hatte nur die einfach gequoteten, und das damalige Prüfskript
|
||||
hielt Einzelwörter ohne Leerzeichen für Bezeichner.
|
||||
|
||||
Deshalb steht die Prüfung jetzt als Test (`src/sprache.test.ts`) statt als
|
||||
Skript, das man einmal laufen lässt. Sie läuft bei jedem `npm test` mit.
|
||||
|
||||
**Der erste Wächter taugte nichts, und das kam nur durch die Mutation heraus:**
|
||||
Er prüfte allein auf Umlaute und liess `"Papierkorb ist leer"` anstandslos
|
||||
durch. Wer einen Wächter baut, muss ihn einmal angreifen.
|
||||
|
||||
Jetzt: Umlaute **plus** eine Liste häufiger deutscher Wörter mit Wortgrenzen
|
||||
(sonst schlägt "ist" in "list" an). Kommentare werden vorher entfernt – und
|
||||
zwar richtig, mit einem kleinen Durchlauf statt zeilenweise: Blockkommentare
|
||||
haben Fortsetzungszeilen ohne führenden Stern, die beim ersten Versuch fünf
|
||||
Fehlalarme erzeugt haben.
|
||||
|
||||
Nachgewiesen an vier Mutationen, alle werden rot:
|
||||
|
||||
| eingeschleuster Text | erkannt an |
|
||||
|---|---|
|
||||
| `Papierkorb ist leer` | "ist", "leer" |
|
||||
| `Loeschen fehlgeschlagen` | "Loeschen", "fehlgeschlagen" |
|
||||
| `Ganztägig` | Umlaut |
|
||||
| `Bitte warten` | "bitte" |
|
||||
|
||||
> **Ein Netz, kein Beweis.** Ein deutscher Satz ohne Umlaut und ohne eines der
|
||||
> gelisteten Wörter käme durch – "Datum bereits vergeben" etwa. Der Test
|
||||
> fängt, was erfahrungsgemäß passiert: ein vergessenes Attribut beim
|
||||
> Übersetzen. Wer die Liste ergänzt, weil einmal etwas durchgerutscht ist, tut
|
||||
> genau das Richtige.
|
||||
|
||||
- [x] Gebautes Bündel enthält keine deutschen Oberflächentexte mehr –
|
||||
gegengeprüft, dass die englischen drin sind (sonst prüfte der Grep nur
|
||||
sich selbst)
|
||||
|
||||
## Wochentage und Monate aus `Intl` (20.09.2026)
|
||||
|
||||
Sechs handgeschriebene Arrays in drei Dateien, und `Intl` kam im ganzen Projekt
|
||||
|
||||
Generated
+18
@@ -19,6 +19,7 @@
|
||||
"@tailwindcss/vite": "^4.3.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^26.6.2",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"jsdom": "^26.1.0",
|
||||
@@ -2914,6 +2915,16 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.2.tgz",
|
||||
"integrity": "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
|
||||
@@ -6371,6 +6382,13 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "8.9.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
|
||||
"integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unicode-canonical-property-names-ecmascript": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@tailwindcss/vite": "^4.3.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^26.6.2",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"jsdom": "^26.1.0",
|
||||
|
||||
@@ -221,7 +221,7 @@ export default function EventDialog({ event, defaultStart, calendars, calendarId
|
||||
)}
|
||||
|
||||
<div style={{ marginBottom: 14 }}>
|
||||
<Toggle checked={allDay} onChange={setAllDay} disabled={readOnly} label="Ganztägig" />
|
||||
<Toggle checked={allDay} onChange={setAllDay} disabled={readOnly} label="All day" />
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
|
||||
|
||||
@@ -582,7 +582,7 @@ export default function CalendarPage() {
|
||||
<ConfirmDialog
|
||||
title="Delete calendar"
|
||||
message={`Permanently delete "${deleteCalendar.name}" and every event in it?`}
|
||||
confirmLabel="Löschen" danger
|
||||
confirmLabel="Delete" danger
|
||||
onConfirm={confirmDeleteCalendar}
|
||||
onCancel={() => setDeleteCalendar(null)}
|
||||
/>
|
||||
@@ -642,7 +642,7 @@ function CalMenu({ onRename, onExport, onImport, onDelete, onShare, own, canWrit
|
||||
}, [open])
|
||||
return (
|
||||
<div ref={ref} style={{ position: 'relative', flexShrink: 0 }}>
|
||||
<button onClick={() => setOpen(o => !o)} aria-label="Kalendermenü" style={{
|
||||
<button onClick={() => setOpen(o => !o)} aria-label="Calendar menu" style={{
|
||||
border: 'none', background: 'none', cursor: 'pointer', color: 'var(--color-text-muted)', padding: '0 2px',
|
||||
}}>⋮</button>
|
||||
{open && (
|
||||
@@ -651,11 +651,11 @@ function CalMenu({ onRename, onExport, onImport, onDelete, onShare, own, canWrit
|
||||
background: 'var(--color-surface)', border: '1px solid var(--color-border)', borderRadius: 8,
|
||||
boxShadow: '0 4px 14px rgba(0,0,0,0.25)', overflow: 'hidden',
|
||||
}}>
|
||||
{own && <MenuRow icon={<RenameIcon />} label="Bearbeiten" onClick={() => { setOpen(false); onRename() }} />}
|
||||
{own && <MenuRow icon={<RenameIcon />} label="Edit" onClick={() => { setOpen(false); onRename() }} />}
|
||||
{own && <MenuRow icon={<PeopleIcon />} label="Freigeben…" onClick={() => { setOpen(false); onShare() }} />}
|
||||
<MenuRow icon={<DownloadIcon />} label="Exportieren (.ics)" onClick={() => { setOpen(false); onExport() }} />
|
||||
{canWrite && <MenuRow label="Importieren (.ics)" onClick={() => { setOpen(false); onImport() }} />}
|
||||
{own && <MenuRow icon={<TrashIcon />} label="Löschen" danger onClick={() => { setOpen(false); onDelete() }} />}
|
||||
{own && <MenuRow icon={<TrashIcon />} label="Delete" danger onClick={() => { setOpen(false); onDelete() }} />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
/**
|
||||
* Die Oberfläche spricht Englisch – dieser Test hält sie dabei.
|
||||
*
|
||||
* Am 18.09.2026 wurde der Kalender übersetzt, 80 Stellen. Sechs Tage später
|
||||
* fielen beim Nachsehen **vier Reste** auf: `label="Ganztägig"`,
|
||||
* `aria-label="Kalendermenü"`, zweimal `confirmLabel="Löschen"`. Alle in
|
||||
* doppelt gequoteten JSX-Attributen – meine Ersetzungsliste hatte nur die
|
||||
* einfach gequoteten, und mein damaliges Prüfskript hielt Einzelwörter ohne
|
||||
* Leerzeichen für Bezeichner und übersprang sie.
|
||||
*
|
||||
* Genau deshalb steht die Prüfung jetzt hier statt in einem Skript, das man
|
||||
* einmal laufen lässt: Sie läuft bei jedem `npm test` mit.
|
||||
*
|
||||
* **Kommentare bleiben deutsch** und werden übersprungen. Erkannt wird nur,
|
||||
* was in Anführungszeichen oder zwischen Tags steht.
|
||||
*/
|
||||
|
||||
const hier = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
/**
|
||||
* Woran man Deutsch erkennt.
|
||||
*
|
||||
* **Umlaute allein genügen nicht.** Die erste Fassung dieses Tests prüfte nur
|
||||
* darauf – und liess „Papierkorb ist leer" anstandslos durch. Aufgefallen ist
|
||||
* das, weil der Test mutiert wurde: Wer einen Wächter baut, muss ihn einmal
|
||||
* angreifen, sonst bewacht er nichts.
|
||||
*
|
||||
* Deshalb zusätzlich eine Liste häufiger deutscher Wörter, mit Wortgrenzen
|
||||
* geprüft – sonst schlüge „ist" in „list" an. Bewusst **nicht** darin: Wörter,
|
||||
* die es im Englischen genauso gibt (Name, Start, Status, Import, Server) und
|
||||
* solche, die zu leicht danebengreifen (man, war).
|
||||
*
|
||||
* **Das ist ein Netz, kein Beweis.** Ein deutscher Satz ohne Umlaut und ohne
|
||||
* eines dieser Wörter käme durch – „Datum bereits vergeben" etwa. Der Test
|
||||
* fängt, was erfahrungsgemäß passiert (ein vergessenes Attribut beim
|
||||
* Übersetzen), nicht jeden denkbaren Fall. Wer hier etwas ergänzt, weil es
|
||||
* einmal durchgerutscht ist, tut genau das Richtige.
|
||||
*/
|
||||
const umlaute = /[äöüßÄÖÜ]/
|
||||
const deutscheWoerter = new RegExp(
|
||||
'\\b(' + [
|
||||
// Funktionswörter
|
||||
'der', 'die', 'das', 'den', 'dem', 'des', 'ein', 'eine', 'einen', 'einem',
|
||||
'und', 'oder', 'nicht', 'kein', 'keine', 'keinen', 'ist', 'sind', 'wird',
|
||||
'werden', 'wurde', 'mit', 'vom', 'zum', 'zur', 'aus', 'bei', 'nach',
|
||||
'durch', 'ohne', 'gegen', 'noch', 'schon', 'auch', 'nur', 'sehr', 'mehr',
|
||||
'alle', 'alles', 'sich', 'wenn', 'dann', 'hier', 'wie',
|
||||
// Wörter der Oberfläche
|
||||
'Datei', 'Dateien', 'Ordner', 'Termin', 'Termine', 'Kalender',
|
||||
'Papierkorb', 'Einstellungen', 'Freigabe', 'Etikett', 'Absender',
|
||||
'Speichern', 'Abbrechen', 'Bearbeiten', 'Hochladen', 'Herunterladen',
|
||||
'Suchen', 'Anmelden', 'Abmelden', 'Benutzer', 'Nutzer', 'Passwort',
|
||||
'Fehler', 'Hinweis', 'Warnung', 'leer', 'neu',
|
||||
// Wörter, an denen typische Meldungen hängen – auch in der
|
||||
// umlautfreien Schreibweise, die beim Tippen gern entsteht.
|
||||
'fehlgeschlagen', 'erfolgreich', 'gespeichert', 'geladen', 'konnte',
|
||||
'bitte', 'entfernen', 'aendern', 'hinzufuegen', 'Loeschen', 'Schliessen',
|
||||
'Groesse', 'Aendern',
|
||||
].join('|') + ')\\b', 'i')
|
||||
|
||||
function istDeutsch(text: string): boolean {
|
||||
return umlaute.test(text) || deutscheWoerter.test(text)
|
||||
}
|
||||
|
||||
/** Einzelne Stellen, an denen Deutsch bleiben darf. Bisher keine. */
|
||||
const erlaubt: string[] = []
|
||||
|
||||
function quellen(ordner: string): string[] {
|
||||
return readdirSync(ordner).flatMap(name => {
|
||||
const pfad = join(ordner, name)
|
||||
if (statSync(pfad).isDirectory()) return quellen(pfad)
|
||||
if (!/\.tsx?$/.test(name) || name.includes('.test.')) return []
|
||||
return [pfad]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Kommentare unkenntlich machen, Zeilenumbrüche behalten.
|
||||
*
|
||||
* Zeilenweise „fängt die Zeile mit // an?" reicht nicht: Ein Blockkommentar
|
||||
* hat Fortsetzungszeilen ohne führenden Stern, und genau die wurden als
|
||||
* Zeichenketten gelesen – fünf Fehlalarme beim ersten Versuch. Also einmal
|
||||
* richtig durchgehen und dabei mitzählen, ob man gerade in einem Kommentar
|
||||
* oder in einer Zeichenkette steht.
|
||||
*
|
||||
* Ersetzt wird durch Leerzeichen statt zu löschen, damit Zeilennummern und
|
||||
* Spalten stimmen bleiben.
|
||||
*/
|
||||
function ohneKommentare(quelle: string): string {
|
||||
const out: string[] = []
|
||||
let i = 0
|
||||
let stringZeichen = ''
|
||||
while (i < quelle.length) {
|
||||
const c = quelle[i]
|
||||
const naechste = quelle[i + 1]
|
||||
if (stringZeichen) {
|
||||
if (c === '\\') { out.push(c, naechste ?? ''); i += 2; continue }
|
||||
if (c === stringZeichen) stringZeichen = ''
|
||||
out.push(c); i++; continue
|
||||
}
|
||||
if (c === '/' && naechste === '/') {
|
||||
while (i < quelle.length && quelle[i] !== '\n') { out.push(' '); i++ }
|
||||
continue
|
||||
}
|
||||
if (c === '/' && naechste === '*') {
|
||||
while (i < quelle.length && !(quelle[i] === '*' && quelle[i + 1] === '/')) {
|
||||
out.push(quelle[i] === '\n' ? '\n' : ' '); i++
|
||||
}
|
||||
out.push(' ', ' '); i += 2; continue
|
||||
}
|
||||
if (c === "'" || c === '"' || c === '`') stringZeichen = c
|
||||
out.push(c); i++
|
||||
}
|
||||
return out.join('')
|
||||
}
|
||||
|
||||
function deutscheTexte(pfad: string): string[] {
|
||||
const treffer: string[] = []
|
||||
ohneKommentare(readFileSync(pfad, 'utf8')).split('\n').forEach((zeile, i) => {
|
||||
for (const m of zeile.matchAll(/(['"])((?:(?!\1)[^\\\n])*)\1/g)) {
|
||||
if (istDeutsch(m[2]) && !erlaubt.includes(m[2])) {
|
||||
treffer.push(`${pfad}:${i + 1} ${m[0]}`)
|
||||
}
|
||||
}
|
||||
for (const m of zeile.matchAll(/>\s*([^<>{}\n]*?)\s*</g)) {
|
||||
if (m[1] && istDeutsch(m[1]) && !erlaubt.includes(m[1])) {
|
||||
treffer.push(`${pfad}:${i + 1} >${m[1]}<`)
|
||||
}
|
||||
}
|
||||
})
|
||||
return treffer
|
||||
}
|
||||
|
||||
describe('Sprache der Oberfläche', () => {
|
||||
it('enthält keinen deutschen Text mehr', () => {
|
||||
const gefunden = quellen(hier).flatMap(deutscheTexte)
|
||||
expect(gefunden, `Deutscher Oberflächentext:\n${gefunden.join('\n')}`).toEqual([])
|
||||
})
|
||||
|
||||
it('erkennt Deutsch überhaupt – auch in Attributen', () => {
|
||||
// Gegenprobe: Ohne sie wäre der Test oben grün, wenn das Muster kaputt
|
||||
// ist. Genau dieser Fehler ist am 18.09. passiert.
|
||||
const probe = '/tmp/aether-sprachprobe.tsx'
|
||||
const inhalt = [
|
||||
'// Ein Kommentar mit Umlaut: Größe – darf nicht auffallen',
|
||||
'const a = <Toggle label="Ganztägig" />',
|
||||
"const b = 'Löschen'",
|
||||
'const c = <p>Keine Termine für heute</p>',
|
||||
].join('\n')
|
||||
writeFileSync(probe, inhalt)
|
||||
const gefunden = deutscheTexte(probe)
|
||||
expect(gefunden).toHaveLength(3)
|
||||
expect(gefunden.join(' ')).toContain('Ganztägig')
|
||||
expect(gefunden.join(' ')).not.toContain('Kommentar')
|
||||
})
|
||||
})
|
||||
@@ -4,7 +4,9 @@
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
// Vites Zusaetze zum Import: ?url, ?raw und der Seiteneffekt-Import von CSS.
|
||||
"types": ["vite/client"],
|
||||
// "node" kam am 24.09.2026 dazu: sprache.test.ts liest den Quelltext von
|
||||
// der Platte, um deutschen Oberflaechentext zu finden.
|
||||
"types": ["vite/client", "node"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
Reference in new issue
Block a user