ajhahn.de
← Theria
GDScript 136 lines
extends GutTest
## Behavioural checks on the connect menu — the screen shown on a windowed launch
## with no mode flag. They verify each choice maps to the right signal and that a
## blank address falls back to the injected default. The menu owns no networking, so
## this is the whole of its logic; the driver wiring and the live socket are the host
## smoke's job, not these unit tests.


# Builds the menu in the scene tree so `_ready` lays out its controls (the address
# field, the buttons), and frees it when the test ends.
func _menu() -> ConnectMenu:
	var menu := ConnectMenu.new()
	add_child_autoqfree(menu)
	return menu


# Finds the picker item carrying `hero` as metadata and selects it; fails the test if
# no item matches, so a typo'd hero name surfaces rather than silently selecting nothing.
func _select_hero(menu: ConnectMenu, hero: String) -> void:
	for i in menu._hero_picker.item_count:
		if menu._hero_picker.get_item_metadata(i) == hero:
			menu._hero_picker.select(i)
			return
	fail_test("hero %s is not in the picker" % hero)


# Selects the difficulty item carrying `level` as metadata; fails if none matches.
func _select_difficulty(menu: ConnectMenu, level: String) -> void:
	for i in menu._difficulty_picker.item_count:
		if menu._difficulty_picker.get_item_metadata(i) == level:
			menu._difficulty_picker.select(i)
			return
	fail_test("difficulty %s is not in the picker" % level)


func test_practice_choice_requests_a_local_match() -> void:
	var menu := _menu()
	watch_signals(menu)
	menu._on_practice_pressed()
	# With no injected defaults the pickers rest on the first roster hero (the Solane lead)
	# and the first difficulty (Easy).
	assert_signal_emitted_with_parameters(menu, "practice_requested", ["lion", "easy"])


func test_practice_carries_the_picked_hero() -> void:
	var menu := _menu()
	_select_hero(menu, "chameleon")
	watch_signals(menu)
	menu._on_practice_pressed()
	assert_signal_emitted_with_parameters(menu, "practice_requested", ["chameleon", "easy"])


func test_practice_carries_the_picked_difficulty() -> void:
	var menu := _menu()
	_select_difficulty(menu, "hard")
	watch_signals(menu)
	menu._on_practice_pressed()
	assert_signal_emitted_with_parameters(menu, "practice_requested", ["lion", "hard"])


func test_injected_default_preselects_its_difficulty() -> void:
	# default_difficulty must be set before `_ready` populates, so build it outside `_menu()`.
	var menu := ConnectMenu.new()
	menu.default_difficulty = "normal"
	add_child_autoqfree(menu)
	var selected: String = menu._difficulty_picker.get_item_metadata(menu._difficulty_picker.selected)
	assert_eq(selected, "normal", "the driver's default difficulty is pre-selected")


func test_picker_offers_every_tribe_hero() -> void:
	var menu := _menu()
	var total := 0
	for tribe in AbilityData.TRIBE:
		total += (AbilityData.TRIBE[tribe] as Array).size()
	assert_eq(menu._hero_picker.item_count, total, "every hero in every tribe is offered")
	assert_eq(menu._hero_picker.get_item_metadata(0), "lion", "the first item is the Solane lead")


func test_injected_default_preselects_its_hero() -> void:
	# default_hero must be set before `_ready` populates, so build it outside `_menu()`.
	var menu := ConnectMenu.new()
	menu.default_hero = "spider"
	add_child_autoqfree(menu)
	var selected: String = menu._hero_picker.get_item_metadata(menu._hero_picker.selected)
	assert_eq(selected, "spider", "the driver's default hero is pre-selected")


func test_host_choice_requests_a_host() -> void:
	var menu := _menu()
	watch_signals(menu)
	menu._on_host_pressed()
	assert_signal_emitted(menu, "host_requested")


func test_join_carries_the_typed_address() -> void:
	var menu := _menu()
	menu._address_field.text = "10.0.0.7"
	watch_signals(menu)
	menu._on_join_pressed()
	assert_signal_emitted_with_parameters(menu, "join_requested", ["10.0.0.7"])


func test_blank_address_falls_back_to_the_default() -> void:
	var menu := _menu()
	menu.default_address = "203.0.113.9"
	menu._address_field.text = "   "  # whitespace only is treated as blank
	watch_signals(menu)
	menu._on_join_pressed()
	assert_signal_emitted_with_parameters(menu, "join_requested", ["203.0.113.9"])


func test_settings_dialog_preselects_the_saved_channel() -> void:
	# The Settings dialog builds its channel picker on the saved channel, so reopening it
	# always shows the player's current choice rather than resetting to a default.
	var menu := _menu()
	var dialog := menu._build_settings_dialog()
	add_child_autoqfree(dialog)
	var picker: OptionButton = dialog.find_children("", "OptionButton", true, false)[0]
	assert_eq(
		picker.get_item_metadata(picker.selected),
		Settings.update_channel(),
		"the channel picker opens on the saved channel"
	)


func test_settings_dialog_offers_a_manual_update_check() -> void:
	# The Settings dialog carries a force-check button so a player who hears a new build is out can
	# pull it without waiting out the launch-time throttle.
	var menu := _menu()
	var dialog := menu._build_settings_dialog()
	add_child_autoqfree(dialog)
	var labels := PackedStringArray()
	for button in dialog.find_children("", "Button", true, false):
		labels.append((button as Button).text)
	assert_has(labels, "Check for updates now", "the Settings dialog offers a manual update check")