package gonexapp_test import ( "context" "encoding/base64" "encoding/json" "html" "net/http" "net/http/httptest" "testing" "testing/fstest" "time" "github.com/chromedp/cdproto/network" "github.com/chromedp/chromedp" "gitea.neitzel.de/konrad/go-nc-exapp" ) func TestExAppPhrasesOnThePage(t *testing.T) { phrases := mergedPhrases(t) tests := []struct { name string ncLang string want phraseSeen }{ { name: "german", ncLang: "de", want: phraseSeen{ lang: "de", heading: "Spätere Überschrift", placeholder: "Platzhalter", title: "Titel", aria: "Beschriftung", hole: "example.com fehlt", openHole: "{zone} fehlt", fallback: "English only", unknown: "not.a.phrase", htmlHole: "x fehlt", scripted: "Skript", chromeID: "dialog.cancel", err: "x fehlt", nav: "Domänen", fold: "Domänen einklappen", missing: "Dieser Eintrag fehlt", }, }, { name: "english", ncLang: "en", want: phraseSeen{ lang: "en", heading: "Later heading", placeholder: "Placeholder", title: "Title", aria: "Aria", hole: "Zone example.com is missing", openHole: "Zone {zone} is missing", fallback: "English only", unknown: "not.a.phrase", htmlHole: "Zone x is missing", scripted: "Scripted", chromeID: "dialog.cancel", err: "Missing x", nav: "Domains", fold: "Fold Domains", missing: "that item is not in this Visit", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { nc := fakeLanguage(http.StatusOK, tt.ncLang) t.Cleanup(nc.Close) page := phrasePage(nc.URL, phrases) t.Cleanup(page.Close) got := readPhrases(t, page.URL+"/?item=missing", network.Headers{ "Accept-Language": browserLanguage, "AUTHORIZATION-APP-API": base64.StdEncoding.EncodeToString([]byte("alice:secret")), }) if got != tt.want { t.Fatalf("got %+v, want %+v", got, tt.want) } }) } } type phraseSeen struct { lang string heading string placeholder string title string aria string hole string openHole string fallback string unknown string htmlHole string scripted string chromeID string err string nav string fold string missing string } func mergedPhrases(t *testing.T) gonexapp.Phrases { t.Helper() base, err := gonexapp.PhrasesFromFS(fstest.MapFS{ "i18n/en.json": {Data: []byte(phraseEN)}, "i18n/de.json": {Data: []byte(phraseDE)}, }) if err != nil { t.Fatal(err) } mod, err := gonexapp.PhrasesFromFS(fstest.MapFS{ "i18n/en.json": {Data: []byte(`{"page.heading":"Later heading"}`)}, "i18n/de.json": {Data: []byte(`{"page.heading":"Spätere Überschrift"}`)}, }) if err != nil { t.Fatal(err) } return gonexapp.Merge(base, mod) } const phraseEN = `{ "page.heading": "Heading", "page.placeholder": "Placeholder", "page.title": "Title", "page.aria": "Aria", "page.hole": "Zone {zone} is missing", "page.english-only": "English only", "page.scripted": "Scripted", "page.error": "Missing {name}", "nav.domains": "Domains", "nav.missing": "that item is not in this Visit" }` const phraseDE = `{ "page.placeholder": "Platzhalter", "page.title": "Titel", "page.aria": "Beschriftung", "page.hole": "{zone} fehlt", "page.scripted": "Skript", "page.error": "{name} fehlt", "nav.domains": "Domänen", "nav.missing": "Dieser Eintrag fehlt" }` func phrasePage(ncBase string, phrases gonexapp.Phrases) *httptest.Server { nav := sampleNavigation() nav.Cred = gonexapp.Credentials{ BaseURL: ncBase, AppID: "exapp", AppVersion: "1.0.0", AAVersion: "1.0.0", AppSecret: "secret", } nav.Phrases = phrases nav.MissingMessage = "that item is not in this Visit" nav.MissingPhrase = "nav.missing" nav.Items = func(*http.Request) []gonexapp.Item { return []gonexapp.Item{{ ID: "domains", Label: "Domains", Phrase: "nav.domains", Children: []gonexapp.Item{{ID: "example.com", Label: "example.com"}}, }} } nav.Page = func(r *http.Request, id string) (string, bool) { if id != "domains" { return "", false } msg := phrases.Sentence(nav.Cred.UILanguage(r), "page.error", map[string]string{"name": "x"}) return `
Zone {zone} is missing
Zone {zone} is missing
English only
English source
Zone {zone} is missing
` + html.EscapeString(msg) + `
`, true } return httptest.NewServer(nav.Handler()) } func readPhrases(t *testing.T, pageURL string, headers network.Headers) phraseSeen { t.Helper() ctx, cancel := chromedp.NewContext(allocCtx) t.Cleanup(cancel) ctx, cancel = context.WithTimeout(ctx, 25*time.Second) t.Cleanup(cancel) var raw string if err := chromedp.Run(ctx, network.Enable(), network.SetExtraHTTPHeaders(headers), chromedp.Navigate(pageURL), chromedp.Evaluate(phraseProbeJS, &raw), ); err != nil { t.Fatal(err) } var probe struct { Lang string `json:"lang"` Heading string `json:"heading"` Placeholder string `json:"placeholder"` Title string `json:"title"` Aria string `json:"aria"` Hole string `json:"hole"` OpenHole string `json:"openHole"` Fallback string `json:"fallback"` Unknown string `json:"unknown"` HTMLHole string `json:"htmlHole"` HTMLHasB bool `json:"htmlHasB"` Scripted string `json:"scripted"` ChromeID string `json:"chromeId"` Err string `json:"err"` ErrHasB bool `json:"errHasB"` Nav string `json:"nav"` Fold string `json:"fold"` Missing string `json:"missing"` } if err := json.Unmarshal([]byte(raw), &probe); err != nil { t.Fatalf("probe %s: %v", raw, err) } if probe.HTMLHasB || probe.ErrHasB { t.Fatalf("hole value was parsed as HTML: %+v", probe) } return phraseSeen{ lang: probe.Lang, heading: probe.Heading, placeholder: probe.Placeholder, title: probe.Title, aria: probe.Aria, hole: probe.Hole, openHole: probe.OpenHole, fallback: probe.Fallback, unknown: probe.Unknown, htmlHole: probe.HTMLHole, scripted: probe.Scripted, chromeID: probe.ChromeID, err: probe.Err, nav: probe.Nav, fold: probe.Fold, missing: probe.Missing, } } const phraseProbeJS = `(() => { const field = document.getElementById("field"); const htmlHole = document.getElementById("htmlhole"); const err = document.getElementById("err"); const name = document.querySelector('[data-id="domains"] > .app-navigation-entry .app-navigation-entry__name'); const fold = document.querySelector('[data-id="domains"] button.fold'); const missing = document.getElementById("nav-missing"); return JSON.stringify({ lang: document.documentElement.lang, heading: document.getElementById("heading").textContent, placeholder: field.placeholder, title: field.title, aria: field.getAttribute("aria-label"), hole: document.getElementById("hole").textContent, openHole: document.getElementById("open-hole").textContent, fallback: document.getElementById("fallback").textContent, unknown: document.getElementById("unknown").textContent, htmlHole: htmlHole.textContent, htmlHasB: !!htmlHole.querySelector("b"), scripted: document.getElementById("scripted").textContent, chromeId: document.getElementById("chrome-id").textContent, err: err.textContent, errHasB: !!err.querySelector("b"), nav: name ? name.textContent : "", fold: fold ? fold.getAttribute("aria-label") : "", missing: missing ? missing.textContent : "" }); })()`