package llm import ( "strings" "testing" ) func TestLangForFallsBackToDefault(t *testing.T) { if got := LangFor("zh"); got.Code != "zh" { t.Fatalf("LangFor(zh) = %+v", got) } if got := LangFor("pt-PT"); got.Code != "pt-PT" { t.Fatalf("LangFor(pt-PT) = %+v", got) } // A blank column, a stray value, and stray whitespace all resolve rather // than erroring — a prompt is the wrong place to discover a config problem. for _, in := range []string{"", " ", "klingon", "ZH"} { if got := LangFor(in); got.Code != DefaultLang.Code { t.Fatalf("LangFor(%q) = %q, want the default %q", in, got.Code, DefaultLang.Code) } } if got := LangFor(" zh "); got.Code != "zh" { t.Fatalf("LangFor with padding = %+v", got) } } // The three prompts that name the writer's language must actually name *hers*. // Before Phase 19 all three said "Simplified Chinese" outright, which is the // bug this guards: a pt-PT writer asking "porquê" would have been answered in // Mandarin. func TestPromptsNameTheWritersLanguage(t *testing.T) { pt := LangFor("pt-PT") collocation := CollocationMessages("The rain was strong.", "casual", EnglishTarget(pt))[0].Content if !strings.Contains(collocation, "European Portuguese") { t.Fatalf("collocation prompt doesn't ask for a pt-PT gloss:\n%s", collocation) } if strings.Contains(collocation, "Simplified Chinese") { t.Fatalf("collocation prompt still hardcodes Chinese:\n%s", collocation) } // The tone steering must survive alongside the language — they share one // format string, and getting the verbs in the wrong order silently drops one. if !strings.Contains(collocation, "relaxed, friendly, and conversational") { t.Fatalf("collocation prompt lost its tone guidance:\n%s", collocation) } translate := TranslateMessages("Try a shorter sentence here.", pt)[0].Content if !strings.Contains(translate, "European Portuguese") || strings.Contains(translate, "Chinese") { t.Fatalf("translate prompt targets the wrong language:\n%s", translate) } ask := AskPetalSystemPrompt("origin", "replacement", "grammar", "explanation", "paragraph", pt) if !strings.Contains(ask, "European Portuguese") || strings.Contains(ask, "Mandarin") { t.Fatalf("ask-petal prompt targets the wrong language:\n%s", ask) } if !strings.Contains(ask, "porquê") { t.Fatalf("ask-petal prompt doesn't recognise her word for \"why\":\n%s", ask) } // The suggestion context is positional in that template; a mis-numbered // verb would quietly blank one of these fields. for _, want := range []string{"origin", "replacement", "grammar", "explanation", "paragraph"} { if !strings.Contains(ask, want) { t.Fatalf("ask-petal prompt dropped %q:\n%s", want, ask) } } if strings.Contains(ask, "%!") { t.Fatalf("ask-petal prompt has a formatting error:\n%s", ask) } } // The zh pair is in daily use and must be untouched by the extraction: its // prompts should read exactly as they did when they were hardcoded. func TestDefaultPairStillReadsAsBefore(t *testing.T) { zh := LangFor("zh") if got := CollocationMessages("x", "", EnglishTarget(zh))[0].Content; !strings.Contains(got, "Simplified Chinese (Mandarin) gloss in parentheses") { t.Fatalf("zh collocation gloss changed:\n%s", got) } if got := TranslateMessages("x", zh)[0].Content; !strings.Contains(got, "natural, friendly Simplified Chinese (Mandarin)") { t.Fatalf("zh translate target changed:\n%s", got) } if got := AskPetalSystemPrompt("a", "b", "c", "d", "e", zh); !strings.Contains(got, "为什么") { t.Fatalf("zh ask-petal lost its Mandarin \"why\":\n%s", got) } } // UX item 6: the Ask Petal answer is bilingual, pair language first, halves // separated by one blank line. That separator is not a stylistic preference — // AskPetal.tsx splits on it to render the two halves the way the companion // renders its two lines — so the instruction has to survive prompt edits. // // The direction the writer is learning in is deliberately not encoded: the pair // is (English + X), and an English speaker learning French needs the same two // halves a Mandarin speaker learning English does. The prompt asks for both and // lets the reader choose, so there is nothing here that names one half the // answer and the other a courtesy. func TestAskPetalAnswersInBothLanguages(t *testing.T) { for _, code := range []string{"zh", "pt-PT", "fr", "es"} { lang := LangFor(code) ask := AskPetalSystemPrompt("a", "b", "grammar", "d", "e", lang) if !strings.Contains(ask, "BOTH languages") { t.Fatalf("%s: ask-petal no longer asks for both languages:\n%s", code, ask) } if !strings.Contains(ask, "single blank line") { t.Fatalf("%s: ask-petal lost the blank-line separator the client splits on:\n%s", code, ask) } // Order matters to the rendering: the pair language is the prominent // half, English the muted one beneath it. if !strings.Contains(ask, "first the whole answer in "+lang.Name) { t.Fatalf("%s: ask-petal doesn't put %s first:\n%s", code, lang.Name, ask) } // The instruction it replaced. Left in place it directly contradicts the // new one, and a model given both will pick one at random. if strings.Contains(ask, "Never mix languages") { t.Fatalf("%s: ask-petal still forbids the bilingual reply it now asks for:\n%s", code, ask) } if strings.Contains(ask, "%!") { t.Fatalf("%s: ask-petal prompt has a formatting error:\n%s", code, ask) } } }