Short songs (#30): a quiz settings switch and a Seconds per song row (30 by default, 10-120 in steps of 5), saved with the quiz settings. Only part of each quiz song plays, from a random point 10-60% into it, picked once per song so restart and repeat play the same part. The progress bar, time and seeking stay within the part, a hidden beginning counts inside it, and its end counts as the song ending (repeat, stop or go to the next song as set). Random songs (#51): the low-played switch in Settings is now a Normal / Low played / Classic choice, stored as Settings.random_mode and not saved. fetch_random_song(mode) picks /music/rand, /music/rand/low or /music/rand/classic. Closes #30 Closes #51 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0fc3be79e1
commit
a735c515bf
20 files changed
+1325
-327
No files matched your search
+831
-237
File diff suppressed because it is too large.
Load diff
@@ -26,7 +26,7 @@ func before_test() -> void:
|
||||
QuizSave.delete()
|
||||
Settings.add_to_stats = true
|
||||
Settings.hide_next_track = true
|
||||
Settings.use_low_played_mode = false
|
||||
Settings.random_mode = Settings.RandomMode.NORMAL
|
||||
Settings.winning_score = 20
|
||||
# Otherwise the About popup opens at start, as for a new version
|
||||
Settings.last_seen_version = Settings.version
|
||||
|
||||
@@ -172,7 +172,7 @@ static func prepare_autoloads() -> void:
|
||||
Settings.character_select_open = false
|
||||
Settings.add_to_stats = true
|
||||
Settings.hide_next_track = true
|
||||
Settings.use_low_played_mode = false
|
||||
Settings.random_mode = Settings.RandomMode.NORMAL
|
||||
Settings.winning_score = 20
|
||||
Settings.last_seen_version = Settings.version
|
||||
# The same picture every time, without half-finished animations
|
||||
|
||||
@@ -11,7 +11,7 @@ func test_startup_loads_soundtracks_and_preloads_a_song() -> void:
|
||||
|
||||
|
||||
func test_low_played_mode_fetches_from_low_played_endpoint() -> void:
|
||||
Settings.use_low_played_mode = true
|
||||
Settings.random_mode = Settings.RandomMode.LOW_PLAYED
|
||||
_server.respond_bytes(
|
||||
"/music/rand/low", FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
|
||||
|
||||
|
||||
@@ -177,3 +177,33 @@ func test_changes_are_saved_at_once() -> void:
|
||||
assert_int(saved.load(Settings.saved_settings_path())).is_equal(OK)
|
||||
assert_bool(saved.get_value("quiz", "hide_next_track")).is_false()
|
||||
assert_int(saved.get_value("quiz", "winning_score")).is_equal(19)
|
||||
|
||||
|
||||
func test_short_songs_shows_its_seconds_and_is_saved() -> void:
|
||||
Settings.short_songs = false
|
||||
await _open()
|
||||
assert_bool(_card.short_song_seconds_row.visible).is_false()
|
||||
|
||||
_card.short_songs_button.button_pressed = true
|
||||
|
||||
assert_bool(Settings.short_songs).is_true()
|
||||
assert_bool(_card.short_song_seconds_row.visible).is_true()
|
||||
var saved: ConfigFile = ConfigFile.new()
|
||||
assert_int(saved.load(Settings.saved_settings_path())).is_equal(OK)
|
||||
assert_bool(saved.get_value("quiz", "short_songs")).is_true()
|
||||
|
||||
|
||||
func test_seconds_per_song_go_in_fives_between_ten_and_two_minutes() -> void:
|
||||
Settings.short_song_seconds = 30
|
||||
Settings.short_songs = true
|
||||
await _open()
|
||||
|
||||
_card.increase_short_song_seconds_button.pressed.emit()
|
||||
assert_int(Settings.short_song_seconds).is_equal(35)
|
||||
assert_str(_card.short_song_seconds_label.text).is_equal("35")
|
||||
for i: int in 10:
|
||||
_card.lower_short_song_seconds_button.pressed.emit()
|
||||
assert_int(Settings.short_song_seconds).is_equal(QuizSettingsCard.MIN_SHORT_SONG_SECONDS)
|
||||
for i: int in 40:
|
||||
_card.increase_short_song_seconds_button.pressed.emit()
|
||||
assert_int(Settings.short_song_seconds).is_equal(QuizSettingsCard.MAX_SHORT_SONG_SECONDS)
|
||||
@@ -9,15 +9,33 @@ func before_test() -> void:
|
||||
_popup = _window.get_node("SettingsPopup")
|
||||
|
||||
|
||||
func test_less_played_toggle_changes_the_setting() -> void:
|
||||
Settings.use_low_played_mode = false
|
||||
func test_random_songs_choice_lists_the_three_modes() -> void:
|
||||
var names: Array[String] = []
|
||||
for index: int in _popup.random_mode_button.item_count:
|
||||
names.append(_popup.random_mode_button.get_item_text(index))
|
||||
|
||||
assert_array(names).contains_exactly(["Normal", "Low played", "Classic"])
|
||||
|
||||
|
||||
@warning_ignore("unused_parameter")
|
||||
func test_random_songs_choice_changes_the_setting(mode: Settings.RandomMode,
|
||||
test_parameters: Array = [[Settings.RandomMode.LOW_PLAYED],
|
||||
[Settings.RandomMode.CLASSIC], [Settings.RandomMode.NORMAL]]) -> void:
|
||||
_popup.open()
|
||||
await _runner.simulate_frames(1)
|
||||
assert_bool(_popup.low_played_button.button_pressed).is_false()
|
||||
|
||||
await _click_at(_popup.low_played_button.get_global_rect().get_center())
|
||||
_popup.random_mode_button.select(mode)
|
||||
_popup.random_mode_button.item_selected.emit(mode)
|
||||
|
||||
assert_bool(Settings.use_low_played_mode).is_true()
|
||||
assert_int(Settings.random_mode).is_equal(mode)
|
||||
|
||||
|
||||
func test_random_songs_choice_shows_the_mode_in_use() -> void:
|
||||
Settings.random_mode = Settings.RandomMode.CLASSIC
|
||||
|
||||
_popup.open()
|
||||
|
||||
assert_int(_popup.random_mode_button.selected).is_equal(Settings.RandomMode.CLASSIC)
|
||||
|
||||
|
||||
func test_statistics_toggle_changes_the_setting() -> void:
|
||||
@@ -33,14 +51,15 @@ func test_statistics_toggle_changes_the_setting() -> void:
|
||||
|
||||
func test_statistics_settings_are_not_saved_with_the_quiz_settings() -> void:
|
||||
Settings.add_to_stats = false
|
||||
Settings.use_low_played_mode = true
|
||||
Settings.random_mode = Settings.RandomMode.LOW_PLAYED
|
||||
|
||||
Settings.save_settings()
|
||||
|
||||
var saved: ConfigFile = ConfigFile.new()
|
||||
assert_int(saved.load(Settings.saved_settings_path())).is_equal(OK)
|
||||
assert_bool(saved.has_section_key("quiz", "add_to_stats")).is_false()
|
||||
assert_bool(saved.has_section_key("quiz", "use_low_played_mode")).is_false()
|
||||
assert_bool(saved.has_section_key("quiz", "random_mode")).is_false()
|
||||
assert_bool(saved.has_section_key("app", "random_mode")).is_false()
|
||||
|
||||
|
||||
func test_popup_fits_its_rows_and_is_centred() -> void:
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
extends GdUnitTestSuite
|
||||
## Short songs: only a part of each quiz song plays, from a random point picked once per
|
||||
## song, and the end of the part counts as the song ending.
|
||||
|
||||
## How long the test song is, in seconds.
|
||||
const SONG_SECONDS: float = 60.0
|
||||
|
||||
var _snapshot: AutoloadSnapshot
|
||||
var _runner: GdUnitSceneRunner
|
||||
var _player: MusicPlayer
|
||||
|
||||
|
||||
func before_test() -> void:
|
||||
_snapshot = AutoloadSnapshot.take([Settings, Playlist])
|
||||
Settings.short_songs = true
|
||||
Settings.short_song_seconds = 20
|
||||
Settings.hide_beginning = false
|
||||
Settings.hide_length = false
|
||||
Settings.auto_repeat_song = false
|
||||
Settings.stop_after_current = true
|
||||
Playlist.song_object_array.clear()
|
||||
_runner = scene_runner("res://player/MusicPlayer.tscn")
|
||||
_player = _runner.scene()
|
||||
|
||||
|
||||
func after_test() -> void:
|
||||
_player.audio_player.stop()
|
||||
_snapshot.restore()
|
||||
|
||||
|
||||
# A silent song of [param seconds], in the playlist
|
||||
func _add_song(seconds: float = SONG_SECONDS) -> SongObject:
|
||||
var song: AudioStreamWAV = AudioStreamWAV.new()
|
||||
song.format = AudioStreamWAV.FORMAT_8_BITS
|
||||
song.mix_rate = 8000
|
||||
var silence: PackedByteArray = []
|
||||
silence.resize(int(seconds * song.mix_rate))
|
||||
song.data = silence
|
||||
var song_object: SongObject = SongObject.new()
|
||||
song_object.song = song
|
||||
song_object.song_number = Playlist.song_object_array.size()
|
||||
Playlist.add_to_playlist(song_object)
|
||||
return song_object
|
||||
|
||||
|
||||
func _play(song_object: SongObject) -> void:
|
||||
await _player.play_song_object(song_object.song_number)
|
||||
await _runner.simulate_frames(2)
|
||||
|
||||
|
||||
func test_part_starts_between_ten_and_sixty_percent_in() -> void:
|
||||
for i: int in 20:
|
||||
var song_object: SongObject = _add_song()
|
||||
_player.choose_part(song_object, song_object.song)
|
||||
|
||||
assert_float(song_object.short_start).is_between(SONG_SECONDS * 0.1, SONG_SECONDS * 0.6)
|
||||
assert_float(_player.part_length()).is_equal(20.0)
|
||||
|
||||
|
||||
func test_part_is_picked_once_per_song() -> void:
|
||||
var song_object: SongObject = _add_song()
|
||||
_player.choose_part(song_object, song_object.song)
|
||||
var start: float = song_object.short_start
|
||||
|
||||
_player.choose_part(song_object, song_object.song)
|
||||
|
||||
assert_float(song_object.short_start).is_equal(start)
|
||||
|
||||
|
||||
func test_part_always_fits_in_the_song() -> void:
|
||||
Settings.short_song_seconds = 50
|
||||
for i: int in 20:
|
||||
var song_object: SongObject = _add_song()
|
||||
_player.choose_part(song_object, song_object.song)
|
||||
|
||||
assert_float(song_object.short_start + 50).is_less_equal(SONG_SECONDS)
|
||||
|
||||
|
||||
func test_song_shorter_than_the_part_plays_whole() -> void:
|
||||
var song_object: SongObject = _add_song(15.0)
|
||||
|
||||
await _play(song_object)
|
||||
|
||||
assert_float(_player.part_length()).is_equal_approx(15.0, 0.01)
|
||||
assert_float(song_object.short_start).is_equal(-1.0)
|
||||
|
||||
|
||||
func test_whole_song_plays_with_short_songs_off() -> void:
|
||||
Settings.short_songs = false
|
||||
var song_object: SongObject = _add_song()
|
||||
|
||||
await _play(song_object)
|
||||
|
||||
assert_float(_player.part_length()).is_equal_approx(SONG_SECONDS, 0.01)
|
||||
assert_float(_player.position_in_part()).is_less(1.0)
|
||||
|
||||
|
||||
func test_song_starts_at_its_part_and_shows_the_part_length() -> void:
|
||||
var song_object: SongObject = _add_song()
|
||||
|
||||
await _play(song_object)
|
||||
|
||||
assert_float(_player.audio_player.get_playback_position()) \
|
||||
.is_greater_equal(song_object.short_start - 0.01)
|
||||
assert_float(_player.position_in_part()).is_less(1.0)
|
||||
assert_float(_player.progress_slider.max_value).is_equal(20.0)
|
||||
assert_str(_player.music_time_label.text).ends_with("/ 00:20")
|
||||
|
||||
|
||||
func test_seeking_stays_inside_the_part() -> void:
|
||||
var song_object: SongObject = _add_song()
|
||||
await _play(song_object)
|
||||
|
||||
_player.seek(500)
|
||||
|
||||
assert_float(_player.progress_slider.value).is_equal(20.0)
|
||||
assert_float(_player.playback_position) \
|
||||
.is_equal_approx(song_object.short_start + 20.0, 0.01)
|
||||
|
||||
|
||||
func test_end_of_the_part_stops_the_song() -> void:
|
||||
var song_object: SongObject = _add_song()
|
||||
await _play(song_object)
|
||||
|
||||
_player.seek(19.9)
|
||||
await await_millis(300)
|
||||
|
||||
assert_bool(_player.song_finished).is_true()
|
||||
assert_bool(_player.audio_player.playing).is_false()
|
||||
|
||||
|
||||
func test_end_of_the_part_repeats_it_with_auto_repeat() -> void:
|
||||
Settings.auto_repeat_song = true
|
||||
var song_object: SongObject = _add_song()
|
||||
await _play(song_object)
|
||||
|
||||
_player.seek(19.9)
|
||||
await await_millis(300)
|
||||
|
||||
assert_bool(_player.audio_player.playing).is_true()
|
||||
assert_float(_player.position_in_part()).is_less(1.0)
|
||||
assert_float(_player.audio_player.get_playback_position()) \
|
||||
.is_greater_equal(song_object.short_start - 0.01)
|
||||
|
||||
|
||||
func test_end_of_the_part_goes_to_the_next_song_without_stop_after_current() -> void:
|
||||
Settings.stop_after_current = false
|
||||
var song_object: SongObject = _add_song()
|
||||
await _play(song_object)
|
||||
var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_player))
|
||||
|
||||
_player.seek(19.9)
|
||||
|
||||
await signals.wait_until(1000).is_emitted("play_next_song")
|
||||
|
||||
|
||||
func test_restart_plays_the_same_part_again() -> void:
|
||||
var song_object: SongObject = _add_song()
|
||||
await _play(song_object)
|
||||
_player.seek(10)
|
||||
|
||||
_player.restart()
|
||||
await _runner.simulate_frames(2)
|
||||
|
||||
assert_float(_player.position_in_part()).is_less(1.0)
|
||||
assert_float(_player.audio_player.get_playback_position()) \
|
||||
.is_greater_equal(song_object.short_start - 0.01)
|
||||
|
||||
|
||||
func test_hidden_beginning_counts_inside_the_part() -> void:
|
||||
Settings.hide_beginning = true
|
||||
Settings.hide_for_seconds = 5.0
|
||||
var song_object: SongObject = _add_song()
|
||||
|
||||
await _play(song_object)
|
||||
|
||||
assert_str(_player.music_time_label.text).is_equal("??:?? / 00:20")
|
||||
_player.seek(6)
|
||||
await _runner.simulate_frames(2)
|
||||
assert_str(_player.music_time_label.text).starts_with("00:06")
|
||||
|
||||
|
||||
func test_other_sounds_always_play_whole() -> void:
|
||||
var song_object: SongObject = _add_song()
|
||||
await _play(song_object)
|
||||
|
||||
_player.play_song(_add_song(30.0).song)
|
||||
|
||||
assert_float(_player.part_length()).is_equal_approx(30.0, 0.01)
|
||||
assert_float(_player.position_in_part()).is_less(1.0)
|
||||
@@ -0,0 +1 @@
|
||||
uid://ov2rqiwl8x6s
|
||||
@@ -15,7 +15,7 @@ func before_test() -> void:
|
||||
Playlist.reset_playlist()
|
||||
Playlist.is_song_downloading = false
|
||||
Playlist.number_of_tracks_to_preload = 1
|
||||
Settings.use_low_played_mode = false
|
||||
Settings.random_mode = Settings.RandomMode.NORMAL
|
||||
_server = FakeHttpTransport.new().install()
|
||||
_server.respond_json("/music/list", [])
|
||||
_server.respond_bytes("/music/rand", FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
|
||||
@@ -79,15 +79,22 @@ func test_fetch_song_keeps_going_until_the_cache_is_full() -> void:
|
||||
assert_int(_server.count("/music/rand")).is_equal(3)
|
||||
|
||||
|
||||
func test_low_played_mode_uses_its_endpoint() -> void:
|
||||
Settings.use_low_played_mode = true
|
||||
_server.respond_bytes("/music/rand/low",
|
||||
FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
|
||||
@warning_ignore("unused_parameter")
|
||||
func test_each_random_mode_uses_its_endpoint(mode: Settings.RandomMode, path: String,
|
||||
test_parameters: Array = [
|
||||
[Settings.RandomMode.NORMAL, "/music/rand"],
|
||||
[Settings.RandomMode.LOW_PLAYED, "/music/rand/low"],
|
||||
[Settings.RandomMode.CLASSIC, "/music/rand/classic"],
|
||||
]) -> void:
|
||||
Settings.random_mode = mode
|
||||
_server.respond_bytes(path, FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
|
||||
|
||||
_loader.fetch_song()
|
||||
|
||||
assert_int(_server.count("/music/rand/low")).is_equal(1)
|
||||
assert_int(_server.count("/music/rand")).is_equal(0)
|
||||
assert_int(_server.count(path)).is_equal(1)
|
||||
for other: String in ["/music/rand", "/music/rand/low", "/music/rand/classic"]:
|
||||
if other != path:
|
||||
assert_int(_server.count(other)).override_failure_message(other).is_equal(0)
|
||||
|
||||
|
||||
func test_fetch_song_if_idle_does_nothing_while_downloading() -> void:
|
||||
|
||||
@@ -59,16 +59,16 @@ func test_json_list_that_is_not_an_array_calls_on_failed() -> void:
|
||||
assert_array(outcome).contains_exactly(["failed"])
|
||||
|
||||
|
||||
func test_fetch_random_song_uses_normal_endpoint() -> void:
|
||||
MusicServerClient.fetch_random_song(false, func(_body: PackedByteArray) -> void: pass)
|
||||
@warning_ignore("unused_parameter")
|
||||
func test_fetch_random_song_uses_the_endpoint_of_its_mode(mode: Settings.RandomMode,
|
||||
path: String, test_parameters: Array = [
|
||||
[Settings.RandomMode.NORMAL, "/music/rand"],
|
||||
[Settings.RandomMode.LOW_PLAYED, "/music/rand/low"],
|
||||
[Settings.RandomMode.CLASSIC, "/music/rand/classic"],
|
||||
]) -> void:
|
||||
MusicServerClient.fetch_random_song(mode, func(_body: PackedByteArray) -> void: pass)
|
||||
|
||||
assert_array(_server.requested_paths).contains_exactly(["/music/rand"])
|
||||
|
||||
|
||||
func test_fetch_random_song_uses_low_played_endpoint() -> void:
|
||||
MusicServerClient.fetch_random_song(true, func(_body: PackedByteArray) -> void: pass)
|
||||
|
||||
assert_array(_server.requested_paths).contains_exactly(["/music/rand/low"])
|
||||
assert_array(_server.requested_paths).contains_exactly([path])
|
||||
|
||||
|
||||
func test_fetch_song_passes_raw_bytes() -> void:
|
||||
@@ -103,7 +103,7 @@ func test_failed_request_calls_on_failed() -> void:
|
||||
_server.respond_bytes("/music/rand", PackedByteArray(), 503)
|
||||
var outcome: Array[String] = []
|
||||
|
||||
MusicServerClient.fetch_random_song(false,
|
||||
MusicServerClient.fetch_random_song(Settings.RandomMode.NORMAL,
|
||||
func(_body: PackedByteArray) -> void: outcome.append("done"),
|
||||
func() -> void: outcome.append("failed"))
|
||||
|
||||
@@ -370,8 +370,6 @@ func test_delete_token_without_token_sends_nothing() -> void:
|
||||
func test_endpoint_paths_and_methods() -> void:
|
||||
var noop: Callable = func(_data: Variant = null) -> void: pass
|
||||
var calls: Array[Array] = [
|
||||
[func() -> void: MusicServerClient.fetch_random_song_classic(noop),
|
||||
"/music/rand/classic", HTTPClient.METHOD_GET],
|
||||
[func() -> void: MusicServerClient.fetch_next_song(noop),
|
||||
"/music/next", HTTPClient.METHOD_GET],
|
||||
[func() -> void: MusicServerClient.fetch_previous_song(noop),
|
||||
|
||||
@@ -8,6 +8,7 @@ const SAVED_VALUES: Dictionary = {
|
||||
&"hide_ticks": true, &"winning_score": 13, &"inspiration_list_speed": 4,
|
||||
&"fullscreen": true, &"last_seen_version": "1.2.3", &"theme_id": "original_web",
|
||||
&"reduce_animations": true,
|
||||
&"short_songs": true, &"short_song_seconds": 45,
|
||||
}
|
||||
|
||||
var _snapshot: AutoloadSnapshot
|
||||
@@ -93,7 +94,7 @@ func test_statistics_settings_are_not_saved() -> void:
|
||||
|
||||
var file: ConfigFile = _saved_file()
|
||||
assert_bool(file.has_section_key("quiz", "add_to_stats")).is_false()
|
||||
assert_bool(file.has_section_key("quiz", "use_low_played_mode")).is_false()
|
||||
assert_bool(file.has_section_key("quiz", "random_mode")).is_false()
|
||||
|
||||
|
||||
func test_queue_ids_are_saved_and_load_back() -> void:
|
||||
|
||||
Reference in new issue
Block a user