package topdata import ( "encoding/json" "os" "path/filepath" "strings" "testing" "time" "gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project" ) func TestBuildNativeGeneratesAndSkipsWikiPages(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "skills")) writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") writeFile(t, filepath.Join(root, "topdata", "data", "skills", "base.json"), `{ "output": "skills.2da", "columns": ["Label", "Name", "Description", "HideFromLevelUp", "Untrained", "KeyAbility", "ArmorCheckPenalty", "Constant"], "rows": [ { "id": 0, "key": "skills:athletics", "Label": "Athletics", "Name": {"tlk": {"key": "skills:athletics.name", "text": "Athletics"}}, "Description": {"tlk": {"key": "skills:athletics.description", "text": "Ability: Strength.\n\nUses:\n- Climb\n- Swim"}}, "HideFromLevelUp": "0", "Untrained": "1", "KeyAbility": "STR", "ArmorCheckPenalty": "1", "Constant": "SKILL_ATHLETICS" } ] }`+"\n") writeFile(t, filepath.Join(root, "topdata", "data", "skills", "lock.json"), `{"skills:athletics":0}`+"\n") proj := testProject(root) proj.Config.TopData.ReferenceBuilder = "" result, err := BuildNative(proj, nil) if err != nil { t.Fatalf("BuildNative failed: %v", err) } generatedHTMLCount := countGeneratedWikiHTMLFiles(t, filepath.Join(root, ".cache", "wiki", "pages")) if result.WikiPages != generatedHTMLCount { t.Fatalf("expected wiki page count to match generated HTML files, got result=%d files=%d", result.WikiPages, generatedHTMLCount) } pagePath := filepath.Join(root, ".cache", "wiki", "pages", "skills", "Athletics.html") got, err := os.ReadFile(pagePath) if err != nil { t.Fatalf("read generated page: %v", err) } text := string(got) if !strings.Contains(text, "
This is generated.
Generated description.
| Hit Die | d6 |
|---|
| Lvl | BAB | HP |
|---|---|---|
| {{Level|ordinal}} | {{BAB}} | {{HPMin}}-{{HPMax}} |
| 1st | +1 | 1-10 | ||
| 2nd | +2 | 2-20 |
| {{Label}} | {{Value|wiki}} |
|---|
| Ability Adjustments | {{ability_adjustments()}} |
|---|---|
| Favored Class | {{Favored|ref:"classes"|wiki}} |
You gain a +2 dodge bonus.
The bonus applies only while unarmored.
Type of Feat: General
Use: Automatic.
| Lvl | `, `Saves | `, `1st | `, `+1 | `, `2 | `, `[[Feats/Literate|Literate]], [[Feats/Simple_Weapon_Proficiency|Simple Weapon Proficiency]] | `, `1-10 | `, `[[Feats/Weapon_Specialization|Weapon Specialization]] first available. | `, } { if !strings.Contains(got, expected) { t.Fatalf("expected %q in rendered table:\n%s", expected, got) } } } func TestWikiTemplateRendersConfiguredClassProgressionSpellColumns(t *testing.T) { ctx := testWikiTableContext(t, ` tables: ClassProgression: source: class_progression class: "wiki-table wiki-class-progression" rows: levels: 1-2 columns: - key: level label: Lvl value: "{{Level}}" - key: per_day label: Base spells per day when: "{{HasSpellsPerDay}}" children: - key: per_day_0 label: 0 value: "{{SpellsPerDay0}}" empty: "-" - key: per_day_1 label: 1 value: "{{SpellsPerDay1}}" empty: "-" - key: known label: Known spells when: "{{HasSpellsKnown}}" children: - key: known_0 label: 0 value: "{{SpellsKnown0}}" empty: "-" - key: known_1 label: 1 value: "{{SpellsKnown1}}" empty: "-" `) ctx.classSpellGainTables["classes/spellsgained:bard"] = wikiTable{Rows: []map[string]any{ {"Level": 1, "SpellLevel0": 2, "SpellLevel1": 1}, {"Level": 2, "SpellLevel0": 3, "SpellLevel1": nil}, }} ctx.classSpellKnownTables["classes/spellsknown:bard"] = wikiTable{Rows: []map[string]any{ {"Level": 1, "SpellLevel0": 4, "SpellLevel1": 2}, {"Level": 2, "SpellLevel0": 5, "SpellLevel1": 3}, }} page := wikiTemplatePage{ PageID: "classes:bard", Category: "classes", Key: "classes:bard", Title: "Bard", Row: map[string]any{ "SpellCaster": 1, "SpellGainTable": map[string]any{"table": "classes/spellsgained:bard"}, "SpellKnownTable": map[string]any{"table": "classes/spellsknown:bard"}, }, } got, err := ctx.renderWikiTemplateString("classes.html", `{{table:ClassProgression}}`, page) if err != nil { t.Fatalf("render table template: %v", err) } for _, expected := range []string{ `Base spells per day | `, `Known spells | `, `
|---|---|---|---|---|
| 1 | 2 | 1 | 4 | 2 |
| 2 | 3 | - | 5 | 3 | Base spells per day | `) { t.Fatalf("expected only two visible spell columns:\n%s", got) } if strings.Contains(got, `2 | `) { t.Fatalf("expected unused spell level 2 column to be hidden:\n%s", got) } } func TestWikiTemplateOmitsSpellColumnsForPreparedAndNonCasterClasses(t *testing.T) { ctx := testWikiTableContext(t, ` tables: ClassProgression: source: class_progression rows: levels: 1 columns: - key: level label: Lvl value: "{{Level}}" - key: per_day label: Base spells per day when: "{{HasSpellsPerDay}}" children: - key: per_day_0 label: 0 value: "{{SpellsPerDay0}}" - key: known label: Known spells when: "{{HasSpellsKnown}}" children: - key: known_0 label: 0 value: "{{SpellsKnown0}}" `) ctx.classSpellGainTables["classes/spellsgained:cleric"] = wikiTable{Rows: []map[string]any{ {"Level": 1, "SpellLevel0": 3}, }} prepared := wikiTemplatePage{ PageID: "classes:cleric", Category: "classes", Key: "classes:cleric", Title: "Cleric", Row: map[string]any{ "SpellCaster": 1, "SpellGainTable": map[string]any{"table": "classes/spellsgained:cleric"}, }, } nonCaster := wikiTemplatePage{ PageID: "classes:fighter", Category: "classes", Key: "classes:fighter", Title: "Fighter", Row: map[string]any{"SpellCaster": 0}, } gotPrepared, err := ctx.renderWikiTemplateString("classes.html", `{{table:ClassProgression}}`, prepared) if err != nil { t.Fatalf("render prepared caster table: %v", err) } if !strings.Contains(gotPrepared, `Base spells per day`) || strings.Contains(gotPrepared, `Known spells`) { t.Fatalf("expected prepared caster to render per-day but not known columns:\n%s", gotPrepared) } gotNonCaster, err := ctx.renderWikiTemplateString("classes.html", `{{table:ClassProgression}}`, nonCaster) if err != nil { t.Fatalf("render non-caster table: %v", err) } if strings.Contains(gotNonCaster, `Base spells per day`) || strings.Contains(gotNonCaster, `Known spells`) { t.Fatalf("expected non-caster to omit spell columns:\n%s", gotNonCaster) } } func TestWikiTemplateRendersClassSpellbookProviderBySpellLevel(t *testing.T) { root := t.TempDir() dataPath := filepath.Join(root, "data.yaml") writeFile(t, dataPath, ` providers: ClassSpellbook: source: class_spellbook `+"\n") providers, err := loadWikiDataProviders(dataPath) if err != nil { t.Fatalf("load data providers: %v", err) } ctx := testWikiTableContext(t, "") ctx.wikiDataPath = dataPath ctx.wikiDataProviders = providers ctx.spellRows = map[string]map[string]any{ "spells:light": {"Name": map[string]any{"tlk": map[string]any{"text": "Light"}}}, "spells:cure_light_wounds": {"Name": map[string]any{"tlk": map[string]any{"text": "Cure Light Wounds"}}}, "spells:bless": {"Name": map[string]any{"tlk": map[string]any{"text": "Bless"}}}, } ctx.classSpellbooks = map[string]classSpellbookSpec{ "classes:acolyte": { Class: "classes:acolyte", Levels: map[int][]string{ 0: {"spells:light"}, 1: {"spells:cure_light_wounds", "spells:bless"}, }, }, } page := wikiTemplatePage{ PageID: "classes:acolyte", Category: "classes", Key: "classes:acolyte", Title: "Acolyte", Row: map[string]any{"SpellCaster": 1}, } got, err := ctx.renderWikiTemplateString("classes.html", `{{#each ClassSpellbook}}