extends SceneTree const SettingsStore = preload("res://entry/settings_store.gd") const EntryShell = preload("res://entry/entry_shell.gd") const SETTINGS_PATH := "user://dev1_first_launch_test.json" var failures: Array[String] = [] func _initialize() -> void: call_deferred("run_test") func run_test() -> void: _cleanup() verify_defaults_validation_and_atomic_roundtrip() await verify_first_launch_once_and_restart() _cleanup() if failures.is_empty(): print("ABG_DEV1_FIRST_LAUNCH_OK language=true safe_area=true text=100,115,130 reduced_motion=true reduced_flashes=true captions=true persisted=true once=true") quit(0) else: for failure in failures: push_error(failure) quit(1) func verify_defaults_validation_and_atomic_roundtrip() -> void: var store: RefCounted = SettingsStore.new() store.path = SETTINGS_PATH var loaded: Dictionary = store.load_settings() expect(loaded.ok and not loaded.settings.setup_completed, "clean install supplies incomplete defaults") var value: Dictionary = loaded.settings value.locale = "en" value.safe_area = 48 value.text_scale = 1.3 value.reduced_motion = true value.reduced_flashes = true value.captions = false value.setup_completed = true var saved: Dictionary = store.save_settings(value) expect(saved.ok, "valid first-launch settings save atomically") var reloaded: Dictionary = store.load_settings() expect( reloaded.ok and reloaded.settings.locale == "en" and int(reloaded.settings.safe_area) == 48 and is_equal_approx(float(reloaded.settings.text_scale), 1.3) and reloaded.settings.reduced_motion and reloaded.settings.reduced_flashes and not reloaded.settings.captions and reloaded.settings.setup_completed, "saved settings survive reload with the same semantics" ) value.text_scale = 1.2 expect(not store.save_settings(value).ok, "unsupported text scale is rejected") func verify_first_launch_once_and_restart() -> void: _cleanup() var first = EntryShell.new() first.auto_start = false first.settings_store.path = SETTINGS_PATH root.add_child(first) await process_frame first.settings = first.settings_store.defaults() first.show_first_launch() await process_frame expect(first.find_child("SetupApply", true, false) != null and first.find_child("FirstLaunchReturn", true, false) == null, "first launch exposes Apply and no Back") expect(first.find_child("FirstLaunchPreviewFrame", true, false) != null, "first launch uses the approved preview frame") expect(first.find_child("SetupLanguage", true, false).get_node_or_null("ValuePlate") != null, "first-launch choices use authored value plates") expect(first.find_child("SetupReducedMotion", true, false).get_node_or_null("ValuePlate") != null, "first-launch toggles use authored state plates") expect(first.find_child("SetupApply", true, false).get_node_or_null("CommandFrame") != null, "first-launch Apply uses the primary command treatment") first.toggle_setup_reduced_motion() await process_frame expect(bool(first.setup_draft.reduced_motion), "reduced-motion row toggles through the real button callback") first.toggle_setup_reduced_flashes() await process_frame expect(bool(first.setup_draft.reduced_flashes), "reduced-flashes row toggles through the real button callback") first.toggle_setup_captions() await process_frame expect(not bool(first.setup_draft.captions), "captions row toggles through the real button callback") first.setup_draft.locale = "en" first.setup_draft.safe_area = 48 first.setup_draft.text_scale = 1.3 first.setup_draft.reduced_motion = true first.setup_draft.reduced_flashes = true first.setup_draft.captions = true first.apply_first_launch() await process_frame expect(first.settings.setup_completed, "Apply completes setup") first.queue_free() await process_frame var restarted = EntryShell.new() restarted.auto_start = false restarted.settings_store.path = SETTINGS_PATH root.add_child(restarted) await process_frame restarted._open_after_boot() await process_frame expect(restarted.get_node_or_null("FirstLaunchResponsiveLayout") == null, "completed setup is not forced after restart") expect(restarted.get_node_or_null("TitleSafeArea") != null, "restart advances to Title boundary") restarted.queue_free() await process_frame func _cleanup() -> void: for path in [SETTINGS_PATH, "%s.bak" % SETTINGS_PATH, "%s.candidate" % SETTINGS_PATH]: if FileAccess.file_exists(path): DirAccess.remove_absolute(path) func expect(condition: bool, message: String) -> void: if not condition: failures.append("DEV-1 first launch: %s" % message)