v0.32.0 0617278

converter_test.go RawHistoryFolder

1package favicon
2
3import (
4	"bytes"
5	"context"
6	"errors"
7	"image"
8	"image/color"
9	"image/png"
10	"testing"
11
12	appport "github.com/bnema/dumber/internal/application/port"
13)
14
15func TestConverterPNGNormalizesAndSizes(t *testing.T) {
16	conv := NewImageConverter()
17	out, err := conv.Convert(context.Background(), testPNG(t, 64, 48), "image/png", []int{32})
18	if err != nil {
19		t.Fatal(err)
20	}
21	if _, decodeErr := png.Decode(bytes.NewReader(out.PNG)); decodeErr != nil {
22		t.Fatalf("normalized png invalid: %v", decodeErr)
23	}
24	img, err := png.Decode(bytes.NewReader(out.SizedPNG[32]))
25	if err != nil {
26		t.Fatal(err)
27	}
28	if img.Bounds().Dx() != 32 || img.Bounds().Dy() != 32 {
29		t.Fatalf("size = %v", img.Bounds())
30	}
31}
32
33func TestConverterAcceptedFaviconFormatsProducePNGs(t *testing.T) {
34	conv := NewImageConverter()
35	for _, tc := range []struct {
36		name, ct string
37		data     []byte
38	}{
39		{"png", "image/png", testPNG(t, 16, 16)},
40		{"ico with png payload", "image/x-icon", testICOWithPNG(t, testPNG(t, 16, 16))},
41		{"ico with bitmap payload", "image/x-icon", testICOWithDIB(t)},
42	} {
43		t.Run(tc.name, func(t *testing.T) {
44			out, err := conv.Convert(context.Background(), tc.data, tc.ct, []int{32})
45			if err != nil {
46				t.Fatal(err)
47			}
48			if _, decodeErr := png.Decode(bytes.NewReader(out.PNG)); decodeErr != nil {
49				t.Fatalf("normalized png invalid: %v", decodeErr)
50			}
51			img, err := png.Decode(bytes.NewReader(out.SizedPNG[32]))
52			if err != nil {
53				t.Fatal(err)
54			}
55			if img.Bounds().Dx() != 32 || img.Bounds().Dy() != 32 {
56				t.Fatalf("size = %v", img.Bounds())
57			}
58		})
59	}
60}
61
62func TestConverterRejectsFormatsThatWouldProducePlaceholders(t *testing.T) {
63	conv := NewImageConverter()
64	for _, tc := range []struct {
65		name, ct string
66		data     []byte
67	}{
68		{"svg", "image/svg+xml", []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><rect width="16" height="16" fill="#ff0000"/></svg>`)},
69		{"webp", "image/webp", []byte("RIFF\x04\x00\x00\x00WEBPVP8 ")},
70	} {
71		t.Run(tc.name, func(t *testing.T) {
72			if _, err := conv.Convert(context.Background(), tc.data, tc.ct, []int{32}); !errors.Is(err, appport.ErrFaviconMiss) {
73				t.Fatalf("err = %v, want favicon miss", err)
74			}
75		})
76	}
77}
78
79func TestConverterMalformedInputsAreMisses(t *testing.T) {
80	conv := NewImageConverter()
81	for _, tc := range []struct {
82		name, ct string
83		data     []byte
84	}{
85		{"ico", "image/x-icon", []byte{0, 0, 1, 0}},
86		{"svg", "image/svg+xml", []byte(`not svg`)},
87		{"webp", "image/webp", []byte("not webp")},
88		{"malformed", "image/png", []byte("not an image")},
89	} {
90		t.Run(tc.name, func(t *testing.T) {
91			if _, err := conv.Convert(context.Background(), tc.data, tc.ct, []int{32}); !errors.Is(err, appport.ErrFaviconMiss) {
92				t.Fatalf("err = %v", err)
93			}
94		})
95	}
96}
97
98func testICOWithDIB(t *testing.T) []byte {
99	t.Helper()
100	const width, height, bitCount = 2, 2, 32
101	var dib bytes.Buffer
102	writeLE32 := func(v uint32) { dib.Write([]byte{byte(v), byte(v >> 8), byte(v >> 16), byte(v >> 24)}) }
103	writeLE16 := func(v uint16) { dib.Write([]byte{byte(v), byte(v >> 8)}) }
104	writeLE32(40)                 // BITMAPINFOHEADER size
105	writeLE32(width)              // width
106	writeLE32(height * 2)         // xor + and mask height
107	writeLE16(1)                  // planes
108	writeLE16(bitCount)           // bit count
109	writeLE32(0)                  // BI_RGB
110	writeLE32(width * height * 4) // image size
111	writeLE32(0)
112	writeLE32(0) // pels per meter
113	writeLE32(0)
114	writeLE32(0)                                      // colors
115	dib.Write([]byte{0, 0, 255, 255, 0, 255, 0, 255}) // bottom row BGRA
116	dib.Write([]byte{255, 0, 0, 255, 0, 0, 0, 255})   // top row BGRA
117	return testICOWithPayload(t, dib.Bytes(), bitCount)
118}
119
120func testICOWithPNG(t *testing.T, pngData []byte) []byte {
121	t.Helper()
122	return testICOWithPayload(t, pngData, 32)
123}
124
125func testICOWithPayload(t *testing.T, payload []byte, bitCount byte) []byte {
126	t.Helper()
127	buf := bytes.NewBuffer(nil)
128	buf.Write([]byte{0, 0, 1, 0, 1, 0})  // reserved, type icon, count
129	buf.Write([]byte{16, 16, 0, 0})      // width, height, colors, reserved
130	buf.Write([]byte{1, 0, bitCount, 0}) // planes, bit count
131	size := uint32(len(payload))
132	offset := uint32(6 + 16)
133	buf.Write([]byte{byte(size), byte(size >> 8), byte(size >> 16), byte(size >> 24)})
134	buf.Write([]byte{byte(offset), byte(offset >> 8), byte(offset >> 16), byte(offset >> 24)})
135	buf.Write(payload)
136	return buf.Bytes()
137}
138
139func testPNG(t *testing.T, w, h int) []byte {
140	t.Helper()
141	img := image.NewRGBA(image.Rect(0, 0, w, h))
142	for y := range h {
143		for x := range w {
144			img.Set(x, y, color.RGBA{R: 200, A: 255})
145		}
146	}
147	var buf bytes.Buffer
148	if err := png.Encode(&buf, img); err != nil {
149		t.Fatal(err)
150	}
151	return buf.Bytes()
152}

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 8

Tags 42

↑↓ moveenter openesc close