Files
factorio-clusterio/test/host/export.js
T
bbassieandClaude Opus 5.5 5238a0fbb3 Report missing base game icons when exporting from headless Factorio
The headless build has no graphics, so export-data found none of the
base game's icon files. Plain icons were skipped silently and layered
icons were drawn blank and counted as exported.

Skip a layered icon when any of its layers is missing, and log one
warning with the number of base game icon files that were not found.
Add host.factorio_graphics_directory to read builtin mod graphics from
a full install when the instance runs the headless build.

Fixes #1068

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 00:02:52 +00:00

116 lines
4.2 KiB
JavaScript

import assert from "node:assert/strict";
import path from "node:path";
import { FactorioServer } from "@clusterio/host/dist/node/src/server.js";
import {
_exportLocale, _loadLayeredIcon, _loadSimpleIcon, _warnMissingBuiltinIcons,
} from "@clusterio/host/dist/node/src/export.js";
describe("host/src/export", function() {
let testServer;
before(async function() {
let writePath = path.join("temp", "test", "server");
testServer = await FactorioServer.fromOptions(path.join("test", "file", "factorio"), writePath, {});
});
describe("exportLocale()", function() {
it("returns a flattened mapping with locale definitions", async function() {
let locale = await _exportLocale(testServer, new Map(), new Map(), ["base"], "en");
assert.deepEqual(locale, new Map([
["test.core-a", "1"], ["test.core-b", "2"],
["test.base-a", "1"], ["test.base-b", "2"],
]));
});
it("returns a filtered locale definitions based on mod order", async function() {
let locale = await _exportLocale(testServer, new Map(), new Map(), [], "en");
assert.deepEqual(locale, new Map([
["test.core-a", "1"], ["test.core-b", "2"],
]));
});
});
// test-icon.png is a red 64px icon followed by a blue 32px mipmap
const red = 0xff0000ff;
// Stands in for the data dir of a full install
const graphicsDataDir = path.join("test", "file", "factorio-graphics");
describe("loadSimpleIcon()", function() {
it("defaults icon_size to 64", async function() {
const icon = await _loadSimpleIcon(
testServer, new Map(), { zipCache: new Map() },
{ icon: "__base__/graphics/icons/test-icon.png" }, 32, new Map(),
);
assert.equal(icon.bitmap.width, 32);
assert.equal(icon.bitmap.height, 32);
assert.equal(icon.getPixelColor(31, 16), red);
});
it("returns null when the icon file is missing", async function() {
const icon = await _loadSimpleIcon(
testServer, new Map(), { zipCache: new Map() },
{ icon: "__base__/graphics/icons/graphics-icon.png" }, 32, new Map(),
);
assert.equal(icon, null);
});
it("falls back to graphicsDataDir for builtin mod files", async function() {
const icon = await _loadSimpleIcon(
testServer, new Map(), { zipCache: new Map(), graphicsDataDir },
{ icon: "__base__/graphics/icons/graphics-icon.png" }, 32, new Map(),
);
assert.equal(icon.getPixelColor(31, 16), red);
});
});
describe("loadLayeredIcon()", function() {
it("defaults icon_size to 64", async function() {
const icon = await _loadLayeredIcon(
testServer, new Map(), { zipCache: new Map() },
{ icons: [{ icon: "__base__/graphics/icons/test-icon.png" }] }, 32, new Map(),
);
assert.equal(icon.bitmap.width, 32);
assert.equal(icon.bitmap.height, 32);
assert.equal(icon.getPixelColor(0, 0), red);
assert.equal(icon.getPixelColor(31, 31), red);
});
it("returns null when a layer is missing", async function() {
const icon = await _loadLayeredIcon(
testServer, new Map(), { zipCache: new Map() },
{ icons: [
{ icon: "__base__/graphics/icons/test-icon.png" },
{ icon: "__base__/graphics/icons/missing.png" },
] }, 32, new Map(),
);
assert.equal(icon, null);
});
});
describe("warnMissingBuiltinIcons()", function() {
let warnings;
let logger;
beforeEach(function() {
warnings = [];
logger = testServer._logger;
testServer._logger = { warn: msg => warnings.push(msg) };
});
afterEach(function() {
testServer._logger = logger;
});
it("warns once with the number of missing builtin files", function() {
_warnMissingBuiltinIcons(testServer, { zipCache: new Map() }, new Map([
["__base__/graphics/icons/a.png", null],
["__core__/graphics/icons/b.png", null],
["__base__/graphics/icons/c.png", {}],
["__some_mod__/graphics/icons/d.png", null],
]));
assert.equal(warnings.length, 1);
assert.match(warnings[0], /^2 icon files of the base game were not found/);
assert.match(warnings[0], /headless/);
});
it("does not warn when no builtin files are missing", function() {
_warnMissingBuiltinIcons(testServer, { zipCache: new Map() }, new Map([
["__base__/graphics/icons/c.png", {}],
["__some_mod__/graphics/icons/d.png", null],
]));
assert.deepEqual(warnings, []);
});
});
});