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], '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.id')->all())->toBe([$tag->id]); });