++ fix e controlli

This commit is contained in:
2026-07-30 17:05:39 +02:00
parent 712336d652
commit 440d63a6b9
54 changed files with 4921 additions and 1322 deletions

View File

@@ -0,0 +1,163 @@
<?php
namespace Tests\Unit;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
/**
* Guardia contro il ritorno dei vocabolari di stile paralleli.
*
* L'applicazione aveva quattro modi di scrivere un pulsante: style= inline con
* i colori a mano (piu' onmouseover per l'hover), catene di utility Tailwind
* ripetute a memoria, .btn-action* in sei tinte e .btn-primary-* in due. La
* stessa azione cambiava aspetto da una pagina all'altra e nessuno di quei
* pulsanti aveva uno stato di focus: chi naviga da tastiera non vedeva dove si
* trovava.
*
* Adesso c'e' <x-btn> e basta. Questo test non giudica l'estetica: verifica che
* i modi vecchi non rientrino dalla finestra, cosa che succede da sola alla
* prossima pagina scritta copiando una vecchia.
*
* Legge i sorgenti, quindi non serve ne' applicazione ne' database.
*/
class StileUnificatoTest extends TestCase
{
/** Viste dell'applicazione: quelle pubblicate da pacchetti terzi non ci riguardano. */
private const ESCLUSI = [
'resources/views/vendor/',
// I template dei PDF non sono pagine: li impagina dompdf, che di
// Tailwind e delle nostre classi non sa niente.
'resources/views/pdf/',
];
/**
* @return array<int, string> percorsi relativi delle viste da controllare
*/
private static function viste(): array
{
$radice = dirname(__DIR__, 2);
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($radice . '/resources/views', \FilesystemIterator::SKIP_DOTS)
);
$viste = [];
foreach ($iterator as $file) {
/** @var \SplFileInfo $file */
if (! $file->isFile() || ! str_ends_with($file->getFilename(), '.blade.php')) {
continue;
}
$relativo = str_replace('\\', '/', substr($file->getPathname(), strlen($radice) + 1));
foreach (self::ESCLUSI as $escluso) {
if (str_starts_with($relativo, $escluso)) {
continue 2;
}
}
$viste[] = $relativo;
}
sort($viste);
return $viste;
}
public static function vistePerProvider(): array
{
return array_map(fn (string $vista) => [$vista], self::viste());
}
private function contenuto(string $vista): string
{
return (string) file_get_contents(dirname(__DIR__, 2) . '/' . $vista);
}
#[DataProvider('vistePerProvider')]
public function test_nessuna_vista_usa_le_vecchie_classi_dei_pulsanti(string $vista): void
{
$html = $this->rimuoviCommenti($this->contenuto($vista));
$this->assertStringNotContainsString('btn-action', $html, "{$vista}: .btn-action non esiste piu', usa <x-btn>.");
$this->assertStringNotContainsString('btn-primary-', $html, "{$vista}: .btn-primary-* non esiste piu', usa <x-btn>.");
}
/**
* onmouseover/onmouseout servivano a simulare :hover su pulsanti con lo
* stile inline. Non funzionano da tastiera e non lasciano traccia nel
* foglio di stile: se ricompaiono, e' ricomparso anche il pulsante scritto
* a mano.
*/
#[DataProvider('vistePerProvider')]
public function test_nessuna_vista_simula_l_hover_con_javascript_inline(string $vista): void
{
$html = $this->rimuoviCommenti($this->contenuto($vista));
$this->assertStringNotContainsString('onmouseover', $html, "{$vista}: l'hover si scrive nel CSS, non in un attributo.");
$this->assertStringNotContainsString('onmouseout', $html, "{$vista}: l'hover si scrive nel CSS, non in un attributo.");
}
/**
* Un <button> o un <a> con le utility di riempimento addosso e' un pulsante
* scritto a mano: gli manca lo stato di focus e prima o poi diverge dagli
* altri. Il controllo guarda solo il tag di apertura, quindi non inciampa
* nelle celle di tabella o nei contenitori che usano le stesse utility.
*/
#[DataProvider('vistePerProvider')]
public function test_nessun_pulsante_e_riscritto_a_mano(string $vista): void
{
$html = $this->rimuoviCommenti($this->contenuto($vista));
preg_match_all('/<(?:button|a)\s[^>]*>/i', $html, $trovati);
// Molte viste non hanno pulsanti: senza questa riga il test risulta
// "rischioso" (nessuna asserzione) invece che superato.
$this->assertIsArray($trovati[0]);
foreach ($trovati[0] as $tag) {
$riempimento = preg_match('/\bbg-(?:indigo|red|green|emerald|amber|blue|gray)-\d{3}\b/', $tag) === 1;
$sfondoInline = preg_match('/style="[^"]*background\s*:/i', $tag) === 1;
$this->assertFalse(
$riempimento || $sfondoInline,
"{$vista}: pulsante con lo sfondo scritto a mano, usa <x-btn>.\n" . trim($tag)
);
}
}
/**
* Il componente e' l'unico posto in cui le classi .tm-btn si scrivono: se
* compaiono in una vista, qualcuno ha saltato <x-btn> e si e' portato
* dietro solo l'aspetto, non le regole.
*/
#[DataProvider('vistePerProvider')]
public function test_le_classi_del_sistema_non_si_scrivono_a_mano(string $vista): void
{
if ($vista === 'resources/views/components/btn.blade.php') {
$this->assertTrue(true, 'E\' il componente stesso.');
return;
}
$html = $this->rimuoviCommenti($this->contenuto($vista));
$this->assertDoesNotMatchRegularExpression(
'/class="[^"]*\btm-btn\b/',
$html,
"{$vista}: le classi tm-btn le mette <x-btn>, non la vista."
);
}
/**
* I commenti Blade spiegano proprio le regole che questi test verificano, e
* contengono quindi i nomi vietati. Toglierli evita di dover scrivere le
* spiegazioni a mezze parole per non far fallire il controllo.
*/
private function rimuoviCommenti(string $html): string
{
return (string) preg_replace('/\{\{--.*?--\}\}/s', '', $html);
}
}