Files
skald/webserver/static_accessibility_test.go

148 lines
6.5 KiB
Go

package webserver
import (
"os"
"path/filepath"
"strings"
"testing"
)
func readStaticFile(t *testing.T, name string) string {
t.Helper()
data, err := os.ReadFile(filepath.Join("..", "static", name))
if err != nil {
t.Fatalf("read %s: %v", name, err)
}
return string(data)
}
func requireContains(t *testing.T, text, want, context string) {
t.Helper()
if !strings.Contains(text, want) {
t.Fatalf("%s missing %q", context, want)
}
}
func requireNotContains(t *testing.T, text, unwanted, context string) {
t.Helper()
if strings.Contains(text, unwanted) {
t.Fatalf("%s unexpectedly contains %q", context, unwanted)
}
}
func requireFunctionContains(t *testing.T, source, functionName, want string) {
t.Helper()
start := strings.Index(source, "function "+functionName+"(")
if start < 0 {
t.Fatalf("missing function %s", functionName)
}
next := strings.Index(source[start+1:], "\nfunction ")
end := len(source)
if next >= 0 {
end = start + 1 + next
}
body := source[start:end]
if !strings.Contains(body, want) {
t.Fatalf("function %s missing %q", functionName, want)
}
}
func TestStaticLiveRegionAnnouncementsStayWired(t *testing.T) {
html := readStaticFile(t, "skald.html")
js := readStaticFile(t, "skald.js")
requireContains(t, html, `id="chat-announcements"`, "chat live region")
requireContains(t, html, `role="status"`, "chat live region")
requireContains(t, html, `aria-live="polite"`, "chat live region")
requireContains(t, html, `id="urgent-announcements"`, "urgent live region")
requireContains(t, html, `role="alert"`, "urgent live region")
requireContains(t, html, `aria-live="assertive"`, "urgent live region")
requireFunctionContains(t, js, "announceChat", "chat-announcements")
requireFunctionContains(t, js, "announceUrgent", "urgent-announcements")
requireFunctionContains(t, js, "announceLiveRegion", "state.queue.push")
requireFunctionContains(t, js, "announceLiveRegion", "flushLiveRegion")
requireFunctionContains(t, js, "flushLiveRegion", "appendChild")
requireFunctionContains(t, js, "flushLiveRegion", "if(text === undefined) {\n announcement.replaceChildren();")
requireFunctionContains(t, js, "addToChatbox", "announceChat")
requireFunctionContains(t, js, "setUserStatus", "announceUrgent(`${username}: hand raised`)")
requireFunctionContains(t, js, "setUserStatus", "announceUrgent(`${username}: hand lowered`)")
}
func TestCaptionOverlayHasBeenRemoved(t *testing.T) {
html := readStaticFile(t, "skald.html")
js := readStaticFile(t, "skald.js")
css := readStaticFile(t, "skald.css")
requireNotContains(t, html, "captions", "hall page")
requireNotContains(t, js, "caption", "hall script")
requireNotContains(t, css, "caption", "hall styles")
}
func TestHallHeadingIncludesCurrentStatus(t *testing.T) {
html := readStaticFile(t, "skald.html")
js := readStaticFile(t, "skald.js")
requireContains(t, html, "Skald Version 2026.07.08", "hall page title")
requireContains(t, js, "const skaldVersionLabel = 'Skald Version 2026.07.08'", "version label")
requireFunctionContains(t, js, "setTitle", "hallStatus.locked ? 'locked' : 'unlocked'")
requireFunctionContains(t, js, "setTitle", "status += ', recording'")
requireFunctionContains(t, js, "setTitle", "${skaldVersionLabel}")
requireFunctionContains(t, js, "gotJoined", "hallStatus.locked = false")
requireFunctionContains(t, js, "gotJoined", "hallStatus.recording = false")
requireFunctionContains(t, js, "gotJoined", "hallRecording = !!hallStatus.recording")
requireFunctionContains(t, js, "gotUserMessage", "hallRecording = true")
requireFunctionContains(t, js, "gotUserMessage", "hallRecording = false")
}
func TestGlobalShortcutAliasesStayDocumented(t *testing.T) {
js := readStaticFile(t, "skald.js")
requireFunctionContains(t, js, "isGlobalShortcut", "e.altKey && (e.ctrlKey || e.shiftKey)")
requireFunctionContains(t, js, "isGlobalShortcut", "e.key.toLowerCase() === key")
requireContains(t, js, "Control+Alt+H or Alt+Shift+H: Raise or lower hand", "shortcut help")
requireContains(t, js, "Control+Alt+C or Alt+Shift+C: Expand or collapse chat", "shortcut help")
requireContains(t, js, "Control+Alt+T or Alt+Shift+T: Mute or unmute microphone", "shortcut help")
requireContains(t, js, "if(isGlobalShortcut(e, 'h'))", "hand shortcut")
requireContains(t, js, "if(isGlobalShortcut(e, 'c'))", "chat shortcut")
requireContains(t, js, "if(isGlobalShortcut(e, 't'))", "microphone shortcut")
}
func TestTimedUnlockCommandStaysDocumentedAndUsesServerAnnouncements(t *testing.T) {
js := readStaticFile(t, "skald.js")
requireContains(t, js, "[minutes[m|M] | HH:MM | h:mm AM/PM]", "unlock command help")
requireContains(t, js, "Intl.DateTimeFormat().resolvedOptions().timeZone", "unlock timezone")
requireContains(t, js, "timezoneOffset: new Date().getTimezoneOffset()", "unlock timezone fallback")
requireContains(t, js, "serverConnection.hallAction('unlock', {", "scheduled unlock request")
requireFunctionContains(t, js, "gotUserMessage", "displayMessage(text, false)")
requireFunctionContains(t, js, "displayError", "if(announce)")
requireFunctionContains(t, js, "displayError", "announceChat(text)")
}
func TestMicrophoneHardStopLivesInSelfMenu(t *testing.T) {
html := readStaticFile(t, "skald.html")
js := readStaticFile(t, "skald.js")
requireNotContains(t, html, `id="unpresentbutton"`, "toolbar")
requireNotContains(t, html, `aria-label="Disable microphone"`, "toolbar")
requireNotContains(t, js, "getButtonElement('unpresentbutton')", "toolbar handler")
requireNotContains(t, js, "setVisibility('unpresentbutton'", "toolbar visibility")
requireFunctionContains(t, js, "userMenu", "Turn microphone off")
requireFunctionContains(t, js, "userMenu", "closeUpMedia('audio')")
requireFunctionContains(t, js, "userMenu", "Restart audio connection")
}
func TestRecordingControlLivesInSelfMenu(t *testing.T) {
js := readStaticFile(t, "skald.js")
requireFunctionContains(t, js, "canRecordHall", "serverConnection.permissions.indexOf('record') >= 0")
requireFunctionContains(t, js, "recordingsUrl", "hall.split('/').map(part => encodeURIComponent(part)).join('/')")
requireFunctionContains(t, js, "recordingPredicate", "if(canRecordHall())")
requireFunctionContains(t, js, "userMenu", "if(canRecordHall())")
requireFunctionContains(t, js, "userMenu", "hallRecording ? 'Stop recording' : 'Start recording'")
requireFunctionContains(t, js, "userMenu", "serverConnection.hallAction(hallRecording ? 'unrecord' : 'record')")
requireFunctionContains(t, js, "userMenu", "Open recordings (opens in new tab)")
requireFunctionContains(t, js, "userMenu", "window.open(recordingsUrl(), '_blank', 'noopener')")
}