diff --git a/MainWindow.gd b/MainWindow.gd index 7c28c98..cf2ce1b 100644 --- a/MainWindow.gd +++ b/MainWindow.gd @@ -186,7 +186,6 @@ func _ready() -> void: get_suggestion_list() fetch_song_list_at_start() - fetch_song() search_view.get_list_of_soundtracks() new_character_select.load_characters() @@ -240,14 +239,12 @@ func show_answer_pressed() -> void: Playlist.song_is_answered() update_song_list() +# Loads the songs already in the server playlist, then starts preloading new ones. +# Preloading waits for the list, because loading the list clears the playlist. func fetch_song_list_at_start() -> void: print("fetch_song_list_at_start") var fetch_song_list_at_start_done: Callable = func fetch_songs_at_start_done(data) -> void: Playlist.clear_song_list() - if data == null: - Playlist.update_last_played_song() - update_song_list() - return if typeof(data) == TYPE_ARRAY: for d: Dictionary in data: var new_song_object: SongObject = SongObject.new() @@ -257,11 +254,14 @@ func fetch_song_list_at_start() -> void: new_song_object.is_answered = true new_song_object.has_played = true Playlist.add_to_playlist(new_song_object) - else: + elif data != null: print("Unexpected data") Playlist.update_last_played_song() update_song_list() - MusicServerClient.fetch_song_list(fetch_song_list_at_start_done) + if !Playlist.is_song_downloading: + fetch_song() + MusicServerClient.fetch_song_list(fetch_song_list_at_start_done, + _retry_later.bind(fetch_song_list_at_start)) func play_next_song() -> void: print("play_next_song ", Playlist.latest_played_song) @@ -360,7 +360,6 @@ func reset_playlist() -> void: var playlist_reset_done: Callable = func playlist_reset_done() -> void: Playlist.reset_playlist() fetch_song_list_at_start() - fetch_song() MusicServerClient.reset_playlist(playlist_reset_done) diff --git a/tests/fakes/fake_http_transport.gd b/tests/fakes/fake_http_transport.gd index 7521215..3cbf698 100644 --- a/tests/fakes/fake_http_transport.gd +++ b/tests/fakes/fake_http_transport.gd @@ -13,6 +13,7 @@ var requested_methods: Array[int] = [] var _responses: Dictionary = {} var _successes_left: Dictionary = {} +var _held_paths: Dictionary = {} var _previous_transport: HttpTransport @@ -49,6 +50,20 @@ func fail_after(path: String, successes: int) -> void: _successes_left[path] = successes +## Holds back responses to [param path] until [method release] is called, like a slow +## server. +func hold(path: String) -> void: + _held_paths[path] = [] + + +## Delivers the responses held back for [param path] and stops holding it. +func release(path: String) -> void: + var waiting: Array = _held_paths.get(path, []) + _held_paths.erase(path) + for pending: Callable in waiting: + pending.call() + + ## Returns how many times [param path] has been requested. func count(path: String) -> int: return requested_paths.count(path) @@ -58,6 +73,13 @@ func request(url: String, method: int, on_complete: Callable) -> void: var path: String = url.trim_prefix(Settings.default_path) requested_paths.append(path) requested_methods.append(method) + if _held_paths.has(path): + _held_paths[path].append(_answer.bind(path, on_complete)) + return + _answer(path, on_complete) + + +func _answer(path: String, on_complete: Callable) -> void: if _successes_left.has(path): if _successes_left[path] == 0: on_complete.call(503, PackedByteArray()) diff --git a/tests/fakes/main_window_test_suite.gd b/tests/fakes/main_window_test_suite.gd index 6bf3490..8ac4fb0 100644 --- a/tests/fakes/main_window_test_suite.gd +++ b/tests/fakes/main_window_test_suite.gd @@ -36,12 +36,19 @@ func before_test() -> void: _server.respond_bytes("/music/reset", PackedByteArray()) _server.respond_bytes("/music/played?song=0", PackedByteArray()) + _before_window_starts() _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 +## 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() diff --git a/tests/integration/test_main_window_startup.gd b/tests/integration/test_main_window_startup.gd new file mode 100644 index 0000000..7acffb2 --- /dev/null +++ b/tests/integration/test_main_window_startup.gd @@ -0,0 +1,41 @@ +extends MainWindowTestSuite +## Startup with a slow /music/list response. + + +func _before_window_starts() -> void: + _server.respond_json("/music/list", [ + {"SongNo": 0, "Soundtrack": "Chrono Trigger", "Song": "Corridors of Time"}, + ]) + _next_song_is({"SongNo": 1, "Soundtrack": "Final Fantasy VI", "Song": "Dancing Mad"}) + _server.hold("/music/list") + + +func test_preloaded_song_survives_a_slow_song_list() -> void: + _server.release("/music/list") + + # The song already on the server, then the preloaded one + assert_int(Playlist.song_object_array.size()).is_equal(2) + assert_str(Playlist.song_object_array[1].song_title).is_equal("Dancing Mad") + assert_int(Playlist.get_preloaded_songs()).is_equal(1) + + +func test_next_plays_the_preloaded_song_after_a_slow_song_list() -> void: + _server.release("/music/list") + + await _play_first_song() + + assert_int(Playlist.currently_playing_song).is_equal(1) + + +func test_failed_song_list_is_retried_before_preloading() -> void: + _window.song_fetch_retry_seconds = 0.05 + _server.respond_json("/music/list", [], 503) + _server.release("/music/list") + + assert_int(_server.count("/music/rand")).is_equal(0) + + _server.respond_json("/music/list", []) + await await_millis(200) + + assert_int(_server.count("/music/rand")).is_equal(1) + assert_int(Playlist.get_preloaded_songs()).is_equal(1) diff --git a/tests/integration/test_main_window_startup.gd.uid b/tests/integration/test_main_window_startup.gd.uid new file mode 100644 index 0000000..71b315e --- /dev/null +++ b/tests/integration/test_main_window_startup.gd.uid @@ -0,0 +1 @@ +uid://sfwrqidmuj04