Commit workspace changes
This commit is contained in:
@@ -5,7 +5,10 @@ use App\Models\User;
|
||||
test('login screen can be rendered', function () {
|
||||
$response = $this->get('/login');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertStatus(200)
|
||||
->assertSee('Read signup and login help')
|
||||
->assertSee(route('help.auth'), false)
|
||||
->assertSee(route('help.troubleshooting'), false);
|
||||
});
|
||||
|
||||
test('users can authenticate using the login screen', function () {
|
||||
|
||||
@@ -7,7 +7,10 @@ use Illuminate\Support\Facades\Notification;
|
||||
test('reset password link screen can be rendered', function () {
|
||||
$response = $this->get('/forgot-password');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertStatus(200)
|
||||
->assertSee('Read signup and login help')
|
||||
->assertSee(route('help.auth'), false)
|
||||
->assertSee(route('help.troubleshooting'), false);
|
||||
});
|
||||
|
||||
test('reset password link can be requested', function () {
|
||||
|
||||
@@ -7,6 +7,9 @@ test('registration screen can be rendered', function () {
|
||||
$response = $this->get('/register');
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertSee('Read signup and login help')
|
||||
->assertSee(route('help.auth'), false)
|
||||
->assertSee(route('help.troubleshooting'), false)
|
||||
->assertDontSee('name="name"', false)
|
||||
->assertDontSee('name="username"', false)
|
||||
->assertDontSee('name="password"', false)
|
||||
|
||||
169
tests/Feature/Console/CheckArtworkUserReferencesCommandTest.php
Normal file
169
tests/Feature/Console/CheckArtworkUserReferencesCommandTest.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Artwork;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('passes when every artwork has a matching user', function (): void {
|
||||
Artwork::factory()->count(3)->create();
|
||||
|
||||
$code = Artisan::call('artworks:check-user-refs', [
|
||||
'--chunk' => 2,
|
||||
'--show-missing' => 5,
|
||||
]);
|
||||
|
||||
$output = Artisan::output();
|
||||
file_put_contents(storage_path('logs/check-artwork-user-refs-test-output.log'), $output);
|
||||
|
||||
expect($code)->toBe(0)
|
||||
->and($output)->toContain('Checked 3 artworks: 3 valid, 0 missing user references, 0 null user_id values.')
|
||||
->and($output)->toContain('No missing user references found in artworks.user_id.');
|
||||
});
|
||||
|
||||
it('fails and reports orphaned artwork user references', function (): void {
|
||||
$validArtwork = Artwork::factory()->create();
|
||||
$orphanedArtwork = Artwork::factory()->create();
|
||||
|
||||
DB::table('artworks')
|
||||
->where('id', $orphanedArtwork->id)
|
||||
->update(['user_id' => 999999]);
|
||||
|
||||
$code = Artisan::call('artworks:check-user-refs', [
|
||||
'--chunk' => 1,
|
||||
'--show-missing' => 5,
|
||||
]);
|
||||
|
||||
$output = Artisan::output();
|
||||
|
||||
expect($validArtwork->fresh())->not->toBeNull()
|
||||
->and($code)->toBe(1)
|
||||
->and($output)->toContain('Checked 2 artworks: 1 valid, 1 missing user references, 0 null user_id values.')
|
||||
->and($output)->toContain('Found artworks with missing user references.')
|
||||
->and($output)->toContain((string) $orphanedArtwork->id)
|
||||
->and($output)->toContain('999999');
|
||||
});
|
||||
|
||||
it('can copy missing referenced users from the legacy users table by the same id', function (): void {
|
||||
$legacyUserId = 777;
|
||||
$artwork = Artwork::factory()->create();
|
||||
|
||||
DB::table('artworks')
|
||||
->where('id', $artwork->id)
|
||||
->update(['user_id' => $legacyUserId]);
|
||||
|
||||
config()->set('database.connections.legacy', config('database.connections.' . config('database.default')));
|
||||
DB::purge('legacy');
|
||||
|
||||
Schema::connection('legacy')->dropIfExists('legacy_users');
|
||||
Schema::connection('legacy')->create('legacy_users', function (Blueprint $table): void {
|
||||
$table->unsignedBigInteger('user_id')->primary();
|
||||
$table->string('uname')->nullable();
|
||||
$table->string('real_name')->nullable();
|
||||
$table->string('email')->nullable();
|
||||
$table->unsignedTinyInteger('active')->default(1);
|
||||
$table->timestamp('joinDate')->nullable();
|
||||
$table->timestamp('LastVisit')->nullable();
|
||||
$table->string('country')->nullable();
|
||||
$table->string('country_code', 2)->nullable();
|
||||
$table->string('web')->nullable();
|
||||
$table->text('about_me')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
$table->string('gender', 16)->nullable();
|
||||
});
|
||||
|
||||
DB::connection('legacy')->table('legacy_users')->insert([
|
||||
'user_id' => $legacyUserId,
|
||||
'uname' => 'legacy_artist',
|
||||
'real_name' => 'Legacy Artist',
|
||||
'email' => 'legacy.artist@example.test',
|
||||
'active' => 1,
|
||||
'joinDate' => '2020-01-02 03:04:05',
|
||||
'LastVisit' => '2025-01-05 06:07:08',
|
||||
'country' => 'Finland',
|
||||
'country_code' => 'FI',
|
||||
'web' => 'legacy.example.test',
|
||||
'about_me' => 'Imported from legacy.',
|
||||
'gender' => 'F',
|
||||
]);
|
||||
|
||||
$code = Artisan::call('artworks:check-user-refs', [
|
||||
'--chunk' => 1,
|
||||
'--show-missing' => 5,
|
||||
'--copy-missing-from-legacy' => true,
|
||||
'--legacy-users-table' => 'legacy_users',
|
||||
]);
|
||||
|
||||
$output = Artisan::output();
|
||||
file_put_contents(storage_path('logs/check-artwork-user-refs-copy-test-output.log'), $output);
|
||||
|
||||
expect($code)->toBe(0)
|
||||
->and(DB::table('users')->where('id', $legacyUserId)->exists())->toBeTrue()
|
||||
->and(DB::table('user_profiles')->where('user_id', $legacyUserId)->value('country_code'))->toBe('FI')
|
||||
->and($output)->toContain('[copied] imported legacy user #777 username=@legacy_artist name="Legacy Artist" email=<legacy.artist@example.test>')
|
||||
->and($output)->toContain('Checked 1 artworks: 1 valid, 0 missing user references, 0 null user_id values.')
|
||||
->and($output)->toContain('Legacy copy summary: requested 1 users, copied 1, would copy 0, conflicts 0, not found in legacy 0, errors 0.')
|
||||
->and($output)->toContain('Copied or would-copy user ids: 777');
|
||||
|
||||
Schema::connection('legacy')->dropIfExists('legacy_users');
|
||||
});
|
||||
|
||||
it('shows dry run copy details for legacy imports', function (): void {
|
||||
$legacyUserId = 778;
|
||||
$artwork = Artwork::factory()->create();
|
||||
|
||||
DB::table('artworks')
|
||||
->where('id', $artwork->id)
|
||||
->update(['user_id' => $legacyUserId]);
|
||||
|
||||
config()->set('database.connections.legacy', config('database.connections.' . config('database.default')));
|
||||
DB::purge('legacy');
|
||||
|
||||
Schema::connection('legacy')->dropIfExists('legacy_users');
|
||||
Schema::connection('legacy')->create('legacy_users', function (Blueprint $table): void {
|
||||
$table->unsignedBigInteger('user_id')->primary();
|
||||
$table->string('uname')->nullable();
|
||||
$table->string('real_name')->nullable();
|
||||
$table->string('email')->nullable();
|
||||
$table->unsignedTinyInteger('active')->default(1);
|
||||
$table->timestamp('joinDate')->nullable();
|
||||
$table->timestamp('LastVisit')->nullable();
|
||||
$table->string('country')->nullable();
|
||||
$table->string('country_code', 2)->nullable();
|
||||
$table->string('web')->nullable();
|
||||
$table->text('about_me')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
$table->string('gender', 16)->nullable();
|
||||
});
|
||||
|
||||
DB::connection('legacy')->table('legacy_users')->insert([
|
||||
'user_id' => $legacyUserId,
|
||||
'uname' => 'legacy_preview',
|
||||
'real_name' => 'Legacy Preview',
|
||||
'email' => 'legacy.preview@example.test',
|
||||
'active' => 1,
|
||||
]);
|
||||
|
||||
$code = Artisan::call('artworks:check-user-refs', [
|
||||
'--chunk' => 1,
|
||||
'--show-missing' => 5,
|
||||
'--copy-missing-from-legacy' => true,
|
||||
'--dry-run-copy' => true,
|
||||
'--legacy-users-table' => 'legacy_users',
|
||||
]);
|
||||
|
||||
$output = Artisan::output();
|
||||
|
||||
expect($code)->toBe(1)
|
||||
->and(DB::table('users')->where('id', $legacyUserId)->exists())->toBeFalse()
|
||||
->and($output)->toContain('[dry-run] would import legacy user #778 username=@legacy_preview name="Legacy Preview" email=<legacy.preview@example.test>')
|
||||
->and($output)->toContain('Legacy copy summary: requested 1 users, copied 0, would copy 1, conflicts 0, not found in legacy 0, errors 0.');
|
||||
|
||||
Schema::connection('legacy')->dropIfExists('legacy_users');
|
||||
});
|
||||
91
tests/Feature/Console/PublishScheduledNewsCommandTest.php
Normal file
91
tests/Feature/Console/PublishScheduledNewsCommandTest.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use cPad\Plugins\News\Models\NewsArticle;
|
||||
use cPad\Plugins\News\Models\NewsCategory;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('publishes scheduled news articles whose publish time has passed', function (): void {
|
||||
$author = User::factory()->create();
|
||||
$category = NewsCategory::query()->create([
|
||||
'name' => 'Announcements',
|
||||
'slug' => 'announcements',
|
||||
'description' => 'Announcement category',
|
||||
'position' => 0,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$dueArticle = NewsArticle::query()->create([
|
||||
'title' => 'Due scheduled article',
|
||||
'slug' => 'due-scheduled-article',
|
||||
'excerpt' => 'Due now.',
|
||||
'content' => 'Body',
|
||||
'author_id' => $author->id,
|
||||
'category_id' => $category->id,
|
||||
'type' => NewsArticle::TYPE_ANNOUNCEMENT,
|
||||
'status' => 'scheduled',
|
||||
'editorial_status' => NewsArticle::EDITORIAL_STATUS_SCHEDULED,
|
||||
'published_at' => now()->subMinute(),
|
||||
]);
|
||||
|
||||
$futureArticle = NewsArticle::query()->create([
|
||||
'title' => 'Future scheduled article',
|
||||
'slug' => 'future-scheduled-article',
|
||||
'excerpt' => 'Not due yet.',
|
||||
'content' => 'Body',
|
||||
'author_id' => $author->id,
|
||||
'category_id' => $category->id,
|
||||
'type' => NewsArticle::TYPE_ANNOUNCEMENT,
|
||||
'status' => 'scheduled',
|
||||
'editorial_status' => NewsArticle::EDITORIAL_STATUS_SCHEDULED,
|
||||
'published_at' => now()->addHour(),
|
||||
]);
|
||||
|
||||
$this->artisan('news:publish-scheduled')
|
||||
->expectsOutput(sprintf('Published News article #%d: "%s"', $dueArticle->id, $dueArticle->title))
|
||||
->expectsOutput('Done. Published: 1, Errors: 0.')
|
||||
->assertSuccessful();
|
||||
|
||||
expect($dueArticle->fresh())
|
||||
->editorial_status->toBe(NewsArticle::EDITORIAL_STATUS_PUBLISHED)
|
||||
->status->toBe('published')
|
||||
->and($futureArticle->fresh())
|
||||
->editorial_status->toBe(NewsArticle::EDITORIAL_STATUS_SCHEDULED)
|
||||
->status->toBe('scheduled');
|
||||
});
|
||||
|
||||
it('supports dry run for scheduled news publishing', function (): void {
|
||||
$author = User::factory()->create();
|
||||
$category = NewsCategory::query()->create([
|
||||
'name' => 'Platform',
|
||||
'slug' => 'platform',
|
||||
'description' => 'Platform category',
|
||||
'position' => 0,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$article = NewsArticle::query()->create([
|
||||
'title' => 'Dry run article',
|
||||
'slug' => 'dry-run-article',
|
||||
'excerpt' => 'Due but not published in dry run.',
|
||||
'content' => 'Body',
|
||||
'author_id' => $author->id,
|
||||
'category_id' => $category->id,
|
||||
'type' => NewsArticle::TYPE_PLATFORM_UPDATE,
|
||||
'status' => 'scheduled',
|
||||
'editorial_status' => NewsArticle::EDITORIAL_STATUS_SCHEDULED,
|
||||
'published_at' => now()->subMinute(),
|
||||
]);
|
||||
|
||||
$this->artisan('news:publish-scheduled', ['--dry-run' => true])
|
||||
->expectsOutput(sprintf('[dry-run] Would publish News article #%d: "%s"', $article->id, $article->title))
|
||||
->assertSuccessful();
|
||||
|
||||
expect($article->fresh())
|
||||
->editorial_status->toBe(NewsArticle::EDITORIAL_STATUS_SCHEDULED)
|
||||
->status->toBe('scheduled');
|
||||
});
|
||||
168
tests/Feature/Groups/GroupPolicyTest.php
Normal file
168
tests/Feature/Groups/GroupPolicyTest.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Group;
|
||||
use App\Models\GroupMember;
|
||||
use App\Models\User;
|
||||
use App\Policies\GroupPolicy;
|
||||
use App\Services\GroupMembershipService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
function attachGroupMember(Group $group, User $user, string $role): GroupMember
|
||||
{
|
||||
return GroupMember::query()->create([
|
||||
'group_id' => $group->id,
|
||||
'user_id' => $user->id,
|
||||
'invited_by_user_id' => $group->owner_user_id,
|
||||
'role' => $role,
|
||||
'status' => Group::STATUS_ACTIVE,
|
||||
'invited_at' => now(),
|
||||
'accepted_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
it('allows contributors into studio but not management or publishing policy actions', function () {
|
||||
$owner = User::factory()->create();
|
||||
$contributor = User::factory()->create();
|
||||
$group = Group::factory()->for($owner, 'owner')->create();
|
||||
|
||||
app(GroupMembershipService::class)->ensureOwnerMembership($group);
|
||||
attachGroupMember($group, $contributor, Group::ROLE_MEMBER);
|
||||
|
||||
$policy = app(GroupPolicy::class);
|
||||
|
||||
expect($policy->viewStudio($contributor, $group))->toBeTrue()
|
||||
->and($policy->update($contributor, $group))->toBeFalse()
|
||||
->and($policy->manageMembers($contributor, $group))->toBeFalse()
|
||||
->and($policy->publishArtworks($contributor, $group))->toBeFalse()
|
||||
->and($policy->manageCollections($contributor, $group))->toBeFalse();
|
||||
});
|
||||
|
||||
it('allows editors to publish artworks and manage collections without member administration', function () {
|
||||
$owner = User::factory()->create();
|
||||
$editor = User::factory()->create();
|
||||
$group = Group::factory()->for($owner, 'owner')->create();
|
||||
|
||||
app(GroupMembershipService::class)->ensureOwnerMembership($group);
|
||||
attachGroupMember($group, $editor, Group::ROLE_EDITOR);
|
||||
|
||||
$policy = app(GroupPolicy::class);
|
||||
|
||||
expect($policy->viewStudio($editor, $group))->toBeTrue()
|
||||
->and($policy->publishArtworks($editor, $group))->toBeTrue()
|
||||
->and($policy->manageCollections($editor, $group))->toBeTrue()
|
||||
->and($policy->manageMembers($editor, $group))->toBeFalse()
|
||||
->and($policy->archive($editor, $group))->toBeFalse();
|
||||
});
|
||||
|
||||
it('allows admins to manage group settings and members but not archive ownership actions', function () {
|
||||
$owner = User::factory()->create();
|
||||
$admin = User::factory()->create();
|
||||
$group = Group::factory()->for($owner, 'owner')->create();
|
||||
|
||||
app(GroupMembershipService::class)->ensureOwnerMembership($group);
|
||||
attachGroupMember($group, $admin, Group::ROLE_ADMIN);
|
||||
|
||||
$policy = app(GroupPolicy::class);
|
||||
|
||||
expect($policy->viewStudio($admin, $group))->toBeTrue()
|
||||
->and($policy->update($admin, $group))->toBeTrue()
|
||||
->and($policy->manageMembers($admin, $group))->toBeTrue()
|
||||
->and($policy->publishArtworks($admin, $group))->toBeTrue()
|
||||
->and($policy->archive($admin, $group))->toBeFalse();
|
||||
});
|
||||
|
||||
it('blocks studio access for suspended groups even for active non-owner members', function () {
|
||||
$owner = User::factory()->create();
|
||||
$editor = User::factory()->create();
|
||||
$group = Group::factory()->for($owner, 'owner')->create([
|
||||
'status' => Group::LIFECYCLE_SUSPENDED,
|
||||
]);
|
||||
|
||||
app(GroupMembershipService::class)->ensureOwnerMembership($group);
|
||||
attachGroupMember($group, $editor, Group::ROLE_EDITOR);
|
||||
|
||||
$policy = app(GroupPolicy::class);
|
||||
|
||||
expect($policy->viewStudio($editor, $group))->toBeFalse()
|
||||
->and($policy->publishArtworks($editor, $group))->toBeFalse()
|
||||
->and($policy->manageCollections($editor, $group))->toBeFalse();
|
||||
});
|
||||
|
||||
it('keeps archive authority with the owner', function () {
|
||||
$owner = User::factory()->create();
|
||||
$group = Group::factory()->for($owner, 'owner')->create();
|
||||
|
||||
app(GroupMembershipService::class)->ensureOwnerMembership($group);
|
||||
|
||||
$policy = app(GroupPolicy::class);
|
||||
|
||||
expect($policy->update($owner, $group))->toBeTrue()
|
||||
->and($policy->manageMembers($owner, $group))->toBeTrue()
|
||||
->and($policy->publishArtworks($owner, $group))->toBeTrue()
|
||||
->and($policy->archive($owner, $group))->toBeTrue();
|
||||
});
|
||||
|
||||
it('does not allow admins or editors to transfer ownership through policy update access alone', function () {
|
||||
$owner = User::factory()->create();
|
||||
$admin = User::factory()->create();
|
||||
$editor = User::factory()->create();
|
||||
$group = Group::factory()->for($owner, 'owner')->create();
|
||||
|
||||
app(GroupMembershipService::class)->ensureOwnerMembership($group);
|
||||
attachGroupMember($group, $admin, Group::ROLE_ADMIN);
|
||||
attachGroupMember($group, $editor, Group::ROLE_EDITOR);
|
||||
|
||||
$policy = app(GroupPolicy::class);
|
||||
|
||||
expect($policy->update($admin, $group))->toBeTrue()
|
||||
->and($policy->archive($admin, $group))->toBeFalse()
|
||||
->and($policy->manageMembers($editor, $group))->toBeFalse();
|
||||
});
|
||||
|
||||
it('exposes explicit v3 event and private access policy hooks', function () {
|
||||
$owner = User::factory()->create();
|
||||
$editor = User::factory()->create();
|
||||
$member = User::factory()->create();
|
||||
$group = Group::factory()->for($owner, 'owner')->create();
|
||||
|
||||
app(GroupMembershipService::class)->ensureOwnerMembership($group);
|
||||
attachGroupMember($group, $editor, Group::ROLE_EDITOR);
|
||||
attachGroupMember($group, $member, Group::ROLE_MEMBER);
|
||||
|
||||
$policy = app(GroupPolicy::class);
|
||||
|
||||
expect($policy->publishEventUpdates($editor, $group))->toBeTrue()
|
||||
->and($policy->viewInternalEvents($editor, $group))->toBeTrue()
|
||||
->and($policy->viewPrivateProject($editor, $group))->toBeTrue()
|
||||
->and($policy->participateInChallenge($member, $group))->toBeTrue()
|
||||
->and($policy->publishEventUpdates($member, $group))->toBeFalse();
|
||||
});
|
||||
|
||||
it('exposes explicit v4 release, milestone, badge, and trust policy hooks', function () {
|
||||
$owner = User::factory()->create();
|
||||
$admin = User::factory()->create();
|
||||
$editor = User::factory()->create();
|
||||
$member = User::factory()->create();
|
||||
$group = Group::factory()->for($owner, 'owner')->create();
|
||||
|
||||
app(GroupMembershipService::class)->ensureOwnerMembership($group);
|
||||
attachGroupMember($group, $admin, Group::ROLE_ADMIN);
|
||||
attachGroupMember($group, $editor, Group::ROLE_EDITOR);
|
||||
attachGroupMember($group, $member, Group::ROLE_MEMBER);
|
||||
|
||||
$policy = app(GroupPolicy::class);
|
||||
|
||||
expect($policy->manageReleases($editor, $group))->toBeTrue()
|
||||
->and($policy->publishReleases($editor, $group))->toBeTrue()
|
||||
->and($policy->moveReleaseStage($editor, $group))->toBeTrue()
|
||||
->and($policy->manageMilestones($editor, $group))->toBeTrue()
|
||||
->and($policy->assignReleaseLead($editor, $group))->toBeTrue()
|
||||
->and($policy->viewReputationDashboard($editor, $group))->toBeFalse()
|
||||
->and($policy->manageBadges($admin, $group))->toBeTrue()
|
||||
->and($policy->viewInternalTrustMetrics($admin, $group))->toBeTrue()
|
||||
->and($policy->featureRelease($admin, $group))->toBeTrue()
|
||||
->and($policy->manageReleases($member, $group))->toBeFalse()
|
||||
->and($policy->viewReputationDashboard($member, $group))->toBeFalse();
|
||||
});
|
||||
19
tests/Feature/Groups/GroupsFaqPageTest.php
Normal file
19
tests/Feature/Groups/GroupsFaqPageTest.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
it('renders the groups faq page with related help links', function () {
|
||||
$this->get(route('help.groups.faq'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Group/GroupFaqPage')
|
||||
->where('title', 'Groups FAQ')
|
||||
->where('seo.canonical', route('help.groups.faq'))
|
||||
->where('links.full_documentation', route('help.groups'))
|
||||
->where('links.quickstart', route('help.groups.quickstart'))
|
||||
->where('links.group_studio', route('studio.groups.index'))
|
||||
->where('links.create_group', route('studio.groups.create'))
|
||||
->where('links.contact_support', route('contact.show'))
|
||||
->where('links.report_issue', route('bug-report'))
|
||||
);
|
||||
});
|
||||
2088
tests/Feature/Groups/GroupsFeatureTest.php
Normal file
2088
tests/Feature/Groups/GroupsFeatureTest.php
Normal file
File diff suppressed because it is too large
Load Diff
18
tests/Feature/Groups/GroupsHelpPageTest.php
Normal file
18
tests/Feature/Groups/GroupsHelpPageTest.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
it('renders the groups help page with real internal links', function () {
|
||||
$this->get(route('help.groups'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Group/GroupHelpPage')
|
||||
->where('title', 'Groups Help & Guide')
|
||||
->where('seo.canonical', route('help.groups'))
|
||||
->where('links.groups_directory', route('groups.index'))
|
||||
->where('links.create_group', route('studio.groups.create'))
|
||||
->where('links.group_studio', route('studio.groups.index'))
|
||||
->where('links.contact_support', route('contact.show'))
|
||||
->where('links.report_issue', route('bug-report'))
|
||||
);
|
||||
});
|
||||
17
tests/Feature/Groups/GroupsQuickstartPageTest.php
Normal file
17
tests/Feature/Groups/GroupsQuickstartPageTest.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
it('renders the groups quickstart page with onboarding links', function () {
|
||||
$this->get(route('help.groups.quickstart'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Group/GroupQuickstartPage')
|
||||
->where('title', 'Groups Quickstart')
|
||||
->where('seo.canonical', route('help.groups.quickstart'))
|
||||
->where('links.create_group', route('studio.groups.create'))
|
||||
->where('links.group_studio', route('studio.groups.index'))
|
||||
->where('links.groups_directory', route('groups.index'))
|
||||
->where('links.full_documentation', route('help.groups'))
|
||||
);
|
||||
});
|
||||
26
tests/Feature/Help/AccountHelpPageTest.php
Normal file
26
tests/Feature/Help/AccountHelpPageTest.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
it('renders the account help page with real internal links', function () {
|
||||
$this->get(route('help.account'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Help/AccountHelpPage')
|
||||
->where('title', 'Account Settings Help')
|
||||
->where('seo.canonical', route('help.account'))
|
||||
->where('links.help_home', route('help'))
|
||||
->where('links.help_auth', route('help.auth'))
|
||||
->where('links.help_profile', route('help.profile'))
|
||||
->where('links.studio_help', route('help.studio'))
|
||||
->where('links.upload_help', route('help.upload'))
|
||||
->where('links.help_troubleshooting', route('help.troubleshooting'))
|
||||
->where('links.profile_settings', route('dashboard.profile'))
|
||||
->where('links.open_studio', route('studio.index'))
|
||||
->where('links.login', route('login'))
|
||||
->where('links.register', route('register'))
|
||||
->where('links.password_request', route('password.request'))
|
||||
->where('links.contact_support', route('contact.show'))
|
||||
->where('links.report_issue', route('bug-report'))
|
||||
);
|
||||
});
|
||||
27
tests/Feature/Help/AuthHelpPageTest.php
Normal file
27
tests/Feature/Help/AuthHelpPageTest.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
it('renders the auth help page with real internal links', function () {
|
||||
$this->get(route('help.auth'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Help/AuthHelpPage')
|
||||
->where('title', 'Signup & Login Help')
|
||||
->where('seo.canonical', route('help.auth'))
|
||||
->where('links.help_home', route('help'))
|
||||
->where('links.help_profile', route('help.profile'))
|
||||
->where('links.studio_help', route('help.studio'))
|
||||
->where('links.groups_help', route('help.groups'))
|
||||
->where('links.help_account', route('help.account'))
|
||||
->where('links.help_troubleshooting', route('help.troubleshooting'))
|
||||
->where('links.login', route('login'))
|
||||
->where('links.register', route('register'))
|
||||
->where('links.password_request', route('password.request'))
|
||||
->where('links.verification_notice', route('verification.notice'))
|
||||
->where('links.open_studio', route('studio.index'))
|
||||
->where('links.profile_settings', route('dashboard.profile'))
|
||||
->where('links.contact_support', route('contact.show'))
|
||||
->where('links.report_issue', route('bug-report'))
|
||||
);
|
||||
});
|
||||
24
tests/Feature/Help/CardsHelpPageTest.php
Normal file
24
tests/Feature/Help/CardsHelpPageTest.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
it('renders the cards help page with real internal links', function () {
|
||||
$this->get(route('help.cards'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Help/CardsHelpPage')
|
||||
->where('title', 'Cards Help')
|
||||
->where('seo.canonical', route('help.cards'))
|
||||
->where('links.help_home', route('help'))
|
||||
->where('links.studio_help', route('help.studio'))
|
||||
->where('links.upload_help', route('help.upload'))
|
||||
->where('links.groups_help', route('help.groups'))
|
||||
->where('links.open_studio', route('studio.index'))
|
||||
->where('links.studio_cards', route('studio.cards.index'))
|
||||
->where('links.create_card', route('studio.cards.create'))
|
||||
->where('links.cards_index', route('cards.index'))
|
||||
->where('links.help_profile', route('help.profile'))
|
||||
->where('links.contact_support', route('contact.show'))
|
||||
->where('links.report_issue', route('bug-report'))
|
||||
);
|
||||
});
|
||||
32
tests/Feature/Help/HelpCenterPageTest.php
Normal file
32
tests/Feature/Help/HelpCenterPageTest.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
it('renders the help center homepage with key platform help links', function () {
|
||||
$this->get(route('help'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Help/HelpCenterPage')
|
||||
->where('title', 'Help Center')
|
||||
->where('seo.canonical', route('help'))
|
||||
->where('links.studio_help', route('help.studio'))
|
||||
->where('links.upload_help', route('help.upload'))
|
||||
->where('links.groups_documentation', route('help.groups'))
|
||||
->where('links.groups_quickstart', route('help.groups.quickstart'))
|
||||
->where('links.groups_faq', route('help.groups.faq'))
|
||||
->where('links.open_studio', route('studio.index'))
|
||||
->where('links.studio_home', route('studio.index'))
|
||||
->where('links.upload', route('upload'))
|
||||
->where('links.help_cards', route('help.cards'))
|
||||
->where('links.help_profile', route('help.profile'))
|
||||
->where('links.help_auth', route('help.auth'))
|
||||
->where('links.help_account', route('help.account'))
|
||||
->where('links.help_troubleshooting', route('help.troubleshooting'))
|
||||
->where('links.cards_create', route('studio.cards.create'))
|
||||
->where('links.profile_settings', route('dashboard.profile'))
|
||||
->where('links.login', route('login'))
|
||||
->where('links.register', route('register'))
|
||||
->where('links.contact_support', route('contact.show'))
|
||||
->where('links.report_issue', route('bug-report'))
|
||||
);
|
||||
});
|
||||
25
tests/Feature/Help/ProfileHelpPageTest.php
Normal file
25
tests/Feature/Help/ProfileHelpPageTest.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
it('renders the profile help page with real internal links', function () {
|
||||
$this->get(route('help.profile'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Help/ProfileHelpPage')
|
||||
->where('title', 'Profile Help')
|
||||
->where('seo.canonical', route('help.profile'))
|
||||
->where('links.help_home', route('help'))
|
||||
->where('links.groups_help', route('help.groups'))
|
||||
->where('links.studio_help', route('help.studio'))
|
||||
->where('links.upload_help', route('help.upload'))
|
||||
->where('links.cards_help', route('help.cards'))
|
||||
->where('links.profile_settings', route('dashboard.profile'))
|
||||
->where('links.open_studio', route('studio.index'))
|
||||
->where('links.help_auth', route('help.auth'))
|
||||
->where('links.login', route('login'))
|
||||
->where('links.register', route('register'))
|
||||
->where('links.contact_support', route('contact.show'))
|
||||
->where('links.report_issue', route('bug-report'))
|
||||
);
|
||||
});
|
||||
23
tests/Feature/Help/StudioHelpPageTest.php
Normal file
23
tests/Feature/Help/StudioHelpPageTest.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
it('renders the studio help page with real internal links', function () {
|
||||
$this->get(route('help.studio'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Help/StudioHelpPage')
|
||||
->where('title', 'Studio Help')
|
||||
->where('seo.canonical', route('help.studio'))
|
||||
->where('links.open_studio', route('studio.index'))
|
||||
->where('links.studio_drafts', route('studio.drafts'))
|
||||
->where('links.upload_help', route('help.upload'))
|
||||
->where('links.group_studio', route('studio.groups.index'))
|
||||
->where('links.groups_help', route('help.groups'))
|
||||
->where('links.help_cards', route('help.cards'))
|
||||
->where('links.help_profile', route('help.profile'))
|
||||
->where('links.help_auth', route('help.auth'))
|
||||
->where('links.contact_support', route('contact.show'))
|
||||
->where('links.report_issue', route('bug-report'))
|
||||
);
|
||||
});
|
||||
28
tests/Feature/Help/TroubleshootingHelpPageTest.php
Normal file
28
tests/Feature/Help/TroubleshootingHelpPageTest.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
it('renders the troubleshooting help page with real internal links', function () {
|
||||
$this->get(route('help.troubleshooting'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Help/TroubleshootingHelpPage')
|
||||
->where('title', 'Troubleshooting Help')
|
||||
->where('seo.canonical', route('help.troubleshooting'))
|
||||
->where('links.help_home', route('help'))
|
||||
->where('links.help_auth', route('help.auth'))
|
||||
->where('links.help_account', route('help.account'))
|
||||
->where('links.help_profile', route('help.profile'))
|
||||
->where('links.studio_help', route('help.studio'))
|
||||
->where('links.upload_help', route('help.upload'))
|
||||
->where('links.groups_help', route('help.groups'))
|
||||
->where('links.groups_faq', route('help.groups.faq'))
|
||||
->where('links.profile_settings', route('dashboard.profile'))
|
||||
->where('links.open_studio', route('studio.index'))
|
||||
->where('links.login', route('login'))
|
||||
->where('links.register', route('register'))
|
||||
->where('links.password_request', route('password.request'))
|
||||
->where('links.contact_support', route('contact.show'))
|
||||
->where('links.report_issue', route('bug-report'))
|
||||
);
|
||||
});
|
||||
22
tests/Feature/Help/UploadHelpPageTest.php
Normal file
22
tests/Feature/Help/UploadHelpPageTest.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
it('renders the upload help page with real internal links', function () {
|
||||
$this->get(route('help.upload'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Help/UploadHelpPage')
|
||||
->where('title', 'Upload Help')
|
||||
->where('seo.canonical', route('help.upload'))
|
||||
->where('links.upload', route('upload'))
|
||||
->where('links.studio_help', route('help.studio'))
|
||||
->where('links.studio_drafts', route('studio.drafts'))
|
||||
->where('links.groups_help', route('help.groups'))
|
||||
->where('links.group_studio', route('studio.groups.index'))
|
||||
->where('links.help_cards', route('help.cards'))
|
||||
->where('links.help_profile', route('help.profile'))
|
||||
->where('links.contact_support', route('contact.show'))
|
||||
->where('links.report_issue', route('bug-report'))
|
||||
);
|
||||
});
|
||||
105
tests/Feature/News/NewsPublicPagesTest.php
Normal file
105
tests/Feature/News/NewsPublicPagesTest.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
use cPad\Plugins\News\Models\NewsArticle;
|
||||
use cPad\Plugins\News\Models\NewsCategory;
|
||||
use cPad\Plugins\News\Models\NewsTag;
|
||||
|
||||
function newsCategory(array $attributes = []): NewsCategory
|
||||
{
|
||||
return NewsCategory::query()->create(array_merge([
|
||||
'name' => 'Platform Updates',
|
||||
'slug' => 'platform-updates-' . Str::lower(Str::random(6)),
|
||||
'description' => 'Skinbase platform updates.',
|
||||
'position' => 0,
|
||||
'is_active' => true,
|
||||
], $attributes));
|
||||
}
|
||||
|
||||
function newsTag(array $attributes = []): NewsTag
|
||||
{
|
||||
return NewsTag::query()->create(array_merge([
|
||||
'name' => 'Launch',
|
||||
'slug' => 'launch-' . Str::lower(Str::random(6)),
|
||||
], $attributes));
|
||||
}
|
||||
|
||||
function publishedNewsArticle(User $author, NewsCategory $category, array $attributes = []): NewsArticle
|
||||
{
|
||||
$article = NewsArticle::query()->create(array_merge([
|
||||
'title' => 'Nova newsroom launch',
|
||||
'slug' => 'nova-newsroom-launch-' . Str::lower(Str::random(6)),
|
||||
'excerpt' => 'A first-party newsroom for launches, tutorials, and editorials.',
|
||||
'content' => "# Nova newsroom\n\nThis is the first newsroom article.",
|
||||
'author_id' => $author->id,
|
||||
'category_id' => $category->id,
|
||||
'type' => NewsArticle::TYPE_PLATFORM_UPDATE,
|
||||
'status' => 'published',
|
||||
'editorial_status' => NewsArticle::EDITORIAL_STATUS_PUBLISHED,
|
||||
'published_at' => now()->subHour(),
|
||||
'is_featured' => true,
|
||||
'is_pinned' => true,
|
||||
], $attributes));
|
||||
|
||||
return $article;
|
||||
}
|
||||
|
||||
it('renders published news across public discovery routes', function (): void {
|
||||
$author = User::factory()->create([
|
||||
'username' => 'newsauthor',
|
||||
'name' => 'News Author',
|
||||
]);
|
||||
$category = newsCategory([
|
||||
'name' => 'Announcements',
|
||||
'slug' => 'announcements',
|
||||
]);
|
||||
$tag = newsTag([
|
||||
'name' => 'Release',
|
||||
'slug' => 'release',
|
||||
]);
|
||||
$article = publishedNewsArticle($author, $category, [
|
||||
'title' => 'Skinbase Nova Newsroom',
|
||||
'slug' => 'skinbase-nova-newsroom',
|
||||
]);
|
||||
$article->tags()->sync([$tag->id]);
|
||||
|
||||
publishedNewsArticle($author, $category, [
|
||||
'title' => 'Hidden Draft',
|
||||
'slug' => 'hidden-draft',
|
||||
'status' => 'draft',
|
||||
'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
|
||||
'published_at' => null,
|
||||
'is_featured' => false,
|
||||
'is_pinned' => false,
|
||||
]);
|
||||
|
||||
$this->get(route('news.index'))
|
||||
->assertOk()
|
||||
->assertSee('Skinbase Nova Newsroom')
|
||||
->assertDontSee('Hidden Draft');
|
||||
|
||||
$this->get(route('news.show', ['slug' => $article->slug]))
|
||||
->assertOk()
|
||||
->assertSee('Skinbase Nova Newsroom')
|
||||
->assertSee('News Author');
|
||||
|
||||
$this->get(route('news.category', ['slug' => $category->slug]))
|
||||
->assertOk()
|
||||
->assertSee('Skinbase Nova Newsroom');
|
||||
|
||||
$this->get(route('news.tag', ['slug' => $tag->slug]))
|
||||
->assertOk()
|
||||
->assertSee('Skinbase Nova Newsroom');
|
||||
|
||||
$this->get(route('news.archive', ['year' => $article->published_at->year, 'month' => $article->published_at->month]))
|
||||
->assertOk()
|
||||
->assertSee('Skinbase Nova Newsroom');
|
||||
|
||||
$this->get(route('news.author', ['username' => $author->username]))
|
||||
->assertOk()
|
||||
->assertSee('Skinbase Nova Newsroom');
|
||||
});
|
||||
216
tests/Feature/Studio/StudioNewsPagesTest.php
Normal file
216
tests/Feature/Studio/StudioNewsPagesTest.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?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],
|
||||
'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]);
|
||||
});
|
||||
Reference in New Issue
Block a user