Files
termanager2/tests/Feature/PiantineEditorTest.php
Francesco Picone 056d95cfcb Editor piantine: bozza automatica e pubblicazione con anteprima
- Il disegno è bozza e si salva da solo; i dati del territorio si
  modificano dall'editor e valgono subito nel gestionale.
- Un solo "Pubblica" (immagine + PDF), uguale in editor e gestione:
  anteprima preparata in coda su disco privato, conferma obbligatoria,
  rifiutata se nel frattempo è cambiato qualcosa di stampato.
- Stati Bozza / Pubblicata / Modifiche da pubblicare, calcolati
  sull'impronta di confine e dati stampati e sul PDF in uso.
- Gestione riscritta: stato, ricerca, filtri, storico paginato.
- Editor ridisegnato a tre zone, utilizzabile anche su tablet e telefono.
- Avvisi via evento (trait Avvisa + <x-avvisi>).
- .gitattributes: script shell sempre LF, altrimenti il container
  locale su Windows non parte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 12:08:34 +02:00

417 lines
17 KiB
PHP

<?php
namespace Tests\Feature;
use App\Livewire\Piantine\PiantineEditor;
use App\Livewire\Piantine\Pubblicazione;
use App\Models\Setting;
use App\Models\Territorio;
use App\Models\TerritorioMappa;
use App\Models\Tipologia;
use App\Models\User;
use App\Models\Zona;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use Livewire\Livewire;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
use Tests\TestCase;
/**
* L'editor a schermo intero: il disegno è bozza e si salva da solo, i dati
* del territorio si salvano subito nel gestionale, il PDF non si tocca finché
* non si pubblica.
*/
class PiantineEditorTest extends TestCase
{
use RefreshDatabase;
private const GEOMETRIA = [
'type' => 'Polygon',
'coordinates' => [[[13.35, 38.11], [13.37, 38.11], [13.37, 38.13], [13.35, 38.11]]],
];
private const ALTRA_GEOMETRIA = [
'type' => 'Polygon',
'coordinates' => [[[13.40, 38.11], [13.42, 38.11], [13.42, 38.13], [13.40, 38.11]]],
];
protected function setUp(): void
{
parent::setUp();
$this->withoutVite();
Storage::fake('public');
Storage::fake('local');
}
public function test_la_pagina_e_riservata_all_amministratore(): void
{
Role::findOr('utente', fn () => Role::create(['name' => 'utente', 'guard_name' => 'web']));
app(PermissionRegistrar::class)->forgetCachedPermissions();
$utente = User::create([
'name' => 'Utente semplice',
'email' => 'utente@example.test',
'password' => 'password-di-prova',
]);
$utente->assignRole('utente');
$this->actingAs($utente)->get(route('piantine.editor'))->assertForbidden();
$this->actingAs($this->admin())->get(route('piantine.editor'))->assertOk();
}
public function test_la_pagina_si_apre_su_una_piantina_esistente(): void
{
$mappa = TerritorioMappa::create(['nome' => 'Zona porto', 'geojson' => json_encode(self::GEOMETRIA)]);
$this->actingAs($this->admin())
->get(route('piantine.editor', $mappa->id))
->assertOk()
->assertSee('Zona porto');
}
public function test_un_disegno_crea_subito_una_bozza_senza_territorio(): void
{
Livewire::actingAs($this->admin())
->test(PiantineEditor::class)
->dispatch('geometria-modificata', geometry: self::GEOMETRIA, nuova: true)
->assertDispatched('piantina-aggiornata')
->assertDispatched('editor-indirizzo')
->set('nome', 'Zona porto — lato nord');
$mappa = TerritorioMappa::sole();
$this->assertNull($mappa->territorio_id);
$this->assertSame('Zona porto — lato nord', $mappa->nome);
$this->assertSame(self::GEOMETRIA, $mappa->geometria());
$this->assertSame(TerritorioMappa::STATO_BOZZA, $mappa->stato());
}
public function test_spostare_i_vertici_aggiorna_la_piantina_aperta(): void
{
$mappa = TerritorioMappa::create(['geojson' => json_encode(self::GEOMETRIA)]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->dispatch('geometria-modificata', geometry: self::ALTRA_GEOMETRIA, nuova: false);
$this->assertSame(1, TerritorioMappa::count());
$this->assertSame(self::ALTRA_GEOMETRIA, $mappa->fresh()->geometria());
}
public function test_un_disegno_nuovo_con_una_piantina_aperta_non_ne_sostituisce_il_confine(): void
{
$mappa = TerritorioMappa::create(['geojson' => json_encode(self::GEOMETRIA)]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->dispatch('geometria-modificata', geometry: self::ALTRA_GEOMETRIA, nuova: true);
$this->assertSame(2, TerritorioMappa::count());
$this->assertSame(self::GEOMETRIA, $mappa->fresh()->geometria());
}
public function test_una_geometria_non_valida_viene_respinta(): void
{
Livewire::actingAs($this->admin())
->test(PiantineEditor::class)
->dispatch('geometria-modificata', geometry: ['type' => 'Point', 'coordinates' => [1, 2]], nuova: true)
->dispatch('geometria-modificata', geometry: [
'type' => 'Polygon',
'coordinates' => [[[0, 0], [1, 0], [0, 0]]],
], nuova: true)
->dispatch('geometria-modificata', geometry: [
'type' => 'Polygon',
'coordinates' => [[[0, 0], [500, 0], [1, 1], [0, 0]]],
], nuova: true)
->assertDispatched('avviso');
$this->assertSame(0, TerritorioMappa::count());
}
public function test_il_territorio_si_crea_dall_editor_con_tutte_le_proprieta(): void
{
$zona = Zona::create(['nome' => 'Nord', 'attivo' => true]);
$tipologia = Tipologia::create(['nome' => 'Residenziale', 'attivo' => true]);
$mappa = TerritorioMappa::create(['geojson' => json_encode(self::GEOMETRIA)]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->set('territorioScelto', 'nuovo')
->set('numero', '42')
->set('zonaId', (string) $zona->id)
->set('tipologiaId', (string) $tipologia->id)
->set('confini', "Nord: via Roma\nSud: via Milano")
->call('creaTerritorio')
->assertHasNoErrors()
->assertSet('territorioScelto', fn ($v) => $v !== 'nuovo' && $v !== '');
$territorio = Territorio::where('numero', '42')->sole();
$this->assertTrue((bool) $territorio->attivo);
$this->assertSame($zona->id, $territorio->zona_id);
$this->assertSame($tipologia->id, $territorio->tipologia_id);
$this->assertStringContainsString('via Roma', $territorio->confini);
$this->assertSame($territorio->id, $mappa->fresh()->territorio_id);
}
public function test_in_creazione_i_campi_non_salvano_niente_prima_del_pulsante(): void
{
$mappa = TerritorioMappa::create(['geojson' => json_encode(self::GEOMETRIA)]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->set('territorioScelto', 'nuovo')
->set('numero', '4');
$this->assertSame(0, Territorio::count());
$this->assertNull($mappa->fresh()->territorio_id);
}
public function test_un_numero_gia_in_uso_non_crea_un_secondo_territorio(): void
{
Territorio::create(['numero' => '42', 'attivo' => true]);
$mappa = TerritorioMappa::create(['geojson' => json_encode(self::GEOMETRIA)]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->set('territorioScelto', 'nuovo')
->set('numero', '42')
->call('creaTerritorio')
->assertHasErrors('numero');
$this->assertSame(1, Territorio::where('numero', '42')->count());
$this->assertNull($mappa->fresh()->territorio_id);
}
public function test_scegliere_un_territorio_collega_subito_la_piantina(): void
{
$territorio = Territorio::create(['numero' => '12', 'attivo' => true, 'confini' => 'Nord: via Verdi']);
$mappa = TerritorioMappa::create(['geojson' => json_encode(self::GEOMETRIA)]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->set('territorioScelto', (string) $territorio->id)
->assertHasNoErrors()
->assertSet('numero', '12')
->assertSet('confini', 'Nord: via Verdi');
$this->assertSame($territorio->id, $mappa->fresh()->territorio_id);
}
/** La tendina leggeva `attivo` senza averlo caricato: tutti "disattivati". */
public function test_la_tendina_segna_disattivato_solo_chi_lo_e(): void
{
Territorio::create(['numero' => '12', 'attivo' => true]);
$mappa = TerritorioMappa::create(['geojson' => json_encode(self::GEOMETRIA)]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->assertSee('Territorio 12')
->assertDontSee('(disattivato)');
}
public function test_i_dati_del_territorio_si_salvano_subito_campo_per_campo(): void
{
$zona = Zona::create(['nome' => 'Sud', 'attivo' => true]);
$territorio = Territorio::create(['numero' => '12', 'attivo' => true]);
$mappa = TerritorioMappa::create(['territorio_id' => $territorio->id, 'geojson' => json_encode(self::GEOMETRIA)]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->set('confini', 'Est: via Garibaldi')
->set('zonaId', (string) $zona->id)
->assertHasNoErrors();
$territorio->refresh();
$this->assertSame('Est: via Garibaldi', $territorio->confini);
$this->assertSame($zona->id, $territorio->zona_id);
}
public function test_un_campo_non_valido_non_trascina_gli_altri(): void
{
Territorio::create(['numero' => '7', 'attivo' => true]);
$territorio = Territorio::create(['numero' => '12', 'attivo' => true]);
$mappa = TerritorioMappa::create(['territorio_id' => $territorio->id, 'geojson' => json_encode(self::GEOMETRIA)]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->set('numero', '7')
->assertHasErrors('numero')
->set('confini', 'Ovest: via Dante');
$territorio->refresh();
$this->assertSame('12', $territorio->numero);
$this->assertSame('Ovest: via Dante', $territorio->confini);
}
public function test_non_si_ruba_il_territorio_a_un_altra_piantina(): void
{
$territorio = Territorio::create(['numero' => '12', 'attivo' => true]);
TerritorioMappa::create(['territorio_id' => $territorio->id, 'geojson' => json_encode(self::GEOMETRIA)]);
$seconda = TerritorioMappa::create(['geojson' => json_encode(self::ALTRA_GEOMETRIA)]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $seconda->id])
->set('territorioScelto', (string) $territorio->id)
->assertHasErrors('territorioScelto')
->assertSet('territorioScelto', '');
$this->assertNull($seconda->fresh()->territorio_id);
}
public function test_scollegare_lascia_un_etichetta_riconoscibile(): void
{
$territorio = Territorio::create(['numero' => '12', 'attivo' => true]);
$mappa = TerritorioMappa::create(['territorio_id' => $territorio->id, 'geojson' => json_encode(self::GEOMETRIA)]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->set('territorioScelto', '');
$mappa->refresh();
$this->assertNull($mappa->territorio_id);
$this->assertSame('Ex territorio 12', $mappa->nome);
$this->assertNotNull(Territorio::find($territorio->id));
}
public function test_un_territorio_nel_cestino_resta_collegato(): void
{
$territorio = Territorio::create(['numero' => '12', 'attivo' => true]);
$mappa = TerritorioMappa::create(['territorio_id' => $territorio->id, 'geojson' => json_encode(self::GEOMETRIA)]);
$territorio->delete();
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->assertSet('territorioScelto', (string) $territorio->id)
->set('nome', 'Prova');
$mappa->refresh();
$this->assertSame($territorio->id, $mappa->territorio_id);
$this->assertNotNull($mappa->motivoNonPubblicabile());
}
public function test_zona_e_tipologia_si_creano_dall_editor_e_restano_selezionate(): void
{
$territorio = Territorio::create(['numero' => '12', 'attivo' => true]);
$mappa = TerritorioMappa::create(['territorio_id' => $territorio->id, 'geojson' => json_encode(self::GEOMETRIA)]);
$componente = Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->set('nuovaZona', 'Litorale')
->call('creaZona')
->assertHasNoErrors()
->set('nuovaTipologia', 'Commerciale')
->call('creaTipologia')
->assertHasNoErrors();
$zona = Zona::where('nome', 'Litorale')->sole();
$tipologia = Tipologia::where('nome', 'Commerciale')->sole();
$componente->assertSet('zonaId', (string) $zona->id);
$componente->assertSet('tipologiaId', (string) $tipologia->id);
$territorio->refresh();
$this->assertSame($zona->id, $territorio->zona_id);
$this->assertSame($tipologia->id, $territorio->tipologia_id);
}
public function test_una_zona_gia_esistente_non_viene_duplicata(): void
{
Zona::create(['nome' => 'Nord', 'attivo' => true]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class)
->set('nuovaZona', 'Nord')
->call('creaZona')
->assertHasErrors('nuovaZona');
$this->assertSame(1, Zona::where('nome', 'Nord')->count());
}
public function test_pubblica_apre_la_finestra_solo_se_si_puo(): void
{
$scollegata = TerritorioMappa::create(['geojson' => json_encode(self::GEOMETRIA)]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $scollegata->id])
->call('pubblica')
->assertNotDispatched('pubblica-piantina')
->assertDispatched('avviso');
$territorio = Territorio::create(['numero' => '12', 'attivo' => true]);
$collegata = TerritorioMappa::create(['territorio_id' => $territorio->id, 'geojson' => json_encode(self::ALTRA_GEOMETRIA)]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $collegata->id])
->call('pubblica')
->assertDispatchedTo(Pubblicazione::class, 'pubblica-piantina', mappaId: $collegata->id);
}
public function test_scartare_le_modifiche_riporta_il_confine_pubblicato(): void
{
$territorio = Territorio::create(['numero' => '12', 'attivo' => true]);
$mappa = TerritorioMappa::create([
'territorio_id' => $territorio->id,
'geojson' => json_encode(self::ALTRA_GEOMETRIA),
'geojson_pubblicato' => json_encode(self::GEOMETRIA),
'pubblicata_at' => now(),
]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->call('scartaModifiche');
$this->assertSame(self::GEOMETRIA, $mappa->fresh()->geometria());
}
public function test_eliminare_toglie_disegno_e_immagine_ma_non_il_territorio(): void
{
Storage::disk('public')->put('territori-mappe/piantina-1.jpg', 'x');
$territorio = Territorio::create(['numero' => '12', 'attivo' => true]);
$mappa = TerritorioMappa::create([
'territorio_id' => $territorio->id,
'geojson' => json_encode(self::GEOMETRIA),
'image_path' => 'territori-mappe/piantina-1.jpg',
]);
Livewire::actingAs($this->admin())
->test(PiantineEditor::class, ['mappa' => $mappa->id])
->call('elimina')
->assertSet('mappaId', null)
->assertDispatched('piantina-rimossa', mappaId: $mappa->id);
$this->assertSame(0, TerritorioMappa::count());
$this->assertNotNull(Territorio::find($territorio->id));
Storage::disk('public')->assertMissing('territori-mappe/piantina-1.jpg');
}
public function test_dati_mappa_esclude_le_geometrie_illeggibili(): void
{
TerritorioMappa::create(['geojson' => json_encode(self::GEOMETRIA)]);
TerritorioMappa::create(['geojson' => 'non è json']);
$dati = Livewire::actingAs($this->admin())
->test(PiantineEditor::class)
->instance()
->datiMappa(app(\App\Services\Piantine\PiantineArea::class));
$this->assertCount(1, $dati['piantine']);
$this->assertSame(TerritorioMappa::STATO_BOZZA, $dati['piantine'][0]['stato']);
}
private function admin(): User
{
Role::firstOrCreate(['name' => 'amministratore', 'guard_name' => 'web']);
app(PermissionRegistrar::class)->forgetCachedPermissions();
Setting::firstOrCreate([], ['congregazione_nome' => 'Congregazione di prova']);
$utente = User::firstOrCreate(
['email' => 'admin@example.test'],
['name' => 'Amministratore', 'password' => 'password-di-prova'],
);
$utente->assignRole('amministratore');
return $utente;
}
}