slot_test.go (6278B)
1 package icns 2 3 import ( 4 "bytes" 5 "image" 6 "image/color" 7 "reflect" 8 "testing" 9 ) 10 11 // slotOf names the element that fills each slot, which is what per-slot 12 // artwork has to land in. 13 var slotOf = map[string]Slot{ 14 "ic10": {512, 2}, 15 "ic09": {512, 1}, 16 "ic14": {256, 2}, 17 "ic08": {256, 1}, 18 "ic13": {128, 2}, 19 "ic07": {128, 1}, 20 "ic12": {32, 2}, 21 "ic11": {16, 2}, 22 "il32": {32, 1}, 23 "is32": {16, 1}, 24 } 25 26 func solid(side int, c color.NRGBA) *image.NRGBA { 27 img := image.NewNRGBA(image.Rect(0, 0, side, side)) 28 for i := 0; i < len(img.Pix); i += 4 { 29 img.Pix[i], img.Pix[i+1], img.Pix[i+2], img.Pix[i+3] = c.R, c.G, c.B, c.A 30 } 31 return img 32 } 33 34 // centre reports the colour at the middle of img. 35 func centre(img image.Image) color.NRGBA { 36 b := img.Bounds() 37 return color.NRGBAModel.Convert(img.At(b.Min.X+b.Dx()/2, b.Min.Y+b.Dy()/2)).(color.NRGBA) 38 } 39 40 func TestParseSlot(t *testing.T) { 41 t.Parallel() 42 tests := []struct { 43 name string 44 want Slot 45 ok bool 46 }{ 47 {"icon_16x16.png", Slot{16, 1}, true}, 48 {"icon_16x16@2x.png", Slot{16, 2}, true}, 49 {"icon_32x32@2x.png", Slot{32, 2}, true}, 50 {"icon_512x512.png", Slot{512, 1}, true}, 51 {"icon_512x512@2x.png", Slot{512, 2}, true}, 52 {"icon_16x32.png", Slot{}, false}, 53 {"icon_0x0.png", Slot{}, false}, 54 {"icon_16x16@0x.png", Slot{}, false}, 55 {"icon_16x16.jpg", Slot{}, false}, 56 {"16x16.png", Slot{}, false}, 57 {"icon_16x16@2x.png.bak", Slot{}, false}, 58 {"", Slot{}, false}, 59 } 60 for _, tt := range tests { 61 t.Run(tt.name, func(st *testing.T) { 62 got, ok := ParseSlot(tt.name) 63 if ok != tt.ok || got != tt.want { 64 st.Fatalf("ParseSlot(%q) = %v, %v; want %v, %v", tt.name, got, ok, tt.want, tt.ok) 65 } 66 }) 67 } 68 } 69 70 func TestSlotString(t *testing.T) { 71 t.Parallel() 72 if got := (Slot{16, 1}).String(); got != "16x16" { 73 t.Errorf("got %q, want 16x16", got) 74 } 75 if got := (Slot{512, 2}).String(); got != "512x512@2x" { 76 t.Errorf("got %q, want 512x512@2x", got) 77 } 78 if got := (Slot{16, 2}).Pixels(); got != 32 { 79 t.Errorf("16x16@2x is %d pixels, want 32", got) 80 } 81 } 82 83 // TestSlots checks that the slots reported are exactly the ten an iconset 84 // holds, so a caller knows what artwork to supply. 85 func TestSlots(t *testing.T) { 86 t.Parallel() 87 got := Slots() 88 if len(got) != len(slotOf) { 89 t.Fatalf("Slots returned %d entries, want %d", len(got), len(slotOf)) 90 } 91 seen := map[Slot]bool{} 92 for _, slot := range got { 93 seen[slot] = true 94 } 95 for id, slot := range slotOf { 96 if !seen[slot] { 97 t.Errorf("slot %s, filled by %s, is missing", slot, id) 98 } 99 } 100 // Largest artwork first. 101 for i := 1; i < len(got); i++ { 102 if got[i].Pixels() > got[i-1].Pixels() { 103 t.Fatalf("slot %d (%s) is larger than the one before it (%s)", i, got[i], got[i-1]) 104 } 105 } 106 } 107 108 // TestEncodeSlots checks that artwork given for a slot reaches the element 109 // that fills it, including the three pixel sizes two slots share. 110 func TestEncodeSlots(t *testing.T) { 111 t.Parallel() 112 var ( 113 images = map[Slot]image.Image{} 114 want = map[Slot]color.NRGBA{} 115 ) 116 for i, slot := range Slots() { 117 c := color.NRGBA{R: uint8(10 + i*20), G: uint8(200 - i*15), B: 0x40, A: 0xFF} 118 // Artwork at exactly the slot's size, so nothing is resampled and 119 // the colour has to survive exactly. 120 images[slot] = solid(int(slot.Pixels()), c) 121 want[slot] = c 122 } 123 buf := bytes.NewBuffer(nil) 124 if err := NewEncoder(buf).EncodeSlots(images); err != nil { 125 t.Fatal(err) 126 } 127 d, err := NewDecoder(bytes.NewReader(buf.Bytes())) 128 if err != nil { 129 t.Fatal(err) 130 } 131 icons := d.Icons() 132 if len(icons) != len(slotOf) { 133 t.Fatalf("encoded %d icons, want %d", len(icons), len(slotOf)) 134 } 135 for _, icon := range icons { 136 slot, ok := slotOf[icon.ID] 137 if !ok { 138 t.Fatalf("unexpected element %s", icon.ID) 139 } 140 img, err := icon.Decode() 141 if err != nil { 142 t.Fatalf("%s: %v", icon.ID, err) 143 } 144 if got := uint(img.Bounds().Dx()); got != slot.Pixels() { 145 t.Errorf("%s is %d pixels, want %d", icon.ID, got, slot.Pixels()) 146 } 147 if got := centre(img); got != want[slot] { 148 t.Errorf("%s fills slot %s with %v, want %v", icon.ID, slot, got, want[slot]) 149 } 150 } 151 } 152 153 func TestEncodeSlotsFallback(t *testing.T) { 154 t.Parallel() 155 var ( 156 source = color.NRGBA{R: 0x20, G: 0x40, B: 0x60, A: 0xFF} 157 tuned = color.NRGBA{R: 0xF0, G: 0x10, B: 0x80, A: 0xFF} 158 ) 159 // Only two slots are given artwork, and the 16 pixel one is supplied at 160 // the wrong size so it has to be resized into place. 161 images := map[Slot]image.Image{ 162 {Points: 512, Scale: 2}: solid(1024, source), 163 {Points: 16, Scale: 1}: solid(64, tuned), 164 } 165 buf := bytes.NewBuffer(nil) 166 if err := NewEncoder(buf).EncodeSlots(images); err != nil { 167 t.Fatal(err) 168 } 169 d, err := NewDecoder(bytes.NewReader(buf.Bytes())) 170 if err != nil { 171 t.Fatal(err) 172 } 173 if got := len(d.Icons()); got != len(slotOf) { 174 t.Fatalf("encoded %d icons, want the full set of %d", got, len(slotOf)) 175 } 176 for _, icon := range d.Icons() { 177 img, err := icon.Decode() 178 if err != nil { 179 t.Fatalf("%s: %v", icon.ID, err) 180 } 181 want := source 182 if icon.ID == "is32" { 183 want = tuned 184 if got := img.Bounds().Dx(); got != 16 { 185 t.Errorf("is32 is %d pixels, want 16", got) 186 } 187 } 188 if got := centre(img); got != want { 189 t.Errorf("%s = %v, want %v", icon.ID, got, want) 190 } 191 } 192 } 193 194 func TestEncodeSlotsRejects(t *testing.T) { 195 t.Parallel() 196 buf := bytes.NewBuffer(nil) 197 if err := NewEncoder(buf).EncodeSlots(nil); err == nil { 198 t.Error("encoding without artwork was accepted") 199 } 200 if err := NewEncoder(buf).EncodeSlots(map[Slot]image.Image{{16, 1}: nil}); err == nil { 201 t.Error("encoding a nil image was accepted") 202 } 203 if err := NewEncoder(buf).EncodeSlots(map[Slot]image.Image{{16, 1}: solid(8, color.NRGBA{})}); err == nil { 204 t.Error("encoding artwork below the smallest icon was accepted") 205 } 206 } 207 208 // TestEncodeSlotsMatchesSingleImage checks the two entry points agree when 209 // the artwork is the same. 210 func TestEncodeSlotsMatchesSingleImage(t *testing.T) { 211 t.Parallel() 212 src := gradient(256) 213 var single, slots bytes.Buffer 214 if err := Encode(&single, src); err != nil { 215 t.Fatal(err) 216 } 217 if err := NewEncoder(&slots).EncodeSlots(map[Slot]image.Image{{Points: 128, Scale: 2}: src}); err != nil { 218 t.Fatal(err) 219 } 220 if !reflect.DeepEqual(single.Bytes(), slots.Bytes()) { 221 t.Fatal("the same artwork through each entry point produced different files") 222 } 223 }