ido-cr+-test.el 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  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+-mode-activation ()
  171. :tags '(ido ido-cr+)
  172. "Test whether ido-ubiquitous-mode can be turned on and off."
  173. (with-ido-cr+-standard-env
  174. (cl-letf (((symbol-function 'test-command)
  175. (lambda ()
  176. (interactive)
  177. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  178. ;; Verify that the mode can be activated
  179. (should
  180. (string=
  181. "green"
  182. (with-mode ido-ubiquitous-mode 1
  183. (with-simulated-input "g RET"
  184. (call-interactively 'test-command)))))
  185. ;; Verify that the mode can be deactivated
  186. (should
  187. (string=
  188. "g"
  189. (with-mode ido-ubiquitous-mode 0
  190. (with-simulated-input "g RET"
  191. (call-interactively 'test-command))))))))
  192. (ert-deftest ido-cr+-test-maxitems ()
  193. :tags '(ido ido-cr+)
  194. "Test whether the large-collection fallback works."
  195. (with-ido-cr+-standard-env
  196. ;; This should not fall back
  197. (should
  198. (string=
  199. "green"
  200. (with-simulated-input "g RET"
  201. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  202. (let ((ido-cr+-max-items -1))
  203. ;; This should fall back because the collection is too large
  204. (should
  205. (string=
  206. "g"
  207. (with-simulated-input "g RET"
  208. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green"))))))))
  209. (ert-deftest ido-cr+-test-blacklist ()
  210. :tags '(ido ido-cr+)
  211. "Test whether `ido-ubiquitous-function-blacklist' works."
  212. (with-ido-cr+-standard-env
  213. (cl-letf (((symbol-function 'blacklisted-command)
  214. (lambda (arg)
  215. (interactive (list (completing-read "Prompt: " '("blue" "yellow" "green"))))
  216. arg))
  217. ((symbol-function 'blacklisted-function)
  218. (lambda ()
  219. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  220. ((symbol-function 'cmd-that-calls-blacklisted-function)
  221. (lambda ()
  222. (interactive)
  223. (funcall 'blacklisted-function)))
  224. ((symbol-function 'blacklisted-collection)
  225. (collection-as-function '("blue" "yellow" "green"))))
  226. ;; First verify that they work normally before blacklisting them
  227. (should
  228. (string=
  229. "green"
  230. (with-simulated-input "g RET"
  231. (call-interactively 'blacklisted-command))))
  232. (should
  233. (string=
  234. "green"
  235. (with-simulated-input "g RET"
  236. (call-interactively 'cmd-that-calls-blacklisted-function))))
  237. (should
  238. (string=
  239. "green"
  240. (with-simulated-input "g RET"
  241. (ido-completing-read+ "Prompt: " 'blacklisted-collection))))
  242. ;; Now add them to the blacklist and try again
  243. (let ((ido-cr+-function-blacklist
  244. (append '(blacklisted-command
  245. blacklisted-function
  246. blacklisted-collection)
  247. ido-cr+-function-blacklist)))
  248. ;; All should now have ido-cr+ disabled
  249. (should
  250. (string=
  251. "g"
  252. (with-simulated-input "g RET"
  253. (call-interactively 'blacklisted-command))))
  254. (should
  255. (string=
  256. "g"
  257. (with-simulated-input "g RET"
  258. (call-interactively 'cmd-that-calls-blacklisted-function))))
  259. (should
  260. (string=
  261. "g"
  262. (with-simulated-input "g RET"
  263. (ido-completing-read+ "Prompt: " 'blacklisted-collection))))))))
  264. (ert-deftest ido-cr+-test-whitelist ()
  265. :tags '(ido ido-cr+)
  266. "Test whether `ido-ubiquitous-function-whitelist' works."
  267. (with-ido-cr+-standard-env
  268. (cl-letf (((symbol-function 'whitelisted-command)
  269. (lambda (arg)
  270. (interactive (list (completing-read "Prompt: " '("blue" "yellow" "green"))))
  271. arg))
  272. ((symbol-function 'whitelisted-function)
  273. (lambda ()
  274. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  275. ((symbol-function 'cmd-that-calls-whitelisted-function)
  276. (lambda ()
  277. (interactive)
  278. (funcall 'whitelisted-function)))
  279. ((symbol-function 'whitelisted-collection)
  280. (lambda (string pred action)
  281. (complete-with-action action '("blue" "yellow" "green") string pred))))
  282. ;; Now add them to the whitelist
  283. (let ((ido-cr+-function-whitelist
  284. (append '(whitelisted-command
  285. whitelisted-function
  286. whitelisted-collection)
  287. ido-cr+-function-whitelist)))
  288. ;; All should now have ido-cr+ enabled
  289. (should
  290. (string=
  291. "green"
  292. (with-simulated-input "g RET"
  293. (call-interactively 'whitelisted-command))))
  294. (should
  295. (string=
  296. "green"
  297. (with-simulated-input "g RET"
  298. (call-interactively 'cmd-that-calls-whitelisted-function))))
  299. (should
  300. (string=
  301. "green"
  302. (with-simulated-input "g RET"
  303. (ido-completing-read+ "Prompt: " 'whitelisted-collection)))))
  304. ;; Run again with nothing whitelisted
  305. (let ((ido-cr+-function-whitelist '(nil)))
  306. ;; All should now have ido-cr+ disabled
  307. (should
  308. (string=
  309. "g"
  310. (with-simulated-input "g RET"
  311. (call-interactively 'whitelisted-command))))
  312. (should
  313. (string=
  314. "g"
  315. (with-simulated-input "g RET"
  316. (call-interactively 'cmd-that-calls-whitelisted-function))))
  317. (should
  318. (string=
  319. "g"
  320. (with-simulated-input "g RET"
  321. (ido-completing-read+ "Prompt: " 'whitelisted-collection))))))))
  322. (ert-deftest ido-cr+-require-match ()
  323. :tags '(ido ido-cr+)
  324. "Test whether REQUIRE-MATCH and DEF work as expected together."
  325. (with-ido-cr+-standard-env
  326. ;; "C-j" should be allowed to return an empty string even if
  327. ;; require-match is non-nil, as long as default is nil
  328. (should
  329. (string=
  330. ""
  331. (with-simulated-input "C-j"
  332. (ido-completing-read+
  333. "Prompt: "
  334. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  335. ;; "C-j" should NOT be allowed to return an empty string if
  336. ;; require-match and default are both non-nil.
  337. (should-error
  338. (with-simulated-input "C-j"
  339. (ido-completing-read+
  340. "Prompt: "
  341. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t nil nil "yellow")))
  342. ;; Multiple presses of C-j won't just select the first match
  343. (should-error
  344. (with-simulated-input "b C-j C-j C-j"
  345. (ido-completing-read+
  346. "Prompt: "
  347. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  348. ;; First press of C-j should complete unique common prefix after the
  349. ;; first b, but then get stuck on the choice for the second b.
  350. (should-error
  351. (with-simulated-input "b C-j b C-j C-j"
  352. (ido-completing-read+
  353. "Prompt: "
  354. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  355. ;; This should complete to "blueberry" via 2 rounds of unique common
  356. ;; prefix completion, and then return on the 3rd "C-j"
  357. (should
  358. (string=
  359. "blueberry"
  360. (with-simulated-input "b C-j b C-j e C-j C-j"
  361. (ido-completing-read+
  362. "Prompt: "
  363. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  364. ;; The "C-j" should complete to "bluegrass" and return, because
  365. ;; `ido-confirm-unique-completion is nil.
  366. (should
  367. (string=
  368. "bluegrass"
  369. (with-simulated-input "b l u e g C-j"
  370. (ido-completing-read+
  371. "Prompt: "
  372. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  373. (let ((ido-confirm-unique-completion t))
  374. ;; Now the first "C-j" should complete to "bluegrass" but should
  375. ;; not return.
  376. (should-error
  377. (with-simulated-input "b l u e g C-j"
  378. (ido-completing-read+
  379. "Prompt: "
  380. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  381. ;; The first "C-j" should complete to "bluegrass", and the second
  382. ;; should return.
  383. (should
  384. (string=
  385. "bluegrass"
  386. (with-simulated-input "b l u e g C-j C-j"
  387. (ido-completing-read+
  388. "Prompt: "
  389. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))))
  390. ;; Finally, a few tests for the expected wrong behavior without
  391. ;; ido-cr+. If ido.el ever fixes this bug, it will cause this test
  392. ;; to fail as a signal that the workaround can be phased out.
  393. (should
  394. (string=
  395. ""
  396. (with-simulated-input "C-j"
  397. (ido-completing-read
  398. "Prompt: "
  399. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  400. (should
  401. (string=
  402. "b"
  403. (with-simulated-input "b C-j"
  404. (ido-completing-read
  405. "Prompt: "
  406. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))))
  407. (ert-deftest ido-cr+-test-fallback ()
  408. :tags '(ido ido-cr+)
  409. "Test whether manually invoking fallback works."
  410. (with-ido-cr+-standard-env
  411. (should
  412. ;; C-b/f not at beginning/end of input should not fall back
  413. (string=
  414. "green"
  415. (with-simulated-input "g C-b C-f RET"
  416. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  417. (should
  418. ;; C-f at end of input should fall back
  419. (string=
  420. "g"
  421. (with-simulated-input "g C-f RET"
  422. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  423. (should
  424. ;; Repeated C-b should not fall back
  425. (string=
  426. "green"
  427. (with-simulated-input "g C-b C-b C-b C-b RET"
  428. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  429. (should
  430. ;; C-b at beginning of line should fall back (if previous action
  431. ;; was not also C-b)
  432. (string=
  433. "g"
  434. (with-simulated-input "g C-b x DEL C-b RET"
  435. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))))
  436. (ert-deftest ido-cr+-dot-prefix-empty-string ()
  437. :tags '(ido ido-cr+)
  438. "Test whether ido-ubiquitous successfully works around a bug in ido.
  439. See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=26997 for more
  440. information on this bug."
  441. (with-ido-cr+-standard-env
  442. (let ((ido-enable-dot-prefix t))
  443. (should
  444. (string=
  445. ""
  446. (with-simulated-input "RET"
  447. (ido-completing-read+ "Pick: " '("" "aaa" "aab" "aac")))))
  448. (should
  449. (string=
  450. "aab"
  451. (with-simulated-input "a a b RET"
  452. (ido-completing-read+ "Pick: " '("" "aaa" "aab" "aac"))))))))
  453. (defun ido-cr+-run-all-tests ()
  454. (interactive)
  455. (ert "^ido-cr\\+-"))
  456. (provide 'ido-ubiquitous-test)
  457. ;;; ido-ubiquitous-test.el ends here