Files
aetherbound-guild/runtime/tests/foundation/settings_service_test.gd
T

124 lines
5.2 KiB
GDScript

extends SceneTree
const EntryShell = preload("res://entry/entry_shell.gd")
const SettingsService = preload("res://foundation/settings_service.gd")
const SETTINGS_PATH := "user://abg_m15_settings_service_test.json"
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
_cleanup()
verify_service_boundary()
verify_invalid_file_and_v1_migration()
await verify_screen_context_boundary()
_cleanup()
if failures.is_empty():
print("ABG_M15_SETTINGS_OK defaults=true forwarding=true invalid_file=true migration=v1_to_v2 context=true return=true stack_clear=true")
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func verify_service_boundary() -> void:
var service := SettingsService.new()
service.path = SETTINGS_PATH
var defaults := service.defaults()
expect(defaults.settings_version == SettingsService.SETTINGS_VERSION, "service preserves the validated schema version")
expect(defaults.locale == "zh_CN" and defaults.safe_area == 24, "service exposes the existing defaults")
expect(service.backend_type_name() == "settings_store.gd", "service keeps the proven backend behind the boundary")
var invalid := defaults.duplicate(true)
invalid.locale = "fr"
var validation := service.validate(invalid)
expect(not validation.ok and validation.code == "SETTINGS_LOCALE_INVALID", "service forwards validation errors")
var saved := service.save_settings(defaults)
expect(saved.ok and FileAccess.file_exists(SETTINGS_PATH), "service forwards atomic saves")
var loaded := service.load_settings()
expect(loaded.ok and loaded.code == "loaded", "service forwards load results")
if loaded.ok:
expect(String(loaded.settings.locale) == "zh_CN" and int(loaded.settings.safe_area) == 24, "service load preserves discrete settings")
expect(is_equal_approx(float(loaded.settings.music_volume), 0.8) and bool(loaded.settings.captions), "service load preserves scalar and boolean settings")
func verify_invalid_file_and_v1_migration() -> void:
var service := SettingsService.new()
service.path = SETTINGS_PATH
_write_text(SETTINGS_PATH, "{not-json")
var invalid := service.load_settings()
expect(not invalid.ok and invalid.code == "SETTINGS_FILE_INVALID", "corrupt JSON remains an explicit settings error")
var legacy := service.defaults()
legacy.settings_version = 1
legacy.locale = "en"
legacy.text_scale = 1.15
_write_text(SETTINGS_PATH, JSON.stringify(legacy))
var migrated := service.load_settings()
expect(migrated.ok and migrated.code == "migrated_v1", "v1 settings migrate through the application service")
if migrated.ok:
expect(int(migrated.settings.settings_version) == SettingsService.SETTINGS_VERSION, "migration promotes the schema version")
expect(migrated.settings.locale == "en" and is_equal_approx(float(migrated.settings.text_scale), 1.15), "migration preserves compatible user values")
var persisted := service.load_settings()
expect(persisted.ok and persisted.code == "loaded", "migrated settings are atomically persisted")
func verify_screen_context_boundary() -> void:
var shell = EntryShell.new()
shell.auto_start = false
shell.settings_service.path = SETTINGS_PATH
root.add_child(shell)
await process_frame
shell.settings = shell.settings_service.defaults()
shell.settings.setup_completed = true
shell.settings_caller = "title"
shell.show_title()
await process_frame
shell.show_settings("audio")
await process_frame
var context = shell.app_services.screen_stack.current()
expect(context != null and context.screen_id == "settings", "opening Settings pushes a screen context")
if context != null:
expect(context.caller_id == "title" and context.return_screen_id == "title", "title caller is recorded for return")
expect(context.return_focus_path == "TitleSettings" and context.metadata.category == "audio", "return focus and category are explicit context data")
expect(shell.app_services.resolve_service(&"settings") == shell.settings_service, "Settings is registered in the app service bundle")
shell.settings_draft = shell.settings.duplicate(true)
shell.discard_settings()
await process_frame
expect(shell.current_screen == "title", "discard returns to the originating title route")
expect(shell.app_services.screen_stack.depth() == 0, "return clears the Settings stack entry")
shell.settings_caller = "guild_handoff"
shell.show_settings("credits")
await process_frame
context = shell.app_services.screen_stack.current()
expect(context != null and context.caller_id == "guild_handoff", "non-title callers are preserved")
if context != null:
expect(context.return_screen_id == "guild_handoff" and context.return_focus_path == "GuildSettings", "non-title return route and focus are mapped")
shell._close_settings_context()
shell.queue_free()
await process_frame
func _write_text(path: String, value: String) -> void:
var file := FileAccess.open(path, FileAccess.WRITE)
if file == null:
expect(false, "test fixture file opens")
return
file.store_string(value)
file.close()
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("M15 Settings: %s" % message)