Add offline mode: play from a music folder instead of the server
Build / test (push) Successful in 1m2s
Build / build (push) Successful in 1m12s

Settings has a Music folder row. A folder with subfolders is played as
soundtracks: Next picks a random subfolder and a random song in it, the
subfolder is the answer's soundtrack and the file name without its type the
song. The inspiration list and Search show the subfolders.

A folder with songs and no subfolders is a prepared quiz, played once in order
(natural sort) or at random, showing only the song title. It has no winning
score: after the last song Next becomes Show winner, and the players with the
most points win, ties included.

While a folder is used nothing but the characters is asked from the server:
Sync, QR, Statistics, the update check and play counting are off. Characters
fall back to the cached pictures when the server can't be reached. mp3, ogg and
wav play; other files and broken ones are skipped.

The old local playback code, the hidden Open button and the hidden Local toggle
are removed. Next now waits for the song to start before preparing the next one.
This commit is contained in:
Sansan committed 2026-10-07 23:07:47 +02:00
1 parent b120440404
commit 222d231b1b
42 files changed
+8245 -474

No files matched your search

File diff suppressed because it is too large. Load diff
Binary file not shown.
Binary file not shown.
+50
View File
@@ -296,6 +296,56 @@ func run(on_state: Callable) -> void:
winner_popup.close()
await _settle()
# Playing from a music folder: a soundtrack folder, then a prepared quiz in Settings
var music: String = _make_music_folders()
var settings_popup: SettingsPopup = _node("SettingsPopup")
# Songs are picked at random; the same picks every time keep the pictures the same
seed(1)
settings_popup.use_music_folder(music.path_join("Soundtracks"))
await _settle()
_call("play_next_song")
# A playing song is stopped first, which takes half a second
await _main.get_tree().create_timer(0.7).timeout
await _settle()
_call("show_answer_pressed")
await _settle()
await on_state.call("offline", null)
settings_popup.use_music_folder(music.path_join("Musiktävling"))
settings_popup.open()
await _settle()
await on_state.call("popup_SettingsWindow_offline", settings_popup)
settings_popup.close()
# The end of a prepared quiz, where two players share the most points
var scores: Array[int] = [7, 12, 12, 3, 9]
for index: int in Settings.player_array.size():
Settings.player_array[index].player_score = scores[index % scores.size()]
_call("show_final_winners")
var final_popup: WinnerPopup = _node("WinnerPopup")
await _settle()
await on_state.call("popup_WinnerWindow_prepared_quiz", final_popup)
final_popup.close()
settings_popup.use_server()
await _settle()
# Writes a soundtrack folder and a prepared quiz with copies of the test song, and
# returns the folder they are in
static func _make_music_folders() -> String:
var root: String = OS.get_user_data_dir().path_join("ui_tour_music")
var song: PackedByteArray = FileAccess.get_file_as_bytes("res://tests/data/short.mp3")
var files: Array[String] = [
"Soundtracks/Chrono Trigger/Corridors of Time.mp3",
"Soundtracks/Final Fantasy VI/Dancing Mad.mp3",
"Soundtracks/Secret of Mana/Into the Thick of It.mp3",
"Musiktävling/01. Opening.mp3",
"Musiktävling/02. Ace Combat 7 - Daredevil.mp3",
]
for file: String in files:
var path: String = root.path_join(file)
DirAccess.make_dir_recursive_absolute(path.get_base_dir())
FileAccess.open(path, FileAccess.WRITE).store_buffer(song)
return root
## Returns where every Control and Window in [param subtree] is on screen, keyed by its
## path from [param main]: [code]{"rect": [x, y, width, height], "visible": bool}[/code].
+7 -7
View File
@@ -33,15 +33,15 @@ func _texts() -> Array:
return _rows().map(func(row: ListRow) -> String: return row.text)
func test_load_from_server_shows_every_soundtrack_in_order() -> void:
_list.load_from_server()
func test_load_soundtracks_shows_every_soundtrack_in_order() -> void:
_list.load_soundtracks()
assert_array(_texts()).contains_exactly(SOUNDTRACKS)
assert_array(_list.soundtracks).contains_exactly(SOUNDTRACKS)
func test_rows_alternate_backgrounds() -> void:
_list.load_from_server()
_list.load_soundtracks()
var rows: Array[Node] = _rows()
assert_that((rows[0] as ListRow).get_theme_stylebox("panel")) \
@@ -51,19 +51,19 @@ func test_rows_alternate_backgrounds() -> void:
func test_loading_again_replaces_the_rows() -> void:
_list.load_from_server()
_list.load_soundtracks()
_server.respond_json("/music/all", ["Terranigma"])
_list.load_from_server()
_list.load_soundtracks()
assert_array(_texts()).contains_exactly(["Terranigma"])
func test_failed_load_keeps_the_rows() -> void:
_list.load_from_server()
_list.load_soundtracks()
_server.respond_bytes("/music/all", PackedByteArray(), 500)
_list.load_from_server()
_list.load_soundtracks()
assert_array(_texts()).contains_exactly(SOUNDTRACKS)
+339
View File
@@ -0,0 +1,339 @@
extends MainWindowTestSuite
## Playing from a music folder instead of the server: soundtrack folders and prepared
## quizzes, with nothing but the characters asked from the server.
var _root: String
var _settings: SettingsPopup
var _top_bar: TopBar
func before_test() -> void:
_root = create_temp_dir("offline_%d" % randi())
super()
_settings = _window.get_node("SettingsPopup")
_top_bar = _window.get_node("TopBar")
func _song(relative_path: String) -> void:
var path: String = _root.path_join(relative_path)
DirAccess.make_dir_recursive_absolute(path.get_base_dir())
FileAccess.open(path, FileAccess.WRITE).store_buffer(
FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
func _soundtrack_folder() -> String:
_song("Music/Chrono Trigger/Corridors of Time.mp3")
_song("Music/Final Fantasy VI/Dancing Mad.mp3")
return _root.path_join("Music")
func _prepared_quiz() -> String:
for file_name: String in ["02. Ace Combat 7 - Daredevil.mp3", "01. Opening.mp3",
"10. Banjo Tooie - Cloud Race.mp3"]:
_song("Quiz/" + file_name)
return _root.path_join("Quiz")
# Requests to the server other than for characters
func _server_requests() -> Array[String]:
return _server.requested_paths.filter(
func(path: String) -> bool: return !path.begins_with("/character"))
func _use_folder(path: String) -> void:
_settings.use_music_folder(path)
await _runner.simulate_frames(2)
# Presses Next and waits until the song plays. Another playing song is stopped first,
# which takes half a second.
func _next() -> void:
_press("SongControls/NextButton")
await await_millis(700)
func _now_playing() -> Array[String]:
var now_playing: Node = _quiz.get_node("NowPlaying")
return [(now_playing.get_node("SoundtrackLabel") as Label).text,
(now_playing.get_node("SongLabel") as Label).text]
func test_picking_a_folder_makes_no_server_requests() -> void:
var before: int = _server_requests().size()
await _use_folder(_soundtrack_folder())
await _play_first_song()
_press("SongControls/ShowAnswerButton")
_press("QuizToolbar/SoundTestButton")
_press("QuizToolbar/ResetPlaylistButton")
await _runner.simulate_frames(2)
(_window.get_node("%AboutPopup") as AboutPopup).open()
assert_array(_server_requests().slice(before)).is_empty()
func test_next_plays_a_song_from_the_folder() -> void:
await _use_folder(_soundtrack_folder())
await _play_first_song()
var playing: SongObject = Playlist.song_object_array[Playlist.currently_playing_song]
assert_str(playing.song_path).starts_with(_root)
assert_bool((_quiz.get_node("MusicPlayer") as MusicPlayer).audio_player.playing).is_true()
func test_show_answer_shows_the_subfolder_and_the_song() -> void:
await _use_folder(_soundtrack_folder())
await _play_first_song()
_press("SongControls/ShowAnswerButton")
var song: SongObject = Playlist.song_object_array[Playlist.currently_playing_song]
assert_array(_now_playing()).contains_exactly([song.soundtrack_title, song.song_title])
assert_array(["Chrono Trigger", "Final Fantasy VI"]).contains([song.soundtrack_title])
assert_bool(song.song_title.ends_with(".mp3")).is_false()
func test_points_are_given_as_usual() -> void:
await _use_folder(_soundtrack_folder())
_add_player("Crono")
await _play_first_song()
await _give_point_with_keybind(1)
assert_array(_scores()).contains_exactly([1])
assert_array(Playlist.song_object_array[0].players_given_point).has_size(1)
func test_inspiration_list_and_search_show_the_subfolders() -> void:
await _use_folder(_soundtrack_folder())
assert_array(_quiz.inspiration_list.soundtracks) \
.contains_exactly(["Chrono Trigger", "Final Fantasy VI"])
assert_array((_window.get_node("SearchPopup") as SearchPopup).soundtracks) \
.contains_exactly(["Chrono Trigger", "Final Fantasy VI"])
func test_server_features_are_off_and_the_folder_is_shown() -> void:
await _use_folder(_soundtrack_folder())
assert_bool(_top_bar.sync_button.disabled).is_true()
assert_bool(_top_bar.qr_button.disabled).is_true()
assert_bool(_top_bar.statistics_tab.disabled).is_true()
assert_bool(_top_bar.offline_label.visible).is_true()
assert_str(_top_bar.offline_label.text).is_equal("Offline: Music")
assert_bool(_settings.select_server_button.disabled).is_true()
assert_bool(_settings.add_to_database_button.disabled).is_true()
func test_picking_a_folder_on_the_statistics_tab_goes_to_the_quiz() -> void:
_window.show_tab(&"statistics")
await _use_folder(_soundtrack_folder())
assert_bool(_quiz.visible).is_true()
func test_reset_playlist_starts_an_empty_playlist() -> void:
await _use_folder(_soundtrack_folder())
await _play_first_song()
await _next()
_press("QuizToolbar/ResetPlaylistButton")
await _runner.simulate_frames(2)
assert_int(Playlist.currently_playing_song).is_equal(-1)
assert_bool(Playlist.song_object_array.all(
func(song: SongObject) -> bool: return !song.has_played)).is_true()
func test_prepared_quiz_plays_in_order_and_shows_only_the_title() -> void:
await _use_folder(_prepared_quiz())
assert_bool(_settings.play_order_row.visible).is_true()
await _play_first_song()
_press("SongControls/ShowAnswerButton")
assert_array(_now_playing()).contains_exactly(["", "01. Opening"])
var row: SongRow = _quiz.music_list_panel.get_node("%MusicList").get_child(0)
assert_str(row.song_list_label.text).starts_with("1. 01. Opening")
assert_array(_quiz.inspiration_list.soundtracks).is_empty()
func test_prepared_quiz_stops_after_the_last_song() -> void:
await _use_folder(_prepared_quiz())
var titles: Array[String] = []
for i: int in 3:
await _next()
titles.append(Playlist.song_object_array[Playlist.currently_playing_song].song_title)
assert_array(titles).contains_exactly(
["01. Opening", "02. Ace Combat 7 - Daredevil", "10. Banjo Tooie - Cloud Race"])
assert_int(Playlist.song_object_array.size()).is_equal(3)
assert_str((_quiz.get_node("SongControls/NextButton") as Button).text) \
.is_equal("Show winner")
func test_prepared_quiz_at_random_plays_every_song() -> void:
await _use_folder(_prepared_quiz())
_settings.play_order_button.select(MusicFolder.PlayOrder.RANDOM)
_settings.play_order_button.item_selected.emit(MusicFolder.PlayOrder.RANDOM)
await _runner.simulate_frames(2)
for i: int in 3:
await _next()
assert_int(Settings.music_folder.play_order).is_equal(MusicFolder.PlayOrder.RANDOM)
var titles: Array = Playlist.song_object_array.map(
func(song: SongObject) -> String: return song.song_title)
assert_array(titles).contains_exactly_in_any_order(
["01. Opening", "02. Ace Combat 7 - Daredevil", "10. Banjo Tooie - Cloud Race"])
func test_play_order_is_only_shown_for_a_prepared_quiz() -> void:
await _use_folder(_soundtrack_folder())
assert_bool(_settings.play_order_row.visible).is_false()
func test_use_server_goes_back_to_the_server() -> void:
await _use_folder(_soundtrack_folder())
var before: int = _server_requests().size()
_settings.use_server()
await _runner.simulate_frames(2)
assert_bool(Settings.is_offline()).is_false()
assert_int(_server_requests().size()).is_greater(before)
assert_bool(_top_bar.sync_button.disabled).is_false()
assert_bool(_top_bar.offline_label.visible).is_false()
assert_bool(_settings.use_server_button.visible).is_false()
func test_folder_without_songs_is_not_used() -> void:
DirAccess.make_dir_recursive_absolute(_root.path_join("Empty"))
await _use_folder(_root.path_join("Empty"))
assert_bool(Settings.is_offline()).is_false()
assert_bool(_settings.folder_error_label.visible).is_true()
assert_str(_settings.folder_error_label.text).is_equal("No songs found in Empty")
func test_settings_panel_fits_its_rows_and_stays_centred() -> void:
_settings.open()
await _use_folder(_prepared_quiz())
await _runner.simulate_frames(1)
var panel: Rect2 = _settings.settings_panel.get_global_rect()
assert_bool(panel.encloses(_settings.rows.get_global_rect())).is_true()
assert_float(_settings.rows.get_combined_minimum_size().y) \
.is_less_equal(_settings.rows.size.y)
assert_float(panel.get_center().y).is_equal_approx(540.0, 1.0)
func test_characters_still_come_from_the_server() -> void:
var before: int = _server.count("/character/details")
await _use_folder(_soundtrack_folder())
assert_int(_server.count("/character/details")).is_greater(before)
func _play_whole_quiz() -> void:
await _play_first_song()
for i: int in 2:
await _next()
func test_prepared_quiz_has_no_winning_score() -> void:
Settings.winning_score = 1
var crono: PlayerObject = _add_player("Crono")
await _use_folder(_prepared_quiz())
await _play_first_song()
await _give_point_with_keybind(1)
await _give_point_with_keybind(1)
assert_int(crono.player_score).is_equal(2)
assert_bool(_quiz.winner_popup.visible).is_false()
func test_next_shows_the_winner_after_the_last_song() -> void:
_add_player("Crono")
_add_player("Marle")
await _use_folder(_prepared_quiz())
await _play_first_song()
await _give_point_with_keybind(2)
await _play_whole_quiz_rest()
var next_button: Button = _quiz.get_node("SongControls/NextButton")
assert_str(next_button.text).is_equal("Show winner")
assert_bool(next_button.disabled).is_false()
next_button.pressed.emit()
assert_bool(_quiz.winner_popup.visible).is_true()
assert_str(_quiz.winner_popup.winner_label.text).is_equal("Marle won with 1 point!!")
func _play_whole_quiz_rest() -> void:
for i: int in 2:
await _next()
func test_tied_players_all_win() -> void:
_add_player("Crono")
_add_player("Marle")
_add_player("Lucca")
await _use_folder(_prepared_quiz())
await _play_first_song()
await _give_point_with_keybind(1)
await _give_point_with_keybind(3)
await _play_whole_quiz_rest()
_press("SongControls/NextButton")
assert_str(_quiz.winner_popup.winner_label.text).is_equal("Crono and Lucca won with 1 point!!")
var header: Node = _quiz.winner_popup.winner_picture.get_parent()
var pictures: Array = header.get_children().filter(
func(node: Node) -> bool: return node is TextureRect && node.visible)
assert_array(pictures).has_size(2)
var others: Array = _quiz.winner_popup.players_list.get_children().map(
func(label: Label) -> String: return label.text)
assert_array(others).contains_exactly(["3. Marle - 0 points"])
func test_new_game_turns_show_winner_back_into_next() -> void:
await _use_folder(_prepared_quiz())
await _play_whole_quiz()
_press("QuizToolbar/ResetPlaylistButton")
await _runner.simulate_frames(2)
var next_button: Button = _quiz.get_node("SongControls/NextButton")
assert_str(next_button.text).is_equal("Randomize new track")
await _play_first_song()
assert_str(Playlist.song_object_array[Playlist.currently_playing_song].song_title) \
.is_equal("01. Opening")
func test_songs_playing_on_by_themselves_stop_at_the_end() -> void:
await _use_folder(_prepared_quiz())
await _play_whole_quiz()
_quiz.play_next_song()
await _runner.simulate_frames(2)
assert_str(Playlist.song_object_array[Playlist.currently_playing_song].song_title) \
.is_equal("10. Banjo Tooie - Cloud Race")
assert_str((_quiz.get_node("SongControls/NextButton") as Button).text) \
.is_equal("Show winner")
func test_quiz_settings_hide_the_winning_score_for_a_prepared_quiz() -> void:
await _use_folder(_prepared_quiz())
_quiz.quiz_settings_card.open()
assert_bool(_quiz.quiz_settings_card.winning_score_row.visible).is_false()
@@ -0,0 +1 @@
uid://bhg71e3byrwja
+1 -1
View File
@@ -29,7 +29,7 @@ const LOCKED_BY_SYNC: Array[String] = [
const TOP_BAR_CHILDREN: Array[String] = [
"TopPanelContainer", "Title", "SyncButton", "SettingsButton", "ShortcutsButton",
"DebugLabel", "UpdateLabel",
"DebugLabel", "UpdateLabel", "OfflineLabel",
"AboutButton", "QRButton", "QuizTab", "StatisticsTab",
]
+1 -1
View File
@@ -52,4 +52,4 @@ func test_popup_fits_its_rows_and_is_centred() -> void:
assert_bool(panel.encloses(rows.get_global_rect())).is_true()
# The rows' own height fits, so the last toggle is not squeezed or cut off
assert_float(rows.get_combined_minimum_size().y).is_less_equal(rows.size.y)
assert_vector(panel.get_center()).is_equal(Vector2(960, 540))
assert_vector(panel.get_center()).is_equal_approx(Vector2(960, 540), Vector2(1, 1))
+17
View File
@@ -158,3 +158,20 @@ func test_long_game_with_five_players_stays_readable() -> void:
# The whole popup fits in the window
assert_bool(Rect2(Vector2.ZERO, Vector2(1920, 1080)).encloses(
_popup.winner_panel.get_global_rect())).is_true()
func test_winners_text() -> void:
var crono: PlayerObject = PlayerObject.new("Crono")
var marle: PlayerObject = PlayerObject.new("Marle")
var lucca: PlayerObject = PlayerObject.new("Lucca")
crono.player_score = 1
var one: Array[PlayerObject] = [crono]
var two: Array[PlayerObject] = [crono, marle]
var three: Array[PlayerObject] = [crono, marle, lucca]
var none: Array[PlayerObject] = []
assert_str(WinnerPopup.winners_text(one)).is_equal("Crono won with 1 point!!")
assert_str(WinnerPopup.winners_text(two)).is_equal("Crono and Marle won with 1 point!!")
assert_str(WinnerPopup.winners_text(three)) \
.is_equal("Crono, Marle and Lucca won with 1 point!!")
assert_str(WinnerPopup.winners_text(none)).is_equal("The quiz is over!")
+45
View File
@@ -0,0 +1,45 @@
extends GdUnitTestSuite
## Reading songs from files by their type.
@warning_ignore("unused_parameter")
func test_playable_types_load(
file_name: String,
type: GDScript,
test_parameters: Array = [
["short.mp3", AudioStreamMP3], ["short.ogg", AudioStreamOggVorbis],
["short.wav", AudioStreamWAV],
],
) -> void:
var stream: AudioStream = AudioFile.load("res://tests/data/" + file_name)
assert_object(stream).is_not_null()
assert_bool(is_instance_of(stream, type)).is_true()
assert_float(stream.get_length()).is_greater(0.0)
func test_missing_file_gives_null() -> void:
assert_object(AudioFile.load("res://tests/data/missing.mp3")).is_null()
func test_other_types_give_null() -> void:
var path: String = create_temp_dir("audio_file").path_join("song.flac")
FileAccess.open(path, FileAccess.WRITE).store_buffer(
FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
assert_object(AudioFile.load(path)).is_null()
func test_broken_file_gives_null() -> void:
var path: String = create_temp_dir("audio_file").path_join("broken.mp3")
FileAccess.open(path, FileAccess.WRITE).store_string("not a song")
assert_object(AudioFile.load(path)).is_null()
func test_playable_types() -> void:
assert_bool(AudioFile.is_playable("a/b/Song.MP3")).is_true()
assert_bool(AudioFile.is_playable("Song.ogg")).is_true()
assert_bool(AudioFile.is_playable("Song.wav")).is_true()
assert_bool(AudioFile.is_playable("Song.flac")).is_false()
assert_bool(AudioFile.is_playable("cover.jpg")).is_false()
+1
View File
@@ -0,0 +1 @@
uid://crghfr0gnyngh
+23
View File
@@ -189,3 +189,26 @@ func test_broken_index_starts_over() -> void:
assert_int(images.size()).is_equal(1)
assert_int(_server.count(_image_path("Crono.png"))).is_equal(2)
func test_cached_pictures_are_shown_when_the_server_cannot_be_reached() -> void:
_serve({"Crono.png": 100, "Marle.png": 200})
_load_all()
_server.respond_json("/character/details", {}, 503)
_server.respond_json("/character/list", {}, 503)
var images: Array[Image] = _load_all()
assert_int(images.size()).is_equal(2)
assert_int(_server.count(_image_path("Crono.png"))).is_equal(1)
assert_bool(FileAccess.file_exists(CharacterCache.folder().path_join("Marle.png"))).is_true()
func test_nothing_is_shown_without_a_server_or_cached_pictures() -> void:
var cache: CharacterCache = CharacterCache.new()
var listed: Array = []
cache.load_list(func(names: Array[String]) -> void: listed.append(names))
assert_array(listed).has_size(1)
assert_array(listed[0]).is_empty()
+202
View File
@@ -0,0 +1,202 @@
extends GdUnitTestSuite
## Music folders: soundtrack folders, where songs are picked at random from subfolders,
## and prepared quizzes, where every song in the folder plays once.
var _root: String
var _mp3: PackedByteArray
func before_test() -> void:
_root = create_temp_dir("music_folder_%d" % randi())
_mp3 = FileAccess.get_file_as_bytes("res://tests/data/short.mp3")
func _song(relative_path: String) -> void:
_file(relative_path, _mp3)
func _file(relative_path: String, bytes: PackedByteArray) -> void:
var path: String = _root.path_join(relative_path)
DirAccess.make_dir_recursive_absolute(path.get_base_dir())
FileAccess.open(path, FileAccess.WRITE).store_buffer(bytes)
func _folder(relative_path: String = "") -> MusicFolder:
return MusicFolder.scan(_root.path_join(relative_path))
func test_folder_with_subfolders_is_a_soundtrack_folder() -> void:
_song("Chrono Trigger/Corridors of Time.mp3")
_song("Final Fantasy VI/Dancing Mad.mp3")
var folder: MusicFolder = _folder()
assert_int(folder.kind).is_equal(MusicFolder.Kind.SOUNDTRACKS)
assert_array(folder.soundtrack_names()) \
.contains_exactly(["Chrono Trigger", "Final Fantasy VI"])
func test_songs_at_the_top_of_a_soundtrack_folder_are_left_out() -> void:
_song("Chrono Trigger/Corridors of Time.mp3")
_song("Loose song.mp3")
var folder: MusicFolder = _folder()
for i: int in 10:
assert_str(folder.next_song().soundtrack_title).is_equal("Chrono Trigger")
func test_folder_with_only_songs_is_a_prepared_quiz() -> void:
_song("01. Opening.mp3")
_song("02. Ace Combat 7 - Daredevil.mp3")
var folder: MusicFolder = _folder()
assert_int(folder.kind).is_equal(MusicFolder.Kind.PREPARED_QUIZ)
assert_int(folder.quiz_size()).is_equal(2)
assert_array(folder.soundtrack_names()).is_empty()
func test_folder_without_songs_is_empty() -> void:
_file("notes.txt", "hello".to_utf8_buffer())
_file("Chrono Trigger/cover.jpg", "jpg".to_utf8_buffer())
assert_int(_folder().kind).is_equal(MusicFolder.Kind.EMPTY)
func test_missing_folder_is_empty() -> void:
assert_int(_folder("missing").kind).is_equal(MusicFolder.Kind.EMPTY)
func test_hidden_folders_and_files_are_left_out() -> void:
_song(".sync/Archive/Old.mp3")
_song("Chrono Trigger/.hidden.mp3")
_song("Chrono Trigger/Corridors of Time.mp3")
var folder: MusicFolder = _folder()
assert_array(folder.soundtrack_names()).contains_exactly(["Chrono Trigger"])
for i: int in 10:
assert_str(folder.next_song().song_title).is_equal("Corridors of Time")
func test_hidden_folder_alone_does_not_make_a_soundtrack_folder() -> void:
_song(".sync/Archive/Old.mp3")
_song("01. Opening.mp3")
assert_int(_folder().kind).is_equal(MusicFolder.Kind.PREPARED_QUIZ)
func test_songs_in_deeper_folders_belong_to_their_soundtrack() -> void:
_song("Xenogears/Disc 2/Small Two of Pieces.mp3")
var song: SongObject = _folder().next_song()
assert_str(song.soundtrack_title).is_equal("Xenogears")
assert_str(song.song_title).is_equal("Small Two of Pieces")
func test_picked_song_has_its_file_and_names_without_the_file_type() -> void:
_song("Chrono Trigger/Corridors of Time.mp3")
var song: SongObject = _folder().next_song()
assert_str(song.song_path).is_equal(_root.path_join("Chrono Trigger/Corridors of Time.mp3"))
assert_str(song.soundtrack_title).is_equal("Chrono Trigger")
assert_str(song.song_title).is_equal("Corridors of Time")
func test_soundtrack_folder_can_play_a_song_again() -> void:
_song("Chrono Trigger/Corridors of Time.mp3")
var folder: MusicFolder = _folder()
for i: int in 3:
assert_object(folder.next_song()).is_not_null()
func test_files_that_cannot_be_played_are_skipped() -> void:
_song("Chrono Trigger/Corridors of Time.mp3")
_file("Chrono Trigger/Lost.flac", _mp3)
_file("Chrono Trigger/Broken.mp3", "not a song".to_utf8_buffer())
_file("Chrono Trigger/cover.jpg", "jpg".to_utf8_buffer())
var folder: MusicFolder = _folder()
for i: int in 10:
assert_str(folder.next_song().song_title).is_equal("Corridors of Time")
func test_soundtrack_with_only_unplayable_files_is_left_out() -> void:
_song("Chrono Trigger/Corridors of Time.mp3")
_file("Tunic/Subterra.flac", _mp3)
assert_array(_folder().soundtrack_names()).contains_exactly(["Chrono Trigger"])
func test_prepared_quiz_plays_in_natural_order_and_then_stops() -> void:
for file_name: String in ["10. Ten.mp3", "2. Two.mp3", "01. Warhammer.mp3",
"01. Opening.mp3"]:
_song(file_name)
var folder: MusicFolder = _folder()
var titles: Array[String] = []
for i: int in 4:
titles.append(folder.next_song().song_title)
assert_array(titles).contains_exactly(["01. Opening", "01. Warhammer", "2. Two", "10. Ten"])
assert_object(folder.next_song()).is_null()
func test_prepared_quiz_has_no_soundtrack() -> void:
_song("04. Ace Combat 7 - Daredevil.mp3")
var song: SongObject = _folder().next_song()
assert_str(song.soundtrack_title).is_empty()
assert_str(song.song_title).is_equal("04. Ace Combat 7 - Daredevil")
func test_random_prepared_quiz_plays_every_song_once() -> void:
for i: int in 8:
_song("%02d. Song.mp3" % i)
var folder: MusicFolder = _folder()
folder.set_play_order(MusicFolder.PlayOrder.RANDOM)
var titles: Array[String] = []
for i: int in 8:
titles.append(folder.next_song().song_title)
assert_array(titles).has_size(8)
for i: int in 8:
assert_array(titles).contains(["%02d. Song" % i])
assert_object(folder.next_song()).is_null()
func test_restart_plays_the_prepared_quiz_from_the_start() -> void:
_song("01. One.mp3")
_song("02. Two.mp3")
var folder: MusicFolder = _folder()
folder.next_song()
folder.next_song()
folder.restart()
assert_str(folder.next_song().song_title).is_equal("01. One")
func test_unplayable_file_in_a_prepared_quiz_is_skipped() -> void:
_song("01. One.mp3")
_file("02. Broken.mp3", "not a song".to_utf8_buffer())
_song("03. Three.mp3")
var folder: MusicFolder = _folder()
assert_str(folder.next_song().song_title).is_equal("01. One")
assert_str(folder.next_song().song_title).is_equal("03. Three")
assert_object(folder.next_song()).is_null()
func test_display_name_is_the_folder_name() -> void:
_song("Musiktävling 2025/01. Opening.mp3")
var folder: MusicFolder = _folder("Musiktävling 2025/")
assert_str(folder.display_name()).is_equal("Musiktävling 2025")
+1
View File
@@ -0,0 +1 @@
uid://ca61kypdrbj8j