'Story ' . Str::random(8), 'cover_image' => 'https://example.test/cover.jpg', 'excerpt' => 'A compact excerpt for testing.', 'story_type' => 'creator_story', 'content' => '

Hello Story World

', 'status' => 'draft', 'submit_action' => 'save_draft', ], $overrides); } it('creator can create a draft story from editor form', function () { $creator = User::factory()->create(); $response = $this->actingAs($creator) ->post(route('creator.stories.store'), storyPayload()); $story = Story::query()->where('creator_id', $creator->id)->latest('id')->first(); expect($story)->not->toBeNull(); expect($story->status)->toBe('draft'); expect($story->slug)->not->toBe(''); $response->assertRedirect(route('creator.stories.edit', ['story' => $story->id])); }); it('creator autosave updates draft fields and creates tags from csv', function () { $creator = User::factory()->create(); $story = Story::query()->create([ 'creator_id' => $creator->id, 'title' => 'Autosave Draft', 'slug' => 'autosave-draft-' . Str::lower(Str::random(6)), 'content' => '

Original

', 'story_type' => 'creator_story', 'status' => 'draft', ]); $response = $this->actingAs($creator)->postJson( route('creator.stories.autosave', ['story' => $story->id]), [ 'title' => 'Autosaved Title', 'content' => '

Autosaved content with enough words for reading time.

', 'tags_csv' => 'alpha,beta', ] ); $response->assertOk()->assertJson(['ok' => true]); $story->refresh(); expect($story->title)->toBe('Autosaved Title'); expect($story->status)->toBe('draft'); $this->assertDatabaseHas('story_tags', ['slug' => 'alpha']); $this->assertDatabaseHas('story_tags', ['slug' => 'beta']); $tagIds = DB::table('story_tags')->whereIn('slug', ['alpha', 'beta'])->pluck('id')->all(); foreach ($tagIds as $tagId) { $this->assertDatabaseHas('relation_story_tags', [ 'story_id' => $story->id, 'tag_id' => $tagId, ]); } }); it('creator can submit draft for review', function () { $creator = User::factory()->create(); $story = Story::query()->create([ 'creator_id' => $creator->id, 'title' => 'Review Draft', 'slug' => 'review-draft-' . Str::lower(Str::random(6)), 'content' => '

Review content

', 'story_type' => 'creator_story', 'status' => 'draft', ]); $response = $this->actingAs($creator) ->post(route('creator.stories.submit-review', ['story' => $story->id])); $response->assertRedirect(); $story->refresh(); expect($story->status)->toBe('pending_review'); expect($story->submitted_for_review_at)->not->toBeNull(); }); it('creator publish records story activity and stores a story_published notification', function () { $creator = User::factory()->create(); $story = Story::query()->create([ 'creator_id' => $creator->id, 'title' => 'Publish Me', 'slug' => 'publish-me-' . Str::lower(Str::random(6)), 'content' => '

Publish content

', 'story_type' => 'creator_story', 'status' => 'draft', ]); $response = $this->actingAs($creator) ->post(route('creator.stories.publish-now', ['story' => $story->id])); $response->assertRedirect(route('stories.show', ['slug' => $story->slug])); $story->refresh(); expect($story->status)->toBe('published'); expect($story->published_at)->not->toBeNull(); $this->assertDatabaseHas('activity_events', [ 'actor_id' => $creator->id, 'type' => ActivityEvent::TYPE_UPLOAD, 'target_type' => ActivityEvent::TARGET_STORY, 'target_id' => $story->id, ]); $notification = $creator->fresh() ->notifications() ->where('type', 'story_published') ->latest() ->first(); expect($notification)->toBeInstanceOf(Notification::class); expect($notification->data['type'] ?? null)->toBe('story_published'); }); it('published story page renders successfully', function () { $creator = User::factory()->create(); $story = Story::query()->create([ 'creator_id' => $creator->id, 'title' => 'Renderable Story', 'slug' => 'renderable-story-' . Str::lower(Str::random(6)), 'excerpt' => 'Renderable excerpt', 'content' => json_encode([ 'type' => 'doc', 'content' => [ ['type' => 'paragraph', 'content' => [['type' => 'text', 'text' => 'Renderable story content.']]], ['type' => 'codeBlock', 'attrs' => ['language' => 'bash'], 'content' => [['type' => 'text', 'text' => 'git clone https://github.com/klevze/sqlBackup.git']]], ], ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 'story_type' => 'creator_story', 'status' => 'published', 'published_at' => now()->subMinute(), ]); $this->get(route('stories.show', ['slug' => $story->slug])) ->assertOk() ->assertSee('Renderable Story', false) ->assertSee('language-bash', false) ->assertDontSee('{"type":"doc"', false); }); it('creator can publish through story editor api create endpoint', function () { $creator = User::factory()->create(); $response = $this->actingAs($creator) ->postJson(route('api.stories.create'), [ 'title' => 'API Publish Story', 'story_type' => 'creator_story', 'content' => [ 'type' => 'doc', 'content' => [ ['type' => 'paragraph', 'content' => [['type' => 'text', 'text' => 'Story body for API publishing.']]], ], ], 'submit_action' => 'publish_now', 'status' => 'published', ]); $response->assertOk() ->assertJsonPath('status', 'published') ->assertJsonPath('public_url', fn (string $url) => str_contains($url, '/stories/')); $storyId = (int) $response->json('story_id'); $story = Story::query()->findOrFail($storyId); expect($story->status)->toBe('published'); expect($story->published_at)->not->toBeNull(); }); it('creator can publish through story editor api update endpoint', function () { $creator = User::factory()->create(); $story = Story::query()->create([ 'creator_id' => $creator->id, 'title' => 'API Draft Story', 'slug' => 'api-draft-story-' . Str::lower(Str::random(6)), 'content' => '

Draft content

', 'story_type' => 'creator_story', 'status' => 'draft', ]); $response = $this->actingAs($creator) ->putJson(route('api.stories.update'), [ 'story_id' => $story->id, 'title' => 'API Published Story', 'story_type' => 'creator_story', 'content' => [ 'type' => 'doc', 'content' => [ ['type' => 'paragraph', 'content' => [['type' => 'text', 'text' => 'Updated story content for publication.']]], ], ], 'submit_action' => 'publish_now', 'status' => 'published', ]); $response->assertOk() ->assertJsonPath('status', 'published') ->assertJsonPath('preview_url', fn (string $url) => str_contains($url, '/preview')); $story->refresh(); expect($story->title)->toBe('API Published Story'); expect($story->status)->toBe('published'); expect($story->published_at)->not->toBeNull(); });