Files
TheTree_mock/tests/ui/test_supply_management.gd
T
Takeshi b84bade045 Save The Tree implementation and acceptance work in progress
Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
2026-10-11 15:58:00 +09:00

268 lines
13 KiB
GDScript

extends SceneTree
const AudioTeardown := preload("res://tests/helpers/ui_audio_teardown.gd")
## Normal build/purchase commands: UI drafts, money/stock reconciliation, queued
## purchases and an isolated save slot. No free inventory is inserted.
var _checks := 0
var _failures := 0
var _main: Control
var _panel: SupplyManagementPanel
var _runtime: Variant
var _clock: Variant
var _warehouse: StringName = &""
var _kitchens: Array[StringName] = []
var _item: StringName = &""
var _save_path := "user://tests/supply_ui_%d/queued_purchase" % OS.get_process_id()
func _test_layer_palette() -> void:
_main.call("_set_layer", 0)
var before: Color = _panel.warehouse_stock_label.get_theme_color("font_color")
var selected: bool = _panel.production_enabled.button_pressed
_main.call("_set_layer", 1)
_panel.refresh()
_expect(_panel.warehouse_stock_label.get_theme_color("font_color") == Color("d8eeea"), "BACK panel text survives refresh")
_expect(_panel.production_enabled.get_theme_color("font_hover_color") == Color("d8eeea") and _panel.production_enabled.get_theme_color("font_disabled_color") == Color("8da4a4"), "BACK toggle remains readable for hover and disabled states")
_panel.call("_set_result", "条件未達", false)
_expect(_panel.result_label.get_theme_color("font_color") == Color("ffb498"), "BACK error retains warning colour")
_main.call("_set_layer", 0)
_expect(_panel.warehouse_stock_label.get_theme_color("font_color") == before and _panel.result_label.get_theme_color("font_color") == Color("a7402e"), "FRONT restores panel and warning colours")
_expect(_panel.production_enabled.button_pressed == selected, "palette does not toggle policy")
_panel.call("_set_result", "", true)
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
_clock = root.get_node("GameClock")
_runtime = root.get_node("SimulationRuntime")
var state: Variant = root.get_node("GameState")
_clock.reset(1, 6, 0)
state.reset()
_runtime.initialize(_clock, state, 824242)
_clock.set_paused(true)
root.size = Vector2i(1280, 720)
_main = load("res://scenes/main/main.tscn").instantiate() as Control
_main.set("manual_save_path", "user://tests/supply_ui_%d/manual" % OS.get_process_id())
root.add_child(_main)
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
await process_frame
await process_frame
_panel = _main.find_child("SupplyManagementPanel", true, false) as SupplyManagementPanel
_expect(_panel != null, "normal scene includes supply management")
if _panel == null:
await _finish()
return
(_main.get("navigation_buttons")[&"product"] as Button).pressed.emit()
_expect(_panel.visible and _panel.purchase_button.disabled, "product opens a safe empty state before any warehouse exists")
_expect("倉庫" in _panel.warehouse_stock_label.text, "empty purchase page explains the warehouse prerequisite")
_test_layer_palette()
for request: Array in [
[&"warehouse", Vector3i(-8, 0, 1)],
[&"kitchen", Vector3i(0, 0, 1)],
[&"kitchen", Vector3i(6, 0, 1)],
]:
var before: int = _runtime.construction_service.get_jobs().size()
_main.call("_select_build_definition", request[0])
_main.call("_on_map_cell_clicked", request[1])
_expect(_runtime.construction_service.get_jobs().size() == before + 1, "normal UI builds %s" % request[0])
_clock.advance_minutes(180)
for facility: FacilityInstance in _runtime.facility_service.get_instances():
if facility.definition_id == &"warehouse":
_warehouse = facility.instance_id
elif facility.definition_id == &"kitchen":
_kitchens.append(facility.instance_id)
_expect(not _warehouse.is_empty() and _kitchens.size() == 2, "paid construction produces a warehouse and two kitchens")
if _warehouse.is_empty() or _kitchens.size() != 2:
await _finish()
return
_main.call("_activate_management_mode", &"product")
_panel.refresh()
_item = _panel.selected_item_id
_expect(not _item.is_empty(), "Runtime purchase catalog supplies a real item")
_test_purchase_and_failure()
_test_kitchen_drafts()
_test_saved_pending_purchase()
await _test_navigation_and_layout()
await _finish()
func _test_purchase_and_failure() -> void:
_choose(_panel.warehouse_option, _warehouse)
_panel.quantity_spin.value = 4
var quote: Dictionary = _runtime.preview_supply_purchase(_warehouse, _item, 4)
_expect(bool(quote.get(&"ok", false)), "Runtime quotes a paid purchase for the completed warehouse")
var balance_before: int = _runtime.economy_service.get_balance(&"PLAYER_CASH")
var stock_before := _stock()
_panel.purchase_button.pressed.emit()
_expect(_stock() == stock_before + 4, "purchase button credits the warehouse's actual inventory")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == balance_before - int(quote.get(&"total_cost", -1)),
"purchase pays exactly the authoritative quoted cost")
_expect("仕入れました" in _panel.result_label.text, "successful purchase is visible in Japanese")
# A deliberately oversized draft must fail without reducing cash or stock.
_panel.quantity_spin.allow_greater = true
_panel.quantity_spin.value = int(quote.get(&"max_quantity", 100)) + 100
var invalid_quantity := int(_panel.quantity_spin.value)
var balance_after: int = _runtime.economy_service.get_balance(&"PLAYER_CASH")
var stock_after := _stock()
_expect(_panel.purchase_button.disabled, "invalid quoted quantity disables purchase before submission")
_panel.submit_purchase()
_expect(_stock() == stock_after and _runtime.economy_service.get_balance(&"PLAYER_CASH") == balance_after,
"rejected purchase never partially spends money or inserts stock")
_expect(int(_panel.quantity_spin.value) == invalid_quantity and not _panel.result_label.text.is_empty(),
"rejected purchase preserves the quantity and an actionable result")
_panel.quantity_spin.allow_greater = false
_panel.quantity_spin.value = 2
func _test_kitchen_drafts() -> void:
_panel.select_tab(&"kitchen")
_choose(_panel.kitchen_option, _kitchens[0])
_panel.production_enabled.button_pressed = true
_panel.target_spin.value = 4
_panel.select_tab(&"purchase")
_clock.advance_minutes(1)
_panel.select_tab(&"kitchen")
_expect(_panel.production_enabled.button_pressed and int(_panel.target_spin.value) == 4,
"time updates and tab changes preserve an unsaved kitchen draft")
_clock.set_paused(false)
_panel.production_button.pressed.emit()
_expect(_panel.production_button.disabled, "queued kitchen setting disables another submission for that kitchen")
_choose(_panel.kitchen_option, _kitchens[1])
_panel.production_enabled.button_pressed = true
_panel.target_spin.value = 8
_clock.advance_minutes(1)
_clock.set_paused(true)
_expect(_panel.selected_kitchen_id == _kitchens[1] and int(_panel.target_spin.value) == 8
and _panel.production_enabled.button_pressed, "another kitchen's completion preserves the selected kitchen's draft")
var configured := _kitchen_row(_kitchens[0])
_expect(bool(configured.get(&"enabled", false)) and int(configured.get(&"target_stock", -1)) == 4,
"saved kitchen settings reach the authoritative Runtime model")
_panel.production_button.pressed.emit()
var second := _kitchen_row(_kitchens[1])
_expect(bool(second.get(&"enabled", false)) and int(second.get(&"target_stock", -1)) == 8,
"second kitchen applies its own saved target")
_expect("職員" in _panel.kitchen_status_label.text or "経路" in _panel.kitchen_status_label.text,
"unready kitchen explains its authoritative staffing or route blocker in Japanese")
_panel.production_enabled.button_pressed = false
_panel.production_button.pressed.emit()
_expect(not bool(_kitchen_row(_kitchens[1]).get(&"enabled", true)), "automatic production can be stopped through the same controls")
func _test_saved_pending_purchase() -> void:
_panel.select_tab(&"purchase")
_panel.quantity_spin.value = 2
var stock_before := _stock()
var balance_before: int = _runtime.economy_service.get_balance(&"PLAYER_CASH")
var quote: Dictionary = _runtime.preview_supply_purchase(_warehouse, _item, 2)
_clock.set_paused(false)
_panel.purchase_button.pressed.emit()
var pending_before: int = _runtime.command_queue.create_snapshot().get("pending", []).size()
_panel.submit_purchase()
_expect(pending_before == 1 and _runtime.command_queue.create_snapshot().get("pending", []).size() == 1,
"repeated purchase cannot enqueue the same draft twice")
var saved: Dictionary = _runtime.save_to_path(_save_path)
_expect(bool(saved.get(&"ok", false)), "queued purchase saves to an isolated test slot")
_clock.advance_minutes(1)
var loaded: Dictionary = _runtime.load_from_path(_save_path)
_expect(bool(loaded.get(&"ok", false)), "pending purchase reloads successfully")
_panel.refresh()
_expect(_panel.purchase_button.disabled, "restored pending purchase cannot be resubmitted")
_clock.advance_minutes(1)
_clock.set_paused(true)
_panel.refresh()
_expect(_stock() == stock_before + 2 and _runtime.economy_service.get_balance(&"PLAYER_CASH") == balance_before - int(quote.get(&"total_cost", -1)),
"restored purchase credits and charges exactly once")
_expect(not _panel.purchase_button.disabled, "completed restored command releases purchase controls")
var digest: String = _runtime.latest_digest
_panel.refresh()
_panel.select_tab(&"delivery")
_expect(_runtime.latest_digest == digest, "displaying stock and deliveries never mutates simulation")
_expect(not (_panel.get_report().get(&"deliveries", []) as Array).is_empty(),
"real warehouse stock creates visible kitchen replenishment requests")
_expect("倉庫" in _panel.delivery_label.text and "厨房" in _panel.delivery_label.text,
"delivery page resolves source and destination to identifiable facilities")
_expect("運搬員" in _panel.delivery_label.text and "経路" in _panel.delivery_label.text,
"waiting delivery explains the actual porter or route shortage in Japanese")
func _test_navigation_and_layout() -> void:
_main.call("_activate_management_mode", &"ledger")
_expect(not _panel.visible and (_main.get("operating_ledger_panel") as Control).visible, "ledger replaces supply controls")
_main.call("_unhandled_key_input", _key(KEY_H))
_expect(not _panel.visible and (_main.get("staff_candidate_panel") as Control).visible, "H returns to the hiring candidates")
_main.call("_activate_management_mode", &"product")
await process_frame
await process_frame
var side := _main.find_child("SidePanel", true, false) as Control
var guide := _main.find_child("InnOpeningGuide", true, false) as Control
_expect(not side.get_global_rect().intersects(guide.get_global_rect()), "1280x720 keeps supply inspector clear of opening guidance")
_expect(_panel.get_global_rect().position.x >= side.get_global_rect().position.x
and _panel.get_global_rect().end.x <= side.get_global_rect().end.x,
"supply tabs and inputs fit inside the existing sidebar width")
if OS.get_environment("THE_TREE_SUPPLY_UI_CAPTURE") == "1":
for tab: StringName in [&"purchase", &"kitchen", &"delivery"]:
_panel.select_tab(tab)
if tab == &"kitchen":
_choose(_panel.kitchen_option, _kitchens[0])
await process_frame
await RenderingServer.frame_post_draw
var captured := root.get_texture().get_image()
captured.save_png("/tmp/the-tree-supply-%s.png" % tab)
if tab == &"purchase":
captured.save_png("/tmp/the-tree-supply-management.png")
if OS.get_environment("THE_TREE_PANEL_SKIN_CAPTURE") == "1":
_main.call("_set_layer", 1)
_panel.select_tab(&"kitchen")
_choose(_panel.kitchen_option, _kitchens[0])
await process_frame;await RenderingServer.frame_post_draw
_expect(root.get_texture().get_image().save_png(ProjectSettings.globalize_path("res://artifacts/facility-dimensions-2026-09-29/supply-skin-back.png")) == OK, "capture BACK management skin")
_main.call("_unhandled_key_input", _key(KEY_ESCAPE))
_expect(not _panel.visible and not bool(_main.get("_management_panel_open")), "Escape closes supply management and restores the map")
func _stock() -> int:
var facility: FacilityInstance = _runtime.facility_service.get_instance(_warehouse)
return _runtime.inventory_service.total_quantity(facility.local_inventory_id, _item)
func _kitchen_row(id: StringName) -> Dictionary:
for row: Dictionary in _runtime.get_supply_management_report().get(&"kitchens", []):
if StringName(row.get(&"facility_id", &"")) == id:
return row
return {}
func _choose(picker: OptionButton, id: StringName) -> void:
for index: int in picker.item_count:
if StringName(picker.get_item_metadata(index)) == id:
picker.select(index)
picker.item_selected.emit(index)
return
_expect(false, "requested facility appears in the relevant supply dropdown: %s" % id)
func _key(code: Key) -> InputEventKey:
var event := InputEventKey.new()
event.keycode = code
event.pressed = true
return event
func _expect(condition: bool, message: String) -> void:
_checks += 1
if not condition:
_failures += 1
push_error(message)
func _finish() -> void:
SaveService.delete_save(_save_path)
var teardown: Dictionary = await AudioTeardown.free_owner_and_wait(self, _main)
_expect(bool(teardown.get(&"passed", false)) and not bool(teardown.get(&"deadline_reached", true)), "owned audio references release before bounded teardown deadline with whole Runtime unchanged")
print("Supply management UI: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures > 0 else 0)