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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
;;; test-prog-json--json-format-buffer.el --- Tests for cj/json-format-buffer -*- lexical-binding: t -*-
;;; Commentary:
;; Tests for the cj/json-format-buffer function in prog-json.el.
;; Tests both the jq path and the built-in fallback path.
;;; Code:
(require 'ert)
(require 'cl-lib)
(require 'json)
(require 'prog-json)
;;; argv path — process invocation (stubbed, no shell)
(ert-deftest test-prog-json--json-format-buffer-invokes-jq-argv ()
"Normal: with jq present, the formatter calls jq via argv, no shell."
(let (program args)
(cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/jq"))
((symbol-function 'call-process-region)
(lambda (_start _end prog &rest rest)
(setq program prog
args (nthcdr 3 rest))
(with-current-buffer (nth 1 rest) (insert "{}"))
0)))
(with-temp-buffer
(insert "{\"a\":1}")
(cj/json-format-buffer)))
(should (equal "jq" program))
(should (equal '("--sort-keys" ".") args))))
(ert-deftest test-prog-json--json-format-buffer-no-clobber-on-failure ()
"Error: a non-zero jq exit leaves the buffer untouched and signals an error."
(cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/jq"))
((symbol-function 'call-process-region)
(lambda (_start _end _prog _delete buffer &rest _)
(with-current-buffer buffer (insert "jq: parse error"))
1)))
(with-temp-buffer
(insert "{not valid json}")
(should-error (cj/json-format-buffer) :type 'user-error)
(should (string= (buffer-string) "{not valid json}")))))
;;; Normal Cases — jq path
(ert-deftest test-prog-json--json-format-buffer-normal-formats-object ()
"Compact JSON object is pretty-printed with sorted keys."
(with-temp-buffer
(insert "{\"zebra\":1,\"alpha\":2}")
(cj/json-format-buffer)
(should (string= (string-trim (buffer-string))
"{\n \"alpha\": 2,\n \"zebra\": 1\n}"))))
(ert-deftest test-prog-json--json-format-buffer-normal-formats-array ()
"Compact JSON array is pretty-printed."
(with-temp-buffer
(insert "[1,2,3]")
(cj/json-format-buffer)
(should (string= (string-trim (buffer-string))
"[\n 1,\n 2,\n 3\n]"))))
(ert-deftest test-prog-json--json-format-buffer-normal-nested ()
"Nested JSON is pretty-printed with sorted keys at all levels."
(with-temp-buffer
(insert "{\"b\":{\"d\":1,\"c\":2},\"a\":3}")
(cj/json-format-buffer)
(should (string-match-p "\"a\": 3" (buffer-string)))
(should (string-match-p "\"c\": 2" (buffer-string)))
;; "a" should appear before "b" (sorted)
(should (< (string-match "\"a\"" (buffer-string))
(string-match "\"b\"" (buffer-string))))))
(ert-deftest test-prog-json--json-format-buffer-normal-already-formatted ()
"Already-formatted JSON is unchanged."
(let ((formatted "{\n \"alpha\": 1,\n \"beta\": 2\n}\n"))
(with-temp-buffer
(insert formatted)
(cj/json-format-buffer)
(should (string= (buffer-string) formatted)))))
;;; Boundary Cases
(ert-deftest test-prog-json--json-format-buffer-boundary-empty-object ()
"Empty JSON object formats cleanly."
(with-temp-buffer
(insert "{}")
(cj/json-format-buffer)
(should (string= (string-trim (buffer-string)) "{}"))))
(ert-deftest test-prog-json--json-format-buffer-boundary-empty-array ()
"Empty JSON array formats cleanly."
(with-temp-buffer
(insert "[]")
(cj/json-format-buffer)
(should (string= (string-trim (buffer-string)) "[]"))))
(ert-deftest test-prog-json--json-format-buffer-boundary-scalar-string ()
"Bare JSON string scalar formats without error."
(with-temp-buffer
(insert "\"hello\"")
(cj/json-format-buffer)
(should (string= (string-trim (buffer-string)) "\"hello\""))))
(ert-deftest test-prog-json--json-format-buffer-boundary-unicode ()
"JSON with unicode characters is preserved."
(with-temp-buffer
(insert "{\"emoji\":\"\\u2764\",\"name\":\"café\"}")
(cj/json-format-buffer)
(should (string-match-p "café" (buffer-string)))))
;;; Fallback path — built-in formatter
(ert-deftest test-prog-json--json-format-buffer-fallback-formats-without-jq ()
"Falls back to built-in formatter when jq is not found."
(cl-letf (((symbol-function 'executable-find) (lambda (_) nil)))
(with-temp-buffer
(insert "{\"b\":1,\"a\":2}")
(cj/json-format-buffer)
;; Built-in formatter should pretty-print (key order may vary)
(should (string-match-p "\"a\"" (buffer-string)))
(should (string-match-p "\"b\"" (buffer-string)))
;; Should be multi-line (formatted, not compact)
(should (> (count-lines (point-min) (point-max)) 1)))))
;;; Error Cases
(ert-deftest test-prog-json--json-format-buffer-error-invalid-json ()
"Invalid JSON produces an error, does not silently corrupt buffer."
(with-temp-buffer
(insert "{not valid json}")
(let ((original (buffer-string)))
;; jq will fail on invalid JSON — buffer should not be emptied
(condition-case _err
(cj/json-format-buffer)
(error nil))
;; Buffer should still have content (not wiped)
(should (> (length (buffer-string)) 0)))))
(provide 'test-prog-json--json-format-buffer)
;;; test-prog-json--json-format-buffer.el ends here
|