summaryrefslogtreecommitdiff
path: root/internal/comic/comic_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-22 08:42:56 +0300
committerPaul Buetow <paul@buetow.org>2026-04-22 08:42:56 +0300
commitf9baad3a11e48a6d438d3e614e1ca1ee45c1547d (patch)
tree72db85faa6307b7b586d0ed982da977ad12c6e46 /internal/comic/comic_test.go
parent15639ba3508ab92599bd1884ba07139c73b2dac0 (diff)
Fix manual prompt mode flags
Diffstat (limited to 'internal/comic/comic_test.go')
-rw-r--r--internal/comic/comic_test.go100
1 files changed, 100 insertions, 0 deletions
diff --git a/internal/comic/comic_test.go b/internal/comic/comic_test.go
index 29f2a31..866df64 100644
--- a/internal/comic/comic_test.go
+++ b/internal/comic/comic_test.go
@@ -3,6 +3,7 @@ package comic
import (
"context"
"errors"
+ "fmt"
"os"
"path/filepath"
"strings"
@@ -485,6 +486,52 @@ func TestRunnerRunPromptRejectsEmptyPrompt(t *testing.T) {
}
}
+func TestRunnerRunPromptAppliesStyleThemeAndUltraContext(t *testing.T) {
+ t.Parallel()
+
+ originalLeakValidation := validateImagePromptLeakageFn
+ validateImagePromptLeakageFn = func(context.Context, string, string, string) error { return nil }
+ t.Cleanup(func() {
+ validateImagePromptLeakageFn = originalLeakValidation
+ })
+
+ provider := &capturingImageProvider{t: t}
+ renderer := &recordingPromptRenderer{}
+ runner := NewRunner(&RunnerConfig{
+ ImageProvider: provider,
+ Prompts: renderer,
+ OutputDir: t.TempDir(),
+ Slug: "manual-robot",
+ Style: "noir",
+ Theme: "mystery",
+ UltraRealistic: boolPtr(true),
+ PageMaxRetries: 1,
+ PageRetryBase: time.Second,
+ })
+
+ if err := runner.RunPrompt(context.Background(), "a robot reading a newspaper"); err != nil {
+ t.Fatalf("RunPrompt() error = %v", err)
+ }
+ if provider.lastPrompt == "" {
+ t.Fatal("image prompt was not captured")
+ }
+ if !strings.Contains(provider.lastPrompt, "Create a single image based on this prompt") {
+ t.Fatalf("prompt = %q, want manual prompt template", provider.lastPrompt)
+ }
+ if !strings.Contains(provider.lastPrompt, "a robot reading a newspaper") {
+ t.Fatalf("prompt = %q, want raw user prompt", provider.lastPrompt)
+ }
+ if !strings.Contains(provider.lastPrompt, "Visual style: noir.") {
+ t.Fatalf("prompt = %q, want style context", provider.lastPrompt)
+ }
+ if !strings.Contains(provider.lastPrompt, "Theme context: mystery.") {
+ t.Fatalf("prompt = %q, want theme context", provider.lastPrompt)
+ }
+ if !strings.Contains(provider.lastPrompt, "FINAL STYLE LOCK — PHOTOREALISM") {
+ t.Fatalf("prompt = %q, want ultra-realistic context", provider.lastPrompt)
+ }
+}
+
func TestNewRunnerUsesRealisticWeightWhenUltraRealisticUnset(t *testing.T) {
t.Parallel()
@@ -847,6 +894,12 @@ func (fakePromptRenderer) RenderPrompt(name string, data any) (string, error) {
return "system prompt", nil
case storyPromptTemplate, storyFullPromptTemplate:
return "prompt", nil
+ case renderingRequirementPrompt:
+ return "ULTRA-REALISTIC MODE for this image:\n • The image must look like a real photograph or a frame from a high-budget live-action film.\n • Skin, hair, fabric, metal, and environments must look natural and real, with no drawn or painterly effect.\n • Avoid cartoon, anime, manga, comic-line-art, or obviously illustrated rendering.\n • Text elements, if required by the composition, should look like part of a photographed physical cover, sign, or poster.\n • For gallery images, the whole image must be entirely photographic.\n", nil
+ case renderingRequirementEndPrompt:
+ return "FINAL STYLE LOCK — PHOTOREALISM: the entire image must look camera-captured. If anything looks drawn or painted, the result is wrong. Do not drift toward comic art between panels or in gallery images.", nil
+ case manualPromptTemplate:
+ return renderManualPromptForTest(data), nil
case coverPromptTemplate, storyPagePromptTemplate, galleryPagePromptTemplate, backCoverPromptTemplate:
return "image prompt", nil
case blurbSystemTemplate, introSystemTemplate, conclusionSystemTemplate:
@@ -880,6 +933,12 @@ func (r *recordingPromptRenderer) RenderPrompt(name string, data any) (string, e
return "system prompt", nil
case storyPromptTemplate, storyFullPromptTemplate:
return "prompt", nil
+ case renderingRequirementPrompt:
+ return "ULTRA-REALISTIC MODE for this image:\n • The image must look like a real photograph or a frame from a high-budget live-action film.\n • Skin, hair, fabric, metal, and environments must look natural and real, with no drawn or painterly effect.\n • Avoid cartoon, anime, manga, comic-line-art, or obviously illustrated rendering.\n • Text elements, if required by the composition, should look like part of a photographed physical cover, sign, or poster.\n • For gallery images, the whole image must be entirely photographic.\n", nil
+ case renderingRequirementEndPrompt:
+ return "FINAL STYLE LOCK — PHOTOREALISM: the entire image must look camera-captured. If anything looks drawn or painted, the result is wrong. Do not drift toward comic art between panels or in gallery images.", nil
+ case manualPromptTemplate:
+ return renderManualPromptForTest(data), nil
case coverPromptTemplate, storyPagePromptTemplate, galleryPagePromptTemplate, backCoverPromptTemplate:
return "image prompt", nil
case blurbSystemTemplate, introSystemTemplate, conclusionSystemTemplate:
@@ -898,6 +957,32 @@ func isImagePromptTemplate(name string) bool {
}
}
+func renderManualPromptForTest(data any) string {
+ m, _ := data.(map[string]any)
+ if m == nil {
+ return "Create a single image based on this prompt:"
+ }
+ var sb strings.Builder
+ sb.WriteString("Create a single image based on this prompt:\n\n")
+ sb.WriteString(fmt.Sprint(m["Prompt"]))
+ sb.WriteString("\n\n")
+ if style, _ := m["Style"].(string); strings.TrimSpace(style) != "" {
+ fmt.Fprintf(&sb, "Visual style: %s.\n", style)
+ }
+ if theme, _ := m["Theme"].(string); strings.TrimSpace(theme) != "" {
+ fmt.Fprintf(&sb, "Theme context: %s.\n", theme)
+ }
+ if requirement, _ := m["RenderingRequirement"].(string); strings.TrimSpace(requirement) != "" {
+ sb.WriteString(requirement)
+ sb.WriteString("\n")
+ }
+ sb.WriteString("No text, captions, logos, borders, panels, or UI elements.")
+ if requirementEnd, _ := m["RenderingRequirementEnd"].(string); strings.TrimSpace(requirementEnd) != "" {
+ sb.WriteString(requirementEnd)
+ }
+ return sb.String()
+}
+
type fakeTextProvider struct{ text string }
func (f fakeTextProvider) Name() string { return "fake-text" }
@@ -936,6 +1021,21 @@ func (f fakeImageProvider) GenerateImage(_ context.Context, _ string, outputFile
return nil
}
+type capturingImageProvider struct {
+ t *testing.T
+ lastPrompt string
+}
+
+func (p *capturingImageProvider) Name() string { return "capturing-image" }
+func (p *capturingImageProvider) IsAvailable() error { return nil }
+func (p *capturingImageProvider) GenerateImage(_ context.Context, prompt, outputFile string) error {
+ p.lastPrompt = prompt
+ if err := os.WriteFile(outputFile, []byte("png"), 0o644); err != nil {
+ p.t.Fatal(err)
+ }
+ return nil
+}
+
type refTrackingImageProvider struct {
t *testing.T
referenceCounts []int