- Implement tests for PropFile, including preset conversion, file dictionary conversion, round-trip serialization, version rejection, default values for missing keys, flattening, sanitization, and save/load functionality. - Create tests for PropEditor, covering window title formatting, mode button text, safe saving, and recent file list management. - Add tests for PropTheme, validating fallback behavior, invalid input handling, and default values for primitives and UI tokens. - Introduce tests for ThumbnailCache, ensuring proper key formatting, PNG handling, and behavior with missing files.
90 lines
4.1 KiB
GDScript
90 lines
4.1 KiB
GDScript
# Phase 9 Prop Editor Round 3 - headless regression checks for the pure static
|
|
# helpers (window title, mode button text) and PropFile.save_safe atomic writes.
|
|
# Run with:
|
|
# & "C:\Godot4\Godot_v4.7.1-stable_win64_console.exe" --headless --path "C:\tmp\stickman" --script "res://tests/test_prop_editor_round3.gd"
|
|
|
|
extends SceneTree
|
|
|
|
const PROP_EDITOR := preload("res://scripts/prop_editor.gd")
|
|
const PROP_FILE := preload("res://scripts/prop_file.gd")
|
|
|
|
var _checks: int = 0
|
|
var _failures: int = 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
print("\n=== PHASE 9 PROP EDITOR ROUND 3 TEST ===")
|
|
await _test_window_title()
|
|
await _test_mode_button_text()
|
|
await _test_save_safe()
|
|
await _test_recent_list()
|
|
print("----------------------------------------")
|
|
if _failures == 0:
|
|
print("RESULT: ALL PASSED (%d assertions)" % _checks)
|
|
quit(0)
|
|
else:
|
|
print("RESULT: %d FAILURE(S) out of %d assertions" % [_failures, _checks])
|
|
quit(1)
|
|
|
|
|
|
func check(label: String, condition: bool) -> void:
|
|
_checks += 1
|
|
if condition:
|
|
print("PASS: %s" % label)
|
|
else:
|
|
_failures += 1
|
|
print("FAIL: %s" % label)
|
|
|
|
|
|
func _test_window_title() -> void:
|
|
check("untitled no path no dirty", PROP_EDITOR.build_window_title("", "", false) == "Untitled")
|
|
check("name dirty", PROP_EDITOR.build_window_title("My Crate", "", true) == "My Crate *")
|
|
check("name path clean", PROP_EDITOR.build_window_title("My Crate", "res://props/mycrate.prop", false) == "My Crate — mycrate.prop")
|
|
check("untitled path dirty", PROP_EDITOR.build_window_title("", "res://props/mycrate.prop", true) == "Untitled — mycrate.prop *")
|
|
check("whitespace name falls back", PROP_EDITOR.build_window_title(" ", "", false) == "Untitled")
|
|
check("extension kept in title", PROP_EDITOR.build_window_title("X", "res://props/ball.prop", false).ends_with("ball.prop"))
|
|
|
|
|
|
func _test_mode_button_text() -> void:
|
|
check("arrange label", PROP_EDITOR.mode_button_text(0) == "🖐️ Arrange")
|
|
check("reshape label", PROP_EDITOR.mode_button_text(1) == "✏️ Reshape")
|
|
check("out-of-range clamps to last", PROP_EDITOR.mode_button_text(99) == "✏️ Reshape")
|
|
check("negative clamps to first", PROP_EDITOR.mode_button_text(-5) == "🖐️ Arrange")
|
|
|
|
|
|
func _test_save_safe() -> void:
|
|
var path := "user://round3_autosave_test.prop"
|
|
var data := {
|
|
"version": "1.0",
|
|
"prop_name": "X",
|
|
"material_preset": "none",
|
|
"shapes": [],
|
|
"metadata": {"autosave": true, "source_path": "", "modified_unix": 123},
|
|
}
|
|
check("save_safe first write", PROP_FILE.save_safe(path, data))
|
|
var loaded: Dictionary = PROP_FILE.load(path)
|
|
check("save_safe round trip", not loaded.is_empty() and loaded.get("prop_name", "") == "X" and bool((loaded.get("metadata", {}) as Dictionary).get("autosave", false)))
|
|
check("no tmp leftover", not FileAccess.file_exists(path + ".tmp"))
|
|
check("no bak leftover", not FileAccess.file_exists(path + ".bak"))
|
|
data["prop_name"] = "Y"
|
|
check("save_safe overwrite", PROP_FILE.save_safe(path, data))
|
|
var loaded2: Dictionary = PROP_FILE.load(path)
|
|
check("overwrite persisted", loaded2.get("prop_name", "") == "Y")
|
|
check("no leftovers after overwrite", not FileAccess.file_exists(path + ".tmp") and not FileAccess.file_exists(path + ".bak"))
|
|
DirAccess.remove_absolute(path)
|
|
|
|
|
|
func _test_recent_list() -> void:
|
|
var list: Array = PROP_EDITOR.update_recent_list([], "res://props/a.prop", 5)
|
|
check("recent empty list inserts", list == ["res://props/a.prop"])
|
|
list = PROP_EDITOR.update_recent_list(["res://props/b.prop", "res://props/a.prop"], "res://props/a.prop", 5)
|
|
check("recent dedupe moves to front", list == ["res://props/a.prop", "res://props/b.prop"])
|
|
list = PROP_EDITOR.update_recent_list(["res://props/b.prop", "res://props/c.prop"], "res://props/a.prop", 5)
|
|
check("recent insert keeps most-recent-first", list == ["res://props/a.prop", "res://props/b.prop", "res://props/c.prop"])
|
|
list = PROP_EDITOR.update_recent_list(["res://props/b.prop", "res://props/c.prop", "res://props/d.prop", "res://props/e.prop", "res://props/f.prop"], "res://props/a.prop", 5)
|
|
check("recent trims to max", list.size() == 5 and list[0] == "res://props/a.prop" and list[4] == "res://props/e.prop")
|