feat(marke): Logo, Banner, Favicons und Manifest-Icons
Alle Varianten aus dem Symbol der Vorgabe per Skript (pnpm marke); Texte als Pfade, damit Wortmarke und Banner ohne installierte Schrift gleich aussehen. Closes #017 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { Resvg } from "@resvg/resvg-js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import manifest from "@/app/manifest";
|
||||
import { metadata } from "@/app/layout";
|
||||
|
||||
const ORDNER = "public/brand";
|
||||
|
||||
/** Breite und Höhe aus dem IHDR-Block einer PNG-Datei. */
|
||||
function pngGroesse(datei: string): [number, number] {
|
||||
const daten = readFileSync(join(ORDNER, datei));
|
||||
expect(daten.subarray(1, 4).toString()).toBe("PNG");
|
||||
return [daten.readUInt32BE(16), daten.readUInt32BE(20)];
|
||||
}
|
||||
|
||||
describe("Marken-Dateien (pnpm marke)", () => {
|
||||
it("alle SVGs der Vorgabe sind da, renderbar und ohne kaputte Koordinaten", () => {
|
||||
const svgs = readdirSync(ORDNER).filter((d) => d.endsWith(".svg"));
|
||||
expect(svgs.sort()).toEqual(
|
||||
[
|
||||
"avatar.svg",
|
||||
"banner.svg",
|
||||
"icon-small.svg",
|
||||
"logo-light.svg",
|
||||
"logo.svg",
|
||||
"symbol-light.svg",
|
||||
"symbol.svg",
|
||||
].sort(),
|
||||
);
|
||||
for (const datei of svgs) {
|
||||
const text = readFileSync(join(ORDNER, datei), "utf8");
|
||||
expect(text, datei).not.toMatch(/NaN|Infinity/);
|
||||
const bild = new Resvg(text).render();
|
||||
expect(bild.width, datei).toBeGreaterThan(0);
|
||||
}
|
||||
const banner = new Resvg(readFileSync(join(ORDNER, "banner.svg"), "utf8")).render();
|
||||
expect([banner.width, banner.height]).toEqual([1280, 640]);
|
||||
});
|
||||
|
||||
it("kein Proxmox-Orange und nur Markenfarben", () => {
|
||||
const erlaubt = new Set(["#11161C", "#1A2129", "#2A333D", "#8B98A5", "#E6EDF3", "#2DD4BF", "#0F766E"]);
|
||||
for (const datei of readdirSync(ORDNER).filter((d) => d.endsWith(".svg"))) {
|
||||
for (const [farbe] of readFileSync(join(ORDNER, datei), "utf8").matchAll(/#[0-9A-Fa-f]{6}\b/g)) {
|
||||
expect(erlaubt.has(farbe.toUpperCase()), `${datei}: ${farbe}`).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("Rasterbilder haben die richtige Größe; Layout und Manifest zeigen nur auf vorhandene Dateien", () => {
|
||||
expect(pngGroesse("favicon-16.png")).toEqual([16, 16]);
|
||||
expect(pngGroesse("favicon-32.png")).toEqual([32, 32]);
|
||||
expect(pngGroesse("apple-touch-icon.png")).toEqual([180, 180]);
|
||||
expect(pngGroesse("icon-192.png")).toEqual([192, 192]);
|
||||
expect(pngGroesse("icon-512.png")).toEqual([512, 512]);
|
||||
expect(pngGroesse("icon-maskable-512.png")).toEqual([512, 512]);
|
||||
expect(pngGroesse("banner.png")).toEqual([1280, 640]);
|
||||
|
||||
const pfade = [
|
||||
...(manifest().icons ?? []).map((i) => i.src),
|
||||
...JSON.stringify(metadata.icons)
|
||||
.matchAll(/"url":"([^"]+)"/g)
|
||||
.map((t) => t[1]!),
|
||||
];
|
||||
expect(pfade.length).toBeGreaterThan(5);
|
||||
for (const pfad of pfade) expect(existsSync(join("public", pfad)), pfad).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user