Add WikiLinkResolver with deterministic 5-step resolution
Prefers current locale, then document default_locale, then any locale (lowest document_id), then slug match (legacy).
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Models\DocumentTranslation;
|
||||
use App\Services\WikiLinkResolver;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class WikiLinkResolverTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private WikiLinkResolver $resolver;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->resolver = new WikiLinkResolver();
|
||||
}
|
||||
|
||||
public function test_resolves_via_current_locale_title(): void
|
||||
{
|
||||
$doc = Document::factory()->create(['default_locale' => 'en', 'slug' => 'getting-started']);
|
||||
$doc->translations()->where('locale', 'en')->update(['title' => 'Getting Started']);
|
||||
DocumentTranslation::factory()->create([
|
||||
'document_id' => $doc->id,
|
||||
'locale' => 'ja',
|
||||
'title' => 'はじめに',
|
||||
]);
|
||||
|
||||
$resolved = $this->resolver->resolve('はじめに', 'ja');
|
||||
$this->assertSame($doc->id, $resolved->id);
|
||||
}
|
||||
|
||||
public function test_resolves_via_default_locale_when_current_locale_missing(): void
|
||||
{
|
||||
$doc = Document::factory()->create(['default_locale' => 'en']);
|
||||
$doc->translations()->where('locale', 'en')->update(['title' => 'Getting Started']);
|
||||
|
||||
$resolved = $this->resolver->resolve('Getting Started', 'ja');
|
||||
$this->assertSame($doc->id, $resolved->id);
|
||||
}
|
||||
|
||||
public function test_resolves_via_any_locale_deterministically(): void
|
||||
{
|
||||
// Two documents both have a 'fr' translation titled "Bonjour", neither is current/default
|
||||
$docA = Document::factory()->create(['default_locale' => 'en']);
|
||||
DocumentTranslation::factory()->create(['document_id' => $docA->id, 'locale' => 'fr', 'title' => 'Bonjour']);
|
||||
|
||||
$docB = Document::factory()->create(['default_locale' => 'en']);
|
||||
DocumentTranslation::factory()->create(['document_id' => $docB->id, 'locale' => 'fr', 'title' => 'Bonjour']);
|
||||
|
||||
$resolved = $this->resolver->resolve('Bonjour', 'ja');
|
||||
// Lower id wins (deterministic)
|
||||
$this->assertSame($docA->id, $resolved->id);
|
||||
}
|
||||
|
||||
public function test_resolves_via_slug_when_no_title_match(): void
|
||||
{
|
||||
$doc = Document::factory()->create(['slug' => 'unique-slug', 'default_locale' => 'en']);
|
||||
$doc->translations()->where('locale', 'en')->update(['title' => 'Whatever']);
|
||||
|
||||
$resolved = $this->resolver->resolve('unique-slug', 'ja');
|
||||
$this->assertSame($doc->id, $resolved->id);
|
||||
}
|
||||
|
||||
public function test_returns_null_when_nothing_matches(): void
|
||||
{
|
||||
$this->assertNull($this->resolver->resolve('Nonexistent', 'en'));
|
||||
}
|
||||
|
||||
public function test_current_locale_wins_over_default(): void
|
||||
{
|
||||
// Doc A has en title "Setup"; Doc B has ja title "Setup"
|
||||
$docA = Document::factory()->create(['default_locale' => 'en']);
|
||||
$docA->translations()->where('locale', 'en')->update(['title' => 'Setup']);
|
||||
|
||||
$docB = Document::factory()->create(['default_locale' => 'en']);
|
||||
$docB->translations()->where('locale', 'en')->update(['title' => 'Different']);
|
||||
DocumentTranslation::factory()->create(['document_id' => $docB->id, 'locale' => 'ja', 'title' => 'Setup']);
|
||||
|
||||
// Browsing in ja: ja-locale match (Doc B) should win over default-locale match (Doc A)
|
||||
$resolved = $this->resolver->resolve('Setup', 'ja');
|
||||
$this->assertSame($docB->id, $resolved->id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user