v0.21.0 ef38ad0

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	assert.Contains(t, page, `data-install`)
61}
62
63// The icons are exactly the size the manifest declares: Chrome does not
64// install from an icon that is smaller than it says.
65func TestAppIconSizes(t *testing.T) {
66	t.Parallel()
67	for _, ic := range appIcons {
68		b, err := staticFS.ReadFile("static/app/" + ic.file)
69		require.NoError(t, err)
70		cfg, err := png.DecodeConfig(bytes.NewReader(b))
71		require.NoError(t, err, ic.file)
72		assert.Equal(t, ic.sizes, fmt.Sprintf("%dx%d", cfg.Width, cfg.Height), ic.file)
73	}
74}
75
76// The service worker is served from the root scope, is revalidated on each
77// check, and answers a conditional request with 304.
78func TestServiceWorkerRoute(t *testing.T) {
79	t.Parallel()
80	srv := newBareServer(t)
81	get := func(inm string) *httptest.ResponseRecorder {
82		rec := httptest.NewRecorder()
83		r := httptest.NewRequestWithContext(context.Background(), http.MethodGet, ServiceWorkerPath, nil)
84		if inm != "" {
85			r.Header.Set("If-None-Match", inm)
86		}
87		srv.ServeHTTP(rec, r)
88		return rec
89	}
90	rec := get("")
91	require.Equal(t, http.StatusOK, rec.Code)
92	h := rec.Header()
93	assert.Equal(t, "text/javascript; charset=utf-8", h.Get("Content-Type"))
94	assert.Equal(t, "no-cache", h.Get("Cache-Control"), "a cached worker would never update")
95	assert.Equal(t, "/", h.Get("Service-Worker-Allowed"))
96	assert.NotEmpty(t, h.Get("ETag"))
97	assert.Contains(t, rec.Body.String(), "addEventListener('fetch'")
98	assert.NotContains(t, rec.Body.String(), "caches.", "nothing authenticated is stored")
99
100	again := get(h.Get("ETag"))
101	assert.Equal(t, http.StatusNotModified, again.Code)
102
103	// The registered script is the one served.
104	assert.Contains(t, staticJS(t, "ermite.js"), "register('"+ServiceWorkerPath+"'")
105}
106
107func staticJS(t *testing.T, name string) string {
108	t.Helper()
109	b, err := staticFS.ReadFile("static/js/" + name)
110	require.NoError(t, err)
111	return string(b)
112}

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