aboutsummaryrefslogtreecommitdiff
path: root/languages/typescript/tests/validate-typescript.bats
blob: c5da5d456f85619b5be1e1965924196e8506b034 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/env bats
#
# Tests for languages/typescript/claude/hooks/validate-typescript.sh — the
# PostToolUse hook that syntax-checks edited TS/JS files and blocks on a
# violation.
#
# The hook reads tool-call JSON on stdin and extracts the file path, so each
# test pipes a JSON payload naming a real file it wrote into a temp dir.
#
# The syntax gate needs node, so those tests skip when node is absent. Full
# type checking is deliberately out of scope for the hook (it needs the whole
# project graph), so a type error that is syntactically valid must pass.

HOOK="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)/claude/hooks/validate-typescript.sh"

setup() {
  TEST_DIR="$(mktemp -d -t validate-ts-bats.XXXXXX)"
}

teardown() {
  rm -rf "$TEST_DIR"
}

payload() {
  printf '{"tool_input": {"file_path": "%s"}}' "$1"
}

# ---- Normal ----------------------------------------------------------

@test "validate-typescript: a clean .ts file passes silently (exit 0)" {
  command -v node >/dev/null 2>&1 || skip "node not installed"
  printf 'export function f(x: number): number {\n  return x + 1;\n}\n' > "$TEST_DIR/clean.ts"
  run bash "$HOOK" <<< "$(payload "$TEST_DIR/clean.ts")"
  [ "$status" -eq 0 ]
  [ -z "$output" ]
}

@test "validate-typescript: a clean .js file passes silently (exit 0)" {
  command -v node >/dev/null 2>&1 || skip "node not installed"
  printf 'export function f(x) {\n  return x + 1;\n}\n' > "$TEST_DIR/clean.js"
  run bash "$HOOK" <<< "$(payload "$TEST_DIR/clean.js")"
  [ "$status" -eq 0 ]
}

@test "validate-typescript: a .tsx file is validated too" {
  command -v node >/dev/null 2>&1 || skip "node not installed"
  printf 'export const A = 1;\n' > "$TEST_DIR/clean.tsx"
  run bash "$HOOK" <<< "$(payload "$TEST_DIR/clean.tsx")"
  [ "$status" -eq 0 ]
}

# ---- Error -----------------------------------------------------------

@test "validate-typescript: a syntax error blocks (exit 2, names the failure)" {
  command -v node >/dev/null 2>&1 || skip "node not installed"
  printf 'export function f( {\n  return 1;\n}\n' > "$TEST_DIR/bad.ts"
  run bash "$HOOK" <<< "$(payload "$TEST_DIR/bad.ts")"
  [ "$status" -eq 2 ]
  [[ "$output" == *"SYNTAX"* ]]
}

@test "validate-typescript: the block payload is valid JSON carrying the context" {
  command -v node >/dev/null 2>&1 || skip "node not installed"
  printf 'const x = ;\n' > "$TEST_DIR/bad.js"
  run bash "$HOOK" <<< "$(payload "$TEST_DIR/bad.js")"
  [ "$status" -eq 2 ]
  echo "$output" | head -1 | jq -e '.hookSpecificOutput.hookEventName == "PostToolUse"'
}

# ---- Boundary --------------------------------------------------------

@test "validate-typescript: a type error that parses is NOT blocked (out of scope)" {
  command -v node >/dev/null 2>&1 || skip "node not installed"
  # Assigning a string to a number is a type error, not a syntax error. The
  # hook checks parseability only; tsc over the project graph owns this.
  printf 'const n: number = "not a number";\nexport { n };\n' > "$TEST_DIR/typeerr.ts"
  run bash "$HOOK" <<< "$(payload "$TEST_DIR/typeerr.ts")"
  [ "$status" -eq 0 ]
}

@test "validate-typescript: TS-only syntax in a .ts file parses (not read as JS)" {
  command -v node >/dev/null 2>&1 || skip "node not installed"
  # Interfaces and type annotations are invalid JS. Stripping types must happen
  # before the parse, or every real .ts file would be reported as broken.
  printf 'interface P { a: string }\nexport const p: P = { a: "x" };\n' > "$TEST_DIR/types.ts"
  run bash "$HOOK" <<< "$(payload "$TEST_DIR/types.ts")"
  [ "$status" -eq 0 ]
}

@test "validate-typescript: a non-TS/JS file is ignored (exit 0)" {
  printf 'not javascript at all (((\n' > "$TEST_DIR/notes.txt"
  run bash "$HOOK" <<< "$(payload "$TEST_DIR/notes.txt")"
  [ "$status" -eq 0 ]
}

@test "validate-typescript: a .json file is ignored (exit 0)" {
  printf '{"a": 1}\n' > "$TEST_DIR/data.json"
  run bash "$HOOK" <<< "$(payload "$TEST_DIR/data.json")"
  [ "$status" -eq 0 ]
}

@test "validate-typescript: empty file_path is a no-op (exit 0)" {
  run bash "$HOOK" <<< '{"tool_input": {}}'
  [ "$status" -eq 0 ]
}

@test "validate-typescript: a missing file is a no-op (exit 0)" {
  run bash "$HOOK" <<< "$(payload "$TEST_DIR/does-not-exist.ts")"
  [ "$status" -eq 0 ]
}

@test "validate-typescript: an empty .ts file passes" {
  command -v node >/dev/null 2>&1 || skip "node not installed"
  : > "$TEST_DIR/empty.ts"
  run bash "$HOOK" <<< "$(payload "$TEST_DIR/empty.ts")"
  [ "$status" -eq 0 ]
}

@test "validate-typescript: a file in a dotted parent dir is still matched" {
  command -v node >/dev/null 2>&1 || skip "node not installed"
  mkdir -p "$TEST_DIR/my.project"
  printf 'const x = ;\n' > "$TEST_DIR/my.project/bad.ts"
  run bash "$HOOK" <<< "$(payload "$TEST_DIR/my.project/bad.ts")"
  [ "$status" -eq 2 ]
}