Short songs (#30): a quiz settings switch and a Seconds per song row (30 by default, 10-120 in steps of 5), saved with the quiz settings. Only part of each quiz song plays, from a random point 10-60% into it, picked once per song so restart and repeat play the same part. The progress bar, time and seeking stay within the part, a hidden beginning counts inside it, and its end counts as the song ending (repeat, stop or go to the next song as set). Random songs (#51): the low-played switch in Settings is now a Normal / Low played / Classic choice, stored as Settings.random_mode and not saved. fetch_random_song(mode) picks /music/rand, /music/rand/low or /music/rand/classic. Closes #30 Closes #51 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
145 lines
5.0 KiB
GDScript
145 lines
5.0 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.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+<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)
|