check-safety/tailwind_test.go
Alex Dunmow cd88c808b0 initial: standalone check-safety module hoisted from CMS
Static safety/lint runner for the BlockNinja codebase. ~25 invariant
checks across Go and frontend sources. Was at git.dev.alexdunmow.com:block/ninja
in backend/cmd/check-safety/ until the 2026-06-06 consolidation moved
the BlockNinja repos under a shared ~/src/blockninja/ parent.

This repo is the standalone extraction:
- Own go.mod (git.dev.alexdunmow.com/block/check-safety, go 1.26.4)
- Vendored internal/{helpers,theme} from CMS (Go's internal/ rule
  blocks cross-module imports; vendoring is the workaround)
- CLI contract unchanged: `check-safety <target-dir> [--flags]`
- CMS Makefile shells into ../check-safety for safety-check /
  install-safety-checker targets

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 13:04:02 +08:00

223 lines
5.4 KiB
Go

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)
}
}