Files
MusicPlayer/tests/fakes/main_window_test_suite.gd
T
SansanandClaude Opus 5.5 70e970b24c
Build / test (push) Successful in 1m30s
Build / build (push) Successful in 1m17s
Make the buttons consistent and rework the player list
Buttons get 12 px of side padding. The top bar buttons, the quiz toolbar and
Show answer/Next are laid out in rows with 12 px gaps and are all 36 px tall,
and the ±1 buttons use a compact StepButton style. "Randomize new track" is now
"Next song".

Player rows show the name and the score apart, alternate like the other lists
and highlight the character under the mouse. The name field is a LineEdit with
a 10-letter limit; blank and duplicate names are refused, and a full list says
"Up to 7 players". Remove buttons update when editing is switched instead of
every frame, and the spacer panels are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:35:24 +02:00

143 lines
4.9 KiB
GDScript

class_name MainWindowTestSuite
extends GdUnitTestSuite
## Base for tests that run the real main window against a fake MusicServer.
const SONG_INFO: Array[Dictionary] = [
{"SongNo": 0, "Soundtrack": "Chrono Trigger", "Song": "Corridors of Time"},
{"SongNo": 1, "Soundtrack": "Final Fantasy VI", "Song": "Dancing Mad"},
]
var _server: FakeHttpTransport
var _snapshot: AutoloadSnapshot
var _runner: GdUnitSceneRunner
var _window: Control
var _quiz: Quiz
func before_test() -> void:
_snapshot = AutoloadSnapshot.take([Settings, Playlist])
Playlist.reset_playlist()
Playlist.first_song_played = false
Playlist.is_song_downloading = false
Settings.player_array.clear()
Settings.music_folder = null
Settings.character_folder = ""
# Every test starts a new quiz, unless it saves one in _before_window_starts()
QuizSave.delete()
Settings.add_to_stats = true
Settings.hide_next_track = true
Settings.use_low_played_mode = false
Settings.winning_score = 20
# Otherwise the About popup opens at start, as for a new version
Settings.last_seen_version = Settings.version
_server = FakeHttpTransport.new().install()
var mp3: PackedByteArray = FileAccess.get_file_as_bytes("res://tests/data/short.mp3")
_server.respond_json("/music/all", ["Chrono Trigger", "Final Fantasy VI"])
_server.respond_json("/music/all/order", ["Chrono Trigger", "Final Fantasy VI"])
_server.respond_json("/music/list", [])
_server.respond_json("/character/list", [])
_server.respond_json("/download", "1.0.0")
_server.respond_bytes("/music/rand", mp3)
_server.respond_bytes("/music/addQue", PackedByteArray())
_server.respond_json("/music/info", SONG_INFO[0])
_server.respond_bytes("/music/reset", PackedByteArray())
_server.respond_bytes("/music/played?song=0", PackedByteArray())
_before_window_starts()
_runner = scene_runner("res://main/MainWindow.tscn")
_window = _runner.scene()
_quiz = _window.get_node("Quiz")
# Rows of buttons are laid out by their containers at the end of the first frame
await _runner.simulate_frames(1)
## Override to change the fake server before the window starts and sends its first
## requests.
func _before_window_starts() -> void:
pass
func after_test() -> void:
# Let nodes queued for deletion by the window be freed before orphans are counted
await await_idle_frame()
QuizSave.delete()
_server.uninstall()
_snapshot.restore()
## Makes the next song the server hands out the one described by [param info].
func _next_song_is(info: Dictionary) -> void:
_server.respond_json("/music/info", info)
## Presses a button in the quiz.
func _press(button_path: String) -> void:
(_quiz.get_node(button_path) as Button).pressed.emit()
## Presses Next and waits until the first song is playing.
func _play_first_song() -> void:
_press("SongControls/NextButton")
await _runner.simulate_frames(2)
## Adds a player through the name field, the same way the user does.
func _add_player(player_name: String) -> PlayerObject:
var name_field: LineEdit = _quiz.get_node(
"PlayersPanel/VBoxContainer/AddPlayerContainer/PlayerNameField")
name_field.text = player_name
_quiz.players_panel.add_player()
return Settings.player_array.back() if !Settings.player_array.is_empty() else null
## Returns the row in the player list that shows [param player_object].
func _player_row(player_object: PlayerObject) -> PlayerRow:
var rows: Node = _quiz.get_node(
"PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer")
for row: PlayerRow in rows.get_children():
if row.player_object == player_object:
return row
return null
## Removes a player with the remove button in its row.
func _remove_player(player_object: PlayerObject) -> void:
(_player_row(player_object).get_node("%RemovePlayer") as BaseButton) \
.pressed.emit()
await _runner.simulate_frames(1)
## Clicks at [param position], given in the window's 1920x1080 layout coordinates.
func _click_at(position: Vector2) -> void:
# The runner takes window pixels; the headless test window is much smaller than the
# layout, so convert like a real mouse position would be
_runner.set_mouse_position(_window.get_viewport().get_final_transform() * position)
await _runner.simulate_mouse_button_pressed(MOUSE_BUTTON_LEFT)
await _runner.await_input_processed()
## Presses [param keys] together, e.g. [code][KEY_ALT, KEY_1][/code], then releases them.
func _press_keys(keys: Array[Key]) -> void:
for key: Key in keys:
_runner.simulate_key_press(key)
await _runner.await_input_processed()
keys.reverse()
for key: Key in keys:
_runner.simulate_key_release(key)
await _runner.await_input_processed()
## Gives a point with Alt+<number>.
func _give_point_with_keybind(number: int) -> void:
await _press_keys([KEY_ALT, (KEY_0 + number) as Key])
## Takes a point with Ctrl+Alt+<number>.
func _take_point_with_keybind(number: int) -> void:
await _press_keys([KEY_CTRL, KEY_ALT, (KEY_0 + number) as Key])
func _scores() -> Array:
return Settings.player_array.map(func(p: PlayerObject) -> int: return p.player_score)