Files
aetherbound-guild/runtime/tests/foundation/guild_page_view_test.gd
T

169 lines
7.6 KiB
GDScript

extends SceneTree
const PageContext = preload("res://foundation/page_context.gd")
const GuildPageView = preload("res://entry/views/guild_page_view.gd")
var failures: Array[String] = []
var intents: Array[Dictionary] = []
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
# Single target size (owner policy): every screen is authored at 1280x720.
root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_KEEP
root.content_scale_size = Vector2i(1280, 720)
root.size = Vector2i(1280, 720)
var view := GuildPageView.new()
view.name = "GuildViewContractFixture"
view.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
view.configure_presentation({"safe_area": 24, "text_scale": 1.0, "locale": "en"})
view.mount(PageContext.new({
"page_id": "guild",
"caller_id": "entry",
"return_page_id": "title",
"return_focus_path": "GuildContractBoard",
"metadata": {"company_id": "company-001"},
}))
view.intent_emitted.connect(_record_intent)
root.add_child(view)
view.render(_fixture_view_data("empty"))
await process_frame
for node_name in [
"GuildMasterWorldExtension", "GuildMasterDesignCanvas", "GuildMasterLayeredWorld",
"GuildAnchorRoot", "GuildTopRail", "GuildTopRailLeft", "GuildTopRailCenter",
"GuildTopRailRight", "GuildBottomRail", "GuildBottomRailLeft",
"GuildBottomRailCenter", "GuildBottomRailRight", "GuildTitlePlate",
"GuildAnchorTitle", "GuildAnchorStatus", "GuildProgressStatus",
"GuildCoinStatus", "GuildAnchorCompanyStatus", "GuildSettings", "GuildTitle",
"GuildRoster", "GuildMemberPortraitTexture_0", "GuildMemberPortraitTexture_1",
"GuildMasterIdentityPlate0", "GuildMasterIdentityPlate1", "GuildMasterKioskTexture",
"GuildKioskCopyArea", "GuildKioskText", "GuildContractReason", "GuildMarketReason",
"GuildNotice", "GuildRosterButton", "GuildEquipment", "GuildParty", "GuildRisk",
"GuildContractBoard", "GuildMarketUnavailable", "GuildContractSticker", "GuildMarketSticker",
]:
expect(view.find_child(node_name, true, false) != null, "%s is rendered" % node_name)
var canvas := view.find_child("GuildMasterDesignCanvas", true, false) as Control
if canvas != null:
expect(is_equal_approx(canvas.size.x / canvas.size.y, 16.0 / 9.0), "Guild canvas preserves 16:9")
expect(canvas.get_global_rect().encloses(view.find_child("GuildMasterLayeredWorld", true, false).get_global_rect()), "layered world stays inside canvas")
var contract := view.find_child("GuildContractBoard", true, false) as Button
var market := view.find_child("GuildMarketUnavailable", true, false) as Button
var party := view.find_child("GuildParty", true, false) as Button
var risk := view.find_child("GuildRisk", true, false) as Button
expect(contract != null and not contract.disabled, "empty Guild enables Contract")
expect(market != null and market.disabled, "empty Guild disables Market")
expect(party != null and party.disabled, "empty Guild disables Party")
expect(risk != null and risk.disabled, "empty Guild disables Risk")
if contract != null:
contract.emit_signal("pressed")
await process_frame
expect(intents.size() == 1 and intents[0].get("intent_id") == "contract", "Contract emits explicit intent")
view.render(_fixture_view_data("active"))
await process_frame
for node_name in ["GuildContractActive", "GuildMarket", "GuildParty", "GuildRisk"]:
expect(view.find_child(node_name, true, false) != null, "%s is rendered for active state" % node_name)
for node_name in ["GuildMarket", "GuildParty", "GuildRisk"]:
var enabled := view.find_child(node_name, true, false) as Button
expect(enabled != null and not enabled.disabled, "%s is enabled for active state" % node_name)
for node_name in ["GuildSettings", "GuildTitle", "GuildRosterButton", "GuildEquipment", "GuildParty", "GuildRisk", "GuildMarket", "GuildContractActive"]:
var button := view.find_child(node_name, true, false) as Button
if button != null:
button.emit_signal("pressed")
await process_frame
expect(intents.size() == 9, "View emits one explicit intent per command")
if intents.size() == 9:
expect(intents[1].get("intent_id") == "settings", "Settings intent is named")
expect(intents[2].get("intent_id") == "title", "Title intent is named")
expect(intents[3].get("intent_id") == "roster", "Roster intent is named")
expect(intents[4].get("intent_id") == "equipment", "Equipment intent is named")
expect(intents[5].get("intent_id") == "party", "Party intent is named")
expect(intents[6].get("intent_id") == "risk", "Risk intent is named")
expect(intents[7].get("intent_id") == "market", "Market intent is named")
expect(intents[8].get("intent_id") == "contract", "Contract intent is named")
view.render(_fixture_view_data("returned"))
await process_frame
expect(view.find_child("GuildContractActive", true, false) != null and view.find_child("GuildContractActive", true, false).disabled, "returned state blocks later Contract")
expect(view.find_child("GuildMarketUnavailable", true, false) != null and view.find_child("GuildMarketUnavailable", true, false).disabled, "returned state blocks Market")
view.render({"ok": false, "locale": "en", "notice": "Guild unavailable"})
await process_frame
expect(view.find_child("GuildInvalidOverlay", true, false) != null, "invalid state renders safely")
expect(view.find_child("GuildContractBoard", true, false) == null, "invalid state is non-interactive")
var source := FileAccess.get_file_as_string("res://entry/views/guild_page_view.gd")
for forbidden in ["GuildContractService", "RuntimeContentRegistry", "current_screen", "entry_shell.gd", "res://compat/", "Save", "company_path"]:
expect(not source.contains(forbidden), "View does not own %s" % forbidden)
view.queue_free()
await process_frame
if failures.is_empty():
print("ABG_M43_GUILD_VIEW_CONTRACT_OK mount=true normalized_canvas=true layered_world=true rails=true actors=2 kiosk=true states=empty,active,returned commands=title,settings,roster,equipment,party,risk,contract,market intents=true invalid=true forbidden_imports=true")
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func _record_intent(intent_id: String, payload: Dictionary) -> void:
intents.append({"intent_id": intent_id, "payload": payload})
func _fixture_view_data(state: String) -> Dictionary:
var contract := {}
if state == "active" or state == "returned":
contract = {
"present": true,
"active": state == "active",
"returned": state == "returned",
"state": state,
"status": "market" if state == "active" else "returned",
"contract_id": "CTR-R1-01",
"run_id": "run-001",
"route_id": "windglass_verge",
"difficulty": "stormbound",
"coin": 38,
"line_capacity": 4,
}
return {
"ok": true,
"page_id": "guild",
"locale": "en",
"company": {
"company_id": "company-001",
"owned_count": 2,
"line_capacity": 4,
"display_recruits": [_recruit("owned-001", "Ari", "PF-B01"), _recruit("owned-002", "Bram", "PF-B02")],
},
"contract": contract,
"destinations": {
"contract": {"enabled": state == "empty"},
"market": {"enabled": state == "active"},
"equipment": {"enabled": state == "active"},
"party": {"enabled": state == "active"},
"risk": {"enabled": state == "active"},
},
}
func _recruit(recruit_id: String, name: String, profession_id: String) -> Dictionary:
return {
"recruit_id": recruit_id,
"profession_id": profession_id,
"rendered_name_snapshot": {"en": name, "zh_CN": name},
"profession_name": {"en": "Vanguard", "zh_CN": "先锋"},
}
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("M43 Guild View: %s" % message)