import { describe, expect, it } from 'vitest' import { readFileSync } from 'node:fs' import { join } from 'node:path' const css = readFileSync(join(process.cwd(), 'app/globals.css'), 'utf8') describe('Design-Tokens', () => { it('enthaelt die Grundpalette aus dem Design-System', () => { for (const hex of ['#15181a', '#1d2124', '#262b2f', '#3a4147', '#e4e7e4', '#939c99', '#7a97a6']) { expect(css).toContain(hex) } }) it('enthaelt alle sechs Klassifizierungsfarben', () => { for (const hex of ['#8a918d', '#6d8a9c', '#4e8f72', '#b08637', '#b4543c', '#7e5e96']) { expect(css).toContain(hex) } }) it('haelt Flaechen eckig und Bedienelemente rund', () => { // Die Flaechenradien bleiben bei 2 px: eine Akte ist ein Blatt. const flaechen = [...css.matchAll(/--radius-(?!field|control)[\w-]+:\s*([^;]+);/g)].map((m) => m[1]!.trim(), ) expect(flaechen.length).toBeGreaterThan(0) for (const value of flaechen) expect(['0px', '2px']).toContain(value) // Und daneben genau zwei Radien fuer Bedienelemente. expect(css).toMatch(/--radius-field:\s*10px;/) expect(css).toMatch(/--radius-control:\s*999px;/) }) it('gibt jedem Knopf Rahmen, Ueberfahren und Druckrueckmeldung', () => { const knopf = css.slice(css.indexOf('.btn {'), css.indexOf('.btn-primary')) expect(knopf).toContain('border: 1px solid var(--color-line)') expect(knopf).toContain('border-radius: var(--radius-control)') // Ueberfahren und Druecken sind beide beschrieben ... expect(css).toMatch(/\.btn:hover/) expect(css).toMatch(/\.btn:active/) // ... und der Druck setzt ohne Verzoegerung ein, sonst bliebe ein // schneller Klick unsichtbar. const gedrueckt = css.slice(css.indexOf('.btn:active')) expect(gedrueckt.slice(0, 400)).toContain('transition-duration: 0ms') expect(gedrueckt.slice(0, 400)).toContain('inset 0 0 0') }) it('haelt Beschriftungen weiter weg als die Polsterung des Feldes', () => { // Die Regel, aus der die Zahlen folgen: eine Beschriftung, die naeher am // Rahmen steht als der Inhalt des Rahmens an demselben Rahmen, wirkt // angeklebt. Also muss der Abstand nach aussen groesser sein als die // Polsterung nach innen. Geprueft wird die Regel, nicht der Wert - wer die // Polsterung aendert, faellt hier auf. const feld = css.slice(css.indexOf('.field {'), css.indexOf('.field:hover')) const polsterung = /padding:\s*([\d.]+)rem\s+([\d.]+)rem/.exec(feld) expect(polsterung, feld).not.toBeNull() const [innenSenkrecht, innenWaagerecht] = [ Number(polsterung![1]), Number(polsterung![2]), ] const label = css.slice(css.indexOf('.field-label {')) const unten = /margin-block-end:\s*([\d.]+)rem/.exec(label.slice(0, 300)) expect(unten, label.slice(0, 300)).not.toBeNull() expect(Number(unten![1])).toBeGreaterThan(innenSenkrecht) const reihe = css.slice(css.indexOf('.field-row {')) const abstand = /gap:\s*([\d.]+)rem/.exec(reihe.slice(0, 300)) expect(abstand, reihe.slice(0, 300)).not.toBeNull() expect(Number(abstand![1])).toBeGreaterThan(innenWaagerecht) }) it('gibt jeder Spalte einer Listenzeile eine feste Breite', () => { // Ohne feste Breiten bestimmt jede Zeile ihre Spalten selbst, und der // Titel fängt in jeder Zeile woanders an. Geprüft wird, dass die Breiten // an **einer** Stelle stehen und nicht an den Aufrufstellen. for (const klasse of ['.record-key', '.record-label', '.record-meta']) { const block = css.slice(css.indexOf(`${klasse} {`)) expect(block.slice(0, 400), klasse).toMatch(/width:\s*var\(--record-[a-z]+,/) expect(block.slice(0, 400), klasse).toContain('flex: none') } // Die Stempelmarke ist überall gleich breit, sonst lesen sich // unterschiedliche Namen als Unordnung statt als Unterschied. const stempel = css.slice(css.indexOf('.stamp-mark {')) expect(stempel.slice(0, 500)).toMatch(/width:\s*var\(--stamp-width,/) // Und auf schmalen Schirmen fallen sie weg, statt überzulaufen. expect(css).toMatch(/@media \(max-width: 40rem\)/) }) it('kennt keine Schatten ausser dem Fokusring', () => { const shadows = [...css.matchAll(/--shadow-[\w-]+:\s*([^;]+);/g)].map((m) => m[1]!.trim()) expect(shadows.length).toBeGreaterThan(0) for (const value of shadows) expect(value).toBe('none') }) it('respektiert prefers-reduced-motion bei der Schwaerzung', () => { expect(css).toMatch(/@media \(prefers-reduced-motion: reduce\)/) }) })