diff --git a/README.md b/README.md index a62cfaa..ecdf0b9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![PkgGoDev](https://pkg.go.dev/badge/image)](https://pkg.go.dev/github.com/ncruces/zenity) [![Go Report](https://goreportcard.com/badge/github.com/ncruces/zenity)](https://goreportcard.com/report/github.com/ncruces/zenity) +[![Go Report](http://gocover.io/_badge/github.com/ncruces/zenity)](https://gocover.io/github.com/ncruces/zenity) This repo includes both a cross-platform Go package providing [Zenity](https://help.gnome.org/users/zenity/stable/)-like dialogs diff --git a/file.go b/file.go index 23b27c0..0ea5dab 100644 --- a/file.go +++ b/file.go @@ -270,7 +270,6 @@ func isUniformTypeIdentifier(pattern string) bool { func splitDirAndName(path string) (dir, name string) { if path != "" { - path = filepath.Clean(path) fi, err := os.Stat(path) if err == nil && fi.IsDir() { return path, "" diff --git a/file_filter_test.go b/file_filter_test.go index 53dc4d6..4b7e214 100644 --- a/file_filter_test.go +++ b/file_filter_test.go @@ -5,6 +5,23 @@ import ( "testing" ) +func TestFileFilters_name(t *testing.T) { + tests := []struct { + data FileFilters + want string + }{ + {FileFilters{{"", []string{`*.png`}}}, "*.png"}, + {FileFilters{{"", []string{`*.png`, `*.jpg`}}}, "*.png *.jpg"}, + {FileFilters{{"Image files", []string{`*.png`, `*.jpg`}}}, "Image files"}, + } + for i, tt := range tests { + tt.data.name() + if got := tt.data[0].Name; got != tt.want { + t.Errorf("FileFilters.name[%d] = %q, want %q", i, got, tt.want) + } + } +} + func TestFileFilters_simplify(t *testing.T) { tests := []struct { data FileFilters @@ -54,10 +71,11 @@ func TestFileFilters_types(t *testing.T) { {FileFilters{{"", []string{`*.[\[]PNG`}}}, []string{"", "[PNG"}}, {FileFilters{{"", []string{`*.[\]]PNG`}}}, []string{"", "]PNG"}}, {FileFilters{{"", []string{`public.png`}}}, []string{"", "public.png"}}, + {FileFilters{{"", []string{`-public-.png`}}}, []string{"", "png"}}, } for i, tt := range tests { if got := tt.data.types(); !reflect.DeepEqual(got, tt.want) { - t.Fatalf("FileFilters.types[%d] = %v, want %v", i, got, tt.want) + t.Errorf("FileFilters.types[%d] = %v, want %v", i, got, tt.want) } } } diff --git a/file_util_test.go b/file_util_test.go new file mode 100644 index 0000000..ecdd451 --- /dev/null +++ b/file_util_test.go @@ -0,0 +1,37 @@ +package zenity + +import ( + "os" + "path/filepath" + "testing" +) + +func Test_splitDirAndName(t *testing.T) { + tempDir := os.TempDir() + tests := []struct { + path string + wantDir string + wantName string + }{ + // filepath.Split test cases + {"a/b", "a/", "b"}, + {"a/b/", "a/b/", ""}, + {"a/", "a/", ""}, + {"a", "", "a"}, + {"/", "/", ""}, + // we split differently if we know it's a directory + {tempDir, tempDir, ""}, + {filepath.Clean(tempDir), filepath.Clean(tempDir), ""}, + {filepath.Join(tempDir, "a"), filepath.Clean(tempDir) + string(filepath.Separator), "a"}, + } + + for i, tt := range tests { + gotDir, gotName := splitDirAndName(tt.path) + if gotDir != tt.wantDir { + t.Errorf("splitDirAndName[%d].dir = %q, want %q", i, gotDir, tt.wantDir) + } + if gotName != tt.wantName { + t.Errorf("splitDirAndName[%d].name = %q, want %q", i, gotName, tt.wantName) + } + } +} diff --git a/internal/zenutil/color.go b/internal/zenutil/color.go index 0cd82a7..8388270 100644 --- a/internal/zenutil/color.go +++ b/internal/zenutil/color.go @@ -47,8 +47,11 @@ func ParseColor(s string) color.Color { } } - c, _ := colornames.Map[strings.ToLower(s)] - return c + c, ok := colornames.Map[strings.ToLower(s)] + if ok { + return c + } + return nil } // UnparseColor is internal. diff --git a/internal/zenutil/color_test.go b/internal/zenutil/color_test.go index 6c1210a..6993630 100644 --- a/internal/zenutil/color_test.go +++ b/internal/zenutil/color_test.go @@ -7,7 +7,7 @@ import ( "golang.org/x/image/colornames" ) -func TestColor(t *testing.T) { +func TestColor_names(t *testing.T) { tests := []string{ "chocolate", "lime", @@ -17,20 +17,73 @@ func TestColor(t *testing.T) { "salmon", "tomato", } - eq := func(c1, c2 color.Color) bool { - r1, g1, b1, a1 := c1.RGBA() - r2, g2, b2, a2 := c2.RGBA() - return r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2 - } for _, test := range tests { c1 := colornames.Map[test] c2 := ParseColor(test) - c3 := ParseColor(UnparseColor(c2)) - if !eq(c1, c2) { + c3 := ParseColor(UnparseColor(c1)) + if !colorEq(c1, c2) { t.Errorf("ParseColor(%s) = %v, want %v", test, c2, c1) } - if !eq(c1, c3) { - t.Errorf("ParseColor(UnparseColor(%s)) = %v, want %v", test, c3, c1) + if !colorEq(c1, c3) { + t.Errorf("ParseColor(UnparseColor(%v)) = %v, want %v", c1, c3, c1) } } } + +func TestColor_colors(t *testing.T) { + tests := []color.Color{ + color.Black, + color.White, + color.Opaque, + color.Transparent, + } + for _, test := range tests { + c := ParseColor(UnparseColor(test)) + if !colorEq(c, test) { + t.Errorf("ParseColor(UnparseColor(%v)) = %v, want %v", test, c, test) + } + } +} + +func TestColor_strings(t *testing.T) { + tests := []struct { + data string + want color.Color + }{ + {"#000", color.Black}, + {"#000f", color.Black}, + {"#000000", color.Black}, + {"#000000ff", color.Black}, + {"#fff", color.White}, + {"#ffff", color.White}, + {"#ffffff", color.White}, + {"#ffffffff", color.White}, + {"#FFF", color.Opaque}, + {"#FFFF", color.Opaque}, + {"#FFFFFF", color.Opaque}, + {"#FFFFFFFF", color.Opaque}, + {"#0000", color.Transparent}, + {"#00000000", color.Transparent}, + {"#8888", color.NRGBA{0x88, 0x88, 0x88, 0x88}}, + {"#80808080", color.NRGBA{0x80, 0x80, 0x80, 0x80}}, + {"rgb(128,128,128)", color.NRGBA{0x80, 0x80, 0x80, 0xff}}, + {"rgba(128,128,128,0.5)", color.NRGBA{0x80, 0x80, 0x80, 0x80}}, + {"rgba(128,128,128,1.0)", color.NRGBA{0x80, 0x80, 0x80, 0xff}}, + {"not a color", nil}, + } + for _, test := range tests { + c := ParseColor(test.data) + if !colorEq(c, test.want) { + t.Errorf("ParseColor(%s) = %v, want %v", test.data, c, test.want) + } + } +} + +func colorEq(c1, c2 color.Color) bool { + if c1 == nil || c2 == nil { + return c1 == c2 + } + r1, g1, b1, a1 := c1.RGBA() + r2, g2, b2, a2 := c2.RGBA() + return r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2 +} diff --git a/internal/zenutil/run_unix_test.go b/internal/zenutil/run_unix_test.go new file mode 100644 index 0000000..fda5c0c --- /dev/null +++ b/internal/zenutil/run_unix_test.go @@ -0,0 +1,53 @@ +// +build !windows,!darwin + +package zenutil + +import ( + "context" + "errors" + "os" + "os/exec" + "testing" +) + +func TestRun(t *testing.T) { + _, err := Run(nil, []string{"--version"}) + if err, skip := skip(err); skip { + t.Skip("skipping:", err) + } + if err != nil { + t.Fatal(err) + } +} + +func TestRun_context(t *testing.T) { + _, err := Run(context.TODO(), []string{"--version"}) + if err, skip := skip(err); skip { + t.Skip("skipping:", err) + } + if err != nil { + t.Fatal(err) + } +} + +func TestRunProgress(t *testing.T) { + _, err := RunProgress(nil, 100, nil, []string{"--version"}) + if err, skip := skip(err); skip { + t.Skip("skipping:", err) + } + if err != nil { + t.Fatal(err) + } +} + +func skip(err error) (error, bool) { + if _, ok := err.(*exec.Error); ok { + // zenity/osascript/etc were not found in path + return err, true + } + if err != nil && os.Getenv("DISPLAY") == "" { + // no display + return errors.New("no display"), true + } + return nil, false +} diff --git a/internal/zenutil/unescape_test.go b/internal/zenutil/unescape_test.go index 12a00f7..3b32290 100644 --- a/internal/zenutil/unescape_test.go +++ b/internal/zenutil/unescape_test.go @@ -10,6 +10,11 @@ func TestUnescape(t *testing.T) { {`abc`, "abc"}, {`ab\c`, "abc"}, {`a\bc`, "a\bc"}, + {`abc\f`, "abc\f"}, + {`abc\n`, "abc\n"}, + {`abc\r`, "abc\r"}, + {`abc\t`, "abc\t"}, + {`abc\v`, "abc\v"}, {`a\1c`, "a\001c"}, {`a\12c`, "a\012c"}, {`a\123c`, "a\123c"}, diff --git a/pwd_test.go b/pwd_test.go index c385984..08d3d4e 100644 --- a/pwd_test.go +++ b/pwd_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "os" + "runtime" "testing" "time" @@ -16,6 +17,13 @@ func ExamplePassword() { // Output: } +func ExamplePassword_username() { + zenity.Password( + zenity.Title("Type your username and password"), + zenity.Username()) + // Output: +} + func TestPassword_timeout(t *testing.T) { defer goleak.VerifyNone(t) ctx, cancel := context.WithTimeout(context.Background(), time.Second/10) @@ -43,3 +51,23 @@ func TestPassword_cancel(t *testing.T) { t.Error("was not canceled:", err) } } + +func TestPassword_username(t *testing.T) { + defer goleak.VerifyNone(t) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + _, _, err := zenity.Password(zenity.Context(ctx), zenity.Username()) + if err, skip := skip(err); skip { + t.Skip("skipping:", err) + } + if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { + if !errors.Is(err, zenity.ErrUnsupported) { + t.Error("was not unsupported:", err) + } + } else { + if !errors.Is(err, context.Canceled) { + t.Error("was not canceled:", err) + } + } +}