ido-ubiquitous-test.el 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. ;;; ido-ubiquitous-test.el --- -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2015 Ryan C. Thompson
  3. ;; Filename: ido-ubiquitous-test.el
  4. ;; Author: Ryan C. Thompson
  5. ;; Created: Tue Oct 6 20:52:45 2015 (-0700)
  6. ;; This file is NOT part of GNU Emacs.
  7. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  8. ;;
  9. ;; This program is free software: you can redistribute it and/or modify
  10. ;; it under the terms of the GNU General Public License as published by
  11. ;; the Free Software Foundation, either version 3 of the License, or (at
  12. ;; your option) any later version.
  13. ;;
  14. ;; This program is distributed in the hope that it will be useful, but
  15. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. ;; General Public License for more details.
  18. ;;
  19. ;; You should have received a copy of the GNU General Public License
  20. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  21. ;;
  22. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  23. ;;
  24. ;;; Code:
  25. (require 'ido-completing-read+)
  26. (require 'ido-ubiquitous)
  27. (require 'ert)
  28. (require 'cl-lib)
  29. ;; This is a series of macros to facilitate the testing of completion
  30. ;; non-interactively by simulating input.
  31. (defmacro keyboard-quit-to-error (&rest body)
  32. "Evaluate BODY but signal an error on `keyboard-quit'."
  33. `(condition-case nil
  34. (progn ,@body)
  35. (quit
  36. (error "Caught `keyboard-quit'"))))
  37. (defmacro with-simulated-input (keys &rest body)
  38. "Eval body with KEYS as simulated input.
  39. This macro is intended for testing normally interactive functions
  40. by simulating input. If BODY tries to read more input events than
  41. KEYS provides, `keyboard-quit' is invoked (by means of appending
  42. multple C-g keys to KEYS). This is to ensure that BODY will never
  43. block waiting for input, since this macro is intended for
  44. noninteractive use. As such, BODY should not invoke
  45. `keyboard-quit' under normal operation, and KEYS should not
  46. include C-g, or this macro will interpret it as reading past the
  47. end of input."
  48. ;; It would be better to detect end-of-input by overriding
  49. ;; `read-event' to throw an error, since theoretically C-g could be
  50. ;; rebound to something other than `keyboard-quit'. But apparently
  51. ;; some functions read input directly in C code, and redefining
  52. ;; `read-event' has no effect on those. So the suboptimal solution
  53. ;; is to rely on C-g.
  54. (declare (indent 1))
  55. `(let* ((key-sequence (listify-key-sequence (kbd ,keys)))
  56. (C-g-key-sequence
  57. (listify-key-sequence
  58. ;; We *really* want to trigger `keyboard-quit' if we reach
  59. ;; the end of the input.
  60. (kbd "C-g C-g C-g C-g C-g C-g C-g")))
  61. (unread-command-events
  62. (append key-sequence C-g-key-sequence)))
  63. (when (member (car C-g-key-sequence) key-sequence)
  64. (error "KEYS must not include C-g"))
  65. (condition-case nil
  66. (progn ,@body)
  67. (quit
  68. (error "Reached end of simulated input while evaluating body")))))
  69. (defmacro with-mode (mode arg &rest body)
  70. "Eval (MODE ARG), then body, then restore previous status of MODE.
  71. This will only work on modes that respect the normal conventions
  72. for activation and deactivation."
  73. (declare (indent 2))
  74. (let* ((orig-status (eval mode))
  75. (restore-arg (if orig-status 1 0)))
  76. `(unwind-protect
  77. (progn
  78. (,mode ,arg)
  79. ,@body)
  80. (,mode ,restore-arg))))
  81. (defmacro with-ido-ubiquitous-standard-env (&rest body)
  82. "Execute BODY with standard ido-ubiquitous settings.\n\nAll ido-ubiquitous and ido-cr+ options will be let-bound to their\ndefault values, and `ido-ubiquitous-mode' will be enabled."
  83. (declare (indent 0))
  84. (let*
  85. ((ido-ubiquitous-options
  86. '(ido-ubiquitous-allow-on-functional-collection
  87. ido-ubiquitous-command-overrides
  88. ido-ubiquitous-debug-mode
  89. ido-ubiquitous-default-state
  90. ido-ubiquitous-function-overrides
  91. ido-cr+-fallback-function
  92. ido-cr+-max-items
  93. ido-cr+-replace-completely))
  94. (idu-bindings
  95. (cl-loop for var in ido-ubiquitous-options collect
  96. (list var
  97. (list 'quote
  98. (default-value var))))))
  99. `(with-mode ido-ubiquitous-mode 1
  100. (let ,idu-bindings ,@body))))
  101. (defun collection-as-function (collection)
  102. "Return a function equivalent to COLLECTION.
  103. The returned function will work equivalently to COLLECTION when
  104. passed to `all-completions' and `try-completion'."
  105. (completion-table-dynamic (lambda (string) (all-completions string collection))))
  106. (cl-defmacro should-with-tag (form &key tag)
  107. "Equivalent to `(should FORM)' but with a tag on the output.
  108. This is useful if the same `should' form will be called multiple
  109. times in different contexts. Each test can pass a different tag
  110. so it's clear in the ERT output which context is causing the
  111. failure."
  112. `(if ,tag
  113. (should (and ,tag ,form))
  114. (should ,form)))
  115. (defun plist-delete (plist property)
  116. "Delete PROPERTY from PLIST.
  117. This is in contrast to merely setting it to 0."
  118. (let (p)
  119. (while plist
  120. (if (not (eq property (car plist)))
  121. (setq p (plist-put p (car plist) (nth 1 plist))))
  122. (setq plist (cddr plist)))
  123. p))
  124. (cl-defmacro should-error-with-tag (form &rest other-keys &key tag &allow-other-keys)
  125. "Equivalent to `(should FORM)' but with a tag on the output.
  126. See `should-with-tag'."
  127. (setq other-keys (plist-delete other-keys :tag))
  128. `(if ,tag
  129. (should-error (and ,tag ,form) ,@other-keys)
  130. (should-error ,form ,@other-keys)))
  131. (defun test-ido-ubiquitous-expected-mode (override &optional tag)
  132. "Test whether observed ido-ubiquitous behavior matches OVERRIDE."
  133. (declare (indent 1))
  134. (if (eq override 'disable)
  135. (progn
  136. (should-with-tag
  137. ;; Verify that we get standard completion
  138. (string=
  139. "g"
  140. (with-simulated-input "g RET"
  141. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  142. :tag tag)
  143. (should-with-tag
  144. (string=
  145. "green"
  146. (with-simulated-input "g RET"
  147. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  148. :tag tag)
  149. ;; Standard completion should refuse to finish with incomplete
  150. ;; input if match is required
  151. (should-error-with-tag
  152. (with-simulated-input "b RET"
  153. (completing-read "Prompt: " '("brown" "blue" "yellow" "green") nil t))
  154. :type 'error
  155. :tag tag))
  156. ;; Common tests whenever ido-ubiquitous is enabled in any way
  157. (should-with-tag
  158. ;; Verify that ido completion is active
  159. (string=
  160. "green"
  161. (with-simulated-input "g RET"
  162. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  163. :tag tag)
  164. ;; Verify that C-j is working correctly
  165. (should-with-tag
  166. (string=
  167. "g"
  168. (with-simulated-input "g C-j"
  169. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  170. :tag tag)
  171. (let ((collection '("brown" "blue" "yellow" "green")))
  172. (should-with-tag
  173. (member
  174. (with-simulated-input "b RET"
  175. (completing-read "Prompt: " collection))
  176. (all-completions "b" collection))
  177. :tag tag))
  178. (case override
  179. (enable
  180. ;; Test for new style
  181. (should-with-tag
  182. (string=
  183. "blue"
  184. (with-simulated-input "RET"
  185. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  186. :tag tag)
  187. (should-with-tag
  188. (string=
  189. ""
  190. (with-simulated-input "C-j"
  191. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  192. :tag tag))
  193. (enable-old
  194. (should-with-tag
  195. (string=
  196. ""
  197. (with-simulated-input "RET"
  198. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  199. :tag tag)
  200. (should-with-tag
  201. (string=
  202. "blue"
  203. (with-simulated-input "C-j"
  204. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  205. :tag tag)
  206. ;; Verify that doing other stuff reverts RET and C-j to standard
  207. ;; meanings
  208. (should-with-tag
  209. (string=
  210. "blue"
  211. (with-simulated-input "g DEL RET"
  212. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  213. :tag tag)
  214. (should-with-tag
  215. (string=
  216. "blue"
  217. (with-simulated-input "<right> <left> RET"
  218. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  219. :tag tag)
  220. (should-with-tag
  221. (string=
  222. ""
  223. (with-simulated-input "g DEL C-j"
  224. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  225. :tag tag)
  226. (should-with-tag
  227. (string=
  228. ""
  229. (with-simulated-input "<right> <left> C-j"
  230. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  231. :tag tag))
  232. (otherwise (error "Unknown override %S" override)))))
  233. (defun test-ido-ubiquitous-expected-mode-on-functional-collection (override &optional tag)
  234. "Test whether observed ido-ubiquitous behavior on functional collection matches OVERRIDE."
  235. (declare (indent 1))
  236. ;; This just temporarily replaces `completing-read' with a wrapper
  237. ;; that always converts the collection argument to an equivalent
  238. ;; function.
  239. (cl-letf* ((original-completing-read (symbol-function #'completing-read))
  240. ((symbol-function #'completing-read)
  241. (lambda (prompt collection &rest args)
  242. (apply original-completing-read prompt
  243. (collection-as-function collection)
  244. args))))
  245. (test-ido-ubiquitous-expected-mode override tag)))
  246. (ert-deftest ido-ubiquitous-test-simple ()
  247. :tags '(ido ido-ubiquitous)
  248. "Test that basic ido-ubiquitous functionality is working."
  249. (with-ido-ubiquitous-standard-env
  250. (ido-ubiquitous-mode 1)
  251. (test-ido-ubiquitous-expected-mode 'enable
  252. :simple-enable)
  253. (ido-ubiquitous-mode 0)
  254. (test-ido-ubiquitous-expected-mode 'disable
  255. :simple-disable)))
  256. (ert-deftest ido-ubiquitous-test-oldstyle ()
  257. :tags '(ido ido-ubiquitous)
  258. "Test whether old-style completion works as expected."
  259. (with-ido-ubiquitous-standard-env
  260. (let ((ido-ubiquitous-default-state 'enable-old))
  261. (test-ido-ubiquitous-expected-mode 'enable-old
  262. :simple-oldstyle))))
  263. (ert-deftest ido-ubiquitous-test-maxitems ()
  264. :tags '(ido ido-ubiquitous)
  265. "Test whether the large-collection fallback works."
  266. (with-ido-ubiquitous-standard-env
  267. (let ((ido-cr+-max-items -1))
  268. (test-ido-ubiquitous-expected-mode 'disable
  269. :maxitems))))
  270. (ert-deftest ido-ubiquitous-test-override ()
  271. :tags '(ido ido-ubiquitous)
  272. "Test whether ido-ubiquitous overrides work."
  273. (with-ido-ubiquitous-standard-env
  274. (ido-ubiquitous-with-override 'enable
  275. (test-ido-ubiquitous-expected-mode 'enable
  276. :override-enable))
  277. (ido-ubiquitous-with-override 'enable-old
  278. (test-ido-ubiquitous-expected-mode 'enable-old
  279. :override-enable-old))
  280. (ido-ubiquitous-with-override 'disable
  281. (test-ido-ubiquitous-expected-mode 'disable
  282. :override-disable))))
  283. (ert-deftest ido-ubiquitous-test-functional-collection ()
  284. :tags '(ido ido-ubiquitous)
  285. "Test whether ido-ubiquitous overrides work when collection is a function."
  286. (with-ido-ubiquitous-standard-env
  287. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  288. :collfunc)
  289. (ido-ubiquitous-with-override 'enable
  290. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  291. :override-enable-collfunc))
  292. (ido-ubiquitous-with-override 'enable-old
  293. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  294. :override-enable-old-collfunc))))
  295. (ert-deftest ido-cr+-require-match ()
  296. :tags '(ido ido-cr+)
  297. "Test whether require-match works."
  298. ;; "C-j" should be allowed to return an empty string even if
  299. ;; require-match is non-nil, as long as default is nil
  300. (should
  301. (string=
  302. ""
  303. (with-simulated-input "C-j"
  304. (ido-completing-read+
  305. "Prompt: "
  306. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  307. ;; "C-j" should NOT be allowed to return an empty string if
  308. ;; require-match and default are both non-nil.
  309. (should-error
  310. (with-simulated-input "C-j"
  311. (ido-completing-read+
  312. "Prompt: "
  313. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t nil nil "yellow")))
  314. ;; Multiple presses of C-j won't just select the first match
  315. (should-error
  316. (with-simulated-input "b C-j C-j C-j"
  317. (ido-completing-read+
  318. "Prompt: "
  319. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  320. ;; First press of C-j should complete unique common prefix after the
  321. ;; first b, but then get stuck on the choice for the second b.
  322. (should-error
  323. (with-simulated-input "b C-j b C-j C-j"
  324. (ido-completing-read+
  325. "Prompt: "
  326. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  327. ;; This should complete to "blueberry" via 2 rounds of unique common
  328. ;; prefix completion, and then return on the 3rd "C-j"
  329. (should
  330. (string=
  331. "blueberry"
  332. (with-simulated-input "b C-j b C-j e C-j C-j"
  333. (ido-completing-read+
  334. "Prompt: "
  335. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  336. ;; The "C-j" should complete to "bluegrass" but should
  337. ;; not return.
  338. (should-error
  339. (with-simulated-input "b l u e g C-j"
  340. (ido-completing-read+
  341. "Prompt: "
  342. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  343. ;; The first "C-j" should complete to "bluegrass", and the second
  344. ;; should return.
  345. (should
  346. (string=
  347. "bluegrass"
  348. (with-simulated-input "b l u e g C-j C-j"
  349. (ido-completing-read+
  350. "Prompt: "
  351. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  352. ;; Finally, a few tests for the expected wrong behavior without
  353. ;; ido-cr+. If ido.el ever fixes this bug, it will cause this test
  354. ;; to fail as a signal that the workaround can be phased out.
  355. (should
  356. (string=
  357. ""
  358. (with-simulated-input "C-j"
  359. (ido-completing-read
  360. "Prompt: "
  361. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  362. (should
  363. (string=
  364. "b"
  365. (with-simulated-input "b C-j"
  366. (ido-completing-read
  367. "Prompt: "
  368. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))))
  369. ;; Functions to define overrides on for testing
  370. (defun idu-no-override-testfunc ()
  371. (test-ido-ubiquitous-expected-mode 'enable
  372. :func-override-none)
  373. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  374. :func-override-none-collfunc))
  375. (defun idu-enabled-testfunc (&rest args)
  376. (test-ido-ubiquitous-expected-mode 'enable
  377. :func-override-enable)
  378. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  379. :func-override-enable-collfunc))
  380. (defun idu-disabled-testfunc (&rest args)
  381. (test-ido-ubiquitous-expected-mode 'disable
  382. :func-override-disable)
  383. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  384. :func-override-disable-collfunc))
  385. (defun idu-enabled-oldstyle-testfunc (&rest args)
  386. (test-ido-ubiquitous-expected-mode 'enable-old
  387. :func-override-enable-old)
  388. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  389. :func-override-enable-old-collfunc))
  390. ;; commands to define overrides on for testing
  391. (defun idu-no-override-testcmd (&rest args)
  392. (interactive
  393. (list
  394. (test-ido-ubiquitous-expected-mode 'enable
  395. :cmd-override-none)
  396. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  397. :cmd-override-non-collfunc)))
  398. (test-ido-ubiquitous-expected-mode 'enable
  399. :cmd-override-none)
  400. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  401. :cmd-override-non-collfunc))
  402. (defun idu-enabled-testcmd (&rest args)
  403. (interactive
  404. (list
  405. (test-ido-ubiquitous-expected-mode 'enable
  406. :cmd-override-enable)
  407. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  408. :cmd-override-enable-collfunc)))
  409. (test-ido-ubiquitous-expected-mode 'enable
  410. :cmd-override-enable)
  411. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  412. :cmd-override-enable-collfunc))
  413. (defun idu-disabled-testcmd (&rest args)
  414. (interactive
  415. (list
  416. (test-ido-ubiquitous-expected-mode 'disable
  417. :cmd-override-disable)
  418. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  419. :cmd-override-disable-collfunc)))
  420. (test-ido-ubiquitous-expected-mode 'disable
  421. :cmd-override-disable)
  422. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  423. :cmd-override-disable-collfunc))
  424. (defun idu-enabled-oldstyle-testcmd (&rest args)
  425. (interactive
  426. (list
  427. (test-ido-ubiquitous-expected-mode 'enable-old
  428. :cmd-override-enable-old)
  429. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  430. :cmd-override-enable-old-collfunc)))
  431. (test-ido-ubiquitous-expected-mode 'enable-old
  432. :cmd-override-enable-old)
  433. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  434. :cmd-override-enable-old-collfunc))
  435. (ert-deftest ido-ubiquitous-test-command-and-function-overrides ()
  436. :tags '(ido ido-ubiquitous)
  437. "Test whether command- and function-specific overrides work."
  438. (let ((orig-func-overrides ido-ubiquitous-function-overrides)
  439. (orig-cmd-overrides ido-ubiquitous-command-overrides))
  440. (unwind-protect
  441. (progn
  442. (customize-set-variable
  443. 'ido-ubiquitous-function-overrides
  444. (append ido-ubiquitous-function-overrides
  445. '((enable exact "idu-enabled-testfunc")
  446. (disable exact "idu-disabled-testfunc")
  447. (enable-old exact "idu-enabled-oldstyle-testfunc"))))
  448. (cl-loop for func in
  449. '(idu-no-override-testfunc
  450. idu-enabled-testfunc
  451. idu-disabled-testfunc
  452. idu-enabled-oldstyle-testfunc)
  453. do (funcall func))
  454. (customize-set-variable
  455. 'ido-ubiquitous-command-overrides
  456. (append ido-ubiquitous-command-overrides
  457. '((enable exact "idu-enabled-testcmd")
  458. (disable exact "idu-disabled-testcmd")
  459. (enable-old exact "idu-enabled-oldstyle-testcmd"))))
  460. (cl-loop for cmd in
  461. '(idu-no-override-testcmd
  462. idu-enabled-testcmd
  463. idu-disabled-testcmd
  464. idu-enabled-oldstyle-testcmd)
  465. do (call-interactively cmd)))
  466. (customize-set-variable 'ido-ubiquitous-function-overrides orig-func-overrides)
  467. (customize-set-variable 'ido-ubiquitous-command-overrides orig-cmd-overrides))))
  468. (ert-deftest ido-ubiquitous-test-fallback ()
  469. :tags '(ido ido-ubiquitous)
  470. "Test whether manually invoking fallback works."
  471. (with-ido-ubiquitous-standard-env
  472. (should
  473. ;; C-b/f not at beginning/end of input should not fall back
  474. (string=
  475. "green"
  476. (with-simulated-input "g C-b C-f RET"
  477. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  478. (should
  479. ;; C-f at end of input should fall back
  480. (string=
  481. "g"
  482. (with-simulated-input "g C-f RET"
  483. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  484. (should
  485. ;; Repeated C-b should not fall back
  486. (string=
  487. "green"
  488. (with-simulated-input "g C-b C-b C-b C-b RET"
  489. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  490. (should
  491. ;; C-b at beginning of line should fall back (if previous action
  492. ;; was not also C-b)
  493. (string=
  494. "g"
  495. (with-simulated-input "g C-b x DEL C-b RET"
  496. (completing-read "Prompt: " '("blue" "yellow" "green")))))))
  497. (defun ido-ubiquitous-run-all-tests ()
  498. (interactive)
  499. (ert "^ido-\\(ubiquitous\\|cr\\+\\)-"))
  500. (provide 'ido-ubiquitous-test)
  501. ;;; ido-ubiquitous-test.el ends here