From e91cf0f0c21038219ca8a4675a7d702b2229fc65 Mon Sep 17 00:00:00 2001 From: Andrew Quenehen Date: Sat, 1 Aug 2026 05:27:07 -0300 Subject: [PATCH] test(nx-mods): lock zip and chord edge regressions Co-authored-by: Cursor --- tests/engine/game_display_chord_test.lua | 19 ++++++++++++++ tests/engine/input_display_chord_test.lua | 6 +++++ tests/rom_importer_nx_mods_inbox_test.lua | 30 +++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/tests/engine/game_display_chord_test.lua b/tests/engine/game_display_chord_test.lua index 02f478b3..a7235f15 100644 --- a/tests/engine/game_display_chord_test.lua +++ b/tests/engine/game_display_chord_test.lua @@ -114,6 +114,25 @@ Game:gamepadpressed(joySelectDown, "y") eq(digits[1], "5", "Select via isGamepadDown(back) + Y -> 5") eq(#padForwarded, 0, "isGamepadDown Select chord does not forward face") +-- Edge: every chord face without Select must not cycle (NXMOD-09) +GamepadMap._setForceNXForTests(false) +for _, btn in ipairs({ "a", "b", "y", "x", "leftshoulder" }) do + Input:init() + resetSpies() + Game:gamepadpressed(joy, btn) + eq(#digits, 0, "edge: no cycle without Select for " .. btn) + eq(#padForwarded, 1, "edge: " .. btn .. " still reaches Input without Select") + check(not wroteOptions, "edge: no options write without Select for " .. btn) +end + +-- Edge: Select alone (no face) does not synthesize a digit +holdSelect() +resetSpies() +-- pressing back again while held is not a display chord partner +Game:gamepadpressed(joy, "back") +eq(#digits, 0, "edge: Select alone does not fire a display digit") +eq(#padForwarded, 1, "edge: Select alone still forwards to Input") + GamepadMap._setForceNXForTests(false) Game.keypressed = origKeypressed Input.gamepadpressed = origPad diff --git a/tests/engine/input_display_chord_test.lua b/tests/engine/input_display_chord_test.lua index 9acddcec..47b115f6 100644 --- a/tests/engine/input_display_chord_test.lua +++ b/tests/engine/input_display_chord_test.lua @@ -40,4 +40,10 @@ eq(GamepadMap.displayChordDigit("x"), "6", "NX X -> key 6") eq(GamepadMap.displayChordDigit("leftshoulder"), "7", "NX L -> key 7") GamepadMap._setForceNXForTests(false) +-- Edge: map alone never invents a digit for non-chord faces (NXMOD-09 map half) +for _, btn in ipairs({ "guide", "leftstick", "rightstick", "lefttrigger" }) do + eq(GamepadMap.displayChordDigit(btn), nil, + "edge: unmapped " .. btn .. " is not a display digit") +end + T.finish() diff --git a/tests/rom_importer_nx_mods_inbox_test.lua b/tests/rom_importer_nx_mods_inbox_test.lua index c278736d..4c8e9f38 100644 --- a/tests/rom_importer_nx_mods_inbox_test.lua +++ b/tests/rom_importer_nx_mods_inbox_test.lua @@ -119,6 +119,19 @@ for _, path in ipairs(roms) do end eq(#roms, 0, "ROM scanInbox finds no zip-only inbox entries") +-- Edge: ROM inbox with .gb alongside .zip still ignores zip (spec edge) +ri = freshImporter() +love.filesystem.write("imports/cart.gb", string.rep("G", 16)) +love.filesystem.write("imports/sidecar.zip", "NOTAROM") +roms = ri:scanInbox(ri.ready) +local sawGb, sawZip = false, false +for _, path in ipairs(roms) do + if path:lower():match("%.zip$") then sawZip = true end + if path:lower():match("%.gb$") then sawGb = true end +end +check(sawGb, "ROM scan still finds .gb when zip present") +check(not sawZip, "ROM scan never lists .zip even beside .gb") + -- Stub LauncherMods.installZip for rescan tests (NXMOD-02..04) local installCalls = {} local installBehavior = {} -- path -> {ok=bool, id=string|err} @@ -232,10 +245,27 @@ eq(desk:_modsImportButtonLabel(), "Import mod .zip", check(desk:_modsDefaultHint():find("drop a mod", 1, true), "desktop default hint stays drop-oriented") +-- Edge: id-already-exists conflict retains inbox zip (no silent delete) +ri = freshImporter() +installCalls = {} +removed = {} +love.filesystem.write("imports/mods/dup.zip", "DUP") +installBehavior["imports/mods/dup.zip"] = { + ok = false, err = "mod id already installed", +} +ri:rescanModsAction() +eq(#installCalls, 1, "conflict still attempts installZip") +check(ri.modNotice and not ri.modNotice.ok, "conflict surfaces notice") +check(not removed["imports/mods/dup.zip"], "conflict retains inbox zip") +check(love.filesystem.read("imports/mods/dup.zip") == "DUP", + "conflict leaves zip bytes intact") + -- Cleanup + restore stubs clearModsInbox() love.filesystem.remove("imports/other.zip") love.filesystem.remove("imports/modpack.zip") +love.filesystem.remove("imports/cart.gb") +love.filesystem.remove("imports/sidecar.zip") package.loaded["src.mods.LauncherMods"] = nil package.loaded["src.core.HostShell"] = nil love.system.getOS = saved.getOS