Resolve the page UI language once and let each ExApp fill its own Phrases.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
package gonexapp_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/chromedp/cdproto/network"
|
||||
"github.com/chromedp/chromedp"
|
||||
|
||||
"gitea.neitzel.de/konrad/go-nc-exapp"
|
||||
)
|
||||
|
||||
const browserLanguage = "de-DE,de;q=0.9"
|
||||
|
||||
func TestUILanguageChrome(t *testing.T) {
|
||||
german := chromeSeen{
|
||||
lang: "de", open: "Navigation öffnen", close: "Navigation schließen",
|
||||
fold: "Domains einklappen", cancel: "Abbrechen", ok: "OK",
|
||||
}
|
||||
english := chromeSeen{
|
||||
lang: "en", open: "Open navigation", close: "Close navigation",
|
||||
fold: "Fold Domains", cancel: "Cancel", ok: "OK",
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
user bool
|
||||
status int
|
||||
ncLang string
|
||||
want chromeSeen
|
||||
}{
|
||||
{name: "german", user: true, status: http.StatusOK, ncLang: "de", want: german},
|
||||
{name: "english", user: true, status: http.StatusOK, ncLang: "en", want: english},
|
||||
{name: "formal german", user: true, status: http.StatusOK, ncLang: "de_DE", want: german},
|
||||
{name: "british english", user: true, status: http.StatusOK, ncLang: "en_GB", want: english},
|
||||
{name: "french", user: true, status: http.StatusOK, ncLang: "fr", want: english},
|
||||
{name: "failed read", user: true, status: http.StatusInternalServerError, ncLang: "de", want: english},
|
||||
{name: "missing user", user: false, status: http.StatusOK, ncLang: "de", want: english},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
nc := fakeLanguage(tt.status, tt.ncLang)
|
||||
t.Cleanup(nc.Close)
|
||||
page := languagePage(nc.URL)
|
||||
t.Cleanup(page.Close)
|
||||
|
||||
headers := network.Headers{"Accept-Language": browserLanguage}
|
||||
if tt.user {
|
||||
headers["AUTHORIZATION-APP-API"] = base64.StdEncoding.EncodeToString([]byte("alice:secret"))
|
||||
}
|
||||
got := readChrome(t, page.URL+"/?item=domains", headers)
|
||||
if !strings.Contains(got.page, "domains-page") {
|
||||
t.Fatalf("page did not render: %+v", got)
|
||||
}
|
||||
if got.lang != tt.want.lang || got.open != tt.want.open || got.close != tt.want.close ||
|
||||
got.fold != tt.want.fold || got.cancel != tt.want.cancel || got.ok != tt.want.ok || got.agree != "Remove" {
|
||||
t.Fatalf("got %+v, want %+v with agree %q", got, tt.want, "Remove")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type chromeSeen struct {
|
||||
lang string
|
||||
open string
|
||||
close string
|
||||
fold string
|
||||
cancel string
|
||||
ok string
|
||||
agree string
|
||||
page string
|
||||
}
|
||||
|
||||
func fakeLanguage(status int, language string) *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet || !strings.Contains(r.URL.Path, "/cloud/users/") {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if status != http.StatusOK {
|
||||
http.Error(w, "language unavailable", status)
|
||||
return
|
||||
}
|
||||
// A read that drops the browser language must not look like a hit.
|
||||
lang := language
|
||||
if r.Header.Get("Accept-Language") != browserLanguage {
|
||||
lang = "en"
|
||||
}
|
||||
// locale differs from language so a read of the wrong field fails the page.
|
||||
locale := "fr"
|
||||
if language == "fr" || language == "en_GB" {
|
||||
locale = "de"
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"language":%q,"locale":%q}}}`, lang, locale)
|
||||
}))
|
||||
}
|
||||
|
||||
func languagePage(ncBase string) *httptest.Server {
|
||||
nav := sampleNavigation()
|
||||
nav.Cred = gonexapp.Credentials{
|
||||
BaseURL: ncBase,
|
||||
AppID: "exapp",
|
||||
AppVersion: "1.0.0",
|
||||
AAVersion: "1.0.0",
|
||||
AppSecret: "secret",
|
||||
}
|
||||
nav.Page = func(_ *http.Request, id string) (string, bool) {
|
||||
switch id {
|
||||
case "domains":
|
||||
return `<p>domains-page</p><button type="button" id="ask">Ask</button>
|
||||
<script>
|
||||
document.getElementById("ask").addEventListener("click", function () {
|
||||
exappDialog.confirm({ text: "Sure?", agree: "Remove" });
|
||||
});
|
||||
</script>`, true
|
||||
case "keys":
|
||||
return "<p>keys-page</p>", true
|
||||
case "example.com":
|
||||
return "<p>zone-page</p>", true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
return httptest.NewServer(nav.Handler())
|
||||
}
|
||||
|
||||
func readChrome(t *testing.T, pageURL string, headers network.Headers) chromeSeen {
|
||||
t.Helper()
|
||||
ctx, cancel := chromedp.NewContext(allocCtx)
|
||||
t.Cleanup(cancel)
|
||||
ctx, cancel = context.WithTimeout(ctx, 25*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
var raw, closeLabel, agree string
|
||||
err := chromedp.Run(ctx,
|
||||
network.Enable(),
|
||||
network.SetExtraHTTPHeaders(headers),
|
||||
chromedp.Navigate(pageURL),
|
||||
chromedp.Evaluate(chromeProbeJS, &raw),
|
||||
chromedp.Evaluate(`document.getElementById("nav-toggle").click()`, nil),
|
||||
chromedp.Evaluate(`document.getElementById("nav-toggle").getAttribute("aria-label")`, &closeLabel),
|
||||
chromedp.Evaluate(`document.getElementById("ask").click()`, nil),
|
||||
chromedp.Evaluate(`document.getElementById("exapp-dialog-agree").textContent`, &agree),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var probe struct {
|
||||
Lang string `json:"lang"`
|
||||
Open string `json:"open"`
|
||||
Fold string `json:"fold"`
|
||||
Cancel string `json:"cancel"`
|
||||
OK string `json:"ok"`
|
||||
Page string `json:"page"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(raw), &probe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return chromeSeen{
|
||||
lang: probe.Lang, open: probe.Open, close: closeLabel, fold: probe.Fold,
|
||||
cancel: probe.Cancel, ok: probe.OK, agree: agree, page: probe.Page,
|
||||
}
|
||||
}
|
||||
|
||||
const chromeProbeJS = `(() => {
|
||||
const fold = document.querySelector("button.fold");
|
||||
return JSON.stringify({
|
||||
lang: document.documentElement.lang,
|
||||
open: document.getElementById("nav-toggle").getAttribute("aria-label"),
|
||||
fold: fold ? fold.getAttribute("aria-label") : "",
|
||||
cancel: document.getElementById("exapp-dialog-cancel").textContent,
|
||||
ok: document.getElementById("exapp-dialog-agree").textContent,
|
||||
page: document.querySelector("main").textContent
|
||||
});
|
||||
})()`
|
||||
Reference in New Issue
Block a user