# client/tests/unit/test_theme_resource.gd extends "res://addons/gut/test.gd" const Builder = preload("res://scripts/theme/build_game_theme.gd") const THEME_PATH := "res://assets/theme/game_theme.tres" func test_theme_loads(): assert_true(FileAccess.file_exists(THEME_PATH), "theme not built — run build_game_theme.gd") var t = load(THEME_PATH) assert_true(t is Theme) func test_every_variation_resolves(): var t: Theme = load(THEME_PATH) for key in ThemeKeys.ALL: var base: String = ThemeKeys.ALL[key] assert_eq(t.get_type_variation_base(key), StringName(base), "variation %s missing or wrong base" % key) func test_cta_has_normal_stylebox(): var t: Theme = load(THEME_PATH) assert_true(t.has_stylebox(&"normal", ThemeKeys.PRIMARY_CTA)) func test_default_font_is_set(): var t: Theme = load(THEME_PATH) assert_true(t.default_font is FontFile) func _guarded_variations() -> Array: # EVERY variation the builder touches — the stylebox variations (ThemeKeys.ALL) # AND the six font roles (ThemeKeys.FONT_ROLES), which ALL deliberately excludes # by its own docstring. Iterating ALL alone left every font, font size and font # colour in the theme unguarded: change Palette.CREAM (TitleLogo's font colour, # and nothing else's), skip the rebuild, and the suite stayed green with a stale # .tres shipped. traps.md #12 — "covers everything" must iterate the full set. var out: Array = ThemeKeys.ALL.keys() out.append_array(ThemeKeys.FONT_ROLES.keys()) return out func test_the_drift_guard_covers_every_variation_the_builder_touches(): # The guard below is only as good as the set it walks. Pin that set to the # builder's own output: every variation the fresh Theme carries a stylebox, a # font, a font size or a font colour for MUST be in _guarded_variations(), or # it can drift unwatched. A new variation added to the builder and forgotten in # ThemeKeys goes red here rather than sitting unguarded forever. var fresh: Theme = Builder.build_theme() var guarded := _guarded_variations() for variation in fresh.get_type_list(): var touched := (not fresh.get_stylebox_list(variation).is_empty() or not fresh.get_font_list(variation).is_empty() or not fresh.get_font_size_list(variation).is_empty() or not fresh.get_color_list(variation).is_empty()) if not touched: continue if fresh.get_type_variation_base(variation) == &"": continue # a BASE type (the builder styles RichTextLabel directly), not a variation assert_true(variation in guarded, "the builder styles the variation %s but no drift guard walks it — add it to ThemeKeys.ALL or ThemeKeys.FONT_ROLES" % variation) func test_committed_tres_matches_builder(): # Catches "palette/builder changed but nobody regenerated game_theme.tres." # Preloading the builder script and calling its static build_theme() does # NOT run _init() (that only fires on .new()), so this is side-effect-free. # # Drives its checks off ThemeKeys.ALL *and* ThemeKeys.FONT_ROLES so every # variation is guarded, not just whichever three someone remembered to hardcode # here — and not just the stylebox half. For each variation: every stylebox # state, every font, every font size and every font colour the builder actually # set is compared. Nothing the builder never touches is asserted on. var fresh: Theme = Builder.build_theme() var committed: Theme = load(THEME_PATH) for variation in _guarded_variations(): for state in fresh.get_stylebox_list(variation): assert_true(committed.has_stylebox(state, variation), "stale game_theme.tres — %s/%s stylebox is missing entirely; re-run build_game_theme.gd" % [variation, state]) var fresh_box: StyleBox = fresh.get_stylebox(state, variation) var committed_box: StyleBox = committed.get_stylebox(state, variation) assert_eq(committed_box.get_class(), fresh_box.get_class(), "stale game_theme.tres — %s/%s stylebox type drifted from the builder; re-run build_game_theme.gd" % [variation, state]) if fresh_box is StyleBoxFlat: var f: StyleBoxFlat = fresh_box var c: StyleBoxFlat = committed_box assert_eq(c.bg_color, f.bg_color, "stale game_theme.tres — %s/%s bg_color drifted from the builder; re-run build_game_theme.gd" % [variation, state]) assert_eq(c.border_color, f.border_color, "stale game_theme.tres — %s/%s border_color drifted from the builder; re-run build_game_theme.gd" % [variation, state]) assert_eq(c.border_width_left, f.border_width_left, "stale game_theme.tres — %s/%s border_width_left drifted from the builder; re-run build_game_theme.gd" % [variation, state]) assert_eq(c.border_width_top, f.border_width_top, "stale game_theme.tres — %s/%s border_width_top drifted from the builder; re-run build_game_theme.gd" % [variation, state]) assert_eq(c.border_width_right, f.border_width_right, "stale game_theme.tres — %s/%s border_width_right drifted from the builder; re-run build_game_theme.gd" % [variation, state]) assert_eq(c.border_width_bottom, f.border_width_bottom, "stale game_theme.tres — %s/%s border_width_bottom drifted from the builder; re-run build_game_theme.gd" % [variation, state]) assert_eq(c.corner_radius_top_left, f.corner_radius_top_left, "stale game_theme.tres — %s/%s corner_radius_top_left drifted from the builder; re-run build_game_theme.gd" % [variation, state]) assert_eq(c.corner_radius_top_right, f.corner_radius_top_right, "stale game_theme.tres — %s/%s corner_radius_top_right drifted from the builder; re-run build_game_theme.gd" % [variation, state]) assert_eq(c.corner_radius_bottom_right, f.corner_radius_bottom_right, "stale game_theme.tres — %s/%s corner_radius_bottom_right drifted from the builder; re-run build_game_theme.gd" % [variation, state]) assert_eq(c.corner_radius_bottom_left, f.corner_radius_bottom_left, "stale game_theme.tres — %s/%s corner_radius_bottom_left drifted from the builder; re-run build_game_theme.gd" % [variation, state]) # The FONT half — the whole reason this guard could not fail against a font # role. Compare the font's resource_path, not the Resource: `load()` caches, # so two references to the same .ttf are the same instance and `==` would be # true even when the builder swapped SERIF for MONO... only if the .tres # happened to point at the same file anyway. The path is the thing that drifts. for font_name in fresh.get_font_list(variation): assert_true(committed.has_font(font_name, variation), "stale game_theme.tres — %s/%s font is missing entirely; re-run build_game_theme.gd" % [variation, font_name]) var fresh_font: Font = fresh.get_font(font_name, variation) var committed_font: Font = committed.get_font(font_name, variation) assert_eq(committed_font.resource_path, fresh_font.resource_path, "stale game_theme.tres — %s/%s font drifted from the builder; re-run build_game_theme.gd" % [variation, font_name]) for size_name in fresh.get_font_size_list(variation): assert_true(committed.has_font_size(size_name, variation), "stale game_theme.tres — %s/%s font size is missing entirely; re-run build_game_theme.gd" % [variation, size_name]) assert_eq(committed.get_font_size(size_name, variation), fresh.get_font_size(size_name, variation), "stale game_theme.tres — %s/%s font size drifted from the builder; re-run build_game_theme.gd" % [variation, size_name]) for color_name in fresh.get_color_list(variation): assert_true(committed.has_color(color_name, variation), "stale game_theme.tres — %s/%s colour is missing entirely; re-run build_game_theme.gd" % [variation, color_name]) var fresh_color: Color = fresh.get_color(color_name, variation) var committed_color: Color = committed.get_color(color_name, variation) assert_eq(committed_color, fresh_color, "stale game_theme.tres — %s/%s drifted from the builder; re-run build_game_theme.gd" % [variation, color_name]) func test_creation_variations_exist_with_the_states_the_screen_needs(): var t: Theme = load(THEME_PATH) # A race/calling card is a Button that must show a CHOSEN state. assert_true(t.has_stylebox(&"normal", ThemeKeys.SELECT_CARD)) assert_true(t.has_stylebox(&"hover", ThemeKeys.SELECT_CARD)) assert_true(t.has_stylebox(&"pressed", ThemeKeys.SELECT_CARD), "pressed IS the chosen ring") assert_true(t.has_stylebox(&"disabled", ThemeKeys.SELECT_CARD), "an origin may disallow a calling") # A skill chip is a toggle with three states: unpicked, picked, and granted-so-inert. assert_true(t.has_stylebox(&"normal", ThemeKeys.SKILL_CHIP)) assert_true(t.has_stylebox(&"pressed", ThemeKeys.SKILL_CHIP)) assert_true(t.has_stylebox(&"disabled", ThemeKeys.SKILL_CHIP)) # The tags and the primary ring. assert_true(t.has_stylebox(&"normal", ThemeKeys.CHOSEN_TAG)) assert_true(t.has_stylebox(&"normal", ThemeKeys.PRIMARY_TAG)) assert_true(t.has_stylebox(&"panel", ThemeKeys.PRIMARY_CARD)) func test_section_label_is_a_font_role_on_parchment(): var t: Theme = load(THEME_PATH) assert_true(t.has_font(&"font", ThemeKeys.SECTION_LABEL)) assert_eq(t.get_color(&"font_color", ThemeKeys.SECTION_LABEL), Palette.INK_LABEL_MUTED)