ido-cr+-test.el 18 KB

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