diff --git a/apps/gittensory-miner-extension/icons/icon-128.png b/apps/gittensory-miner-extension/icons/icon-128.png new file mode 100644 index 000000000..acc7ab28e Binary files /dev/null and b/apps/gittensory-miner-extension/icons/icon-128.png differ diff --git a/apps/gittensory-miner-extension/icons/icon-16.png b/apps/gittensory-miner-extension/icons/icon-16.png new file mode 100644 index 000000000..ef4576ea8 Binary files /dev/null and b/apps/gittensory-miner-extension/icons/icon-16.png differ diff --git a/apps/gittensory-miner-extension/icons/icon-32.png b/apps/gittensory-miner-extension/icons/icon-32.png new file mode 100644 index 000000000..cb37b26ae Binary files /dev/null and b/apps/gittensory-miner-extension/icons/icon-32.png differ diff --git a/apps/gittensory-miner-extension/icons/icon-48.png b/apps/gittensory-miner-extension/icons/icon-48.png new file mode 100644 index 000000000..ccaf2d2be Binary files /dev/null and b/apps/gittensory-miner-extension/icons/icon-48.png differ diff --git a/apps/gittensory-miner-extension/manifest.json b/apps/gittensory-miner-extension/manifest.json index f27558bd7..a43eb2d15 100644 --- a/apps/gittensory-miner-extension/manifest.json +++ b/apps/gittensory-miner-extension/manifest.json @@ -3,6 +3,12 @@ "name": "LoopOver Miner Opportunity", "description": "Contributor-facing GitHub issue opportunity signals from a locally configured miner plane.", "version": "0.1.0", + "icons": { + "16": "icons/icon-16.png", + "32": "icons/icon-32.png", + "48": "icons/icon-48.png", + "128": "icons/icon-128.png" + }, "permissions": ["storage"], "host_permissions": ["https://github.com/*", "http://localhost/*", "http://127.0.0.1/*"], "background": { @@ -19,6 +25,11 @@ ], "options_page": "options.html", "action": { - "default_title": "LoopOver Miner" + "default_title": "LoopOver Miner", + "default_icon": { + "16": "icons/icon-16.png", + "32": "icons/icon-32.png", + "48": "icons/icon-48.png" + } } } diff --git a/scripts/build-miner-extension.mjs b/scripts/build-miner-extension.mjs index 67d4a4fd4..d22e73a73 100644 --- a/scripts/build-miner-extension.mjs +++ b/scripts/build-miner-extension.mjs @@ -22,13 +22,19 @@ const PACKAGE_FILES = [ "options.js", "styles.css", "toolbar-badge.js", + "icons/icon-16.png", + "icons/icon-32.png", + "icons/icon-48.png", + "icons/icon-128.png", ]; rmSync(outDir, { recursive: true, force: true }); mkdirSync(outDir, { recursive: true }); for (const file of PACKAGE_FILES) { - cpSync(resolve(source, file), resolve(outDir, file)); + const dest = resolve(outDir, file); + mkdirSync(dirname(dest), { recursive: true }); // create nested dirs (e.g. icons/) before copying + cpSync(resolve(source, file), dest); } const zipPath = resolve(source, "dist/loopover-miner-extension.zip"); diff --git a/test/unit/miner-extension-icons.test.ts b/test/unit/miner-extension-icons.test.ts new file mode 100644 index 000000000..d9230bc07 --- /dev/null +++ b/test/unit/miner-extension-icons.test.ts @@ -0,0 +1,56 @@ +import { existsSync, readFileSync } from "node:fs"; + +import { describe, expect, it } from "vitest"; + +const EXT = "apps/gittensory-miner-extension"; +const manifest = JSON.parse(readFileSync(`${EXT}/manifest.json`, "utf8")); + +/** Parse a PNG's signature + IHDR (width/height/colorType) — enough to assert a real image of the expected size. */ +function readPng(path: string): { + valid: boolean; + width: number; + height: number; + colorType: number; +} { + const buf = readFileSync(path); + const valid = + buf.subarray(0, 8).toString("hex") === "89504e470d0a1a0a" && + buf.subarray(12, 16).toString("ascii") === "IHDR"; + return { + valid, + width: buf.readUInt32BE(16), + height: buf.readUInt32BE(20), + colorType: buf[25] ?? 0, + }; +} + +describe("miner extension icons (#4862)", () => { + const SIZES = [16, 32, 48, 128] as const; + + it("declares an icon set at the standard sizes in the manifest", () => { + for (const size of SIZES) { + expect(manifest.icons[String(size)]).toBe(`icons/icon-${size}.png`); + } + }); + + it("wires the toolbar action's default_icon at 16/32/48", () => { + for (const size of [16, 32, 48] as const) { + expect(manifest.action.default_icon[String(size)]).toBe( + `icons/icon-${size}.png`, + ); + } + }); + + it("ships every declared icon as a real RGBA PNG whose dimensions match its declared size", () => { + for (const size of SIZES) { + const rel = manifest.icons[String(size)] as string; + const path = `${EXT}/${rel}`; + expect(existsSync(path), `${rel} exists`).toBe(true); + const png = readPng(path); + expect(png.valid, `${rel} is a valid PNG`).toBe(true); + expect(png.width).toBe(size); + expect(png.height).toBe(size); + expect(png.colorType).toBe(6); // 6 = truecolor + alpha (RGBA) + } + }); +});