ido-cr+-test.el 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. ;;; -*- lexical-binding: t -*-
  2. (require 'ido-completing-read+)
  3. (require 'ert)
  4. (require 'cl-lib)
  5. ;; This is a series of macros to facilitate the non-interactive
  6. ;; testing of interactive functions by simulating user input.
  7. (defmacro keyboard-quit-to-error (&rest body)
  8. "Evaluate BODY but signal an error on `keyboard-quit'."
  9. `(condition-case nil
  10. (progn ,@body)
  11. (quit
  12. (error "Caught `keyboard-quit'"))))
  13. (defmacro with-simulated-input (keys &rest body)
  14. "Eval body with KEYS as simulated input.
  15. This macro is intended for testing normally interactive functions
  16. by simulating input. If BODY tries to read more input events than
  17. KEYS provides, `keyboard-quit' is invoked (by means of appending
  18. multple C-g keys to KEYS). This is to ensure that BODY will never
  19. block waiting for input, since this macro is intended for
  20. noninteractive use. As such, BODY should not invoke
  21. `keyboard-quit' under normal operation, and KEYS should not
  22. include C-g, or this macro will interpret it as reading past the
  23. end of input."
  24. ;; It would be better to detect end-of-input by overriding
  25. ;; `read-event' to throw an error, since theoretically C-g could be
  26. ;; rebound to something other than `keyboard-quit'. But apparently
  27. ;; some functions read input directly in C code, and redefining
  28. ;; `read-event' has no effect on those. So the suboptimal solution
  29. ;; is to rely on C-g.
  30. (declare (indent 1))
  31. `(let* ((key-sequence (listify-key-sequence (kbd ,keys)))
  32. (C-g-key-sequence
  33. (listify-key-sequence
  34. ;; We *really* want to trigger `keyboard-quit' if we reach
  35. ;; the end of the input.
  36. (kbd "C-g C-g C-g C-g C-g C-g C-g")))
  37. (unread-command-events
  38. (append key-sequence C-g-key-sequence)))
  39. (when (member (car C-g-key-sequence) key-sequence)
  40. (error "KEYS must not include C-g"))
  41. (condition-case nil
  42. (progn ,@body)
  43. (quit
  44. (error "Reached end of simulated input while evaluating body")))))
  45. (defmacro with-mode (mode arg &rest body)
  46. "Eval (MODE ARG), then body, then restore previous status of MODE.
  47. This will only work on modes that respect the normal conventions
  48. for activation and deactivation."
  49. (declare (indent 2))
  50. `(let* ((orig-status ,mode)
  51. (restore-arg (if orig-status 1 0)))
  52. (unwind-protect
  53. (progn
  54. (,mode ,arg)
  55. ,@body)
  56. (message "Restoring mode %s to %s" ',mode restore-arg)
  57. (,mode restore-arg))))
  58. (defmacro with-ido-cr+-standard-env (&rest body)
  59. "Execute BODY with standard ido-cr+ settings.
  60. All ido-cr+ options will be let-bound to their default values,
  61. and `ido-ubiquitous-mode' will be enabled. The values will all br
  62. restored to what they were previously after BODY exits."
  63. (declare (indent 0))
  64. (let*
  65. ((options
  66. '(ido-cr+-debug-mode
  67. ido-cr+-fallback-function
  68. ido-cr+-max-items
  69. ido-cr+-function-blacklist
  70. ido-cr+-function-whitelist
  71. ido-confirm-unique-completion))
  72. (bindings
  73. (cl-loop for var in options collect
  74. (list var
  75. (list 'quote
  76. (eval (car (get var 'standard-value))))))))
  77. `(with-mode ido-ubiquitous-mode 1
  78. (let ,bindings ,@body))))
  79. (defmacro collection-as-function (collection)
  80. "Return a function equivalent to COLLECTION.
  81. The returned function will work equivalently to COLLECTION when
  82. passed to `all-completions' and `try-completion'."
  83. `(completion-table-dynamic (lambda (string) (all-completions string ,collection))))
  84. (ert-deftest ido-cr+-test-basic ()
  85. :tags '(ido ido-cr+)
  86. "Test that basic ido-cr+ functionality is working."
  87. (with-ido-cr+-standard-env
  88. ;; Verify that pressing RET selects a matching item
  89. (should
  90. (string=
  91. "green"
  92. (with-simulated-input "g RET"
  93. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  94. ;; Verify that pressing RET with multiple matches selects the
  95. ;; first one
  96. (should
  97. (string=
  98. "brown"
  99. (with-simulated-input "b RET"
  100. (ido-completing-read+ "Prompt: " '("brown" "blue" "yellow" "green")))))
  101. ;; Verify that cycling with <left> and <right> works, including
  102. ;; wrap-around
  103. (should
  104. (string=
  105. "blue"
  106. (with-simulated-input "b <right> <right> <right> <right> <left> RET"
  107. (ido-completing-read+ "Prompt: " '("brown" "blue" "yellow" "green")))))
  108. ;; Verify that RET or C-j on empty input returns "" when
  109. ;; REQUIRE-MATCH is t
  110. (should
  111. (string=
  112. ""
  113. (with-simulated-input "RET"
  114. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil t))))
  115. (should
  116. (string=
  117. ""
  118. (with-simulated-input "C-j"
  119. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil t))))
  120. ;; Verify that DEF works, whether or not it is an element of
  121. ;; COLLECTION
  122. (should
  123. (string=
  124. "green"
  125. (with-simulated-input "RET"
  126. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil nil nil "green"))))
  127. (should
  128. (string=
  129. "green"
  130. (with-simulated-input "RET"
  131. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil t nil nil "green"))))
  132. (should
  133. (string=
  134. "brown"
  135. (with-simulated-input "RET"
  136. (ido-completing-read+ "Prompt: " '("blue" "yellow" "brown") nil nil nil nil "brown"))))
  137. (should
  138. (string=
  139. "brown"
  140. (with-simulated-input "RET"
  141. (ido-completing-read+ "Prompt: " '("blue" "yellow" "brown") nil t nil nil "brown"))))
  142. (should
  143. (string=
  144. "brown"
  145. (with-simulated-input "RET"
  146. (ido-completing-read+ "Prompt: " '("blue" "yellow" "brown") nil t nil nil '("brown" "green")))))
  147. ;; Verify that INITIAL-INPUT works
  148. (should
  149. (string=
  150. "green"
  151. (with-simulated-input "RET"
  152. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil "gr"))))
  153. ;; Verify that INITIAL-INPUT and DEF don't interfere with each other
  154. (should
  155. (string=
  156. "green"
  157. (with-simulated-input "RET"
  158. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil "gr" nil "blue"))))
  159. (should
  160. (string=
  161. "blue"
  162. (with-simulated-input "DEL DEL RET"
  163. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil "gr" nil "blue"))))
  164. ;; Verify that ido-cr+ works on function collections
  165. (should
  166. (string=
  167. "green"
  168. (with-simulated-input "g RET"
  169. (ido-completing-read+ "Prompt: " (collection-as-function '("blue" "yellow" "green"))))))))
  170. (ert-deftest ido-cr+-test-maxitems ()
  171. :tags '(ido ido-cr+)
  172. "Test whether the large-collection fallback works."
  173. (with-ido-cr+-standard-env
  174. ;; This should not fall back
  175. (should
  176. (string=
  177. "green"
  178. (with-simulated-input "g RET"
  179. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  180. (let ((ido-cr+-max-items -1))
  181. ;; This should fall back because the collection is too large
  182. (should
  183. (string=
  184. "g"
  185. (with-simulated-input "g RET"
  186. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green"))))))))
  187. (ert-deftest ido-cr+-test-blacklist ()
  188. :tags '(ido ido-cr+)
  189. "Test whether `ido-ubiquitous-function-blacklist' works."
  190. (with-ido-cr+-standard-env
  191. (cl-letf (((symbol-function 'blacklisted-command)
  192. (lambda (arg)
  193. (interactive (list (completing-read "Prompt: " '("blue" "yellow" "green"))))
  194. arg))
  195. ((symbol-function 'blacklisted-function)
  196. (lambda ()
  197. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  198. ((symbol-function 'cmd-that-calls-blacklisted-function)
  199. (lambda ()
  200. (interactive)
  201. (funcall 'blacklisted-function)))
  202. ((symbol-function 'blacklisted-collection)
  203. (collection-as-function '("blue" "yellow" "green"))))
  204. ;; First verify that they work normally before blacklisting them
  205. (should
  206. (string=
  207. "green"
  208. (with-simulated-input "g RET"
  209. (call-interactively 'blacklisted-command))))
  210. (should
  211. (string=
  212. "green"
  213. (with-simulated-input "g RET"
  214. (call-interactively 'cmd-that-calls-blacklisted-function))))
  215. (should
  216. (string=
  217. "green"
  218. (with-simulated-input "g RET"
  219. (ido-completing-read+ "Prompt: " 'blacklisted-collection))))
  220. ;; Now add them to the blacklist and try again
  221. (let ((ido-cr+-function-blacklist
  222. (append '(blacklisted-command
  223. blacklisted-function
  224. blacklisted-collection)
  225. ido-cr+-function-blacklist)))
  226. ;; All should now have ido-cr+ disabled
  227. (should
  228. (string=
  229. "g"
  230. (with-simulated-input "g RET"
  231. (call-interactively 'blacklisted-command))))
  232. (should
  233. (string=
  234. "g"
  235. (with-simulated-input "g RET"
  236. (call-interactively 'cmd-that-calls-blacklisted-function))))
  237. (should
  238. (string=
  239. "g"
  240. (with-simulated-input "g RET"
  241. (ido-completing-read+ "Prompt: " 'blacklisted-collection))))))))
  242. (ert-deftest ido-cr+-test-whitelist ()
  243. :tags '(ido ido-cr+)
  244. "Test whether `ido-ubiquitous-function-whitelist' works."
  245. (with-ido-cr+-standard-env
  246. (cl-letf (((symbol-function 'whitelisted-command)
  247. (lambda (arg)
  248. (interactive (list (completing-read "Prompt: " '("blue" "yellow" "green"))))
  249. arg))
  250. ((symbol-function 'whitelisted-function)
  251. (lambda ()
  252. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  253. ((symbol-function 'cmd-that-calls-whitelisted-function)
  254. (lambda ()
  255. (interactive)
  256. (funcall 'whitelisted-function)))
  257. ((symbol-function 'whitelisted-collection)
  258. (lambda (string pred action)
  259. (complete-with-action action '("blue" "yellow" "green") string pred))))
  260. ;; Now add them to the whitelist
  261. (let ((ido-cr+-function-whitelist
  262. (append '(whitelisted-command
  263. whitelisted-function
  264. whitelisted-collection)
  265. ido-cr+-function-whitelist)))
  266. ;; All should now have ido-cr+ enabled
  267. (should
  268. (string=
  269. "green"
  270. (with-simulated-input "g RET"
  271. (call-interactively 'whitelisted-command))))
  272. (should
  273. (string=
  274. "green"
  275. (with-simulated-input "g RET"
  276. (call-interactively 'cmd-that-calls-whitelisted-function))))
  277. (should
  278. (string=
  279. "green"
  280. (with-simulated-input "g RET"
  281. (ido-completing-read+ "Prompt: " 'whitelisted-collection)))))
  282. ;; Run again with nothing whitelisted
  283. (let ((ido-cr+-function-whitelist '(nil)))
  284. ;; All should now have ido-cr+ disabled
  285. (should
  286. (string=
  287. "g"
  288. (with-simulated-input "g RET"
  289. (call-interactively 'whitelisted-command))))
  290. (should
  291. (string=
  292. "g"
  293. (with-simulated-input "g RET"
  294. (call-interactively 'cmd-that-calls-whitelisted-function))))
  295. (should
  296. (string=
  297. "g"
  298. (with-simulated-input "g RET"
  299. (ido-completing-read+ "Prompt: " 'whitelisted-collection))))))))
  300. (ert-deftest ido-cr+-require-match ()
  301. :tags '(ido ido-cr+)
  302. "Test whether REQUIRE-MATCH and DEF work as expected together."
  303. (with-ido-cr+-standard-env
  304. ;; "C-j" should be allowed to return an empty string even if
  305. ;; require-match is non-nil, as long as default is nil
  306. (should
  307. (string=
  308. ""
  309. (with-simulated-input "C-j"
  310. (ido-completing-read+
  311. "Prompt: "
  312. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  313. ;; "C-j" should NOT be allowed to return an empty string if
  314. ;; require-match and default are both non-nil.
  315. (should-error
  316. (with-simulated-input "C-j"
  317. (ido-completing-read+
  318. "Prompt: "
  319. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t nil nil "yellow")))
  320. ;; Multiple presses of C-j won't just select the first match
  321. (should-error
  322. (with-simulated-input "b C-j C-j C-j"
  323. (ido-completing-read+
  324. "Prompt: "
  325. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  326. ;; First press of C-j should complete unique common prefix after the
  327. ;; first b, but then get stuck on the choice for the second b.
  328. (should-error
  329. (with-simulated-input "b C-j b C-j C-j"
  330. (ido-completing-read+
  331. "Prompt: "
  332. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  333. ;; This should complete to "blueberry" via 2 rounds of unique common
  334. ;; prefix completion, and then return on the 3rd "C-j"
  335. (should
  336. (string=
  337. "blueberry"
  338. (with-simulated-input "b C-j b C-j e C-j C-j"
  339. (ido-completing-read+
  340. "Prompt: "
  341. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  342. ;; The "C-j" should complete to "bluegrass" and return, because
  343. ;; `ido-confirm-unique-completion is nil.
  344. (should
  345. (string=
  346. "bluegrass"
  347. (with-simulated-input "b l u e g C-j"
  348. (ido-completing-read+
  349. "Prompt: "
  350. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  351. (let ((ido-confirm-unique-completion t))
  352. ;; Now the first "C-j" should complete to "bluegrass" but should
  353. ;; not return.
  354. (should-error
  355. (with-simulated-input "b l u e g C-j"
  356. (ido-completing-read+
  357. "Prompt: "
  358. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  359. ;; The first "C-j" should complete to "bluegrass", and the second
  360. ;; should return.
  361. (should
  362. (string=
  363. "bluegrass"
  364. (with-simulated-input "b l u e g C-j C-j"
  365. (ido-completing-read+
  366. "Prompt: "
  367. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))))
  368. ;; Finally, a few tests for the expected wrong behavior without
  369. ;; ido-cr+. If ido.el ever fixes this bug, it will cause this test
  370. ;; to fail as a signal that the workaround can be phased out.
  371. (should
  372. (string=
  373. ""
  374. (with-simulated-input "C-j"
  375. (ido-completing-read
  376. "Prompt: "
  377. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  378. (should
  379. (string=
  380. "b"
  381. (with-simulated-input "b C-j"
  382. (ido-completing-read
  383. "Prompt: "
  384. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))))
  385. (ert-deftest ido-cr+-test-fallback ()
  386. :tags '(ido ido-cr+)
  387. "Test whether manually invoking fallback works."
  388. (with-ido-cr+-standard-env
  389. (should
  390. ;; C-b/f not at beginning/end of input should not fall back
  391. (string=
  392. "green"
  393. (with-simulated-input "g C-b C-f RET"
  394. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  395. (should
  396. ;; C-f at end of input should fall back
  397. (string=
  398. "g"
  399. (with-simulated-input "g C-f RET"
  400. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  401. (should
  402. ;; Repeated C-b should not fall back
  403. (string=
  404. "green"
  405. (with-simulated-input "g C-b C-b C-b C-b RET"
  406. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  407. (should
  408. ;; C-b at beginning of line should fall back (if previous action
  409. ;; was not also C-b)
  410. (string=
  411. "g"
  412. (with-simulated-input "g C-b x DEL C-b RET"
  413. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))))
  414. (ert-deftest ido-cr+-dot-prefix-empty-string ()
  415. :tags '(ido ido-cr+)
  416. "Test whether ido-ubiquitous successfully works around a bug in ido.
  417. See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=26997 for more
  418. information on this bug."
  419. (with-ido-cr+-standard-env
  420. (let ((ido-enable-dot-prefix t))
  421. (should
  422. (string=
  423. ""
  424. (with-simulated-input "RET"
  425. (ido-completing-read+ "Pick: " '("" "aaa" "aab" "aac")))))
  426. (should
  427. (string=
  428. "aab"
  429. (with-simulated-input "a a b RET"
  430. (ido-completing-read+ "Pick: " '("" "aaa" "aab" "aac"))))))))
  431. (defun ido-cr+-run-all-tests ()
  432. (interactive)
  433. (ert "^ido-cr\\+-"))
  434. (provide 'ido-ubiquitous-test)
  435. ;;; ido-ubiquitous-test.el ends here