Files
Sansan a21b6e9b22
Build / test (push) Successful in 40s
Build / build (push) Successful in 1m16s
Add a Statistics tab with a card for every soundtrack
Part of #41. The top bar has Quiz and Statistics tabs instead of the
Statistics button. The Statistics tab replaces the quiz below the top bar
(the song keeps playing) and shows every soundtrack as a card with its
plays, last played date and most and least played songs, loaded from the
new GET /api/v1/statistics/soundtracks. The cards can be searched (with
the Search popup's matching, now in SoundtrackSearch), filtered on played
or never played, and sorted A-Z, Z-A, most or least played. Clicking a
card opens a popup with all its songs.

Screenshots of the other states only differ in the top bar, and the
layout baseline is taken again with the new statistics states.
2026-10-06 23:33:00 +02:00

3.6 KiB

Testing

Tests use GdUnit4 6.2.1, committed in addons/gdUnit4/. The whole suite runs headless in about 10 seconds.

Running the tests

./run_tests.sh                                   # everything
./run_tests.sh res://tests/unit                  # only unit tests
./run_tests.sh res://tests/unit/test_search.gd   # one suite

The script finds Godot through $GODOT, then godot on your PATH, then /Applications/Godot.app or /Applications/Godot_mono.app. Reports are written to reports/ (ignored by git).

In the editor, open the GdUnit4 panel or right-click the tests folder and choose Run Tests.

CI runs the suite in the test job of .gitea/workflows/gitea-build.yaml on every push and pull request to main and develop. The build job only starts once the tests pass.

Layout

Folder Contents
tests/unit/ Logic without scenes: playlist, songs, player scoring, time formatting, search, the server client and the API classes
tests/integration/ Real scenes (MainWindow.tscn, SyncPopup.tscn) run against a fake server, including the point keybinds and the Statistics tab
tests/fakes/ FakeHttpTransport, AutoloadSnapshot and the MainWindowTestSuite base class
tests/data/ Files the fake server sends, e.g. a short MP3

Faking the MusicServer

All server calls go through the MusicServerClient autoload, which sends requests with a swappable transport. Tests install a FakeHttpTransport that answers from canned responses and records what was requested:

var server: FakeHttpTransport = FakeHttpTransport.new().install()
server.respond_json("/music/list", [{"SongNo": 0, "Soundtrack": "Chrono Trigger", "Song": "Frog's Theme"}])
server.respond_bytes("/music/rand", mp3_bytes)

# ... run the code under test ...

assert_int(server.count("/music/rand")).is_equal(1)
server.uninstall()

Paths are relative to the API (/music/list is <server>/api/v1/music/list). Paths without a response get a 404, so the code under test sees a failed request.

Like the real server, the fake hands out tokens on POST /token and answers 401 to requests without a valid one, so the client's token handling runs in every test. Token requests are counted in server.token_requests, not in requested_paths. Use server.expire_tokens() to make the client renew its token, server.hold("/token") to keep it waiting for one, and server.respond_json("/token", {...}, 401) for a refused password.

API classes

The classes in server/generated/ (SongInfo, StatisticsSummary, ...) are generated from the server's OpenAPI spec, committed as server/openapi.json. To update them, copy a new spec there (just openapi in MusicServer, or <server>/openapi.json) and run:

godot --headless --path . -s tools/generate_api_types.gd

tests/unit/test_api_types.gd parses a sample response with each class.

Writing tests

  • Settings and Playlist are global. Tests that change them restore them afterwards, either by hand or with AutoloadSnapshot.take([Settings, Playlist]) and restore().
  • Main window tests extend MainWindowTestSuite, which starts the window against the fake server and has helpers such as _add_player(), _remove_player() and _give_point_with_keybind().
  • Player ids are UUID strings that never change. A player's position in Settings.player_array only decides their keybind (Alt+1 for the first row and so on), so tests check both: that points land on the right player id, and that keybinds follow the rows when players are added or removed.