mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-11 23:54:52 +09:00
Framework: - Intervals are gone. Update periods are hardcoded on_nth_tick values again, since nobody has needed to change them. - apply is a single loop over the declared fields and logs a warning when an override is invalid. receive_update also warns about unknown keys. - Nested tables are addressed as "section.key". Lists of strings can be overridden. Features.optional declares a value with no default. - Features.on_apply rebuilds values derived from the config, such as lookup sets. It also runs during on_load. exp_roles is assumed to be present: - AFK Kick picks a role (active_role_id) instead of a custom check function. - Chat Auto Reply, Protection, and Nuke Protection no longer take a permission as a setting. New features, each chosen for an edge case: - Discord Alerts: conditional handlers, and a command map that becomes a list. - Entity Protection: lists turned into sets at load, and bookkeeping that must keep running while disabled. - Nuke Protection: config keyed by defines, flattened to a banned items list. - Popups: one config shared by two control files. - Spawn Area: nested sections, and settings that only apply when the map is created. Page: now laid out like the role page. It has a search across every setting, one long list with a sticky sidebar to jump between sections, and the enabled switch separated at the front of each header with Reset to defaults on the right. Changed rows are highlighted, and a bottom bar offers Revert and Apply. Role settings use a role picker and lists use a tag select. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
138 lines
7.2 KiB
Lua
138 lines
7.2 KiB
Lua
local Suite = ... --- @type Suite<ExpScenario.TestEnv>
|
|
|
|
Suite.test("config() enables features unless the defaults say otherwise", function(env)
|
|
Suite.eq(env.Features.config("on", { value = 1 }).enabled, true, "enabled by default")
|
|
Suite.eq(env.Features.config("off", { enabled = false }).enabled, false, "the defaults can disable it")
|
|
Suite.eq(env.Features.config("empty"), { enabled = true }, "config is optional")
|
|
Suite.throws(function() env.Features.config("on", {}) end, "Feature already declared: on", "names are unique")
|
|
end)
|
|
|
|
Suite.test("is_enabled() takes a name or a config", function(env)
|
|
local config = env.Features.config("test", { enabled = false })
|
|
Suite.eq(env.Features.is_enabled("test"), false, "by name")
|
|
Suite.eq(env.Features.is_enabled(config), false, "by config")
|
|
Suite.throws(function() env.Features.is_enabled("unknown") end, "Unknown feature: unknown", "unknown names error")
|
|
end)
|
|
|
|
Suite.test("receive_update() changes the config in place", function(env)
|
|
local config = env.Features.config("test", { flag = true, count = 5, text = "a" })
|
|
env.Features.receive_update{ { name = "test", enabled = false, values = { flag = false, count = 7 } } }
|
|
Suite.eq(config, { enabled = false, flag = false, count = 7, text = "a" }, "given values replace the defaults")
|
|
|
|
env.Features.receive_update{ { name = "test", enabled = true, values = { text = "b" } } }
|
|
Suite.eq(config, { enabled = true, flag = true, count = 5, text = "b" }, "values left out go back to their default")
|
|
Suite.eq(env.storage, { test = { enabled = true, text = "b" } }, "the overrides are kept in storage")
|
|
end)
|
|
|
|
Suite.test("receive_update() raises on_feature_changed for each known feature", function(env)
|
|
env.Features.config("test")
|
|
env.Features.receive_update{
|
|
{ name = "test", enabled = false, values = {} },
|
|
{ name = "unknown", enabled = false, values = {} },
|
|
}
|
|
Suite.eq(env.events, {
|
|
{ name = env.Features.events.on_feature_changed, tick = game.tick, feature_name = "test" },
|
|
}, "raised once")
|
|
end)
|
|
|
|
Suite.test("receive_update() ignores values which can not be overridden", function(env)
|
|
local function check() return true end
|
|
local config = env.Features.config("test", { flag = true, check = check, icon = nil })
|
|
env.Features.receive_update{
|
|
{ name = "unknown", enabled = false, values = {} },
|
|
{ name = "test", enabled = true, values = { flag = "yes", check = false, icon = "x", extra = 1 } },
|
|
}
|
|
Suite.eq(config, { enabled = true, flag = true, check = check }, "wrong types, functions, and unknown keys are skipped")
|
|
Suite.eq(env.storage.unknown, nil, "unknown features are not stored")
|
|
end)
|
|
|
|
Suite.test("on_load applies the overrides from storage", function(env)
|
|
local config = env.Features.config("test", { flag = true })
|
|
env.on_load{ test = { enabled = false, flag = false } }
|
|
Suite.eq(config, { enabled = false, flag = false }, "the saved overrides apply")
|
|
|
|
env.Features.receive_update{ { name = "test", enabled = true, values = {} } }
|
|
Suite.eq(env.storage, { test = { enabled = true } }, "later updates go to the loaded storage table")
|
|
end)
|
|
|
|
Suite.test("guard() skips handlers while the feature is disabled", function(env)
|
|
local config = env.Features.config("test")
|
|
local calls = {}
|
|
local lib = env.Features.guard(config, {
|
|
events = { on_event = function(event) calls[#calls + 1] = event end },
|
|
on_nth_tick = { [60] = function(event) calls[#calls + 1] = event end },
|
|
on_init = function() end,
|
|
})
|
|
|
|
lib.events.on_event("a")
|
|
lib.on_nth_tick[60]("b")
|
|
env.Features.receive_update{ { name = "test", enabled = false, values = {} } }
|
|
lib.events.on_event("c")
|
|
lib.on_nth_tick[60]("d")
|
|
Suite.eq(calls, { "a", "b" }, "only called while enabled")
|
|
end)
|
|
|
|
Suite.test("guard() declares features by name", function(env)
|
|
local calls = 0
|
|
local lib = env.Features.guard("test", { events = { on_event = function() calls = calls + 1 end } })
|
|
env.Features.receive_update{ { name = "test", enabled = false, values = {} } }
|
|
lib.events.on_event()
|
|
Suite.eq(calls, 0, "the name is declared without config")
|
|
Suite.eq(env.Features.is_enabled("test"), false, "and can be looked up")
|
|
end)
|
|
|
|
Suite.test("receive_update() logs invalid and unknown overrides", function(env)
|
|
local config = env.Features.config("test", { flag = true })
|
|
env.Features.receive_update{ { name = "test", enabled = true, values = { flag = 1, extra = 2 } } }
|
|
Suite.eq(config.flag, true, "the default is used")
|
|
Suite.eq(#env.logged, 2, "both are logged")
|
|
end)
|
|
|
|
Suite.test("config() addresses nested values by path", function(env)
|
|
local offset = { 1, 2 }
|
|
local config = env.Features.config("test", { section = { flag = true, count = 1 }, offset = offset })
|
|
env.Features.receive_update{ { name = "test", enabled = true, values = { ["section.flag"] = false, offset = { "x" } } } }
|
|
Suite.eq(config.section, { flag = false, count = 1 }, "the nested value changes in place")
|
|
Suite.eq(config.offset, offset, "lists of other types can not be overridden")
|
|
|
|
env.Features.receive_update{ { name = "test", enabled = true, values = {} } }
|
|
Suite.eq(config.section.flag, true, "and goes back to its default")
|
|
end)
|
|
|
|
Suite.test("config() allows lists of strings to be overridden", function(env)
|
|
local config = env.Features.config("test", { names = { "a", "b" }, empty = {} })
|
|
env.Features.receive_update{ { name = "test", enabled = true, values = { names = { "c" }, empty = { "d" } } } }
|
|
Suite.eq(config.names, { "c" }, "a list is replaced")
|
|
Suite.eq(config.empty, { "d" }, "an empty default is a list")
|
|
env.Features.receive_update{ { name = "test", enabled = true, values = { names = { 1 } } } }
|
|
Suite.eq(config.names, { "a", "b" }, "a list with other types is invalid")
|
|
end)
|
|
|
|
Suite.test("optional() declares a value without a default", function(env)
|
|
local config = env.Features.config("test", { role_id = env.Features.optional("number") })
|
|
Suite.eq(config.role_id, nil, "nil until overridden")
|
|
env.Features.receive_update{ { name = "test", enabled = true, values = { role_id = 3 } } }
|
|
Suite.eq(config.role_id, 3, "overridden with the given type")
|
|
env.Features.receive_update{ { name = "test", enabled = true, values = {} } }
|
|
Suite.eq(config.role_id, nil, "and back to nil")
|
|
end)
|
|
|
|
Suite.test("on_apply() is called now and after every change", function(env)
|
|
local config = env.Features.config("test", { names = { "a" } })
|
|
local seen = {}
|
|
env.Features.on_apply("test", function(applied) seen[#seen + 1] = applied.names[1] end)
|
|
env.Features.receive_update{ { name = "test", enabled = true, values = { names = { "b" } } } }
|
|
env.on_load{ test = { enabled = true, names = { "c" } } }
|
|
Suite.eq(seen, { "a", "b", "c" }, "on register, update, and load")
|
|
Suite.eq(config.names, { "c" }, "after the config is applied")
|
|
end)
|
|
|
|
Suite.test("config files declare their feature", function(env)
|
|
local config = env.load_config("popups")
|
|
env.Features.receive_update{ { name = "popups", enabled = true, values = { show_player_damage = false } } }
|
|
Suite.eq(config.show_player_damage, false, "popups can be changed")
|
|
Suite.eq(config.show_player_health, true, "other values keep their default")
|
|
end)
|
|
|
|
return Suite.run()
|