main_test.go (3864B)
1 package main 2 3 import ( 4 "bytes" 5 "image" 6 "image/color" 7 "image/png" 8 "os" 9 "path/filepath" 10 "testing" 11 12 "gioui.org/gpu/headless" 13 l "gioui.org/layout" 14 "gioui.org/op" 15 "gioui.org/unit" 16 m "gioui.org/widget/material" 17 "github.com/jackmordaunt/icns/v4" 18 ) 19 20 // iconColour is what the test icon is painted, chosen so it cannot be 21 // confused with the theme's greys. 22 var iconColour = color.NRGBA{R: 0xFF, G: 0x20, B: 0x20, A: 0xFF} 23 24 // testIcons encodes a flat coloured icns and decodes it back, the same route 25 // the window takes when it opens a file. 26 func testIcons(t *testing.T) []image.Image { 27 t.Helper() 28 src := image.NewNRGBA(image.Rect(0, 0, 512, 512)) 29 for i := 0; i < len(src.Pix); i += 4 { 30 src.Pix[i] = iconColour.R 31 src.Pix[i+1] = iconColour.G 32 src.Pix[i+2] = iconColour.B 33 src.Pix[i+3] = iconColour.A 34 } 35 var buf bytes.Buffer 36 if err := icns.Encode(&buf, src); err != nil { 37 t.Fatalf("encoding test icns: %v", err) 38 } 39 imgs, err := icns.DecodeAll(&buf) 40 if err != nil { 41 t.Fatalf("decoding test icns: %v", err) 42 } 43 return imgs 44 } 45 46 const ( 47 frameWidth = 800 48 frameHeight = 600 49 ) 50 51 // render lays the window out and rasterises it offscreen, returning how many 52 // pixels of the icon's colour reached the preview area on the right. 53 func render(t *testing.T, window *headless.Window, ui *UI) (int, *image.RGBA) { 54 t.Helper() 55 var ops op.Ops 56 gtx := l.Context{ 57 Ops: &ops, 58 Metric: unit.Metric{PxPerDp: 1, PxPerSp: 1}, 59 Constraints: l.Exact(image.Pt(frameWidth, frameHeight)), 60 } 61 ui.Update(gtx) 62 ui.Layout(gtx) 63 if err := window.Frame(gtx.Ops); err != nil { 64 t.Fatalf("rendering frame: %v", err) 65 } 66 shot := image.NewRGBA(image.Rect(0, 0, frameWidth, frameHeight)) 67 if err := window.Screenshot(shot); err != nil { 68 t.Fatalf("reading frame: %v", err) 69 } 70 var painted int 71 for y := 0; y < frameHeight; y++ { 72 for x := frameWidth / 2; x < frameWidth; x++ { 73 c := color.NRGBAModel.Convert(shot.At(x, y)).(color.NRGBA) 74 if c.R > 0xC0 && c.G < 0x60 && c.B < 0x60 && c.A > 0x80 { 75 painted++ 76 } 77 } 78 } 79 return painted, shot 80 } 81 82 // TestRender rasterises the window offscreen, which is the only way to tell 83 // that the event and input plumbing still produces a frame. 84 func TestRender(t *testing.T) { 85 window, err := headless.NewWindow(frameWidth, frameHeight) 86 if err != nil { 87 t.Skipf("headless rendering unavailable: %v", err) 88 } 89 defer window.Release() 90 91 // An empty window shows the open button and none of the icon's colour, 92 // which is what makes the count below mean something. 93 empty := &UI{Th: m.NewTheme(), ProcessedIcon: make(chan ProcessedIconResult, 1)} 94 if painted, shot := render(t, window, empty); painted > 100 { 95 t.Errorf("an empty window drew %d pixels of the icon's colour", painted) 96 if path := keep(t, shot); path != "" { 97 t.Logf("frame written to %s", path) 98 } 99 } 100 101 ui := &UI{Th: m.NewTheme(), ProcessedIcon: make(chan ProcessedIconResult, 1)} 102 // Hand the window a loaded file the way the loader goroutine does, so 103 // Update drains it into thumbnails. 104 ui.ProcessedIcon <- ProcessedIconResult{File: "test.icns", Imgs: testIcons(t)} 105 painted, shot := render(t, window, ui) 106 if len(ui.Icons) == 0 { 107 t.Fatal("the loaded icons did not reach the window") 108 } 109 if ui.Preview == nil { 110 t.Fatal("no icon was selected for the preview area") 111 } 112 t.Logf("icon pixels in the preview area: %d", painted) 113 if painted < 1000 { 114 t.Errorf("only %d pixels of the icon were drawn in the preview area", painted) 115 if path := keep(t, shot); path != "" { 116 t.Logf("frame written to %s", path) 117 } 118 } 119 } 120 121 // keep writes the frame out so a failure can be looked at. 122 func keep(t *testing.T, img image.Image) string { 123 t.Helper() 124 path := filepath.Join(t.TempDir(), "frame.png") 125 f, err := os.Create(path) 126 if err != nil { 127 return "" 128 } 129 defer f.Close() 130 if err := png.Encode(f, img); err != nil { 131 return "" 132 } 133 return path 134 }