|
|
| (22 intermediate revisions by 2 users not shown) |
| 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 |