ido-cr+-test.el 19 KB

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