The layout test lets the changelog grow in height with its text, and still checks its position and width.
160 lines
6.1 KiB
GDScript
160 lines
6.1 KiB
GDScript
extends GdUnitTestSuite
|
|
## Checks that every node of the main window is where it was before the window was
|
|
## split into scenes.
|
|
##
|
|
## [code]tests/data/layout_baseline.json[/code] was written by
|
|
## [code]tools/take_screenshots.gd[/code] before the split. It holds the position, size
|
|
## and visibility of every node in each UiTour state, keyed by the node's old path. This
|
|
## test runs the same tour and compares, after moving each old path to where the node
|
|
## lives now.
|
|
|
|
const BASELINE_PATH: String = "res://tests/data/layout_baseline.json"
|
|
|
|
## Where each node of the old main window lives now: its old name, and the path that
|
|
## replaces it. Nodes that are not listed are still where they were.
|
|
const MOVED: Dictionary = {
|
|
# The top bar
|
|
"TopPanelContainer": "TopBar/TopPanelContainer",
|
|
"Title": "TopBar/Title",
|
|
"SearchButton": "TopBar/SearchButton",
|
|
"SyncButton": "TopBar/SyncButton",
|
|
"StatisticsButton": "TopBar/StatisticsButton",
|
|
"ShortcutsButton": "TopBar/ShortcutsButton",
|
|
"AboutButton": "TopBar/AboutButton",
|
|
"QRButton": "TopBar/QRButton",
|
|
# The popups of the main window, renamed from *Window to *Popup
|
|
"Search": "SearchPopup",
|
|
"SyncWindow": "SyncPopup",
|
|
"QRWindow": "QRPopup",
|
|
"ShortcutsWindow": "ShortcutsPopup",
|
|
"AboutWindow": "AboutPopup",
|
|
"Log": "LogPopup",
|
|
# The quiz and its parts
|
|
"Open": "Quiz/Open",
|
|
"FileDialog": "Quiz/FileDialog",
|
|
"DebugLabel": "Quiz/DebugLabel",
|
|
"MusicListPanel": "Quiz/MusicListPanel",
|
|
"Players": "Quiz/PlayersPanel",
|
|
"ScrollContainer": "Quiz/InspirationList",
|
|
"VBoxContainer": "Quiz/NowPlaying",
|
|
"MusicPlayer": "Quiz/MusicPlayer/PlayerBar",
|
|
"RepeatSongCheckButton": "Quiz/MusicPlayer/RepeatSongCheckButton",
|
|
"ResetPlaylistButton": "Quiz/QuizToolbar/ResetPlaylistButton",
|
|
"ResetPointsButton": "Quiz/QuizToolbar/ResetPointsButton",
|
|
"SettingsButton": "Quiz/QuizToolbar/SettingsButton",
|
|
"SoundTestButton": "Quiz/QuizToolbar/SoundTestButton",
|
|
"ShowAnswerButton": "Quiz/SongControls/ShowAnswerButton",
|
|
"NextButton": "Quiz/SongControls/NextButton",
|
|
"SettingsWindow": "Quiz/SettingsPopup",
|
|
"NewCharacterSelect": "Quiz/CharacterSelectPopup",
|
|
"WinnerWindow": "Quiz/WinnerPopup",
|
|
}
|
|
|
|
## Scenes that were renamed, by their old and new root name. A row added to a list is
|
|
## named after its scene's root when the name is still free, so these show up deeper in a
|
|
## path.
|
|
const RENAMED_SCENES: Dictionary = {
|
|
"SongListObject": "SongRow",
|
|
"Player": "PlayerRow",
|
|
}
|
|
|
|
const SYNC_COLUMNS: String = "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer"
|
|
|
|
## Nodes renamed inside a popup: the path after the moves above, and the new path.
|
|
const RENAMED_PATHS: Dictionary = {
|
|
"SearchPopup/ScrollContainer/VBoxContainer": "SearchPopup/ScrollContainer/SearchResults",
|
|
SYNC_COLUMNS + "/CatchedErrorsVBox": SYNC_COLUMNS + "/CaughtErrorsVBox",
|
|
SYNC_COLUMNS + "/CaughtErrorsVBox/CatchedErrorsLabel":
|
|
SYNC_COLUMNS + "/CaughtErrorsVBox/CaughtErrorsLabel",
|
|
}
|
|
|
|
## Nodes whose height follows text that changes between versions, such as the changelog.
|
|
## Their position and width are still checked.
|
|
const HEIGHT_FOLLOWS_TEXT: Array[String] = [
|
|
"AboutPopup/AboutPanel/ScrollContainer/ContentVBox",
|
|
]
|
|
|
|
## How far a node may be from its old place, in pixels.
|
|
const TOLERANCE: float = 0.5
|
|
|
|
var _snapshot: AutoloadSnapshot
|
|
var _server: FakeHttpTransport
|
|
var _main: Control
|
|
var _layout: Dictionary = {}
|
|
|
|
|
|
func before() -> void:
|
|
_snapshot = AutoloadSnapshot.take([Settings, Playlist])
|
|
UiTour.prepare_autoloads()
|
|
_server = UiTour.make_server().install()
|
|
# Laid out at the size the baseline was taken at, like the screenshots
|
|
var viewport: SubViewport = auto_free(SubViewport.new())
|
|
viewport.size = Vector2i(1920, 1080)
|
|
viewport.gui_embed_subwindows = true
|
|
Engine.get_main_loop().root.add_child(viewport)
|
|
_main = load("res://main/MainWindow.tscn").instantiate()
|
|
viewport.add_child(_main)
|
|
await UiTour.new(_main).run(func(state: String, popup: Node) -> void:
|
|
_layout[state] = UiTour.dump_layout(_main, _main if popup == null else popup))
|
|
|
|
|
|
func after() -> void:
|
|
_main.free()
|
|
await Engine.get_main_loop().process_frame
|
|
_server.uninstall()
|
|
_snapshot.restore()
|
|
|
|
|
|
## Where the node that used to be at [param old_path] in the main window is now.
|
|
static func new_path(old_path: String) -> String:
|
|
var parts: PackedStringArray = old_path.split("/")
|
|
for i: int in parts.size():
|
|
parts[i] = RENAMED_SCENES.get(parts[i], parts[i])
|
|
var first: String = parts[0]
|
|
parts[0] = MOVED.get(first, first)
|
|
var path: String = "/".join(parts)
|
|
# In order, so a node inside a renamed node is found after its parent's rename
|
|
for renamed: String in RENAMED_PATHS:
|
|
if path == renamed || path.begins_with(renamed + "/"):
|
|
path = RENAMED_PATHS[renamed] + path.trim_prefix(renamed)
|
|
return path
|
|
|
|
|
|
func _baseline() -> Dictionary:
|
|
return JSON.parse_string(FileAccess.get_file_as_string(BASELINE_PATH))
|
|
|
|
|
|
func test_tour_has_the_same_states_as_the_baseline() -> void:
|
|
assert_array(_layout.keys()).contains_exactly_in_any_order(_baseline().keys())
|
|
|
|
|
|
func test_every_node_is_where_it_was() -> void:
|
|
var problems: Array[String] = []
|
|
var baseline: Dictionary = _baseline()
|
|
for state: String in baseline:
|
|
var actual: Dictionary = _layout.get(state, {})
|
|
for old_path: String in baseline[state]:
|
|
var path: String = new_path(old_path)
|
|
var expected: Dictionary = baseline[state][old_path]
|
|
if !actual.has(path):
|
|
problems.append("%s: %s is missing (was %s)" % [state, path, old_path])
|
|
continue
|
|
var rect: Array = actual[path].rect
|
|
var sides: int = 3 if _height_follows_text(path) else 4
|
|
for i: int in sides:
|
|
if absf(rect[i] - expected.rect[i]) > TOLERANCE:
|
|
problems.append("%s: %s is at %s, was %s"
|
|
% [state, path, rect, expected.rect])
|
|
break
|
|
if actual[path].visible != expected.visible:
|
|
problems.append("%s: %s visible is %s, was %s"
|
|
% [state, path, actual[path].visible, expected.visible])
|
|
assert_array(problems).override_failure_message(
|
|
"%d nodes moved:\n%s" % [problems.size(), "\n".join(problems.slice(0, 30))]) \
|
|
.is_empty()
|
|
|
|
|
|
static func _height_follows_text(path: String) -> bool:
|
|
return HEIGHT_FOLLOWS_TEXT.any(func(node_path: String) -> bool:
|
|
return path == node_path || path.begins_with(node_path + "/"))
|