246 lines
8.1 KiB
Go
246 lines
8.1 KiB
Go
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: "<b>x</b> fehlt", scripted: "Skript", chromeID: "dialog.cancel",
|
|
err: "<b>x</b> 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 <b>x</b> is missing", scripted: "Scripted", chromeID: "dialog.cancel",
|
|
err: "Missing <b>x</b>", 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": "<b>x</b>"})
|
|
return `<h1 id="heading" data-phrase="page.heading">Heading</h1>
|
|
<input id="field" data-phrase-placeholder="page.placeholder" placeholder="Placeholder" data-phrase-title="page.title" title="Title" data-phrase-aria-label="page.aria" aria-label="Aria">
|
|
<p id="hole" data-phrase="page.hole" data-phrase-holes='{"zone":"example.com"}'>Zone {zone} is missing</p>
|
|
<p id="open-hole" data-phrase="page.hole">Zone {zone} is missing</p>
|
|
<p id="fallback" data-phrase="page.english-only">English only</p>
|
|
<p id="unknown" data-phrase="not.a.phrase">English source</p>
|
|
<p id="htmlhole" data-phrase="page.hole" data-phrase-holes='{"zone":"<b>x</b>"}'>Zone {zone} is missing</p>
|
|
<p id="scripted"></p>
|
|
<p id="chrome-id"></p>
|
|
<p id="err">` + html.EscapeString(msg) + `</p>
|
|
<script>
|
|
document.getElementById("scripted").textContent = exappPhrase("page.scripted");
|
|
document.getElementById("chrome-id").textContent = exappPhrase("dialog.cancel");
|
|
</script>`, 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 : ""
|
|
});
|
|
})()`
|