|
| 1 | +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; |
| 2 | +import { tmpdir } from 'node:os'; |
| 3 | +import { join } from 'node:path'; |
| 4 | +import sharp from 'sharp'; |
| 5 | +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; |
| 6 | +import { iconErrors, pngSize } from './icons'; |
| 7 | + |
| 8 | +const png = (width: number, height = width) => |
| 9 | + sharp({ create: { width, height, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } } }) |
| 10 | + .png() |
| 11 | + .toBuffer(); |
| 12 | + |
| 13 | +describe('pngSize', () => { |
| 14 | + it("reads a PNG's width and height", async () => { |
| 15 | + expect(pngSize(await png(640, 480))).toEqual({ width: 640, height: 480 }); |
| 16 | + }); |
| 17 | + |
| 18 | + it('is undefined for other files', () => { |
| 19 | + expect(pngSize(Buffer.from('<svg></svg>'))).toBeUndefined(); |
| 20 | + expect(pngSize(Buffer.from('\u0089PNG'))).toBeUndefined(); |
| 21 | + }); |
| 22 | +}); |
| 23 | + |
| 24 | +describe('iconErrors', () => { |
| 25 | + let publicDir: string; |
| 26 | + |
| 27 | + beforeEach(() => { |
| 28 | + publicDir = mkdtempSync(join(tmpdir(), 'hoverboard-icon-')); |
| 29 | + }); |
| 30 | + |
| 31 | + afterEach(() => rmSync(publicDir, { recursive: true, force: true })); |
| 32 | + |
| 33 | + it('accepts a PNG of at least 512×512 pixels, and any SVG', async () => { |
| 34 | + writeFileSync(join(publicDir, 'icon.png'), await png(1024)); |
| 35 | + writeFileSync(join(publicDir, 'icon.svg'), '<svg viewBox="0 0 24 24"></svg>'); |
| 36 | + |
| 37 | + expect(iconErrors('icon.png', publicDir)).toEqual([]); |
| 38 | + expect(iconErrors('icon.svg', publicDir)).toEqual([]); |
| 39 | + }); |
| 40 | + |
| 41 | + it('rejects an icon that is not in packages/web/public', () => { |
| 42 | + expect(iconErrors('images/missing.png', publicDir)).toEqual([ |
| 43 | + 'site.json/icon: "images/missing.png" is not in packages/web/public', |
| 44 | + ]); |
| 45 | + }); |
| 46 | + |
| 47 | + it('rejects a PNG that is too small for the largest icon', async () => { |
| 48 | + writeFileSync(join(publicDir, 'icon.png'), await png(512, 256)); |
| 49 | + |
| 50 | + expect(iconErrors('icon.png', publicDir)).toEqual([ |
| 51 | + 'site.json/icon: "icon.png" is 512×256 pixels, and needs to be at least 512×512', |
| 52 | + ]); |
| 53 | + }); |
| 54 | + |
| 55 | + it('rejects a .png file that is not a PNG', () => { |
| 56 | + writeFileSync(join(publicDir, 'icon.png'), 'not a png'); |
| 57 | + |
| 58 | + expect(iconErrors('icon.png', publicDir)).toEqual([ |
| 59 | + 'site.json/icon: "icon.png" is not a PNG file', |
| 60 | + ]); |
| 61 | + }); |
| 62 | + |
| 63 | + it("accepts the repository's icon", () => { |
| 64 | + expect(iconErrors('images/icon.png', join(import.meta.dirname, '../public'))).toEqual([]); |
| 65 | + }); |
| 66 | +}); |
0 commit comments