Files
SkinbaseNova/tests/Feature/Studio/StudioNewsPagesTest.php

301 lines
10 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\User;
use Inertia\Testing\AssertableInertia;
use Illuminate\Support\Carbon;
use cPad\Plugins\News\Models\NewsArticle;
use cPad\Plugins\News\Models\NewsCategory;
use cPad\Plugins\News\Models\NewsTag;
function studioNewsCategory(array $attributes = []): NewsCategory
{
return NewsCategory::query()->create(array_merge([
'name' => 'Studio News',
'slug' => 'studio-news',
'description' => 'Studio News category',
'position' => 0,
'is_active' => true,
], $attributes));
}
function studioNewsTag(array $attributes = []): NewsTag
{
return NewsTag::query()->create(array_merge([
'name' => 'Studio',
'slug' => 'studio',
], $attributes));
}
it('forbids newsroom studio pages for non moderators', function (): void {
$user = User::factory()->create();
$this->actingAs($user)
->get(route('studio.news.index'))
->assertForbidden();
});
it('renders newsroom studio pages for moderators', function (): void {
$moderator = User::factory()->create([
'role' => 'moderator',
'username' => 'modnews',
'name' => 'Moderator News',
]);
$author = User::factory()->create([
'username' => 'writernews',
'name' => 'Writer News',
]);
$category = studioNewsCategory();
$tag = studioNewsTag();
$article = NewsArticle::query()->create([
'title' => 'Moderated newsroom article',
'slug' => 'moderated-newsroom-article',
'excerpt' => 'Studio-managed newsroom article.',
'content' => 'Studio body',
'author_id' => $author->id,
'category_id' => $category->id,
'type' => NewsArticle::TYPE_EDITORIAL,
'status' => 'published',
'editorial_status' => NewsArticle::EDITORIAL_STATUS_PUBLISHED,
'published_at' => Carbon::parse('2026-04-05 09:30:00'),
]);
$article->tags()->sync([$tag->id]);
$this->actingAs($moderator)
->get(route('studio.news.index'))
->assertOk()
->assertInertia(fn (AssertableInertia $page) => $page
->component('Studio/StudioNewsIndex')
->where('title', 'Newsroom')
->where('listing.items.0.title', 'Moderated newsroom article')
->where('createUrl', route('studio.news.create')));
$this->actingAs($moderator)
->get(route('studio.news.create'))
->assertOk()
->assertInertia(fn (AssertableInertia $page) => $page
->component('Studio/StudioNewsEditor')
->where('title', 'Create article')
->has('typeOptions')
->has('statusOptions')
->has('categoryOptions')
->has('tagOptions')
->where('defaultAuthor.id', $moderator->id));
$this->actingAs($moderator)
->get(route('studio.news.categories'))
->assertOk()
->assertInertia(fn (AssertableInertia $page) => $page
->component('Studio/StudioNewsTaxonomies')
->where('activeTab', 'categories')
->has('categories.0')
->has('tags.0'));
$this->actingAs($moderator)
->get(route('studio.news.preview', ['article' => $article->id]))
->assertOk()
->assertSee('Preview mode')
->assertSee('Moderated newsroom article');
});
it('filters newsroom listing by status type and category', function (): void {
$moderator = User::factory()->create([
'role' => 'moderator',
]);
$category = studioNewsCategory([
'name' => 'Filtered Category',
'slug' => 'filtered-category',
]);
$otherCategory = studioNewsCategory([
'name' => 'Other Category',
'slug' => 'other-category',
]);
$author = User::factory()->create();
NewsArticle::query()->create([
'title' => 'Keep Me',
'slug' => 'keep-me',
'excerpt' => 'Should survive filtering.',
'content' => 'Content',
'author_id' => $author->id,
'category_id' => $category->id,
'type' => NewsArticle::TYPE_ANNOUNCEMENT,
'status' => 'draft',
'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
]);
NewsArticle::query()->create([
'title' => 'Drop Me By Type',
'slug' => 'drop-me-type',
'excerpt' => 'Wrong type.',
'content' => 'Content',
'author_id' => $author->id,
'category_id' => $category->id,
'type' => NewsArticle::TYPE_EDITORIAL,
'status' => 'draft',
'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
]);
NewsArticle::query()->create([
'title' => 'Drop Me By Category',
'slug' => 'drop-me-category',
'excerpt' => 'Wrong category.',
'content' => 'Content',
'author_id' => $author->id,
'category_id' => $otherCategory->id,
'type' => NewsArticle::TYPE_ANNOUNCEMENT,
'status' => 'draft',
'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
]);
$this->actingAs($moderator)
->get(route('studio.news.index', [
'status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
'type' => NewsArticle::TYPE_ANNOUNCEMENT,
'category_id' => $category->id,
]))
->assertOk()
->assertInertia(fn (AssertableInertia $page) => $page
->component('Studio/StudioNewsIndex')
->where('listing.filters.status', NewsArticle::EDITORIAL_STATUS_DRAFT)
->where('listing.filters.type', NewsArticle::TYPE_ANNOUNCEMENT)
->where('listing.filters.category_id', $category->id)
->has('listing.items', 1)
->where('listing.items.0.title', 'Keep Me'));
});
it('stores a newsroom draft with taxonomy links', function (): void {
$moderator = User::factory()->create([
'role' => 'moderator',
'username' => 'editornews',
'name' => 'Editor News',
]);
$author = User::factory()->create();
$category = studioNewsCategory([
'name' => 'Launches',
'slug' => 'launches',
]);
$tag = studioNewsTag([
'name' => 'Update',
'slug' => 'update',
]);
$response = $this->actingAs($moderator)->post(route('studio.news.store'), [
'title' => 'Stored newsroom draft',
'slug' => 'stored-newsroom-draft',
'excerpt' => 'Stored through the Studio newsroom form.',
'content' => 'This article was created through the new Studio News flow.',
'type' => NewsArticle::TYPE_ANNOUNCEMENT,
'category_id' => $category->id,
'author_id' => $author->id,
'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
'published_at' => null,
'tag_ids' => [$tag->id],
'new_tag_names' => ['Studio Exclusive'],
'is_featured' => true,
'is_pinned' => false,
'meta_title' => 'Stored newsroom draft meta',
'meta_description' => 'Stored newsroom draft description',
]);
$article = NewsArticle::query()->where('slug', 'stored-newsroom-draft')->firstOrFail();
$response->assertRedirect(route('studio.news.edit', ['article' => $article->id]));
$this->assertDatabaseHas('news_articles', [
'id' => $article->id,
'title' => 'Stored newsroom draft',
'slug' => 'stored-newsroom-draft',
'author_id' => $author->id,
'category_id' => $category->id,
'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
'status' => 'draft',
]);
expect($article->tags()->pluck('news_tags.name')->all())
->toContain('Update')
->toContain('Studio Exclusive');
});
it('updates an existing newsroom article', function (): void {
$moderator = User::factory()->create([
'role' => 'moderator',
]);
$author = User::factory()->create();
$category = studioNewsCategory([
'name' => 'Editorial',
'slug' => 'editorial',
]);
$tag = studioNewsTag([
'name' => 'Feature',
'slug' => 'feature',
]);
$article = NewsArticle::query()->create([
'title' => 'Original newsroom article',
'slug' => 'original-newsroom-article',
'excerpt' => 'Original excerpt.',
'content' => 'Original content.',
'author_id' => $author->id,
'type' => NewsArticle::TYPE_ANNOUNCEMENT,
'status' => 'draft',
'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
]);
$this->actingAs($moderator)
->patch(route('studio.news.update', ['article' => $article->id]), [
'title' => 'Updated newsroom article',
'slug' => 'updated-newsroom-article',
'excerpt' => 'Updated excerpt.',
'content' => '<p>Updated content.</p>',
'type' => NewsArticle::TYPE_EDITORIAL,
'category_id' => $category->id,
'author_id' => $author->id,
'editorial_status' => NewsArticle::EDITORIAL_STATUS_IN_REVIEW,
'tag_ids' => [$tag->id],
'new_tag_names' => ['Deep Dive'],
'is_featured' => true,
'is_pinned' => true,
])
->assertSessionHasNoErrors()
->assertRedirect();
$article->refresh();
expect($article->title)->toBe('Updated newsroom article')
->and($article->slug)->toBe('updated-newsroom-article')
->and($article->type)->toBe(NewsArticle::TYPE_EDITORIAL)
->and($article->editorial_status)->toBe(NewsArticle::EDITORIAL_STATUS_IN_REVIEW)
->and((int) $article->category_id)->toBe($category->id)
->and((bool) $article->is_featured)->toBeTrue()
->and((bool) $article->is_pinned)->toBeTrue()
->and($article->tags()->pluck('news_tags.name')->all())->toContain('Feature')
->and($article->tags()->pluck('news_tags.name')->all())->toContain('Deep Dive');
});
it('soft deletes a newsroom article from studio', function (): void {
$moderator = User::factory()->create([
'role' => 'moderator',
]);
$author = User::factory()->create();
$article = NewsArticle::query()->create([
'title' => 'Delete me softly',
'slug' => 'delete-me-softly',
'excerpt' => 'Soft delete test article.',
'content' => 'Studio delete content.',
'author_id' => $author->id,
'type' => NewsArticle::TYPE_EDITORIAL,
'status' => 'draft',
'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
]);
$this->actingAs($moderator)
->delete(route('studio.news.destroy', ['article' => $article->id]))
->assertRedirect(route('studio.news.index'));
$this->assertSoftDeleted('news_articles', [
'id' => $article->id,
]);
});