package main import ( "os" "path/filepath" "testing" ) func TestCheckPostCSSConfig_BarePlugin(t *testing.T) { dir := t.TempDir() configPath := filepath.Join(dir, "postcss.config.js") if err := os.WriteFile(configPath, []byte(`export default { plugins: { tailwindcss: {}, autoprefixer: {}, }, } `), 0644); err != nil { t.Fatal(err) } violations := checkPostCSSConfig(dir) if len(violations) == 0 { t.Fatal("expected violations for bare tailwindcss plugin") } foundPlugin := false for _, v := range violations { if v.rule == "postcss-v4-plugin" { foundPlugin = true } } if !foundPlugin { t.Fatalf("expected postcss-v4-plugin violation, got %v", violations) } } func TestCheckPostCSSConfig_V4Plugin_OK(t *testing.T) { dir := t.TempDir() configPath := filepath.Join(dir, "postcss.config.js") if err := os.WriteFile(configPath, []byte(`export default { plugins: { '@tailwindcss/postcss': {}, }, } `), 0644); err != nil { t.Fatal(err) } violations := checkPostCSSConfig(dir) if len(violations) > 0 { t.Fatalf("unexpected violations for v4 postcss config: %v", violations) } } func TestCheckPostCSSConfig_AutoprefixerWithV4(t *testing.T) { dir := t.TempDir() configPath := filepath.Join(dir, "postcss.config.js") if err := os.WriteFile(configPath, []byte(`export default { plugins: { '@tailwindcss/postcss': {}, autoprefixer: {}, }, } `), 0644); err != nil { t.Fatal(err) } violations := checkPostCSSConfig(dir) foundAutoprefixer := false for _, v := range violations { if v.rule == "postcss-no-autoprefixer" { foundAutoprefixer = true } } if !foundAutoprefixer { t.Fatalf("expected postcss-no-autoprefixer violation, got %v", violations) } } func TestCheckCSSDirectives_V3Syntax(t *testing.T) { dir := t.TempDir() cssPath := filepath.Join(dir, "index.css") if err := os.WriteFile(cssPath, []byte(`@tailwind base; @tailwind components; @tailwind utilities; `), 0644); err != nil { t.Fatal(err) } violations := checkCSSDirectives(dir, dir) if len(violations) != 3 { t.Fatalf("expected 3 violations for @tailwind directives, got %d: %v", len(violations), violations) } for _, v := range violations { if v.rule != "css-v4-import" { t.Fatalf("expected css-v4-import rule, got %s", v.rule) } } } func TestCheckCSSDirectives_V4Syntax_OK(t *testing.T) { dir := t.TempDir() cssPath := filepath.Join(dir, "index.css") if err := os.WriteFile(cssPath, []byte(`@import "tailwindcss"; @config "../tailwind.config.ts"; `), 0644); err != nil { t.Fatal(err) } parentDir := filepath.Dir(dir) configPath := filepath.Join(parentDir, "tailwind.config.ts") if err := os.WriteFile(configPath, []byte(`export default {}`), 0644); err != nil { t.Fatal(err) } violations := checkCSSDirectives(dir, parentDir) if len(violations) > 0 { t.Fatalf("unexpected violations for v4 CSS: %v", violations) } } func TestCheckCSSDirectives_MissingConfig(t *testing.T) { dir := t.TempDir() srcDir := filepath.Join(dir, "src") if err := os.MkdirAll(srcDir, 0755); err != nil { t.Fatal(err) } cssPath := filepath.Join(srcDir, "index.css") if err := os.WriteFile(cssPath, []byte(`@import "tailwindcss"; `), 0644); err != nil { t.Fatal(err) } configPath := filepath.Join(dir, "tailwind.config.ts") if err := os.WriteFile(configPath, []byte(`export default {}`), 0644); err != nil { t.Fatal(err) } violations := checkCSSDirectives(srcDir, dir) foundMissing := false for _, v := range violations { if v.rule == "css-missing-config" { foundMissing = true } } if !foundMissing { t.Fatalf("expected css-missing-config violation, got %v", violations) } } func TestCheckPostCSSConfig_PostCSSRC_JSON(t *testing.T) { dir := t.TempDir() configPath := filepath.Join(dir, ".postcssrc.json") if err := os.WriteFile(configPath, []byte(`{ "plugins": { "tailwindcss": {}, "autoprefixer": {} } } `), 0644); err != nil { t.Fatal(err) } violations := checkPostCSSConfig(dir) foundPlugin := false for _, v := range violations { if v.rule == "postcss-v4-plugin" { foundPlugin = true } } if !foundPlugin { t.Fatalf("expected postcss-v4-plugin violation for .postcssrc.json, got %v", violations) } } func TestCheckCSSDirectives_PlainCSS_NoViolation(t *testing.T) { dir := t.TempDir() srcDir := filepath.Join(dir, "src") if err := os.MkdirAll(srcDir, 0755); err != nil { t.Fatal(err) } cssPath := filepath.Join(srcDir, "components.css") if err := os.WriteFile(cssPath, []byte(`.btn { padding: 8px 16px; } .card { border: 1px solid #ccc; } `), 0644); err != nil { t.Fatal(err) } configPath := filepath.Join(dir, "tailwind.config.ts") if err := os.WriteFile(configPath, []byte(`export default {}`), 0644); err != nil { t.Fatal(err) } violations := checkCSSDirectives(srcDir, dir) if len(violations) > 0 { t.Fatalf("plain CSS file should not trigger violations, got %v", violations) } } func TestCheckCSSDirectives_NodeModulesSkipped(t *testing.T) { dir := t.TempDir() nmDir := filepath.Join(dir, "node_modules", "some-lib") if err := os.MkdirAll(nmDir, 0755); err != nil { t.Fatal(err) } cssPath := filepath.Join(nmDir, "style.css") if err := os.WriteFile(cssPath, []byte(`@tailwind base; @tailwind components; @tailwind utilities; `), 0644); err != nil { t.Fatal(err) } violations := checkCSSDirectives(dir, dir) if len(violations) > 0 { t.Fatalf("node_modules CSS should be skipped, got %v", violations) } }