import { test } from 'node:test'; import assert from 'node:assert/strict'; import { join } from 'node:path'; import { mkdirSync, writeFileSync, rmSync, mkdtempSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { deflateSync } from 'node:zlib'; import { checkSkin } from '../check-skin.mjs'; function crc32(buf) { let c = ~0; for (const x of buf) { c ^= x; for (let i = 0; i < 8; i++) c = (c >>> 1) ^ (0xEDB88320 & -(c & 1)); } return ~c >>> 0; } function chunk(type, data) { const len = Buffer.alloc(4); len.writeUInt32BE(data.length); const td = Buffer.concat([Buffer.from(type, 'ascii'), data]); const c = Buffer.alloc(4); c.writeUInt32BE(crc32(td)); return Buffer.concat([len, td, c]); } function makePng(w, h) { const ihdr = Buffer.alloc(13); ihdr.writeUInt32BE(w, 0); ihdr.writeUInt32BE(h, 4); ihdr[8] = 8; ihdr[9] = 2; const raw = Buffer.alloc(h * (1 + w * 3)); return Buffer.concat([ Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]), chunk('IHDR', ihdr), chunk('IDAT', deflateSync(raw)), chunk('IEND', Buffer.alloc(0)), ]); } function put(dir, rel, content) { const p = join(dir, ...rel.split('/')); mkdirSync(join(p, '..'), { recursive: true }); writeFileSync(p, content); } function scaffold() { const root = mkdtempSync(join(tmpdir(), 'youle-checkskin-')); const frameworkUiDir = join(root, 'ui'); const gamesDir = join(root, 'games'); mkdirSync(frameworkUiDir, { recursive: true }); mkdirSync(join(gamesDir, 'demo', 'assets', 'game', 'override'), { recursive: true }); put(frameworkUiDir, 'atlas-hall/btn.png', makePng(64, 64)); put(frameworkUiDir, 'atlas-hall/bg.png', makePng(64, 64)); return { root, frameworkUiDir, gamesDir, overrideDir: join(gamesDir, 'demo', 'assets', 'game', 'override') }; } test('checkSkin 统计命中数与框架资源总数', () => { const s = scaffold(); try { put(s.overrideDir, 'atlas-hall/btn.png', makePng(64, 64)); const r = checkSkin('demo', { gamesDir: s.gamesDir, frameworkUiDir: s.frameworkUiDir }); assert.deepEqual(r.errors, []); assert.deepEqual(r.matched, ['atlas-hall/btn.png']); assert.equal(r.frameworkTotal, 2); } finally { rmSync(s.root, { recursive: true, force: true }); } }); test('checkSkin 报出孤儿 override', () => { const s = scaffold(); try { put(s.overrideDir, 'atlas-hall/nope.png', makePng(64, 64)); const r = checkSkin('demo', { gamesDir: s.gamesDir, frameworkUiDir: s.frameworkUiDir }); assert.equal(r.errors.length, 1); assert.equal(r.errors[0].code, 'orphan'); } finally { rmSync(s.root, { recursive: true, force: true }); } }); test('checkSkin 对不存在的子游戏抛错', () => { const s = scaffold(); try { assert.throws( () => checkSkin('nosuch', { gamesDir: s.gamesDir, frameworkUiDir: s.frameworkUiDir }), /找不到子游戏工程/, ); } finally { rmSync(s.root, { recursive: true, force: true }); } }); test('checkSkin 对没有 override 目录的子游戏返回零命中而非报错', () => { const s = scaffold(); try { rmSync(s.overrideDir, { recursive: true, force: true }); const r = checkSkin('demo', { gamesDir: s.gamesDir, frameworkUiDir: s.frameworkUiDir }); assert.deepEqual(r.matched, []); assert.deepEqual(r.errors, []); } finally { rmSync(s.root, { recursive: true, force: true }); } });