package main
import (
"path/filepath"
"testing"
)
// todoRoot is a convenience helper for these tests.
func todoRoot(root string) []todoScanRoot {
return []todoScanRoot{{root: root}}
}
func TestCheckTODOs_GoFileFlagsTODO(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "service.go", `package service
// TODO: implement retry logic
func Retry() {}
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) == 0 {
t.Fatal("expected TODO violation in .go file")
}
if vs[0].file != "service.go" {
t.Fatalf("expected file 'service.go', got %q", vs[0].file)
}
if vs[0].line != 3 {
t.Fatalf("expected line 3, got %d", vs[0].line)
}
}
func TestCheckTODOs_TSFileFlagsTODO(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "utils.ts", `export function foo() {
// TODO: add error handling
return null
}
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) == 0 {
t.Fatal("expected TODO violation in .ts file")
}
}
func TestCheckTODOs_TSXFileFlagsTODO(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "component.tsx", `export function Comp() {
// TODO(alex): fix this
return
}
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) == 0 {
t.Fatal("expected TODO violation in .tsx file")
}
}
func TestCheckTODOs_HTMLFileFlagsTODO(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "page.html", `
Hello
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) == 0 {
t.Fatal("expected TODO violation in .html file")
}
}
func TestCheckTODOs_TemplFileFlagsTODO(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "block.templ", `package block
templ Hero() {
// TODO: make dynamic
Static
}
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) == 0 {
t.Fatal("expected TODO violation in .templ file")
}
}
func TestCheckTODOs_SQLFileFlagsTODO(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "queries.sql", `-- TODO: add index
SELECT * FROM users;
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) == 0 {
t.Fatal("expected TODO violation in .sql file")
}
}
func TestCheckTODOs_ScssFileFlagsTODO(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "styles.scss", `// TODO: update colours
.btn { color: red; }
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) == 0 {
t.Fatal("expected TODO violation in .scss file")
}
}
func TestCheckTODOs_CaseInsensitive(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "service.go", `package service
// todo: lowercase todo should also match
func f() {}
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) == 0 {
t.Fatal("expected TODO violation for lowercase 'todo'")
}
}
func TestCheckTODOs_WithParens(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "service.go", `package service
// TODO(alex): with author attribution
func f() {}
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) == 0 {
t.Fatal("expected TODO violation for 'TODO(alex)' form")
}
}
func TestCheckTODOs_CleanFile_NoViolation(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "clean.go", `package service
// This is a regular comment about the implementation.
func DoWork() error {
return nil
}
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) != 0 {
t.Fatalf("expected no violations for clean file, got %d", len(vs))
}
}
func TestCheckTODOs_TestGoFile_Skipped(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "service_test.go", `package service
// TODO: add more test cases
func TestFoo(t *testing.T) {}
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) != 0 {
t.Fatalf("expected _test.go files to be skipped, got %d violations", len(vs))
}
}
func TestCheckTODOs_TestTSFile_Skipped(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "utils.test.ts", `// TODO: more tests
describe("foo", () => {})
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) != 0 {
t.Fatalf("expected .test.ts files to be skipped, got %d violations", len(vs))
}
}
func TestCheckTODOs_TestTSXFile_Skipped(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "comp.test.tsx", `// TODO: add render tests
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) != 0 {
t.Fatalf("expected .test.tsx files to be skipped, got %d violations", len(vs))
}
}
func TestCheckTODOs_SpecTSFile_Skipped(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "utils.spec.ts", `// TODO: cover edge cases
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) != 0 {
t.Fatalf("expected .spec.ts files to be skipped, got %d violations", len(vs))
}
}
func TestCheckTODOs_NonCodeFile_Skipped(t *testing.T) {
// .md files are not in todoFileExts — should be skipped
dir := t.TempDir()
writeFile(t, dir, "README.md", `# Project
TODO: finish the docs
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) != 0 {
t.Fatalf("expected .md files to be skipped, got %d violations", len(vs))
}
}
func TestCheckTODOs_NodeModulesSkipped(t *testing.T) {
dir := t.TempDir()
nmDir := filepath.Join(dir, "node_modules", "some-lib")
writeTestFile(t, filepath.Join(nmDir, "index.ts"), `// TODO: vendor file — should be skipped
`, 0644)
vs := checkTODOs(todoRoot(dir))
if len(vs) != 0 {
t.Fatalf("expected node_modules to be skipped, got %d violations", len(vs))
}
}
func TestCheckTODOs_VendorDirSkipped(t *testing.T) {
dir := t.TempDir()
vendorDir := filepath.Join(dir, "vendor", "somelib")
writeTestFile(t, filepath.Join(vendorDir, "lib.go"), `package lib
// TODO: vendor should be skipped
`, 0644)
vs := checkTODOs(todoRoot(dir))
if len(vs) != 0 {
t.Fatalf("expected vendor/ to be skipped, got %d violations", len(vs))
}
}
func TestCheckTODOs_EmptyRootSkipped(t *testing.T) {
// An empty root string should be skipped without panicking
vs := checkTODOs([]todoScanRoot{{root: ""}})
if len(vs) != 0 {
t.Fatalf("expected no violations for empty root, got %d", len(vs))
}
}
func TestCheckTODOs_DisplayPrefix(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "service.go", `package service
// TODO: remove this
func f() {}
`)
vs := checkTODOs([]todoScanRoot{{root: dir, displayPrefix: "backend"}})
if len(vs) == 0 {
t.Fatal("expected TODO violation")
}
if vs[0].file != "backend/service.go" {
t.Fatalf("expected prefixed path 'backend/service.go', got %q", vs[0].file)
}
}
func TestCheckTODOs_MultipleFiles_SortedByFileAndLine(t *testing.T) {
dir := t.TempDir()
// b.go has one TODO on line 2
writeFile(t, dir, "b.go", `package p
// TODO: b
func b() {}
`)
// a.go has two TODOs — line 3 and line 5
writeFile(t, dir, "a.go", `package p
func a() {
// TODO: first
x := 1
// TODO: second
_ = x
}
`)
vs := checkTODOs(todoRoot(dir))
if len(vs) != 3 {
t.Fatalf("expected 3 violations, got %d: %#v", len(vs), vs)
}
// Sorted: a.go first (alphabetically before b.go)
if vs[0].file != "a.go" {
t.Fatalf("first violation should be in a.go, got %q", vs[0].file)
}
if vs[0].line > vs[1].line {
t.Fatalf("violations in same file should be sorted by line")
}
if vs[2].file != "b.go" {
t.Fatalf("last violation should be in b.go, got %q", vs[2].file)
}
}