Вместо двух отдельных списков (Workshop ID и Mod ID) — один список модов: галочка включает мод, перетаскивание задаёт порядок загрузки, кнопка убирает пакет вместе со всеми его модами. Чтобы галочкам было за что зацепиться, панель читает mod.info уже скачанных модов: серверу в Mods нужен именно Mod ID, а он лежит внутри пакета мастерской и вручную его приходилось искать в описании мода. Пока мод не скачан, строка помечена и галочка недоступна — Mod ID неизвестен. Один пакет мастерской может содержать несколько модов; теперь они видны отдельными строками и включаются по отдельности. Коллекции: кнопка разворачивает коллекцию Steam в список входящих в неё модов, сохраняя порядок со страницы коллекции. И мод, и коллекция принимаются как ссылкой, так и голым идентификатором. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
78 lines
3.0 KiB
Go
78 lines
3.0 KiB
Go
package pzconfig
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// TestScanInstalledMods проверяет обе раскладки мастерской и локальную папку
|
|
// модов, а также то, что дубликаты схлопываются.
|
|
func TestScanInstalledMods(t *testing.T) {
|
|
root := t.TempDir()
|
|
content := filepath.Join(root, "server", "steamapps", "workshop", "content", pzAppID)
|
|
|
|
// Обычная раскладка: <workshop id>/mods/<папка>/mod.info
|
|
writeModInfo(t, filepath.Join(content, "111", "mods", "BetterSorting"),
|
|
"name=Better Sorting\nid=BetterSorting\ndescription=Сортировка\n")
|
|
// Второй мод в том же пакете мастерской.
|
|
writeModInfo(t, filepath.Join(content, "111", "mods", "BetterSortingCore"),
|
|
"name=Better Sorting Core\nid=BetterSortingCore\n")
|
|
// Раскладка без промежуточной папки mods.
|
|
writeModInfo(t, filepath.Join(content, "222", "ModOptions"),
|
|
"name=Mod Options\nid=ModOptions\n")
|
|
// Локальный мод, положенный руками.
|
|
local := filepath.Join(root, "Zomboid", "mods")
|
|
writeModInfo(t, filepath.Join(local, "MyTweak"), "name=My Tweak\nid=MyTweak\n")
|
|
// Тот же мод, что и в мастерской: должен попасть в список один раз.
|
|
writeModInfo(t, filepath.Join(local, "BetterSortingCopy"), "name=Better Sorting\nid=BetterSorting\n")
|
|
// Папка без mod.info игнорируется.
|
|
if err := os.MkdirAll(filepath.Join(content, "333", "mods", "empty"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mods := ScanInstalledMods([]string{filepath.Join(root, "server")}, local)
|
|
|
|
byID := make(map[string]InstalledMod, len(mods))
|
|
for _, m := range mods {
|
|
if _, dup := byID[m.ModID]; dup {
|
|
t.Errorf("мод %s встретился дважды", m.ModID)
|
|
}
|
|
byID[m.ModID] = m
|
|
}
|
|
if len(byID) != 4 {
|
|
t.Fatalf("найдено %d модов, ожидалось 4: %v", len(byID), byID)
|
|
}
|
|
|
|
if got := byID["BetterSorting"]; got.WorkshopID != "111" || got.Name != "Better Sorting" {
|
|
t.Errorf("BetterSorting разобран неверно: %+v", got)
|
|
}
|
|
if got := byID["ModOptions"]; got.WorkshopID != "222" {
|
|
t.Errorf("раскладка без папки mods не распознана: %+v", got)
|
|
}
|
|
if got := byID["MyTweak"]; got.WorkshopID != "" {
|
|
t.Errorf("локальному моду проставлен Workshop ID: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestReadModInfoRequiresID(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "mod.info")
|
|
if err := os.WriteFile(path, []byte("name=Без идентификатора\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := readModInfo(path); ok {
|
|
t.Error("мод без id принят")
|
|
}
|
|
}
|
|
|
|
func writeModInfo(t *testing.T, dir, content string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "mod.info"), []byte(content), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|