package main import ( "testing" ignore "github.com/sabhiram/go-gitignore" ) func TestGitignore_SimplePatterns(t *testing.T) { tests := []struct { name string patterns []string path string expected bool }{ {"exact file", []string{"foo.txt"}, "foo.txt", true}, {"exact file no match", []string{"foo.txt"}, "bar.txt", false}, {"wildcard extension", []string{"*.log"}, "debug.log", true}, {"wildcard no match", []string{"*.log"}, "debug.txt", false}, {"directory pattern with content", []string{"build/"}, "build/output.o", true}, {"leading slash anchored", []string{"/foo"}, "foo", true}, {"leading slash anchored no match", []string{"/foo"}, "bar/foo", false}, {"comment line ignored", []string{"# this is a comment"}, "foo", false}, {"empty line ignored", []string{""}, "foo", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gi := ignore.CompileIgnoreLines(tt.patterns...) result := gi.MatchesPath(tt.path) if result != tt.expected { t.Errorf("MatchesPath(%q) = %v, want %v", tt.path, result, tt.expected) } }) } } func TestGitignore_Negation(t *testing.T) { gi := ignore.CompileIgnoreLines("*.log", "!important.log") tests := []struct { path string expected bool }{ {"debug.log", true}, {"important.log", false}, {"test.log", true}, {"readme.txt", false}, } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { result := gi.MatchesPath(tt.path) if result != tt.expected { t.Errorf("MatchesPath(%q) = %v, want %v", tt.path, result, tt.expected) } }) } } func TestGitignore_DoubleStar(t *testing.T) { tests := []struct { name string patterns []string path string expected bool }{ {"double star prefix", []string{"**/temp"}, "temp", true}, {"double star prefix nested", []string{"**/temp"}, "foo/temp", true}, {"double star prefix deep nested", []string{"**/temp"}, "foo/bar/baz/temp", true}, {"double star suffix", []string{"temp/**"}, "temp/file.txt", true}, {"double star suffix deep", []string{"temp/**"}, "temp/sub/file.txt", true}, {"double star middle", []string{"foo/**/bar"}, "foo/bar", true}, {"double star middle nested", []string{"foo/**/bar"}, "foo/x/y/bar", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gi := ignore.CompileIgnoreLines(tt.patterns...) result := gi.MatchesPath(tt.path) if result != tt.expected { t.Errorf("MatchesPath(%q) = %v, want %v", tt.path, result, tt.expected) } }) } } func TestGitignore_QuestionMark(t *testing.T) { // NOTE: go-gitignore does not support ? wildcard // This test documents the limitation gi := ignore.CompileIgnoreLines("?.txt") result := gi.MatchesPath("a.txt") if result { t.Log("go-gitignore now supports ? wildcard - update docs") } else { t.Log("go-gitignore does NOT support ? wildcard - known limitation") } } func TestGitignore_BracketRange(t *testing.T) { gi := ignore.CompileIgnoreLines("[abc].txt") tests := []struct { path string expected bool }{ {"a.txt", true}, {"b.txt", true}, {"c.txt", true}, {"d.txt", false}, } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { result := gi.MatchesPath(tt.path) if result != tt.expected { t.Errorf("MatchesPath(%q) = %v, want %v", tt.path, result, tt.expected) } }) } } func TestGitignore_NestedGitignore(t *testing.T) { rules := map[string][]string{ ".gitignore": {"*.log"}, "subdir/.gitignore": {"*.tmp"}, "subdir/deep/.gitignore": {"*.bak"}, } gi := ignore.CompileIgnoreLines(rules[".gitignore"]...) tests := []struct { name string path string expected bool }{ {"root log", "debug.log", true}, {"root txt", "readme.txt", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := gi.MatchesPath(tt.path) if result != tt.expected { t.Errorf("MatchesPath(%q) = %v, want %v", tt.path, result, tt.expected) } }) } // Nested gitignore needs separate handling per directory subGi := ignore.CompileIgnoreLines(rules["subdir/.gitignore"]...) if subGi.MatchesPath("file.tmp") != true { t.Error("subdir gitignore should match *.tmp") } } func TestGitignore_CombinedPatterns(t *testing.T) { gi := ignore.CompileIgnoreLines( "*.o", "*.a", "build/", "!lib/special.o", ) tests := []struct { path string expected bool }{ {"foo.o", true}, {"lib.a", true}, {"build/output.o", true}, {"lib/special.o", false}, {"src/main.c", false}, } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { result := gi.MatchesPath(tt.path) if result != tt.expected { t.Errorf("MatchesPath(%q) = %v, want %v", tt.path, result, tt.expected) } }) } } func TestGitignore_HashInFilename(t *testing.T) { gi := ignore.CompileIgnoreLines("*.txt") tests := []struct { path string expected bool }{ {"file#1.txt", true}, {"file#2.txt", true}, {"readme.md", false}, } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { result := gi.MatchesPath(tt.path) if result != tt.expected { t.Errorf("MatchesPath(%q) = %v, want %v", tt.path, result, tt.expected) } }) } } func TestGitignore_TrailingSpace(t *testing.T) { // NOTE: go-gitignore does not handle trailing spaces in patterns // This test documents the limitation gi := ignore.CompileIgnoreLines("foo.txt ") result := gi.MatchesPath("foo.txt") if result { t.Log("go-gitignore handles trailing spaces correctly") } else { t.Log("go-gitignore does NOT handle trailing spaces - known limitation") } }