package web
import (
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
)
// THE GUIDE, RENDERED (v0.315.0, Scenario F). The schema and the overlay are pinned in the stacks
// package (guide_test.go); these are the four rules the PAGE is responsible for:
//
// 1. a guide whose author named no version is not shown — and the page says one is still to come
// 2. the household's own address replaces DOMAIN in every guide string
// 3. English shows English where it has it and Hungarian where it does not, FIELD BY FIELD
// 4. a step containing markup is shown as the characters the author typed
//
// Rendered through the real template. A guide that parses and never renders is the seam-built-but-
// never-wired class, which this project has shipped four times.
// guideAppInfo is a two-section, two-FAQ guide with an address in it.
func guideAppInfo(checkedVersion string) stacks.AppInfo {
return stacks.AppInfo{
Tagline: "Teszt alkalmazas",
Guide: stacks.AppGuide{
CheckedVersion: checkedVersion,
Sections: []stacks.GuideSection{
{Title: "Elso bekapcsolas", Intro: "Nyisd meg a wiki.DOMAIN oldalt.",
Steps: []string{"Kattints a Fiok menure", "Add meg a DOMAIN cimet"}},
{Title: "Megosztas", Steps: []string{"Valaszd ki a mappat"}},
},
FAQ: []stacks.GuideFAQ{
{Q: "Hol vannak a fajlok?", A: "A megadott mappaban."},
{Q: "Lehet tobb felhasznalo?", A: "Igen."},
},
},
}
}
func renderGuideTab(t *testing.T, ai stacks.AppInfo) string {
t.Helper()
st := stacks.Stack{Name: "testapp", Deployed: true, State: stacks.StateRunning}
st.Meta = stacks.Metadata{DisplayName: "TestApp", Slug: "testapp", AppInfo: ai}
return renderBackupPage(t, "app_guide", map[string]interface{}{
"Page": "stacks", "Title": "TestApp", "Domain": "example.hu", "CSRFToken": "tok",
"Stack": &st, "Meta": st.Meta, "AppInfo": ai, "HasAppInfo": true,
"Deployed": true, "AppOperational": true,
})
}
// TestGuide_RenderRules — the render gate, the address, and the escaping.
//
// RED-PROOF (REPORT.md): change the template's gate from `.AppInfo.Guide.Rendered` to
// `.AppInfo.Guide.Sections` → the empty-checked_version case renders the guide and fails. Reverted.
func TestGuide_RenderRules(t *testing.T) {
t.Run("checked version renders", func(t *testing.T) {
// R-498 over the guide: the catalog says wiki.DOMAIN, the household gets its own address.
h := renderGuideTab(t, guideAppInfo("1.2.3").WithAddress("jegyzet", "example.hu"))
if !strings.Contains(h, `data-guide="1"`) {
t.Fatal("a checked guide must render")
}
if !strings.Contains(h, `data-guide-checked="1.2.3"`) {
t.Error("the checked version must be on the page as the gate's evidence")
}
if n := strings.Count(h, `data-guide-section="1"`); n != 2 {
t.Errorf("%d guide sections rendered, want 2", n)
}
if !strings.Contains(h, `data-guide-faq="1"`) {
t.Error("the FAQ block must render")
}
for _, want := range []string{"Elso bekapcsolas", "Megosztas", "Kattints a Fiok menure",
"Valaszd ki a mappat", "Hol vannak a fajlok?", "A megadott mappaban."} {
if !strings.Contains(h, want) {
t.Errorf("the guide is missing %q", want)
}
}
// The address, in the intro AND in a step.
if !strings.Contains(h, "Nyisd meg a jegyzet.example.hu oldalt.") {
t.Error("DOMAIN was not replaced in the section intro")
}
if !strings.Contains(h, "Add meg a example.hu cimet") {
t.Error("DOMAIN was not replaced in a step")
}
if strings.Contains(h, "DOMAIN") {
t.Error("the literal DOMAIN placeholder reached the page")
}
if strings.Contains(h, `data-guide-missing="1"`) {
t.Error("the „no guide yet” line must not show beside a rendered guide")
}
})
t.Run("empty checked version does not render", func(t *testing.T) {
h := renderGuideTab(t, guideAppInfo(""))
if strings.Contains(h, `data-guide="1"`) || strings.Contains(h, `data-guide-section="1"`) {
t.Error("a guide whose author named NO version must not render — nobody vouched for it")
}
if strings.Contains(h, "Elso bekapcsolas") {
t.Error("an unchecked guide's text reached the page")
}
if !strings.Contains(h, `data-guide-missing="1"`) {
t.Error("the page must say a guide is still to come")
}
})
t.Run("absent guide says so", func(t *testing.T) {
h := renderGuideTab(t, stacks.AppInfo{Tagline: "Teszt"})
if !strings.Contains(h, `data-guide-missing="1"`) {
t.Error("an app with no guide block must say so")
}
if strings.Contains(h, `data-guide="1"`) {
t.Error("an absent guide rendered a guide block")
}
})
t.Run("markup in a step is shown as text", func(t *testing.T) {
ai := guideAppInfo("1.2.3")
ai.Guide.Sections[0].Steps = []string{``}
ai.Guide.FAQ[0].A = `
`
h := renderGuideTab(t, ai)
if strings.Contains(h, "") {
t.Error("a catalog step was rendered as HTML — catalog copy is DATA, never markup")
}
if strings.Contains(h, "
") {
t.Error("a FAQ answer was rendered as HTML")
}
if !strings.Contains(h, "<script>") {
t.Error("the step's characters must appear, escaped, as the author typed them")
}
})
}
// TestGuide_EnglishFallsBackFieldByField — the half-translated state the catalog is allowed to ship,
// through the real localisation path and the real template: English FAQ, Hungarian sections.
func TestGuide_EnglishFallsBackFieldByField(t *testing.T) {
en := "en"
meta := stacks.Metadata{DisplayName: "TestApp", Slug: "testapp", AppInfo: guideAppInfo("1.2.3")}
meta.I18n = map[string]stacks.MetadataOverlay{
en: {AppInfo: &stacks.AppInfoOverlay{Guide: &stacks.AppGuideOverlay{
// Section 1: the title translated, the intro left BLANK (= absent, operator ruling 3).
Sections: []stacks.GuideSectionOverlay{{Title: sptr("First start"), Intro: sptr("")}},
FAQ: []stacks.GuideFAQOverlay{
{Q: sptr("Where are the files?"), A: sptr("In the folder you chose.")},
{Q: sptr("Can several people use it?"), A: sptr("Yes.")},
},
}}},
}
h := renderGuideTab(t, meta.For(en).AppInfo.WithAddress("jegyzet", "example.hu"))
if !strings.Contains(h, "First start") {
t.Error("the translated section title did not reach the English page")
}
if !strings.Contains(h, "Where are the files?") || !strings.Contains(h, "In the folder you chose.") {
t.Error("the English FAQ did not reach the English page")
}
// A BLANK English intro is absent, so the Hungarian one stays — with the address filled.
if !strings.Contains(h, "Nyisd meg a jegyzet.example.hu oldalt.") {
t.Error("a blank English intro must fall back to the Hungarian one, not render empty")
}
// Section 2 had no English entry at all: wholly Hungarian.
if !strings.Contains(h, "Megosztas") || !strings.Contains(h, "Valaszd ki a mappat") {
t.Error("an untranslated section must render in Hungarian")
}
// And the gate is not copy: it survived the overlay.
if !strings.Contains(h, `data-guide-checked="1.2.3"`) {
t.Error("the checked version did not survive the English overlay")
}
// The Hungarian page is unchanged by the presence of the block.
hu := renderGuideTab(t, meta.For("hu").AppInfo.WithAddress("jegyzet", "example.hu"))
if strings.Contains(hu, "First start") || strings.Contains(hu, "Where are the files?") {
t.Error("English copy reached the Hungarian page")
}
if !strings.Contains(hu, "Elso bekapcsolas") {
t.Error("the Hungarian section title is gone")
}
}