Extract shared YamlParser utility (client/scripts/util/yaml_parser.gd) with parse() for nested typed dicts and parse_flat() for dotted-key string format. UIStrings._parse_yaml() and ChecklistEvaluator's inline parser both delegate to YamlParser, removing ~140 lines of duplication. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
262 lines
9.3 KiB
GDScript
262 lines
9.3 KiB
GDScript
## YamlParser unit tests (#560).
|
|
## Verifies parse() (nested, typed) and parse_flat() (dotted keys, string values).
|
|
## Covers: maps, nested maps, arrays of dicts, type conversion, comments,
|
|
## quoted strings, empty input, edge cases.
|
|
##
|
|
## D-030: fixture-based, server-free, no subprocess required.
|
|
class_name TestYamlParser
|
|
extends GdUnitTestSuite
|
|
|
|
|
|
# -- parse(): basic key-value pairs -------------------------------------------
|
|
|
|
func test_parse_simple_key_value() -> void:
|
|
var result := YamlParser.parse("key: value")
|
|
assert_that(result["key"]).is_equal("value")
|
|
|
|
|
|
func test_parse_quoted_string() -> void:
|
|
var result := YamlParser.parse('key: "hello world"')
|
|
assert_that(result["key"]).is_equal("hello world")
|
|
|
|
|
|
func test_parse_empty_quoted_string() -> void:
|
|
var result := YamlParser.parse('key: ""')
|
|
assert_that(result["key"]).is_equal("")
|
|
|
|
|
|
func test_parse_integer_value() -> void:
|
|
var result := YamlParser.parse("count: 42")
|
|
assert_that(result["count"]).is_equal(42)
|
|
assert_that(typeof(result["count"])).is_equal(TYPE_INT)
|
|
|
|
|
|
func test_parse_negative_integer() -> void:
|
|
var result := YamlParser.parse("offset: -3")
|
|
assert_that(result["offset"]).is_equal(-3)
|
|
|
|
|
|
func test_parse_float_value() -> void:
|
|
var result := YamlParser.parse("radius: 2.5")
|
|
assert_that(typeof(result["radius"])).is_equal(TYPE_FLOAT)
|
|
assert_float(result["radius"]).is_equal_approx(2.5, 0.001)
|
|
|
|
|
|
func test_parse_boolean_true() -> void:
|
|
var result := YamlParser.parse("enabled: true")
|
|
assert_that(result["enabled"]).is_equal(true)
|
|
assert_that(typeof(result["enabled"])).is_equal(TYPE_BOOL)
|
|
|
|
|
|
func test_parse_boolean_false() -> void:
|
|
var result := YamlParser.parse("enabled: false")
|
|
assert_that(result["enabled"]).is_equal(false)
|
|
|
|
|
|
func test_parse_empty_input() -> void:
|
|
var result := YamlParser.parse("")
|
|
assert_that(result.size()).is_equal(0)
|
|
|
|
|
|
func test_parse_only_comments() -> void:
|
|
var result := YamlParser.parse("# comment\n# another")
|
|
assert_that(result.size()).is_equal(0)
|
|
|
|
|
|
func test_parse_inline_comment_stripped() -> void:
|
|
var result := YamlParser.parse("room_id: test # this is a comment")
|
|
assert_that(result["room_id"]).is_equal("test")
|
|
|
|
|
|
func test_parse_quoted_value_with_hash() -> void:
|
|
## Quoted strings preserve literal # characters.
|
|
var result := YamlParser.parse('color: "#e0e8ff"')
|
|
assert_that(result["color"]).is_equal("#e0e8ff")
|
|
|
|
|
|
func test_parse_value_with_colon() -> void:
|
|
var result := YamlParser.parse('time: "12:30"')
|
|
assert_that(result["time"]).is_equal("12:30")
|
|
|
|
|
|
# -- parse(): nested maps -----------------------------------------------------
|
|
|
|
func test_parse_nested_map() -> void:
|
|
var yaml := "section:\n key: value"
|
|
var result := YamlParser.parse(yaml)
|
|
assert_that(result.has("section")).is_true()
|
|
assert_that(result["section"] is Dictionary).is_true()
|
|
assert_that(result["section"]["key"]).is_equal("value")
|
|
|
|
|
|
func test_parse_deeply_nested() -> void:
|
|
var yaml := "a:\n b:\n c: deep"
|
|
var result := YamlParser.parse(yaml)
|
|
assert_that(result["a"]["b"]["c"]).is_equal("deep")
|
|
|
|
|
|
func test_parse_multiple_sections() -> void:
|
|
var yaml := "hud:\n mode: Mode\ninteraction:\n talk: Talk"
|
|
var result := YamlParser.parse(yaml)
|
|
assert_that(result["hud"]["mode"]).is_equal("Mode")
|
|
assert_that(result["interaction"]["talk"]).is_equal("Talk")
|
|
|
|
|
|
func test_parse_multiple_keys_per_section() -> void:
|
|
var yaml := "hud:\n a: 1\n b: 2\n c: 3"
|
|
var result := YamlParser.parse(yaml)
|
|
assert_that(result["hud"]["a"]).is_equal(1)
|
|
assert_that(result["hud"]["b"]).is_equal(2)
|
|
assert_that(result["hud"]["c"]).is_equal(3)
|
|
|
|
|
|
func test_parse_sibling_subsections() -> void:
|
|
var yaml := "states:\n a:\n x: 1\n b:\n x: 2"
|
|
var result := YamlParser.parse(yaml)
|
|
assert_that(result["states"]["a"]["x"]).is_equal(1)
|
|
assert_that(result["states"]["b"]["x"]).is_equal(2)
|
|
|
|
|
|
func test_parse_section_with_trailing_comment() -> void:
|
|
## "section: # comment" should be treated as a section header.
|
|
var yaml := "section: # comment\n key: value"
|
|
var result := YamlParser.parse(yaml)
|
|
assert_that(result["section"]["key"]).is_equal("value")
|
|
|
|
|
|
# -- parse(): arrays of dicts -------------------------------------------------
|
|
|
|
func test_parse_single_array_item() -> void:
|
|
var yaml := "conditions:\n - id: test-1\n type: near\n x: 10"
|
|
var result := YamlParser.parse(yaml)
|
|
assert_that(result.has("conditions")).is_true()
|
|
assert_that(result["conditions"] is Array).is_true()
|
|
assert_that(result["conditions"].size()).is_equal(1)
|
|
assert_that(result["conditions"][0]["id"]).is_equal("test-1")
|
|
assert_that(result["conditions"][0]["type"]).is_equal("near")
|
|
assert_that(result["conditions"][0]["x"]).is_equal(10)
|
|
|
|
|
|
func test_parse_multiple_array_items() -> void:
|
|
var yaml := "conditions:\n - id: a\n x: 1\n - id: b\n x: 2"
|
|
var result := YamlParser.parse(yaml)
|
|
assert_that(result["conditions"].size()).is_equal(2)
|
|
assert_that(result["conditions"][0]["id"]).is_equal("a")
|
|
assert_that(result["conditions"][1]["id"]).is_equal("b")
|
|
|
|
|
|
func test_parse_array_items_with_blank_lines() -> void:
|
|
var yaml := "conditions:\n - id: a\n x: 1\n\n - id: b\n x: 2"
|
|
var result := YamlParser.parse(yaml)
|
|
assert_that(result["conditions"].size()).is_equal(2)
|
|
|
|
|
|
func test_parse_top_level_plus_array() -> void:
|
|
## Checklist format: top-level key-value pairs followed by a conditions array.
|
|
var yaml := "room_id: warehouse\nconditions:\n - id: c1\n type: near\n x: 5"
|
|
var result := YamlParser.parse(yaml)
|
|
assert_that(result["room_id"]).is_equal("warehouse")
|
|
assert_that(result["conditions"].size()).is_equal(1)
|
|
assert_that(result["conditions"][0]["id"]).is_equal("c1")
|
|
|
|
|
|
func test_parse_array_typed_values() -> void:
|
|
var yaml := "items:\n - id: t\n x: 42\n radius: 2.5\n active: true"
|
|
var result := YamlParser.parse(yaml)
|
|
var item: Dictionary = result["items"][0]
|
|
assert_that(item["x"]).is_equal(42)
|
|
assert_that(typeof(item["radius"])).is_equal(TYPE_FLOAT)
|
|
assert_that(item["active"]).is_equal(true)
|
|
|
|
|
|
# -- parse_flat(): dotted keys -------------------------------------------------
|
|
|
|
func test_flat_simple() -> void:
|
|
var yaml := "section:\n key: value"
|
|
var result := YamlParser.parse_flat(yaml)
|
|
assert_that(result.has("section.key")).is_true()
|
|
assert_that(result["section.key"]).is_equal("value")
|
|
|
|
|
|
func test_flat_deeply_nested() -> void:
|
|
var yaml := "a:\n b:\n c: deep"
|
|
var result := YamlParser.parse_flat(yaml)
|
|
assert_that(result["a.b.c"]).is_equal("deep")
|
|
|
|
|
|
func test_flat_multiple_sections() -> void:
|
|
var yaml := "hud:\n mode: Mode\ninteraction:\n talk: Talk"
|
|
var result := YamlParser.parse_flat(yaml)
|
|
assert_that(result["hud.mode"]).is_equal("Mode")
|
|
assert_that(result["interaction.talk"]).is_equal("Talk")
|
|
|
|
|
|
func test_flat_values_are_strings() -> void:
|
|
## parse_flat returns all values as strings, unlike parse() which returns typed.
|
|
var yaml := "section:\n count: 42\n rate: 0.9\n active: true"
|
|
var result := YamlParser.parse_flat(yaml)
|
|
assert_that(result["section.count"]).is_equal("42")
|
|
assert_that(result["section.rate"]).is_equal("0.9")
|
|
assert_that(result["section.active"]).is_equal("true")
|
|
|
|
|
|
func test_flat_quoted_value() -> void:
|
|
var yaml := 'section:\n key: "hello world"'
|
|
var result := YamlParser.parse_flat(yaml)
|
|
assert_that(result["section.key"]).is_equal("hello world")
|
|
|
|
|
|
func test_flat_inline_comment() -> void:
|
|
var yaml := "hud:\n mode: Standard # default"
|
|
var result := YamlParser.parse_flat(yaml)
|
|
assert_that(result["hud.mode"]).is_equal("Standard")
|
|
|
|
|
|
func test_flat_empty_quoted_value() -> void:
|
|
var yaml := 'hud:\n prefix: "" # No prefix'
|
|
var result := YamlParser.parse_flat(yaml)
|
|
assert_that(result.has("hud.prefix")).is_true()
|
|
assert_that(result["hud.prefix"]).is_equal("")
|
|
|
|
|
|
func test_flat_skips_arrays() -> void:
|
|
## Arrays have no dotted-key representation — they are skipped in flat output.
|
|
var yaml := "room_id: test\nconditions:\n - id: c1\n x: 5"
|
|
var result := YamlParser.parse_flat(yaml)
|
|
assert_that(result.has("room_id")).is_true()
|
|
# No dotted keys for array contents
|
|
assert_that(result.has("conditions")).is_false()
|
|
assert_that(result.has("conditions.0")).is_false()
|
|
|
|
|
|
func test_flat_empty_input() -> void:
|
|
var result := YamlParser.parse_flat("")
|
|
assert_that(result.size()).is_equal(0)
|
|
|
|
|
|
# -- Dialogue theme format (regression) ----------------------------------------
|
|
|
|
func test_dialogue_theme_format() -> void:
|
|
## dialogue-theme.yaml has top-level values and one nested map (npc_colors).
|
|
var yaml := "player_color: \"#e0e8ff\"\nnpc_colors:\n 0: \"#4a9ebb\"\n 1: \"#6bc9a6\"\npassive_opacity: 0.9"
|
|
var flat := YamlParser.parse_flat(yaml)
|
|
assert_that(flat["player_color"]).is_equal("#e0e8ff")
|
|
assert_that(flat["npc_colors.0"]).is_equal("#4a9ebb")
|
|
assert_that(flat["npc_colors.1"]).is_equal("#6bc9a6")
|
|
assert_that(flat["passive_opacity"]).is_equal("0.9")
|
|
|
|
|
|
# -- Checklist format (regression) ---------------------------------------------
|
|
|
|
func test_checklist_format() -> void:
|
|
## checklist.yaml: top-level kv + conditions array with typed values.
|
|
var yaml := "room_id: inventory_warehouse\nconditions:\n - id: test-1\n condition_type: player_near\n x: 10\n y: 20\n radius: 3.0"
|
|
var result := YamlParser.parse(yaml)
|
|
assert_that(result["room_id"]).is_equal("inventory_warehouse")
|
|
var cond: Dictionary = result["conditions"][0]
|
|
assert_that(cond["id"]).is_equal("test-1")
|
|
assert_that(cond["condition_type"]).is_equal("player_near")
|
|
assert_that(cond["x"]).is_equal(10)
|
|
assert_that(cond["y"]).is_equal(20)
|
|
assert_that(typeof(cond["radius"])).is_equal(TYPE_FLOAT)
|