extends SceneTree ## Smoke test for redesigned AvatarCustomizationUI (Tasks 680b6e91d699 & f87183b6d9e4). ## ## Verifies: ## 1. Header Bar: Back button (< Back [ESC], Vector2(120, 38)), Title ("Avatar Studio"), ## Live Buffalo Chips balance label ("Wallet: %d Buffalo Chips"), Status pill ("All changes saved"). ## 2. Category Tabs: Tab 0 "Body & Head", Tab 1 "Clothing", Tab 2 "Accessories", Tab 3 "Skin & Colors". ## 3. Human-friendly item names (display_name lookup, no raw IDs like hair_mohawk_01 as primary title). ## 4. Equipped badges ("✓ EQUIPPED") and highlighted borders on equipped items. ## 5. Skin & Colors presets, shading slider, and custom RGB sliders. ## 6. Preview Camera Zoom & Framing Presets: ## - Presets: "Full Body" (3.5, 0.98), "Head" (1.6, 1.62), "Torso" (2.2, 1.12), "Feet" (1.8, 0.35). ## - Zoom clamping: [1.5, 4.5]. ## - Mouse-wheel zoom events. ## - Camera controls bar with preset buttons, zoom slider, and +/- buttons. ## 7. Auto-Save on Exit: ## - user://loadout.json written. ## - AvatarSystem sync. ## - Signals: back_requested and closed. const AvatarCustomizationUI := preload("res://scripts/avatar/avatar_customization_ui.gd") const AvatarLoadout := preload("res://scripts/avatar/avatar_loadout.gd") var passed := 0 var failed := 0 func _initialize() -> void: _run() func _run() -> void: await process_frame print("SMOKE: Starting AvatarStudioUI verification...") var ui: AvatarCustomizationUI = AvatarCustomizationUI.new() ui.name = "AvatarCustomizationUI" ui.set_anchors_preset(Control.PRESET_FULL_RECT) root.add_child(ui) await process_frame await process_frame # 1. Header Bar Verification var back_btn: Button = ui.find_child("BackButton", true, false) as Button _check("Back button exists", back_btn != null) if back_btn: _check("Back button text is '< Back [ESC]'", back_btn.text == "< Back [ESC]") _check("Back button custom_minimum_size is at least (120, 38)", back_btn.custom_minimum_size.x >= 120 and back_btn.custom_minimum_size.y >= 38) var title_lbl: Label = ui.find_child("TitleLabel", true, false) as Label _check("TitleLabel exists", title_lbl != null) if title_lbl: _check("TitleLabel text is 'Avatar Studio'", title_lbl.text == "Avatar Studio") var fsize: int = title_lbl.get_theme_font_size("font_size") _check("TitleLabel font_size >= 24", fsize >= 24) var status_pill: Label = ui.find_child("StatusPill", true, false) as Label _check("StatusPill exists", status_pill != null) if status_pill: _check("StatusPill text is 'All changes saved'", status_pill.text == "All changes saved") var wallet_lbl: Label = ui.find_child("WalletBalanceLabel", true, false) as Label _check("WalletBalanceLabel exists", wallet_lbl != null) if wallet_lbl: _check("WalletBalanceLabel starts with 'Wallet:' and contains 'Buffalo Chips'", wallet_lbl.text.begins_with("Wallet:") and wallet_lbl.text.contains("Buffalo Chips")) ui.update_wallet_balance(750) _check("WalletBalanceLabel formats 'Wallet: 750 Buffalo Chips'", wallet_lbl.text == "Wallet: 750 Buffalo Chips") # 2. Category Tabs Verification var tab_bar: TabBar = ui.find_child("CategoryTabBar", true, false) as TabBar _check("CategoryTabBar exists", tab_bar != null) if tab_bar: _check("CategoryTabBar has 4 tabs", tab_bar.tab_count == 4) _check("Tab 0 is 'Body & Head'", tab_bar.get_tab_title(0) == "Body & Head") _check("Tab 1 is 'Clothing'", tab_bar.get_tab_title(1) == "Clothing") _check("Tab 2 is 'Accessories'", tab_bar.get_tab_title(2) == "Accessories") _check("Tab 3 is 'Skin & Colors'", tab_bar.get_tab_title(3) == "Skin & Colors") # Check Category constants _check("Category 0 has slots: base_body, head, hair, face", ui.CATEGORIES[0]["slots"] == ["base_body", "head", "hair", "face"]) _check("Category 1 has slots: chest, pants, torso, left_hand, right_hand, left_foot, right_foot", ui.CATEGORIES[1]["slots"] == ["chest", "pants", "torso", "left_hand", "right_hand", "left_foot", "right_foot"]) _check("Category 2 has slots: back, accessory", ui.CATEGORIES[2]["slots"] == ["back", "accessory"]) # 3. Friendly Item Names Verification var mohawk_name: String = ui.get_friendly_item_name("hair_mohawk_01") _check("hair_mohawk_01 friendly name is 'Mohawk'", mohawk_name == "Mohawk") var waterbuffalo_name: String = ui.get_friendly_item_name("hair_waterbuffalo_01") _check("hair_waterbuffalo_01 friendly name is 'Water Buffalo Lodge Hat'", waterbuffalo_name == "Water Buffalo Lodge Hat") var right_glove_name: String = ui.get_friendly_item_name("right_hand_glove_01") _check("right_hand_glove_01 friendly name is 'Right Glove'", right_glove_name == "Right Glove") var right_boot_name: String = ui.get_friendly_item_name("right_foot_boot_01") _check("right_foot_boot_01 friendly name is 'Right Boot'", right_boot_name == "Right Boot") var cyber_name: String = ui.get_friendly_item_name("head_cyber_01") _check("head_cyber_01 friendly name is 'Cyber Head'", cyber_name == "Cyber Head") var cargo_name: String = ui.get_friendly_item_name("pants_cargo_01") _check("pants_cargo_01 friendly name is 'Cargo Pants'", cargo_name == "Cargo Pants") var base_name: String = ui.get_friendly_item_name("base_human_male_01") _check("base_human_male_01 friendly name is 'Human Male Base'", base_name == "Human Male Base") var none_name: String = ui.get_friendly_item_name("") _check("empty asset_id friendly name is '(None)'", none_name == "(None)") # 4. Item Selection, Equipped Badges, and Highlighted Borders ui._select_slot("hair") await process_frame var item_list: VBoxContainer = ui.find_child("ItemList", true, false) as VBoxContainer _check("ItemList exists and has children", item_list != null and item_list.get_child_count() > 0) var mohawk_btn: Button = ui.find_child("Item_hair_mohawk_01", true, false) as Button _check("Item_hair_mohawk_01 button exists", mohawk_btn != null) if mohawk_btn: _check("Mohawk button primary text does not show raw ID 'hair_mohawk_01'", not mohawk_btn.text.begins_with("hair_mohawk_01")) _check("Mohawk button contains friendly name 'Mohawk'", mohawk_btn.text.contains("Mohawk")) # Equip mohawk var tracker := {"loadout_changed": false, "back_requested": false, "closed": false} ui.loadout_changed.connect(func(_ld): tracker["loadout_changed"] = true) ui._on_equip_pressed("hair_mohawk_01") await process_frame _check("Equipping mohawk emitted loadout_changed signal", tracker["loadout_changed"]) _check("Loadout hair slot is 'hair_mohawk_01'", ui.loadout.get_equipment("hair") == "hair_mohawk_01") # Re-check button equipped badge & border mohawk_btn = ui.find_child("Item_hair_mohawk_01", true, false) as Button if mohawk_btn: var badge: Label = mohawk_btn.find_child("EquippedBadge", true, false) as Label _check("Equipped mohawk has '✓ EQUIPPED' badge", badge != null or mohawk_btn.text.contains("✓ EQUIPPED")) var sb: StyleBox = mohawk_btn.get_theme_stylebox("normal") if sb is StyleBoxFlat: _check("Equipped mohawk has highlighted border (width >= 2)", (sb as StyleBoxFlat).border_width_left >= 2) # Unequip hair ui._on_equip_pressed("") await process_frame _check("Unequipping hair clears hair slot", ui.loadout.get_equipment("hair") == "") var none_btn: Button = ui.find_child("Item_none", true, false) as Button if none_btn: var none_badge: Label = none_btn.find_child("EquippedBadge", true, false) as Label _check("(None) button has '✓ EQUIPPED' badge when slot is unequipped", none_badge != null or none_btn.text.contains("✓ EQUIPPED")) # 5. Category Tab Switching ui._on_category_tab_changed(1) # Clothing await process_frame var slot_list: HBoxContainer = ui.find_child("SlotList", true, false) as HBoxContainer _check("Switching to Clothing tab populates slot buttons", slot_list != null and slot_list.get_child_count() >= 5) var chest_slot_btn: Button = ui.find_child("Slot_chest", true, false) as Button _check("Slot_chest button exists in Clothing tab", chest_slot_btn != null) var right_hand_slot_btn: Button = ui.find_child("Slot_right_hand", true, false) as Button _check("Slot_right_hand button exists in Clothing tab", right_hand_slot_btn != null) var right_foot_slot_btn: Button = ui.find_child("Slot_right_foot", true, false) as Button _check("Slot_right_foot button exists in Clothing tab", right_foot_slot_btn != null) # Verify selecting right_hand slot populates items ui._select_slot("right_hand") await process_frame var right_glove_btn: Button = ui.find_child("Item_right_hand_glove_01", true, false) as Button _check("Item_right_hand_glove_01 button exists under right_hand slot", right_glove_btn != null) # 6. Skin & Colors Tab Verification ui._on_category_tab_changed(3) # Skin & Colors await process_frame var items_section: Control = ui.find_child("ItemsSection", true, false) as Control var skin_section: Control = ui.find_child("SkinSection", true, false) as Control _check("Skin & Colors tab hides ItemsSection and shows SkinSection", items_section != null and not items_section.visible and skin_section != null and skin_section.visible) var shade_slider: HSlider = ui.find_child("ShadeSlider", true, false) as HSlider _check("ShadeSlider exists", shade_slider != null) var slider_r: HSlider = ui.find_child("SliderR", true, false) as HSlider var slider_g: HSlider = ui.find_child("SliderG", true, false) as HSlider var slider_b: HSlider = ui.find_child("SliderB", true, false) as HSlider _check("RGB sliders (R, G, B) exist", slider_r != null and slider_g != null and slider_b != null) var skin_preview: ColorRect = ui.find_child("SkinColorPreview", true, false) as ColorRect _check("SkinColorPreview exists", skin_preview != null) # Test swatch press var test_color := Color(0.33, 0.21, 0.15) ui._on_skin_swatch_pressed(test_color) await process_frame _check("Pressing swatch updates loadout skin_tone", ui.loadout.skin_tone.is_equal_approx(test_color)) if skin_preview: _check("SkinColorPreview matches swatch color", skin_preview.color.is_equal_approx(test_color)) # Test RGB slider change if slider_r: slider_r.value = 0.75 ui._on_rgb_slider_changed(0.75) await process_frame _check("Adjusting RGB slider updates loadout skin_tone.r", is_equal_approx(ui.loadout.skin_tone.r, 0.75)) _check("Status pill remains 'All changes saved'", status_pill != null and status_pill.text == "All changes saved") # 7. Camera Zoom & Framing Presets Verification (Task f87183b6d9e4) # Check framing presets ui.frame_camera_preset("Head") _check("Head preset frames camera to dist 1.6, height 1.62", is_equal_approx(ui.camera_distance, 1.6) and is_equal_approx(ui.camera_target_height, 1.62)) ui.frame_camera_preset("Torso") _check("Torso preset frames camera to dist 2.2, height 1.12", is_equal_approx(ui.camera_distance, 2.2) and is_equal_approx(ui.camera_target_height, 1.12)) ui.frame_camera_preset("Feet") _check("Feet preset frames camera to dist 1.8, height 0.35", is_equal_approx(ui.camera_distance, 1.8) and is_equal_approx(ui.camera_target_height, 0.35)) ui.frame_camera_preset("Full Body") _check("Full Body preset frames camera to dist 3.5, height 0.98", is_equal_approx(ui.camera_distance, 3.5) and is_equal_approx(ui.camera_target_height, 0.98)) # Check zoom clamping [1.5, 4.5] ui.set_camera_zoom(1.0) # should clamp to 1.5 _check("set_camera_zoom(1.0) clamps to 1.5", is_equal_approx(ui.camera_distance, 1.5)) ui.set_camera_zoom(5.0) # should clamp to 4.5 _check("set_camera_zoom(5.0) clamps to 4.5", is_equal_approx(ui.camera_distance, 4.5)) # Check mouse wheel zoom on preview container ui.set_camera_zoom(3.0) var wheel_up := InputEventMouseButton.new() wheel_up.button_index = MOUSE_BUTTON_WHEEL_UP wheel_up.pressed = true ui._on_preview_gui_input(wheel_up) _check("Wheel up zooms in by 0.2m (3.0 -> 2.8)", is_equal_approx(ui.camera_distance, 2.8)) var wheel_down := InputEventMouseButton.new() wheel_down.button_index = MOUSE_BUTTON_WHEEL_DOWN wheel_down.pressed = true ui._on_preview_gui_input(wheel_down) _check("Wheel down zooms out by 0.2m (2.8 -> 3.0)", is_equal_approx(ui.camera_distance, 3.0)) # Check Camera Controls Bar UI elements var p_head_btn: Button = ui.find_child("Preset_Head", true, false) as Button var p_full_btn: Button = ui.find_child("Preset_FullBody", true, false) as Button var p_torso_btn: Button = ui.find_child("Preset_Torso", true, false) as Button var p_feet_btn: Button = ui.find_child("Preset_Feet", true, false) as Button _check("Preset buttons (Full Body, Head, Torso, Feet) exist", p_head_btn != null and p_full_btn != null and p_torso_btn != null and p_feet_btn != null) var zoom_slider: HSlider = ui.find_child("CameraZoomSlider", true, false) as HSlider var zoom_in: Button = ui.find_child("ZoomInBtn", true, false) as Button var zoom_out: Button = ui.find_child("ZoomOutBtn", true, false) as Button _check("Zoom slider and +/- buttons exist", zoom_slider != null and zoom_in != null and zoom_out != null) # 8. Auto-Save on Exit Verification (Task f87183b6d9e4) ui.back_requested.connect(func(): tracker["back_requested"] = true) ui.closed.connect(func(): tracker["closed"] = true) # Equip a distinct item before exit ui._select_slot("head") ui._on_equip_pressed("head_cyber_01") await process_frame # Trigger save_and_exit await ui.save_and_exit() await process_frame _check("save_and_exit emits back_requested signal", tracker["back_requested"]) _check("save_and_exit emits closed signal", tracker["closed"]) # Check that user://loadout.json exists and contains head_cyber_01 _check("user://loadout.json was saved to disk", FileAccess.file_exists("user://loadout.json")) if FileAccess.file_exists("user://loadout.json"): var f := FileAccess.open("user://loadout.json", FileAccess.READ) var json_str := f.get_as_text() f.close() _check("user://loadout.json contains 'head_cyber_01'", json_str.contains("head_cyber_01")) # Check AvatarSystem sync var avatars := root.get_node_or_null("AvatarSystem") if avatars != null and avatars.current_loadout != null: _check("AvatarSystem current_loadout synced 'head_cyber_01'", avatars.current_loadout.get_equipment("head") == "head_cyber_01") ui.queue_free() _finish() func _check(description: String, condition: bool) -> bool: if condition: print(" PASS: ", description) passed += 1 else: printerr(" FAIL: ", description) failed += 1 return condition func _finish() -> void: print("\n=== SMOKE RESULT: %d/%d passed ===" % [passed, passed + failed]) quit(0 if failed == 0 else 1)