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") # A quiz's own characters func _picture(relative_path: String) -> void: _file(relative_path, Image.create(8, 4, false, Image.FORMAT_RGB8).save_png_to_buffer()) func test_prepared_quiz_with_a_characters_folder_is_still_a_prepared_quiz() -> void: _song("01. Jingle Bells.mp3") _song("02. Last Christmas.mp3") _picture("characters/Santa.png") var folder: MusicFolder = _folder() assert_int(folder.kind).is_equal(MusicFolder.Kind.PREPARED_QUIZ) assert_int(folder.quiz_size()).is_equal(2) assert_str(folder.characters_path).is_equal(_root.path_join("characters")) func test_characters_folder_is_not_a_soundtrack() -> void: _song("Chrono Trigger/Corridors of Time.mp3") _picture("Characters/Frog.png") var folder: MusicFolder = _folder() assert_int(folder.kind).is_equal(MusicFolder.Kind.SOUNDTRACKS) assert_array(folder.soundtrack_names()).contains_exactly(["Chrono Trigger"]) assert_str(folder.characters_path).is_equal(_root.path_join("Characters")) func test_characters_folder_without_pictures_is_not_used() -> void: _song("01. Opening.mp3") _file("characters/notes.txt", "not a picture".to_utf8_buffer()) var folder: MusicFolder = _folder() assert_int(folder.kind).is_equal(MusicFolder.Kind.PREPARED_QUIZ) assert_str(folder.characters_path).is_empty() func test_only_a_characters_folder_is_nothing_to_play() -> void: _picture("characters/Santa.png") assert_int(_folder().kind).is_equal(MusicFolder.Kind.EMPTY) func test_folder_without_characters_has_no_characters_path() -> void: _song("Chrono Trigger/Corridors of Time.mp3") assert_str(_folder().characters_path).is_empty() # Continuing a prepared quiz func _prepared_quiz(songs: int) -> MusicFolder: for i: int in songs: _song("%02d. Song.mp3" % (i + 1)) return _folder() func test_prepared_quiz_continues_where_it_was() -> void: var folder: MusicFolder = _prepared_quiz(4) folder.next_song() folder.next_song() var again: MusicFolder = _folder() assert_bool(again.restore_progress(folder.progress())).is_true() assert_str(again.next_song().song_title).is_equal("03. Song") assert_str(again.next_song().song_title).is_equal("04. Song") assert_object(again.next_song()).is_null() func test_random_prepared_quiz_continues_in_the_same_order() -> void: var folder: MusicFolder = _prepared_quiz(8) folder.set_play_order(MusicFolder.PlayOrder.RANDOM) folder.next_song() var saved: Dictionary = JSON.parse_string(JSON.stringify(folder.progress())) var expected: Array[String] = [] for i: int in 7: expected.append(folder.next_song().song_title) var again: MusicFolder = _folder() again.restore_progress(saved) assert_int(again.play_order).is_equal(MusicFolder.PlayOrder.RANDOM) var titles: Array[String] = [] for i: int in 7: titles.append(again.next_song().song_title) assert_array(titles).contains_exactly(expected) func test_songs_removed_from_the_folder_are_left_out_when_continuing() -> void: var folder: MusicFolder = _prepared_quiz(4) folder.next_song() folder.next_song() var saved: Dictionary = folder.progress() # One played song and one still to play are gone DirAccess.remove_absolute(_root.path_join("01. Song.mp3")) DirAccess.remove_absolute(_root.path_join("04. Song.mp3")) var again: MusicFolder = _folder() again.restore_progress(saved) assert_str(again.next_song().song_title).is_equal("03. Song") assert_object(again.next_song()).is_null() func test_progress_that_does_not_fit_changes_nothing() -> void: var folder: MusicFolder = _prepared_quiz(2) assert_bool(folder.restore_progress({})).is_false() assert_bool(folder.restore_progress({"queue": "nope", "next": 1})).is_false() assert_str(folder.next_song().song_title).is_equal("01. Song") func test_soundtrack_folder_has_no_progress_to_continue() -> void: _song("Chrono Trigger/Corridors of Time.mp3") assert_bool(_folder().restore_progress({"queue": [], "next": 0})).is_false()