waybetter-for-x/apps/desktop/tests/storeIconPacks.test.ts
2026-10-02 02:54:04 +07:00

63 lines
3.5 KiB
TypeScript

// The icon packs of the Store (store/x/icons/): the four the app bundles are published under the same
// ids as the same files, the two retro packs are our own drawings and cover X's left rail and action
// bar, and every pack the Store lists parses with the app's own parser.
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import test from 'node:test';
import { ICON_LIMITS, compileIcons, parseIconPackJson, type IconName } from '../../../waybetter/packages/runtime/src/themeIcons.ts';
// @ts-expect-error: plain ES module shared with scripts/check-store.mjs
import { STORE_ICON_IDS, iconsPreviewSvg, packFile } from '../../../scripts/build-store-icons.mjs';
import { BUILTIN_PACKS } from '../src/inject/packs/index.ts';
import { RETRO_PACKS } from '../src/inject/packs/retro.ts';
import { X_ICONS } from '../src/inject/themeIcons.ts';
const ROOT = join(import.meta.dirname, '../../..');
const read = (id: string, name: string) => readFileSync(join(ROOT, 'store/x/icons', id, name), 'utf8');
test('every bundled pack is in the Store under its own id, as the same file', () => {
for (const pack of BUILTIN_PACKS) {
assert.ok(STORE_ICON_IDS.includes(pack.id), `${pack.id} is a Store item`);
assert.equal(read(pack.id, `${pack.id}.icons.json`), packFile(pack.id), `${pack.id}: run node scripts/build-store-icons.mjs`);
assert.equal(read(pack.id, 'preview.svg'), iconsPreviewSvg(pack), `${pack.id}: the preview is current`);
}
});
test('every Store icon pack parses with the app parser, keeps all its glyphs and is within the limits', () => {
for (const id of STORE_ICON_IDS as string[]) {
const json = read(id, `${id}.icons.json`);
assert.ok(json.length <= ICON_LIMITS.packBytes, `${id}: at most 120 KB`);
const parsed = parseIconPackJson(json);
assert.ok(!('error' in parsed), `${id} parses`);
const { pack, warnings } = parsed as Exclude<typeof parsed, { error: string }>;
assert.equal(pack.id, id);
assert.deepEqual(warnings, [], `${id}: nothing is dropped, the license is named`);
assert.equal(pack.license, 'own');
const item = JSON.parse(read(id, 'item.json'));
assert.equal(item.category, 'icons');
assert.ok(item.license && item.credits, `${id}: item.json names the license and the credits`);
}
});
test('the retro packs draw X\'s whole left rail and action bar, and the sheet carries every one of them', () => {
const needed = Object.keys(X_ICONS).filter(name => ['nav', 'actions'].includes(X_ICONS[name as IconName]!.group)) as IconName[];
for (const pack of RETRO_PACKS) {
for (const name of needed) assert.ok(pack.glyphs[name], `${pack.id} draws ${name}`);
const css = compileIcons(pack, null, X_ICONS, { allowed: true, colors: null });
for (const name of needed) assert.ok(css.includes(X_ICONS[name]!.ctx!.split(',')[0]!), `${pack.id}: the sheet has a rule for ${name}`);
assert.doesNotMatch(css, /<script|javascript:|@import/i);
}
});
test('the retro packs are colored images: nothing needs the text color, so a dark or light theme changes nothing', () => {
for (const pack of RETRO_PACKS) {
for (const [name, glyph] of Object.entries(pack.glyphs)) assert.ok('layers' in glyph!, `${pack.id}.${name} is layers`);
}
});
test('no retro pack names an outside source for its drawings', () => {
for (const pack of RETRO_PACKS) {
const text = JSON.stringify(pack) + read(pack.id, 'item.json');
assert.doesNotMatch(text, /shell32|iconarchive|win98icons|msn1999/i, `${pack.id}: our own drawings only`);
}
});