Files
MusicPlayer/tests/integration/test_layout_unchanged.gd
T
Sansan e94c75e84d Split now playing, the quiz buttons and song loading out of the quiz
NowPlaying, QuizToolbar and SongControls become their own scenes, the auto
repeat switch moves into the music player scene, and downloading songs moves
into a SongLoader node. The quiz only wires the parts together. Every node
keeps its place on screen.

Screenshots of every UI state are pixel-identical to before, the layout test
still matches the original baseline, and each new part has its own tests.
2026-10-06 11:11:18 +02:00

115 lines
4.2 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"
## Nodes that moved from the main window into the Quiz scene, by their old name.
const QUIZ_NODES: Array[String] = [
"Open", "FileDialog", "MusicListPanel", "DebugLabel", "SettingsWindow",
"NewCharacterSelect", "WinnerWindow", "Log",
]
## Nodes that moved into the Quiz scene under another path: old name to new path in
## the quiz.
const RENAMED_QUIZ_NODES: Dictionary = {
"Players": "PlayersPanel",
"ScrollContainer": "InspirationList",
"VBoxContainer": "NowPlaying",
"MusicPlayer": "MusicPlayer/PlayerBar",
"RepeatSongCheckButton": "MusicPlayer/RepeatSongCheckButton",
"ResetPlaylistButton": "QuizToolbar/ResetPlaylistButton",
"ResetPointsButton": "QuizToolbar/ResetPointsButton",
"SettingsButton": "QuizToolbar/SettingsButton",
"SoundTestButton": "QuizToolbar/SoundTestButton",
"ShowAnswerButton": "SongControls/ShowAnswerButton",
"NextButton": "SongControls/NextButton",
}
## Nodes that moved from the main window into the TopBar scene, by their old name.
const TOP_BAR_NODES: Array[String] = [
"TopPanelContainer", "Title", "SearchButton", "SyncButton", "StatisticsButton",
"ShortcutsButton", "AboutButton", "QRButton",
]
## 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://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 first: String = old_path.get_slice("/", 0)
if first in QUIZ_NODES:
return "Quiz/" + old_path
if first in RENAMED_QUIZ_NODES:
return "Quiz/" + RENAMED_QUIZ_NODES[first] + old_path.trim_prefix(first)
if first in TOP_BAR_NODES:
return "TopBar/" + old_path
return old_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
for i: int in 4:
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()