Files
MusicPlayer/tests/integration/test_sync_popup.gd
T
SansanandClaude Opus 5.5 0862d160e5
Build / test (push) Successful in 1m55s
Build / build (push) Successful in 1m12s
Give the sync progress bar colour and animation
The bar in the Sync popup is now an AnimatedProgressBar: taller, with a
darker track and the theme's accent fill (orange, frost blue in Nord, green
with a gold shine for Christmas). It shows Godot's busy animation until the
server reports progress, glides to each new value, has a soft highlight
sweeping along the filled part inside its rounded corners, and fills and
pops before the result shows. With Reduce animations it only jumps to its
values.

The styles are SyncProgressBar, SyncProgressBarFill and SyncProgressBarText
in every theme. Anim.stop stops a keyed animation without starting an empty
one.

The server now sends sync times as "MM:SS"; the spec is updated from
MusicServer develop (also the linux-package route), and the test data and
screenshots use the new format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 11:13:43 +02:00

221 lines
7.0 KiB
GDScript

extends GdUnitTestSuite
## The sync popup on its own: starting a sync, its progress, and how the result is shown.
var _server: FakeHttpTransport
var _snapshot: AutoloadSnapshot
var _runner: GdUnitSceneRunner
var _window: Control
func before_test() -> void:
_snapshot = AutoloadSnapshot.take([Settings])
Settings.currently_syncing = false
# The bar jumps to its values and the result shows at once
Settings.reduce_animations = true
_server = FakeHttpTransport.new().install()
_server.respond_bytes("/sync", PackedByteArray())
_runner = scene_runner("res://popups/SyncPopup.tscn")
_window = _runner.scene()
func after_test() -> void:
_server.uninstall()
_snapshot.restore()
func _node(unique_name: String) -> Control:
return _window.get_node("%" + unique_name)
func _finish_sync(result: Dictionary) -> void:
_server.respond_json("/sync/progress", result)
_runner.invoke("_on_sync_timeout")
func _empty_result() -> Dictionary:
return {
"soundtracks_added": [],
"soundtracks_re_added": [],
"soundtracks_changed_title": {},
"soundtracks_changed_content": [],
"soundtracks_removed": [],
"catched_errors": [],
"total_time": "00:03",
}
func test_start_sync_asks_server_and_shows_progress() -> void:
monitor_signals(_window, false)
_runner.invoke("start_sync")
assert_array(_server.requested_paths).contains_exactly(["/sync"])
assert_bool(Settings.currently_syncing).is_true()
assert_bool(_node("ProgressHBoxContainer").visible).is_true()
assert_str(_node("SyncTitleLabel").text).is_equal("Sync in Progress")
await assert_signal(_window).is_emitted("sync_started")
func test_start_sync_does_nothing_while_already_syncing() -> void:
Settings.currently_syncing = true
_runner.invoke("start_sync")
assert_array(_server.requested_paths).is_empty()
func test_progress_updates_progress_bar() -> void:
_runner.invoke("start_sync")
_server.respond_json("/sync/progress", {"progress": "42", "time_spent": "00:12"})
_runner.invoke("_on_sync_timeout")
assert_float(_node("SyncProgressBar").value).is_equal(42.0)
assert_str(_node("SyncTime").text).is_equal("00:12")
func test_finished_sync_fills_the_bar_before_the_result() -> void:
Settings.reduce_animations = false
_runner.invoke("start_sync")
var bar: AnimatedProgressBar = _node("SyncProgressBar")
assert_bool(bar.indeterminate).is_true()
_finish_sync(_empty_result())
assert_bool(_node("ProgressHBoxContainer").visible).is_true()
await await_millis(int(Anim.SLOW * 1000) + 100)
assert_float(bar.value).is_equal(100.0)
assert_bool(_node("ProgressHBoxContainer").visible).is_true()
await await_millis(int(Anim.SLOW * 1000) + 100)
assert_bool(_node("ProgressHBoxContainer").visible).is_false()
assert_str(_node("SyncTitleLabel").text).is_equal("Sync Result")
func test_finished_sync_shows_only_sections_with_changes() -> void:
_runner.invoke("start_sync")
monitor_signals(_window, false)
var result: Dictionary = _empty_result()
result.soundtracks_added = ["Chrono Cross", "Xenogears"]
result.soundtracks_removed = ["Bad Rip"]
_finish_sync(result)
assert_bool(_node("SoundtracksAddedVBox").visible).is_true()
assert_bool(_node("SoundtracksRemovedVBox").visible).is_true()
assert_bool(_node("SoundtracksReAddedVBox").visible).is_false()
assert_bool(_node("SoundtracksChangedTitleVBox").visible).is_false()
assert_bool(_node("NoChangesLabel").visible).is_false()
# Header plus one label per soundtrack
assert_int(_node("SoundtracksAddedVBox").get_child_count()).is_equal(3)
assert_str(_node("SyncTime").text).is_equal("Total time: 00:03")
assert_bool(Settings.currently_syncing).is_false()
await assert_signal(_window).is_emitted("sync_finished")
func test_separator_shown_only_between_visible_sections() -> void:
_runner.invoke("start_sync")
var result: Dictionary = _empty_result()
result.soundtracks_added = ["Chrono Cross"]
result.soundtracks_removed = ["Bad Rip"]
_finish_sync(result)
assert_bool(_node("SoundtracksAddedSeparator").visible).is_true()
assert_bool(_node("SoundtracksRemovedSeparator").visible).is_false()
func test_changed_titles_list_old_and_new_title() -> void:
_runner.invoke("start_sync")
var result: Dictionary = _empty_result()
result.soundtracks_changed_title = {"FF7": "Final Fantasy VII"}
_finish_sync(result)
var vbox: Control = _node("SoundtracksChangedTitleVBox")
assert_bool(vbox.visible).is_true()
assert_str((vbox.get_child(1) as ListRow).text).is_equal(
"FF7\nchanged title to\nFinal Fantasy VII")
func test_no_changes_shows_no_changes_label() -> void:
_runner.invoke("start_sync")
_finish_sync(_empty_result())
assert_bool(_node("NoChangesLabel").visible).is_true()
func test_errors_are_listed() -> void:
_runner.invoke("start_sync")
var result: Dictionary = _empty_result()
result.catched_errors = ["Could not read tags in broken.mp3"]
_finish_sync(result)
assert_bool(_node("CaughtErrorsVBox").visible).is_true()
func test_reopening_after_sync_shows_result_without_syncing_again() -> void:
_runner.invoke("start_sync")
_finish_sync(_empty_result())
_runner.invoke("start_sync")
assert_int(_server.count("/sync")).is_equal(1)
assert_str(_node("SyncTitleLabel").text).is_equal("Sync Result")
func test_errors_section_has_errors_header() -> void:
_runner.invoke("start_sync")
var result: Dictionary = _empty_result()
result.catched_errors = ["Could not read tags in broken.mp3"]
_finish_sync(result)
assert_str((_node("CaughtErrorsVBox").get_child(0) as Label).text).is_equal("Errors")
func test_long_names_wrap_instead_of_widening_the_window() -> void:
_runner.invoke("start_sync")
var long_name: String = "The Legend of Zelda: Breath of the Wild Original Soundtrack"
var result: Dictionary = _empty_result()
result.soundtracks_added = [long_name]
result.soundtracks_re_added = [long_name]
result.soundtracks_changed_content = [long_name]
result.soundtracks_removed = [long_name]
result.soundtracks_changed_title = {long_name: long_name}
result.catched_errors = [long_name + " could not be read"]
_finish_sync(result)
await _runner.simulate_frames(2)
# The minimum width is what the content needs; wider than the panel it would stick out
var panel_width: float = (_window.get_node("SyncPanel") as Control).size.x
assert_float(_node("SyncHBoxContainer").get_combined_minimum_size().x) \
.is_less(panel_width)
assert_float(_node("CaughtErrorsVBox").get_combined_minimum_size().x) \
.is_less(panel_width)
func test_visible_columns_share_the_width_equally() -> void:
_runner.invoke("start_sync")
var result: Dictionary = _empty_result()
result.soundtracks_added = ["Chrono Cross", "Xenoblade Chronicles X Original Soundtrack"]
result.soundtracks_removed = ["Bad Rip"]
_finish_sync(result)
await _runner.simulate_frames(2)
assert_float(_node("SoundtracksAddedVBox").size.x).is_equal_approx(
_node("SoundtracksRemovedVBox").size.x, 1.0)
func test_column_headers() -> void:
_runner.invoke("start_sync")
var result: Dictionary = _empty_result()
result.soundtracks_re_added = ["Super Metroid"]
_finish_sync(result)
assert_str((_node("SoundtracksReAddedVBox").get_child(0) as Label).text).is_equal("Re-added")