| 1 | package docs |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "net/http" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | ) |
| 9 | |
| 10 | // The searchable-select enhancement's strings reach the page resolved, |
| 11 | // not as raw catalog keys. |
| 12 | // |
| 13 | // This goes through the real handler rather than calling |
| 14 | // newToolbarView, and that is the entire point of it. rastrillo.T |
| 15 | // returns its key verbatim when no locale middleware has run — |
| 16 | // documented, and what every direct view-data test in this package sees |
| 17 | // — so a unit test here would compare "rastrillo.ui.select_filter" |
| 18 | // against itself and pass whether or not the key exists. |
| 19 | // |
| 20 | // These three are the only keys this app resolves out of the |
| 21 | // FRAMEWORK's namespace rather than its own Catalog, so they are the |
| 22 | // only ones a typo would strand: our own are held by |
| 23 | // TestEveryMenuKeyIsInTheCatalog against a map we control. A stranded |
| 24 | // key here raises no error anywhere — ui/select.js carries English |
| 25 | // fallbacks and would simply use the attribute's value as its |
| 26 | // placeholder — so the whole failure is a person reading the literal |
| 27 | // text "rastrillo.ui.select_filter" inside the filter box. |
| 28 | func TestTheSelectEnhancementStringsResolve(t *testing.T) { |
| 29 | app, err := NewForTest(t) |
| 30 | if err != nil { |
| 31 | t.Fatalf("NewForTest: %v", err) |
| 32 | } |
| 33 | owner := app.TestPerson(t, "selects@example.com") |
| 34 | doc, err := app.Store().Create(context.Background(), owner, "Selects") |
| 35 | if err != nil { |
| 36 | t.Fatalf("Create: %v", err) |
| 37 | } |
| 38 | |
| 39 | body := serve(app, app.TestRequest(t, "selects@example.com", http.MethodGet, "/d/"+doc.Sqid, nil)).Body.String() |
| 40 | |
| 41 | // The opt-in is present at all. Without this the checks below would |
| 42 | // be looking for keys in a page that emits no such attributes, and |
| 43 | // would pass by finding nothing. |
| 44 | if !strings.Contains(body, "data-rst-select") { |
| 45 | t.Fatal("no select carries data-rst-select — the searchable enhancement is not opted into, so the checks below verify nothing") |
| 46 | } |
| 47 | |
| 48 | // CALIBRATE AGAINST OUR OWN KEYS FIRST, and skip if they are raw. |
| 49 | // |
| 50 | // The first version of this test asserted the framework's three keys |
| 51 | // resolve, full stop. It went red, and the finding looked serious: |
| 52 | // cmd/docs/main.go sets opts.BaseCatalog = docs.Catalog, which — if |
| 53 | // it REPLACED the framework's own strings rather than extending |
| 54 | // them — would leave every rastrillo.ui.* key unresolved app-wide, |
| 55 | // including the shell's skip link, which is a WCAG bypass-blocks |
| 56 | // mechanism that would have been rendering the literal text |
| 57 | // "rastrillo.ui.shell_skip". |
| 58 | // |
| 59 | // It does not replace them. rastrillo's serve.go builds `base := |
| 60 | // BaseCatalog()` and merges opts.BaseCatalog on top, so the |
| 61 | // framework's strings sit under the app's and both resolve. |
| 62 | // |
| 63 | // Confirmed by running the app rather than only by reading that |
| 64 | // source: `scripts/run-dev.sh`, sign in, fetch a document, and the |
| 65 | // page carries `data-rst-select-filter="Type to filter"` with zero |
| 66 | // occurrences of any `rastrillo.ui.*` or `docs.*` key anywhere in |
| 67 | // its 34KB. That is the observation this test cannot make, and it |
| 68 | // is worth having made once — the alternative was believing a |
| 69 | // source-read about a merge that happens two packages away. |
| 70 | // |
| 71 | // This test still cannot make it, because the harness serves |
| 72 | // through app.Handler() directly and rastrillo installs its locale |
| 73 | // middleware in Serve, so in a test NOTHING resolves, ours |
| 74 | // included, and rastrillo.T returns every key verbatim exactly as |
| 75 | // documented. |
| 76 | // |
| 77 | // An assertion that reports the same failure whether or not the bug |
| 78 | // exists is not evidence of the bug. That is AGENTS.md's "cannot |
| 79 | // distinguish no from I can't see it", written by me rather than |
| 80 | // found in a tool, and it cost an hour it should not have. |
| 81 | // |
| 82 | // So this compares like with like: if our own catalog keys came |
| 83 | // back raw, the environment resolves nothing and there is nothing |
| 84 | // here to learn. If they resolved, the framework's must have too — |
| 85 | // and if they did not, the replacement-versus-merge bug above is |
| 86 | // real and this is what finds it. |
| 87 | ourKey := "docs.editor.toolbar.bold" |
| 88 | if strings.Contains(body, ourKey) { |
| 89 | t.Skipf("this harness resolves no catalog keys at all (%q is raw in the page), so it cannot say whether the framework's resolve — see this test's own comment", ourKey) |
| 90 | } |
| 91 | |
| 92 | for _, key := range []string{ |
| 93 | "rastrillo.ui.select_filter", |
| 94 | "rastrillo.ui.select_results", |
| 95 | "rastrillo.ui.select_result_one", |
| 96 | } { |
| 97 | if strings.Contains(body, key) { |
| 98 | t.Errorf("our own keys resolved but the framework's %q did not — opts.BaseCatalog replaces rastrillo's base catalog instead of extending it, and a person reads this key as the filter box's placeholder", key) |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | // All three selects opt in, not just the one with enough options to |
| 104 | // earn a filter on the framework's own judgement. |
| 105 | // |
| 106 | // Paul's instruction, and the reasoning is in views/show.html's comment: |
| 107 | // they sit side by side in one group, and one combobox flanked by two |
| 108 | // native selects looks worse than either extreme. Asserted because the |
| 109 | // opposite is a tempting tidy-up — field-select.html's ten-option rule |
| 110 | // is right there in the framework — and because reverting it should be |
| 111 | // a decision someone takes on purpose rather than a plausible-looking |
| 112 | // diff. |
| 113 | func TestEveryToolbarSelectIsSearchable(t *testing.T) { |
| 114 | app, err := NewForTest(t) |
| 115 | if err != nil { |
| 116 | t.Fatalf("NewForTest: %v", err) |
| 117 | } |
| 118 | owner := app.TestPerson(t, "allselects@example.com") |
| 119 | doc, err := app.Store().Create(context.Background(), owner, "Selects") |
| 120 | if err != nil { |
| 121 | t.Fatalf("Create: %v", err) |
| 122 | } |
| 123 | |
| 124 | body := serve(app, app.TestRequest(t, "allselects@example.com", http.MethodGet, "/d/"+doc.Sqid, nil)).Body.String() |
| 125 | |
| 126 | if got := strings.Count(body, `data-docs-select=`); got != 3 { |
| 127 | t.Fatalf("the toolbar renders %d selects, expected 3 — this test's arithmetic no longer describes the bar", got) |
| 128 | } |
| 129 | if got := strings.Count(body, ` data-rst-select `); got != 3 { |
| 130 | t.Errorf("%d of the 3 toolbar selects opt into the searchable enhancement", got) |
| 131 | } |
| 132 | } |
| 133 | |