The quiz is saved to current_quiz.json in the config folder after every change, and the game goes on where it was when it starts again: the players with their points and characters, every song with who got a point for it and whether it was played, answered or is playing, and the songs downloaded ahead with their files. The song that was playing is ready to play again. Songs whose files are gone are downloaded again. On a server, the saved songs are checked against the server's playlist. If it changed, e.g. after a sync, or another server is used, the quiz starts over with the same players. A quiz from a music folder continues offline, including how far a prepared quiz has come. A new game is saved like any other change, and a file that can't be read starts a new quiz. Characters can be read from a folder picked in Settings instead of the server, for this session. A music folder with a characters folder brings its own characters, e.g. for a themed quiz; that folder is not counted as a soundtrack. When the characters come from somewhere else, players pick their characters again. Closes #11, closes #44
141 lines
4.8 KiB
GDScript
141 lines
4.8 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")
|
|
|
|
|
|
## 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: TextEdit = _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("HBoxContainer/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)
|