ver. 0.280: Articles frontend migration, class.Article removal, Settings facade cleanup
- Add 8 frontend methods to ArticleRepository (with Redis cache) - Create front\Views\Articles (rendering + utility methods) - Rewire front\view\Site::show() and front\controls\Site::route() to repo + Views - Update 5 article templates to use \front\Views\Articles:: - Convert front\factory\Articles and front\view\Articles to facades - Remove class.Article (entity + static methods migrated to repo + Views) - Remove front\factory\Settings facade (already migrated) - Fix: eliminate global $lang from articleNoindex(), inline page sort query - Tests: 450 OK, 1431 assertions (+13 new) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -683,6 +683,292 @@ class ArticleRepositoryTest extends TestCase
|
||||
$this->assertMatchesRegularExpression('/LIMIT\s+100\s+OFFSET\s+0/i', $dataSql);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// FRONTEND METHODS
|
||||
// =========================================================================
|
||||
|
||||
public function testArticleDetailsFrontendReturnsArticleWithRelations(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$mockDb->expects($this->once())
|
||||
->method('get')
|
||||
->with('pp_articles', '*', ['id' => 5])
|
||||
->willReturn(['id' => 5, 'status' => 1, 'show_title' => 1]);
|
||||
|
||||
$mockDb->expects($this->exactly(4))
|
||||
->method('select')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
[['lang_id' => 'pl', 'title' => 'Testowy', 'copy_from' => null]],
|
||||
[['id' => 10, 'src' => '/img/a.jpg']],
|
||||
[['id' => 20, 'src' => '/files/a.pdf']],
|
||||
[1, 2]
|
||||
);
|
||||
|
||||
$repo = new ArticleRepository($mockDb);
|
||||
$article = $repo->articleDetailsFrontend(5, 'pl');
|
||||
|
||||
$this->assertIsArray($article);
|
||||
$this->assertEquals(5, $article['id']);
|
||||
$this->assertArrayHasKey('language', $article);
|
||||
$this->assertEquals('Testowy', $article['language']['title']);
|
||||
$this->assertCount(1, $article['images']);
|
||||
$this->assertCount(1, $article['files']);
|
||||
}
|
||||
|
||||
public function testArticleDetailsFrontendReturnsNullForMissing(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->expects($this->once())
|
||||
->method('get')
|
||||
->willReturn(false);
|
||||
|
||||
$repo = new ArticleRepository($mockDb);
|
||||
$this->assertNull($repo->articleDetailsFrontend(999, 'pl'));
|
||||
}
|
||||
|
||||
public function testArticleDetailsFrontendCopyFromFallback(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$mockDb->method('get')
|
||||
->willReturn(['id' => 7, 'status' => 1]);
|
||||
|
||||
$mockDb->expects($this->exactly(5))
|
||||
->method('select')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
// First call: langs with copy_from
|
||||
[['lang_id' => 'en', 'title' => 'English', 'copy_from' => 'pl']],
|
||||
// Second call: copy_from fallback
|
||||
[['lang_id' => 'pl', 'title' => 'Polski']],
|
||||
// images
|
||||
[],
|
||||
// files
|
||||
[],
|
||||
// pages
|
||||
[]
|
||||
);
|
||||
|
||||
$repo = new ArticleRepository($mockDb);
|
||||
$article = $repo->articleDetailsFrontend(7, 'en');
|
||||
|
||||
$this->assertEquals('Polski', $article['language']['title']);
|
||||
}
|
||||
|
||||
private function createFetchAllMock(array $data): object
|
||||
{
|
||||
return new class($data) {
|
||||
private $data;
|
||||
public function __construct($data) { $this->data = $data; }
|
||||
public function fetchAll($mode = null) { return $this->data; }
|
||||
};
|
||||
}
|
||||
|
||||
public function testArticlesIdsReturnsSortedIds(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$mockDb->expects($this->once())
|
||||
->method('query')
|
||||
->willReturn($this->createFetchAllMock([
|
||||
['id' => 3],
|
||||
['id' => 7],
|
||||
['id' => 1],
|
||||
]));
|
||||
|
||||
$repo = new ArticleRepository($mockDb);
|
||||
$result = $repo->articlesIds(1, 'pl', 10, 1, 0);
|
||||
|
||||
$this->assertEquals([3, 7, 1], $result);
|
||||
}
|
||||
|
||||
public function testArticlesIdsReturnsNullForEmpty(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$mockDb->method('query')
|
||||
->willReturn($this->createFetchAllMock([]));
|
||||
|
||||
$repo = new ArticleRepository($mockDb);
|
||||
$this->assertNull($repo->articlesIds(1, 'pl', 10, 0, 0));
|
||||
}
|
||||
|
||||
public function testPageArticlesCountReturnsInt(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$mockDb->expects($this->once())
|
||||
->method('query')
|
||||
->willReturn($this->createFetchAllMock([[12]]));
|
||||
|
||||
$repo = new ArticleRepository($mockDb);
|
||||
$this->assertSame(12, $repo->pageArticlesCount(5, 'pl'));
|
||||
}
|
||||
|
||||
public function testPageArticlesCountReturnsZeroForEmpty(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$mockDb->method('query')
|
||||
->willReturn($this->createFetchAllMock([]));
|
||||
|
||||
$repo = new ArticleRepository($mockDb);
|
||||
$this->assertSame(0, $repo->pageArticlesCount(5, 'pl'));
|
||||
}
|
||||
|
||||
public function testPageArticlesPagination(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
// pageArticlesCount query returns 25 articles
|
||||
// articlesIds query returns 10 article IDs
|
||||
$mockDb->method('query')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
$this->createFetchAllMock([[25]]),
|
||||
$this->createFetchAllMock([
|
||||
['id' => 11], ['id' => 12], ['id' => 13], ['id' => 14], ['id' => 15],
|
||||
['id' => 16], ['id' => 17], ['id' => 18], ['id' => 19], ['id' => 20],
|
||||
])
|
||||
);
|
||||
|
||||
$page = ['id' => 3, 'articles_limit' => 10, 'sort_type' => 1];
|
||||
$repo = new ArticleRepository($mockDb);
|
||||
$result = $repo->pageArticles($page, 'pl', 2);
|
||||
|
||||
$this->assertArrayHasKey('articles', $result);
|
||||
$this->assertArrayHasKey('ls', $result);
|
||||
$this->assertSame(3, $result['ls']); // ceil(25/10) = 3
|
||||
$this->assertCount(10, $result['articles']);
|
||||
}
|
||||
|
||||
public function testArticleNoindexReturnsBool(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$mockDb->expects($this->once())
|
||||
->method('get')
|
||||
->with('pp_articles_langs', 'noindex', [
|
||||
'AND' => ['article_id' => 5, 'lang_id' => 'pl']
|
||||
])
|
||||
->willReturn(1);
|
||||
|
||||
$repo = new ArticleRepository($mockDb);
|
||||
$this->assertTrue($repo->articleNoindex(5, 'pl'));
|
||||
}
|
||||
|
||||
public function testArticleNoindexReturnsFalseForNonNoindex(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$mockDb->method('get')
|
||||
->willReturn(null);
|
||||
|
||||
$repo = new ArticleRepository($mockDb);
|
||||
$this->assertFalse($repo->articleNoindex(5, 'pl'));
|
||||
}
|
||||
|
||||
public function testNewsReturnsArticlesArray(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
// First get() for sort_type, then get() for article_details
|
||||
$mockDb->method('get')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
1, // sort_type
|
||||
['id' => 10, 'status' => 1] // article data
|
||||
);
|
||||
|
||||
// articlesIds query returns [10]
|
||||
$mockDb->method('query')
|
||||
->willReturn($this->createFetchAllMock([['id' => 10]]));
|
||||
|
||||
// article details selects
|
||||
$mockDb->method('select')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
[['lang_id' => 'pl', 'title' => 'News', 'copy_from' => null]],
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
);
|
||||
|
||||
$repo = new ArticleRepository($mockDb);
|
||||
$result = $repo->news(3, 6, 'pl');
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertEquals(10, $result[0]['id']);
|
||||
}
|
||||
|
||||
public function testTopArticlesOrderByViews(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$queryCalls = 0;
|
||||
$mockDb->method('query')
|
||||
->willReturnCallback(function ($sql) use (&$queryCalls) {
|
||||
$queryCalls++;
|
||||
if ($queryCalls === 1) {
|
||||
$this->assertStringContainsString('views DESC', $sql);
|
||||
return $this->createFetchAllMock([
|
||||
['id' => 5, 'date_add' => '2025-01-01', 'views' => 100, 'title' => 'Popular'],
|
||||
]);
|
||||
}
|
||||
return $this->createFetchAllMock([]);
|
||||
});
|
||||
|
||||
$mockDb->method('get')
|
||||
->willReturn(['id' => 5, 'status' => 1]);
|
||||
|
||||
$mockDb->method('select')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
[['lang_id' => 'pl', 'title' => 'Popular', 'copy_from' => null]],
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
);
|
||||
|
||||
$repo = new ArticleRepository($mockDb);
|
||||
$result = $repo->topArticles(3, 6, 'pl');
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertCount(1, $result);
|
||||
}
|
||||
|
||||
public function testNewsListArticlesOrderByDateDesc(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$queryCalls = 0;
|
||||
$mockDb->method('query')
|
||||
->willReturnCallback(function ($sql) use (&$queryCalls) {
|
||||
$queryCalls++;
|
||||
if ($queryCalls === 1) {
|
||||
$this->assertStringContainsString('date_add DESC', $sql);
|
||||
return $this->createFetchAllMock([
|
||||
['id' => 8, 'date_add' => '2025-06-15', 'title' => 'Newest'],
|
||||
]);
|
||||
}
|
||||
return $this->createFetchAllMock([]);
|
||||
});
|
||||
|
||||
$mockDb->method('get')
|
||||
->willReturn(['id' => 8, 'status' => 1]);
|
||||
|
||||
$mockDb->method('select')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
[['lang_id' => 'pl', 'title' => 'Newest', 'copy_from' => null]],
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
);
|
||||
|
||||
$repo = new ArticleRepository($mockDb);
|
||||
$result = $repo->newsListArticles(3, 6, 'pl');
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertCount(1, $result);
|
||||
}
|
||||
|
||||
public function testListForAdminUsesBoundParamsForTitleFilter(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
Reference in New Issue
Block a user