package core_test import ( "context" "encoding/json" "fmt" "testing" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tests" "github.com/pocketbase/pocketbase/tools/types" ) func TestSelectFieldBaseMethods(t *testing.T) { testFieldBaseMethods(t, core.FieldTypeSelect) } func TestSelectFieldColumnType(t *testing.T) { app, _ := tests.NewTestApp() defer app.Cleanup() scenarios := []struct { name string field *core.SelectField expected string }{ { "single (zero)", &core.SelectField{}, "TEXT DEFAULT '' NOT NULL", }, { "single", &core.SelectField{MaxSelect: 1}, "TEXT DEFAULT '' NOT NULL", }, { "multiple", &core.SelectField{MaxSelect: 2}, "JSON DEFAULT '[]' NOT NULL", }, } for _, s := range scenarios { t.Run(s.name, func(t *testing.T) { if v := s.field.ColumnType(app); v != s.expected { t.Fatalf("Expected\n%q\ngot\n%q", s.expected, v) } }) } } func TestSelectFieldIsMultiple(t *testing.T) { scenarios := []struct { name string field *core.SelectField expected bool }{ { "single (zero)", &core.SelectField{}, false, }, { "single", &core.SelectField{MaxSelect: 1}, false, }, { "multiple (>1)", &core.SelectField{MaxSelect: 2}, true, }, } for _, s := range scenarios { t.Run(s.name, func(t *testing.T) { if v := s.field.IsMultiple(); v != s.expected { t.Fatalf("Expected %v, got %v", s.expected, v) } }) } } func TestSelectFieldPrepareValue(t *testing.T) { app, _ := tests.NewTestApp() defer app.Cleanup() record := core.NewRecord(core.NewBaseCollection("test")) scenarios := []struct { raw any field *core.SelectField expected string }{ // single {nil, &core.SelectField{}, `""`}, {"", &core.SelectField{}, `""`}, {123, &core.SelectField{}, `"123"`}, {"a", &core.SelectField{}, `"a"`}, {`["a"]`, &core.SelectField{}, `"a"`}, {[]string{}, &core.SelectField{}, `""`}, {[]string{"a", "b"}, &core.SelectField{}, `"b"`}, // multiple {nil, &core.SelectField{MaxSelect: 2}, `[]`}, {"", &core.SelectField{MaxSelect: 2}, `[]`}, {123, &core.SelectField{MaxSelect: 2}, `["123"]`}, {"a", &core.SelectField{MaxSelect: 2}, `["a"]`}, {`["a"]`, &core.SelectField{MaxSelect: 2}, `["a"]`}, {[]string{}, &core.SelectField{MaxSelect: 2}, `[]`}, {[]string{"a", "b", "c"}, &core.SelectField{MaxSelect: 2}, `["a","b","c"]`}, } for i, s := range scenarios { t.Run(fmt.Sprintf("%d_%#v_%v", i, s.raw, s.field.IsMultiple()), func(t *testing.T) { v, err := s.field.PrepareValue(record, s.raw) if err != nil { t.Fatal(err) } vRaw, err := json.Marshal(v) if err != nil { t.Fatal(err) } if string(vRaw) != s.expected { t.Fatalf("Expected %q, got %q", s.expected, vRaw) } }) } } func TestSelectFieldDriverValue(t *testing.T) { app, _ := tests.NewTestApp() defer app.Cleanup() scenarios := []struct { raw any field *core.SelectField expected string }{ // single {nil, &core.SelectField{}, `""`}, {"", &core.SelectField{}, `""`}, {123, &core.SelectField{}, `"123"`}, {"a", &core.SelectField{}, `"a"`}, {`["a"]`, &core.SelectField{}, `"a"`}, {[]string{}, &core.SelectField{}, `""`}, {[]string{"a", "b"}, &core.SelectField{}, `"b"`}, // multiple {nil, &core.SelectField{MaxSelect: 2}, `[]`}, {"", &core.SelectField{MaxSelect: 2}, `[]`}, {123, &core.SelectField{MaxSelect: 2}, `["123"]`}, {"a", &core.SelectField{MaxSelect: 2}, `["a"]`}, {`["a"]`, &core.SelectField{MaxSelect: 2}, `["a"]`}, {[]string{}, &core.SelectField{MaxSelect: 2}, `[]`}, {[]string{"a", "b", "c"}, &core.SelectField{MaxSelect: 2}, `["a","b","c"]`}, } for i, s := range scenarios { t.Run(fmt.Sprintf("%d_%#v_%v", i, s.raw, s.field.IsMultiple()), func(t *testing.T) { record := core.NewRecord(core.NewBaseCollection("test")) record.SetRaw(s.field.GetName(), s.raw) v, err := s.field.DriverValue(record) if err != nil { t.Fatal(err) } if s.field.IsMultiple() { _, ok := v.(types.JSONArray[string]) if !ok { t.Fatalf("Expected types.JSONArray value, got %T", v) } } else { _, ok := v.(string) if !ok { t.Fatalf("Expected string value, got %T", v) } } vRaw, err := json.Marshal(v) if err != nil { t.Fatal(err) } if string(vRaw) != s.expected { t.Fatalf("Expected %q, got %q", s.expected, vRaw) } }) } } func TestSelectFieldValidateValue(t *testing.T) { app, _ := tests.NewTestApp() defer app.Cleanup() collection := core.NewBaseCollection("test_collection") values := []string{"a", "b", "c"} scenarios := []struct { name string field *core.SelectField record func() *core.Record expectError bool }{ // single { "[single] zero field value (not required)", &core.SelectField{Name: "test", Values: values, MaxSelect: 1}, func() *core.Record { record := core.NewRecord(collection) record.SetRaw("test", "") return record }, false, }, { "[single] zero field value (required)", &core.SelectField{Name: "test", Values: values, MaxSelect: 1, Required: true}, func() *core.Record { record := core.NewRecord(collection) record.SetRaw("test", "") return record }, true, }, { "[single] unknown value", &core.SelectField{Name: "test", Values: values, MaxSelect: 1}, func() *core.Record { record := core.NewRecord(collection) record.SetRaw("test", "unknown") return record }, true, }, { "[single] known value", &core.SelectField{Name: "test", Values: values, MaxSelect: 1}, func() *core.Record { record := core.NewRecord(collection) record.SetRaw("test", "a") return record }, false, }, { "[single] > MaxSelect", &core.SelectField{Name: "test", Values: values, MaxSelect: 1}, func() *core.Record { record := core.NewRecord(collection) record.SetRaw("test", []string{"a", "b"}) return record }, true, }, // multiple { "[multiple] zero field value (not required)", &core.SelectField{Name: "test", Values: values, MaxSelect: 2}, func() *core.Record { record := core.NewRecord(collection) record.SetRaw("test", []string{}) return record }, false, }, { "[multiple] zero field value (required)", &core.SelectField{Name: "test", Values: values, MaxSelect: 2, Required: true}, func() *core.Record { record := core.NewRecord(collection) record.SetRaw("test", []string{}) return record }, true, }, { "[multiple] unknown value", &core.SelectField{Name: "test", Values: values, MaxSelect: 2}, func() *core.Record { record := core.NewRecord(collection) record.SetRaw("test", []string{"a", "unknown"}) return record }, true, }, { "[multiple] known value", &core.SelectField{Name: "test", Values: values, MaxSelect: 2}, func() *core.Record { record := core.NewRecord(collection) record.SetRaw("test", []string{"a", "b"}) return record }, false, }, { "[multiple] > MaxSelect", &core.SelectField{Name: "test", Values: values, MaxSelect: 2}, func() *core.Record { record := core.NewRecord(collection) record.SetRaw("test", []string{"a", "b", "c"}) return record }, true, }, { "[multiple] > MaxSelect (duplicated values)", &core.SelectField{Name: "test", Values: values, MaxSelect: 2}, func() *core.Record { record := core.NewRecord(collection) record.SetRaw("test", []string{"a", "b", "b", "a"}) return record }, false, }, } for _, s := range scenarios { t.Run(s.name, func(t *testing.T) { err := s.field.ValidateValue(context.Background(), app, s.record()) hasErr := err != nil if hasErr != s.expectError { t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err) } }) } } func TestSelectFieldValidateSettings(t *testing.T) { testDefaultFieldIdValidation(t, core.FieldTypeSelect) testDefaultFieldNameValidation(t, core.FieldTypeSelect) app, _ := tests.NewTestApp() defer app.Cleanup() scenarios := []struct { name string field func() *core.SelectField expectErrors []string }{ { "zero minimal", func() *core.SelectField { return &core.SelectField{ Id: "test", Name: "test", } }, []string{"values"}, }, { "MaxSelect > Values length", func() *core.SelectField { return &core.SelectField{ Id: "test", Name: "test", Values: []string{"a", "b"}, MaxSelect: 3, } }, []string{"maxSelect"}, }, { "MaxSelect <= Values length", func() *core.SelectField { return &core.SelectField{ Id: "test", Name: "test", Values: []string{"a", "b"}, MaxSelect: 2, } }, []string{}, }, } for _, s := range scenarios { t.Run(s.name, func(t *testing.T) { field := s.field() collection := core.NewBaseCollection("test_collection") collection.Fields.Add(field) errs := field.ValidateSettings(context.Background(), app, collection) tests.TestValidationErrors(t, errs, s.expectErrors) }) } } func TestSelectFieldFindSetter(t *testing.T) { values := []string{"a", "b", "c", "d"} scenarios := []struct { name string key string value any field *core.SelectField hasSetter bool expected string }{ { "no match", "example", "b", &core.SelectField{Name: "test", MaxSelect: 1, Values: values}, false, "", }, { "exact match (single)", "test", "b", &core.SelectField{Name: "test", MaxSelect: 1, Values: values}, true, `"b"`, }, { "exact match (multiple)", "test", []string{"a", "b"}, &core.SelectField{Name: "test", MaxSelect: 2, Values: values}, true, `["a","b"]`, }, { "append (single)", "test+", "b", &core.SelectField{Name: "test", MaxSelect: 1, Values: values}, true, `"b"`, }, { "append (multiple)", "test+", []string{"a"}, &core.SelectField{Name: "test", MaxSelect: 2, Values: values}, true, `["c","d","a"]`, }, { "prepend (single)", "+test", "b", &core.SelectField{Name: "test", MaxSelect: 1, Values: values}, true, `"d"`, // the last of the existing values }, { "prepend (multiple)", "+test", []string{"a"}, &core.SelectField{Name: "test", MaxSelect: 2, Values: values}, true, `["a","c","d"]`, }, { "subtract (single)", "test-", "d", &core.SelectField{Name: "test", MaxSelect: 1, Values: values}, true, `"c"`, }, { "subtract (multiple)", "test-", []string{"unknown", "c"}, &core.SelectField{Name: "test", MaxSelect: 2, Values: values}, true, `["d"]`, }, } for _, s := range scenarios { t.Run(s.name, func(t *testing.T) { collection := core.NewBaseCollection("test_collection") collection.Fields.Add(s.field) setter := s.field.FindSetter(s.key) hasSetter := setter != nil if hasSetter != s.hasSetter { t.Fatalf("Expected hasSetter %v, got %v", s.hasSetter, hasSetter) } if !hasSetter { return } record := core.NewRecord(collection) record.SetRaw(s.field.GetName(), []string{"c", "d"}) setter(record, s.value) raw, err := json.Marshal(record.Get(s.field.GetName())) if err != nil { t.Fatal(err) } rawStr := string(raw) if rawStr != s.expected { t.Fatalf("Expected %q, got %q", s.expected, rawStr) } }) } }