feat(icons): render the app icons from the Movie Loop mark
frontend-ci / secrets-scan (push) Successful in 4s
frontend-ci / sast (push) Successful in 7s
frontend-ci / fs-scan (push) Successful in 14s
frontend-ci / typecheck (push) Successful in 24s
frontend-ci / lint (push) Successful in 26s
frontend-ci / test (push) Successful in 32s
frontend-ci / build (push) Successful in 38s
frontend-ci / push (push) Skipped
frontend-ci / lint (pull_request) Successful in 24s
frontend-ci / test (pull_request) Successful in 31s
frontend-ci / secrets-scan (pull_request) Successful in 5s
frontend-ci / sast (pull_request) Successful in 8s
frontend-ci / fs-scan (pull_request) Successful in 15s
frontend-ci / typecheck (pull_request) Successful in 14s
frontend-ci / build (pull_request) Successful in 38s
frontend-ci / push (pull_request) Skipped
frontend-ci / secrets-scan (push) Successful in 4s
frontend-ci / sast (push) Successful in 7s
frontend-ci / fs-scan (push) Successful in 14s
frontend-ci / typecheck (push) Successful in 24s
frontend-ci / lint (push) Successful in 26s
frontend-ci / test (push) Successful in 32s
frontend-ci / build (push) Successful in 38s
frontend-ci / push (push) Skipped
frontend-ci / lint (pull_request) Successful in 24s
frontend-ci / test (pull_request) Successful in 31s
frontend-ci / secrets-scan (pull_request) Successful in 5s
frontend-ci / sast (pull_request) Successful in 8s
frontend-ci / fs-scan (pull_request) Successful in 15s
frontend-ci / typecheck (pull_request) Successful in 14s
frontend-ci / build (pull_request) Successful in 38s
frontend-ci / push (pull_request) Skipped
Safari, the iOS home screen, Android install and push notifications still showed the old film-reel artwork beside the new theme colour. The PNGs are now rendered from the SVG mark by `npm run icons` (Inkscape, no image library in node_modules): the bare mark for the 32px favicon, the mark on the stage colour for app icons, and a maskable variant with the extra margin Android's adaptive crop needs.
This commit is contained in:
1 parent
8d0b7f39ee
commit
ef7b8aa878
14 files changed
+213
-4
No files matched your search
@@ -0,0 +1,96 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
ICONS,
|
||||
INKSCAPE_CANDIDATES,
|
||||
exportArgs,
|
||||
findInkscape,
|
||||
makeIcons,
|
||||
} from './make-icons.mjs';
|
||||
|
||||
/**
|
||||
* The PNG icons are rendered from SVG by Inkscape rather than checked in by
|
||||
* hand, so the manifest, the service worker and the HTML can keep pointing at
|
||||
* the same filenames while the mark changes. These tests pin the plan (which
|
||||
* file comes from which source, at what size) and the Inkscape invocation;
|
||||
* they never run Inkscape.
|
||||
*/
|
||||
|
||||
const ROOT = resolve(__dirname, '..');
|
||||
|
||||
describe('ICONS', () => {
|
||||
it('covers every raster icon the app references, from a source that exists', () => {
|
||||
const outs = ICONS.map((i) => i.out);
|
||||
expect(outs).toEqual(
|
||||
expect.arrayContaining([
|
||||
'public/favicon-32.png',
|
||||
'public/apple-touch-icon.png',
|
||||
'public/icon-192.png',
|
||||
'public/icon-512.png',
|
||||
'public/icon-512-maskable.png',
|
||||
]),
|
||||
);
|
||||
for (const icon of ICONS) {
|
||||
expect(existsSync(resolve(ROOT, icon.source)), icon.source).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('renders the tab favicon from the bare mark and the app icons from the opaque source', () => {
|
||||
const favicon = ICONS.find((i) => i.out === 'public/favicon-32.png');
|
||||
expect(favicon?.source).toBe('public/favicon.svg');
|
||||
expect(favicon?.size).toBe(32);
|
||||
for (const out of ['public/apple-touch-icon.png', 'public/icon-192.png', 'public/icon-512.png']) {
|
||||
expect(ICONS.find((i) => i.out === out)?.source).toBe('scripts/icons/app-icon.svg');
|
||||
}
|
||||
expect(ICONS.find((i) => i.out === 'public/icon-512-maskable.png')?.source).toBe(
|
||||
'scripts/icons/app-icon-maskable.svg',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findInkscape', () => {
|
||||
it('prefers an explicit INKSCAPE variable', () => {
|
||||
expect(findInkscape({ INKSCAPE: 'D:\\tools\\inkscape.com' }, () => true)).toBe(
|
||||
'D:\\tools\\inkscape.com',
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to the first standard install location that exists', () => {
|
||||
const second = INKSCAPE_CANDIDATES[1]!;
|
||||
expect(findInkscape({}, (p: string) => p === second)).toBe(second);
|
||||
});
|
||||
|
||||
it('falls back to PATH lookup when nothing is installed in a standard place', () => {
|
||||
expect(findInkscape({}, () => false)).toBe('inkscape');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exportArgs', () => {
|
||||
it('builds an Inkscape 1.x PNG export at the requested square size', () => {
|
||||
expect(exportArgs({ source: 'in.svg', out: 'out.png', size: 180 })).toEqual([
|
||||
'in.svg',
|
||||
'--export-type=png',
|
||||
'--export-filename=out.png',
|
||||
'--export-width=180',
|
||||
'--export-height=180',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeIcons', () => {
|
||||
it('runs one export per icon, with paths resolved against the project root', () => {
|
||||
const run = vi.fn();
|
||||
const log = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
try {
|
||||
makeIcons(ROOT, run);
|
||||
} finally {
|
||||
log.mockRestore();
|
||||
}
|
||||
expect(run).toHaveBeenCalledTimes(ICONS.length);
|
||||
const [, args, opts] = run.mock.calls[0]!;
|
||||
expect(args[0]).toBe(resolve(ROOT, ICONS[0]!.source));
|
||||
expect(args).toContain(`--export-filename=${resolve(ROOT, ICONS[0]!.out)}`);
|
||||
expect(opts).toEqual({ stdio: 'inherit' });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user