fix(theme-guard): close the drift guard's third miss — the meta-guard's own continue whitelisted the gap
test_committed_tres_matches_builder has now been "fixed" three times (traps.md #12). v1 hardcoded 3 checks; v2 iterated ThemeKeys.ALL but missed the six font roles; v3 added a meta-guard specifically to catch "the builder styles a thing guarded by nothing" — but that meta-guard contained `if fresh.get_type_variation_base(variation) == &"": continue`, which whitelisted exactly the case it was built to catch. The builder styles RichTextLabel directly (build_game_theme.gd's _rich_text()), with no set_type_variation, so it reports base "" and was skipped unconditionally — and RichTextLabel is what renders every line of DM prose the creation screen shows (_origin_text, _detail_blurb). Nothing compared its fonts, font size, or colour, nor the theme-level default_font/default_font_size, against the committed .tres. Adds ThemeKeys.BASE_TYPES for base Control types the builder styles directly, folds it into the drift guard's coverage, replaces the meta-guard's continue with an assertion that names the offending type, and adds the missing theme-level default font/size comparison. Proved with three reverted breaks: a RichTextLabel font-size edit, a theme-level default_font_size edit, and an unregistered "GhostRole" variation — all three now go red and name the drifted property. Also: strengthens test_creation_copy's error-copy sweep to assert a mapped error actually reaches its authored line rather than silently falling through to the generic UNSPOKEN fallback (CreationCopy.UNSPOKEN satisfied all four prior checks, so a renamed validator string could regress silently); and removes two assertions that were true by construction / already true before their test's action ran. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -141,10 +141,35 @@ func test_an_unmapped_error_still_speaks_in_voice():
|
||||
assert_false(CreationCopy.UNSPOKEN.contains("_"))
|
||||
|
||||
|
||||
func _is_deliberately_unmapped(raw: String) -> bool:
|
||||
# The shapes error_line has NO authored line for, on purpose, because the
|
||||
# screen structurally cannot produce them (CreationDraft always emits an
|
||||
# int seed and a spend built only from increment/decrement, which can
|
||||
# never exceed the pool or name a non-attribute) — a malformed seed or
|
||||
# spend, or a broken content reference, is not a player mistake to voice
|
||||
# in character; test_an_unmapped_error_still_speaks_in_voice already pins
|
||||
# the fallback for exactly this set. Named by the validator's own prefixes
|
||||
# so this stays independent of error_line's internals.
|
||||
return (raw.begins_with("unresolved ref:")
|
||||
or raw.begins_with("seed is required")
|
||||
or raw.begins_with("spend")
|
||||
or raw.begins_with("cannot spend on")
|
||||
or raw == "skills must be an array")
|
||||
|
||||
|
||||
func test_no_error_the_validator_can_raise_reaches_the_player_raw():
|
||||
# Sweep EVERY error NewGame.validate actually produces for a draft the screen can
|
||||
# be driven into (plus the hostile shapes only a saga could hand it), and assert
|
||||
# the player never sees the id, the underscore, or the validator's grammar.
|
||||
# the player never sees the id, the underscore, or the validator's grammar — AND,
|
||||
# for every shape error_line actually has an authored line for, that the line
|
||||
# fired rather than silently falling through to the generic UNSPOKEN fallback.
|
||||
#
|
||||
# CreationCopy.UNSPOKEN ("something in this does not hold — look again") is
|
||||
# itself non-blank, isn't the raw string, has no underscore, and has no "got " —
|
||||
# so the four checks below cannot tell "correctly mapped to its authored line"
|
||||
# from "silently fell through." Without the UNSPOKEN check, renaming a validator
|
||||
# string in new_game.gd so an error_line branch stops matching would leave every
|
||||
# error line reading the generic fallback, suite green.
|
||||
var d := _draft()
|
||||
var hostiles: Array = [
|
||||
{"name": "", "skills": [], "bonus_skill": ""}, # nameless, unpicked
|
||||
@@ -165,6 +190,7 @@ func test_no_error_the_validator_can_raise_reaches_the_player_raw():
|
||||
}
|
||||
|
||||
var seen := 0
|
||||
var mapped_seen := 0
|
||||
for h in hostiles:
|
||||
var creation := base.duplicate(true)
|
||||
for k in h:
|
||||
@@ -173,9 +199,16 @@ func test_no_error_the_validator_can_raise_reaches_the_player_raw():
|
||||
assert_gt(errors.size(), 0, "hostile creation %s must actually be invalid, or it guards nothing" % str(h))
|
||||
for e in errors:
|
||||
seen += 1
|
||||
var line: String = CreationCopy.error_line(str(e), d)
|
||||
assert_ne(line.strip_edges(), "", "'%s' put a BLANK line on the label" % e)
|
||||
assert_ne(line, str(e), "'%s' reached the player raw" % e)
|
||||
assert_false(line.contains("_"), "'%s' put a snake_case id on the label: %s" % [e, line])
|
||||
assert_false(line.contains("got "), "'%s' put the validator's grammar on the label: %s" % [e, line])
|
||||
var raw := str(e)
|
||||
var line: String = CreationCopy.error_line(raw, d)
|
||||
assert_ne(line.strip_edges(), "", "'%s' put a BLANK line on the label" % raw)
|
||||
assert_ne(line, raw, "'%s' reached the player raw" % raw)
|
||||
assert_false(line.contains("_"), "'%s' put a snake_case id on the label: %s" % [raw, line])
|
||||
assert_false(line.contains("got "), "'%s' put the validator's grammar on the label: %s" % [raw, line])
|
||||
|
||||
if not _is_deliberately_unmapped(raw):
|
||||
mapped_seen += 1
|
||||
assert_ne(line, CreationCopy.UNSPOKEN,
|
||||
"'%s' has an authored error_line branch but silently fell through to the generic fallback" % raw)
|
||||
assert_gt(seen, 12, "the sweep must actually reach a spread of errors, not one or two")
|
||||
assert_gt(mapped_seen, 8, "the sweep must actually exercise error_line's mapped branches, not just the deliberately-unmapped ones")
|
||||
|
||||
Reference in New Issue
Block a user