Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
use OpenNIT\Core\AuthorColor;
/**
* Deckt das Notizbrett ab.
*
* Zwei Zusagen tragen das Modul: Jede Person erscheint überall in derselben
* Farbe, und ein Eintrag bleibt Text – aus einer Notiz darf nichts in die
* Seite gelangen. Dazu die Verdrahtung, ohne die ein Modul zwar existiert,
* aber nicht erreichbar ist.
*/
return static function (): void {
$wurzel = dirname(__DIR__);
// ── Farbe je Person ──────────────────────────────────────────────────
test('Die ersten zwoelf Konten bekommen verschiedene Farben', function () {
$farben = [];
for ($i = 1; $i <= AuthorColor::COUNT; $i++) {
$farben[] = AuthorColor::index($i);
}
assertSame(AuthorColor::COUNT, count(array_unique($farben)),
'Zwei Konten teilen sich eine Farbe: ' . implode(',', $farben));
});
test('Benachbarte Konten liegen sichtbar auseinander', function () {
// Wer nacheinander angelegt wird, sitzt meist im selben Team.
for ($i = 1; $i < AuthorColor::COUNT; $i++) {
$abstand = abs(AuthorColor::index($i) - AuthorColor::index($i + 1));
assertTrue($abstand > 1, 'Konten ' . $i . ' und ' . ($i + 1) . ' liegen zu nah beieinander');
}
});
test('Dieselbe Person bekommt immer dieselbe Farbe', function () {
assertSame(AuthorColor::index(42), AuthorColor::index(42));
assertSame(AuthorColor::css(7), AuthorColor::css(7));
});
test('Die Farbe kommt als Token, nie als Farbwert', function () {
for ($i = 0; $i <= 30; $i++) {
$css = AuthorColor::css($i);
assertTrue(str_starts_with($css, 'var(--author-'), 'Kein Token: ' . $css);
assertFalse(str_contains($css, '#'), 'Hex-Wert statt Token: ' . $css);
}
});
test('Jede Farbe ist in den Tokens hinterlegt', function () use ($wurzel) {
$tokens = (string)file_get_contents($wurzel . '/public/assets/css/tokens.css');
for ($i = 1; $i <= AuthorColor::COUNT; $i++) {
assertTrue(str_contains($tokens, '--author-' . $i . ':'), 'Token --author-' . $i . ' fehlt');
assertTrue(str_contains($tokens, '--author-' . $i . '-soft:'), 'Token --author-' . $i . '-soft fehlt');
}
});
test('Ein unbekannter Verfasser bekommt die neutrale Farbe', function () {
assertSame(AuthorColor::COUNT, AuthorColor::index(0));
assertSame(AuthorColor::COUNT, AuthorColor::index(-5));
});
test('Das Namenskuerzel bleibt auch bei ungewoehnlichen Namen lesbar', function () {
assertSame('TT', AuthorColor::initials('Theo Test'));
assertSame('LB', AuthorColor::initials('Lena Marie Brandt'));
assertSame('FR', AuthorColor::initials('friloo'));
assertSame('?', AuthorColor::initials(' '));
});
// ── Der Text bleibt Text ─────────────────────────────────────────────
test('Die Notiz wird beim Ausgeben maskiert', function () use ($wurzel) {
// Gespeichert wird Klartext; ohne Maskierung stuende er als Markup
// in der Seite.
$seite = (string)file_get_contents($wurzel . '/templates/teamboard/index.php');
assertTrue(str_contains($seite, '$this->e($n[\'body\'])'), 'Der Text wird ungefiltert ausgegeben');
$widget = (string)file_get_contents($wurzel . '/templates/dashboard/index.php');
assertTrue(str_contains($widget, '$this->e($_n[\'body\'])'), 'Das Widget gibt den Text ungefiltert aus');
});
test('Der Controller nimmt keine Steuerzeichen an', function () use ($wurzel) {
$quelle = (string)file_get_contents($wurzel . '/src/Controllers/TeamboardController.php');
assertTrue(str_contains($quelle, 'saubererText'), 'Die Eingabe wird nicht bereinigt');
assertTrue(str_contains($quelle, 'MAX_LENGTH'), 'Die Länge ist nicht begrenzt');
});
// ── Verdrahtung ──────────────────────────────────────────────────────
test('Der Modulschluessel steht an jeder noetigen Stelle', function () use ($wurzel) {
$stellen = [
'public/index.php',
'src/Controllers/Admin/SettingsController.php',
'templates/admin/settings/index.php',
'public/install.php',
'src/Core/Application.php',
'templates/layouts/main.php',
];
foreach ($stellen as $datei) {
$inhalt = (string)file_get_contents($wurzel . '/' . $datei);
assertTrue(str_contains($inhalt, 'teamboard'), 'Modulschlüssel fehlt in ' . $datei);
}
});
test('Jede Route haengt am Modulschalter und an einem Recht', function () use ($wurzel) {
$quelle = (string)file_get_contents($wurzel . '/src/Core/Application.php');
$von = strpos($quelle, "\$this->config['modules']['teamboard']");
assertTrue($von !== false, 'Der Routenblock hängt nicht am Modulschalter');
$block = substr($quelle, $von, 1200);
$ende = strpos($block, "\n }");
$block = substr($block, 0, $ende !== false ? $ende : 1200);
preg_match_all("/router->(?:get|post)\('(\/teamboard[^']*)'[^;]*;/", $block, $routen);
assertTrue(count($routen[0]) >= 4, 'Es fehlen Routen: ' . implode(', ', $routen[1]));
// Die Optionen dürfen aus einer gemeinsamen Variablen kommen –
// entscheidend ist, dass jede Route ein Recht mitbringt.
assertTrue(str_contains($block, "'capability' => 'view_teamboard'"),
'Der Routenblock nennt kein Recht');
foreach ($routen[0] as $zeile) {
assertTrue(str_contains($zeile, '$teamboardOpts') || str_contains($zeile, "'capability'"),
'Route ohne Recht: ' . trim($zeile));
}
// Die Sammelroute muss vor der {id}-Route stehen, sonst greift sie nie.
$store = strpos($block, "post('/teamboard',");
$id = strpos($block, "post('/teamboard/{id}'");
assertTrue($store !== false && $id !== false && $store < $id,
'Die {id}-Route steht vor der Sammelroute');
});
test('Die Migration legt Tabelle und Rechte an', function () use ($wurzel) {
$sql = (string)file_get_contents($wurzel . '/database/migrations/245_teamboard.sql');
assertTrue(str_contains($sql, 'CREATE TABLE IF NOT EXISTS `teamboard_notes`'), 'Die Tabelle fehlt');
assertTrue(str_contains($sql, 'view_teamboard'), 'Das Leserecht fehlt');
assertTrue(str_contains($sql, 'manage_teamboard'), 'Das Verwaltungsrecht fehlt');
// Ein zweiter Lauf darf nichts anfassen.
assertTrue(str_contains($sql, 'INSERT IGNORE'), 'Die Rechte werden nicht ohne Duplikate eingefügt');
assertFalse(str_contains(strtoupper($sql), 'TRUNCATE'), 'Die Migration leert eine Tabelle');
});
test('Fremde Eintraege verlangen das eigene Recht', function () use ($wurzel) {
$quelle = (string)file_get_contents($wurzel . '/src/Controllers/TeamboardController.php');
assertTrue(str_contains($quelle, 'manage_teamboard'), 'Das Recht für fremde Einträge wird nicht geprüft');
// Ändern und Löschen gehen beide über dieselbe Prüfung.
assertSame(2, substr_count($quelle, '$this->darfBearbeiten('),
'Nicht beide Wege prüfen die Zuständigkeit');
});
test('Schreibende Wege stehen im Audit-Log', function () use ($wurzel) {
$quelle = (string)file_get_contents($wurzel . '/src/Controllers/TeamboardController.php');
foreach (['note_created', 'note_updated', 'note_deleted'] as $art) {
assertTrue(str_contains($quelle, $art), 'Ohne Eintrag im Audit-Log: ' . $art);
}
});
test('Das Widget laedt die Stile seines Moduls mit', function () use ($wurzel) {
// Ohne die Modul-CSS stuende das Widget unformatiert im Dashboard.
$dash = (string)file_get_contents($wurzel . '/templates/dashboard/index.php');
assertTrue(str_contains($dash, '/assets/css/teamboard.css'), 'Die Modul-CSS wird nicht geladen');
assertTrue(str_contains($dash, "data-widget=\"teamboard\""), 'Das Widget fehlt');
});
};