ido-cr+-test.el 19 KB

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