Spellbook lists
This commit is contained in:
@@ -13544,6 +13544,88 @@ func TestBuildNativeSupportsLoosePlainTableAtTopdataDataRoot(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildNativeGeneratesSpellColumnsFromClassSpellbooks(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
writeMinimalSpellbookProject(t, root)
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "classes", "spellbooks", "bard.json"), `{
|
||||
"key": "classes/spellbooks:bard",
|
||||
"class": "classes:bard",
|
||||
"levels": {
|
||||
"0": ["spells:flare"],
|
||||
"1": ["spells:aid"]
|
||||
}
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "classes", "spellbooks", "wizard.json"), `{
|
||||
"key": "classes/spellbooks:wizard",
|
||||
"class": "classes:wizard",
|
||||
"levels": {
|
||||
"1": ["spells:shield"]
|
||||
}
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "classes", "spellbooks", "sorcerer.json"), `{
|
||||
"key": "classes/spellbooks:sorcerer",
|
||||
"class": "classes:sorcerer",
|
||||
"levels": {
|
||||
"2": ["spells:shield"]
|
||||
}
|
||||
}`+"\n")
|
||||
|
||||
proj := testProject(root)
|
||||
report := ValidateProject(proj)
|
||||
if report.HasErrors() {
|
||||
t.Fatalf("expected spellbook project to validate cleanly, got:\n%s", diagnosticsText(report.Diagnostics))
|
||||
}
|
||||
|
||||
result, err := BuildNativeWithOptions(proj, NativeBuildOptions{BuildWiki: false}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildNative failed: %v", err)
|
||||
}
|
||||
|
||||
table := read2DATable(t, filepath.Join(result.Output2DADir, "spells.2da"))
|
||||
if !table.hasColumn("Bard") || !table.hasColumn("Wizard") || !table.hasColumn("Sorcerer") {
|
||||
t.Fatalf("expected generated spell columns, got %v", table.columns)
|
||||
}
|
||||
for row, values := range map[string]map[string]string{
|
||||
"0": {"Bard": "0", "Wizard": "****", "Sorcerer": "****"},
|
||||
"1": {"Bard": "1", "Wizard": "****", "Sorcerer": "****"},
|
||||
"2": {"Bard": "****", "Wizard": "1", "Sorcerer": "2"},
|
||||
} {
|
||||
for column, want := range values {
|
||||
if got := table.cell(row, column); got != want {
|
||||
t.Fatalf("row %s column %s: got %q, want %q", row, column, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateProjectRejectsInvalidClassSpellbooks(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
writeMinimalSpellbookProject(t, root)
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "classes", "spellbooks", "broken.json"), `{
|
||||
"key": "classes/spellbooks:broken",
|
||||
"class": "classes:missing",
|
||||
"levels": {
|
||||
"x": ["spells:flare"],
|
||||
"1": ["spells:missing"]
|
||||
}
|
||||
}`+"\n")
|
||||
|
||||
report := ValidateProject(testProject(root))
|
||||
if !report.HasErrors() {
|
||||
t.Fatal("expected invalid spellbook validation errors")
|
||||
}
|
||||
text := diagnosticsText(report.Diagnostics)
|
||||
for _, want := range []string{
|
||||
`unknown class "classes:missing"`,
|
||||
`level key "x" must be a nonnegative integer`,
|
||||
`unknown spell "spells:missing"`,
|
||||
} {
|
||||
if !strings.Contains(text, want) {
|
||||
t.Fatalf("expected validation message %q, got:\n%s", want, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateProjectDoesNotRewritePlainDatasetLockfile(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "ruleset"))
|
||||
@@ -13606,6 +13688,94 @@ func diagnosticsText(diags []Diagnostic) string {
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func writeMinimalSpellbookProject(t *testing.T, root string) {
|
||||
t.Helper()
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "classes", "core"))
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "classes", "spellbooks"))
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "spells"))
|
||||
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "classes", "core", "base.json"), `{
|
||||
"output": "classes.2da",
|
||||
"columns": ["Label", "SpellTableColumn"],
|
||||
"rows": [
|
||||
{"id": 0, "key": "classes:bard", "Label": "Bard", "SpellTableColumn": "Bard"},
|
||||
{"id": 1, "key": "classes:wizard", "Label": "Wizard", "SpellTableColumn": "Wizard"},
|
||||
{"id": 2, "key": "classes:sorcerer", "Label": "Sorcerer", "SpellTableColumn": "Sorcerer"}
|
||||
]
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "classes", "core", "lock.json"), `{
|
||||
"classes:bard": 0,
|
||||
"classes:wizard": 1,
|
||||
"classes:sorcerer": 2
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "base.json"), `{
|
||||
"output": "spells.2da",
|
||||
"columns": ["Label", "Bard", "Wiz_Sorc"],
|
||||
"rows": [
|
||||
{"id": 0, "key": "spells:flare", "Label": "Flare", "Bard": "9", "Wiz_Sorc": "9"},
|
||||
{"id": 1, "key": "spells:aid", "Label": "Aid", "Bard": "9", "Wiz_Sorc": "9"},
|
||||
{"id": 2, "key": "spells:shield", "Label": "Shield", "Bard": "9", "Wiz_Sorc": "9"}
|
||||
]
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "lock.json"), `{
|
||||
"spells:flare": 0,
|
||||
"spells:aid": 1,
|
||||
"spells:shield": 2
|
||||
}`+"\n")
|
||||
}
|
||||
|
||||
type twoDATable struct {
|
||||
columns []string
|
||||
rows map[string]map[string]string
|
||||
}
|
||||
|
||||
func read2DATable(t *testing.T, path string) twoDATable {
|
||||
t.Helper()
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read 2da %s: %v", path, err)
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(string(raw)), "\n")
|
||||
if len(lines) < 3 {
|
||||
t.Fatalf("expected 2da header and columns in %s, got:\n%s", path, string(raw))
|
||||
}
|
||||
columns := strings.Fields(lines[2])
|
||||
rows := map[string]map[string]string{}
|
||||
for _, line := range lines[3:] {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 0 {
|
||||
continue
|
||||
}
|
||||
row := map[string]string{}
|
||||
for index, column := range columns {
|
||||
value := "****"
|
||||
if index+1 < len(fields) {
|
||||
value = fields[index+1]
|
||||
}
|
||||
row[column] = value
|
||||
}
|
||||
rows[fields[0]] = row
|
||||
}
|
||||
return twoDATable{columns: columns, rows: rows}
|
||||
}
|
||||
|
||||
func (t twoDATable) hasColumn(column string) bool {
|
||||
for _, candidate := range t.columns {
|
||||
if candidate == column {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t twoDATable) cell(rowID, column string) string {
|
||||
row := t.rows[rowID]
|
||||
if row == nil {
|
||||
return ""
|
||||
}
|
||||
return row[column]
|
||||
}
|
||||
|
||||
func runGitTest(t *testing.T, dir string, args ...string) {
|
||||
t.Helper()
|
||||
cmd := exec.Command("git", args...)
|
||||
|
||||
Reference in New Issue
Block a user