package topdata import ( "os" "path/filepath" "strings" "testing" "time" ) 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) } if result.WikiPages != 1 { t.Fatalf("expected one generated entity page, got %d", result.WikiPages) } 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, "

Athletics

") || !strings.Contains(text, `class="wiki-callout wiki-callout--status"`) || !strings.Contains(text, "This skill is unchanged from vanilla.") { t.Fatalf("unexpected generated page:\n%s", text) } if !strings.Contains(text, ``) || !strings.Contains(text, ``) || !strings.Contains(text, "

See Also

") { t.Fatalf("expected generated page to include manual section placeholders:\n%s", text) } statusPath := filepath.Join(root, ".cache", "wiki", "pages", "meta", "wikistatus.html") statusRaw, err := os.ReadFile(statusPath) if err != nil { t.Fatalf("read status page: %v", err) } if !strings.Contains(string(statusRaw), "Vanilla1") { t.Fatalf("expected wiki status summary to count vanilla page:\n%s", string(statusRaw)) } indexRaw, err := os.ReadFile(filepath.Join(root, ".cache", "wiki", "page-index.json")) if err != nil { t.Fatalf("read page index: %v", err) } indexText := string(indexRaw) if !strings.Contains(indexText, `"page_id": "skills:athletics"`) || !strings.Contains(indexText, `"output_path": "pages/skills/athletics.html"`) || !strings.Contains(indexText, `"edit_policy": "preserve_manual_sections"`) { t.Fatalf("expected deterministic page-index metadata, got:\n%s", indexText) } if _, err := os.Stat(filepath.Join(root, ".cache", "wiki", "state.json")); err != nil { t.Fatalf("expected state file after first build: %v", err) } stateBefore, err := loadWikiState(filepath.Join(root, ".cache", "wiki", "state.json")) if err != nil { t.Fatalf("read state before second build: %v", err) } digestBefore, err := computeWikiSourceDigest(filepath.Join(root, "topdata")) if err != nil { t.Fatalf("compute digest before second build: %v", err) } if stateBefore.Digest != digestBefore { t.Fatalf("expected state digest to match current digest before second build, got state=%s current=%s", stateBefore.Digest, digestBefore) } second, err := BuildNative(proj, nil) if err != nil { t.Fatalf("second BuildNative failed: %v", err) } if second.WikiPages != 1 { t.Fatalf("expected a single generated skill wiki page after rebuild, got %d", second.WikiPages) } } func TestWikiClassNamePrefersFullNameInsteadOfShortCode(t *testing.T) { ctx := &wikiContext{} got := ctx.resolveRowName("classes", map[string]any{ "Name": map[string]any{"tlk": map[string]any{"key": "classes:wizard.name", "text": "Wizard"}}, "Short": map[string]any{"tlk": map[string]any{"key": "classes:wizard.short", "text": "Wiz"}}, }) if got != "Wizard" { t.Fatalf("expected full class name, got %q", got) } } func TestBuildAndPackageBuildsWikiOnlyWhenRequested(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": "Skill text."}}, "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 := BuildAndPackageWithOptions(proj, BuildAndPackageOptions{}, nil) if err != nil { t.Fatalf("BuildAndPackageWithOptions failed: %v", err) } if result.WikiOutputDir != "" || result.WikiPages != 0 { t.Fatalf("expected wiki generation to be skipped by default, got dir=%q pages=%d", result.WikiOutputDir, result.WikiPages) } if _, err := os.Stat(filepath.Join(root, ".cache", "wiki", "state.json")); !os.IsNotExist(err) { t.Fatalf("expected no wiki state without explicit wiki build, got %v", err) } result, err = BuildAndPackageWithOptions(proj, BuildAndPackageOptions{Force: true, BuildWiki: true}, nil) if err != nil { t.Fatalf("BuildAndPackageWithOptions with wiki failed: %v", err) } if result.WikiPages != 1 { t.Fatalf("expected one generated wiki page when requested, got %d", result.WikiPages) } if _, err := os.Stat(filepath.Join(root, ".cache", "wiki", "state.json")); err != nil { t.Fatalf("expected wiki state after explicit wiki build: %v", err) } } func TestBuildNativeRegeneratesWikiWhenTLKTextChanges(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "skills")) writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") basePath := filepath.Join(root, "topdata", "data", "skills", "base.json") writeFile(t, basePath, `{ "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": "Original text."}}, "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 = "" proj.Config.Autogen.Consumers = nil if _, err := BuildNative(proj, nil); err != nil { t.Fatalf("initial BuildNative failed: %v", err) } writeFile(t, basePath, `{ "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": "Updated text."}}, "HideFromLevelUp": "0", "Untrained": "1", "KeyAbility": "STR", "ArmorCheckPenalty": "1", "Constant": "SKILL_ATHLETICS" } ] }`+"\n") if _, err := BuildNative(proj, nil); err != nil { t.Fatalf("BuildNative after text change failed: %v", err) } pagePath := filepath.Join(root, ".cache", "wiki", "pages", "skills", "athletics.html") got, err := os.ReadFile(pagePath) if err != nil { t.Fatalf("read regenerated page: %v", err) } if !strings.Contains(string(got), "Updated text.") { t.Fatalf("expected regenerated page to contain updated description:\n%s", string(got)) } } func TestBuildPackageIgnoresWikiSourcesForFreshness(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": "Skill text."}}, "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 = "" proj.Config.Autogen.Consumers = nil if _, err := BuildNative(proj, nil); err != nil { t.Fatalf("BuildNative failed: %v", err) } pagePath := filepath.Join(root, ".cache", "wiki", "pages", "skills", "athletics.html") newTime := time.Now().Add(2 * time.Second) if err := os.Chtimes(pagePath, newTime, newTime); err != nil { t.Fatalf("touch wiki page: %v", err) } if _, err := BuildPackage(proj, nil); err != nil { t.Fatalf("expected wiki output to be ignored for freshness, got %v", err) } }