167 lines
8.2 KiB
GDScript
167 lines
8.2 KiB
GDScript
extends SceneTree
|
|
|
|
const EntryShell = preload("res://entry/entry_shell.gd")
|
|
const SettingsStore = preload("res://entry/settings_store.gd")
|
|
const DraftService = preload("res://entry/initial_company_draft_service.gd")
|
|
|
|
const DRAFT_PATH := "user://dev3_guild_contract_draft.json"
|
|
const COMPANY_PATH := "user://dev3_guild_contract_company.json"
|
|
const SETTINGS_PATH := "user://dev3_guild_contract_settings.json"
|
|
const SEED := "11223344556677889900aabbccddeeff"
|
|
|
|
var failures: Array[String] = []
|
|
var parsed_touches := 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
|
|
func run_test() -> void:
|
|
root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
|
|
root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
|
|
root.content_scale_size = Vector2i(844, 390)
|
|
root.size = Vector2i(844, 390)
|
|
_cleanup()
|
|
var company := _create_company()
|
|
var settings_store := SettingsStore.new()
|
|
settings_store.path = SETTINGS_PATH
|
|
var settings := settings_store.defaults()
|
|
settings.setup_completed = true
|
|
settings.locale = "en"
|
|
settings.text_scale = 1.3
|
|
settings_store.save_settings(settings)
|
|
var shell = await _shell(settings)
|
|
shell.show_guild_handoff(company)
|
|
await _frames(3)
|
|
expect(shell.current_screen == "guild_handoff", "opening Company reaches the real Guild")
|
|
var contract_button := shell.find_child("GuildContractBoard", true, false) as Button
|
|
var market_unavailable := shell.find_child("GuildMarketUnavailable", true, false) as Button
|
|
expect(contract_button != null and not contract_button.disabled, "Guild identifies Contract Board as the available next action")
|
|
expect(market_unavailable != null and market_unavailable.disabled, "Market remains disabled before Contract creation")
|
|
expect("first Contract" in shell.find_child("GuildMarketReason", true, false).text, "disabled Market gives an exact reason")
|
|
await _touch(contract_button)
|
|
expect(shell.current_screen == "contract_choice", "parsed touch opens Contract review")
|
|
expect(shell.find_child("ContractDifficultyStandard", true, false).get_meta("selected", false), "Standard is the initial explicit difficulty")
|
|
await _touch(shell.find_child("ContractDifficultyWayfinder", true, false))
|
|
expect(shell.current_screen == "contract_choice" and shell.contract_difficulty == "wayfinder", "difficulty changes without committing")
|
|
expect(_draft_service().load_company().company.active_contract.is_empty(), "difficulty selection leaves persistence unchanged")
|
|
await _touch(shell.find_child("ContractCancel", true, false))
|
|
expect(shell.current_screen == "guild_handoff" and _draft_service().load_company().company.active_contract.is_empty(), "Cancel returns to the same Guild with no Contract")
|
|
await _touch(shell.find_child("GuildContractBoard", true, false))
|
|
await _touch(shell.find_child("ContractDifficultyStandard", true, false))
|
|
expect(shell.contract_difficulty == "standard", "player can make a different legal choice after Cancel")
|
|
await _touch(shell.find_child("ContractConfirm", true, false))
|
|
expect(shell.current_screen == "market", "explicit confirm creates the Contract and reaches its Market")
|
|
var committed: Dictionary = _draft_service().load_company().company
|
|
expect(not committed.active_contract.is_empty() and committed.active_contract.difficulty == "standard", "confirmed difficulty is the only persisted choice")
|
|
expect(int(committed.active_contract.coin) == 52 and int(committed.active_contract.line_capacity) == 4, "Market handoff shows authoritative starting resources")
|
|
expect(committed.owned_recruits.size() == 2 and committed.active_contract.hire_receipts.is_empty(), "DEV-3B creates no Recruit purchase")
|
|
# The resource rail renders caption and value inside a hand-drawn badge
|
|
# (M58-C); the label carries the text now, with the compact LINE copy.
|
|
var company_badge := shell.find_child("MarketCompanyStatus", true, false) as Control
|
|
var company_label := company_badge.find_child("StatusLabel", true, false) as Label if company_badge != null else null
|
|
expect(company_label != null and company_label.text == "LINE 2/4", "visible status distinguishes Company from Line capacity")
|
|
expect(shell.find_child("MarketNoSelection", true, false) != null and shell.find_child("MarketOfferList", true, false).get_child_count() == 3, "Market opens with no recommended offer and all three Recruit rows")
|
|
var board_hash := String(committed.active_contract.market_board.board_hash)
|
|
await _touch(shell.find_child("MarketBackGuild", true, false))
|
|
expect(shell.current_screen == "guild_handoff", "Market Back returns to Guild")
|
|
var market_button := shell.find_child("GuildMarket", true, false) as Button
|
|
expect(market_button != null and not market_button.disabled and shell.find_child("GuildContractActive", true, false).disabled, "active Contract makes Market available and prevents a second Contract")
|
|
await _touch(market_button)
|
|
expect(shell.current_screen == "market", "active Contract opens the current Market")
|
|
# The reconstructed Market owns Back to Guild and Settings. Title is a
|
|
# Guild-owned utility boundary, so follow the approved player-order route
|
|
# instead of treating the read-only Market title label as a button.
|
|
await _touch(shell.find_child("MarketBackGuild", true, false))
|
|
expect(shell.current_screen == "guild_handoff", "Market Back returns to Guild")
|
|
await _touch(shell.find_child("GuildTitle", true, false))
|
|
expect(shell.current_screen == "title", "Guild Title command returns to Title without changing state")
|
|
await _touch(shell.find_child("TitleContinue", true, false))
|
|
expect(shell.current_screen == "market", "Continue resumes at the active Market destination")
|
|
shell.queue_free()
|
|
await _frames(3)
|
|
var restarted = await _shell(settings)
|
|
restarted._open_after_boot()
|
|
await _frames(4)
|
|
expect(restarted.current_screen == "market", "process restart resumes the committed Market")
|
|
var reloaded: Dictionary = _draft_service().load_company().company
|
|
expect(String(reloaded.active_contract.market_board.board_hash) == board_hash and reloaded.active_contract.hire_receipts.is_empty(), "restart preserves the exact board and zero purchases")
|
|
restarted.queue_free()
|
|
await _frames(3)
|
|
_cleanup()
|
|
if failures.is_empty():
|
|
print("ABG_DEV3_GUILD_OK guild_next=contract difficulty=select_cancel_confirm contract=once market_handoff=true company=2 line=4 coin=52 purchase=0 continue=market restart=true parsed_touch=%d" % parsed_touches)
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func _create_company() -> Dictionary:
|
|
var service := _draft_service()
|
|
var draft: Dictionary = service.begin_or_load(SEED)
|
|
var ids := [String(draft.draft.offers[0].offer_id), String(draft.draft.offers[2].offer_id)]
|
|
service.set_selection(ids)
|
|
var committed: Dictionary = service.commit_company()
|
|
expect(committed.ok, "fixture creates the persistent two-Recruit Company")
|
|
return committed.company if committed.ok else {}
|
|
|
|
|
|
func _shell(settings: Dictionary):
|
|
var shell = EntryShell.new()
|
|
shell.auto_start = false
|
|
shell.settings_store.path = SETTINGS_PATH
|
|
shell.initial_company_service.path = DRAFT_PATH
|
|
shell.initial_company_service.company_path = COMPANY_PATH
|
|
root.add_child(shell)
|
|
await _frames(3)
|
|
shell.settings = settings.duplicate(true)
|
|
return shell
|
|
|
|
|
|
func _draft_service() -> RefCounted:
|
|
var service := DraftService.new()
|
|
service.path = DRAFT_PATH
|
|
service.company_path = COMPANY_PATH
|
|
return service
|
|
|
|
|
|
func _touch(control: Control) -> void:
|
|
if control == null:
|
|
expect(false, "touch target exists")
|
|
return
|
|
var center := root.get_final_transform() * control.get_global_rect().get_center()
|
|
var press := InputEventScreenTouch.new()
|
|
press.position = center
|
|
press.pressed = true
|
|
press.index = 0
|
|
Input.parse_input_event(press)
|
|
await _frames(2)
|
|
var release := InputEventScreenTouch.new()
|
|
release.position = center
|
|
release.pressed = false
|
|
release.index = 0
|
|
Input.parse_input_event(release)
|
|
parsed_touches += 1
|
|
await _frames(5)
|
|
|
|
|
|
func _cleanup() -> void:
|
|
for base in [DRAFT_PATH, COMPANY_PATH, SETTINGS_PATH]:
|
|
for suffix in ["", ".bak", ".candidate", ".bak.candidate", ".corrupt"]:
|
|
var file_path := "%s%s" % [base, suffix]
|
|
if FileAccess.file_exists(file_path):
|
|
DirAccess.remove_absolute(file_path)
|
|
|
|
|
|
func _frames(count: int) -> void:
|
|
for frame in range(count):
|
|
await process_frame
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("DEV-3 Guild/Contract: %s" % message)
|