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

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055
  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 avoid end-of-buffer errors, which are
  290. ;; irrelevant to this test.
  291. (spy-on 'scroll-other-window)
  292. (expect
  293. (with-simulated-input "b C-j C-j C-j"
  294. (ido-completing-read+
  295. "Prompt: "
  296. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  297. :to-throw)
  298. ;; First press of C-j should complete to "blue" after the
  299. ;; first b, but then get stuck on the choice for the second b.
  300. (expect
  301. (with-simulated-input "b C-j b C-j C-j C-j"
  302. (ido-completing-read+
  303. "Prompt: "
  304. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  305. :to-throw))
  306. (it "should allow exiting with an unambiguous match"
  307. (expect
  308. (with-simulated-input "b C-j b C-j e C-j C-j"
  309. (ido-completing-read+
  310. "Prompt: "
  311. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  312. :to-equal "blueberry")
  313. ;; The "C-j" should complete to "bluegrass" and return, because
  314. ;; `ido-confirm-unique-completion is nil.
  315. (expect
  316. (with-simulated-input "b l u e g C-j"
  317. (ido-completing-read+
  318. "Prompt: "
  319. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  320. :to-equal "bluegrass"))
  321. (it "should require an extra C-j to exit when `ido-confirm-unique-completion' is non-nil"
  322. (setq ido-confirm-unique-completion t)
  323. ;; Now the first "C-j" should complete to "bluegrass" but should
  324. ;; not return.
  325. (expect
  326. (with-simulated-input "b l u e g C-j"
  327. (ido-completing-read+
  328. "Prompt: "
  329. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  330. :to-throw)
  331. ;; The first "C-j" should complete to "bluegrass", and the second
  332. ;; should return.
  333. (expect
  334. (with-simulated-input "b l u e g C-j C-j"
  335. (ido-completing-read+
  336. "Prompt: "
  337. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  338. :to-equal "bluegrass"))
  339. ;; Finally, a test for the expected wrong behavior without
  340. ;; ido-cr+. If ido.el ever fixes this bug, it will cause this test
  341. ;; to fail as a signal that the workaround can be phased out.
  342. (it "should return a non-match when ordinary `ido-completing-read' is used"
  343. (expect
  344. (with-simulated-input "b C-j"
  345. (ido-completing-read
  346. "Prompt: "
  347. '("bluebird" "blues" "bluegrass" "blueberry" "yellow" "green") nil t))
  348. :to-equal "b")))
  349. (describe "when INHERIT-INPUT-METHOD is non-nil"
  350. (before-each
  351. (spy-on 'ido-completing-read :and-call-through))
  352. (it "should not fall back if `current-input-method' is nil"
  353. (expect
  354. (let ((current-input-method nil))
  355. (with-simulated-input "g RET"
  356. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil nil nil nil t))
  357. :to-equal "green"))
  358. (expect 'ido-completing-read :to-have-been-called))
  359. (it "should fall back if `current-input-method' is non-nil"
  360. (expect
  361. (let ((current-input-method 'ucs))
  362. (with-simulated-input "g RET"
  363. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green") nil nil nil nil nil t))
  364. :to-equal "green"))
  365. (expect 'ido-completing-read :not :to-have-been-called)))
  366. (describe "with manual fallback shortcuts"
  367. (it "should not fall back when C-b or C-f is used in the middle of the input"
  368. (expect
  369. ;; C-b/f not at beginning/end of input should not fall back
  370. (with-simulated-input "g C-b C-f RET"
  371. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))
  372. :to-equal "green"))
  373. (it "should fall back on C-f at end of input"
  374. (expect
  375. ;; C-f at end of input should fall back
  376. (with-simulated-input "g C-f RET"
  377. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))
  378. :to-equal "g"))
  379. (it "should not fall back from repeated C-b that hits the start of input"
  380. (expect
  381. ;; Repeated C-b should not fall back
  382. (with-simulated-input "g C-b C-b C-b C-b RET"
  383. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))
  384. :to-equal "green"))
  385. (it "should fall back on C-b at beginning of input (if previous action was not C-b)"
  386. (expect
  387. ;; C-b at beginning of line should fall back (if previous action
  388. ;; was not also C-b)
  389. (with-simulated-input "g C-b x DEL C-b RET"
  390. (ido-completing-read+ "Prompt: " '("blue" "yellow" "green")))
  391. :to-equal "g")))
  392. (describe "with a workaround for an bug with non-nil `ido-enable-dot-prefix'"
  393. ;; See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=26997
  394. ;; for more information on this bug.
  395. (before-each
  396. (setq ido-enable-dot-prefix t))
  397. (it "should not throw an error when \"\" is in the collection"
  398. (expect
  399. (with-simulated-input "RET"
  400. (ido-completing-read+ "Pick: " '("" "aaa" "aab" "aac")))
  401. :to-equal "")
  402. (expect
  403. (with-simulated-input "a a b RET"
  404. (ido-completing-read+ "Pick: " '("" "aaa" "aab" "aac")))
  405. :to-equal "aab")))
  406. (describe "with dynamic collections"
  407. (before-all
  408. (setq my-dynamic-collection
  409. (completion-table-dynamic
  410. (lambda (text)
  411. (cond
  412. ;; Sub-completions for "hello"
  413. ((s-prefix-p "hello" text)
  414. '("hello" "hello-world" "hello-everyone" "hello-universe"))
  415. ;; Sub-completions for "goodbye"
  416. ((s-prefix-p "goodbye" text)
  417. '("goodbye" "goodbye-world" "goodbye-everyone" "goodbye-universe"))
  418. ;; General completions
  419. (t
  420. '("hello" "goodbye" "helicopter" "helium" "goodness" "goodwill")))))))
  421. (after-all
  422. (setq my-dynamic-collection nil))
  423. (before-each
  424. (setq ido-enable-flex-matching t
  425. ido-confirm-unique-completion nil)
  426. (spy-on 'ido-cr+-update-dynamic-collection
  427. :and-call-through))
  428. (it "should allow selection of dynamically-added completions"
  429. (expect
  430. (with-simulated-input "hello- RET"
  431. (ido-completing-read+ "Say something: " my-dynamic-collection))
  432. :to-equal "hello-world")
  433. (expect 'ido-cr+-update-dynamic-collection
  434. :to-have-been-called))
  435. (it "should allow ido flex-matching of dynamically-added completions"
  436. (expect
  437. (with-simulated-input "hello-ld RET"
  438. (ido-completing-read+ "Say something: " my-dynamic-collection))
  439. :to-equal
  440. "hello-world")
  441. (expect 'ido-cr+-update-dynamic-collection
  442. :to-have-been-called))
  443. (it "should do a dynamic update when pressing TAB"
  444. (expect
  445. (with-simulated-input "h TAB -ld RET"
  446. (ido-completing-read+ "Say something: " my-dynamic-collection))
  447. :to-equal
  448. "hello-world")
  449. (expect 'ido-cr+-update-dynamic-collection
  450. :to-have-been-called))
  451. (it "should do a dynamic update when idle"
  452. (expect
  453. (with-simulated-input
  454. ("h"
  455. (wsi-simulate-idle-time (1+ ido-cr+-dynamic-update-idle-time))
  456. "-ld RET")
  457. (ido-completing-read+ "Say something: " my-dynamic-collection))
  458. :to-equal
  459. "hello-world")
  460. (expect 'ido-cr+-update-dynamic-collection
  461. :to-have-been-called))
  462. (it "should do a dynamic update when there is only one match remaining"
  463. (expect
  464. (with-simulated-input "hell-ld RET"
  465. (ido-completing-read+ "Say something: " my-dynamic-collection))
  466. :to-equal
  467. "hello-world")
  468. (expect 'ido-cr+-update-dynamic-collection
  469. :to-have-been-called))
  470. (it "should not exit with a unique match if new matches are dynamically added"
  471. (expect
  472. (with-simulated-input ("hell TAB -ld RET")
  473. (ido-completing-read+ "Say something: " my-dynamic-collection))
  474. :to-equal
  475. "hello-world")
  476. (expect 'ido-cr+-update-dynamic-collection
  477. :to-have-been-called))
  478. (it "should exit with a match that is still unique after dynamic updating"
  479. (expect
  480. (with-simulated-input ("helic TAB")
  481. (ido-completing-read+ "Say something: " my-dynamic-collection))
  482. :to-equal
  483. "helicopter")
  484. (expect 'ido-cr+-update-dynamic-collection
  485. :to-have-been-called))
  486. (it "should suppress errors raised by dynamic completion updates"
  487. (let ((collection
  488. (completion-table-dynamic
  489. (lambda (text)
  490. (cond
  491. ((equal text "")
  492. '("hello" "goodbye" "helicopter" "helium" "goodness" "goodwill"))
  493. (t (error "This collection throws an error on a nonempty prefix"))))))
  494. ;; The test framework uses the debugger to catch error
  495. ;; stack traces, but we want to run this code as if it
  496. ;; was not being debugged.
  497. (debug-on-error nil))
  498. (expect
  499. (with-simulated-input ("hell TAB RET")
  500. (ido-completing-read+ "Say something: " collection))
  501. :to-equal
  502. "hello")))
  503. (it "should respect `ido-restrict-to-matches' when doing dynamic updates"
  504. (assume (version<= "25" emacs-version))
  505. (let ((collection
  506. (list "aaa-ddd-ggg" "aaa-eee-ggg" "aaa-fff-ggg"
  507. "bbb-ddd-ggg" "bbb-eee-ggg" "bbb-fff-ggg"
  508. "ccc-ddd-ggg" "ccc-eee-ggg" "ccc-fff-ggg"
  509. "aaa-ddd-hhh" "aaa-eee-hhh" "aaa-fff-hhh"
  510. "bbb-ddd-hhh" "bbb-eee-hhh" "bbb-fff-hhh"
  511. "ccc-ddd-hhh" "ccc-eee-hhh" "ccc-fff-hhh"
  512. "aaa-ddd-iii" "aaa-eee-iii" "aaa-fff-iii"
  513. "bbb-ddd-iii" "bbb-eee-iii" "bbb-fff-iii"
  514. "ccc-ddd-iii" "ccc-eee-iii" "ccc-fff-iii")))
  515. ;; Test the internal function
  516. (expect
  517. (ido-cr+-apply-restrictions
  518. collection
  519. (list (cons nil "bbb")
  520. (cons nil "eee")))
  521. :to-equal '("bbb-eee-ggg" "bbb-eee-hhh" "bbb-eee-iii"))
  522. ;; First verify it without a dynamic collection
  523. (expect
  524. (with-simulated-input "eee C-SPC bbb C-SPC ggg RET"
  525. (ido-completing-read+
  526. "Pick: " collection nil t nil nil (car collection)))
  527. :to-equal "bbb-eee-ggg")
  528. (expect
  529. (with-simulated-input "eee C-SPC aaa C-u C-SPC ccc C-u C-SPC ggg RET"
  530. (ido-completing-read+
  531. "Pick: " collection nil t nil nil (car collection)))
  532. :to-equal "bbb-eee-ggg")
  533. ;; Now test the same with a dynamic collection
  534. (expect
  535. (with-simulated-input "eee C-SPC bbb C-SPC ggg RET"
  536. (ido-completing-read+
  537. "Pick: " (collection-as-function collection) nil t nil nil (car collection)))
  538. :to-equal "bbb-eee-ggg")
  539. (expect
  540. (with-simulated-input "eee C-SPC aaa C-u C-SPC ccc C-u C-SPC ggg RET"
  541. (ido-completing-read+
  542. "Pick: " (collection-as-function collection) nil t nil nil (car collection)))
  543. :to-equal "bbb-eee-ggg")))
  544. ;; It turns out that even `completing-read' can't handle this
  545. ;; ridiculousness, so I'm not going to worry about it unless it
  546. ;; becomes a problem in practice.
  547. (xit "should allow exiting with a match that is only detected by `test-completion'"
  548. (let* ((real-collection '("blue" "yellow" "brown"))
  549. ;; A special dynamic collection function that always
  550. ;; returns nil except for `test-completion'.
  551. (special-collection-function
  552. (lambda (string predicate action)
  553. (pcase action
  554. ('metadata nil)
  555. (`(boundaries . _) nil)
  556. ;; `try-completion'
  557. ('nil nil)
  558. ;; `all-completions'
  559. ('t nil)
  560. ;; `test-completion'
  561. (_ (test-completion string real-collection predicate))))))
  562. ;; Verify that the collection exhibits the desired
  563. ;; pathological behavior
  564. (expect
  565. (all-completions "" special-collection-function)
  566. :to-equal nil)
  567. (expect
  568. (all-completions "yellow" special-collection-function)
  569. :to-equal nil)
  570. (expect
  571. (try-completion "yellow" special-collection-function)
  572. :to-equal nil)
  573. (expect
  574. (test-completion "yellow" special-collection-function)
  575. :to-equal t)
  576. (expect
  577. ;; Unambiguous input, but the collection function only
  578. ;; accepts exact matches, so this should fail.
  579. (with-simulated-input "yel RET RET RET"
  580. (ido-completing-read+ "Pick: " special-collection-function nil t))
  581. :to-throw 'error)
  582. (expect
  583. (with-simulated-input "yellow RET"
  584. (ido-completing-read+ "Pick: " special-collection-function nil t))
  585. :to-equal "yellow"))))
  586. (describe "with unusual inputs"
  587. (it "should accept symbols in COLLECTION"
  588. (expect
  589. (with-simulated-input "g RET"
  590. (ido-completing-read+ "Prompt: " '(blue yellow green)))
  591. :to-equal "green"))
  592. (it "should accept a mix of strings and symbols in COLLECTION"
  593. (expect
  594. (with-simulated-input "g RET"
  595. (ido-completing-read+ "Prompt: " '(blue "yellow" green)))
  596. :to-equal "green"))
  597. (it "should accept symbols in DEF"
  598. (expect
  599. (with-simulated-input "RET"
  600. (ido-completing-read+ "Prompt: " '("blue" "yellow" "brown") nil t nil nil '(brown "green")))
  601. :to-equal "brown"))
  602. (it "should accept an alist COLLECTION"
  603. (expect
  604. (with-simulated-input "RET"
  605. (ido-completing-read+
  606. "Prompt: "
  607. '(("blue" . blue-value)
  608. ("yellow" . yellow-value)
  609. (green . green-value))
  610. nil nil nil nil "green"))
  611. :to-equal "green"))
  612. (it "should accept a hash table COLLECTION"
  613. (expect
  614. (with-simulated-input "RET"
  615. (let ((collection (make-hash-table)))
  616. (puthash "blue" 'blue-value collection)
  617. (puthash "yellow" 'yellow-value collection)
  618. (puthash 'green 'green-value collection)
  619. (ido-completing-read+ "Prompt: " collection nil nil nil nil "green")))
  620. :to-equal "green"))
  621. (it "should accept an obarray COLLECTION"
  622. (expect
  623. (with-simulated-input "forward-char RET"
  624. (ido-completing-read+ "Prompt: " obarray #'commandp
  625. t nil nil "backward-char"))
  626. :to-equal "forward-char"))))
  627. (describe "ido-ubiquitous-mode"
  628. ;; Set up a test command that calls `completing-read'
  629. (before-all
  630. (setf (symbol-function 'test-command)
  631. (lambda ()
  632. (interactive)
  633. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  634. ;; Delete the test command
  635. (after-all
  636. (setf (symbol-function 'test-command) nil))
  637. ;; Verify that the mode can be activated
  638. (it "should enable itself properly"
  639. (expect
  640. (progn
  641. (ido-ubiquitous-mode 1)
  642. (with-simulated-input "g RET"
  643. (command-execute 'test-command)))
  644. :to-equal "green"))
  645. (it "should disable itself properly"
  646. (expect
  647. (progn
  648. (ido-ubiquitous-mode 0)
  649. (with-simulated-input "g RET"
  650. (command-execute 'test-command)))
  651. :to-equal "g"))
  652. (describe "with `ido-cr+-disable-list'"
  653. (before-all
  654. (setf (symbol-function 'disabled-command)
  655. (lambda (arg)
  656. (interactive (list (completing-read "Prompt: " '("blue" "yellow" "green"))))
  657. arg)
  658. (symbol-function 'disabled-function)
  659. (lambda ()
  660. (completing-read "Prompt: " '("blue" "yellow" "green")))
  661. (symbol-function 'cmd-that-calls-disabled-function)
  662. (lambda ()
  663. (interactive)
  664. (funcall 'disabled-function))
  665. (symbol-function 'disabled-collection)
  666. (collection-as-function '("blue" "yellow" "green"))))
  667. (after-all
  668. (setf (symbol-function 'disabled-command) nil
  669. (symbol-function 'disabled-function) nil
  670. (symbol-function 'cmd-that-calls-disabled-function) nil
  671. (symbol-function 'disabled-collection) nil))
  672. ;; First verify that they work normally before disabling them
  673. (describe "when the specified functions are not disabled"
  674. (it "should not affect a non-disabled command"
  675. (expect
  676. (with-simulated-input "g RET"
  677. (call-interactively 'disabled-command))
  678. :to-equal "green"))
  679. (it "should not affect a non-disabled function"
  680. (expect
  681. (with-simulated-input "g RET"
  682. (call-interactively 'cmd-that-calls-disabled-function))
  683. :to-equal "green"))
  684. (it "should not affect a non-disabled collection"
  685. (expect
  686. (with-simulated-input "g RET"
  687. (ido-completing-read+ "Prompt: " 'disabled-collection))
  688. :to-equal "green")))
  689. (describe "when the specified functions are disabled"
  690. (before-each
  691. (setq ido-cr+-disable-list
  692. (append '(disabled-command
  693. disabled-function
  694. disabled-collection)
  695. ido-cr+-disable-list)))
  696. (it "should prevent ido in a disabled command"
  697. (expect
  698. (with-simulated-input "g RET"
  699. (call-interactively 'disabled-command))
  700. :to-equal "g"))
  701. (it "should prevent ido in a disabled function"
  702. (expect
  703. (with-simulated-input "g RET"
  704. (call-interactively 'cmd-that-calls-disabled-function))
  705. :to-equal "g"))
  706. (it "should prevent ido with a disabled collection"
  707. (expect
  708. (with-simulated-input "g RET"
  709. (ido-completing-read+ "Prompt: " 'disabled-collection))
  710. :to-equal "g")))
  711. (describe "when updating ido-cr+"
  712. (before-each
  713. (spy-on 'ido-cr+-update-disable-list :and-call-through))
  714. (it "should update the disable list when `ido-cr+-auto-update-disable-list' is t"
  715. (assume ido-cr+-disable-list)
  716. (let ((orig-disable-list ido-cr+-disable-list))
  717. (customize-set-variable 'ido-cr+-auto-update-disable-list t)
  718. (customize-set-variable 'ido-cr+-disable-list nil)
  719. (ido-cr+-maybe-update-disable-list)
  720. (expect 'ido-cr+-update-disable-list :to-have-been-called)
  721. (expect ido-cr+-disable-list :to-have-same-items-as orig-disable-list)))
  722. (it "should not update the disable list when `ido-cr+-auto-update-disable-list' is nil"
  723. (assume ido-cr+-disable-list)
  724. (let ((orig-disable-list ido-cr+-disable-list))
  725. (customize-set-variable 'ido-cr+-auto-update-disable-list nil)
  726. (customize-set-variable 'ido-cr+-disable-list nil)
  727. (ido-cr+-maybe-update-disable-list)
  728. (expect 'ido-cr+-update-disable-list :not :to-have-been-called)
  729. (expect ido-cr+-disable-list :to-have-same-items-as nil)))
  730. (it "should notify about disable list updates when `ido-cr+-auto-update-disable-list' is `notify'"
  731. (assume ido-cr+-disable-list)
  732. (spy-on 'display-warning)
  733. (let ((orig-disable-list ido-cr+-disable-list))
  734. (customize-set-variable 'ido-cr+-auto-update-disable-list 'notify)
  735. (customize-set-variable 'ido-cr+-disable-list nil)
  736. (ido-cr+-maybe-update-disable-list)
  737. (expect 'ido-cr+-update-disable-list :not :to-have-been-called)
  738. (expect 'display-warning :to-have-been-called)
  739. (expect ido-cr+-disable-list :to-have-same-items-as nil)))))
  740. (describe "with `ido-cr+-allow-list'"
  741. (before-all
  742. (setf (symbol-function 'allowed-command)
  743. (lambda (arg)
  744. (interactive
  745. (list
  746. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  747. arg)
  748. (symbol-function 'allowed-function)
  749. (lambda ()
  750. (completing-read "Prompt: " '("blue" "yellow" "green")))
  751. (symbol-function 'cmd-that-calls-allowed-function)
  752. (lambda ()
  753. (interactive)
  754. (funcall 'allowed-function))
  755. (symbol-function 'allowed-collection)
  756. (lambda (string pred action)
  757. (complete-with-action action '("blue" "yellow" "green") string pred))))
  758. (after-all
  759. (setf (symbol-function 'allowed-command) nil
  760. (symbol-function 'allowed-function) nil
  761. (symbol-function 'cmd-that-calls-allowed-function) nil
  762. (symbol-function 'allowed-collection) nil))
  763. (describe "when the allow list is inactive (i.e. everything is allowed)"
  764. (before-each
  765. (setq ido-cr+-allow-list nil))
  766. (it "should enable ido in a command"
  767. (expect
  768. (with-simulated-input "g RET"
  769. (call-interactively 'allowed-command))
  770. :to-equal "green"))
  771. (it "should enable ido in a function"
  772. (expect
  773. (with-simulated-input "g RET"
  774. (call-interactively 'cmd-that-calls-allowed-function))
  775. :to-equal "green"))
  776. (it "should enable ido for a collection"
  777. (expect
  778. (with-simulated-input "g RET"
  779. (ido-completing-read+ "Prompt: " 'allowed-collection))
  780. :to-equal "green")))
  781. (describe "when the specified functions are allowed"
  782. (before-each
  783. (setq ido-cr+-allow-list
  784. (append '(allowed-command
  785. allowed-function
  786. allowed-collection)
  787. ido-cr+-allow-list)))
  788. (it "should enable ido in an allowed command"
  789. (expect
  790. (with-simulated-input "g RET"
  791. (call-interactively 'allowed-command))
  792. :to-equal "green"))
  793. (it "should enable ido in an allowed function"
  794. (expect
  795. (with-simulated-input "g RET"
  796. (call-interactively 'cmd-that-calls-allowed-function))
  797. :to-equal "green"))
  798. (it "should enable ido for an allowed collection"
  799. (expect
  800. (with-simulated-input "g RET"
  801. (ido-completing-read+ "Prompt: " 'allowed-collection))
  802. :to-equal "green")))
  803. (describe "when the allow list is active but empty (i.e. nothing allowed)"
  804. (before-each
  805. (setq ido-cr+-allow-list (list nil)))
  806. (it "should prevent ido in a command"
  807. (expect
  808. (with-simulated-input "g RET"
  809. (call-interactively 'allowed-command))
  810. :to-equal "g"))
  811. (it "should prevent ido in a function"
  812. (expect
  813. (with-simulated-input "g RET"
  814. (call-interactively 'cmd-that-calls-allowed-function))
  815. :to-equal "g"))
  816. (it "should prevent ido for a collection"
  817. (expect
  818. (with-simulated-input "g RET"
  819. (ido-completing-read+ "Prompt: " 'allowed-collection))
  820. :to-equal "g"))))
  821. (describe "with `ido-cr+-nil-def-alternate-behavior-list'"
  822. (before-all
  823. (setf (symbol-function 'def-nil-command)
  824. (lambda (arg)
  825. (interactive
  826. (list
  827. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  828. arg)
  829. (symbol-function 'def-nil-function)
  830. (lambda ()
  831. (completing-read "Prompt: " '("blue" "yellow" "green") nil t))
  832. (symbol-function 'cmd-that-calls-def-nil-function)
  833. (lambda ()
  834. (interactive)
  835. (funcall 'def-nil-function))
  836. (symbol-function 'def-nil-collection)
  837. (lambda (string pred action)
  838. (complete-with-action action '("blue" "yellow" "green") string pred))))
  839. (after-all
  840. (setf (symbol-function 'def-nil-command) nil
  841. (symbol-function 'def-nil-function) nil
  842. (symbol-function 'cmd-that-calls-def-nil-function) nil
  843. (symbol-function 'def-nil-collection) nil))
  844. (describe "when the specified functions are not in the list"
  845. (before-each
  846. (setq ido-cr+-nil-def-alternate-behavior-list nil))
  847. (it "should use empty string default in a command"
  848. (expect
  849. (with-simulated-input "RET"
  850. (call-interactively 'def-nil-command))
  851. :to-equal ""))
  852. (it "should use empty string default in a function"
  853. (expect
  854. (with-simulated-input "RET"
  855. (call-interactively 'cmd-that-calls-def-nil-function))
  856. :to-equal ""))
  857. (it "should use empty string default for a collection"
  858. (expect
  859. (with-simulated-input "RET"
  860. (ido-completing-read+ "Prompt: " 'def-nil-collection nil t))
  861. :to-equal "")))
  862. (describe "when the specified functions are in the list"
  863. (before-each
  864. (setq ido-cr+-nil-def-alternate-behavior-list
  865. (append '(def-nil-command
  866. def-nil-function
  867. def-nil-collection)
  868. ido-cr+-nil-def-alternate-behavior-list)))
  869. (it "should not use empty string default in a command"
  870. (expect
  871. (with-simulated-input "RET"
  872. (call-interactively 'def-nil-command))
  873. :to-equal "blue"))
  874. (it "should not use empty string default in a function"
  875. (expect
  876. (with-simulated-input "RET"
  877. (call-interactively 'cmd-that-calls-def-nil-function))
  878. :to-equal "blue"))
  879. (it "should not use empty string default for a collection"
  880. (expect
  881. (with-simulated-input "RET"
  882. (ido-completing-read+ "Prompt: " 'def-nil-collection nil t))
  883. :to-equal "blue"))))
  884. ;; Test is currently disabled pending additional information
  885. (xit "should not hang or error when deleting characters in `org-refile' (issue #152)"
  886. (expect
  887. (progn
  888. (ido-ubiquitous-mode 1)
  889. (save-excursion
  890. (with-temp-buffer
  891. (org-mode)
  892. (insert (s-trim "
  893. * Heading 1
  894. ** Subheading 1.1
  895. ** Subheading 1.2
  896. ** Subheading 1.3
  897. * Heading 2
  898. * Heading 3
  899. "))
  900. (goto-char (point-max))
  901. ;; TODO Figure out what else needs to be set up to call
  902. ;; `org-refile'
  903. (with-simulated-input
  904. "Heading DEL DEL DEL DEL DEL RET"
  905. (command-execute 'org-refile)))))
  906. :not :to-throw)))
  907. (describe "regressions should not occur for"
  908. ;; Disabled because I think the nix CI emacs has no info pages, so
  909. ;; the completion for `Info-menu' has nothing to do. However, this
  910. ;; should be thoroughly fixed by now.
  911. (xit "issue #151: should not hang or error when cycling matches in `Info-menu'"
  912. (expect
  913. (progn
  914. (ido-ubiquitous-mode 1)
  915. (with-temp-info-buffer
  916. (with-simulated-input
  917. '((ido-next-match)
  918. (wsi-simulate-idle-time 5)
  919. (ido-next-match)
  920. (wsi-simulate-idle-time 5)
  921. (ido-next-match)
  922. (wsi-simulate-idle-time 5)
  923. (ido-next-match)
  924. (wsi-simulate-idle-time 5)
  925. "RET")
  926. (command-execute 'Info-menu))))
  927. :not :to-throw))
  928. (it "issue #153: should preserve the selected item when doing a deferred dynamic update"
  929. (expect
  930. (with-simulated-input
  931. ("Emacs"
  932. (ido-next-match)
  933. (wsi-simulate-idle-time 5)
  934. "RET")
  935. (ido-completing-read+
  936. "Choose: "
  937. (collection-as-function '("Emacs" "Emacs A" "Emacs B" "Emacs C"))))
  938. :to-equal "Emacs A"))))
  939. ;;; test-ido-completing-read+.el ends here