rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1//go:build browser
2
3package check
4
5import (
6 "context"
7 "encoding/json"
8 "html"
9 "net/http"
10 "strings"
11 "testing"
12 "time"
13
14 "github.com/chromedp/chromedp"
15
16 "amadan.net/rastrillo/fiesta"
17 "amadan.net/rastrillo/fiesta/internal/harness"
18)
19
20// The Node tests in strings_test.go hold the lookup on a stub; these
21// hold it on a real page, through real enhancement: a page that
22// carries no string attributes at all, only the table, still puts the
23// table's words on screen; and a page with no table still reads the
24// vocabulary off its attribute, as JSON text, and falls back to the
25// English labels.
26
27// spanishDoc is a lang="es" page carrying a date field armed with
28// data-rst-date and a twelve-option select armed with data-rst-select,
29// and no data-rst-date-* or data-rst-select-* attribute (asserted)
30// beyond the two this builder adds: with table, an inline
31// <script>self.fiestaStrings = {es: <table>}</script> ahead of the
32// deferred scripts; with words, data-rst-date-words on the input,
33// holding that JSON text.
34func spanishDoc(t *testing.T, table []byte, words string) string {
35 t.Helper()
36 var b strings.Builder
37 b.WriteString(`<div rst-field><label rst-field-label for="when">Fecha</label><input rst-input id="when" name="when" type="date" data-rst-date`)
38 if words != "" {
39 b.WriteString(` data-rst-date-words="` + html.EscapeString(words) + `"`)
40 }
41 b.WriteString(`></div>` + "\n")
42 b.WriteString(`<div rst-field><label rst-field-label for="city">Ciudad</label><select rst-input id="city" name="city" data-rst-select>`)
43 for _, city := range []string{"Dublín", "Lisboa", "Madrid", "Berlín", "Roma", "Atenas", "Helsinki", "Reikiavik", "Varsovia", "Zagreb", "Tallin", "La Valeta"} {
44 b.WriteString(`<option>` + city + `</option>`)
45 }
46 b.WriteString(`</select></div>`)
47 doc := sizingDoc("strings", b.String())
48 wantWords := 0
49 if words != "" {
50 wantWords = 1
51 }
52 if strings.Count(doc, "data-rst-date-") != wantWords || strings.Count(doc, "data-rst-date-words") != wantWords || strings.Contains(doc, "data-rst-select-") {
53 t.Fatalf("the page carries a string attribute it should not:\n%s", doc)
54 }
55 es := strings.Replace(doc, `<html lang="en"`, `<html lang="es"`, 1)
56 if es == doc {
57 t.Fatal("the fixture page does not say lang=\"en\" where this test expects to change it")
58 }
59 if table == nil {
60 return es
61 }
62 withTable := strings.Replace(es, `<head>`, `<head><script>self.fiestaStrings = {es: `+string(table)+`};</script>`, 1)
63 if withTable == es {
64 t.Fatal("the fixture page has no <head> to put the table in")
65 }
66 return withTable
67}
68
69// esStrings is what the two drives read out of es.json.
70type esStrings struct {
71 Set string `json:"date-set"`
72 Today string `json:"date-quick-today"`
73 Filter string `json:"select-filter"`
74 Words json.RawMessage `json:"date-words"`
75}
76
77// loadES reads es.json and checks it can tell the table from the
78// fallback: a table whose strings were the English ones would let
79// either drive pass for the wrong reason.
80func loadES(t *testing.T) ([]byte, esStrings, string) {
81 t.Helper()
82 es, ok := fiesta.Strings("es")
83 if !ok {
84 t.Fatal("no es table")
85 }
86 var want esStrings
87 if err := json.Unmarshal(es, &want); err != nil {
88 t.Fatal(err)
89 }
90 var words map[string]string
91 if err := json.Unmarshal(want.Words, &words); err != nil {
92 t.Fatal(err)
93 }
94 if want.Set == "" || want.Today == "" || want.Filter == "" || words["tomorrow"] == "" {
95 t.Fatalf("es.json is missing a string these drives read: %+v", want)
96 }
97 if want.Set == "Set" || want.Filter == "Type to filter" {
98 t.Fatalf("es.json's strings are the English fallbacks, so these drives could not tell the table from the fallback: %+v", want)
99 }
100 return es, want, strings.Split(words["tomorrow"], "|")[0]
101}
102
103// spanishScreen is what the page shows once both fields are enhanced
104// and tomorrow's word has been typed into the date field: the select's
105// filter placeholder, and the armed reading's set label, its text and
106// the first quick pick's label.
107type spanishScreen struct {
108 Placeholder, Set, Label, Quick string
109}
110
111// driveSpanish serves doc, waits for both enhancements, types tomorrow
112// into the date field and reads the screen. A reading arms the first
113// row; text the parser refused arms nothing, so a vocabulary that did
114// not arrive fails on the wait.
115func driveSpanish(t *testing.T, doc, tomorrow string) spanishScreen {
116 t.Helper()
117 pages := map[string]string{"/": doc}
118 rig := harness.New(t, func(string) http.Handler { return sizingMux(t, pages) })
119 ctx, cancel := context.WithTimeout(rig.Context(), 120*time.Second)
120 defer cancel()
121
122 mustRun(t, ctx,
123 chromedp.Navigate(rig.Origin+"/"),
124 chromedp.WaitVisible(`[rst-dtp] input[role="combobox"]`, chromedp.ByQuery),
125 chromedp.WaitVisible(`[rst-combo] input[role="combobox"]`, chromedp.ByQuery),
126 chromedp.Click(`[rst-dtp] input[role="combobox"]`, chromedp.ByQuery),
127 chromedp.SendKeys(`[rst-dtp] input[role="combobox"]`, tomorrow, chromedp.ByQuery))
128 settleUntil(t, ctx, `!!document.querySelector('[rst-dtp] [role="option"].is-active [rst-dtp-label]')`)
129 var got spanishScreen
130 at(t, ctx, `JSON.stringify({
131 Placeholder: document.querySelector('[rst-combo] input[role="combobox"]').placeholder,
132 Set: document.querySelector('[rst-dtp] [role="option"].is-active [rst-dtp-set]')?.textContent ?? '',
133 Label: document.querySelector('[rst-dtp] [role="option"].is-active [rst-dtp-label]')?.textContent ?? '',
134 Quick: document.querySelector('[rst-dtp] [rst-dtp-quick] [rst-dtp-label]')?.textContent ?? ''})`, &got)
135 if got.Label == "" {
136 t.Errorf("typing %q armed no reading; the vocabulary did not reach the parser", tomorrow)
137 }
138 rig.Screen("body", "the Spanish page after typing "+tomorrow)
139 return got
140}
141
142// TestTableOnlyStringsReachTheScreen: on the table-only page, the
143// select's filter placeholder is es.json's select-filter, typing
144// mañana resolves (the vocabulary came from the table's date-words,
145// an object, not an attribute), the armed reading's set label is
146// es.json's date-set, and the first quick pick is date-quick-today.
147func TestTableOnlyStringsReachTheScreen(t *testing.T) {
148 es, want, tomorrow := loadES(t)
149 got := driveSpanish(t, spanishDoc(t, es, ""), tomorrow)
150 if got.Placeholder != want.Filter {
151 t.Errorf("the select's filter placeholder reads %q, want es.json's select-filter %q", got.Placeholder, want.Filter)
152 }
153 if got.Set != want.Set {
154 t.Errorf("the date field's set label reads %q, want es.json's date-set %q", got.Set, want.Set)
155 }
156 if got.Quick != want.Today {
157 t.Errorf("the first quick pick reads %q, want es.json's date-quick-today %q", got.Quick, want.Today)
158 }
159}
160
161// TestAttributeWordsReachTheParser is the other way in: no table on
162// the page, and es.json's date-words on the input as JSON text, the
163// form a server-rendered page carries. Typing mañana still resolves,
164// and every label is the script's English fallback, since the page's
165// language has no table and nothing else says otherwise.
166func TestAttributeWordsReachTheParser(t *testing.T) {
167 _, want, tomorrow := loadES(t)
168 got := driveSpanish(t, spanishDoc(t, nil, string(want.Words)), tomorrow)
169 if got.Placeholder != "Type to filter" {
170 t.Errorf("with no table, the select's filter placeholder reads %q, want the fallback", got.Placeholder)
171 }
172 if got.Set != "Set" {
173 t.Errorf("with no table, the date field's set label reads %q, want the fallback", got.Set)
174 }
175 if got.Quick != "Today" {
176 t.Errorf("with no table, the first quick pick reads %q, want the fallback", got.Quick)
177 }
178}
179