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())); } }