test-ido-completing-read+.el 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066
  1. ;;; -*- lexical-binding: t -*-
  2. (require 'ido)
  3. (require 'minibuf-eldef)
  4. (require 'ido-completing-read+)
  5. (require 'buttercup)
  6. (require 'cl-lib)
  7. (require 'with-simulated-input)
  8. (require 's)
  9. ;; Note: Currently unused, but potentially useful in the future
  10. (defun ido-cr+-maybe-chop (items elem)
  11. "Like `ido-chop', but a no-op if ELEM is not in ITEMS.
  12. Normal `ido-chop' hangs infinitely in this case."
  13. (cl-loop
  14. with new-tail = ()
  15. for remaining on items
  16. for next = (car remaining)
  17. if (equal next elem)
  18. return (nconc remaining new-tail)
  19. else collect next into new-tail
  20. finally return items))
  21. (defun collection-as-function (collection)
  22. "Return a function equivalent to COLLECTION.
  23. The returned function will work equivalently to COLLECTION when
  24. passed to `all-completions' and `try-completion'."
  25. (completion-table-dynamic (lambda (string) (all-completions string collection))))
  26. (defun shadow-var (var &optional temp-value)
  27. "Shadow the value of VAR.
  28. This will push the current value of VAR to VAR's
  29. `shadowed-values' property, and then set it to TEMP-VALUE. To
  30. reverse this process, call `unshadow-var' on VAR. Vars can
  31. be shadowed recursively, and must be unshadowed once for each
  32. shadowing in order to restore the original value. You can think
  33. of shadowing as dynamic binding with `let', but with manual
  34. control over when bindings start and end.
  35. If VAR is a Custom variable (see `custom-variable-p'), it will be
  36. set using `customize-set-variable', and if TEMP-VALUE is nil it
  37. will be replaces with VAR's standard value.
  38. Other variables will be set with `set-default', and a TEMP-VALUE
  39. of nil will not be treated specially.
  40. `shadow-var' only works on variables declared as special (i.e.
  41. using `defvar' or similar). It will not work on lexically bound
  42. variables."
  43. (unless (special-variable-p var)
  44. (error "Cannot shadow lexical var `%s'" var))
  45. (let* ((use-custom (custom-variable-p var))
  46. (setter (if use-custom 'customize-set-variable 'set-default))
  47. (temp-value (or temp-value
  48. (and use-custom
  49. (eval (car (get var 'standard-value)))))))
  50. ;; Push the current value on the stack
  51. (push (symbol-value var) (get var 'shadowed-values))
  52. (funcall setter var temp-value)))
  53. (defun var-shadowed-p (var)
  54. "Return non-nil if VAR is shadowed by `shadow-var'."
  55. ;; We don't actually want to return that list if it's non-nil.
  56. (and (get var 'shadowed-values) t))
  57. (defun unshadow-var (var)
  58. "Reverse the last call to `shadow-var' on VAR."
  59. (if (var-shadowed-p var)
  60. (let* ((use-custom (custom-variable-p var))
  61. (setter (if use-custom 'customize-set-variable 'set-default))
  62. (value (pop (get var 'shadowed-values))))
  63. (funcall setter var value))
  64. (error "Var is not shadowed: %s" var)))
  65. (defun fully-unshadow-var (var)
  66. "Reverse *all* calls to `shadow-var' on VAR."
  67. (when (var-shadowed-p var)
  68. (let* ((use-custom (custom-variable-p var))
  69. (setter (if use-custom 'customize-set-variable 'set-default))
  70. (value (car (last (get var 'shadowed-values)))))
  71. (put var 'shadowed-values nil)
  72. (funcall setter var value))))
  73. (defun fully-unshadow-all-vars (&optional vars)
  74. "Reverse *all* calls to `shadow-var' on VARS.
  75. If VARS is nil, unshadow *all* variables."
  76. (if vars
  77. (mapc #'fully-unshadow-var vars)
  78. (mapatoms #'fully-unshadow-var))
  79. nil)
  80. (defmacro shadow-vars (varlist)
  81. "Shadow a list of vars with new values.
  82. VARLIST describes the variables to be shadowed with the same
  83. syntax as `let'.
  84. See `shadow-var'."
  85. (declare (indent 0))
  86. (cl-loop
  87. with var = nil
  88. with value = nil
  89. for binding in varlist
  90. if (symbolp binding)
  91. do (setq var binding
  92. value nil)
  93. else
  94. do (setq var (car binding)
  95. value (cadr binding))
  96. collect `(shadow-var ',var ,value) into exprs
  97. finally return `(progn ,@exprs)))
  98. (defmacro unshadow-vars (vars)
  99. "Un-shadow a list of VARS.
  100. This is a macro for consistency with `shadow-vars', but it will
  101. also accept a quoted list for the sake of convenience."
  102. (declare (indent 0))
  103. (when (eq (car vars) 'quote)
  104. (setq vars (eval vars)))
  105. `(mapc #'unshadow-var ',vars))
  106. (defmacro with-temp-info-buffer (&rest body)
  107. "Create a temporary info buffer and exeluate BODY forms there."
  108. (declare (indent 0))
  109. `(let ((temp-bufname (generate-new-buffer-name " *temp-info*")))
  110. (unwind-protect
  111. (save-excursion
  112. (info nil (generate-new-buffer-name " *temp-info*"))
  113. ,@body)
  114. (when (get-buffer temp-bufname)
  115. (kill-buffer temp-bufname)))))
  116. (describe "Within the `ido-completing-read+' package"
  117. ;; Reset all of these variables to their standard values before each
  118. ;; test, saving the previous values for later restoration.
  119. (before-each
  120. (shadow-vars
  121. ((ido-mode t)
  122. (ido-ubiquitous-mode t)
  123. (ido-cr+-debug-mode t)
  124. ido-cr+-auto-update-disable-list
  125. ido-cr+-fallback-function
  126. ido-cr+-max-items
  127. ido-cr+-disable-list
  128. ido-cr+-allow-list
  129. ido-cr+-nil-def-alternate-behavior-list
  130. ido-cr+-replace-completely
  131. ido-confirm-unique-completion
  132. ido-enable-flex-matching
  133. ido-enable-dot-prefix
  134. (minibuffer-electric-default-mode t)))
  135. ;; Suppress all messages during tests
  136. (spy-on 'message))
  137. ;; Restore the saved values after each test
  138. (after-each
  139. (fully-unshadow-all-vars))
  140. (describe "the `ido-completing-read+' function"
  141. (it "should complete with a matching item on RET"
  142. (expect
  143. (with-simulated-input "g RET"
  144. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))
  145. :to-equal "green"))
  146. (it "should complete with the first match when multiple matches are available"
  147. (expect
  148. (with-simulated-input "b RET"
  149. (ido-completing-read+ "Prompt: " '("brown" "blue" "yellow" "green")))
  150. :to-equal "brown"))
  151. (it "should allow <left> and <right> to cycle completions, with wrap-around"
  152. (expect
  153. (with-simulated-input "b <right> <right> <right> <right> <left> RET"
  154. (ido-completing-read+ "Prompt: " '("brown" "blue" "yellow" "green")))
  155. :to-equal
  156. "blue"))
  157. (it "should return \"\" when RET or C-j is pressed on an empty input even when REQUIRE-MATCH is non-nil"
  158. ;; No REQUIRE-MATCH
  159. (expect
  160. (with-simulated-input "RET"
  161. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))
  162. :to-equal "blue")
  163. (expect
  164. (with-simulated-input "C-j"
  165. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))
  166. :to-equal "")
  167. ;; Again, with REQUIRE-MATCH
  168. (expect
  169. (with-simulated-input "RET"
  170. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil t))
  171. :to-equal "")
  172. (expect
  173. (with-simulated-input "C-j"
  174. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil t))
  175. :to-equal ""))
  176. (it "should work with `minibuffer-electric-default-mode'"
  177. (let ((eldef-was-showing nil))
  178. ;; No REQUIRE-MATCH, so electric default should not show
  179. (with-simulated-input
  180. ("blu DEL DEL DEL"
  181. (setq eldef-was-showing minibuf-eldef-showing-default-in-prompt)
  182. "RET")
  183. (ido-completing-read+ "Prompt (default green): " '("blue" "yellow" "green")))
  184. (expect eldef-was-showing :not :to-be-truthy)
  185. ;; With REQUIRE-MATCH, so electric default should show
  186. (with-simulated-input
  187. ("blu DEL DEL DEL"
  188. (setq eldef-was-showing minibuf-eldef-showing-default-in-prompt)
  189. "RET")
  190. (ido-completing-read+ "Prompt (default green): " '("blue" "yellow" "green") nil t))
  191. (expect eldef-was-showing :to-be-truthy)))
  192. (it "should accept all the same forms of DEF as `completing-read-default'"
  193. ;; DEF in COLLECTION
  194. (expect
  195. (with-simulated-input "RET"
  196. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil nil nil "green"))
  197. :to-equal "green")
  198. ;; Same, with REQUIRE-MATCH
  199. (expect
  200. (with-simulated-input "RET"
  201. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil t nil nil "green"))
  202. :to-equal "green")
  203. ;; DEF not in COLLECTION
  204. (expect
  205. (with-simulated-input "RET"
  206. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil nil nil "brown"))
  207. :to-equal "brown")
  208. ;; Same, with REQUIRE-MATCH
  209. (expect
  210. (with-simulated-input "RET"
  211. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil t nil nil "brown"))
  212. :to-equal "brown")
  213. ;; List DEF, partially in COLLECTION
  214. (expect
  215. (with-simulated-input "RET"
  216. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil t nil nil '("brown" "green")))
  217. :to-equal "brown"))
  218. (it "should work with INITIAL-INPUT"
  219. (expect
  220. (with-simulated-input "RET"
  221. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil "gr"))
  222. :to-equal "green"))
  223. (it "should properly handle a cons INITIAL-INPUT"
  224. (expect
  225. (with-simulated-input "ee RET"
  226. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil (cons "gr" 2)))
  227. :to-equal "green"))
  228. (it "should properly handle both INITIAL-INPUT and DEF at the same time"
  229. (expect
  230. (with-simulated-input "RET"
  231. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil "gr" nil "blue"))
  232. :to-equal "green")
  233. (expect
  234. (with-simulated-input "DEL DEL RET"
  235. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil "gr" nil "blue"))
  236. :to-equal "blue"))
  237. (it "should work when COLLECTION is a function"
  238. (expect
  239. (with-simulated-input "g RET"
  240. (ido-completing-read+ "Prompt: " (collection-as-function '("blue" "yellow" "green"))))
  241. :to-equal "green"))
  242. (it "should fall back when COLLECTION is empty"
  243. (spy-on 'ido-completing-read :and-call-through)
  244. (expect
  245. (with-simulated-input "g RET"
  246. (ido-completing-read+ "Prompt: " nil))
  247. :to-equal "g")
  248. (expect 'ido-completing-read :not :to-have-been-called))
  249. (it "should replace `ido-completing-read' when `ido-cr+-replace-completely' is non-nil"
  250. (customize-set-variable 'ido-cr+-replace-completely t)
  251. (spy-on 'ido-completing-read+ :and-call-through)
  252. (expect
  253. (with-simulated-input "g RET"
  254. (ido-completing-read "Prompt: " '("blue" "yellow" "green")))
  255. :to-equal "green")
  256. (expect 'ido-completing-read+ :to-have-been-called))
  257. (describe "when `ido-cr+-max-items' is set"
  258. (it "should not trigger a fallback for small collections"
  259. (expect
  260. (with-simulated-input "g RET"
  261. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))
  262. :to-equal "green"))
  263. (it "should trigger a fallback for large collections"
  264. (expect
  265. ;; With max-items negative, all collections are considered "too
  266. ;; large"
  267. (let ((ido-cr+-max-items -1))
  268. (with-simulated-input "g RET"
  269. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green"))))
  270. :to-equal "g")))
  271. (describe "when REQUIRE-MATCH is non-nil"
  272. (it "should still allow exiting with an empty string if DEF is nil"
  273. (expect
  274. (with-simulated-input "C-j"
  275. (ido-completing-read+
  276. "Prompt: "
  277. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  278. :to-equal ""))
  279. ;; "C-j" should NOT be allowed to return an empty string if
  280. ;; require-match and default are both non-nil.
  281. (it "should not allow exiting with an empty string if DEF is non-nil"
  282. (expect
  283. (with-simulated-input "C-j"
  284. (ido-completing-read+
  285. "Prompt: "
  286. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t nil nil "yellow"))
  287. :to-throw))
  288. (it "shouldn't allow C-j to select an ambiguous match"
  289. ;; Make this a no-op to prevent spurious end-of-buffer errors
  290. ;; when ido tries to create a help buffer in a noninteractive
  291. ;; emacs.
  292. (spy-on 'ido-completion-auto-help)
  293. (expect
  294. (with-simulated-input "b C-j C-j C-j"
  295. (ido-completing-read+
  296. "Prompt: "
  297. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  298. :to-throw)
  299. ;; First press of C-j should complete to "blue" after the
  300. ;; first b, but then get stuck on the choice for the second b.
  301. (expect
  302. (with-simulated-input "b C-j b C-j C-j C-j"
  303. (ido-completing-read+
  304. "Prompt: "
  305. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  306. :to-throw))
  307. (it "should allow exiting with an unambiguous match"
  308. (expect
  309. (with-simulated-input "b C-j b C-j e C-j C-j"
  310. (ido-completing-read+
  311. "Prompt: "
  312. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  313. :to-equal "blueberry")
  314. ;; The "C-j" should complete to "bluegrass" and return, because
  315. ;; `ido-confirm-unique-completion is nil.
  316. (expect
  317. (with-simulated-input "b l u e g C-j"
  318. (ido-completing-read+
  319. "Prompt: "
  320. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  321. :to-equal "bluegrass"))
  322. (it "should require an extra C-j to exit when `ido-confirm-unique-completion' is non-nil"
  323. (setq ido-confirm-unique-completion t)
  324. ;; Now the first "C-j" should complete to "bluegrass" but should
  325. ;; not return.
  326. (expect
  327. (with-simulated-input "b l u e g C-j"
  328. (ido-completing-read+
  329. "Prompt: "
  330. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  331. :to-throw)
  332. ;; The first "C-j" should complete to "bluegrass", and the second
  333. ;; should return.
  334. (expect
  335. (with-simulated-input "b l u e g C-j C-j"
  336. (ido-completing-read+
  337. "Prompt: "
  338. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  339. :to-equal "bluegrass"))
  340. ;; Finally, a test for the expected wrong behavior without
  341. ;; ido-cr+. If ido.el ever fixes this bug, it will cause this test
  342. ;; to fail as a signal that the workaround can be phased out.
  343. (it "should return a non-match when ordinary `ido-completing-read' is used"
  344. (expect
  345. (with-simulated-input "b C-j"
  346. (ido-completing-read
  347. "Prompt: "
  348. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  349. :to-equal "b")))
  350. (describe "when INHERIT-INPUT-METHOD is non-nil"
  351. (before-each
  352. (spy-on 'ido-completing-read :and-call-through))
  353. (it "should not fall back if `current-input-method' is nil"
  354. (expect
  355. (let ((current-input-method nil))
  356. (with-simulated-input "g RET"
  357. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil nil nil nil t))
  358. :to-equal "green"))
  359. (expect 'ido-completing-read :to-have-been-called))
  360. (it "should fall back if `current-input-method' is non-nil"
  361. (expect
  362. (let ((current-input-method 'ucs))
  363. (with-simulated-input "g RET"
  364. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil nil nil nil t))
  365. :to-equal "green"))
  366. (expect 'ido-completing-read :not :to-have-been-called)))
  367. (describe "with manual fallback shortcuts"
  368. (it "should not fall back when C-b or C-f is used in the middle of the input"
  369. (expect
  370. ;; C-b/f not at beginning/end of input should not fall back
  371. (with-simulated-input "g C-b C-f RET"
  372. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))
  373. :to-equal "green"))
  374. (it "should fall back on C-f at end of input"
  375. (expect
  376. ;; C-f at end of input should fall back
  377. (with-simulated-input "g C-f RET"
  378. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))
  379. :to-equal "g"))
  380. (it "should not fall back from repeated C-b that hits the start of input"
  381. (expect
  382. ;; Repeated C-b should not fall back
  383. (with-simulated-input "g C-b C-b C-b C-b RET"
  384. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))
  385. :to-equal "green"))
  386. (it "should fall back on C-b at beginning of input (if previous action was not C-b)"
  387. (expect
  388. ;; C-b at beginning of line should fall back (if previous action
  389. ;; was not also C-b)
  390. (with-simulated-input "g C-b x DEL C-b RET"
  391. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))
  392. :to-equal "g")))
  393. (describe "with a workaround for an bug with non-nil `ido-enable-dot-prefix'"
  394. ;; See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=26997
  395. ;; for more information on this bug.
  396. (before-each
  397. (setq ido-enable-dot-prefix t))
  398. (it "should not throw an error when \"\" is in the collection"
  399. (expect
  400. (with-simulated-input "RET"
  401. (ido-completing-read+ "Pick: " '("" "aaa" "aab" "aac")))
  402. :to-equal "")
  403. (expect
  404. (with-simulated-input "a a b RET"
  405. (ido-completing-read+ "Pick: " '("" "aaa" "aab" "aac")))
  406. :to-equal "aab")))
  407. (describe "with dynamic collections"
  408. (before-all
  409. (setq my-dynamic-collection
  410. (completion-table-dynamic
  411. (lambda (text)
  412. (cond
  413. ;; Sub-completions for "hello"
  414. ((s-prefix-p "hello" text)
  415. '("hello" "hello-world" "hello-everyone" "hello-universe"))
  416. ;; Sub-completions for "goodbye"
  417. ((s-prefix-p "goodbye" text)
  418. '("goodbye" "goodbye-world" "goodbye-everyone" "goodbye-universe"))
  419. ;; General completions
  420. (t
  421. '("hello" "goodbye" "helicopter" "helium" "goodness" "goodwill")))))))
  422. (after-all
  423. (setq my-dynamic-collection nil))
  424. (before-each
  425. (setq ido-enable-flex-matching t
  426. ido-confirm-unique-completion nil)
  427. (spy-on 'ido-cr+-update-dynamic-collection
  428. :and-call-through))
  429. (it "should allow selection of dynamically-added completions"
  430. (expect
  431. (with-simulated-input "hello- RET"
  432. (ido-completing-read+ "Say something: " my-dynamic-collection))
  433. :to-equal "hello-world")
  434. (expect 'ido-cr+-update-dynamic-collection
  435. :to-have-been-called))
  436. (it "should allow ido flex-matching of dynamically-added completions"
  437. (expect
  438. (with-simulated-input "hello-ld RET"
  439. (ido-completing-read+ "Say something: " my-dynamic-collection))
  440. :to-equal
  441. "hello-world")
  442. (expect 'ido-cr+-update-dynamic-collection
  443. :to-have-been-called))
  444. (it "should do a dynamic update when pressing TAB"
  445. (expect
  446. (with-simulated-input "h TAB -ld RET"
  447. (ido-completing-read+ "Say something: " my-dynamic-collection))
  448. :to-equal
  449. "hello-world")
  450. (expect 'ido-cr+-update-dynamic-collection
  451. :to-have-been-called))
  452. (it "should do a dynamic update when idle"
  453. (expect
  454. (with-simulated-input
  455. ("h"
  456. (wsi-simulate-idle-time (1+ ido-cr+-dynamic-update-idle-time))
  457. "-ld RET")
  458. (ido-completing-read+ "Say something: " my-dynamic-collection))
  459. :to-equal
  460. "hello-world")
  461. (expect 'ido-cr+-update-dynamic-collection
  462. :to-have-been-called))
  463. (it "should do a dynamic update when there is only one match remaining"
  464. (expect
  465. (with-simulated-input "hell-ld RET"
  466. (ido-completing-read+ "Say something: " my-dynamic-collection))
  467. :to-equal
  468. "hello-world")
  469. (expect 'ido-cr+-update-dynamic-collection
  470. :to-have-been-called))
  471. (it "should not exit with a unique match if new matches are dynamically added"
  472. (expect
  473. (with-simulated-input ("hell TAB -ld RET")
  474. (ido-completing-read+ "Say something: " my-dynamic-collection))
  475. :to-equal
  476. "hello-world")
  477. (expect 'ido-cr+-update-dynamic-collection
  478. :to-have-been-called))
  479. (it "should exit with a match that is still unique after dynamic updating"
  480. (expect
  481. (with-simulated-input ("helic TAB")
  482. (ido-completing-read+ "Say something: " my-dynamic-collection))
  483. :to-equal
  484. "helicopter")
  485. (expect 'ido-cr+-update-dynamic-collection
  486. :to-have-been-called))
  487. (it "should suppress errors raised by dynamic completion updates"
  488. (let ((collection
  489. (completion-table-dynamic
  490. (lambda (text)
  491. (cond
  492. ((equal text "")
  493. '("hello" "goodbye" "helicopter" "helium" "goodness" "goodwill"))
  494. (t (error "This collection throws an error on a nonempty prefix"))))))
  495. ;; The test framework uses the debugger to catch error
  496. ;; stack traces, but we want to run this code as if it
  497. ;; was not being debugged.
  498. (debug-on-error nil))
  499. (expect
  500. (with-simulated-input ("hell TAB RET")
  501. (ido-completing-read+ "Say something: " collection))
  502. :to-equal
  503. "hello")))
  504. (it "should respect `ido-restrict-to-matches' when doing dynamic updates"
  505. (assume (version<= "25" emacs-version))
  506. (let ((collection
  507. (list "aaa-ddd-ggg" "aaa-eee-ggg" "aaa-fff-ggg"
  508. "bbb-ddd-ggg" "bbb-eee-ggg" "bbb-fff-ggg"
  509. "ccc-ddd-ggg" "ccc-eee-ggg" "ccc-fff-ggg"
  510. "aaa-ddd-hhh" "aaa-eee-hhh" "aaa-fff-hhh"
  511. "bbb-ddd-hhh" "bbb-eee-hhh" "bbb-fff-hhh"
  512. "ccc-ddd-hhh" "ccc-eee-hhh" "ccc-fff-hhh"
  513. "aaa-ddd-iii" "aaa-eee-iii" "aaa-fff-iii"
  514. "bbb-ddd-iii" "bbb-eee-iii" "bbb-fff-iii"
  515. "ccc-ddd-iii" "ccc-eee-iii" "ccc-fff-iii")))
  516. ;; Test the internal function
  517. (expect
  518. (ido-cr+-apply-restrictions
  519. collection
  520. (list (cons nil "bbb")
  521. (cons nil "eee")))
  522. :to-equal '("bbb-eee-ggg" "bbb-eee-hhh" "bbb-eee-iii"))
  523. ;; First verify it without a dynamic collection
  524. (expect
  525. (with-simulated-input "eee C-SPC bbb C-SPC ggg RET"
  526. (ido-completing-read+
  527. "Pick: " collection nil t nil nil (car collection)))
  528. :to-equal "bbb-eee-ggg")
  529. (expect
  530. (with-simulated-input "eee C-SPC aaa C-u C-SPC ccc C-u C-SPC ggg RET"
  531. (ido-completing-read+
  532. "Pick: " collection nil t nil nil (car collection)))
  533. :to-equal "bbb-eee-ggg")
  534. ;; Now test the same with a dynamic collection
  535. (expect
  536. (with-simulated-input "eee C-SPC bbb C-SPC ggg RET"
  537. (ido-completing-read+
  538. "Pick: " (collection-as-function collection) nil t nil nil (car collection)))
  539. :to-equal "bbb-eee-ggg")
  540. (expect
  541. (with-simulated-input "eee C-SPC aaa C-u C-SPC ccc C-u C-SPC ggg RET"
  542. (ido-completing-read+
  543. "Pick: " (collection-as-function collection) nil t nil nil (car collection)))
  544. :to-equal "bbb-eee-ggg")))
  545. ;; It turns out that even `completing-read' can't handle this
  546. ;; ridiculousness, so I'm not going to worry about it unless it
  547. ;; becomes a problem in practice.
  548. (xit "should allow exiting with a match that is only detected by `test-completion'"
  549. (let* ((real-collection '("blue" "yellow" "brown"))
  550. ;; A special dynamic collection function that always
  551. ;; returns nil except for `test-completion'.
  552. (special-collection-function
  553. (lambda (string predicate action)
  554. (pcase action
  555. ('metadata nil)
  556. (`(boundaries . _) nil)
  557. ;; `try-completion'
  558. ('nil nil)
  559. ;; `all-completions'
  560. ('t nil)
  561. ;; `test-completion'
  562. (_ (test-completion string real-collection predicate))))))
  563. ;; Verify that the collection exhibits the desired
  564. ;; pathological behavior
  565. (expect
  566. (all-completions "" special-collection-function)
  567. :to-equal nil)
  568. (expect
  569. (all-completions "yellow" special-collection-function)
  570. :to-equal nil)
  571. (expect
  572. (try-completion "yellow" special-collection-function)
  573. :to-equal nil)
  574. (expect
  575. (test-completion "yellow" special-collection-function)
  576. :to-equal t)
  577. (expect
  578. ;; Unambiguous input, but the collection function only
  579. ;; accepts exact matches, so this should fail.
  580. (with-simulated-input "yel RET RET RET"
  581. (ido-completing-read+ "Pick: " special-collection-function nil t))
  582. :to-throw 'error)
  583. (expect
  584. (with-simulated-input "yellow RET"
  585. (ido-completing-read+ "Pick: " special-collection-function nil t))
  586. :to-equal "yellow"))))
  587. (describe "with unusual inputs"
  588. (it "should accept symbols in COLLECTION"
  589. (expect
  590. (with-simulated-input "g RET"
  591. (ido-completing-read+ "Prompt: " '(blue yellow green)))
  592. :to-equal "green"))
  593. (it "should accept a mix of strings and symbols in COLLECTION"
  594. (expect
  595. (with-simulated-input "g RET"
  596. (ido-completing-read+ "Prompt: " '(blue "yellow" green)))
  597. :to-equal "green"))
  598. (it "should accept symbols in DEF"
  599. (expect
  600. (with-simulated-input "RET"
  601. (ido-completing-read+ "Prompt: " '("blue" "yellow" "brown") nil t nil nil '(brown "green")))
  602. :to-equal "brown"))
  603. (it "should accept an alist COLLECTION"
  604. (expect
  605. (with-simulated-input "RET"
  606. (ido-completing-read+
  607. "Prompt: "
  608. '(("blue" . blue-value)
  609. ("yellow" . yellow-value)
  610. (green . green-value))
  611. nil nil nil nil "green"))
  612. :to-equal "green"))
  613. (it "should accept a hash table COLLECTION"
  614. (expect
  615. (with-simulated-input "RET"
  616. (let ((collection (make-hash-table)))
  617. (puthash "blue" 'blue-value collection)
  618. (puthash "yellow" 'yellow-value collection)
  619. (puthash 'green 'green-value collection)
  620. (ido-completing-read+ "Prompt: " collection nil nil nil nil "green")))
  621. :to-equal "green"))
  622. (it "should accept an obarray COLLECTION"
  623. (expect
  624. (with-simulated-input "forward-char RET"
  625. (ido-completing-read+ "Prompt: " obarray #'commandp
  626. t nil nil "backward-char"))
  627. :to-equal "forward-char"))
  628. (it "should strip display properties from the return value"
  629. (expect
  630. (let ((string-with-display-prop "apple"))
  631. (add-text-properties 4 5 '(display "...") string-with-display-prop)
  632. (next-single-property-change
  633. 0 'display
  634. (with-simulated-input "appl RET"
  635. (ido-completing-read+ "Prompt: " (list string-with-display-prop)))))
  636. :to-be nil))))
  637. (describe "ido-ubiquitous-mode"
  638. ;; Set up a test command that calls `completing-read'
  639. (before-all
  640. (setf (symbol-function 'test-command)
  641. (lambda ()
  642. (interactive)
  643. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  644. ;; Delete the test command
  645. (after-all
  646. (setf (symbol-function 'test-command) nil))
  647. ;; Verify that the mode can be activated
  648. (it "should enable itself properly"
  649. (expect
  650. (progn
  651. (ido-ubiquitous-mode 1)
  652. (with-simulated-input "g RET"
  653. (command-execute 'test-command)))
  654. :to-equal "green"))
  655. (it "should disable itself properly"
  656. (expect
  657. (progn
  658. (ido-ubiquitous-mode 0)
  659. (with-simulated-input "g RET"
  660. (command-execute 'test-command)))
  661. :to-equal "g"))
  662. (describe "with `ido-cr+-disable-list'"
  663. (before-all
  664. (setf (symbol-function 'disabled-command)
  665. (lambda (arg)
  666. (interactive (list (completing-read "Prompt: " '("blue" "yellow" "green"))))
  667. arg)
  668. (symbol-function 'disabled-function)
  669. (lambda ()
  670. (completing-read "Prompt: " '("blue" "yellow" "green")))
  671. (symbol-function 'cmd-that-calls-disabled-function)
  672. (lambda ()
  673. (interactive)
  674. (funcall 'disabled-function))
  675. (symbol-function 'disabled-collection)
  676. (collection-as-function '("blue" "yellow" "green"))))
  677. (after-all
  678. (setf (symbol-function 'disabled-command) nil
  679. (symbol-function 'disabled-function) nil
  680. (symbol-function 'cmd-that-calls-disabled-function) nil
  681. (symbol-function 'disabled-collection) nil))
  682. ;; First verify that they work normally before disabling them
  683. (describe "when the specified functions are not disabled"
  684. (it "should not affect a non-disabled command"
  685. (expect
  686. (with-simulated-input "g RET"
  687. (call-interactively 'disabled-command))
  688. :to-equal "green"))
  689. (it "should not affect a non-disabled function"
  690. (expect
  691. (with-simulated-input "g RET"
  692. (call-interactively 'cmd-that-calls-disabled-function))
  693. :to-equal "green"))
  694. (it "should not affect a non-disabled collection"
  695. (expect
  696. (with-simulated-input "g RET"
  697. (ido-completing-read+ "Prompt: " 'disabled-collection))
  698. :to-equal "green")))
  699. (describe "when the specified functions are disabled"
  700. (before-each
  701. (setq ido-cr+-disable-list
  702. (append '(disabled-command
  703. disabled-function
  704. disabled-collection)
  705. ido-cr+-disable-list)))
  706. (it "should prevent ido in a disabled command"
  707. (expect
  708. (with-simulated-input "g RET"
  709. (call-interactively 'disabled-command))
  710. :to-equal "g"))
  711. (it "should prevent ido in a disabled function"
  712. (expect
  713. (with-simulated-input "g RET"
  714. (call-interactively 'cmd-that-calls-disabled-function))
  715. :to-equal "g"))
  716. (it "should prevent ido with a disabled collection"
  717. (expect
  718. (with-simulated-input "g RET"
  719. (ido-completing-read+ "Prompt: " 'disabled-collection))
  720. :to-equal "g")))
  721. (describe "when updating ido-cr+"
  722. (before-each
  723. (spy-on 'ido-cr+-update-disable-list :and-call-through))
  724. (it "should update the disable list when `ido-cr+-auto-update-disable-list' is t"
  725. (assume ido-cr+-disable-list)
  726. (let ((orig-disable-list ido-cr+-disable-list))
  727. (customize-set-variable 'ido-cr+-auto-update-disable-list t)
  728. (customize-set-variable 'ido-cr+-disable-list nil)
  729. (ido-cr+-maybe-update-disable-list)
  730. (expect 'ido-cr+-update-disable-list :to-have-been-called)
  731. (expect ido-cr+-disable-list :to-have-same-items-as orig-disable-list)))
  732. (it "should not update the disable list when `ido-cr+-auto-update-disable-list' is nil"
  733. (assume ido-cr+-disable-list)
  734. (let ((orig-disable-list ido-cr+-disable-list))
  735. (customize-set-variable 'ido-cr+-auto-update-disable-list nil)
  736. (customize-set-variable 'ido-cr+-disable-list nil)
  737. (ido-cr+-maybe-update-disable-list)
  738. (expect 'ido-cr+-update-disable-list :not :to-have-been-called)
  739. (expect ido-cr+-disable-list :to-have-same-items-as nil)))
  740. (it "should notify about disable list updates when `ido-cr+-auto-update-disable-list' is `notify'"
  741. (assume ido-cr+-disable-list)
  742. (spy-on 'display-warning)
  743. (let ((orig-disable-list ido-cr+-disable-list))
  744. (customize-set-variable 'ido-cr+-auto-update-disable-list 'notify)
  745. (customize-set-variable 'ido-cr+-disable-list nil)
  746. (ido-cr+-maybe-update-disable-list)
  747. (expect 'ido-cr+-update-disable-list :not :to-have-been-called)
  748. (expect 'display-warning :to-have-been-called)
  749. (expect ido-cr+-disable-list :to-have-same-items-as nil)))))
  750. (describe "with `ido-cr+-allow-list'"
  751. (before-all
  752. (setf (symbol-function 'allowed-command)
  753. (lambda (arg)
  754. (interactive
  755. (list
  756. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  757. arg)
  758. (symbol-function 'allowed-function)
  759. (lambda ()
  760. (completing-read "Prompt: " '("blue" "yellow" "green")))
  761. (symbol-function 'cmd-that-calls-allowed-function)
  762. (lambda ()
  763. (interactive)
  764. (funcall 'allowed-function))
  765. (symbol-function 'allowed-collection)
  766. (lambda (string pred action)
  767. (complete-with-action action '("blue" "yellow" "green") string pred))))
  768. (after-all
  769. (setf (symbol-function 'allowed-command) nil
  770. (symbol-function 'allowed-function) nil
  771. (symbol-function 'cmd-that-calls-allowed-function) nil
  772. (symbol-function 'allowed-collection) nil))
  773. (describe "when the allow list is inactive (i.e. everything is allowed)"
  774. (before-each
  775. (setq ido-cr+-allow-list nil))
  776. (it "should enable ido in a command"
  777. (expect
  778. (with-simulated-input "g RET"
  779. (call-interactively 'allowed-command))
  780. :to-equal "green"))
  781. (it "should enable ido in a function"
  782. (expect
  783. (with-simulated-input "g RET"
  784. (call-interactively 'cmd-that-calls-allowed-function))
  785. :to-equal "green"))
  786. (it "should enable ido for a collection"
  787. (expect
  788. (with-simulated-input "g RET"
  789. (ido-completing-read+ "Prompt: " 'allowed-collection))
  790. :to-equal "green")))
  791. (describe "when the specified functions are allowed"
  792. (before-each
  793. (setq ido-cr+-allow-list
  794. (append '(allowed-command
  795. allowed-function
  796. allowed-collection)
  797. ido-cr+-allow-list)))
  798. (it "should enable ido in an allowed command"
  799. (expect
  800. (with-simulated-input "g RET"
  801. (call-interactively 'allowed-command))
  802. :to-equal "green"))
  803. (it "should enable ido in an allowed function"
  804. (expect
  805. (with-simulated-input "g RET"
  806. (call-interactively 'cmd-that-calls-allowed-function))
  807. :to-equal "green"))
  808. (it "should enable ido for an allowed collection"
  809. (expect
  810. (with-simulated-input "g RET"
  811. (ido-completing-read+ "Prompt: " 'allowed-collection))
  812. :to-equal "green")))
  813. (describe "when the allow list is active but empty (i.e. nothing allowed)"
  814. (before-each
  815. (setq ido-cr+-allow-list (list nil)))
  816. (it "should prevent ido in a command"
  817. (expect
  818. (with-simulated-input "g RET"
  819. (call-interactively 'allowed-command))
  820. :to-equal "g"))
  821. (it "should prevent ido in a function"
  822. (expect
  823. (with-simulated-input "g RET"
  824. (call-interactively 'cmd-that-calls-allowed-function))
  825. :to-equal "g"))
  826. (it "should prevent ido for a collection"
  827. (expect
  828. (with-simulated-input "g RET"
  829. (ido-completing-read+ "Prompt: " 'allowed-collection))
  830. :to-equal "g"))))
  831. (describe "with `ido-cr+-nil-def-alternate-behavior-list'"
  832. (before-all
  833. (setf (symbol-function 'def-nil-command)
  834. (lambda (arg)
  835. (interactive
  836. (list
  837. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  838. arg)
  839. (symbol-function 'def-nil-function)
  840. (lambda ()
  841. (completing-read "Prompt: " '("blue" "yellow" "green") nil t))
  842. (symbol-function 'cmd-that-calls-def-nil-function)
  843. (lambda ()
  844. (interactive)
  845. (funcall 'def-nil-function))
  846. (symbol-function 'def-nil-collection)
  847. (lambda (string pred action)
  848. (complete-with-action action '("blue" "yellow" "green") string pred))))
  849. (after-all
  850. (setf (symbol-function 'def-nil-command) nil
  851. (symbol-function 'def-nil-function) nil
  852. (symbol-function 'cmd-that-calls-def-nil-function) nil
  853. (symbol-function 'def-nil-collection) nil))
  854. (describe "when the specified functions are not in the list"
  855. (before-each
  856. (setq ido-cr+-nil-def-alternate-behavior-list nil))
  857. (it "should use empty string default in a command"
  858. (expect
  859. (with-simulated-input "RET"
  860. (call-interactively 'def-nil-command))
  861. :to-equal ""))
  862. (it "should use empty string default in a function"
  863. (expect
  864. (with-simulated-input "RET"
  865. (call-interactively 'cmd-that-calls-def-nil-function))
  866. :to-equal ""))
  867. (it "should use empty string default for a collection"
  868. (expect
  869. (with-simulated-input "RET"
  870. (ido-completing-read+ "Prompt: " 'def-nil-collection nil t))
  871. :to-equal "")))
  872. (describe "when the specified functions are in the list"
  873. (before-each
  874. (setq ido-cr+-nil-def-alternate-behavior-list
  875. (append '(def-nil-command
  876. def-nil-function
  877. def-nil-collection)
  878. ido-cr+-nil-def-alternate-behavior-list)))
  879. (it "should not use empty string default in a command"
  880. (expect
  881. (with-simulated-input "RET"
  882. (call-interactively 'def-nil-command))
  883. :to-equal "blue"))
  884. (it "should not use empty string default in a function"
  885. (expect
  886. (with-simulated-input "RET"
  887. (call-interactively 'cmd-that-calls-def-nil-function))
  888. :to-equal "blue"))
  889. (it "should not use empty string default for a collection"
  890. (expect
  891. (with-simulated-input "RET"
  892. (ido-completing-read+ "Prompt: " 'def-nil-collection nil t))
  893. :to-equal "blue"))))
  894. ;; Test is currently disabled pending additional information
  895. (xit "should not hang or error when deleting characters in `org-refile' (issue #152)"
  896. (expect
  897. (progn
  898. (ido-ubiquitous-mode 1)
  899. (save-excursion
  900. (with-temp-buffer
  901. (org-mode)
  902. (insert (s-trim "
  903. * Heading 1
  904. ** Subheading 1.1
  905. ** Subheading 1.2
  906. ** Subheading 1.3
  907. * Heading 2
  908. * Heading 3
  909. "))
  910. (goto-char (point-max))
  911. ;; TODO Figure out what else needs to be set up to call
  912. ;; `org-refile'
  913. (with-simulated-input
  914. "Heading DEL DEL DEL DEL DEL RET"
  915. (command-execute 'org-refile)))))
  916. :not :to-throw)))
  917. (describe "regressions should not occur for"
  918. ;; Disabled because I think the nix CI emacs has no info pages, so
  919. ;; the completion for `Info-menu' has nothing to do. However, this
  920. ;; should be thoroughly fixed by now.
  921. (xit "issue #151: should not hang or error when cycling matches in `Info-menu'"
  922. (expect
  923. (progn
  924. (ido-ubiquitous-mode 1)
  925. (with-temp-info-buffer
  926. (with-simulated-input
  927. '((ido-next-match)
  928. (wsi-simulate-idle-time 5)
  929. (ido-next-match)
  930. (wsi-simulate-idle-time 5)
  931. (ido-next-match)
  932. (wsi-simulate-idle-time 5)
  933. (ido-next-match)
  934. (wsi-simulate-idle-time 5)
  935. "RET")
  936. (command-execute 'Info-menu))))
  937. :not :to-throw))
  938. (it "issue #153: should preserve the selected item when doing a deferred dynamic update"
  939. (expect
  940. (with-simulated-input
  941. ("Emacs"
  942. (ido-next-match)
  943. (wsi-simulate-idle-time 5)
  944. "RET")
  945. (ido-completing-read+
  946. "Choose: "
  947. (collection-as-function '("Emacs" "Emacs A" "Emacs B" "Emacs C"))))
  948. :to-equal "Emacs A"))))
  949. ;;; test-ido-completing-read+.el ends here