extends GdUnitTestSuite ## How a song in the music list is laid out: the players' pictures on the right in even ## lines of at most four, each ending at the row's right edge, the text in the room they leave, wrapping only when it does ## not fit, and the row as tall as the taller of the two. Checked for every number of ## players, with short, debug mode and very long song texts. const SHORT: Array[String] = ["Chrono Trigger", "Corridors of Time"] const LONG: Array[String] = [ "Final Fantasy Dissidia 012 Duodecim (Sista Fantasin Meningsskiljaktigheter 012 Tolv)", "'Crystal Cave -arrange-' from FINAL FANTASY III", ] ## The row's padding, from the SongRow styles in the theme. const PADDING: float = 8.0 var _snapshot: AutoloadSnapshot var _runner: GdUnitSceneRunner var _panel: MusicListPanel # The panel's width in the quiz, which songs must never make wider var _panel_width: float func before_test() -> void: _snapshot = AutoloadSnapshot.take([Settings, Playlist]) Settings.is_debug = false Playlist.reset_playlist() Settings.player_array.clear() for i: int in Settings.MAX_NUMBER_OF_PLAYERS: Settings.player_array.append(PlayerObject.new("Player %d" % (i + 1))) _runner = scene_runner("res://quiz/MusicListPanel.tscn") _panel = _runner.scene() _panel_width = _panel.size.x func after_test() -> void: await await_idle_frame() _snapshot.restore() # Adds a played and answered song with points for the first [param players] players func _add_song(title: Array[String], players: int) -> SongObject: var song: SongObject = SongObject.new() song.song_number = Playlist.song_object_array.size() song.soundtrack_title = title[0] song.song_title = title[1] song.has_played = true song.is_answered = true for i: int in players: song.add_point(Settings.player_array[i].id) Playlist.add_to_playlist(song) return song func _show() -> void: _panel.refresh() await _runner.simulate_frames(3) func _rows() -> Array[SongRow]: var rows: Array[SongRow] = [] rows.assign(_panel.get_node("%MusicList").get_children()) return rows func _pictures(row: SongRow) -> Array[TextureRect]: return row.pictures() # How wide the row's text would be on one line func _text_width(row: SongRow) -> float: var label: Label = row.song_list_label return label.get_theme_font(&"font").get_string_size(label.text, HORIZONTAL_ALIGNMENT_LEFT, -1, label.get_theme_font_size(&"font_size")).x func _one_line_height() -> float: _add_song(SHORT, 0) await _show() var height: float = _rows()[0].size.y Playlist.reset_playlist() return height # Every number of players with every kind of text, in normal and debug mode func _each_layout(check: Callable) -> void: for debug: bool in [false, true]: Settings.is_debug = debug for title: Array[String] in [SHORT, LONG]: Playlist.reset_playlist() for players: int in Settings.MAX_NUMBER_OF_PLAYERS + 1: _add_song(title, players) await _show() assert_float(_panel.size.x).override_failure_message( "The songs made the list wider than its place in the quiz") \ .is_equal(_panel_width) for row: SongRow in _rows(): check.call(row, "%d players, %s text%s" % [_pictures(row).size(), "long" if title == LONG else "short", " (debug)" if debug else ""]) # Columns func test_pictures_are_spread_evenly_over_as_few_lines_as_possible() -> void: var columns: Array[int] = [] for pictures: int in range(0, 9): columns.append(SongRow.picture_columns(pictures)) # 5 pictures are 3 + 2, not 4 + 1; 7 are 4 + 3 assert_array(columns).contains_exactly([1, 1, 2, 3, 4, 3, 3, 4, 4]) # Placement func test_pictures_are_at_the_right_end_of_the_row() -> void: await _each_layout(func(row: SongRow, layout: String) -> void: if _pictures(row).is_empty(): assert_bool(row.song_list_players.visible).override_failure_message(layout).is_false() return var pictures: Rect2 = row.song_list_players.get_global_rect() assert_float(pictures.end.x).override_failure_message(layout) \ .is_equal_approx(row.get_global_rect().end.x - PADDING, 1.0)) func test_every_line_of_pictures_ends_at_the_right_edge() -> void: await _each_layout(func(row: SongRow, layout: String) -> void: var right_edge: float = row.get_global_rect().end.x - PADDING for line: HBoxContainer in row.song_list_players.get_children(): var last: TextureRect = line.get_child(line.get_child_count() - 1) assert_float(last.get_global_rect().end.x).override_failure_message(layout) \ .is_equal_approx(right_edge, 1.0)) func test_text_and_pictures_never_overlap_and_stay_inside_the_row() -> void: await _each_layout(func(row: SongRow, layout: String) -> void: var row_rect: Rect2 = row.get_global_rect().grow(0.5) var text: Rect2 = row.song_list_label.get_global_rect() assert_bool(row_rect.encloses(text)).override_failure_message( "Text outside the row: " + layout).is_true() for picture: TextureRect in _pictures(row): var rect: Rect2 = picture.get_global_rect() assert_bool(row_rect.encloses(rect)).override_failure_message( "Picture outside the row: " + layout).is_true() assert_float(rect.position.x).override_failure_message( "Picture over the text: " + layout).is_greater_equal(text.end.x)) func test_text_gets_all_the_room_the_pictures_leave() -> void: await _each_layout(func(row: SongRow, layout: String) -> void: var text: Rect2 = row.song_list_label.get_global_rect() assert_float(text.position.x).override_failure_message(layout) \ .is_equal_approx(row.get_global_rect().position.x + PADDING, 1.0) var right_end: float = row.get_global_rect().end.x - PADDING if row.song_list_players.visible: right_end = row.song_list_players.get_global_rect().position.x \ - row.get_node("HBoxContainer").get_theme_constant(&"separation") assert_float(text.end.x).override_failure_message(layout) \ .is_equal_approx(right_end, 1.0)) func test_text_wraps_only_when_it_does_not_fit_on_one_line() -> void: await _each_layout(func(row: SongRow, layout: String) -> void: var lines: int = row.song_list_label.get_line_count() var room: float = row.song_list_label.size.x if _text_width(row) <= room - 1: assert_int(lines).override_failure_message("Wrapped though it fits: " + layout) \ .is_equal(1) elif _text_width(row) > room + 1: assert_int(lines).override_failure_message("Not wrapped: " + layout) \ .is_greater(1)) func test_every_layout_holds_in_a_narrower_list() -> void: _panel.size.x = 640 _panel_width = 640 await _runner.simulate_frames(1) await _each_layout(func(row: SongRow, layout: String) -> void: var text: Rect2 = row.song_list_label.get_global_rect() assert_bool(row.get_global_rect().grow(0.5).encloses(text)).override_failure_message( "Text outside the row: " + layout).is_true() if row.song_list_players.visible: assert_float(row.song_list_players.get_global_rect().position.x) \ .override_failure_message("Pictures over the text: " + layout) \ .is_greater_equal(text.end.x)) func test_short_text_is_one_line_even_with_every_player() -> void: _add_song(SHORT, Settings.MAX_NUMBER_OF_PLAYERS) await _show() assert_int(_rows()[0].song_list_label.get_line_count()).is_equal(1) func test_long_text_wraps_and_keeps_every_word() -> void: _add_song(LONG, 2) await _show() var label: Label = _rows()[0].song_list_label assert_int(label.get_line_count()).is_greater(1) assert_str(label.text).ends_with("FINAL FANTASY III") assert_int(label.get_visible_line_count()).is_equal(label.get_line_count()) # Height func test_row_is_one_line_high_with_up_to_four_pictures() -> void: var one_line: float = await _one_line_height() for players: int in range(0, SongRow.MAX_PICTURES_PER_LINE + 1): _add_song(SHORT, players) await _show() for row: SongRow in _rows(): assert_float(row.size.y).override_failure_message( "%d pictures" % _pictures(row).size()).is_equal_approx(one_line, 0.5) func test_five_pictures_go_on_two_lines_of_three_and_two() -> void: _add_song(SHORT, 5) await _show() assert_array(_lines_of_pictures(_rows()[0])).contains_exactly([3, 2]) func test_seven_pictures_go_on_two_lines_of_four_and_three() -> void: _add_song(SHORT, 7) await _show() assert_array(_lines_of_pictures(_rows()[0])).contains_exactly([4, 3]) # How many pictures are on each line, top to bottom func _lines_of_pictures(row: SongRow) -> Array[int]: var by_top: Dictionary[float, int] = {} for picture: TextureRect in _pictures(row): var top: float = roundf(picture.global_position.y) by_top[top] = by_top.get(top, 0) + 1 var tops: Array[float] = [] tops.assign(by_top.keys()) tops.sort() var lines: Array[int] = [] for top: float in tops: lines.append(by_top[top]) return lines func test_row_grows_to_fit_two_lines_of_pictures_and_centres_the_text() -> void: var one_line: float = await _one_line_height() _add_song(SHORT, 5) await _show() var row: SongRow = _rows()[0] var pictures: Rect2 = row.song_list_players.get_global_rect() assert_float(row.size.y).is_greater(one_line) assert_float(pictures.size.y).is_equal_approx( SongRow.PICTURE_SIZE.y * 2 + row.song_list_players.get_theme_constant( &"separation"), 0.5) assert_bool(row.get_global_rect().encloses(pictures)).is_true() # The single line of text is in the middle, beside the pictures assert_int(row.song_list_label.vertical_alignment).is_equal(VERTICAL_ALIGNMENT_CENTER) assert_float(row.song_list_label.get_global_rect().get_center().y) \ .is_equal_approx(pictures.get_center().y, 1.0) func test_pictures_are_centred_beside_text_on_many_lines() -> void: _add_song(LONG, 1) await _show() var row: SongRow = _rows()[0] var text: Rect2 = row.song_list_label.get_global_rect() var pictures: Rect2 = row.song_list_players.get_global_rect() assert_int(row.song_list_label.get_line_count()).is_greater(1) assert_float(pictures.size.y).is_less(text.size.y) assert_float(pictures.get_center().y).is_equal_approx(text.get_center().y, 1.0) func test_every_picture_has_the_same_size() -> void: await _each_layout(func(row: SongRow, layout: String) -> void: for picture: TextureRect in _pictures(row): assert_vector(picture.size).override_failure_message(layout) \ .is_equal(SongRow.PICTURE_SIZE)) func test_rows_never_need_sideways_scrolling() -> void: Settings.is_debug = true for players: int in Settings.MAX_NUMBER_OF_PLAYERS + 1: _add_song(LONG, players) await _show() var scroll: ScrollContainer = _panel.get_node("ScrollContainer") assert_bool(scroll.get_h_scroll_bar().visible).is_false() assert_float(_panel.size.x).is_equal(_panel_width) for row: SongRow in _rows(): assert_float(row.get_global_rect().end.x) \ .is_less_equal(scroll.get_global_rect().end.x + 0.5) # Pictures func test_pictures_show_who_got_a_point_in_player_list_order() -> void: var song: SongObject = _add_song(SHORT, 0) # Points given in another order than the player list song.add_point(Settings.player_array[2].id) song.add_point(Settings.player_array[0].id) await _show() var names: Array[String] = [] for picture: TextureRect in _pictures(_rows()[0]): names.append(picture.tooltip_text) assert_array(names).contains_exactly(["Player 1", "Player 3"]) assert_object(_pictures(_rows()[0])[0].texture).is_same(Settings.player_array[0].character) func test_pictures_are_redone_when_points_change() -> void: var song: SongObject = _add_song(SHORT, 4) await _show() song.add_point(Settings.player_array[4].id) _rows()[0].update_players() await _runner.simulate_frames(2) assert_array(_lines_of_pictures(_rows()[0])).contains_exactly([3, 2]) func test_clicking_a_picture_clicks_the_song() -> void: _add_song(SHORT, 0) _add_song(SHORT, 3) await _show() var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_panel)) var picture: TextureRect = _pictures(_rows()[1])[1] _runner.set_mouse_position( _panel.get_viewport().get_final_transform() * picture.get_global_rect().get_center()) await _runner.simulate_mouse_button_pressed(MOUSE_BUTTON_LEFT) await _runner.await_input_processed() await signals.is_emitted("song_clicked", [1]) # Styles func test_rows_alternate_and_the_playing_song_stands_out() -> void: for i: int in 4: _add_song(SHORT, 0) Playlist.song_object_array[2].is_playing = true await _show() var styles: Array[StringName] = [] for row: SongRow in _rows(): styles.append(row.theme_type_variation) assert_array(styles).contains_exactly( [&"SongRow", &"SongRowOdd", &"SongRowPlaying", &"SongRowOdd"]) assert_str(_rows()[2].song_list_label.theme_type_variation).is_equal("SongRowLabelPlaying") assert_str(_rows()[1].song_list_label.theme_type_variation).is_equal("SongRowLabel") func test_hovering_a_row_highlights_it_until_the_mouse_leaves() -> void: _add_song(SHORT, 0) _add_song(SHORT, 0) await _show() var row: SongRow = _rows()[1] row.mouse_entered.emit() assert_str(row.theme_type_variation).is_equal("SongRowHover") row.mouse_exited.emit() assert_str(row.theme_type_variation).is_equal("SongRowOdd")