v0.30.0 c899179

manifest_test.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5import (
6	"bytes"
7	"context"
8	"encoding/json"
9	"fmt"
10	"image/png"
11	"net/http"
12	"net/http/httptest"
13	"strings"
14	"testing"
15
16	"github.com/stretchr/testify/assert"
17	"github.com/stretchr/testify/require"
18)
19
20// The web app manifest makes the site installable: every page links it, and
21// its icons are served assets.
22func TestWebAppManifest(t *testing.T) {
23	t.Parallel()
24	srv := newBareServer(t)
25	u := srv.assets.urls
26	man, ok := srv.assets.byName[strings.TrimPrefix(u.Manifest, StaticPrefix)]
27	require.True(t, ok)
28	assert.Equal(t, "application/manifest+json", man.typ)
29
30	var m webManifest
31	require.NoError(t, json.Unmarshal(man.body, &m))
32	assert.Equal(t, "standalone", m.Display)
33	assert.Equal(t, "/", m.StartURL)
34	assert.Equal(t, "/", m.Scope)
35	// The id keeps the identity of the app when the hashed URL of the manifest
36	// changes with a release.
37	assert.Equal(t, "/", m.ID)
38	assert.NotEmpty(t, m.Description)
39	assert.NotContains(t, string(man.body), "prefer_related_applications")
40	require.Len(t, m.Icons, len(appIcons))
41	for _, ic := range m.Icons {
42		a, ok := srv.assets.byName[strings.TrimPrefix(ic.Src, StaticPrefix)]
43		require.True(t, ok, ic.Src)
44		assert.Equal(t, "image/png", a.typ)
45	}
46	_, ok = srv.assets.byName[strings.TrimPrefix(u.AppleIcon, StaticPrefix)]
47	assert.True(t, ok)
48
49	// Chrome asks for a 192 and a 512 icon with purpose any.
50	var any192, any512 bool
51	for _, ic := range m.Icons {
52		any192 = any192 || (ic.Sizes == "192x192" && ic.Purpose == "any")
53		any512 = any512 || (ic.Sizes == "512x512" && ic.Purpose == "any")
54	}
55	assert.True(t, any192 && any512)
56
57	page := renderToString(t, Shell(Layout{Assets: u}, nil))
58	assert.Contains(t, page, `<link rel="manifest" href="`+u.Manifest+`">`)
59	assert.Contains(t, page, `<link rel="apple-touch-icon" href="`+u.AppleIcon+`">`)
60}
61
62// The icons are exactly the size the manifest declares: Chrome does not
63// install from an icon that is smaller than it says.
64func TestAppIconSizes(t *testing.T) {
65	t.Parallel()
66	for _, ic := range appIcons {
67		b, err := staticFS.ReadFile("static/app/" + ic.file)
68		require.NoError(t, err)
69		cfg, err := png.DecodeConfig(bytes.NewReader(b))
70		require.NoError(t, err, ic.file)
71		assert.Equal(t, ic.sizes, fmt.Sprintf("%dx%d", cfg.Width, cfg.Height), ic.file)
72	}
73}
74
75// The service worker is served from the root scope, is revalidated on each
76// check, and answers a conditional request with 304.
77func TestServiceWorkerRoute(t *testing.T) {
78	t.Parallel()
79	srv := newBareServer(t)
80	get := func(inm string) *httptest.ResponseRecorder {
81		rec := httptest.NewRecorder()
82		r := httptest.NewRequestWithContext(context.Background(), http.MethodGet, ServiceWorkerPath, nil)
83		if inm != "" {
84			r.Header.Set("If-None-Match", inm)
85		}
86		srv.ServeHTTP(rec, r)
87		return rec
88	}
89	rec := get("")
90	require.Equal(t, http.StatusOK, rec.Code)
91	h := rec.Header()
92	assert.Equal(t, "text/javascript; charset=utf-8", h.Get("Content-Type"))
93	assert.Equal(t, "no-cache", h.Get("Cache-Control"), "a cached worker would never update")
94	assert.Equal(t, "/", h.Get("Service-Worker-Allowed"))
95	assert.NotEmpty(t, h.Get("ETag"))
96	assert.Contains(t, rec.Body.String(), "addEventListener('fetch'")
97	assert.NotContains(t, rec.Body.String(), "caches.", "nothing authenticated is stored")
98
99	again := get(h.Get("ETag"))
100	assert.Equal(t, http.StatusNotModified, again.Code)
101
102	// The registered script is the one served.
103	assert.Contains(t, staticJS(t, "ermite.js"), "register('"+ServiceWorkerPath+"'")
104}
105
106func staticJS(t *testing.T, name string) string {
107	t.Helper()
108	b, err := staticFS.ReadFile("static/js/" + name)
109	require.NoError(t, err)
110	return string(b)
111}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close