124 lines
5.2 KiB
GDScript
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)
|