Covers: statistics cards show the soundtrack's cover on the left, and the
statistics popup shows it large. Until it arrives, and without a cover, the
soundtrack's initials stand in. Covers load only for cards on or near the
screen, three at a time, and are cached in covers/ next to the settings
(CoverCache), downloaded again only when cover_modified changes. The index and
file handling are shared with CharacterCache in a new FileCache.
Alternative names: the popup shows them and has an editor (add, remove, save)
that sends PUT /soundtracks/{uuid}/alternative-names. The statistics search and
the Search popup find soundtracks by them; Search shows the matching name after
the real one. Search now loads /soundtracks, falling back to the plain list.
The API types are regenerated from the server's spec, with a SoundtrackCover
theme style in every theme.
Closes #49
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
115 lines
3.9 KiB
GDScript
115 lines
3.9 KiB
GDScript
extends GdUnitTestSuite
|
|
## How SoundtrackSearch, used by the Search popup and the statistics view, finds and
|
|
## orders matching soundtracks.
|
|
|
|
var _search: SoundtrackSearch
|
|
|
|
|
|
func before_test() -> void:
|
|
_search = SoundtrackSearch.new()
|
|
_search.set_names([
|
|
"Chrono Trigger",
|
|
"Final Fantasy VI",
|
|
"Mario & Luigi: Superstar Saga",
|
|
"Pokémon Red",
|
|
"Q*bert",
|
|
"Super Mario 64",
|
|
"The Legend of Zelda: A Link to the Past",
|
|
])
|
|
|
|
|
|
func test_plain_text_matches_case_insensitively() -> void:
|
|
assert_array(_search.find_matches("chrono")).contains_exactly(["Chrono Trigger"])
|
|
|
|
|
|
func test_contains_matches_come_before_letter_order_matches() -> void:
|
|
var matches: Array[String] = _search.find_matches("mario")
|
|
|
|
assert_array(matches).contains_exactly([
|
|
"Mario & Luigi: Superstar Saga",
|
|
"Super Mario 64",
|
|
])
|
|
|
|
|
|
func test_ignores_spaces() -> void:
|
|
assert_array(_search.find_matches("finalfantasy")).contains_exactly(["Final Fantasy VI"])
|
|
|
|
|
|
func test_and_matches_ampersand() -> void:
|
|
assert_array(_search.find_matches("mario and luigi")) \
|
|
.contains(["Mario & Luigi: Superstar Saga"])
|
|
|
|
|
|
func test_accented_letters_match_plain_letters() -> void:
|
|
assert_array(_search.find_matches("pokemon")).contains_exactly(["Pokémon Red"])
|
|
|
|
|
|
func test_letters_in_order_match_abbreviations() -> void:
|
|
assert_array(_search.find_matches("lozlttp")).contains_exactly([
|
|
"The Legend of Zelda: A Link to the Past",
|
|
])
|
|
|
|
|
|
func test_regex_characters_are_matched_literally() -> void:
|
|
assert_array(_search.find_matches("q*b")).contains_exactly(["Q*bert"])
|
|
|
|
|
|
func test_no_matches() -> void:
|
|
assert_array(_search.find_matches("metroid")).is_empty()
|
|
|
|
|
|
func test_clean_term() -> void:
|
|
assert_str(SoundtrackSearch.clean_term("Rock 'n Roll Racing")).is_equal("rockandrollracing")
|
|
assert_str(SoundtrackSearch.clean_term("Ys I & II")).is_equal("ysiandii")
|
|
|
|
|
|
func test_short_search_only_finds_names_containing_it() -> void:
|
|
# "sm" is in order in "Super Mario 64" but not next to each other
|
|
assert_int(Settings.LETTERS_FOR_LOOSE_SEARCH).is_equal(3)
|
|
assert_array(_search.find_matches("sm")).is_empty()
|
|
assert_array(_search.find_matches("smr")).contains_exactly(["Super Mario 64"])
|
|
|
|
|
|
func test_matched_letters_of_plain_match() -> void:
|
|
assert_array(SoundtrackSearch.matched_letters("Chrono Trigger", "trig")) \
|
|
.contains_exactly([7, 8, 9, 10])
|
|
|
|
|
|
func test_matched_letters_ignoring_spaces_and_special_characters() -> void:
|
|
# "mario and luigi" matches "Mario & Luigi" with & read as "and"
|
|
var letters: Array[int] = SoundtrackSearch.matched_letters(
|
|
"Mario & Luigi: Superstar Saga", "mario and luigi")
|
|
assert_array(letters).contains_exactly([0, 1, 2, 3, 4, 6, 8, 9, 10, 11, 12])
|
|
|
|
|
|
func test_matched_letters_in_order() -> void:
|
|
assert_array(SoundtrackSearch.matched_letters("Super Mario 64", "smr")) \
|
|
.contains_exactly([0, 6, 8])
|
|
|
|
|
|
func test_matched_letters_of_no_match() -> void:
|
|
assert_array(SoundtrackSearch.matched_letters("Chrono Trigger", "metroid")).is_empty()
|
|
assert_array(SoundtrackSearch.matched_letters("Super Mario 64", "sm")).is_empty()
|
|
|
|
|
|
func test_alternative_names_find_the_real_name() -> void:
|
|
_search.set_names(["Final Fantasy VII", "Chrono Trigger"],
|
|
{"Final Fantasy VII": ["FF7", "ファイナルファンタジーVII"]})
|
|
|
|
assert_array(_search.find_matches("ff7")).contains_exactly(["Final Fantasy VII"])
|
|
assert_str(_search.matched_alternative("Final Fantasy VII")).is_equal("FF7")
|
|
assert_array(_search.find_matches("ファイナル")).contains_exactly(["Final Fantasy VII"])
|
|
|
|
|
|
func test_real_name_match_shows_no_alternative() -> void:
|
|
_search.set_names(["Final Fantasy VII"], {"Final Fantasy VII": ["FF7"]})
|
|
|
|
assert_array(_search.find_matches("fantasy")).contains_exactly(["Final Fantasy VII"])
|
|
assert_str(_search.matched_alternative("Final Fantasy VII")).is_empty()
|
|
|
|
|
|
func test_soundtrack_is_found_once_when_several_names_match() -> void:
|
|
_search.set_names(["Final Fantasy VII"], {"Final Fantasy VII": ["Final Fantasy 7"]})
|
|
|
|
assert_array(_search.find_matches("final")).contains_exactly(["Final Fantasy VII"])
|