Replace the broken test suite with working GdUnit4 tests
The previous tests never compiled: they used GUT's assert functions, Godot 3 APIs and reassigned autoload functions, so no test ran. - 90 tests: unit tests for the playlist, songs, player scoring, time formatting, search and the server client, plus integration tests that run MainWindow and SyncWindow against a fake server - Keybind tests check that Alt+<number> and Ctrl+Alt+<number> keep following the player rows while players are added and removed - run_tests.sh finds Godot and runs the suite headless (~10 seconds) - CI runs the tests before the build job in gitea-build.yaml; the separate test.yml used a GitHub action that does not exist - GdUnit4 is committed instead of ignored, since the project enables it, and .gitea/ is no longer ignored - Exported builds leave out tests/ and addons/gdUnit4/
This commit is contained in:
1 parent
56e9fe66d3
commit
c985a6ce5b
559 files changed
+32474
-2100
No files matched your search
@@ -0,0 +1,115 @@
|
||||
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
|
||||
|
||||
|
||||
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.add_to_stats = true
|
||||
Settings.hide_next_track = true
|
||||
Settings.use_low_played_mode = false
|
||||
Settings.winning_score = 20
|
||||
|
||||
_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("/characters", [])
|
||||
_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())
|
||||
|
||||
_runner = scene_runner("res://MainWindow.tscn")
|
||||
_window = _runner.scene()
|
||||
# The debug build lowers the winning score on startup; keep it out of reach
|
||||
Settings.winning_score = 20
|
||||
|
||||
|
||||
func after_test() -> void:
|
||||
# Let nodes queued for deletion by the window be freed before orphans are counted
|
||||
await await_idle_frame()
|
||||
_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)
|
||||
|
||||
|
||||
func _press(button_path: String) -> void:
|
||||
(_window.get_node(button_path) as Button).pressed.emit()
|
||||
|
||||
|
||||
## Presses Next and waits until the first song is playing.
|
||||
func _play_first_song() -> void:
|
||||
_press("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 = _window.get_node(
|
||||
"Players/VBoxContainer/AddPlayerContainer/PlayerNameField")
|
||||
name_field.text = player_name
|
||||
_runner.invoke("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) -> Player:
|
||||
var rows: Node = _window.get_node(
|
||||
"Players/VBoxContainer/ScrollContainer/PlayersListVBoxContainer")
|
||||
for row: Player 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)
|
||||
|
||||
|
||||
## 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)
|
||||
Reference in new issue
Block a user