# test_item_split_parity.gd —— 40250 物品精准拆分与快捷操作 1:1 纯净回归测试套件 extends SceneTree const ItemSplitSystem = preload("res://item_split_system.gd") var _passed := 0 var _failed := 0 class FakeClient extends Node: var move_calls: Array = [] var sell_calls: Array = [] var move_ok := true var sell_ok := true func move_item(fw: int, fc: int, tw: int, tc: int, count: int) -> bool: move_calls.append([fw, fc, tw, tc, count]) return move_ok func shop_sell(cell: int, count: int) -> bool: sell_calls.append([cell, count]) return sell_ok func _assert(cond: bool, msg: String) -> void: if cond: _passed += 1 print(" [PASS] %s" % msg) else: _failed += 1 printerr(" [FAIL] %s" % msg) func _init() -> void: print("\n=== Running test_item_split_parity (Direction 3: Item Split & Quick Sell 1:1) ===") _test_item_stack_splitting() _test_quick_sell_to_shop() _test_drop_safety_confirmation() _finish() func _test_item_stack_splitting() -> void: print("\n--- Test 1: Stack Splitting (Shift + Left Click) ---") var iss := ItemSplitSystem.new() var client := FakeClient.new() var inv: Array = [ {"vnum": 27003, "name": "红药水(大)", "count": 200}, # Slot 0 {"vnum": 10, "name": "木剑+0", "count": 1}, # Slot 1 (single) null, # Slot 2 (empty) null # Slot 3 (empty) ] # 1. Split single count item var r_single = iss.split_item(1, 1, 2, inv) _assert(r_single["ok"] == false and r_single["reason"] == "NOT_SPLITTABLE", "Single count item rejected for split") # 2. Split count >= total count var r_overflow = iss.split_item(0, 200, 2, inv) _assert(r_overflow["ok"] == false and r_overflow["reason"] == "INVALID_SPLIT_COUNT", "Split count >= total rejected") # 3. Valid split: Split 50 from 200 into slot 2 var split_signal := {"fired": false, "src": -1, "dst": -1, "cnt": 0} iss.item_split.connect(func(s: int, d: int, c: int): split_signal["fired"] = true split_signal["src"] = s split_signal["dst"] = d split_signal["cnt"] = c ) var r_split = iss.split_item(0, 50, 2, inv, client) _assert(r_split["ok"] == true, "Split 50 potions succeeded") _assert(r_split["pending"] == true and client.move_calls == [[1, 0, 1, 2, 50]], "Split sends CG_ITEM_MOVE with source, destination and count") _assert(inv[0]["count"] == 200 and inv[2] == null, "Split request does not mutate local inventory before GC confirmation") _assert(not split_signal["fired"], "legacy item_split is not emitted before server confirmation") # 4. Auto-find empty slot when target_slot is -1 # Simulate the first request's destination after a server GC confirmation; # the helper itself must still not mutate this snapshot. inv[2] = {"vnum": 27003, "count": 50} var r_auto = iss.split_item(0, 30, -1, inv, client) _assert(r_auto["ok"] == true, "Auto-find split succeeded") _assert(r_auto["target_slot"] == 3, "Auto-placed into slot 3") _assert(client.move_calls[-1] == [1, 0, 1, 3, 30], "Auto split sends the selected empty destination") _assert(inv[3] == null and inv[0]["count"] == 200, "Auto split also waits for server confirmation") client.free() func _test_quick_sell_to_shop() -> void: print("\n--- Test 2: Quick Sell to NPC Shop (Ctrl + Right Click) ---") var iss := ItemSplitSystem.new() var client := FakeClient.new() var player_data := {"gold": 1000} var inv: Array = [ {"vnum": 10, "name": "木剑+0", "count": 1, "sale_price": 50, "is_locked": true}, # Locked {"vnum": 27001, "name": "小红药水", "count": 20, "sale_price": 100} # 20 x 100 = 2000 ] # 1. Locked item cannot be sold var r_lock = iss.quick_sell_item(0, inv, player_data) _assert(r_lock["ok"] == false and r_lock["reason"] == "ITEM_LOCKED", "Locked item quick sell blocked") # 2. Valid quick sell var sell_signal := {"fired": false, "count": 0} iss.item_quick_sell_requested.connect(func(_s: int, c: int, _n: String): sell_signal["fired"] = true sell_signal["count"] = c ) var r_sell = iss.quick_sell_item(1, inv, player_data, client) _assert(r_sell["ok"] == true, "Quick sell succeeded") _assert(r_sell["pending"] == true and client.sell_calls == [[1, 20]], "Quick sell sends CG_SHOP SELL2 with cell and count") _assert(inv[1] != null and inv[1]["count"] == 20, "Quick sell does not clear local item before server confirmation") _assert(player_data["gold"] == 1000, "Quick sell does not add local gold before confirmation") _assert(sell_signal["fired"] and sell_signal["count"] == 20, "item_quick_sell_requested signal records pending request") client.free() func _test_drop_safety_confirmation() -> void: print("\n--- Test 3: Drop Safety Check for Valuable Equipment ---") var iss := ItemSplitSystem.new() # 1. Locked item var locked_item := {"vnum": 169, "name": "战神之剑+9", "is_locked": true} var r1 = iss.check_drop_safety(locked_item) _assert(r1["can_drop"] == false and r1["reason"] == "ITEM_LOCKED", "Locked item drop blocked") # 2. +9 equipment requires confirmation var high_refine := {"vnum": 169, "name": "战神之剑+9", "refine_level": 9, "is_locked": false} var r2 = iss.check_drop_safety(high_refine) _assert(r2["can_drop"] == true and r2["require_confirm"] == true, "+9 weapon requires confirmation") # 3. Item with bonus attributes requires confirmation var bonus_item := {"vnum": 10, "name": "木剑+0", "attributes": [{"type": 1, "value": 500}], "is_locked": false} var r3 = iss.check_drop_safety(bonus_item) _assert(r3["can_drop"] == true and r3["require_confirm"] == true, "Item with bonuses requires confirmation") # 4. Plain +0 item requires no confirmation var plain_item := {"vnum": 10, "name": "木剑+0", "refine_level": 0, "is_locked": false} var r4 = iss.check_drop_safety(plain_item) _assert(r4["can_drop"] == true and r4["require_confirm"] == false, "Plain item drops without confirmation") func _finish() -> void: print("\n==========================================") print("Item Split Parity Test Results: %d Passed, %d Failed" % [_passed, _failed]) print("==========================================") if _failed > 0: quit(1) else: quit(0)