Initial commit: Pireon Admin — headless admin panel and public API
Laravel 13 + Filament 5 admin panel and REST API for a non-profit developer collective, backed by the Pireon OAuth identity server. The public site is a separate, server-rendered application consuming /api/public/v1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
195
tests/Feature/Panel/StaleTranslationsTest.php
Normal file
195
tests/Feature/Panel/StaleTranslationsTest.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Panel;
|
||||
|
||||
use App\Models\Page;
|
||||
use App\Models\Project;
|
||||
use App\Support\Health\Finding;
|
||||
use App\Support\Health\HealthReport;
|
||||
use App\Support\TranslationFreshness;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Translations the original has moved on without.
|
||||
*
|
||||
* The fault this catches is the one nothing else can see: the text is there in
|
||||
* every language, so every screen calls the record finished, and one of those
|
||||
* languages has been saying something the source stopped saying. It is worked
|
||||
* out from the change history, so what these tests really hold still is that
|
||||
* the history is being read the way it is written.
|
||||
*/
|
||||
class StaleTranslationsTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function translated(): Page
|
||||
{
|
||||
return Page::factory()->published()->create([
|
||||
'title' => ['es' => 'Uno', 'en' => 'One', 'ca' => 'U'],
|
||||
'slug' => ['es' => 'uno', 'en' => 'one', 'ca' => 'u'],
|
||||
'body' => ['es' => 'Texto', 'en' => 'Text', 'ca' => 'Text'],
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_record_nobody_has_edited_is_not_out_of_date(): void
|
||||
{
|
||||
$page = $this->translated();
|
||||
|
||||
// Written in three languages at once: whatever else is true of it,
|
||||
// none of them is behind another.
|
||||
$this->assertSame([], TranslationFreshness::for($page));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function editing_the_original_leaves_the_other_languages_behind(): void
|
||||
{
|
||||
$page = $this->translated();
|
||||
|
||||
$this->travel(1)->days();
|
||||
$page->setTranslation('title', 'es', 'Uno corregido')->save();
|
||||
|
||||
$stale = TranslationFreshness::for($page->fresh());
|
||||
|
||||
$this->assertSame(['en', 'ca'], array_keys($stale));
|
||||
|
||||
// And it says since when, which is what decides how urgent it is.
|
||||
$this->assertTrue($stale['ca']->isToday());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function catching_a_translation_up_clears_it(): void
|
||||
{
|
||||
$page = $this->translated();
|
||||
|
||||
$this->travel(1)->days();
|
||||
$page->setTranslation('body', 'es', 'Texto nuevo')->save();
|
||||
|
||||
$this->travel(1)->days();
|
||||
$page->fresh()->setTranslation('body', 'ca', 'Text nou')->save();
|
||||
|
||||
$stale = TranslationFreshness::for($page->fresh());
|
||||
|
||||
// Catalan caught up; English never did.
|
||||
$this->assertArrayNotHasKey('ca', $stale);
|
||||
$this->assertArrayHasKey('en', $stale);
|
||||
}
|
||||
|
||||
/**
|
||||
* A language with nothing in it is not out of date, it is untranslated —
|
||||
* and there is already a check that says so. Reporting both about one
|
||||
* record would just make two lists that never empty.
|
||||
*/
|
||||
#[Test]
|
||||
public function a_missing_translation_is_not_a_stale_one(): void
|
||||
{
|
||||
$page = Page::factory()->published()->create([
|
||||
'title' => ['es' => 'Uno'],
|
||||
'slug' => ['es' => 'uno'],
|
||||
]);
|
||||
|
||||
$this->travel(1)->days();
|
||||
$page->setTranslation('title', 'es', 'Uno corregido')->save();
|
||||
|
||||
$this->assertSame([], TranslationFreshness::for($page->fresh()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renaming the Spanish address does not make the Catalan text less true,
|
||||
* and slugs are the one translatable field that routinely changes on their
|
||||
* own — counting them would mark half the site out of date the first time
|
||||
* somebody tidied a URL.
|
||||
*/
|
||||
#[Test]
|
||||
public function renaming_a_slug_does_not_make_a_translation_stale(): void
|
||||
{
|
||||
$page = $this->translated();
|
||||
|
||||
$this->travel(1)->days();
|
||||
$page->setTranslation('slug', 'es', 'uno-mejor')->save();
|
||||
|
||||
$this->assertSame([], TranslationFreshness::for($page->fresh()));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function editing_a_translation_does_not_make_the_original_stale(): void
|
||||
{
|
||||
$page = $this->translated();
|
||||
|
||||
$this->travel(1)->days();
|
||||
$page->setTranslation('title', 'ca', 'U corregit')->save();
|
||||
|
||||
// The source language is never reported against itself, and touching
|
||||
// Catalan says nothing about English.
|
||||
$this->assertSame([], TranslationFreshness::for($page->fresh()));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function the_health_check_reports_it(): void
|
||||
{
|
||||
$page = $this->translated();
|
||||
|
||||
$this->travel(1)->days();
|
||||
$page->setTranslation('body', 'es', 'Texto corregido')->save();
|
||||
|
||||
$finding = app(HealthReport::class)->runAll()->firstWhere('key', 'stale-translations');
|
||||
|
||||
$this->assertInstanceOf(Finding::class, $finding);
|
||||
$this->assertSame(1, $finding->count);
|
||||
$this->assertStringContainsString('ca', $finding->samples[0]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_works_for_every_translated_type(): void
|
||||
{
|
||||
$project = Project::factory()->published()->create([
|
||||
'title' => ['es' => 'Proyecto', 'ca' => 'Projecte'],
|
||||
'slug' => ['es' => 'proyecto', 'ca' => 'projecte'],
|
||||
]);
|
||||
|
||||
$this->travel(1)->days();
|
||||
$project->setTranslation('title', 'es', 'Proyecto nuevo')->save();
|
||||
|
||||
$this->assertArrayHasKey('ca', TranslationFreshness::for($project->fresh()));
|
||||
}
|
||||
|
||||
/**
|
||||
* The filter's half: the answer cannot be a SQL condition, so it is worked
|
||||
* out per record and handed to the query as a list.
|
||||
*/
|
||||
#[Test]
|
||||
public function the_panel_can_ask_for_the_ones_that_fell_behind(): void
|
||||
{
|
||||
$behind = $this->translated();
|
||||
$fine = $this->translated();
|
||||
|
||||
$this->travel(1)->days();
|
||||
$behind->setTranslation('title', 'es', 'Uno corregido')->save();
|
||||
|
||||
$ids = TranslationFreshness::staleIds(Page::query());
|
||||
|
||||
$this->assertContains($behind->id, $ids);
|
||||
$this->assertNotContains($fine->id, $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* The answer is cached against the record's `updated_at`, so it has to be
|
||||
* recomputed the moment anything about that record changes.
|
||||
*/
|
||||
#[Test]
|
||||
public function the_cached_answer_does_not_survive_the_next_edit(): void
|
||||
{
|
||||
$page = $this->translated();
|
||||
|
||||
$this->assertSame([], TranslationFreshness::for($page));
|
||||
|
||||
$this->travel(1)->days();
|
||||
$page->setTranslation('title', 'es', 'Uno corregido')->save();
|
||||
|
||||
$this->assertNotSame([], TranslationFreshness::for($page->fresh()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user