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.random_mode = Settings.RandomMode.NORMAL Settings.winning_score = 20 # Otherwise the About popup opens at start, as for a new version Settings.last_seen_version = Settings.version # Popups open and close at once, so the tests don't wait for animations Settings.reduce_animations = true _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+. func _give_point_with_keybind(number: int) -> void: await _press_keys([KEY_ALT, (KEY_0 + number) as Key]) ## Takes a point with Ctrl+Alt+. 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)