Editing Module:Sandbox/VergirGive feedback
The edit can be undone.
Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.
| Latest revision | Your text | ||
| Line 1: | Line 1: | ||
-- (diag entry point is defined at the bottom of this page) | |||
-- Test harness for the proposed Module:GameData prop-matching change. | |||
-- Renders with {{#invoke:Sandbox/Vergir|test}} | |||
-- | |||
-- Holds a copy of both the current and the proposed matching code, runs the | |||
-- proposed one against Data:AbilityData.json, and checks that every existing | |||
-- list in Module:AbilityTable/Lists still selects exactly the same abilities. | |||
local p = {} | local p = {} | ||
local Lists = require("Module:AbilityTable/Lists") | |||
-- ============================================================ | |||
-- Current implementation (unchanged, still used for plain targets) | |||
-- ============================================================ | |||
local function record_matches_prop(record, prop) | |||
for k, v in pairs(record) do | |||
if k == prop then | |||
if type(v) == "table" then | |||
if v["Value"] ~= nil then | |||
v = v["Value"] | |||
else | |||
for _ in pairs(v) do return true end | |||
return false | |||
end | |||
end | |||
if v ~= nil and v ~= 0 and v ~= "" and v ~= "0" and v ~= "0m" then | |||
return true | |||
end | |||
end | |||
if type(v) == "table" and k ~= "DisabledStateMask" then | |||
if record_matches_prop(v, prop) then return true end | |||
elseif type(v) == "string" then | |||
if v == prop then return true end | |||
end | |||
end | |||
return false | |||
end | |||
local function entity_matches_old(record, properties) | |||
for _, prop in ipairs(properties) do | |||
if record_matches_prop(record, prop) then return true end | |||
end | |||
return false | |||
end | |||
-- ============================================================ | |||
-- Proposed additions | |||
-- ============================================================ | |||
local function value_is_meaningful(v) | |||
if type(v) == "table" then | |||
if v["Value"] ~= nil then | |||
v = v["Value"] | |||
else | |||
for _ in pairs(v) do return true end | |||
return false | |||
end | |||
end | |||
return v ~= nil and v ~= 0 and v ~= "" and v ~= "0" and v ~= "0m" | |||
end | |||
local function value_equals(v, expected) | |||
if type(v) == "table" then v = v["Value"] end | |||
if v == nil or type(v) == "table" then return false end | |||
return mw.ustring.lower(tostring(v)) == expected | |||
end | |||
local function follow_path(node, segments, index, value) | |||
if index > #segments then | |||
if value ~= nil then return value_equals(node, value) end | |||
return value_is_meaningful(node) | |||
end | |||
if type(node) ~= "table" then return false end | |||
local segment = segments[index] | |||
local next_node = node[segment] | |||
if next_node == nil then | |||
local num = tonumber(segment) | |||
if num then next_node = node[num] end | |||
end | |||
if next_node ~= nil and follow_path(next_node, segments, index + 1, value) then | |||
return true | |||
end | |||
for _, element in ipairs(node) do | |||
if type(element) == "table" and follow_path(element, segments, index, value) then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
local function record_matches_path(record, segments, value) | |||
for k, v in pairs(record) do | |||
if k == segments[1] and follow_path(v, segments, 2, value) then | |||
return true | |||
end | |||
if type(v) == "table" and k ~= "DisabledStateMask" then | |||
if record_matches_path(v, segments, value) then return true end | |||
end | |||
end | |||
return false | |||
end | |||
local function parse_target(prop) | |||
if type(prop) ~= "string" then return nil end | |||
local keys, value = prop, nil | |||
local colon = prop:find(":", 1, true) | |||
if colon then | |||
keys = mw.text.trim(prop:sub(1, colon - 1)) | |||
value = mw.text.trim(prop:sub(colon + 1)) | |||
if keys == "" or value == "" then return nil end | |||
value = mw.ustring.lower(value) | |||
end | |||
local segments = {} | |||
for segment in keys:gmatch("[^%.]+") do table.insert(segments, segment) end | |||
if #segments == 0 then return nil end | |||
if #segments == 1 and value == nil then return nil end | |||
return segments, value | |||
end | |||
local function entity_matches_new(record, properties) | |||
for _, prop in ipairs(properties) do | |||
local segments, value = parse_target(prop) | |||
if segments and record_matches_path(record, segments, value) then | |||
return true | |||
elseif record_matches_prop(record, prop) then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
-- ============================================================ | |||
-- Cases | |||
-- ============================================================ | |||
local cases = { | |||
{ target = "Type:spirit", expected = 219, note = "key found at any depth" }, | |||
{ target = "Type:SPIRIT", expected = 219, note = "case-insensitive value" }, | |||
{ target = "Damage:spirit", expected = 0, note = "nested value must not match" }, | |||
{ target = "Damage:90", expected = 5, note = ".Value unwrapped" }, | |||
{ target = "AbilityCharges:2", expected = 18, note = "number compared as string" }, | |||
{ target = "AbilityCooldown.Scale.Type", expected = 300, note = "path, no value" }, | |||
{ target = "AbilityCooldown.Scale.Type:cooldown",expected = 299, note = "Parry scales on parry_cd" }, | |||
{ target = "AbilityCooldown.Scale.Type:spirit", expected = 0, note = "wrong value on a valid path" }, | |||
{ target = "Radius.Scale.Value:1", expected = 67, note = "explicit .Value segment" }, | |||
{ target = "Upgrades.Damage", expected = 63, note = "steps through array entries" }, | |||
{ target = "Upgrades.1.AbilityCooldown", expected = 79, note = "numeric index segment" }, | |||
{ target = "38.1 50.8", expected = 1, note = "dotted value falls back" }, | |||
} | |||
local function select_names(data, properties, matcher) | |||
local names = {} | |||
for key, record in pairs(data) do | |||
if type(record) == "table" and matcher(record, properties) then | |||
table.insert(names, record["Name"] or key) | |||
end | |||
end | |||
table.sort(names) | |||
return names | |||
end | |||
local function ok_cell(pass) | |||
if pass then | |||
return 'style="background:#d5f5d5" | pass' | |||
end | |||
return 'style="background:#f5d5d5" | <b>FAIL</b>' | |||
end | |||
function p.test(frame) | |||
local data = mw.loadJsonData("Data:AbilityData.json") | |||
local out = {} | |||
local failures = 0 | |||
-- --- new target forms --- | |||
table.insert(out, "=== Proposed target forms ===") | |||
table.insert(out, '{| class="wikitable"') | |||
table.insert(out, "! Target !! Expected !! Actual !! Result !! Sample !! Note") | |||
for _, case in ipairs(cases) do | |||
local names = select_names(data, { case.target }, entity_matches_new) | |||
local pass = (#names == case.expected) | |||
if not pass then failures = failures + 1 end | |||
local sample = {} | |||
for i = 1, math.min(4, #names) do table.insert(sample, names[i]) end | |||
local sample_text = #sample > 0 and table.concat(sample, ", ") or "—" | |||
if #names > #sample then sample_text = sample_text .. ", …" end | |||
table.insert(out, "|-") | |||
table.insert(out, "| <code>" .. mw.text.nowiki(case.target) .. "</code>" | |||
.. " || " .. case.expected | |||
.. " || " .. #names | |||
.. " || " .. ok_cell(pass) | |||
.. " || " .. sample_text | |||
.. " || " .. case.note) | |||
end | |||
table.insert(out, "|}") | |||
-- --- regression across every existing list --- | |||
table.insert(out, "=== Existing lists: old vs new ===") | |||
table.insert(out, '{| class="wikitable sortable"') | |||
table.insert(out, "! List !! Old !! New !! Result") | |||
local list_names = {} | |||
for name in pairs(Lists.lists) do table.insert(list_names, name) end | |||
table.sort(list_names) | |||
for _, name in ipairs(list_names) do | |||
local props = Lists.lists[name] | |||
local old_names = select_names(data, props, entity_matches_old) | |||
local new_names = select_names(data, props, entity_matches_new) | |||
local same = (#old_names == #new_names) | |||
if same then | |||
for i = 1, #old_names do | |||
if old_names[i] ~= new_names[i] then same = false break end | |||
end | |||
end | |||
if not same then failures = failures + 1 end | |||
table.insert(out, "|-") | |||
table.insert(out, "| " .. name | |||
.. " || " .. #old_names | |||
.. " || " .. #new_names | |||
.. " || " .. ok_cell(same)) | |||
end | |||
table.insert(out, "|}") | |||
local summary | |||
if failures == 0 then | |||
summary = "'''All checks passed.'''" | |||
else | |||
summary = "'''" .. failures .. " check(s) failed.'''" | |||
end | |||
return summary .. "\n" .. table.concat(out, "\n") | |||
end | |||
return p | return p | ||