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") } }