Phase 2 wiki template refactor
This commit is contained in:
@@ -206,6 +206,176 @@ func TestWikiTemplateMissingFieldFailsUnlessDefaultProvided(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiTemplateRendersConfiguredClassProgressionTable(t *testing.T) {
|
||||
ctx := testWikiTableContext(t, `
|
||||
tables:
|
||||
ClassProgression:
|
||||
source: class_progression
|
||||
class: "wiki-table wiki-class-progression"
|
||||
rows:
|
||||
levels: 1-4
|
||||
when: "{{Level >= 1 && Level <= 4}}"
|
||||
columns:
|
||||
- key: level
|
||||
label: Lvl
|
||||
value: "{{ordinal(Level)}}"
|
||||
- key: bab
|
||||
label: BAB
|
||||
value: "{{BAB}}"
|
||||
- key: saves
|
||||
label: Saves
|
||||
children:
|
||||
- key: fort
|
||||
label: Fort
|
||||
value: "{{FortSave}}"
|
||||
- key: reflex
|
||||
label: Ref
|
||||
value: "{{RefSave}}"
|
||||
- key: will
|
||||
label: Will
|
||||
value: "{{WillSave}}"
|
||||
- key: feats
|
||||
label: Feats
|
||||
value: "{{join(GrantedFeats, \", \")}}"
|
||||
empty: ""
|
||||
allow_wiki_markup: true
|
||||
- key: hp_range
|
||||
label: HP range
|
||||
value: "{{range(HPMin, HPMax)}}"
|
||||
- key: notes
|
||||
label: Notes
|
||||
value: "{{if(Level == 4 && BonusFeat != \"0\", link(\"feat:weapon_specialization\", \"Weapon Specialization\") + \" first available.\", Notes)}}"
|
||||
empty: ""
|
||||
allow_wiki_markup: true
|
||||
`)
|
||||
page := wikiTemplatePage{
|
||||
PageID: "classes:fighter",
|
||||
Category: "classes",
|
||||
Key: "classes:fighter",
|
||||
Title: "Fighter",
|
||||
Row: map[string]any{
|
||||
"AttackBonusTable": "cls_atk_1",
|
||||
"BonusFeatsTable": map[string]any{"table": "classes/bonusfeats:fighter"},
|
||||
"FeatsTable": map[string]any{"table": "classes/feats:fighter"},
|
||||
"HitDie": 10,
|
||||
"SavingThrowTable": map[string]any{"table": "classes/saves:fortitude"},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := ctx.renderWikiTemplateString("classes.html", `<h1>{{title}}</h1>
|
||||
{{table:ClassProgression}}`, page)
|
||||
if err != nil {
|
||||
t.Fatalf("render table template: %v", err)
|
||||
}
|
||||
for _, expected := range []string{
|
||||
`<table class="wiki-table wiki-class-progression">`,
|
||||
`<th scope="col" rowspan="2">Lvl</th>`,
|
||||
`<th scope="colgroup" colspan="3">Saves</th>`,
|
||||
`<td>1st</td>`,
|
||||
`<td>+1</td>`,
|
||||
`<td>2</td>`,
|
||||
`<td>[[feat:literate|Literate]], [[feat:weapon:proficiency:simple|Simple Weapon Proficiency]]</td>`,
|
||||
`<td>1-10</td>`,
|
||||
`<td>[[feat:weapon:specialization|Weapon Specialization]] first available.</td>`,
|
||||
} {
|
||||
if !strings.Contains(got, expected) {
|
||||
t.Fatalf("expected %q in rendered table:\n%s", expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiTableMissingFieldReportsTableAndColumn(t *testing.T) {
|
||||
ctx := testWikiTableContext(t, `
|
||||
tables:
|
||||
Broken:
|
||||
source: class_progression
|
||||
rows:
|
||||
levels: 1
|
||||
columns:
|
||||
- key: missing
|
||||
label: Missing
|
||||
value: "{{NoSuchField}}"
|
||||
`)
|
||||
page := wikiTemplatePage{
|
||||
PageID: "classes:fighter",
|
||||
Category: "classes",
|
||||
Key: "classes:fighter",
|
||||
Row: map[string]any{
|
||||
"AttackBonusTable": "cls_atk_1",
|
||||
"HitDie": 10,
|
||||
"SavingThrowTable": map[string]any{"table": "classes/saves:fortitude"},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ctx.renderWikiTemplateString("classes.html", `{{table:Broken}}`, page)
|
||||
if err == nil {
|
||||
t.Fatal("expected missing field error")
|
||||
}
|
||||
for _, expected := range []string{`classes:fighter`, `table Broken`, `column missing`, `NoSuchField`} {
|
||||
if !strings.Contains(err.Error(), expected) {
|
||||
t.Fatalf("expected error to contain %q, got %v", expected, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiTableRejectsImpossibleNestedHeader(t *testing.T) {
|
||||
_, err := parseWikiTableDefinitions([]byte(`
|
||||
tables:
|
||||
Broken:
|
||||
source: class_progression
|
||||
rows:
|
||||
levels: 1
|
||||
columns:
|
||||
- key: bad
|
||||
label: Bad
|
||||
value: "{{Level}}"
|
||||
children:
|
||||
- key: child
|
||||
label: Child
|
||||
value: "{{Level}}"
|
||||
`), "test-tables.yaml")
|
||||
if err == nil || !strings.Contains(err.Error(), "cannot have both value and children") {
|
||||
t.Fatalf("expected impossible nested header error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testWikiTableContext(t *testing.T, yamlSource string) *wikiContext {
|
||||
t.Helper()
|
||||
ctx := &wikiContext{
|
||||
featRows: map[string]map[string]any{},
|
||||
featIDToKey: map[int]string{},
|
||||
classFeatTables: map[string]wikiTable{},
|
||||
classBonusTables: map[string]wikiTable{},
|
||||
classSaveTables: map[string]wikiTable{},
|
||||
wikiTablesPath: "test-tables.yaml",
|
||||
}
|
||||
ctx.featRows["feat:literate"] = map[string]any{"FEAT": map[string]any{"tlk": map[string]any{"text": "Literate"}}}
|
||||
ctx.featRows["feat:weapon_proficiency_simple"] = map[string]any{"FEAT": map[string]any{"tlk": map[string]any{"text": "Simple Weapon Proficiency"}}}
|
||||
ctx.featRows["feat:weapon_specialization"] = map[string]any{"FEAT": map[string]any{"tlk": map[string]any{"text": "Weapon Specialization"}}}
|
||||
ctx.classFeatTables["cls_feat_fight"] = wikiTable{OutputStem: "cls_feat_fight", Rows: []map[string]any{
|
||||
{"FeatIndex": map[string]any{"id": "feat:literate"}, "GrantedOnLevel": "1", "List": "3"},
|
||||
{"FeatIndex": map[string]any{"id": "feat:weapon_proficiency_simple"}, "GrantedOnLevel": "1", "List": "3"},
|
||||
}}
|
||||
ctx.classFeatTables["classes/feats:fighter"] = ctx.classFeatTables["cls_feat_fight"]
|
||||
ctx.classBonusTables["cls_bfeat_fight"] = wikiTable{OutputStem: "cls_bfeat_fight", Rows: []map[string]any{
|
||||
{"id": 1, "Bonus": "1"}, {"id": 2, "Bonus": "1"}, {"id": 3, "Bonus": "0"}, {"id": 4, "Bonus": "1"},
|
||||
}}
|
||||
ctx.classBonusTables["classes/bonusfeats:fighter"] = ctx.classBonusTables["cls_bfeat_fight"]
|
||||
ctx.classSaveTables["cls_savthr_fort"] = wikiTable{OutputStem: "cls_savthr_fort", Rows: []map[string]any{
|
||||
{"Level": "1", "FortSave": "2", "RefSave": "0", "WillSave": "0"},
|
||||
{"Level": "2", "FortSave": "3", "RefSave": "0", "WillSave": "0"},
|
||||
{"Level": "3", "FortSave": "3", "RefSave": "1", "WillSave": "1"},
|
||||
{"Level": "4", "FortSave": "4", "RefSave": "1", "WillSave": "1"},
|
||||
}}
|
||||
ctx.classSaveTables["classes/saves:fortitude"] = ctx.classSaveTables["cls_savthr_fort"]
|
||||
tables, err := parseWikiTableDefinitions([]byte(yamlSource), ctx.wikiTablesPath)
|
||||
if err != nil {
|
||||
t.Fatalf("parse table definitions: %v", err)
|
||||
}
|
||||
ctx.wikiTableDefinitions = tables
|
||||
return ctx
|
||||
}
|
||||
|
||||
func TestWikiManagedHashIgnoresPreservedSectionBody(t *testing.T) {
|
||||
first := strings.Join([]string{
|
||||
`<!-- sow-topdata-wiki:page=classes:acolyte -->`,
|
||||
|
||||
Reference in New Issue
Block a user