ido-cr+-test.el 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  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. (declare (indent 1))
  25. (let ((temp-cmd (cl-gensym "temp-cmd"))
  26. (cmd-finished-tag (cl-gensym "cmd-finished"))
  27. (temp-var (cl-gensym "temp-var"))
  28. (canary-sym (cl-gensym "canary")))
  29. `(cl-letf*
  30. (;; Wrap BODY in a command that evaluates BODY and throws the
  31. ;; result with `cmd-finished-tag'.
  32. ((symbol-function ',temp-cmd)
  33. (lambda ()
  34. (interactive)
  35. (throw ',cmd-finished-tag (progn ,@body))))
  36. ;; Set up the keymap for invoking the temp command
  37. (transient-map (make-sparse-keymap))
  38. (command-invoke-key-sequence "C-c e")
  39. (simulated-key-sequence ,keys)
  40. (trailing-C-g-key-sequence
  41. ;; We *really* want to trigger `keyboard-quit' if we reach
  42. ;; the end KEYS.
  43. "C-g C-g C-g C-g C-g C-g C-g")
  44. (full-key-sequence
  45. (mapconcat #'identity
  46. (list
  47. command-invoke-key-sequence
  48. simulated-key-sequence
  49. trailing-C-g-key-sequence)
  50. " ")))
  51. (when (seq-contains (kbd simulated-key-sequence) (elt (kbd "C-g") 0))
  52. (error "KEYS must not include C-g"))
  53. ;; Finish setting up the keymap for the temp command
  54. (define-key transient-map (kbd command-invoke-key-sequence) ',temp-cmd)
  55. (set-transient-map transient-map)
  56. ;; Run the command followed by KEYS followed by C-g. The
  57. ;; `catch' ensures that the keyboard macro stops executing as
  58. ;; soon as BODY has finished evaluating, even if there are more
  59. ;; keys to interpret.
  60. (let ((result
  61. (condition-case err
  62. (catch ',cmd-finished-tag
  63. (execute-kbd-macro (kbd full-key-sequence))
  64. ;; If the above doesn't throw, return the canary
  65. ',canary-sym)
  66. ;; On `keyboard-quit', return canary
  67. (quit ',canary-sym))))
  68. (if (eq result ',canary-sym)
  69. (error "Reached end of simulated input while evaluating body")
  70. result)))))
  71. (ert-deftest simulate-input ()
  72. "Tests for the basic functionality of the `with-simulated-input' macro.
  73. This macro is used in testing ido-cr+."
  74. ;; Basic string input
  75. (should
  76. (string= "hello"
  77. (with-simulated-input "hello RET"
  78. (read-string "Enter a string: "))))
  79. ;; Error if RET is not pressed to finish the input
  80. (should-error
  81. (with-simulated-input "hello"
  82. (read-string "Enter a string: ")))
  83. ;; Can throw an error manually
  84. (should-error
  85. (with-simulated-input "(error SPC \"Manually SPC throwing SPC an SPC error\") RET"
  86. (command-execute 'eval-expression)))
  87. ;; Extra keys should not cause errors
  88. (should
  89. (string= "hello"
  90. (with-simulated-input "hello RET M-x eval-expression (error SPC \"Manually SPC throwing SPC an SPC error\") RET"
  91. (read-string "Enter a string: ")))))
  92. (defmacro with-mode (mode arg &rest body)
  93. "Eval (MODE ARG), then body, then restore previous status of MODE.
  94. This will only work on modes that respect the normal conventions
  95. for activation and deactivation."
  96. (declare (indent 2))
  97. `(let* ((orig-status ,mode)
  98. (restore-arg (if orig-status 1 0)))
  99. (unwind-protect
  100. (progn
  101. (,mode ,arg)
  102. ,@body)
  103. (,mode restore-arg))))
  104. (defmacro with-ido-cr+-standard-env (&rest body)
  105. "Execute BODY with standard ido-cr+ settings.
  106. All ido-cr+ options will be let-bound to their default values,
  107. and `ido-ubiquitous-mode' will be enabled. The values will all br
  108. restored to what they were previously after BODY exits."
  109. (declare (indent 0))
  110. (let*
  111. ((options
  112. '(ido-cr+-debug-mode
  113. ido-cr+-fallback-function
  114. ido-cr+-max-items
  115. ido-cr+-function-blacklist
  116. ido-cr+-function-whitelist
  117. ido-confirm-unique-completion))
  118. (bindings
  119. (cl-loop for var in options collect
  120. (list var
  121. (list 'quote
  122. (eval (car (get var 'standard-value))))))))
  123. `(with-mode ido-ubiquitous-mode 1
  124. (let ,bindings ,@body))))
  125. (defmacro collection-as-function (collection)
  126. "Return a function equivalent to COLLECTION.
  127. The returned function will work equivalently to COLLECTION when
  128. passed to `all-completions' and `try-completion'."
  129. `(completion-table-dynamic (lambda (string) (all-completions string ,collection))))
  130. (ert-deftest ido-cr+-test-basic ()
  131. :tags '(ido ido-cr+)
  132. "Test that basic ido-cr+ functionality is working."
  133. (with-ido-cr+-standard-env
  134. ;; Verify that pressing RET selects a matching item
  135. (should
  136. (string=
  137. "green"
  138. (with-simulated-input "g RET"
  139. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  140. ;; Verify that pressing RET with multiple matches selects the
  141. ;; first one
  142. (should
  143. (string=
  144. "brown"
  145. (with-simulated-input "b RET"
  146. (ido-completing-read+ "Prompt: " '("brown" "blue" "yellow" "green")))))
  147. ;; Verify that cycling with <left> and <right> works, including
  148. ;; wrap-around
  149. (should
  150. (string=
  151. "blue"
  152. (with-simulated-input "b <right> <right> <right> <right> <left> RET"
  153. (ido-completing-read+ "Prompt: " '("brown" "blue" "yellow" "green")))))
  154. ;; Verify that RET or C-j on empty input returns "" when
  155. ;; REQUIRE-MATCH is t
  156. (should
  157. (string=
  158. ""
  159. (with-simulated-input "RET"
  160. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil t))))
  161. (should
  162. (string=
  163. ""
  164. (with-simulated-input "C-j"
  165. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil t))))
  166. ;; Verify that DEF works, whether or not it is an element of
  167. ;; COLLECTION
  168. (should
  169. (string=
  170. "green"
  171. (with-simulated-input "RET"
  172. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil nil nil "green"))))
  173. (should
  174. (string=
  175. "green"
  176. (with-simulated-input "RET"
  177. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil t nil nil "green"))))
  178. (should
  179. (string=
  180. "brown"
  181. (with-simulated-input "RET"
  182. (ido-completing-read+ "Prompt: " '("blue" "yellow" "brown") nil nil nil nil "brown"))))
  183. (should
  184. (string=
  185. "brown"
  186. (with-simulated-input "RET"
  187. (ido-completing-read+ "Prompt: " '("blue" "yellow" "brown") nil t nil nil "brown"))))
  188. (should
  189. (string=
  190. "brown"
  191. (with-simulated-input "RET"
  192. (ido-completing-read+ "Prompt: " '("blue" "yellow" "brown") nil t nil nil '("brown" "green")))))
  193. ;; Verify that INITIAL-INPUT works
  194. (should
  195. (string=
  196. "green"
  197. (with-simulated-input "RET"
  198. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil "gr"))))
  199. ;; Verify that INITIAL-INPUT and DEF don't interfere with each other
  200. (should
  201. (string=
  202. "green"
  203. (with-simulated-input "RET"
  204. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil "gr" nil "blue"))))
  205. (should
  206. (string=
  207. "blue"
  208. (with-simulated-input "DEL DEL RET"
  209. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil "gr" nil "blue"))))
  210. ;; Verify that ido-cr+ works on function collections
  211. (should
  212. (string=
  213. "green"
  214. (with-simulated-input "g RET"
  215. (ido-completing-read+ "Prompt: " (collection-as-function '("blue" "yellow" "green"))))))))
  216. (ert-deftest ido-cr+-mode-activation ()
  217. :tags '(ido ido-cr+)
  218. "Test whether ido-ubiquitous-mode can be turned on and off."
  219. (with-ido-cr+-standard-env
  220. (cl-letf (((symbol-function 'test-command)
  221. (lambda ()
  222. (interactive)
  223. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  224. ;; Verify that the mode can be activated
  225. (should
  226. (string=
  227. "green"
  228. (with-mode ido-ubiquitous-mode 1
  229. (with-simulated-input "g RET"
  230. (call-interactively 'test-command)))))
  231. ;; Verify that the mode can be deactivated
  232. (should
  233. (string=
  234. "g"
  235. (with-mode ido-ubiquitous-mode 0
  236. (with-simulated-input "g RET"
  237. (call-interactively 'test-command))))))))
  238. (ert-deftest ido-cr+-test-maxitems ()
  239. :tags '(ido ido-cr+)
  240. "Test whether the large-collection fallback works."
  241. (with-ido-cr+-standard-env
  242. ;; This should not fall back
  243. (should
  244. (string=
  245. "green"
  246. (with-simulated-input "g RET"
  247. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  248. (let ((ido-cr+-max-items -1))
  249. ;; This should fall back because the collection is too large
  250. (should
  251. (string=
  252. "g"
  253. (with-simulated-input "g RET"
  254. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green"))))))))
  255. (ert-deftest ido-cr+-test-blacklist ()
  256. :tags '(ido ido-cr+)
  257. "Test whether `ido-ubiquitous-function-blacklist' works."
  258. (with-ido-cr+-standard-env
  259. (cl-letf (((symbol-function 'blacklisted-command)
  260. (lambda (arg)
  261. (interactive (list (completing-read "Prompt: " '("blue" "yellow" "green"))))
  262. arg))
  263. ((symbol-function 'blacklisted-function)
  264. (lambda ()
  265. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  266. ((symbol-function 'cmd-that-calls-blacklisted-function)
  267. (lambda ()
  268. (interactive)
  269. (funcall 'blacklisted-function)))
  270. ((symbol-function 'blacklisted-collection)
  271. (collection-as-function '("blue" "yellow" "green"))))
  272. ;; First verify that they work normally before blacklisting them
  273. (should
  274. (string=
  275. "green"
  276. (with-simulated-input "g RET"
  277. (call-interactively 'blacklisted-command))))
  278. (should
  279. (string=
  280. "green"
  281. (with-simulated-input "g RET"
  282. (call-interactively 'cmd-that-calls-blacklisted-function))))
  283. (should
  284. (string=
  285. "green"
  286. (with-simulated-input "g RET"
  287. (ido-completing-read+ "Prompt: " 'blacklisted-collection))))
  288. ;; Now add them to the blacklist and try again
  289. (let ((ido-cr+-function-blacklist
  290. (append '(blacklisted-command
  291. blacklisted-function
  292. blacklisted-collection)
  293. ido-cr+-function-blacklist)))
  294. ;; All should now have ido-cr+ disabled
  295. (should
  296. (string=
  297. "g"
  298. (with-simulated-input "g RET"
  299. (call-interactively 'blacklisted-command))))
  300. (should
  301. (string=
  302. "g"
  303. (with-simulated-input "g RET"
  304. (call-interactively 'cmd-that-calls-blacklisted-function))))
  305. (should
  306. (string=
  307. "g"
  308. (with-simulated-input "g RET"
  309. (ido-completing-read+ "Prompt: " 'blacklisted-collection))))))))
  310. (ert-deftest ido-cr+-test-whitelist ()
  311. :tags '(ido ido-cr+)
  312. "Test whether `ido-ubiquitous-function-whitelist' works."
  313. (with-ido-cr+-standard-env
  314. (cl-letf (((symbol-function 'whitelisted-command)
  315. (lambda (arg)
  316. (interactive (list (completing-read "Prompt: " '("blue" "yellow" "green"))))
  317. arg))
  318. ((symbol-function 'whitelisted-function)
  319. (lambda ()
  320. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  321. ((symbol-function 'cmd-that-calls-whitelisted-function)
  322. (lambda ()
  323. (interactive)
  324. (funcall 'whitelisted-function)))
  325. ((symbol-function 'whitelisted-collection)
  326. (lambda (string pred action)
  327. (complete-with-action action '("blue" "yellow" "green") string pred))))
  328. ;; Now add them to the whitelist
  329. (let ((ido-cr+-function-whitelist
  330. (append '(whitelisted-command
  331. whitelisted-function
  332. whitelisted-collection)
  333. ido-cr+-function-whitelist)))
  334. ;; All should now have ido-cr+ enabled
  335. (should
  336. (string=
  337. "green"
  338. (with-simulated-input "g RET"
  339. (call-interactively 'whitelisted-command))))
  340. (should
  341. (string=
  342. "green"
  343. (with-simulated-input "g RET"
  344. (call-interactively 'cmd-that-calls-whitelisted-function))))
  345. (should
  346. (string=
  347. "green"
  348. (with-simulated-input "g RET"
  349. (ido-completing-read+ "Prompt: " 'whitelisted-collection)))))
  350. ;; Run again with nothing whitelisted
  351. (let ((ido-cr+-function-whitelist '(nil)))
  352. ;; All should now have ido-cr+ disabled
  353. (should
  354. (string=
  355. "g"
  356. (with-simulated-input "g RET"
  357. (call-interactively 'whitelisted-command))))
  358. (should
  359. (string=
  360. "g"
  361. (with-simulated-input "g RET"
  362. (call-interactively 'cmd-that-calls-whitelisted-function))))
  363. (should
  364. (string=
  365. "g"
  366. (with-simulated-input "g RET"
  367. (ido-completing-read+ "Prompt: " 'whitelisted-collection))))))))
  368. (ert-deftest ido-cr+-require-match ()
  369. :tags '(ido ido-cr+)
  370. "Test whether REQUIRE-MATCH and DEF work as expected together."
  371. (with-ido-cr+-standard-env
  372. ;; "C-j" should be allowed to return an empty string even if
  373. ;; require-match is non-nil, as long as default is nil
  374. (should
  375. (string=
  376. ""
  377. (with-simulated-input "C-j"
  378. (ido-completing-read+
  379. "Prompt: "
  380. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  381. ;; "C-j" should NOT be allowed to return an empty string if
  382. ;; require-match and default are both non-nil.
  383. (should-error
  384. (with-simulated-input "C-j"
  385. (ido-completing-read+
  386. "Prompt: "
  387. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t nil nil "yellow")))
  388. ;; Multiple presses of C-j won't just select the first match
  389. (should-error
  390. (with-simulated-input "b C-j C-j C-j"
  391. (ido-completing-read+
  392. "Prompt: "
  393. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  394. ;; First press of C-j should complete unique common prefix after the
  395. ;; first b, but then get stuck on the choice for the second b.
  396. (should-error
  397. (with-simulated-input "b C-j b C-j C-j"
  398. (ido-completing-read+
  399. "Prompt: "
  400. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  401. ;; This should complete to "blueberry" via 2 rounds of unique common
  402. ;; prefix completion, and then return on the 3rd "C-j"
  403. (should
  404. (string=
  405. "blueberry"
  406. (with-simulated-input "b C-j b C-j e C-j C-j"
  407. (ido-completing-read+
  408. "Prompt: "
  409. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))))
  410. ;; The "C-j" should complete to "bluegrass" and return, because
  411. ;; `ido-confirm-unique-completion is nil.
  412. (should
  413. (string=
  414. "bluegrass"
  415. (with-simulated-input "b l u e g C-j"
  416. (ido-completing-read+
  417. "Prompt: "
  418. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  419. (let ((ido-confirm-unique-completion t))
  420. ;; Now the first "C-j" should complete to "bluegrass" but should
  421. ;; not return.
  422. (should-error
  423. (with-simulated-input "b l u e g C-j"
  424. (ido-completing-read+
  425. "Prompt: "
  426. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  427. ;; The first "C-j" should complete to "bluegrass", and the second
  428. ;; should return.
  429. (should
  430. (string=
  431. "bluegrass"
  432. (with-simulated-input "b l u e g C-j C-j"
  433. (ido-completing-read+
  434. "Prompt: "
  435. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))))
  436. ;; Finally, a few tests for the expected wrong behavior without
  437. ;; ido-cr+. If ido.el ever fixes this bug, it will cause this test
  438. ;; to fail as a signal that the workaround can be phased out.
  439. (should
  440. (string=
  441. ""
  442. (with-simulated-input "C-j"
  443. (ido-completing-read
  444. "Prompt: "
  445. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  446. (should
  447. (string=
  448. "b"
  449. (with-simulated-input "b C-j"
  450. (ido-completing-read
  451. "Prompt: "
  452. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))))
  453. (ert-deftest ido-cr+-test-fallback ()
  454. :tags '(ido ido-cr+)
  455. "Test whether manually invoking fallback works."
  456. (with-ido-cr+-standard-env
  457. (should
  458. ;; C-b/f not at beginning/end of input should not fall back
  459. (string=
  460. "green"
  461. (with-simulated-input "g C-b C-f RET"
  462. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  463. (should
  464. ;; C-f at end of input should fall back
  465. (string=
  466. "g"
  467. (with-simulated-input "g C-f RET"
  468. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  469. (should
  470. ;; Repeated C-b should not fall back
  471. (string=
  472. "green"
  473. (with-simulated-input "g C-b C-b C-b C-b RET"
  474. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))
  475. (should
  476. ;; C-b at beginning of line should fall back (if previous action
  477. ;; was not also C-b)
  478. (string=
  479. "g"
  480. (with-simulated-input "g C-b x DEL C-b RET"
  481. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))))))
  482. (ert-deftest ido-cr+-dot-prefix-empty-string ()
  483. :tags '(ido ido-cr+)
  484. "Test whether ido-ubiquitous successfully works around a bug in ido.
  485. See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=26997 for more
  486. information on this bug."
  487. (with-ido-cr+-standard-env
  488. (let ((ido-enable-dot-prefix t))
  489. (should
  490. (string=
  491. ""
  492. (with-simulated-input "RET"
  493. (ido-completing-read+ "Pick: " '("" "aaa" "aab" "aac")))))
  494. (should
  495. (string=
  496. "aab"
  497. (with-simulated-input "a a b RET"
  498. (ido-completing-read+ "Pick: " '("" "aaa" "aab" "aac"))))))))
  499. (defun ido-cr+-run-all-tests ()
  500. (interactive)
  501. (ert "^ido-cr\\+-"))
  502. (provide 'ido-ubiquitous-test)
  503. ;;; ido-ubiquitous-test.el ends here