diff --git a/app/Actions/Automation/Node/RunGenerateNode.php b/app/Actions/Automation/Node/RunGenerateNode.php index 4daa1f06..2565169b 100644 --- a/app/Actions/Automation/Node/RunGenerateNode.php +++ b/app/Actions/Automation/Node/RunGenerateNode.php @@ -11,6 +11,7 @@ use App\Ai\Templates\TemplateContext; use App\DataTransferObjects\Automation\NodeRunResult; use App\Enums\Ai\ContentStyle; +use App\Enums\Ai\GeneratorFormat; use App\Enums\PostPlatform\ContentType; use App\Models\AutomationRun; use App\Models\SocialAccount; @@ -84,13 +85,13 @@ public function __invoke(AutomationRun $run, array $config): NodeRunResult ? $activeAccounts->get(data_get($platforms[0], 'social_account_id')) : null; - $isCarousel = $format === 'carousel'; + $isCarousel = $format->isCarousel(); $imageCount = $isCarousel ? $slideCount : ($wantsImage ? 1 : 0); $templateContext = new TemplateContext( workspace: $workspace, socialAccount: $brandAccount, - format: $platformContext ?? $format, + format: $platformContext ?? $format->value, imageCount: $imageCount, isCarousel: $isCarousel, applyBrandVisuals: (bool) data_get($config, 'use_brand_visuals', true), @@ -114,12 +115,12 @@ public function __invoke(AutomationRun $run, array $config): NodeRunResult completionTokens: $generatorResponse->usage->completionTokens, provider: (string) config('ai.default'), model: (string) config('ai.default_text_model'), - metadata: ['agent' => 'post_generator', 'format' => $format, 'source' => 'automation'], + metadata: ['agent' => 'post_generator', 'format' => $format->value, 'source' => 'automation'], ); $structured = $generatorResponse->structured ?? []; - $structured = $this->humanize($workspace, $structured, $format, $style->value, $applyBrandVoice, $platformContext); + $structured = $this->humanize($workspace, $structured, $format, $style, $applyBrandVoice, $platformContext); $intendedImageCount = $this->intendedImageCount($format, $slideCount, $wantsImage, $structured, $brandAccount, $style); @@ -161,14 +162,14 @@ public function __invoke(AutomationRun $run, array $config): NodeRunResult * @param array $structured * @return array */ - private function humanize(Workspace $workspace, array $structured, string $format, string $styleKey = 'image_card', bool $applyBrandVoice = true, ?string $platformContext = null): array + private function humanize(Workspace $workspace, array $structured, GeneratorFormat $format, ContentStyle $style, bool $applyBrandVoice = true, ?string $platformContext = null): array { - if ($styleKey === 'tweet_card' || $styleKey === 'tweet_card_image') { + if (! $style->humanizes()) { return $structured; } try { - $input = $format === 'carousel' + $input = $format->isCarousel() ? [ 'caption' => data_get($structured, 'caption', ''), 'slides' => array_map( @@ -195,10 +196,10 @@ private function humanize(Workspace $workspace, array $structured, string $forma completionTokens: $response->usage->completionTokens, provider: (string) config('ai.default'), model: (string) config('ai.default_text_model'), - metadata: ['agent' => 'post_humanizer', 'format' => $format, 'source' => 'automation'], + metadata: ['agent' => 'post_humanizer', 'format' => $format->value, 'source' => 'automation'], ); - if ($format === 'carousel') { + if ($format->isCarousel()) { $structured['caption'] = data_get($humanized, 'caption', $structured['caption'] ?? ''); $originalSlides = $structured['slides'] ?? []; $humanizedSlides = data_get($humanized, 'slides', []); @@ -232,15 +233,15 @@ private function humanize(Workspace $workspace, array $structured, string $forma * * @param array $structured */ - private function extractContent(array $structured, string $format, ContentStyle $style): string + private function extractContent(array $structured, GeneratorFormat $format, ContentStyle $style): string { - if ($style === ContentStyle::TweetCard || $style === ContentStyle::TweetCardImage) { - return $format === 'carousel' + if ($style->isTweetCard()) { + return $format->isCarousel() ? (string) data_get($structured, 'caption', '') : (string) data_get($structured, 'tweet_text', ''); } - return $format === 'carousel' + return $format->isCarousel() ? (string) data_get($structured, 'caption', '') : (string) data_get($structured, 'content', ''); } @@ -261,7 +262,7 @@ private function extractContent(array $structured, string $format, ContentStyle * * @param array}> $accountsConfig * @param array $config - * @return array{format: string, slide_count: int} + * @return array{format: GeneratorFormat, slide_count: int} */ public function deriveFormat(array $accountsConfig, array $config): array { @@ -278,10 +279,10 @@ public function deriveFormat(array $accountsConfig, array $config): array if ($maxImagesAcross > 1 && $targetSlideCount > 1) { $cap = min(GenerateNodeValidator::MAX_GENERATED_IMAGES, $maxImagesAcross); - return ['format' => 'carousel', 'slide_count' => min($targetSlideCount, $cap)]; + return ['format' => GeneratorFormat::Carousel, 'slide_count' => min($targetSlideCount, $cap)]; } - return ['format' => 'single', 'slide_count' => 1]; + return ['format' => GeneratorFormat::Single, 'slide_count' => 1]; } /** @@ -311,17 +312,17 @@ private function resolvePlatformContext(array $accountsConfig): ?string * * @param array $structured */ - private function intendedImageCount(string $format, int $slideCount, bool $wantsImage, array $structured, ?SocialAccount $brandAccount, ContentStyle $style): int + private function intendedImageCount(GeneratorFormat $format, int $slideCount, bool $wantsImage, array $structured, ?SocialAccount $brandAccount, ContentStyle $style): int { if (! $brandAccount) { return 0; } - if ($style === ContentStyle::TweetCard || $style === ContentStyle::TweetCardImage) { - return $format === 'carousel' ? $slideCount : 1; + if ($style->isTweetCard()) { + return $format->isCarousel() ? $slideCount : 1; } - if ($format === 'carousel') { + if ($format->isCarousel()) { $slides = data_get($structured, 'slides', []); return is_array($slides) ? count($slides) : $slideCount; diff --git a/app/Ai/Agents/PostContentGenerator.php b/app/Ai/Agents/PostContentGenerator.php index 524383c7..14a82146 100644 --- a/app/Ai/Agents/PostContentGenerator.php +++ b/app/Ai/Agents/PostContentGenerator.php @@ -7,6 +7,7 @@ use App\Ai\Agents\Concerns\ResolvesPlatformCopyBudget; use App\Ai\Templates\AiContentTemplate; use App\Ai\Templates\TemplateContext; +use App\Enums\Ai\GeneratorFormat; use App\Models\Workspace; use App\Services\Ai\TemplateContextResolver; use Illuminate\Contracts\JsonSchema\JsonSchema; @@ -25,7 +26,7 @@ class PostContentGenerator implements Agent, HasStructuredOutput public function __construct( public Workspace $workspace, public ?string $currentContent = null, - public string $format = 'single', + public GeneratorFormat $format = GeneratorFormat::Single, public int $slideCount = 1, public ?string $platformContext = null, public bool $applyBrandVoice = true, @@ -51,7 +52,7 @@ public function instructions(): string $budget = $this->platformCopyBudget($this->platformContext); - if ($this->template?->generatorFormat() === 'tweet_card') { + if ($this->template?->style()->isTweetCard()) { $budget['hard_max_chars'] = 560; $budget['target_chars'] = 280; } @@ -66,7 +67,7 @@ public function instructions(): string 'brand_voice_traits' => $this->applyBrandVoice ? ($this->workspace->brand_voice_traits ?? []) : [], 'content_language' => $this->workspace->content_language, 'current_content' => $this->currentContent, - 'format' => $this->format, + 'format' => $this->format->value, 'slide_count' => $this->slideCount, 'examples' => $examples, 'hard_max_chars' => $budget['hard_max_chars'], @@ -81,7 +82,7 @@ public function schema(JsonSchema $schema): array return $this->template->schema($schema, $this->templateContext); } - if ($this->format === 'carousel') { + if ($this->format->isCarousel()) { return [ 'caption' => $schema->string()->description('The Instagram caption for the carousel post.')->required(), 'slides' => $schema->array() diff --git a/app/Ai/Agents/PostContentHumanizer.php b/app/Ai/Agents/PostContentHumanizer.php index 4eba6f5c..36eb5701 100644 --- a/app/Ai/Agents/PostContentHumanizer.php +++ b/app/Ai/Agents/PostContentHumanizer.php @@ -5,6 +5,7 @@ namespace App\Ai\Agents; use App\Ai\Agents\Concerns\ResolvesPlatformCopyBudget; +use App\Enums\Ai\GeneratorFormat; use App\Models\Workspace; use Illuminate\Contracts\JsonSchema\JsonSchema; use Laravel\Ai\Attributes\Temperature; @@ -27,7 +28,7 @@ class PostContentHumanizer implements Agent, HasStructuredOutput public function __construct( public Workspace $workspace, - public string $format = 'single', + public GeneratorFormat $format = GeneratorFormat::Single, public ?string $platformContext = null, public bool $applyBrandVoice = true, ) {} @@ -43,7 +44,7 @@ public function instructions(): string 'brand_name' => $this->workspace->name ?? '', 'brand_voice_traits' => $this->applyBrandVoice ? ($this->workspace->brand_voice_traits ?? []) : [], 'content_language' => $this->workspace->content_language, - 'format' => $this->format, + 'format' => $this->format->value, 'hard_max_chars' => $budget['hard_max_chars'], 'target_chars' => $budget['target_chars'], 'platform_label' => $budget['platform_label'], @@ -52,7 +53,7 @@ public function instructions(): string public function schema(JsonSchema $schema): array { - if ($this->format === 'carousel') { + if ($this->format->isCarousel()) { return [ 'caption' => $schema->string()->description('The humanized Instagram caption.')->required(), 'slides' => $schema->array() diff --git a/app/Ai/Agents/PostContentStreamer.php b/app/Ai/Agents/PostContentStreamer.php index 545c8af6..3bd800e1 100644 --- a/app/Ai/Agents/PostContentStreamer.php +++ b/app/Ai/Agents/PostContentStreamer.php @@ -4,6 +4,7 @@ namespace App\Ai\Agents; +use App\Enums\Ai\GeneratorFormat; use App\Models\Workspace; use Laravel\Ai\Attributes\Temperature; use Laravel\Ai\Contracts\Agent; @@ -36,7 +37,7 @@ public function instructions(): string 'brand_voice_traits' => $this->workspace->brand_voice_traits ?? [], 'content_language' => $this->workspace->content_language, 'current_content' => $this->currentContent, - 'format' => 'single', + 'format' => GeneratorFormat::Single->value, 'slide_count' => 1, ])->render(); } diff --git a/app/Ai/Templates/AiContentTemplate.php b/app/Ai/Templates/AiContentTemplate.php index d88dba23..d49fb10c 100644 --- a/app/Ai/Templates/AiContentTemplate.php +++ b/app/Ai/Templates/AiContentTemplate.php @@ -34,9 +34,6 @@ public function needsAccount(): bool; */ public function supportedFormats(): array; - /** The 'format' string passed to the generator agent (e.g. 'single'). */ - public function generatorFormat(): string; - /** The Blade view path for the generator prompt. */ public function promptView(TemplateContext $context): string; diff --git a/app/Ai/Templates/ImageCardTemplate.php b/app/Ai/Templates/ImageCardTemplate.php index 1c05d5d2..ce2102fd 100644 --- a/app/Ai/Templates/ImageCardTemplate.php +++ b/app/Ai/Templates/ImageCardTemplate.php @@ -49,11 +49,6 @@ public function supportedFormats(): array return []; } - public function generatorFormat(): string - { - return 'single'; - } - public function promptView(TemplateContext $context): string { return 'prompts.post_content.generator'; diff --git a/app/Ai/Templates/TweetCardImageTemplate.php b/app/Ai/Templates/TweetCardImageTemplate.php index ed15aa8c..59dfe7e2 100644 --- a/app/Ai/Templates/TweetCardImageTemplate.php +++ b/app/Ai/Templates/TweetCardImageTemplate.php @@ -47,11 +47,6 @@ public function supportedFormats(): array return []; } - public function generatorFormat(): string - { - return 'tweet_card'; - } - public function promptView(TemplateContext $context): string { return $context->isCarousel diff --git a/app/Ai/Templates/TweetCardTemplate.php b/app/Ai/Templates/TweetCardTemplate.php index 5f944daa..5811f24d 100644 --- a/app/Ai/Templates/TweetCardTemplate.php +++ b/app/Ai/Templates/TweetCardTemplate.php @@ -47,11 +47,6 @@ public function supportedFormats(): array return []; } - public function generatorFormat(): string - { - return 'tweet_card'; - } - public function promptView(TemplateContext $context): string { return $context->isCarousel diff --git a/app/Enums/Ai/ContentStyle.php b/app/Enums/Ai/ContentStyle.php index 038f7dba..37e081b0 100644 --- a/app/Enums/Ai/ContentStyle.php +++ b/app/Enums/Ai/ContentStyle.php @@ -31,6 +31,24 @@ public function needsAccount(): bool }; } + /** Whether this style's generated text is run through the humanizer pass. */ + public function humanizes(): bool + { + return match ($this) { + self::ImageCard => true, + self::TweetCard, self::TweetCardImage => false, + }; + } + + /** Whether this style renders the post as a tweet-style card. */ + public function isTweetCard(): bool + { + return match ($this) { + self::TweetCard, self::TweetCardImage => true, + self::ImageCard => false, + }; + } + /** Public path to the picker preview thumbnail. */ public function previewAsset(): string { diff --git a/app/Enums/Ai/GeneratorFormat.php b/app/Enums/Ai/GeneratorFormat.php new file mode 100644 index 00000000..3d4d5bea --- /dev/null +++ b/app/Enums/Ai/GeneratorFormat.php @@ -0,0 +1,16 @@ +find($this->template); $isCarousel = $this->format === ContentType::CAROUSEL_FORMAT; - $agentFormat = $isCarousel ? 'carousel' : 'single'; + $agentFormat = $isCarousel ? GeneratorFormat::Carousel : GeneratorFormat::Single; $slideCount = $isCarousel && $this->imageCount > 0 ? $this->imageCount : 1; $context = new TemplateContext( @@ -88,8 +90,7 @@ public function handle(): void $structured = $response->structured ?? []; - $humanizeFormat = $isCarousel ? 'carousel' : $agentFormat; - $structured = $this->humanize($workspace, $structured, $humanizeFormat, $this->template); + $structured = $this->humanize($workspace, $structured, $agentFormat, $style->style()); $generated = $style->assemble($structured, $context); $post = $this->createPostFromGenerated($workspace, $generated, $socialAccount); @@ -116,14 +117,14 @@ public function handle(): void * @param array $structured * @return array */ - private function humanize(Workspace $workspace, array $structured, string $format, string $styleKey = 'image_card'): array + private function humanize(Workspace $workspace, array $structured, GeneratorFormat $format, ContentStyle $style): array { - if ($styleKey === 'tweet_card' || $styleKey === 'tweet_card_image') { + if (! $style->humanizes()) { return $structured; } try { - $input = $format === 'carousel' + $input = $format->isCarousel() ? [ 'caption' => data_get($structured, 'caption', ''), 'slides' => array_map( @@ -151,7 +152,7 @@ private function humanize(Workspace $workspace, array $structured, string $forma provider: (string) config('ai.default'), model: (string) config('ai.default_text_model'), userId: $this->userId, - metadata: ['agent' => 'post_humanizer', 'format' => $format], + metadata: ['agent' => 'post_humanizer', 'format' => $format->value], ); } catch (\Throwable $e) { Log::warning('PostContentHumanizer failed, using generator output as-is', [ @@ -162,7 +163,7 @@ private function humanize(Workspace $workspace, array $structured, string $forma return $structured; } - if ($format === 'carousel') { + if ($format->isCarousel()) { $structured['caption'] = data_get($humanized, 'caption', $structured['caption'] ?? ''); $originalSlides = $structured['slides'] ?? []; $humanizedSlides = data_get($humanized, 'slides', []); diff --git a/tests/Feature/Ai/Templates/TemplateContractTest.php b/tests/Feature/Ai/Templates/TemplateContractTest.php index 15c4a372..61066b2a 100644 --- a/tests/Feature/Ai/Templates/TemplateContractTest.php +++ b/tests/Feature/Ai/Templates/TemplateContractTest.php @@ -16,7 +16,6 @@ expect($t->style())->toBe(ContentStyle::ImageCard) ->and($t->key())->toBe('image_card') ->and($t->needsAccount())->toBeFalse() - ->and($t->generatorFormat())->toBe('single') ->and($t->promptView($context))->toBe('prompts.post_content.generator') ->and($t->supportedFormats())->toBe([]); }); diff --git a/tests/Feature/Ai/Templates/TweetCardImageTemplateTest.php b/tests/Feature/Ai/Templates/TweetCardImageTemplateTest.php index 1dc7a47c..d014b501 100644 --- a/tests/Feature/Ai/Templates/TweetCardImageTemplateTest.php +++ b/tests/Feature/Ai/Templates/TweetCardImageTemplateTest.php @@ -17,7 +17,7 @@ expect($t->style())->toBe(ContentStyle::TweetCardImage) ->and($t->key())->toBe('tweet_card_image') ->and($t->needsAccount())->toBeTrue() - ->and($t->generatorFormat())->toBe('tweet_card') + ->and($t->style()->isTweetCard())->toBeTrue() ->and($t->promptView($singleContext))->toBe('prompts.post_content.tweet_card_image') ->and($t->promptView($carouselContext))->toBe('prompts.post_content.tweet_card_image_carousel'); }); diff --git a/tests/Feature/Ai/Templates/TweetCardTemplateTest.php b/tests/Feature/Ai/Templates/TweetCardTemplateTest.php index 35c1771a..ed732309 100644 --- a/tests/Feature/Ai/Templates/TweetCardTemplateTest.php +++ b/tests/Feature/Ai/Templates/TweetCardTemplateTest.php @@ -17,7 +17,7 @@ expect($t->style())->toBe(ContentStyle::TweetCard) ->and($t->key())->toBe('tweet_card') ->and($t->needsAccount())->toBeTrue() - ->and($t->generatorFormat())->toBe('tweet_card') + ->and($t->style()->isTweetCard())->toBeTrue() ->and($t->promptView($singleContext))->toBe('prompts.post_content.tweet_card') ->and($t->promptView($carouselContext))->toBe('prompts.post_content.tweet_card_carousel'); }); diff --git a/tests/Feature/Automation/GenerateNodeValidationTest.php b/tests/Feature/Automation/GenerateNodeValidationTest.php index 0f3eaebd..cce77d0d 100644 --- a/tests/Feature/Automation/GenerateNodeValidationTest.php +++ b/tests/Feature/Automation/GenerateNodeValidationTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); use App\Actions\Automation\Node\RunGenerateNode; +use App\Enums\Ai\GeneratorFormat; use App\Enums\UserWorkspace\Role; use App\Models\Automation; use App\Models\User; @@ -128,20 +129,20 @@ // tiktok_photo allows 35, but AI generation is capped at 10. expect($node->deriveFormat([['content_type' => 'tiktok_photo']], ['target_slide_count' => 15])) - ->toBe(['format' => 'carousel', 'slide_count' => 10]); + ->toBe(['format' => GeneratorFormat::Carousel, 'slide_count' => 10]); // pinterest_carousel allows only 5. expect($node->deriveFormat([['content_type' => 'pinterest_carousel']], ['target_slide_count' => 8])) - ->toBe(['format' => 'carousel', 'slide_count' => 5]); + ->toBe(['format' => GeneratorFormat::Carousel, 'slide_count' => 5]); }); it('recognises facebook_post as multi-image capable (rules-derived, not a hardcoded list)', function () { $node = app(RunGenerateNode::class); expect($node->deriveFormat([['content_type' => 'facebook_post']], ['target_slide_count' => 6])) - ->toBe(['format' => 'carousel', 'slide_count' => 6]); + ->toBe(['format' => GeneratorFormat::Carousel, 'slide_count' => 6]); // A single-image (video) format never becomes a carousel. expect($node->deriveFormat([['content_type' => 'tiktok_video']], ['target_slide_count' => 5])) - ->toBe(['format' => 'single', 'slide_count' => 1]); + ->toBe(['format' => GeneratorFormat::Single, 'slide_count' => 1]); }); diff --git a/tests/Feature/Automation/Node/GenerateNodeTest.php b/tests/Feature/Automation/Node/GenerateNodeTest.php index 325b86b3..93e37e44 100644 --- a/tests/Feature/Automation/Node/GenerateNodeTest.php +++ b/tests/Feature/Automation/Node/GenerateNodeTest.php @@ -6,6 +6,7 @@ use App\Ai\Agents\PostContentGenerator; use App\Ai\Agents\PostContentHumanizer; use App\Enums\Ai\ContentStyle; +use App\Enums\Ai\GeneratorFormat; use App\Enums\Post\Status as PostStatus; use App\Enums\PostPlatform\ContentType; use App\Models\Automation; @@ -180,7 +181,7 @@ 'target_slide_count' => 2, ]); - PostContentGenerator::assertPrompted(fn ($prompt) => $prompt->agent->format === 'carousel' + PostContentGenerator::assertPrompted(fn ($prompt) => $prompt->agent->format === GeneratorFormat::Carousel && $prompt->agent->platformContext === ContentType::XPost->value); }); @@ -260,7 +261,7 @@ ['format' => $format, 'slide_count' => $slideCount] = $action->deriveFormat($accountsConfig, ['target_slide_count' => 5]); - expect($format)->toBe('carousel'); + expect($format)->toBe(GeneratorFormat::Carousel); expect($slideCount)->toBe(5); }); @@ -276,7 +277,7 @@ ['format' => $format, 'slide_count' => $slideCount] = $action->deriveFormat($accountsConfig, ['target_slide_count' => 1]); - expect($format)->toBe('single'); + expect($format)->toBe(GeneratorFormat::Single); expect($slideCount)->toBe(1); }); @@ -290,7 +291,7 @@ ['format' => $format, 'slide_count' => $slideCount] = $action->deriveFormat($accountsConfig, ['target_slide_count' => 5]); - expect($format)->toBe('single'); + expect($format)->toBe(GeneratorFormat::Single); expect($slideCount)->toBe(1); }); diff --git a/tests/Unit/Ai/Agents/PostContentGeneratorTest.php b/tests/Unit/Ai/Agents/PostContentGeneratorTest.php index fd14118c..b0047685 100644 --- a/tests/Unit/Ai/Agents/PostContentGeneratorTest.php +++ b/tests/Unit/Ai/Agents/PostContentGeneratorTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); use App\Ai\Agents\PostContentGenerator; +use App\Enums\Ai\GeneratorFormat; use App\Models\Workspace; use Illuminate\JsonSchema\JsonSchemaTypeFactory; @@ -60,7 +61,7 @@ test('single format schema returns content and image_keywords', function () { $workspace = Workspace::factory()->make(); - $agent = new PostContentGenerator(workspace: $workspace, format: 'single'); + $agent = new PostContentGenerator(workspace: $workspace, format: GeneratorFormat::Single); $schemaFactory = new JsonSchemaTypeFactory; $schema = $agent->schema($schemaFactory); @@ -73,7 +74,7 @@ test('carousel format schema returns caption and slides', function () { $workspace = Workspace::factory()->make(); - $agent = new PostContentGenerator(workspace: $workspace, format: 'carousel', slideCount: 5); + $agent = new PostContentGenerator(workspace: $workspace, format: GeneratorFormat::Carousel, slideCount: 5); $schemaFactory = new JsonSchemaTypeFactory; $schema = $agent->schema($schemaFactory); @@ -85,7 +86,7 @@ test('carousel slide schema includes role enum', function () { $workspace = Workspace::factory()->make(); - $agent = new PostContentGenerator(workspace: $workspace, format: 'carousel', slideCount: 4); + $agent = new PostContentGenerator(workspace: $workspace, format: GeneratorFormat::Carousel, slideCount: 4); $schemaFactory = new JsonSchemaTypeFactory; $schema = $agent->schema($schemaFactory); @@ -100,7 +101,7 @@ test('carousel instructions mention slide count', function () { $workspace = Workspace::factory()->make(); - $agent = new PostContentGenerator(workspace: $workspace, format: 'carousel', slideCount: 5); + $agent = new PostContentGenerator(workspace: $workspace, format: GeneratorFormat::Carousel, slideCount: 5); $instructions = $agent->instructions(); @@ -110,7 +111,7 @@ test('carousel instructions describe the roteiro arc with hook, development, proof and cta', function () { $workspace = Workspace::factory()->make(); - $agent = new PostContentGenerator(workspace: $workspace, format: 'carousel', slideCount: 4); + $agent = new PostContentGenerator(workspace: $workspace, format: GeneratorFormat::Carousel, slideCount: 4); $instructions = $agent->instructions(); @@ -123,7 +124,7 @@ test('single format instructions do not include carousel roteiro section', function () { $workspace = Workspace::factory()->make(); - $agent = new PostContentGenerator(workspace: $workspace, format: 'single'); + $agent = new PostContentGenerator(workspace: $workspace, format: GeneratorFormat::Single); $instructions = $agent->instructions(); @@ -133,7 +134,7 @@ test('single instructions mention image_keywords', function () { $workspace = Workspace::factory()->make(); - $agent = new PostContentGenerator(workspace: $workspace, format: 'single'); + $agent = new PostContentGenerator(workspace: $workspace, format: GeneratorFormat::Single); $instructions = $agent->instructions(); @@ -143,7 +144,7 @@ test('instructions include examples when platform context provided and templates exist', function () { $agent = new PostContentGenerator( workspace: Workspace::factory()->make(), - format: 'carousel', + format: GeneratorFormat::Carousel, slideCount: 3, platformContext: 'instagram_carousel', ); @@ -161,7 +162,7 @@ test('instructions do not include examples section when platform context is null', function () { $agent = new PostContentGenerator( workspace: Workspace::factory()->make(), - format: 'single', + format: GeneratorFormat::Single, ); expect($agent->instructions())->not->toContain('curated library'); @@ -170,7 +171,7 @@ test('instructions inject the platform character cap from the shared budget', function () { $agent = new PostContentGenerator( workspace: Workspace::factory()->make(), - format: 'single', + format: GeneratorFormat::Single, platformContext: 'x_post', ); diff --git a/tests/Unit/Enums/ContentStyleTest.php b/tests/Unit/Enums/ContentStyleTest.php index 77f209a2..96665ceb 100644 --- a/tests/Unit/Enums/ContentStyleTest.php +++ b/tests/Unit/Enums/ContentStyleTest.php @@ -21,6 +21,12 @@ expect(ContentStyle::TweetCardImage->needsAccount())->toBeTrue(); }); +test('humanizes is true only for ImageCard', function () { + expect(ContentStyle::ImageCard->humanizes())->toBeTrue(); + expect(ContentStyle::TweetCard->humanizes())->toBeFalse(); + expect(ContentStyle::TweetCardImage->humanizes())->toBeFalse(); +}); + test('previewAsset returns the correct public path for each case', function () { expect(ContentStyle::ImageCard->previewAsset())->toBe('/images/ai-templates/image-card.png'); expect(ContentStyle::TweetCard->previewAsset())->toBe('/images/ai-templates/tweet-card.png');