oficina / slides Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build browser
2
3// A modal dialog owns the keyboard.
4package slidestest
5
6import (
7 "fmt"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/chromedp/cdproto/input"
13 "github.com/chromedp/chromedp"
14 "github.com/chromedp/chromedp/kb"
15)
16
17// modKey presses key with a modifier held, the way a person does:
18// keydown and keyup through the browser's own input pipeline, so the
19// event starts at the focused element and travels the page.
20func modKey(key string, mods input.Modifier) chromedp.Action {
21 return chromedp.KeyEvent(key, chromedp.KeyModifiers(mods))
22}
23
24// TestBrowserModalOwnsTheKeyboard (Sheets' #39 finding 1, checked here):
25// with the link dialog open, the editor's document-level shortcuts —
26// undo, redo, duplicate slide, new slide — do nothing. Before the fix
27// Ctrl+D duplicated the slide behind the dialog and Ctrl+Z undid an
28// edit nobody could see.
29func TestBrowserModalOwnsTheKeyboard(t *testing.T) {
30 rig, _, _ := openEditor(t)
31 const heading = `[slides-stage] [data-block="heading"]`
32 const saved = `document.querySelector('[estilo-save-status]')?.dataset.state === 'saved'`
33
34 // Something to undo, saved.
35 rig.Run(
36 chromedp.Click(heading, chromedp.ByQuery),
37 chromedp.SendKeys(heading, string(kb.End)+" undoable", chromedp.ByQuery),
38 waitFor(`document.querySelector('`+heading+`').textContent.includes(' undoable')`),
39 chromedp.Sleep(600*time.Millisecond),
40 waitFor(saved),
41 )
42 var slides int
43 var title string
44 rig.Run(
45 chromedp.Evaluate(`document.querySelectorAll('[slides-strip-item]').length`, &slides),
46 chromedp.Evaluate(`document.querySelector('`+heading+`').textContent`, &title),
47 )
48
49 // Count every update the tab sends from here on.
50 rig.Run(chromedp.Evaluate(`(() => {
51 window.__updatePosts = 0
52 const f = window.fetch
53 window.fetch = (u, o) => {
54 if (String(u).endsWith('/updates') && o && o.method === 'POST') window.__updatePosts++
55 return f(u, o)
56 }
57 const b = navigator.sendBeacon.bind(navigator)
58 navigator.sendBeacon = (u, d) => { if (String(u).endsWith('/updates')) window.__updatePosts++; return b(u, d) }
59 })()`, nil))
60
61 // Open the link dialog the way a person does: Mod+K in the heading.
62 var mac bool
63 rig.Run(chromedp.Evaluate(`/Mac|iP(hone|[oa]d)/.test(navigator.platform)`, &mac))
64 mod := input.ModifierCtrl
65 if mac {
66 mod = input.ModifierMeta
67 }
68 rig.Run(
69 chromedp.Focus(heading, chromedp.ByQuery),
70 modKey("k", mod),
71 waitFor(`document.querySelector('[slides-link-dialog]').open`),
72 waitFor(`document.activeElement?.closest('[slides-link-dialog]') !== null`),
73 )
74 rig.Screen(`body`, "the link dialog is open")
75
76 for _, m := range []input.Modifier{input.ModifierCtrl, input.ModifierMeta} {
77 rig.Run(
78 modKey("z", m),
79 modKey("z", m|input.ModifierShift),
80 modKey("y", m),
81 modKey("d", m),
82 )
83 }
84 // Typing in the dialog's own field still works: stopping the
85 // event's propagation is not preventing its default.
86 rig.Run(chromedp.SendKeys(`[slides-link-dialog] input[name="href"]`, "https://example.com", chromedp.ByQuery))
87 rig.Run(chromedp.Sleep(1500 * time.Millisecond))
88
89 var posts, slidesAfter int
90 var titleAfter, field string
91 var open bool
92 rig.Run(
93 chromedp.Evaluate(`window.__updatePosts`, &posts),
94 chromedp.Evaluate(`document.querySelectorAll('[slides-strip-item]').length`, &slidesAfter),
95 chromedp.Evaluate(`document.querySelector('`+heading+`').textContent`, &titleAfter),
96 chromedp.Evaluate(`document.querySelector('[slides-link-dialog]').open`, &open),
97 chromedp.Evaluate(`document.querySelector('[slides-link-dialog] input[name="href"]').value`, &field),
98 )
99 if posts != 0 {
100 t.Errorf("with the link dialog open, shortcuts sent %d update(s) behind it, want 0", posts)
101 }
102 if slidesAfter != slides {
103 t.Errorf("with the link dialog open, the strip went from %d slides to %d", slides, slidesAfter)
104 }
105 if titleAfter != title {
106 t.Errorf("with the link dialog open, the heading went from %q to %q", title, titleAfter)
107 }
108 if !open {
109 t.Error("a shortcut closed the link dialog")
110 }
111 if !strings.Contains(field, "example.com") {
112 t.Errorf("typing in the dialog's field did not reach it: %q", field)
113 }
114
115 // Escape still closes it, and the shortcuts are live again after.
116 rig.Run(
117 chromedp.KeyEvent(kb.Escape),
118 waitFor(`!document.querySelector('[slides-link-dialog]').open`),
119 chromedp.Evaluate(`document.activeElement.blur()`, nil),
120 modKey("d", input.ModifierCtrl),
121 waitFor(fmt.Sprintf(`document.querySelectorAll('[slides-strip-item]').length === %d`, slides+1)),
122 )
123}
124