oficina / slides Public

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

Plain git — no account needed to clone.

Download

Download this file

1package slidestest
2
3import (
4 "net/http"
5 "net/url"
6 "strings"
7 "testing"
8)
9
10// The sample deck's own words, as the seed writes them (sample.go).
11// Slide 2 and slide 3 are named here because the paging test needs two
12// slides that are told apart by their headings alone.
13const (
14 sampleSlide2Heading = "Text and marks"
15 sampleSlide3Heading = "Regions, not coordinates"
16)
17
18// sampleDeck signs a person in and returns the path of the deck their
19// first sign-in seeded. Eight slides, notes on every one — the fixture
20// every viewer test here wants, without writing one of its own.
21func sampleDeck(t *testing.T, a *testApp, cookies []*http.Cookie) string {
22 t.Helper()
23 list := mustGet(t, a, "/", http.StatusOK, cookies).Body.String()
24 path := deckPath.FindString(list)
25 if path == "" {
26 t.Fatalf("GET / lists no deck to view:\n%s", list)
27 }
28 return path
29}
30
31// slideSection returns the one <section slides-slide> on a viewer page,
32// so an assertion about "the slide being shown" cannot be satisfied by
33// the filmstrip, which names every other slide on the same page.
34func slideSection(t *testing.T, page string) string {
35 t.Helper()
36 if n := strings.Count(page, "<section slides-slide"); n != 1 {
37 t.Fatalf("the viewer renders %d slides, want exactly 1:\n%s", n, page)
38 }
39 start := strings.Index(page, "<section slides-slide")
40 end := strings.Index(page[start:], "</section>")
41 if end < 0 {
42 t.Fatalf("the rendered slide is not closed:\n%s", page)
43 }
44 return page[start : start+end]
45}
46
47// TestViewerPagesThroughSlides: ?slide=N is the whole viewer. Slide 2
48// shows the second slide and only the second slide, the next link goes
49// to 3, the previous link goes to 1, and the counter says where you
50// are in words rather than only in a URL.
51func TestViewerPagesThroughSlides(t *testing.T) {
52 a := newApp(t)
53 cookies := signedIn(t, a, allowedAddress)
54 path := sampleDeck(t, a, cookies)
55
56 page := mustGet(t, a, path+"?slide=2", http.StatusOK, cookies).Body.String()
57
58 section := slideSection(t, page)
59 if !strings.Contains(section, sampleSlide2Heading) {
60 t.Errorf("?slide=2 does not render the second slide:\n%s", section)
61 }
62 if strings.Contains(section, sampleSlide3Heading) {
63 t.Errorf("?slide=2 rendered the third slide too:\n%s", section)
64 }
65 if !strings.Contains(page, `href="`+path+`?slide=3"`) {
66 t.Errorf("no next link to slide 3:\n%s", page)
67 }
68 if !strings.Contains(page, `href="`+path+`?slide=1"`) {
69 t.Errorf("no previous link to slide 1:\n%s", page)
70 }
71 if !strings.Contains(page, "2 of 8") {
72 t.Errorf("the counter does not say which slide this is:\n%s", page)
73 }
74
75 // The filmstrip names every slide by its own heading and links to
76 // it — that is how a person jumps rather than clicking next seven
77 // times.
78 if !strings.Contains(page, sampleSlide3Heading) {
79 t.Errorf("the filmstrip does not name the third slide:\n%s", page)
80 }
81 if !strings.Contains(page, `href="`+path+`?slide=8"`) {
82 t.Errorf("the filmstrip does not link the last slide:\n%s", page)
83 }
84
85 // The ends are ends: no link off either edge of the deck.
86 first := mustGet(t, a, path, http.StatusOK, cookies).Body.String()
87 if strings.Contains(first, `?slide=0"`) {
88 t.Errorf("the first slide offers a previous link:\n%s", first)
89 }
90 if !strings.Contains(first, "1 of 8") {
91 t.Errorf("no ?slide= does not default to the first slide:\n%s", first)
92 }
93 last := mustGet(t, a, path+"?slide=8", http.StatusOK, cookies).Body.String()
94 if strings.Contains(last, `?slide=9"`) {
95 t.Errorf("the last slide offers a next link:\n%s", last)
96 }
97
98 // Off the end is not a clamp and not a 500: it is a page that does
99 // not exist.
100 mustGet(t, a, path+"?slide=9", http.StatusNotFound, cookies)
101 mustGet(t, a, path+"?slide=0", http.StatusNotFound, cookies)
102 mustGet(t, a, path+"?slide=two", http.StatusNotFound, cookies)
103}
104
105// TestViewerOnPhoneViewportReflows: ?vp=phone is the settings screen's
106// "preview on a phone" — the same page, rendered for the other
107// viewport, and the viewport travels with every link on it.
108func TestViewerOnPhoneViewportReflows(t *testing.T) {
109 a := newApp(t)
110 cookies := signedIn(t, a, allowedAddress)
111 path := sampleDeck(t, a, cookies)
112
113 page := mustGet(t, a, path+"?slide=2&vp=phone", http.StatusOK, cookies).Body.String()
114 if !strings.Contains(page, `data-vp="phone"`) {
115 t.Errorf("?vp=phone does not render the phone viewport:\n%s", page)
116 }
117 // Paging out of the preview would be a trap: every link keeps it.
118 if !strings.Contains(page, `href="`+path+`?slide=3&amp;vp=phone"`) {
119 t.Errorf("the next link drops the phone viewport:\n%s", page)
120 }
121
122 desktop := mustGet(t, a, path+"?slide=2", http.StatusOK, cookies).Body.String()
123 if strings.Contains(desktop, `data-vp="phone"`) {
124 t.Errorf("the desktop viewer claims the phone viewport:\n%s", desktop)
125 }
126}
127
128// TestViewerShowsNotesToItsPeople: the notes are under the slide for
129// anyone who reached the deck signed in, and the notes page is all of
130// them at once — the scriptless presenter's crib sheet (spec §8).
131func TestViewerShowsNotesToItsPeople(t *testing.T) {
132 a := newApp(t)
133 cookies := signedIn(t, a, allowedAddress)
134 path := sampleDeck(t, a, cookies)
135
136 page := mustGet(t, a, path+"?slide=2", http.StatusOK, cookies).Body.String()
137 if !strings.Contains(page, "<details") {
138 t.Errorf("the viewer does not carry the notes disclosure:\n%s", page)
139 }
140 if !strings.Contains(page, "template. Text arrives as a small Markdown subset") {
141 t.Errorf("the viewer does not show slide 2's notes:\n%s", page)
142 }
143
144 notes := mustGet(t, a, path+"/notes", http.StatusOK, cookies).Body.String()
145 for _, heading := range []string{sampleSlide2Heading, sampleSlide3Heading, "Part two"} {
146 if !strings.Contains(notes, heading) {
147 t.Errorf("the notes page does not name the slide %q:\n%s", heading, notes)
148 }
149 }
150 if !strings.Contains(notes, "chapter break") {
151 t.Errorf("the notes page does not carry slide 4's notes:\n%s", notes)
152 }
153}
154
155// TestViewerOwnerButtons: settings and share are the owner's, and a
156// team member reading the same deck is offered neither.
157func TestViewerOwnerButtons(t *testing.T) {
158 a := newSharedApp(t)
159 mine := signedIn(t, a, allowedAddress)
160 theirs := signedIn(t, a, otherAddress)
161
162 path := createDeck(t, a, mine, "All hands")
163 if rec := post(t, a, path+"/settings", url.Values{
164 "title": {"All hands"}, "theme": {"ink"}, "rung": {"team"},
165 }, mine...); rec.Code != http.StatusSeeOther {
166 t.Fatalf("raising the rung: status %d\n%s", rec.Code, rec.Body)
167 }
168
169 owner := mustGet(t, a, path, http.StatusOK, mine).Body.String()
170 if !strings.Contains(owner, path+"/settings") {
171 t.Errorf("the owner's viewer offers no settings:\n%s", owner)
172 }
173 if !strings.Contains(owner, path+"/share") {
174 t.Errorf("the owner's viewer offers no share page:\n%s", owner)
175 }
176
177 member := mustGet(t, a, path, http.StatusOK, theirs).Body.String()
178 if strings.Contains(member, path+"/settings") {
179 t.Errorf("a member's viewer offers the owner's settings:\n%s", member)
180 }
181 if strings.Contains(member, path+"/share") {
182 t.Errorf("a member's viewer offers the owner's share page:\n%s", member)
183 }
184 // And the screens themselves are the owner's, not just their links.
185 mustGet(t, a, path+"/share", http.StatusNotFound, theirs)
186}
187
188// TestPresentSurfaceIsBare: ?present=1 is the scriptless presenting
189// page — one slide, two links, and none of the chrome. Task 6 replaces
190// it with the live fullscreen surface; this is the floor it has to keep
191// (spec §8), and the assertion that matters most is the notes, which
192// are for the presenter's own screen and not for the projector.
193func TestPresentSurfaceIsBare(t *testing.T) {
194 a := newApp(t)
195 cookies := signedIn(t, a, allowedAddress)
196 path := sampleDeck(t, a, cookies)
197
198 page := mustGet(t, a, path+"?slide=2&present=1", http.StatusOK, cookies).Body.String()
199 if !strings.Contains(page, "slides-present") {
200 t.Errorf("?present=1 does not mark the presenting wrapper:\n%s", page)
201 }
202 slideSection(t, page)
203 if strings.Contains(page, "Speaker notes") {
204 t.Errorf("the presenting page carries the speaker notes:\n%s", page)
205 }
206 // The markup, not the attribute: the page's own stylesheet names
207 // [slides-film] in a rule whether or not a filmstrip is rendered.
208 if strings.Contains(page, "<ol slides-film") {
209 t.Errorf("the presenting page carries the filmstrip:\n%s", page)
210 }
211 if strings.Contains(page, path+"/settings") {
212 t.Errorf("the presenting page carries the deck's buttons:\n%s", page)
213 }
214 // Paging stays in the presenting surface rather than dropping back
215 // into the shell halfway through a talk.
216 if !strings.Contains(page, `href="`+path+`?slide=3&amp;present=1"`) {
217 t.Errorf("the presenting page's next link leaves it:\n%s", page)
218 }
219}
220