Lets plugins declare icon-pack dependencies (e.g. "tabler", "phosphor") in plugin.mod and PluginRegistration. The CMS loader auto-installs declared packs from the bundled registry before the plugin loads. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
274 lines
6.4 KiB
Go
274 lines
6.4 KiB
Go
package plugin
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestParseModFull_BasicFields(t *testing.T) {
|
|
src := []byte(`
|
|
[plugin]
|
|
name = "smartblock"
|
|
scope = "blockninja"
|
|
version = "1.4.2"
|
|
`)
|
|
m, err := ParseModFull(src)
|
|
if err != nil {
|
|
t.Fatalf("ParseModFull err: %v", err)
|
|
}
|
|
if m.Plugin.Name != "smartblock" {
|
|
t.Errorf("Name = %q, want smartblock", m.Plugin.Name)
|
|
}
|
|
if m.Plugin.Scope != "blockninja" {
|
|
t.Errorf("Scope = %q, want blockninja", m.Plugin.Scope)
|
|
}
|
|
if m.Plugin.Version != "1.4.2" {
|
|
t.Errorf("Version = %q, want 1.4.2", m.Plugin.Version)
|
|
}
|
|
if got := m.Coords(); got != "@blockninja/smartblock@1.4.2" {
|
|
t.Errorf("Coords() = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestParseModFull_BackCompatNoScope(t *testing.T) {
|
|
src := []byte(`
|
|
[plugin]
|
|
name = "legacy"
|
|
version = "0.1.0"
|
|
`)
|
|
m, err := ParseModFull(src)
|
|
if err != nil {
|
|
t.Fatalf("ParseModFull err: %v", err)
|
|
}
|
|
if m.Plugin.Scope != "" {
|
|
t.Errorf("Scope should be empty, got %q", m.Plugin.Scope)
|
|
}
|
|
if got := m.Coords(); got != "legacy@0.1.0" {
|
|
t.Errorf("Coords() = %q, want legacy@0.1.0", got)
|
|
}
|
|
}
|
|
|
|
func TestParseModFull_InvalidTOML(t *testing.T) {
|
|
_, err := ParseModFull([]byte("not valid toml = ="))
|
|
if err == nil {
|
|
t.Fatal("expected parse error")
|
|
}
|
|
}
|
|
|
|
func TestParseModFull_EmptyInput(t *testing.T) {
|
|
m, err := ParseModFull(nil)
|
|
if err != nil {
|
|
t.Fatalf("nil input err: %v", err)
|
|
}
|
|
if m.Plugin.Name != "" {
|
|
t.Errorf("Name should be empty")
|
|
}
|
|
}
|
|
|
|
func TestParseModFull_KindAndCategories(t *testing.T) {
|
|
src := []byte(`
|
|
[plugin]
|
|
name = "analyser"
|
|
scope = "blockninja"
|
|
version = "0.1.0"
|
|
kind = "plugin"
|
|
categories = ["analytics", "seo"]
|
|
`)
|
|
m, err := ParseModFull(src)
|
|
if err != nil {
|
|
t.Fatalf("ParseModFull err: %v", err)
|
|
}
|
|
if m.Plugin.Kind != "plugin" {
|
|
t.Errorf("Kind = %q, want plugin", m.Plugin.Kind)
|
|
}
|
|
if got := m.Plugin.Categories; len(got) != 2 || got[0] != "analytics" || got[1] != "seo" {
|
|
t.Errorf("Categories = %v", got)
|
|
}
|
|
}
|
|
|
|
func TestParseModFull_KindDefaultsEmpty(t *testing.T) {
|
|
src := []byte(`
|
|
[plugin]
|
|
name = "legacy"
|
|
version = "0.1.0"
|
|
`)
|
|
m, err := ParseModFull(src)
|
|
if err != nil {
|
|
t.Fatalf("ParseModFull err: %v", err)
|
|
}
|
|
if m.Plugin.Kind != "" {
|
|
t.Errorf("Kind should be empty for legacy mod, got %q", m.Plugin.Kind)
|
|
}
|
|
}
|
|
|
|
func TestCoords_AcceptsScopeWithOrWithoutAt(t *testing.T) {
|
|
want := "@themes/foo@1.0.0"
|
|
|
|
withAt := &ModFile{Plugin: ModPlugin{Name: "foo", Scope: "@themes", Version: "1.0.0"}}
|
|
if got := withAt.Coords(); got != want {
|
|
t.Errorf("Coords() with leading @ = %q, want %q", got, want)
|
|
}
|
|
|
|
withoutAt := &ModFile{Plugin: ModPlugin{Name: "foo", Scope: "themes", Version: "1.0.0"}}
|
|
if got := withoutAt.Coords(); got != want {
|
|
t.Errorf("Coords() without leading @ = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestParseModFull_PrivateField(t *testing.T) {
|
|
src := []byte(`
|
|
[plugin]
|
|
name = "internal-tool"
|
|
version = "0.1.0"
|
|
private = true
|
|
`)
|
|
m, err := ParseModFull(src)
|
|
if err != nil {
|
|
t.Fatalf("ParseModFull err: %v", err)
|
|
}
|
|
if !m.Plugin.Private {
|
|
t.Errorf("Private = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestParseModFull_PrivateDefaultsFalse(t *testing.T) {
|
|
src := []byte(`
|
|
[plugin]
|
|
name = "public-thing"
|
|
scope = "themes"
|
|
version = "0.1.0"
|
|
`)
|
|
m, err := ParseModFull(src)
|
|
if err != nil {
|
|
t.Fatalf("ParseModFull err: %v", err)
|
|
}
|
|
if m.Plugin.Private {
|
|
t.Errorf("Private = true, want false (default)")
|
|
}
|
|
}
|
|
|
|
func TestCoords_PrivateOverridesScope(t *testing.T) {
|
|
m := &ModFile{Plugin: ModPlugin{
|
|
Name: "myplugin",
|
|
Scope: "@themes",
|
|
Version: "0.1.0",
|
|
Private: true,
|
|
}}
|
|
if got := m.Coords(); got != "@private/myplugin@0.1.0" {
|
|
t.Errorf("Coords() = %q, want @private/myplugin@0.1.0", got)
|
|
}
|
|
}
|
|
|
|
func TestCoords_PrivateNoScope(t *testing.T) {
|
|
m := &ModFile{Plugin: ModPlugin{
|
|
Name: "myplugin",
|
|
Version: "0.1.0",
|
|
Private: true,
|
|
}}
|
|
if got := m.Coords(); got != "@private/myplugin@0.1.0" {
|
|
t.Errorf("Coords() = %q, want @private/myplugin@0.1.0", got)
|
|
}
|
|
}
|
|
|
|
func TestParseModFull_RequiredIconPacks(t *testing.T) {
|
|
src := []byte(`
|
|
[plugin]
|
|
name = "neon"
|
|
version = "0.1.0"
|
|
required_icon_packs = ["tabler", "phosphor"]
|
|
`)
|
|
m, err := ParseModFull(src)
|
|
if err != nil {
|
|
t.Fatalf("ParseModFull err: %v", err)
|
|
}
|
|
if len(m.Plugin.RequiredIconPacks) != 2 {
|
|
t.Fatalf("RequiredIconPacks len = %d, want 2", len(m.Plugin.RequiredIconPacks))
|
|
}
|
|
if m.Plugin.RequiredIconPacks[0] != "tabler" || m.Plugin.RequiredIconPacks[1] != "phosphor" {
|
|
t.Errorf("RequiredIconPacks = %v", m.Plugin.RequiredIconPacks)
|
|
}
|
|
}
|
|
|
|
func TestParseModFull_RequiredIconPacksOmitted(t *testing.T) {
|
|
src := []byte(`
|
|
[plugin]
|
|
name = "noicons"
|
|
version = "0.1.0"
|
|
`)
|
|
m, err := ParseModFull(src)
|
|
if err != nil {
|
|
t.Fatalf("ParseModFull err: %v", err)
|
|
}
|
|
if len(m.Plugin.RequiredIconPacks) != 0 {
|
|
t.Errorf("RequiredIconPacks should be empty when omitted, got %v", m.Plugin.RequiredIconPacks)
|
|
}
|
|
}
|
|
|
|
func TestParseRequiredIconPacks(t *testing.T) {
|
|
src := []byte(`
|
|
[plugin]
|
|
name = "neon"
|
|
version = "0.1.0"
|
|
required_icon_packs = ["tabler", " phosphor ", ""]
|
|
`)
|
|
got := ParseRequiredIconPacks(src)
|
|
if len(got) != 2 {
|
|
t.Fatalf("ParseRequiredIconPacks len = %d (%v), want 2", len(got), got)
|
|
}
|
|
if got[0] != "tabler" || got[1] != "phosphor" {
|
|
t.Errorf("ParseRequiredIconPacks = %v, want [tabler phosphor]", got)
|
|
}
|
|
}
|
|
|
|
func TestParseRequiredIconPacks_NilOnAbsent(t *testing.T) {
|
|
src := []byte(`
|
|
[plugin]
|
|
name = "noicons"
|
|
version = "0.1.0"
|
|
`)
|
|
if got := ParseRequiredIconPacks(src); got != nil {
|
|
t.Errorf("ParseRequiredIconPacks on absent field = %v, want nil", got)
|
|
}
|
|
}
|
|
|
|
func TestParseRequiredIconPacks_NilOnInvalidTOML(t *testing.T) {
|
|
if got := ParseRequiredIconPacks([]byte("not valid = = =")); got != nil {
|
|
t.Errorf("ParseRequiredIconPacks on invalid TOML = %v, want nil", got)
|
|
}
|
|
}
|
|
|
|
func TestParseModFull_RequiresAndCompat(t *testing.T) {
|
|
src := []byte(`
|
|
[plugin]
|
|
name = "symposium"
|
|
scope = "blockninja"
|
|
version = "0.2.0"
|
|
|
|
[compatibility]
|
|
block_core = ">=1.5 <2.0"
|
|
|
|
[[requires]]
|
|
name = "@blockninja/smartblock"
|
|
version = ">=1.0 <2.0"
|
|
|
|
[[requires]]
|
|
name = "@blockninja/gotham"
|
|
version = ">=1.2"
|
|
`)
|
|
m, err := ParseModFull(src)
|
|
if err != nil {
|
|
t.Fatalf("ParseModFull err: %v", err)
|
|
}
|
|
if m.Compatibility == nil || m.Compatibility.BlockCore != ">=1.5 <2.0" {
|
|
t.Errorf("Compat = %+v", m.Compatibility)
|
|
}
|
|
if len(m.Requires) != 2 {
|
|
t.Fatalf("Requires len = %d, want 2", len(m.Requires))
|
|
}
|
|
if m.Requires[0].Name != "@blockninja/smartblock" {
|
|
t.Errorf("Requires[0].Name = %q", m.Requires[0].Name)
|
|
}
|
|
if m.Requires[1].Version != ">=1.2" {
|
|
t.Errorf("Requires[1].Version = %q", m.Requires[1].Version)
|
|
}
|
|
}
|