package main import ( "os" "path/filepath" "testing" ) func TestCheckPluginPagesFlagsQueryClientProvider(t *testing.T) { dir := t.TempDir() file := filepath.Join(dir, "editor.tsx") if err := os.WriteFile(file, []byte(`import { QueryClientProvider } from '@tanstack/react-query' export function Editor() { return
} `), 0644); err != nil { t.Fatalf("write editor.tsx: %v", err) } violations := checkPluginPages([]string{dir}) found := false for _, v := range violations { if v.rule != "no-queryclientprovider-in-plugin" { continue } if v.snippet != "remove QueryClientProvider; BlockNinja core provides it." { t.Fatalf("snippet = %q, want concise remediation", v.snippet) } found = true break } if !found { t.Fatalf("expected QueryClientProvider violation, got %#v", violations) } } func TestCheckFrontendSkipsTestFiles(t *testing.T) { dir := t.TempDir() // A transport-layer regression test legitimately drives createClient over the // real transport to exercise interceptors — it is not shipped UI. The frontend // pattern checks must skip *.test.* / *.spec.* files, matching the convention // the coming-soon and TODO checks already follow. for _, name := range []string{"transport.test.ts", "auth.test.tsx", "util.spec.ts", "view.spec.tsx"} { if err := os.WriteFile(filepath.Join(dir, name), []byte(`import { createClient } from "@connectrpc/connect"; import axios from "axios"; const client = createClient(AuthService, transport); `), 0644); err != nil { t.Fatalf("write %s: %v", name, err) } } violations, _ := checkFrontend(dir) if len(violations) != 0 { t.Fatalf("expected test/spec files to be skipped, got %d violation(s): %#v", len(violations), violations) } } func TestIsBrowserConfirmCall(t *testing.T) { tests := []struct { lines []string idx int want bool }{ {lines: []string{`if (window.confirm("delete?")) {`}, idx: 0, want: true}, {lines: []string{`if (confirm("delete?")) {`}, idx: 0, want: true}, {lines: []string{`const confirmed = await confirm({ title: "Delete" })`}, idx: 0, want: false}, {lines: []string{`confirm({ title: "Delete" }).then((confirmed) => {`}, idx: 0, want: false}, { lines: []string{ `const confirmed = await`, ` confirm({`, ` title: "Delete",`, ` })`, }, idx: 1, want: false, }, { lines: []string{ `confirm({`, ` title: "Leave",`, `}).then((confirmed) => {`, }, idx: 0, want: false, }, } for _, tt := range tests { if got := isBrowserConfirmCall(tt.lines, tt.idx); got != tt.want { t.Fatalf("isBrowserConfirmCall(%#v, %d) = %v, want %v", tt.lines, tt.idx, got, tt.want) } } } func TestIsRawButtonJSXLine(t *testing.T) { tests := []struct { line string want bool }{ {line: ``, want: true}, {line: ``, want: false}, {line: ``, want: false}, {line: `{/*
) } `), 0o644); err != nil { t.Fatal(err) } violations, _ := checkFrontend(dir) var rawButton []frontendViolation for _, v := range violations { if v.rule == "no-raw-button" { rawButton = append(rawButton, v) } } if len(rawButton) != 1 { t.Fatalf("expected exactly one no-raw-button violation, got %d: %#v", len(rawButton), violations) } if rawButton[0].line != 4 { t.Fatalf("violation line = %d, want 4", rawButton[0].line) } } func TestCheckFrontendRawButtonExemptions(t *testing.T) { dir := t.TempDir() if err := os.MkdirAll(filepath.Join(dir, "components", "ui"), 0o755); err != nil { t.Fatal(err) } if err := os.MkdirAll(filepath.Join(dir, "lib"), 0o755); err != nil { t.Fatal(err) } // The Button primitive itself renders a raw `\n"), 0o644); err != nil { t.Fatal(err) } violations, _ := checkFrontend(dir) for _, v := range violations { if v.rule == "no-raw-button" { t.Fatalf("expected no no-raw-button violations, got %#v", v) } } } func TestCheckPluginPagesFlagsRawButton(t *testing.T) { dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, "settings.tsx"), []byte(`export function Settings() { return } `), 0o644); err != nil { t.Fatal(err) } violations := checkPluginPages([]string{dir}) found := false for _, v := range violations { if v.rule == "no-raw-button-in-plugin" { found = true break } } if !found { t.Fatalf("expected no-raw-button-in-plugin violation, got %#v", violations) } } func TestPluginRESTFileAllowed(t *testing.T) { if !pluginRESTFileAllowed("/tmp/repo/backend/internal/plugins/calcomblock/web/settings.tsx") { t.Fatalf("expected documented calcom settings panel to be allowlisted") } if !pluginRESTFileAllowed("/tmp/repo/backend/internal/plugins/calcomblock/web/editor.tsx") { t.Fatalf("expected documented calcom editor panel to be allowlisted") } if pluginRESTFileAllowed("/tmp/repo/backend/internal/plugins/calcomblock/web/dashboard.tsx") { t.Fatalf("unexpected allowlist hit for non-allowlisted plugin file") } }