Files
termanager2/tests/Feature/PaginaBackupImportTest.php
2026-07-30 17:05:39 +02:00

196 lines
7.5 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
use Tests\TestCase;
/**
* La pagina "Backup e import" servita per intero: rotta, permessi, layout e i
* tre componenti Livewire annidati.
*
* Serve perché il grosso di questa pagina è markup, e il markup non fallisce
* come il codice: una vista che non compila non rompe niente finché qualcuno non
* la apre. È già successo — `@disabled(...)` dentro un tag <x-btn> non viene
* compilato, il tag resta letterale nel PHP generato e la pagina muore con un
* errore di sintassi. Qui quella classe di errori si vede subito.
*
* Quello che questi test NON possono coprire: Alpine e il comportamento della
* console (apertura automatica, scorrimento, evidenziazione delle zone) girano
* nel browser, e qui non c'è un browser. Vanno provati a mano.
*/
class PaginaBackupImportTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
// L'immagine di test non contiene public/build: gli asset sono
// compilati in uno stage a parte, che serve al runtime e non qui. Senza
// questo, ogni pagina morirebbe su "Vite manifest not found" — un
// fallimento che non parla del markup, cioe' l'unica cosa che questi
// test guardano. Che la build degli asset funzioni lo dice `npm run
// build`, non PHPUnit.
$this->withoutVite();
}
private function admin(): User
{
Role::findOr('amministratore', fn () => Role::create(['name' => 'amministratore', 'guard_name' => 'web']));
app(PermissionRegistrar::class)->forgetCachedPermissions();
$utente = User::create([
'name' => 'Amministratore',
'email' => 'admin@example.test',
'password' => 'password-di-prova',
]);
$utente->assignRole('amministratore');
return $utente;
}
public function test_la_pagina_si_apre_per_l_amministratore(): void
{
$this->actingAs($this->admin())
->get(route('xml.exchange'))
->assertOk();
}
public function test_chi_non_e_amministratore_non_ci_entra(): void
{
$utente = User::create([
'name' => 'Assistente',
'email' => 'assistente@example.test',
'password' => 'password-di-prova',
]);
$this->actingAs($utente)
->get(route('xml.exchange'))
->assertForbidden();
}
/**
* Le cinque zone esistono e sono raggiungibili per ancora: l'indice in cima
* alla pagina ci salta sopra, e un id sbagliato lo renderebbe un pulsante
* che non fa niente.
*/
public function test_ci_sono_tutte_e_cinque_le_zone_con_la_loro_ancora(): void
{
$risposta = $this->actingAs($this->admin())->get(route('xml.exchange'));
foreach (['zona-backup', 'zona-ripristino', 'zona-pdf', 'zona-xml', 'zona-azzeramento'] as $zona) {
$risposta->assertSee('id="' . $zona . '"', false);
$risposta->assertSee('tmFocusZona(\'' . $zona . '\'', false);
}
}
/**
* La zona pericolosa è l'ultima. È il motivo per cui l'azzeramento è stato
* estratto in un componente suo: reso da BackupManager finiva a metà pagina,
* fra il ripristino e l'import, che è l'ultimo posto in cui si vuole trovare
* un pulsante che cancella tutto.
*/
public function test_la_zona_pericolosa_e_l_ultima_della_pagina(): void
{
$html = $this->actingAs($this->admin())->get(route('xml.exchange'))->getContent();
$posizioni = [];
foreach (['zona-backup', 'zona-ripristino', 'zona-pdf', 'zona-xml', 'zona-azzeramento'] as $zona) {
$posizioni[$zona] = strpos($html, 'id="' . $zona . '"');
$this->assertNotFalse($posizioni[$zona], "Zona {$zona} assente dalla pagina.");
}
$this->assertSame(
['zona-backup', 'zona-ripristino', 'zona-pdf', 'zona-xml', 'zona-azzeramento'],
array_keys($posizioni),
'Ordine atteso delle chiavi.'
);
$precedente = -1;
foreach ($posizioni as $zona => $posizione) {
$this->assertGreaterThan($precedente, $posizione, "La zona {$zona} è fuori ordine.");
$precedente = $posizione;
}
}
public function test_la_console_unica_e_nella_pagina(): void
{
$risposta = $this->actingAs($this->admin())->get(route('xml.exchange'));
$risposta->assertSee('tmConsoleOperazioni', false);
$risposta->assertSee('class="tm-console"', false);
}
/**
* Ordine di caricamento, che è il motivo per cui quel JavaScript sta in uno
* <script> nella pagina e non in resources/js/app.js.
*
* Alpine arriva col pacchetto di Livewire, in fondo al body, e valuta
* x-data="tmConsoleOperazioni()" quando parte: se la funzione non fosse
* ancora definita, la console sarebbe un riquadro morto. Il bundle di app.js
* non andrebbe bene perché è un modulo ES, che il browser rimanda a dopo il
* parsing del documento — cioè dopo Alpine.
*
* Il rapporto fra i due è invisibile guardando i file: si vede solo qui.
*/
public function test_la_console_e_definita_prima_che_alpine_parta(): void
{
$html = $this->actingAs($this->admin())->get(route('xml.exchange'))->getContent();
$definizione = strpos($html, 'window.tmConsoleOperazioni =');
$livewire = strpos($html, 'livewire.js');
$this->assertNotFalse($definizione, 'La definizione della console non è nella pagina.');
$this->assertNotFalse($livewire, 'Il pacchetto di Livewire non è nella pagina.');
$this->assertLessThan(
$livewire,
$definizione,
'La console va definita prima del pacchetto che avvia Alpine, altrimenti x-data non la trova.'
);
}
/**
* I cinque riquadri di log separati sono spariti: se ne torna uno, la pagina
* ricomincia a raccontare la stessa operazione in due punti diversi.
*/
public function test_non_sono_rimasti_riquadri_di_log_separati(): void
{
$risposta = $this->actingAs($this->admin())->get(route('xml.exchange'));
$risposta->assertDontSee('Console upload');
$risposta->assertDontSee('Log import PDF');
}
/**
* Il difetto che questo test presidia: se un tag di componente non viene
* compilato, resta nella pagina come testo. Non solleva, non rompe il
* layout in modo evidente — semplicemente il pulsante non c'è.
*/
public function test_nessun_tag_di_componente_e_rimasto_da_compilare(): void
{
$html = $this->actingAs($this->admin())->get(route('xml.exchange'))->getContent();
$this->assertStringNotContainsString('<x-btn', $html);
$this->assertStringNotContainsString('</x-btn>', $html);
}
/**
* I pulsanti passano tutti dal sistema unico. Un `style=` inline su un
* pulsante è il segnale che qualcuno è tornato a definirne uno per conto suo,
* senza stato di focus.
*/
public function test_i_pulsanti_usano_le_classi_del_sistema_unico(): void
{
$html = $this->actingAs($this->admin())->get(route('xml.exchange'))->getContent();
foreach (['tm-btn tm-btn-primary', 'tm-btn tm-btn-danger', 'tm-btn tm-btn-secondary'] as $classe) {
$this->assertStringContainsString($classe, $html, "Manca un pulsante {$classe}.");
}
}
}