Manual changes following smoke test

This commit is contained in:
Cooldude2606
2026-09-01 01:04:09 +01:00
parent 574a0a4b52
commit 59df8bdff3
18 changed files with 191 additions and 98 deletions
+2 -1
View File
@@ -8,7 +8,8 @@
"main": "dist/node/index.js", "main": "dist/node/index.js",
"scripts": { "scripts": {
"prepare": "tsc --build && webpack-cli --env production", "prepare": "tsc --build && webpack-cli --env production",
"test": "tap --disable-coverage --allow-empty-coverage test/*.test.js" "test": "tap --disable-coverage --allow-empty-coverage test/*.test.js",
"coverage": "tap --coverage-report=text test/*.test.js"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
+31
View File
@@ -20,6 +20,12 @@ lib.Link.register(messages.AssignmentUpdateRequest);
const logger = { child: () => logger, info: () => {}, warn: () => {}, error: () => {}, verbose: () => {} }; const logger = { child: () => logger, info: () => {}, warn: () => {}, error: () => {}, verbose: () => {} };
// Silence the singleton logger to avoid polluting the test output
lib.logger.silent = true;
t.after(() => {
lib.logger.silent = false;
});
/** Build a plugin around a real controller, which is side effect free while not started. */ /** Build a plugin around a real controller, which is side effect free while not started. */
async function startPlugin(t2, { roles = [] } = {}) { async function startPlugin(t2, { roles = [] } = {}) {
const controllerConfig = new lib.ControllerConfig("controller", { const controllerConfig = new lib.ControllerConfig("controller", {
@@ -173,6 +179,18 @@ t.test("class ControllerPlugin", t2 => {
t3.strictSame(applied.length, 1, "other player events do not"); t3.strictSame(applied.length, 1, "other player events do not");
}); });
t2.test(".handleRoleListRequest() returns the current roles", async t3 => {
const { plugin } = await startPlugin(t3, {
roles: [role(1, "Player"), role(5, "Moderator")],
});
plugin.roleMeta.set(new messages.RoleMetaRecord(5, 2, 0, "Mod"));
const response = await plugin.handleRoleListRequest(new messages.RoleListRequest());
t3.strictSame(response.length, 2, "the current roles are returned");
t3.ok(response.some(record => record.name === "Moderator"));
t3.ok(response.some(record => record.name === "Player"));
});
t2.test(".handleRoleMetaUpdateRequest() validates the role", async t3 => { t2.test(".handleRoleMetaUpdateRequest() validates the role", async t3 => {
const { plugin } = await startPlugin(t3, { roles: [role(1, "Player"), role(5, "Moderator")] }); const { plugin } = await startPlugin(t3, { roles: [role(1, "Player"), role(5, "Moderator")] });
@@ -187,6 +205,19 @@ t.test("class ControllerPlugin", t2 => {
t3.strictSame(plugin.roleMeta.get(5).shortHand, "Mod", "the properties are stored"); t3.strictSame(plugin.roleMeta.get(5).shortHand, "Mod", "the properties are stored");
}); });
t2.test(".handleAssignmentListRequest() returns the current assignments", async t3 => {
const { plugin, controller } = await startPlugin(t3, {
roles: [role(1, "Player"), role(5, "Moderator")],
});
controller.users.createUser("alice").set("roleIds", new Set([5]));
controller.users.createUser("bob");
const response = await plugin.handleAssignmentListRequest(new messages.AssignmentListRequest());
t3.strictSame(response.length, 1, "the current assignments are returned");
t3.strictSame(response[0].name, "alice");
t3.strictSame(response[0].roleIds, new Set([5]), "the default role is not listed");
});
t2.test(".handleAssignmentSubscription() replays only newer records", async t3 => { t2.test(".handleAssignmentSubscription() replays only newer records", async t3 => {
const { plugin, controller } = await startPlugin(t3, { roles: [role(1, "Player"), role(5, "Moderator")] }); const { plugin, controller } = await startPlugin(t3, { roles: [role(1, "Player"), role(5, "Moderator")] });
controller.users.createUser("alice").set("roleIds", new Set([5])); controller.users.createUser("alice").set("roleIds", new Set([5]));
+43 -37
View File
@@ -7,18 +7,18 @@ const fullMeta = new messages.RoleMetaRecord(
7, 3, 1, "Mod", "[Mod]", new messages.RoleColor(1, 2, 3), 3600000, true, 12345, false, 7, 3, 1, "Mod", "[Mod]", new messages.RoleColor(1, 2, 3), 3600000, true, 12345, false,
); );
t.test("class RoleColor", subtest => { t.test("class RoleColor", t2 => {
testRoundTripJsonSerialisable(messages.RoleColor, testMatrix( testRoundTripJsonSerialisable(t2, messages.RoleColor, testMatrix(
[0, 255], // r [0, 255], // r
[0, 128], // g [0, 128], // g
[0, 1], // b [0, 1], // b
)); ));
subtest.pass("round trips");
subtest.end(); t2.end();
}); });
t.test("class RoleMetaRecord", subtest => { t.test("class RoleMetaRecord", t2 => {
testRoundTripJsonSerialisable(messages.RoleMetaRecord, testMatrix( testRoundTripJsonSerialisable(t2, messages.RoleMetaRecord, testMatrix(
[7], // id [7], // id
[3], // order [3], // order
[0, 1], // priority [0, 1], // priority
@@ -30,12 +30,12 @@ t.test("class RoleMetaRecord", subtest => {
[0, 12345], // updatedAtMs [0, 12345], // updatedAtMs
[false, true], // isDeleted [false, true], // isDeleted
)); ));
subtest.pass("round trips");
subtest.end(); t2.end();
}); });
t.test("class RoleRecord", subtest => { t.test("class RoleRecord", t2 => {
testRoundTripJsonSerialisable(messages.RoleRecord, testMatrix( testRoundTripJsonSerialisable(t2, messages.RoleRecord, testMatrix(
[7], // id [7], // id
["Moderator"], // name ["Moderator"], // name
[[], ["a.b", "c.d"]], // permissions [[], ["a.b", "c.d"]], // permissions
@@ -44,67 +44,73 @@ t.test("class RoleRecord", subtest => {
[0, 12345], // updatedAtMs [0, 12345], // updatedAtMs
[false, true], // isDeleted [false, true], // isDeleted
)); ));
subtest.pass("round trips");
subtest.end(); t2.end();
}); });
t.test("class AssignmentRecord", subtest => { t.test("class AssignmentRecord", t2 => {
testRoundTripJsonSerialisable(messages.AssignmentRecord, testMatrix( testRoundTripJsonSerialisable(t2, messages.AssignmentRecord, testMatrix(
["alice"], // name ["alice"], // name
[new Set(), new Set([5, 6])], // roleIds [new Set(), new Set([5, 6])], // roleIds
[0, 12345], // updatedAtMs [0, 12345], // updatedAtMs
[false, true], // isDeleted [false, true], // isDeleted
)); ));
subtest.pass("round trips");
subtest.end(); t2.test("get id()", t3 => {
const record = new messages.AssignmentRecord("alice", new Set([5]), 12345);
t3.equal(record.id, "alice");
t3.end();
});
t2.end();
}); });
const sampleRecord = new messages.RoleRecord(7, "Moderator", ["a.b"], fullMeta, true, 12345); const sampleRecord = new messages.RoleRecord(7, "Moderator", ["a.b"], fullMeta, true, 12345);
const sampleAssignment = new messages.AssignmentRecord("alice", new Set([5]), 12345); const sampleAssignment = new messages.AssignmentRecord("alice", new Set([5]), 12345);
t.test("class RoleUpdatedEvent", subtest => { t.test("class RoleUpdatedEvent", t2 => {
testRoundTripJsonSerialisable(messages.RoleUpdatedEvent, testMatrix( testRoundTripJsonSerialisable(t2, messages.RoleUpdatedEvent, testMatrix(
[[], [sampleRecord]], // updates [[], [sampleRecord]], // updates
)); ));
subtest.pass("round trips");
subtest.end(); t2.end();
}); });
t.test("class AssignmentUpdatedEvent", subtest => { t.test("class AssignmentUpdatedEvent", t2 => {
testRoundTripJsonSerialisable(messages.AssignmentUpdatedEvent, testMatrix( testRoundTripJsonSerialisable(t2, messages.AssignmentUpdatedEvent, testMatrix(
[[], [sampleAssignment]], // updates [[], [sampleAssignment]], // updates
)); ));
subtest.pass("round trips");
subtest.end(); t2.end();
}); });
t.test("class RoleMetaUpdateRequest", subtest => { t.test("class RoleMetaUpdateRequest", t2 => {
testRoundTripJsonSerialisable(messages.RoleMetaUpdateRequest, testMatrix( testRoundTripJsonSerialisable(t2, messages.RoleMetaUpdateRequest, testMatrix(
[new messages.RoleMetaRecord(7, 3), fullMeta], // meta [new messages.RoleMetaRecord(7, 3), fullMeta], // meta
)); ));
subtest.pass("round trips");
subtest.end(); t2.end();
}); });
t.test("class AssignmentUpdateRequest", subtest => { t.test("class AssignmentUpdateRequest", t2 => {
testRoundTripJsonSerialisable(messages.AssignmentUpdateRequest, testMatrix( testRoundTripJsonSerialisable(t2, messages.AssignmentUpdateRequest, testMatrix(
["alice"], // name ["alice"], // name
[[], [5]], // assign [[], [5]], // assign
[[], [6]], // unassign [[], [6]], // unassign
)); ));
subtest.pass("round trips");
subtest.end(); t2.end();
}); });
t.test("encodeRolesForLua() sends each permission name once", subtest => { t.test("encodeRolesForLua() sends each permission name once", t2 => {
const meta = id => new messages.RoleMetaRecord(id, id); const meta = id => new messages.RoleMetaRecord(id, id);
const encoded = messages.encodeRolesForLua([ const encoded = messages.encodeRolesForLua([
new messages.RoleRecord(1, "A", ["p.one", "p.two"], meta(1)), new messages.RoleRecord(1, "A", ["p.one", "p.two"], meta(1)),
new messages.RoleRecord(2, "B", ["p.two", "p.three"], meta(2)), new messages.RoleRecord(2, "B", ["p.two", "p.three"], meta(2)),
]); ]);
subtest.strictSame(encoded.permission_names, ["p.one", "p.two", "p.three"], "names are deduplicated"); t2.strictSame(encoded.permission_names, ["p.one", "p.two", "p.three"], "names are deduplicated");
subtest.strictSame(encoded.roles[0].permissions, [0, 1], "indexes are zero based"); t2.strictSame(encoded.roles[0].permissions, [0, 1], "indexes are zero based");
subtest.strictSame(encoded.roles[1].permissions, [1, 2], "shared names reuse their index"); t2.strictSame(encoded.roles[1].permissions, [1, 2], "shared names reuse their index");
subtest.end(); t2.end();
}); });
@@ -3,12 +3,12 @@ const path = require("node:path");
const t = require("tap"); const t = require("tap");
const { reportLuaTests } = require("../../test/lua/runner"); const { reportLuaTests } = require("../../test/lua/runner");
const envFile = path.join(__dirname, "lua", "env.lua"); const envFile = path.join(__dirname, "module", "env.lua");
// Each file runs in its own lua state, and each test in a fresh environment // Each file runs in its own lua state, and each test in a fresh environment
t.test("module/control.lua", t2 => { t.test("control.lua", t2 => {
for (const file of ["lookup.lua", "players.lua", "assignment.lua", "sync.lua", "holders.lua"]) { for (const file of ["lookup.lua", "players.lua", "assignment.lua", "sync.lua", "holders.lua"]) {
t2.test(`lua/${file}`, subtest => reportLuaTests(subtest, envFile, path.join(__dirname, "lua", file))); t2.test(file, t3 => reportLuaTests(t3, envFile, path.join(__dirname, "module", file)));
} }
t2.end(); t2.end();
}); });
@@ -1,5 +1,5 @@
--- Tests for the assignment methods of module/control.lua --- Tests for the assignment methods of module/control.lua
local Suite = ... --- The suite this file adds its tests to, see test/lua/framework.lua local Suite = ... --- @type Suite The suite this file adds its tests to, see env.lua
local test, check, eq, empty = Suite.test, Suite.check, Suite.eq, Suite.empty local test, check, eq, empty = Suite.test, Suite.check, Suite.eq, Suite.empty
--- alice is a moderator and bob a regular, with changes sent to the controller --- alice is a moderator and bob a regular, with changes sent to the controller
@@ -8,10 +8,10 @@ returned here becomes `...` in each test file.
]] ]]
local shared_root = ... --- @type string local shared_root = ... --- @type string
local source = assert(debug.getinfo(1, "S")).source local source = assert(debug.getinfo(1, "S")).source:gsub("\\", "/")
local plugin_root = assert(source:match("^@(.*)/test/lua/env%.lua$")) local plugin_root = assert(source:match("^@(.*)/test/module/env%.lua$"))
local Framework = assert(loadfile(shared_root .. "/framework.lua"))() local Framework = assert(loadfile(shared_root .. "/framework.lua"))() --- @type Framework
--- Standard fixture: permission names sent once and referenced by zero based index --- Standard fixture: permission names sent once and referenced by zero based index
local permission_names = { local permission_names = {
@@ -48,7 +48,7 @@ for _, record in ipairs(role_records()) do
end end
return Framework.suite(function(env) return Framework.suite(function(env)
env.Roles = assert(loadfile(plugin_root .. "/module/control.lua"))() env.Roles = assert(loadfile(plugin_root .. "/module/control.lua"))() --- @type ExpRoles
env.Roles.on_server_startup() env.Roles.on_server_startup()
--- Get a fixture role by name, failing the test when it does not exist --- Get a fixture role by name, failing the test when it does not exist
@@ -1,5 +1,5 @@
--- Tests for listing and printing to the players who hold a role --- Tests for listing and printing to the players who hold a role
local Suite = ... --- The suite this file adds its tests to, see test/lua/framework.lua local Suite = ... --- @type Suite The suite this file adds its tests to, see test/lua/framework.lua
local test, check, eq = Suite.test, Suite.check, Suite.eq local test, check, eq = Suite.test, Suite.check, Suite.eq
--- alice and dave are moderators with dave offline, zed has never joined --- alice and dave are moderators with dave offline, zed has never joined
@@ -1,5 +1,5 @@
--- Tests for the role lookup functions of module/control.lua --- Tests for the role lookup functions of module/control.lua
local Suite = ... --- The suite this file adds its tests to, see test/lua/framework.lua local Suite = ... --- @type Suite The suite this file adds its tests to, see test/lua/framework.lua
local test, check, eq = Suite.test, Suite.check, Suite.eq local test, check, eq = Suite.test, Suite.check, Suite.eq
test("get_role() returns the role with the given clusterio id", function(env) test("get_role() returns the role with the given clusterio id", function(env)
@@ -1,5 +1,5 @@
--- Tests for the player checks of module/control.lua --- Tests for the player checks of module/control.lua
local Suite = ... --- The suite this file adds its tests to, see test/lua/framework.lua local Suite = ... --- @type Suite The suite this file adds its tests to, see test/lua/framework.lua
local test, check, eq = Suite.test, Suite.check, Suite.eq local test, check, eq = Suite.test, Suite.check, Suite.eq
--- alice is a moderator, bob is a regular, and carol has only the default role --- alice is a moderator, bob is a regular, and carol has only the default role
@@ -1,5 +1,5 @@
--- Tests for the sync entry points of module/control.lua --- Tests for the sync entry points of module/control.lua
local Suite = ... --- The suite this file adds its tests to, see test/lua/framework.lua local Suite = ... --- @type Suite The suite this file adds its tests to, see test/lua/framework.lua
local test, check, eq, empty = Suite.test, Suite.check, Suite.eq, Suite.empty local test, check, eq, empty = Suite.test, Suite.check, Suite.eq, Suite.empty
--- alice is a moderator and carol has only the default role, both connected --- alice is a moderator and carol has only the default role, both connected
+37 -15
View File
@@ -6,35 +6,57 @@ const { seedRoles, flattenSeedPermissions } = require("../dist/node/seed");
// Importing this defines the exp_scenario permissions the seed grants // Importing this defines the exp_scenario permissions the seed grants
require("@expcluster/scenario/dist/node/permissions"); require("@expcluster/scenario/dist/node/permissions");
t.test("seedRoles grant only defined permissions", subtest => { t.test("seedRoles[] grant only defined permissions", t2 => {
for (const role of seedRoles) { for (const role of seedRoles) {
for (const permission of flattenSeedPermissions(role)) { for (const permission of role.permissions) {
subtest.ok(lib.permissions.has(permission), `${role.name} grants defined permission ${permission}`); t2.ok(lib.permissions.has(permission), `${role.name} grants defined permission ${permission}`);
} }
} }
subtest.end(); t2.end();
}); });
t.test("flattenSeedPermissions() carries parents into their children", subtest => { t.test("seedRoles[] are unique with one default and one admin", t2 => {
const names = seedRoles.map(role => role.name);
t2.strictSame(names.length, new Set(names).size, "names are unique");
t2.strictSame(seedRoles.filter(role => role.isDefault).length, 1, "one default role");
t2.strictSame(seedRoles.filter(role => role.isAdmin).length, 1, "one admin role");
t2.end();
});
t.test("flattenSeedPermissions() inherits permissions from parent roles", t2 => {
const byName = new Map(seedRoles.map(role => [role.name, role])); const byName = new Map(seedRoles.map(role => [role.name, role]));
for (const role of seedRoles) { for (const role of seedRoles) {
if (role.parent === undefined) { if (role.parent === undefined) {
continue; continue;
} }
const parent = byName.get(role.parent); const parent = byName.get(role.parent);
subtest.ok(parent, `${role.name} has parent ${role.parent}`); t2.ok(parent, `${role.name} has parent ${role.parent}`);
const flattened = flattenSeedPermissions(role); const flattened = flattenSeedPermissions(role);
for (const permission of flattenSeedPermissions(parent)) { for (const permission of flattenSeedPermissions(parent)) {
subtest.ok(flattened.has(permission), `${role.name} inherits ${permission}`); t2.ok(flattened.has(permission), `${role.name} inherits ${permission}`);
} }
} }
subtest.end(); t2.end();
}); });
t.test("seedRoles are unique with one default and one admin", subtest => { t.test("flattenSeedPermissions() does not inherit permissions when parent is undefined", t2 => {
const names = seedRoles.map(role => role.name); for (const role of seedRoles) {
subtest.strictSame(names.length, new Set(names).size, "names are unique"); if (role.parent !== undefined) {
subtest.strictSame(seedRoles.filter(role => role.isDefault).length, 1, "one default role"); continue;
subtest.strictSame(seedRoles.filter(role => role.isAdmin).length, 1, "one admin role"); }
subtest.end();
}); const flattened = flattenSeedPermissions(role);
for (const permission of role.permissions) {
t2.ok(flattened.has(permission), `${role.name} contains ${permission}`);
}
t2.equal(
flattened.size,
role.permissions.length,
`${role.name} has no inherited permissions`,
);
}
t2.end();
});
@@ -19,7 +19,7 @@ local function check_items(player, type, banned_items)
local inventory = assert(player.get_inventory(type)) local inventory = assert(player.get_inventory(type))
-- Check what items the player has -- Check what items the player has
for i = 1, #inventory do for i = 1, #inventory do
local item = inventory[i] local item = inventory[i --[[@as uint]]]
if item.valid_for_read and banned_items[item.name] then if item.valid_for_read and banned_items[item.name] then
player.print{ "exp_nuke-protection.chat-found", item.prototype.localised_name } player.print{ "exp_nuke-protection.chat-found", item.prototype.localised_name }
items[#items + 1] = item items[#items + 1] = item
+35 -18
View File
@@ -5,8 +5,8 @@ Adds a custom spawn area with chests and afk turrets
local config = require("modules.exp_legacy.config.spawn_area") local config = require("modules.exp_legacy.config.spawn_area")
--- Apply an offset to a LuaPosition --- Apply an offset to a LuaPosition
--- @param position MapPosition --- @param position MapPosition.struct
--- @param offset MapPosition --- @param offset MapPosition.struct
--- @return MapPosition.struct --- @return MapPosition.struct
local function apply_offset(position, offset) local function apply_offset(position, offset)
return { return {
@@ -17,7 +17,7 @@ end
--- Apply offset to an array of positions --- Apply offset to an array of positions
--- @param positions table --- @param positions table
--- @param offset MapPosition --- @param offset MapPosition.struct
--- @param x_index number --- @param x_index number
--- @param y_index number --- @param y_index number
local function apply_offset_to_array(positions, offset, x_index, y_index) local function apply_offset_to_array(positions, offset, x_index, y_index)
@@ -97,14 +97,19 @@ local belt_details = {
--- Makes a 2x2 afk belt at the locations in the config --- Makes a 2x2 afk belt at the locations in the config
--- @param surface LuaSurface --- @param surface LuaSurface
--- @param offset MapPosition --- @param offset MapPosition.struct
local function create_belts(surface, offset) local function create_belts(surface, offset)
local belt_type = config.afk_belts.belt_type local belt_type = config.afk_belts.belt_type
local entity_pos = { x = 0, y = 0 }
local group_offset = { x = 0, y = 0 }
for _, position in pairs(config.afk_belts.locations) do for _, position in pairs(config.afk_belts.locations) do
position = apply_offset(position, offset) group_offset.x = position[1] + offset.x
group_offset.y = position[2] + offset.y
for _, belt in pairs(belt_details) do for _, belt in pairs(belt_details) do
local pos = apply_offset(position, belt) entity_pos.x = belt[1]
entity_pos.y = belt[2]
local pos = apply_offset(entity_pos, group_offset)
local entity = surface.create_entity{ name = belt_type, position = pos, force = "neutral", direction = belt[3] } local entity = surface.create_entity{ name = belt_type, position = pos, force = "neutral", direction = belt[3] }
if entity and config.afk_belts.protected then if entity and config.afk_belts.protected then
protect_entity(entity) protect_entity(entity)
@@ -115,13 +120,16 @@ end
-- Generates extra tiles in a set pattern as defined in the config -- Generates extra tiles in a set pattern as defined in the config
--- @param surface LuaSurface --- @param surface LuaSurface
--- @param offset MapPosition --- @param offset MapPosition.struct
local function create_pattern_tiles(surface, offset) local function create_pattern_tiles(surface, offset)
local tiles_to_make = {} local tiles_to_make = {}
local pattern_tile = config.pattern.pattern_tile local pattern_tile = config.pattern.pattern_tile
local tile_pos = { x = 0, y = 0 }
for index, position in pairs(config.pattern.locations) do for index, position in pairs(config.pattern.locations) do
tiles_to_make[index] = { name = pattern_tile, position = apply_offset(position, offset) } tile_pos.x = position[1]
tile_pos.y = position[2]
tiles_to_make[index] = { name = pattern_tile, position = apply_offset(tile_pos, offset) }
end end
surface.set_tiles(tiles_to_make) surface.set_tiles(tiles_to_make)
@@ -129,13 +137,16 @@ end
-- Generates extra water as defined in the config -- Generates extra water as defined in the config
--- @param surface LuaSurface --- @param surface LuaSurface
--- @param offset MapPosition --- @param offset MapPosition.struct
local function create_water_tiles(surface, offset) local function create_water_tiles(surface, offset)
local tiles_to_make = {} local tiles_to_make = {}
local water_tile = config.water.water_tile local water_tile = config.water.water_tile
local tile_pos = { x = 0, y = 0 }
for _, position in pairs(config.water.locations) do for _, position in pairs(config.water.locations) do
table.insert(tiles_to_make, { name = water_tile, position = apply_offset(position, offset) }) tile_pos.x = position[1]
tile_pos.y = position[2]
table.insert(tiles_to_make, { name = water_tile, position = apply_offset(tile_pos, offset) })
end end
surface.set_tiles(tiles_to_make) surface.set_tiles(tiles_to_make)
@@ -143,11 +154,17 @@ end
--- Generates the entities that are in the config --- Generates the entities that are in the config
--- @param surface LuaSurface --- @param surface LuaSurface
--- @param offset MapPosition --- @param offset MapPosition.struct
local function create_entities(surface, offset) local function create_entities(surface, offset)
local entity_pos = { x = 0, y = 0 }
for _, entity_details in pairs(config.entities.locations) do for _, entity_details in pairs(config.entities.locations) do
local pos = apply_offset({ entity_details[2], entity_details[3] }, offset) entity_pos.x = entity_details[2]
local entity = surface.create_entity{ name = entity_details[1], position = pos, force = "neutral" } entity_pos.y = entity_details[3]
local entity = surface.create_entity{
name = entity_details[1],
position = apply_offset(entity_pos, offset),
force = "neutral"
}
if entity then if entity then
if config.entities.protected then if config.entities.protected then
@@ -161,7 +178,7 @@ end
--- Generates an area with no water or entities, no water area is larger --- Generates an area with no water or entities, no water area is larger
--- @param surface LuaSurface --- @param surface LuaSurface
--- @param offset MapPosition --- @param offset MapPosition.struct
local function clear_spawn_area(surface, offset) local function clear_spawn_area(surface, offset)
local get_tile = surface.get_tile local get_tile = surface.get_tile
@@ -182,7 +199,7 @@ local function clear_spawn_area(surface, offset)
for y = -fill_radius, fill_radius do -- loop over y for y = -fill_radius, fill_radius do -- loop over y
local y_sqr = (y + 0.5) ^ 2 local y_sqr = (y + 0.5) ^ 2
local dst = x_sqr + y_sqr local dst = x_sqr + y_sqr
local pos = apply_offset({ x, y }, offset) local pos = apply_offset({ x = x, y = y }, offset)
if dst < tile_radius_sqr then if dst < tile_radius_sqr then
-- If it is inside the decon radius always set the tile -- If it is inside the decon radius always set the tile
tiles_to_make[#tiles_to_make + 1] = { name = decon_tile, position = pos } tiles_to_make[#tiles_to_make + 1] = { name = decon_tile, position = pos }
@@ -204,14 +221,14 @@ end
--- Spawn the resource tiles --- Spawn the resource tiles
--- @param surface LuaSurface --- @param surface LuaSurface
--- @param offset MapPosition --- @param offset MapPosition.struct
local function create_resources_tiles(surface, offset) local function create_resources_tiles(surface, offset)
for _, resource in ipairs(config.resource_tiles.resources) do for _, resource in ipairs(config.resource_tiles.resources) do
if resource.enabled then if resource.enabled then
local pos = apply_offset(resource.offset, offset) local pos = apply_offset(resource.offset, offset)
for x = pos.x, pos.x + resource.size[1] do for x = pos.x, pos.x + resource.size[1] do
for y = pos.y, pos.y + resource.size[2] do for y = pos.y, pos.y + resource.size[2] do
surface.create_entity{ name = resource.name, amount = resource.amount, position = { x, y } } surface.create_entity{ name = resource.name, amount = resource.amount, position = { x = x, y = y } }
end end
end end
end end
@@ -220,7 +237,7 @@ end
--- Spawn the resource entities --- Spawn the resource entities
--- @param surface LuaSurface --- @param surface LuaSurface
--- @param offset MapPosition --- @param offset MapPosition.struct
local function create_resource_patches(surface, offset) local function create_resource_patches(surface, offset)
for _, resource in ipairs(config.resource_patches.resources) do for _, resource in ipairs(config.resource_patches.resources) do
if resource.enabled then if resource.enabled then
+1 -1
View File
@@ -459,7 +459,7 @@ local function on_entity_settings_pasted(event)
-- Get the modules and add them to the planner -- Get the modules and add them to the planner
local all_modules = {} local all_modules = {}
for i = 1, #module_inventory do for i = 1, #module_inventory do
local slot = module_inventory[i] local slot = module_inventory[i --[[@as uint]]]
if slot.valid_for_read and slot.count > 0 then if slot.valid_for_read and slot.count > 0 then
all_modules[i] = { name = slot.name, quality = slot.quality.name } all_modules[i] = { name = slot.name, quality = slot.quality.name }
else else
@@ -294,9 +294,9 @@ end
--- Get the achieved time for a force --- Get the achieved time for a force
--- @param force LuaForce --- @param force LuaForce
--- @param research_index number --- @param research_index number
--- @return number --- @return number?
function Elements.container.get_achieved_time(force, research_index) function Elements.container.get_achieved_time(force, research_index)
return assert(Elements.container.data[force][research_index]) return assert(Elements.container.data[force])[research_index]
end end
--- Calculate the starting research index for a force --- Calculate the starting research index for a force
+25 -11
View File
@@ -1,5 +1,4 @@
"use strict"; "use strict";
const assert = require("assert").strict;
const { compile } = require("@clusterio/lib"); const { compile } = require("@clusterio/lib");
/** /**
@@ -15,22 +14,37 @@ function testMatrix(...arrays) {
} }
/** /**
* Test that a class is round trip json serialisable across multiple test cases. * Test that a class is round trip JSON serialisable across multiple test cases.
* *
* @template {any[]} T - Constructor arguments * @template {any[]} T - Constructor arguments
* @param {import("tap").Test} t - Parent test.
* @param {{new(...args: T): object}} Class - The class which has toJSON and fromJSON methods. * @param {{new(...args: T): object}} Class - The class which has toJSON and fromJSON methods.
* @param {T[]} tests - The tests inputs to pass to the class constructor. * @param {T[]} tests - The test inputs to pass to the class constructor.
*/ */
function testRoundTripJsonSerialisable(Class, tests) { function testRoundTripJsonSerialisable(t, Class, tests) {
const validate = compile(Class.jsonSchema); const validate = compile(Class.jsonSchema);
for (const test of tests) { for (const test of tests) {
const original = new Class(...test); const name = JSON.stringify(test);
const serialised = JSON.stringify(original);
const jsonObject = JSON.parse(serialised); try {
const reconstructed = Class.fromJSON(jsonObject); const original = new Class(...test);
assert.deepEqual(reconstructed, original, JSON.stringify(test)); const jsonObject = JSON.parse(JSON.stringify(original));
if (!validate(jsonObject)) { const reconstructed = Class.fromJSON(jsonObject);
throw validate.errors;
if (!validate(jsonObject)) {
t.fail(`schema validation: ${name}`, {
diagnostic: {
json: jsonObject,
errors: validate.errors,
},
});
continue;
}
t.strictSame(reconstructed, original, `round trip: ${name}`);
} catch (error) {
t.fail(`round trip: ${name}`, { error });
} }
} }
} }
+1
View File
@@ -10,6 +10,7 @@ local source = assert(debug.getinfo(1, "S")).source
local shared_root = assert(source:match("^@(.*)/framework%.lua$")) local shared_root = assert(source:match("^@(.*)/framework%.lua$"))
local Stubs = assert(loadfile(shared_root .. "/stubs.lua"))() local Stubs = assert(loadfile(shared_root .. "/stubs.lua"))()
--- @class Framework
local Framework = {} local Framework = {}
--- Turn a value into a string for failure messages --- Turn a value into a string for failure messages
+1
View File
@@ -50,6 +50,7 @@ end
--- Create an independent set of stubs, installed as the lua globals --- Create an independent set of stubs, installed as the lua globals
function Stubs.new() function Stubs.new()
--- @class Stubs
local stubs = { local stubs = {
events = {}, -- events raised through script.raise_event events = {}, -- events raised through script.raise_event
printed = {}, -- game.print and player.print messages printed = {}, -- game.print and player.print messages