extends SceneTree const STICKMAN_SCENE := preload("res://scenes/main.tscn") const PROP_SCENE := preload("res://scenes/editors/prop/prop_editor.tscn") const STAGE_DIALOG_STYLER := preload("res://scripts/shared/stage_ui/stage_dialog_styler.gd") var _checks: int = 0 var _failures: int = 0 func _initialize() -> void: call_deferred("_run") func _run() -> void: print("\n=== EDITOR VISUAL HARMONIZATION TEST ===") root.size = Vector2i(1280, 800) await _test_stickman_workspace_and_transactions() await _test_prop_reshape_and_chrome() 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 push_error("FAIL: %s" % label) func _test_stickman_workspace_and_transactions() -> void: var main_scene: Control = STICKMAN_SCENE.instantiate() as Control root.add_child(main_scene) await process_frame await process_frame var editor: Control = main_scene.find_child("StickmanEditor", true, false) as Control var panels: Dictionary = editor.get("_body_part_panels") as Dictionary var preview: Control = editor.find_child("WholeStickmanPreview", true, false) as Control var menu_bar: Control = editor.get_node("ChromeRoot/MenuBar") as Control var top_bar: Control = editor.get_node("ChromeRoot/TopBar") as Control var workspace_toolbar: Control = editor.get_node("ChromeRoot/WorkspaceToolbar") as Control var workspace_row: Control = editor.get_node("ChromeRoot/WorkspaceRow") as Control var status_bar: Control = editor.get_node("ChromeRoot/StatusBar") as Control var stickman_save_status: Label = editor.get_node("ChromeRoot/StatusBar/SaveStatus") as Label check("Stickman keeps ten live BodyPartPanel instances", panels.size() == 10) check("one permanent Whole Stickman preview exists", preview != null) check("wide layout adapts to Overview", int(editor.call("get_workspace_view")) == 0) check("Stickman chrome rows and workspace do not overlap", top_bar.position.y >= menu_bar.position.y + menu_bar.size.y - 1.0 and workspace_toolbar.position.y >= top_bar.position.y + top_bar.size.y - 1.0 and workspace_row.position.y >= workspace_toolbar.position.y + workspace_toolbar.size.y - 1.0 and status_bar.position.y >= workspace_row.position.y + workspace_row.size.y - 1.0) check("Overview body panels begin below the chrome", (panels.get("head") as Control).get_global_rect().position.y >= workspace_row.get_global_rect().position.y - 1.0) check("clean new Stickman shows explicit unsaved status", stickman_save_status.text == "Not saved yet") var head: Control = panels.get("head") as Control var head_id: int = head.get_instance_id() check("head part supports authored shapes", bool(head.call("add_shape", &"rectangle", head.call("drawing_center_world")))) check("shape creation marks the document dirty", bool(editor.call("has_unsaved_changes"))) check("Stickman dirty transition shows explicit status", stickman_save_status.text == "Unsaved changes") check("new shape is selected", int(head.call("selected_shape_index")) == 0) var zoom: InputEventMouseButton = InputEventMouseButton.new() zoom.button_index = MOUSE_BUTTON_WHEEL_UP zoom.pressed = true zoom.position = Vector2(100.0, 80.0) head.call("_on_drawing_area_gui_input", zoom) var pan_down: InputEventMouseButton = InputEventMouseButton.new() pan_down.button_index = MOUSE_BUTTON_MIDDLE pan_down.pressed = true pan_down.position = Vector2(100.0, 80.0) head.call("_on_drawing_area_gui_input", pan_down) var pan_motion: InputEventMouseMotion = InputEventMouseMotion.new() pan_motion.position = Vector2(112.0, 91.0) head.call("_on_drawing_area_gui_input", pan_motion) var pan_up: InputEventMouseButton = InputEventMouseButton.new() pan_up.button_index = MOUSE_BUTTON_MIDDLE pan_up.pressed = false pan_up.position = Vector2(112.0, 91.0) head.call("_on_drawing_area_gui_input", pan_up) var saved_view: Dictionary = head.call("get_view_context") as Dictionary var saved_pan: Vector2 = saved_view.get("pan_offset", Vector2.ZERO) as Vector2 check("panel zoom and pan are active before moving it", float(saved_view.get("zoom", 1.0)) > 1.0 and saved_pan.length() > 0.0) var stickman_path: String = "user://editor_visual_harmonization_test.stk" check("Stickman saves through its document API", bool(editor.call("save_to_path", stickman_path))) check("successful save clears dirty state", not bool(editor.call("has_unsaved_changes"))) check("saved Stickman shows explicit saved status", stickman_save_status.text == "Saved") var stickman_file: FileAccess = FileAccess.open(stickman_path, FileAccess.READ) var stickman_json: Variant = JSON.parse_string(stickman_file.get_as_text()) stickman_file.close() var saved_parts: Dictionary = (stickman_json as Dictionary).get("body_parts", {}) as Dictionary var saved_head: Dictionary = saved_parts.get("head", {}) as Dictionary check("save preserves the existing 1.5 format", str((stickman_json as Dictionary).get("version", "")) == "1.5") check("save includes authored head shapes", (saved_head.get("shapes", []) as Array).size() == 1) check("save still writes guide offset metadata", saved_head.has("guide_offset")) var before_switch_view: Dictionary = head.call("get_view_context") as Dictionary editor.call("request_workspace_view", 1, true) check("Focus view keeps the same panel instance", head.get_instance_id() == head_id and int(editor.call("get_workspace_view")) == 1) check("Focus view preserves panel zoom and pan", _view_contexts_equal(before_switch_view, head.call("get_view_context") as Dictionary)) check("view choice does not dirty authored content", not bool(editor.call("has_unsaved_changes"))) root.size = Vector2i(520, 800) await process_frame editor.call("_refresh_responsive_workspace") check("manual Focus choice survives a narrow resize", int(editor.call("get_workspace_view")) == 1) var whole_stickman_button: Button = editor.get_node("ChromeRoot/WorkspaceToolbar/WholeStickmanButton") as Button var back_to_part_button: Button = editor.get_node("ChromeRoot/WorkspaceToolbar/BackToPartButton") as Button var body_part_workspace: Control = editor.get_node("ChromeRoot/WorkspaceRow/BodyPartWorkspace") as Control check("narrow Focus keeps part controls and Whole Stickman access visible", body_part_workspace.visible and whole_stickman_button.visible and not preview.visible) whole_stickman_button.emit_signal("pressed") await process_frame check("narrow Whole Stickman control opens the permanent preview", not body_part_workspace.visible and preview.visible and back_to_part_button.visible and int(editor.call("get_workspace_view")) == 1) back_to_part_button.emit_signal("pressed") await process_frame check("Back to Part restores the same live panel at narrow width", body_part_workspace.visible and not preview.visible and whole_stickman_button.visible and head.get_instance_id() == head_id) root.size = Vector2i(1280, 800) await process_frame editor.call("_refresh_responsive_workspace") editor.call("request_active_part", "torso") check("part picker switches the active focus panel", str(editor.call("get_active_part_name")) == "torso") editor.call("request_active_part", "head") check("switching back restores the original head instance", panels.get("head") == head and head.get_instance_id() == head_id) check("switching parts preserves head geometry and selection", int(head.call("selected_shape_index")) == 0 and (head.get("shapes") as Array).size() == 1) editor.call("request_workspace_view", 0, true) check("Overview restores the same authored panel", int(editor.call("get_workspace_view")) == 0 and head.get_instance_id() == head_id) head.call("open_selected_color_picker") var picker: PopupPanel = head.get("color_picker_popup") as PopupPanel check("open color popup blocks presentation switching", picker.visible and not bool(head.call("can_switch_presentation"))) editor.call("request_active_part", "torso") editor.call("request_workspace_view", 1, true) check("deferred view switch waits while popup is open", int(editor.call("get_workspace_view")) == 0 and str(editor.call("get_active_part_name")) == "head") picker.hide() await process_frame await process_frame check("deferred part and view switch resumes after popup closes", int(editor.call("get_workspace_view")) == 1 and str(editor.call("get_active_part_name")) == "torso") check("popup and workspace changes remain clean", not bool(editor.call("has_unsaved_changes"))) editor.call("request_active_part", "head") var preview_area: Control = preview.get("preview_area") as Control await process_frame var raw_points: PackedVector2Array = preview.call("_collect_all_raw_points", "head") as PackedVector2Array var part_center: Vector2 = preview.call("_compute_center", raw_points) as Vector2 var initial_part_position: Vector2 = preview.call("get_part_position", "head") as Vector2 var mouse_down: InputEventMouseButton = InputEventMouseButton.new() mouse_down.button_index = MOUSE_BUTTON_LEFT mouse_down.pressed = true mouse_down.position = part_center preview.call("_on_preview_gui_input", mouse_down) var drag: InputEventMouseMotion = InputEventMouseMotion.new() drag.position = part_center + Vector2(16.0, 12.0) preview.call("_on_preview_gui_input", drag) var mouse_up: InputEventMouseButton = InputEventMouseButton.new() mouse_up.button_index = MOUSE_BUTTON_LEFT mouse_up.pressed = false mouse_up.position = drag.position preview.call("_on_preview_gui_input", mouse_up) check("preview drag commits an authored part transform", not (preview.call("get_part_position", "head") as Vector2).is_equal_approx(initial_part_position)) check("committed preview transform marks document dirty", bool(editor.call("has_unsaved_changes"))) check("preview input uses the live preview area", preview_area != null and preview_area.size.x > 0.0) check("save after transform keeps the existing format", bool(editor.call("save_to_path", stickman_path)) and not bool(editor.call("has_unsaved_changes"))) preview.call("_mirror_part_x") check("mirror transform marks document dirty", bool(editor.call("has_unsaved_changes")) and (preview.call("get_part_scale", "head") as Vector2).x < 0.0) check("mirror transform can be saved", bool(editor.call("save_to_path", stickman_path)) and not bool(editor.call("has_unsaved_changes"))) preview.call("_bring_forward_part") check("part order edit marks document dirty", bool(editor.call("has_unsaved_changes"))) check("second shape can be authored", bool(head.call("add_shape", &"line", head.call("drawing_center_world")))) editor.call("_on_file_menu_id_pressed", 2) var unsaved_dialog: ConfirmationDialog = editor.get("_unsaved_dialog") as ConfirmationDialog check("Start Over shows a save/discard/cancel transaction", STAGE_DIALOG_STYLER.is_open(unsaved_dialog)) unsaved_dialog.get_cancel_button().emit_signal("pressed") await process_frame check("Cancel leaves dirty content in place", bool(editor.call("has_unsaved_changes")) and (head.get("shapes") as Array).size() == 2) editor.call("_on_file_menu_id_pressed", 2) unsaved_dialog.emit_signal("custom_action", "discard") await process_frame check("Discard clears authored parts and dirty state", not bool(editor.call("has_unsaved_changes")) and (head.get("shapes") as Array).is_empty()) check("clean new Stickman returns to explicit unsaved status", stickman_save_status.text == "Not saved yet") check("Start Over keeps the selected workspace view", int(editor.call("get_workspace_view")) == 1) DirAccess.remove_absolute(stickman_path) main_scene.queue_free() await process_frame func _test_prop_reshape_and_chrome() -> void: var editor: Control = PROP_SCENE.instantiate() as Control root.add_child(editor) await process_frame await process_frame var canvas: Control = editor.get("_canvas") as Control var save_status: Label = editor.get("_save_status") as Label check("Prop chrome uses wrapping top and mode rows", editor.get("_top_right") is FlowContainer and editor.get("_mode_bar") is FlowContainer) check("Prop action target meets 44 px minimum", (editor.get("_create_point_button") as Button).custom_minimum_size.y >= 44.0) check("new Prop has an explicit clean save status", str(save_status.text).ends_with("Not saved yet")) check("whole-prop action uses approved discoverable wording", str((editor.get("_whole_prop_resize_button") as Button).text) == "Move, Turn, or Resize Prop") check("material physics tile uses child-facing grip language", str((editor.get("_friction_label") as Label).text) == "Surface grip") var preset_option: PropInspectorDropdown = editor.get("_preset_option") as PropInspectorDropdown check("material choices show colored swatches in field and popup", preset_option.item_count == 5 and preset_option.get_item_icon(0) != null and preset_option.get_item_icon(4) != null and preset_option.icon != null) var material_selected_row: StyleBoxFlat = preset_option.get_popup().get_theme_stylebox("hover") as StyleBoxFlat var material_items_plain: bool = true for index: int in preset_option.item_count: material_items_plain = material_items_plain and not preset_option.get_popup().is_item_checkable(index) check("material popup uses the full-row selection highlight without radio items", material_items_plain and material_selected_row.bg_color.is_equal_approx(Color("#103B4D")) and material_selected_row.border_color.is_equal_approx(Color("#22C6FF"))) await process_frame preset_option.show_popup() await process_frame check("opening the material popup focuses its current value and spans the field", preset_option.get_popup().get_focused_item() == preset_option.selected and preset_option.get_popup().size.x >= preset_option.size.x) check("dropdown popup icons reserve a 44 px row target", preset_option.get_popup().get_item_icon(0).get_width() >= 44) preset_option.hide_popup() preset_option.get_popup().emit_signal("id_pressed", 1) check("choosing a material updates the field and editor model", preset_option.selected == 1 and int(editor.get("_material_preset")) == 1) preset_option.get_popup().emit_signal("id_pressed", 0) var primitive_option: PropInspectorDropdown = editor.get("_primitive_option") as PropInspectorDropdown check("2D shape choices show flat icons in field and popup", primitive_option.item_count == 5 and primitive_option.get_item_icon(0) != null and primitive_option.get_item_icon(4) != null and primitive_option.icon != null) var shape_items_plain: bool = true for index: int in primitive_option.item_count: shape_items_plain = shape_items_plain and not primitive_option.get_popup().is_item_checkable(index) check("shape popup does not draw radio selectors", shape_items_plain) primitive_option.show_popup() await process_frame check("shape popup highlights its current 2D choice", primitive_option.get_popup().get_focused_item() == primitive_option.selected) primitive_option.hide_popup() primitive_option.get_popup().emit_signal("id_pressed", 2) check("choosing a 2D shape updates its parameters", primitive_option.selected == 2 and (editor.get("_param_spins") as Dictionary).has("base")) primitive_option.get_popup().emit_signal("id_pressed", 0) var tag_buttons: Dictionary = editor.get("_tag_checks") as Dictionary var nature_button: Button = tag_buttons.get("nature") as Button check("artwork tag chip has a colored icon and a wide touch target", nature_button != null and nature_button.icon != null and nature_button.custom_minimum_size.y >= 48.0) var nature_icon: Texture2D = nature_button.icon nature_button.set_pressed_no_signal(true) nature_button.emit_signal("toggled", true) var selected_tag_style := nature_button.get_theme_stylebox("pressed") as StyleBoxFlat check("tag selection updates its model", (editor.get("_tags") as Array).has("nature")) check("selected tag keeps its original colored icon without a badge", nature_button.icon == nature_icon) check("selected tag style uses a cyan-tinted highlight", selected_tag_style.bg_color.is_equal_approx(Color("#103B4D"))) nature_button.set_pressed_no_signal(false) nature_button.emit_signal("toggled", false) check("clearing a tag removes it from the tag model", not (editor.get("_tags") as Array).has("nature")) check("inspector sections use themed cards", editor.get("_material_section") is PanelContainer and editor.get("_add_section") is PanelContainer and editor.get("_tag_section") is PanelContainer) check("anchor layer control uses the approved label", (editor.get("_anchor_z") as SpinBox).get_parent().get_child(0) is Label and str(((editor.get("_anchor_z") as SpinBox).get_parent().get_child(0) as Label).text) == "Held prop layer:") canvas.call("add_primitive", "box", Vector2(150.0, 150.0)) var shapes: Array = canvas.call("get_shape_data") as Array check("prop primitive is selected for editing", int(canvas.call("get_selected_index")) == 0 and shapes.size() == 1) check("Prop edit shows explicit unsaved status", str(save_status.text).ends_with("Unsaved changes")) var prop_path: String = "user://editor_visual_harmonization_test.prop" var save_result: Variant = await editor.call("_save_to", prop_path, false) check("clean Prop save shows explicit saved status", bool(save_result) and str(save_status.text).ends_with("Saved")) editor.call("_on_points_mode_pressed") var create_button: Button = editor.get("_create_point_button") as Button check("Reshape exposes the Create Point action", create_button.visible and create_button.text == "📍 Create Point") create_button.emit_signal("pressed") check("Create Point arms with a visible Esc hint", bool(canvas.call("is_create_point_armed")) and str((editor.get("_create_point_hint") as Label).text).contains("Press Esc")) var escape: InputEventKey = InputEventKey.new() escape.keycode = KEY_ESCAPE escape.pressed = true editor.call("_unhandled_key_input", escape) check("Esc cancels the armed point action", not bool(canvas.call("is_create_point_armed"))) create_button.emit_signal("pressed") var internal_shapes: Array = canvas.get("shapes") as Array var first_shape: Dictionary = internal_shapes[0] as Dictionary var original_points: PackedVector2Array = first_shape.get("points", PackedVector2Array()) as PackedVector2Array var edge_midpoint: Vector2 = (original_points[0] + original_points[1]) * 0.5 var canvas_zoom: float = float(canvas.get("_zoom")) var canvas_pan: Vector2 = canvas.get("_pan_offset") as Vector2 var edge_screen_pos: Vector2 = edge_midpoint * canvas_zoom + canvas_pan check("toolbar click target resolves to the visible shape edge", int(canvas.call("hit_test_segment", edge_midpoint)) == 0) var edge_click: InputEventMouseButton = InputEventMouseButton.new() edge_click.button_index = MOUSE_BUTTON_LEFT edge_click.pressed = true edge_click.position = edge_screen_pos canvas.call("_on_canvas_gui_input", edge_click) shapes = canvas.call("get_shape_data") as Array var reshaped: Dictionary = shapes[0] as Dictionary var vertices: PackedVector2Array = reshaped.get("points", PackedVector2Array()) as PackedVector2Array check("armed edge click inserts a point", vertices.size() == 5) check("inserted point records reshaped state", bool(reshaped.get("reshaped", false))) check("inserted point marks Prop document dirty", bool(editor.get("_dirty"))) check("Create Point updates Prop status to unsaved", str(save_status.text).ends_with("Unsaved changes")) check("one click disarms Create Point", not bool(canvas.call("is_create_point_armed"))) if FileAccess.file_exists(prop_path): DirAccess.remove_absolute(ProjectSettings.globalize_path(prop_path)) editor.queue_free() await process_frame func _view_contexts_equal(left: Dictionary, right: Dictionary) -> bool: var left_pan: Vector2 = left.get("pan_offset", Vector2.ZERO) as Vector2 var right_pan: Vector2 = right.get("pan_offset", Vector2.ZERO) as Vector2 return is_equal_approx(float(left.get("zoom", 1.0)), float(right.get("zoom", 1.0))) and left_pan.is_equal_approx(right_pan)