535 lines
19 KiB
PHP
535 lines
19 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Livewire;
|
|
|
|
use App\Enums\ApprovalStatusEnum;
|
|
use App\Events\RouteArticleApproved;
|
|
use App\Livewire\Articles;
|
|
use App\Models\Article;
|
|
use App\Models\Feed;
|
|
use App\Models\Route;
|
|
use App\Models\RouteArticle;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Livewire\Features\SupportTesting\Testable;
|
|
use Livewire\Livewire;
|
|
use Tests\TestCase;
|
|
|
|
class ArticlesTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private function createRouteArticle(ApprovalStatusEnum $status = ApprovalStatusEnum::PENDING, string $title = 'Test Article'): RouteArticle
|
|
{
|
|
$feed = Feed::factory()->create();
|
|
/** @var Route $route */
|
|
$route = Route::factory()->active()->create(['feed_id' => $feed->id]);
|
|
$article = Article::factory()->create(['feed_id' => $feed->id, 'title' => $title]);
|
|
|
|
/** @var RouteArticle $routeArticle */
|
|
$routeArticle = RouteArticle::factory()->forRoute($route)->create([
|
|
'article_id' => $article->id,
|
|
'approval_status' => $status,
|
|
'validated_at' => now(),
|
|
]);
|
|
|
|
return $routeArticle;
|
|
}
|
|
|
|
private function createRouteArticleForFeed(Feed $feed, string $title, ApprovalStatusEnum $status = ApprovalStatusEnum::PENDING): RouteArticle
|
|
{
|
|
/** @var Route $route */
|
|
$route = Route::factory()->active()->create(['feed_id' => $feed->id]);
|
|
$article = Article::factory()->create(['feed_id' => $feed->id, 'title' => $title]);
|
|
|
|
/** @var RouteArticle $routeArticle */
|
|
$routeArticle = RouteArticle::factory()->forRoute($route)->create([
|
|
'article_id' => $article->id,
|
|
'approval_status' => $status,
|
|
'validated_at' => now(),
|
|
]);
|
|
|
|
return $routeArticle;
|
|
}
|
|
|
|
/**
|
|
* @param Testable<Articles> $component
|
|
* @return array<int, int>
|
|
*/
|
|
private function renderedFeedGroupIds(Testable $component): array
|
|
{
|
|
preg_match_all('/wire:key="feed-group-(\d+)"/', $component->html(), $matches);
|
|
|
|
return array_map('intval', $matches[1]);
|
|
}
|
|
|
|
public function test_renders_successfully(): void
|
|
{
|
|
Livewire::test(Articles::class)
|
|
->assertStatus(200);
|
|
}
|
|
|
|
public function test_defaults_to_pending_tab(): void
|
|
{
|
|
Livewire::test(Articles::class)
|
|
->assertSet('tab', 'pending');
|
|
}
|
|
|
|
public function test_pending_tab_shows_only_pending_route_articles(): void
|
|
{
|
|
$feed = Feed::factory()->create(['name' => 'VRT News']);
|
|
|
|
$this->createRouteArticleForFeed($feed, 'Pending Article');
|
|
$this->createRouteArticleForFeed($feed, 'Approved Article', ApprovalStatusEnum::APPROVED);
|
|
$this->createRouteArticleForFeed($feed, 'Rejected Article', ApprovalStatusEnum::REJECTED);
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('toggleFeed', $feed->id)
|
|
->assertSee('Pending Article')
|
|
->assertDontSee('Approved Article')
|
|
->assertDontSee('Rejected Article');
|
|
}
|
|
|
|
public function test_all_tab_shows_all_route_articles(): void
|
|
{
|
|
$this->createRouteArticle(ApprovalStatusEnum::PENDING, 'Pending Article');
|
|
$this->createRouteArticle(ApprovalStatusEnum::APPROVED, 'Approved Article');
|
|
$this->createRouteArticle(ApprovalStatusEnum::REJECTED, 'Rejected Article');
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('setTab', 'all')
|
|
->assertSee('Pending Article')
|
|
->assertSee('Approved Article')
|
|
->assertSee('Rejected Article');
|
|
}
|
|
|
|
public function test_all_tab_search_filters_by_title(): void
|
|
{
|
|
$this->createRouteArticle(ApprovalStatusEnum::PENDING, 'Belgian Politics Update');
|
|
$this->createRouteArticle(ApprovalStatusEnum::PENDING, 'Weather Forecast Today');
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('setTab', 'all')
|
|
->set('search', 'Belgian')
|
|
->assertSee('Belgian Politics Update')
|
|
->assertDontSee('Weather Forecast Today');
|
|
}
|
|
|
|
public function test_approve_changes_status_and_dispatches_event(): void
|
|
{
|
|
Event::fake([RouteArticleApproved::class]);
|
|
|
|
$routeArticle = $this->createRouteArticle(ApprovalStatusEnum::PENDING);
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('approve', $routeArticle->id);
|
|
|
|
$this->assertEquals(ApprovalStatusEnum::APPROVED, $routeArticle->fresh()->approval_status);
|
|
|
|
Event::assertDispatched(RouteArticleApproved::class);
|
|
}
|
|
|
|
public function test_reject_changes_status(): void
|
|
{
|
|
$routeArticle = $this->createRouteArticle(ApprovalStatusEnum::PENDING);
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('reject', $routeArticle->id);
|
|
|
|
$this->assertEquals(ApprovalStatusEnum::REJECTED, $routeArticle->fresh()->approval_status);
|
|
}
|
|
|
|
public function test_restore_changes_status_back_to_pending(): void
|
|
{
|
|
$routeArticle = $this->createRouteArticle(ApprovalStatusEnum::REJECTED);
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('restore', $routeArticle->id);
|
|
|
|
$this->assertEquals(ApprovalStatusEnum::PENDING, $routeArticle->fresh()->approval_status);
|
|
}
|
|
|
|
public function test_clear_rejects_all_pending_route_articles(): void
|
|
{
|
|
$pending1 = $this->createRouteArticle(ApprovalStatusEnum::PENDING);
|
|
$pending2 = $this->createRouteArticle(ApprovalStatusEnum::PENDING);
|
|
$approved = $this->createRouteArticle(ApprovalStatusEnum::APPROVED);
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('clear');
|
|
|
|
$this->assertEquals(ApprovalStatusEnum::REJECTED, $pending1->fresh()->approval_status);
|
|
$this->assertEquals(ApprovalStatusEnum::REJECTED, $pending2->fresh()->approval_status);
|
|
$this->assertEquals(ApprovalStatusEnum::APPROVED, $approved->fresh()->approval_status);
|
|
}
|
|
|
|
public function test_clear_only_rejects_the_filtered_feed(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
$belga = Feed::factory()->create(['name' => 'Belga Press']);
|
|
|
|
$vrtArticle = $this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
$belgaArticle = $this->createRouteArticleForFeed($belga, 'Belga Headline');
|
|
|
|
Livewire::test(Articles::class)
|
|
->set('feedId', $vrt->id)
|
|
->call('clear');
|
|
|
|
$this->assertEquals(ApprovalStatusEnum::REJECTED, $vrtArticle->fresh()->approval_status);
|
|
$this->assertEquals(ApprovalStatusEnum::PENDING, $belgaArticle->fresh()->approval_status);
|
|
}
|
|
|
|
public function test_clear_is_a_no_op_when_the_filtered_feed_has_nothing_pending(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
$belga = Feed::factory()->create(['name' => 'Belga Press']);
|
|
|
|
$vrtArticle = $this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
$belgaApproved = $this->createRouteArticleForFeed($belga, 'Belga Approved', ApprovalStatusEnum::APPROVED);
|
|
|
|
Livewire::test(Articles::class)
|
|
->set('feedId', $belga->id)
|
|
->call('clear');
|
|
|
|
$this->assertEquals(ApprovalStatusEnum::PENDING, $vrtArticle->fresh()->approval_status);
|
|
$this->assertEquals(ApprovalStatusEnum::APPROVED, $belgaApproved->fresh()->approval_status);
|
|
}
|
|
|
|
public function test_clear_confirmation_counts_only_the_filtered_feed(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
$belga = Feed::factory()->create(['name' => 'Belga Press']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT One');
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Two');
|
|
$this->createRouteArticleForFeed($belga, 'Belga One');
|
|
|
|
Livewire::test(Articles::class)
|
|
->set('feedId', $vrt->id)
|
|
->assertSee('Reject 2 pending route articles from the selected feed?')
|
|
->set('feedId', null)
|
|
->assertSee('Reject 3 pending route articles?');
|
|
}
|
|
|
|
public function test_clear_button_hidden_when_filtered_feed_has_nothing_pending(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
$belga = Feed::factory()->create(['name' => 'Belga Press']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
$this->createRouteArticleForFeed($belga, 'Belga Approved', ApprovalStatusEnum::APPROVED);
|
|
|
|
Livewire::test(Articles::class)
|
|
->set('feedId', $belga->id)
|
|
->assertDontSee('Clear Feed')
|
|
->assertDontSee('Clear All');
|
|
}
|
|
|
|
public function test_clear_button_labels_its_scope(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
|
|
Livewire::test(Articles::class)
|
|
->assertSee('Clear All')
|
|
->set('feedId', $vrt->id)
|
|
->assertSee('Clear Feed');
|
|
}
|
|
|
|
public function test_pending_count_badge_shows_correct_count(): void
|
|
{
|
|
$this->createRouteArticle(ApprovalStatusEnum::PENDING);
|
|
$this->createRouteArticle(ApprovalStatusEnum::PENDING);
|
|
$this->createRouteArticle(ApprovalStatusEnum::APPROVED);
|
|
|
|
Livewire::test(Articles::class)
|
|
->assertSeeInOrder(['Pending', '2']);
|
|
}
|
|
|
|
public function test_switching_tabs_resets_search(): void
|
|
{
|
|
Livewire::test(Articles::class)
|
|
->call('setTab', 'all')
|
|
->set('search', 'something')
|
|
->call('setTab', 'pending')
|
|
->assertSet('search', '')
|
|
->assertSet('tab', 'pending');
|
|
}
|
|
|
|
public function test_shows_route_name_in_listing(): void
|
|
{
|
|
$feed = Feed::factory()->create(['name' => 'VRT News']);
|
|
/** @var Route $route */
|
|
$route = Route::factory()->active()->create(['feed_id' => $feed->id]);
|
|
$article = Article::factory()->create(['feed_id' => $feed->id, 'title' => 'Test']);
|
|
|
|
RouteArticle::factory()->forRoute($route)->create([
|
|
'article_id' => $article->id,
|
|
'approval_status' => ApprovalStatusEnum::PENDING,
|
|
'validated_at' => now(),
|
|
]);
|
|
|
|
Livewire::test(Articles::class)
|
|
->assertSee('VRT News');
|
|
}
|
|
|
|
public function test_pending_tab_lists_feeds_collapsed_without_articles(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
$belga = Feed::factory()->create(['name' => 'Belga Press']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
$this->createRouteArticleForFeed($belga, 'Belga Headline');
|
|
|
|
Livewire::test(Articles::class)
|
|
->assertSee('VRT News')
|
|
->assertSee('Belga Press')
|
|
->assertDontSee('VRT Headline')
|
|
->assertDontSee('Belga Headline');
|
|
}
|
|
|
|
public function test_pending_feed_badge_shows_total_count_not_page_count(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
|
|
for ($i = 0; $i < 20; $i++) {
|
|
$this->createRouteArticleForFeed($vrt, "VRT Article {$i}");
|
|
}
|
|
|
|
$html = (string) preg_replace('/\s+/', ' ', Livewire::test(Articles::class)->html());
|
|
|
|
$this->assertStringContainsString('VRT News</span> <span class="ml-auto inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800"> 20 </span>', $html);
|
|
}
|
|
|
|
public function test_expanding_a_feed_reveals_only_that_feeds_articles(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
$belga = Feed::factory()->create(['name' => 'Belga Press']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
$this->createRouteArticleForFeed($belga, 'Belga Headline');
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('toggleFeed', $vrt->id)
|
|
->assertSee('VRT Headline')
|
|
->assertDontSee('Belga Headline');
|
|
}
|
|
|
|
public function test_expanding_a_feed_shows_all_its_articles_beyond_one_page(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
|
|
for ($i = 0; $i < 20; $i++) {
|
|
$this->travelTo(now()->addMinutes($i), function () use ($vrt, $i) {
|
|
$this->createRouteArticleForFeed($vrt, "VRT Article {$i}");
|
|
});
|
|
}
|
|
|
|
$component = Livewire::test(Articles::class)->call('toggleFeed', $vrt->id);
|
|
|
|
for ($i = 0; $i < 20; $i++) {
|
|
$component->assertSee("VRT Article {$i}");
|
|
}
|
|
}
|
|
|
|
public function test_toggling_an_expanded_feed_collapses_it(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('toggleFeed', $vrt->id)
|
|
->assertSee('VRT Headline')
|
|
->call('toggleFeed', $vrt->id)
|
|
->assertDontSee('VRT Headline');
|
|
}
|
|
|
|
public function test_switching_tabs_collapses_expanded_feeds(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('toggleFeed', $vrt->id)
|
|
->call('setTab', 'all')
|
|
->call('setTab', 'pending')
|
|
->assertSet('expandedFeeds', [])
|
|
->assertDontSee('VRT Headline');
|
|
}
|
|
|
|
public function test_all_tab_shows_a_flat_list_not_feed_groups(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
$belga = Feed::factory()->create(['name' => 'Belga Press']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Article');
|
|
$this->createRouteArticleForFeed($belga, 'Belga Article');
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('setTab', 'all')
|
|
->assertViewHas('pendingFeeds', null)
|
|
->assertSee('VRT Article')
|
|
->assertSee('Belga Article');
|
|
}
|
|
|
|
public function test_feed_filter_narrows_pending_tab_to_one_feed(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
$belga = Feed::factory()->create(['name' => 'Belga Press']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
$this->createRouteArticleForFeed($belga, 'Belga Headline');
|
|
|
|
$component = Livewire::test(Articles::class)->set('feedId', $vrt->id);
|
|
|
|
$this->assertSame([$vrt->id], $this->renderedFeedGroupIds($component));
|
|
}
|
|
|
|
public function test_feed_filter_narrows_all_tab_to_one_feed(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
$belga = Feed::factory()->create(['name' => 'Belga Press']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
$this->createRouteArticleForFeed($belga, 'Belga Headline');
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('setTab', 'all')
|
|
->set('feedId', $vrt->id)
|
|
->assertSee('VRT Headline')
|
|
->assertDontSee('Belga Headline');
|
|
}
|
|
|
|
public function test_feed_filter_combines_with_search_on_all_tab(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
$belga = Feed::factory()->create(['name' => 'Belga Press']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'Belgian Politics Update');
|
|
$this->createRouteArticleForFeed($vrt, 'Weather Forecast Today');
|
|
$this->createRouteArticleForFeed($belga, 'Belgian Economy Report');
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('setTab', 'all')
|
|
->set('feedId', $vrt->id)
|
|
->set('search', 'Belgian')
|
|
->assertSee('Belgian Politics Update')
|
|
->assertDontSee('Weather Forecast Today')
|
|
->assertDontSee('Belgian Economy Report');
|
|
}
|
|
|
|
public function test_feed_filter_combines_with_tab_status_filtering(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Pending');
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Approved', ApprovalStatusEnum::APPROVED);
|
|
|
|
Livewire::test(Articles::class)
|
|
->set('feedId', $vrt->id)
|
|
->call('toggleFeed', $vrt->id)
|
|
->assertSee('VRT Pending')
|
|
->assertDontSee('VRT Approved');
|
|
}
|
|
|
|
public function test_clearing_feed_filter_restores_all_feeds(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
$belga = Feed::factory()->create(['name' => 'Belga Press']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
$this->createRouteArticleForFeed($belga, 'Belga Headline');
|
|
|
|
$component = Livewire::test(Articles::class)->set('feedId', $vrt->id);
|
|
|
|
$this->assertSame([$vrt->id], $this->renderedFeedGroupIds($component));
|
|
|
|
$component->set('feedId', null);
|
|
|
|
$this->assertEqualsCanonicalizing([$vrt->id, $belga->id], $this->renderedFeedGroupIds($component));
|
|
}
|
|
|
|
public function test_changing_feed_filter_resets_pagination(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
|
|
for ($i = 0; $i < 20; $i++) {
|
|
$this->createRouteArticleForFeed($vrt, "VRT Article {$i}");
|
|
}
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('setTab', 'all')
|
|
->set('paginators.page', 2)
|
|
->set('feedId', $vrt->id)
|
|
->assertSet('paginators.page', 1);
|
|
}
|
|
|
|
public function test_changing_feed_filter_collapses_expanded_feeds(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
|
|
Livewire::test(Articles::class)
|
|
->call('toggleFeed', $vrt->id)
|
|
->assertSee('VRT Headline')
|
|
->set('feedId', $vrt->id)
|
|
->assertSet('expandedFeeds', [])
|
|
->assertDontSee('VRT Headline');
|
|
}
|
|
|
|
public function test_feed_filter_persists_across_tab_switches(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
|
|
Livewire::test(Articles::class)
|
|
->set('feedId', $vrt->id)
|
|
->call('setTab', 'all')
|
|
->assertSet('feedId', $vrt->id)
|
|
->call('setTab', 'pending')
|
|
->assertSet('feedId', $vrt->id);
|
|
}
|
|
|
|
public function test_feed_filter_options_only_include_feeds_with_route_articles(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
$unused = Feed::factory()->create(['name' => 'Unused Feed']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
|
|
Livewire::test(Articles::class)
|
|
->assertSeeHtml('<option value="'.$vrt->id.'">VRT News</option>')
|
|
->assertDontSeeHtml('<option value="'.$unused->id.'">Unused Feed</option>');
|
|
}
|
|
|
|
public function test_empty_state_when_feed_filter_matches_nothing_on_pending_tab(): void
|
|
{
|
|
$vrt = Feed::factory()->create(['name' => 'VRT News']);
|
|
$belga = Feed::factory()->create(['name' => 'Belga Press']);
|
|
|
|
$this->createRouteArticleForFeed($vrt, 'VRT Headline');
|
|
$this->createRouteArticleForFeed($belga, 'Belga Approved', ApprovalStatusEnum::APPROVED);
|
|
|
|
Livewire::test(Articles::class)
|
|
->set('feedId', $belga->id)
|
|
->assertSee('No pending articles for the selected feed.');
|
|
}
|
|
|
|
public function test_empty_state_on_pending_tab(): void
|
|
{
|
|
Livewire::test(Articles::class)
|
|
->assertSee('No pending articles');
|
|
}
|
|
|
|
public function test_empty_state_on_all_tab(): void
|
|
{
|
|
Livewire::test(Articles::class)
|
|
->call('setTab', 'all')
|
|
->assertSee('No route articles have been created yet.');
|
|
}
|
|
}
|