/** * png.ts — deterministic solid-color PNG encoder. * * The placeholder icons must be REAL PNGs (installers and iOS reject broken * images silently) and byte-deterministic (diff-clean re-runs). Assertions * decode the actual byte stream: signature, IHDR geometry, and a zlib * round-trip of the IDAT payload back to the raw scanline length. */ import { describe, it, expect } from 'vitest' import { inflateSync } from 'node:zlib' import { encodePng, parseHexColor, PNG_SIGNATURE } from '../png.js' function chunksOf(buf: Buffer): Array<{ type: string; data: Buffer }> { const out: Array<{ type: string; data: Buffer }> = [] let off = 8 while (off < buf.length) { const len = buf.readUInt32BE(off) const type = buf.toString('ascii', off + 4, off + 8) out.push({ type, data: buf.subarray(off + 8, off + 8 + len) }) off += 12 + len } return out } describe('encodePng', () => { it('starts with the PNG magic bytes 89 50 4E 47 0D 0A 1A 0A', () => { const png = encodePng(192, 192, '#4f46e5') expect(png.subarray(0, 8).equals(PNG_SIGNATURE)).toBe(true) expect([...png.subarray(0, 8)]).toEqual([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) }) it.each([ [192, 192], [512, 512], [180, 180], ])('IHDR carries the %ix%i geometry (8-bit truecolor RGB, no interlace)', (w, h) => { const png = encodePng(w, h, '#4f46e5') const ihdr = chunksOf(png).find((c) => c.type === 'IHDR')! expect(ihdr.data.readUInt32BE(0)).toBe(w) expect(ihdr.data.readUInt32BE(4)).toBe(h) expect(ihdr.data[8]).toBe(8) // bit depth expect(ihdr.data[9]).toBe(2) // color type: truecolor RGB expect(ihdr.data[12]).toBe(0) // no interlace }) it('IDAT inflates back to the expected raw scanline length (filter byte + RGB triples)', () => { const w = 192 const h = 192 const png = encodePng(w, h, '#111827') const idat = Buffer.concat(chunksOf(png).filter((c) => c.type === 'IDAT').map((c) => c.data)) const raw = inflateSync(idat) expect(raw.length).toBe(h * (1 + w * 3)) // First scanline: filter None then the solid color. expect(raw[0]).toBe(0) expect([raw[1], raw[2], raw[3]]).toEqual([0x11, 0x18, 0x27]) }) it('is deterministic — same inputs produce byte-identical buffers', () => { const a = encodePng(512, 512, '#22c55e') const b = encodePng(512, 512, '#22c55e') expect(a.equals(b)).toBe(true) }) it('ends with an IEND chunk', () => { const png = encodePng(16, 16, '#000000') const types = chunksOf(png).map((c) => c.type) expect(types[types.length - 1]).toBe('IEND') }) it('rejects non-#rrggbb colors and invalid dimensions', () => { expect(() => encodePng(16, 16, 'red')).toThrow() expect(() => encodePng(16, 16, '#fff')).toThrow() expect(() => encodePng(0, 16, '#ffffff')).toThrow() }) }) describe('parseHexColor', () => { it('parses #rrggbb into the RGB triple', () => { expect(parseHexColor('#4f46e5')).toEqual([0x4f, 0x46, 0xe5]) expect(parseHexColor('#FFFFFF')).toEqual([255, 255, 255]) }) })