oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package sheets
2
3// The shared chrome, as Sheets renders it (estilo spec/shared-chrome,
4// §5, §6, §7, §8, §9, §10). Each test names the section it holds.
5
6import (
7 "net/http"
8 "net/http/httptest"
9 "regexp"
10 "strconv"
11 "strings"
12 "testing"
13)
14
15var pageTitle = regexp.MustCompile(`<title>([^<]*)</title>`)
16
17// page GETs path as a browser does, with the session cookies.
18func page(t *testing.T, app *App, cookies []*http.Cookie, path string) *httptest.ResponseRecorder {
19 t.Helper()
20 req := httptest.NewRequest(http.MethodGet, path, nil)
21 req.Header.Set("Accept", "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8")
22 return do(t, app, as(req, cookies))
23}
24
25func titleOf(t *testing.T, body string) string {
26 t.Helper()
27 m := pageTitle.FindStringSubmatch(body)
28 if m == nil {
29 t.Fatal("no <title>")
30 }
31 return m[1]
32}
33
34// homeSignedIn signs the fixture's browser in THROUGH Home: a linked
35// account, then an ordinary sign-in by assertion, so the session's
36// method is Home's.
37func homeSignedIn(t *testing.T, f *homeFixture) Member {
38 t.Helper()
39 member := f.local(t, signedInAddress)
40 f.link(t, "opaque-home-subject")
41 f.jar = map[string]*http.Cookie{}
42 signin := f.request(t, "GET", "/signin?return_to=%2Fsheets", nil, false)
43 start := f.request(t, "GET", signin.Header().Get("Location"), nil, false)
44 if finish := f.exchange(t, start, "opaque-home-subject"); finish.Code != 303 {
45 t.Fatal("Home sign-in", finish.Code)
46 }
47 return member
48}
49
50// §10: every <title> is "{part} · {part} · Sheets", built in the layout,
51// and a page's {Page} is its h1. None says "Oficina Sheets", "—" or "|".
52func TestPageTitlesFollowTheSuitePattern(t *testing.T) {
53 app, cookies := signIn(t)
54 s, _ := newSheet(t, app, cookies, "Commission Q2")
55 id := strconv.FormatUint(s.ID, 10)
56 for path, want := range map[string]string{
57 "/sheets": "Sheets",
58 "/sheets/" + id: "Commission Q2 · Sheets",
59 "/sheets/" + id + "/table": "Table view · Commission Q2 · Sheets",
60 "/sheets/" + id + "/history": "Version history · Commission Q2 · Sheets",
61 "/sheets/" + id + "/comments": "Comments · Commission Q2 · Sheets",
62 "/sheets/" + id + "/share": "Share · Commission Q2 · Sheets",
63 "/sheets/import": "Import a spreadsheet · Sheets",
64 "/sheets/99999": "Sheet not available · Sheets",
65 "/no/such/page": "Page not found · Sheets",
66 } {
67 if got := titleOf(t, page(t, app, cookies, path).Body.String()); got != want {
68 t.Errorf("%s: <title> %q, want %q", path, got, want)
69 }
70 }
71 // A sub-page's h1 is its {Page}; the sheet's title is in the bar.
72 for path, h1 := range map[string]string{
73 "/sheets/" + id + "/table": "Table view",
74 "/sheets/" + id + "/history": "Version history",
75 "/sheets/" + id + "/comments": "Comments",
76 "/sheets/" + id + "/share": "Share",
77 "/sheets": "All sheets",
78 } {
79 body := page(t, app, cookies, path).Body.String()
80 if !strings.Contains(body, "<h1>"+h1+"</h1>") {
81 t.Errorf("%s: the h1 is not %q", path, h1)
82 }
83 if path != "/sheets" && !strings.Contains(body, `<span estilo-bar-context dir="auto" title="Commission Q2">Commission Q2</span>`) {
84 t.Errorf("%s: the sheet's title is not the bar's context", path)
85 }
86 }
87 for path, want := range map[string]string{
88 "/": "Sheets",
89 "/signin": "Sign in · Sheets",
90 "/signin?sent=1": "Check your email · Sheets",
91 "/signed-out": "Signed out · Sheets",
92 "/no/such/page": "Page not found · Sheets",
93 "/signin?err=bad": "Sign in · Sheets",
94 } {
95 if got := titleOf(t, page(t, app, nil, path).Body.String()); got != want {
96 t.Errorf("%s signed out: <title> %q, want %q", path, got, want)
97 }
98 }
99}
100
101// §5.1 / §8.2: on every signed-in page the account menu is the last
102// thing in the bar, and "Sign out" appears exactly once in the page —
103// it used to be the account list's fourth flat row.
104func TestEverySignedInPageHasOneSignOutInTheSameCorner(t *testing.T) {
105 app, cookies := signIn(t)
106 s, _ := newSheet(t, app, cookies, "Budget")
107 id := strconv.FormatUint(s.ID, 10)
108 for _, path := range []string{"/sheets", "/sheets/" + id, "/sheets/" + id + "/table", "/sheets/" + id + "/share", "/sheets/" + id + "/history", "/sheets/" + id + "/comments", "/sheets/import", "/no/such/page", "/sheets/99999"} {
109 body := page(t, app, cookies, path).Body.String()
110 if n := strings.Count(body, ">Sign out<"); n != 1 {
111 t.Errorf("%s: %d Sign out, want exactly one", path, n)
112 }
113 end := body[strings.Index(body, "<div estilo-bar-end>"):strings.Index(body, "</header>")]
114 tail := end[strings.LastIndex(end, "</details>"):]
115 if !strings.Contains(end, "<details estilo-account") || strings.Count(tail, "<") != 2 { // </details> </div>
116 t.Errorf("%s: the account is not the last thing in the bar", path)
117 }
118 if strings.Contains(body, "rst-shell-") {
119 t.Errorf("%s: the rastrillo topbar is still rendered", path)
120 }
121 }
122}
123
124// §5.1 / §8.4: signed out, the corner is "Sign in" carrying the page to
125// come back to; on the sign-in pages themselves it is empty; on the
126// public link page the brand is plain text and Sign in carries no path
127// (the path is a capability).
128func TestTheSignedOutCorner(t *testing.T) {
129 app, cookies := signIn(t)
130 s, _ := newSheet(t, app, cookies, "Budget")
131 token, err := app.createLink(s.ID, SheetViewer, signedInMember(t, app))
132 if err != nil {
133 t.Fatal(err)
134 }
135 body := page(t, app, nil, "/no/such/page?x=1").Body.String()
136 if !strings.Contains(body, `<a rst-btn href="/signin?return_to=%2Fno%2Fsuch%2Fpage%3Fx%3D1" estilo-bar-signin>Sign in</a>`) {
137 t.Error("a signed-out page's corner is not Sign in with its return_to")
138 }
139 for _, path := range []string{"/signin", "/signin?sent=1", "/signed-out"} {
140 body := page(t, app, nil, path).Body.String()
141 if strings.Contains(body, "estilo-bar-signin") || strings.Contains(body, "estilo-account") {
142 t.Errorf("%s: the corner is not empty on a sign-in page", path)
143 }
144 }
145 link := page(t, app, nil, "/l/"+token).Body.String()
146 if !strings.Contains(link, "<span estilo-bar-brand>") || strings.Contains(link, "<a estilo-bar-brand") {
147 t.Error("the public link page's brand is a link a stranger would be bounced through")
148 }
149 if !strings.Contains(link, `<a rst-btn href="/signin" estilo-bar-signin>Sign in</a>`) || strings.Contains(link, "return_to=%2Fl%2F") {
150 t.Error("the link page's Sign in is missing, or copies the capability path into a URL")
151 }
152}
153
154// §6: the switcher shows only when Home names a hub directory, and then
155// holds "All apps" alone (Paul's §13 answer 2) — no tiles yet.
156func TestTheSwitcherIsAllAppsOnly(t *testing.T) {
157 app, cookies := signIn(t)
158 if body := page(t, app, cookies, "/sheets").Body.String(); strings.Contains(body, "estilo-switcher") {
159 t.Error("a standalone instance shows a switcher; there is no directory to switch through")
160 }
161 f := newHomeFixtureWith(t, "https://oficina.example.com")
162 f.local(t, signedInAddress)
163 body := f.request(t, "GET", "/sheets", nil, false).Body.String()
164 for _, want := range []string{`<details estilo-switcher name="estilo-menus">`, `aria-label="Oficina apps"`, `<a estilo-menu-item href="https://oficina.example.com/"><span estilo-menu-label>All apps</span></a>`} {
165 if !strings.Contains(body, want) {
166 t.Errorf("the switcher lacks %s", want)
167 }
168 }
169 if strings.Contains(body, "estilo-switcher-app") {
170 t.Error("the switcher renders app tiles before Home's /apps/<slug> hop is ready")
171 }
172}
173
174// §5.1 / §5.3: the grid is the one page whose shell fills the viewport;
175// every other page's shell is plain, and every page has the bar.
176func TestTheGridFillsTheViewport(t *testing.T) {
177 app, cookies := signIn(t)
178 s, _ := newSheet(t, app, cookies, "Budget")
179 grid := page(t, app, cookies, "/sheets/"+strconv.FormatUint(s.ID, 10)).Body.String()
180 if !strings.Contains(grid, `<div estilo-shell="fill">`) || !strings.Contains(grid, `<header estilo-bar>`) {
181 t.Error("the grid's shell does not fill the viewport, or its bar is sticky in a page that never scrolls")
182 }
183 list := page(t, app, cookies, "/sheets").Body.String()
184 if !strings.Contains(list, "<div estilo-shell>") || !strings.Contains(list, `<header estilo-bar="sticky">`) {
185 t.Error("an ordinary page's shell or bar is wrong")
186 }
187}
188