emacs-ipython-notebook/test/test-ein-notebook.el

1286 lines
52 KiB
EmacsLisp
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(eval-when-compile (require 'cl))
(require 'ert)
(when load-file-name
(add-to-list 'load-path
(concat (file-name-directory load-file-name) "mocker")))
(require 'mocker)
(require 'ein-notebook)
(require 'ein-testing-notebook)
(require 'ein-testing-cell)
;; Test utils
;;; This is the content portion of a response fromt he content API.
(defvar eintest:notebook-data-simple-json
"{
\"name\": \"Untitled0.ipynb\",
\"path\": \"\",
\"type\": \"notebook\",
\"content\" : {
\"metadata\": {
},
\"nbformat\": 3,
\"nbformat_minor\": 0,
\"worksheets\": [
{
\"cells\": [
{
\"cell_type\": \"code\",
\"collapsed\": false,
\"input\": \"1 + 1\",
\"language\": \"python\",
\"outputs\": [
{
\"output_type\": \"pyout\",
\"prompt_number\": 1,
\"text\": \"2\"
}
],
\"prompt_number\": 1
}
]
}
]
}
}
")
(defvar eintest:notebook-data-simple-json-v4
"{
\"metadata\": {
\"name\": \"Untitled0.ipynb\"
},
\"nbformat\": 4,
\"nbformat_minor\": 0,
\"cells\": [
{
\"cell_type\": \"code\",
\"metadata\" : {
\"collapsed\": false,
},
\"source\": \"1 + 1\",
\"outputs\": [
{
\"name\": \"stdout\",
\"output_type\": \"stream\",
\"text\": \"2\"
}
],
\"prompt_number\": 1
}
]
}
")
(defun eintest:kernel-fake-execute-reply (kernel msg-id execution-count)
(let* ((payload nil)
(content (list :execution_count 1 :payload payload))
(packet (list :header (list :msg_type "execute_reply")
:parent_header (list :msg_id msg-id)
:content content)))
(ein:kernel--handle-shell-reply kernel (json-encode packet))))
(defun eintest:kernel-fake-stream (kernel msg-id data)
(let* ((content (list :text data
:name "stdout"))
(packet (list :header (list :msg_type "stream")
:parent_header (list :msg_id msg-id)
:content content)))
(ein:kernel--handle-iopub-reply kernel (json-encode packet))))
(defun eintest:check-search-forward-from (start string &optional null-string)
"Search STRING from START and check it is found.
When non-`nil' NULL-STRING is given, it is searched from the
position where the search of the STRING ends and check that it
is not found."
(save-excursion
(goto-char start)
(should (search-forward string nil t))
(when null-string
(should-not (search-forward null-string nil t)))))
(defun eintest:cell-check-output (cell regexp)
(save-excursion
(goto-char (ein:cell-location cell :after-input))
(should (looking-at-p (concat "\\=" regexp "\n")))))
;; from-json
(ert-deftest ein:notebook-from-json-simple ()
(with-current-buffer (ein:testing-notebook-from-json
eintest:notebook-data-simple-json)
(should (ein:$notebook-p ein:%notebook%))
(should (equal (ein:$notebook-notebook-name ein:%notebook%) "Untitled0.ipynb"))
(should (equal (ein:worksheet-ncells ein:%worksheet%) 1))
(let ((cell (car (ein:worksheet-get-cells ein:%worksheet%))))
(should (ein:codecell-p cell))
(should (equal (oref cell :input) "1 + 1"))
(should (equal (oref cell :input-prompt-number) 1))
(let ((outputs (oref cell :outputs)))
(should (equal (length outputs) 1))
(let ((o1 (car outputs)))
(should (equal (plist-get o1 :output_type) "pyout"))
(should (equal (plist-get o1 :prompt_number) 1))
(should (equal (plist-get o1 :text) "2")))))))
(ert-deftest ein:notebook-from-json-empty ()
(with-current-buffer (ein:testing-notebook-make-empty)
(should (ein:$notebook-p ein:%notebook%))
(should (equal (ein:$notebook-notebook-name ein:%notebook%) ein:testing-notebook-dummy-name))
(should (equal (ein:worksheet-ncells ein:%worksheet%) 0))))
(ert-deftest ein:notebook-from-json-all-cell-types ()
(with-current-buffer
(ein:testing-notebook-make-new
ein:testing-notebook-dummy-name
nil
(list (ein:testing-codecell-data "import numpy")
(ein:testing-markdowncell-data "*markdown* text")
(ein:testing-rawcell-data "`raw` cell text")
(ein:testing-htmlcell-data "<b>HTML</b> text")))
(should (ein:$notebook-p ein:%notebook%))
(should (equal (ein:$notebook-notebook-name ein:%notebook%)
ein:testing-notebook-dummy-name))
(should (equal (ein:worksheet-ncells ein:%worksheet%) 4))
(let ((cells (ein:worksheet-get-cells ein:%worksheet%)))
(should (ein:codecell-p (nth 0 cells)))
(should (ein:markdowncell-p (nth 1 cells)))
(should (ein:rawcell-p (nth 2 cells)))
(should (ein:htmlcell-p (nth 3 cells)))
(should (equal (ein:cell-get-text (nth 0 cells)) "import numpy"))
(should (equal (ein:cell-get-text (nth 1 cells)) "*markdown* text"))
(should (equal (ein:cell-get-text (nth 2 cells)) "`raw` cell text"))
(should (equal (ein:cell-get-text (nth 3 cells)) "<b>HTML</b> text")))))
;;; Destructor
(defun ein:testing-notebook-close-scratchsheet-open-and-close
(num-open num-close)
"Test for closing scratch sheet using `ein:notebook-close-worksheet'.
1. Open NUM-OPEN scratch sheets.
2. Close an existing worksheet.
3. Close NUM-CLOSE scratch sheets.
When NUM-OPEN = NUM-CLOSE, notebook should be closed."
(should (> num-open 0))
(with-current-buffer (ein:testing-notebook-make-empty)
(symbol-macrolet ((ss-list (ein:$notebook-scratchsheets ein:%notebook%)))
;; Add scratchsheets. They can be just empty instance for this test.
(dotimes (_ num-open)
(ein:notebook-scratchsheet-render-new ein:%notebook%))
(let ((ss (car ss-list)))
(kill-buffer (ein:worksheet-buffer ss)))
(should (= (length ss-list) (1- num-open)))
(dotimes (_ (1- num-close))
(kill-buffer (ein:worksheet-buffer (car ss-list))))
(should (= (length ss-list) (- num-open num-close)))
(let ((my-buffer (buffer-name)))
(kill-buffer (current-buffer))
(should-not (seq-some (lambda (b)
(cl-search my-buffer (buffer-name b)))
(buffer-list)))))))
(ert-deftest ein:notebook-close-scratchsheet/open-one-close-one ()
(ein:testing-notebook-close-scratchsheet-open-and-close 1 1))
(ert-deftest ein:notebook-close-scratchsheet/open-two-close-two ()
(ein:testing-notebook-close-scratchsheet-open-and-close 2 2))
(ert-deftest ein:notebook-close-scratchsheet/open-two-close-one ()
(ein:testing-notebook-close-scratchsheet-open-and-close 2 1))
;;; Insertion and deletion of cells
(ert-deftest ein:notebook-insert-cell-below-command-simple ()
(with-current-buffer (ein:testing-notebook-make-empty)
(call-interactively #'ein:worksheet-insert-cell-below)
(call-interactively #'ein:worksheet-insert-cell-below)
(call-interactively #'ein:worksheet-insert-cell-below)
(should (equal (ein:worksheet-ncells ein:%worksheet%) 3))))
(ert-deftest ein:notebook-insert-cell-above-command-simple ()
(with-current-buffer (ein:testing-notebook-make-empty)
(call-interactively #'ein:worksheet-insert-cell-above)
(call-interactively #'ein:worksheet-insert-cell-above)
(call-interactively #'ein:worksheet-insert-cell-above)
(should (equal (ein:worksheet-ncells ein:%worksheet%) 3))))
(ert-deftest ein:notebook-delete-cell-command-simple ()
(with-current-buffer (ein:testing-notebook-make-empty)
(cl-loop repeat 3
do (call-interactively #'ein:worksheet-insert-cell-above))
(should (equal (ein:worksheet-ncells ein:%worksheet%) 3))
(cl-loop repeat 3
do (call-interactively #'ein:worksheet-delete-cell))
(should (equal (ein:worksheet-ncells ein:%worksheet%) 0))))
(ert-deftest ein:notebook-delete-cell-command-no-undo ()
(with-current-buffer (ein:testing-notebook-make-empty)
(call-interactively #'ein:worksheet-insert-cell-above)
(insert "some text")
(should (equal (buffer-string) "
In [ ]:
some text
"))
(call-interactively #'ein:worksheet-delete-cell)
(should (equal (buffer-string) "\n"))
(should-error (undo)) ; should be ignore-error?
(should (equal (buffer-string) "\n"))))
(ert-deftest ein:notebook-kill-cell-command-simple ()
(with-current-buffer (ein:testing-notebook-make-empty)
(let (ein:kill-ring ein:kill-ring-yank-pointer)
(cl-loop repeat 3
do (call-interactively #'ein:worksheet-insert-cell-above))
(should (equal (ein:worksheet-ncells ein:%worksheet%) 3))
(cl-loop for i from 1 to 3
do (call-interactively #'ein:worksheet-kill-cell)
do (should (equal (length ein:kill-ring) i))
do (should (equal (ein:worksheet-ncells ein:%worksheet%) (- 3 i)))))))
(ert-deftest ein:notebook-copy-cell-command-simple ()
(with-current-buffer (ein:testing-notebook-make-empty)
(let (ein:kill-ring ein:kill-ring-yank-pointer)
(cl-loop repeat 3
do (call-interactively #'ein:worksheet-insert-cell-above))
(should (equal (ein:worksheet-ncells ein:%worksheet%) 3))
(cl-loop repeat 3
do (call-interactively #'ein:worksheet-copy-cell))
(should (equal (ein:worksheet-ncells ein:%worksheet%) 3))
(should (equal (length ein:kill-ring) 3)))))
(ert-deftest ein:notebook-yank-cell-command-simple ()
(with-current-buffer (ein:testing-notebook-make-empty)
(let (ein:kill-ring ein:kill-ring-yank-pointer)
(cl-loop repeat 3
do (call-interactively #'ein:worksheet-insert-cell-above))
(should (equal (ein:worksheet-ncells ein:%worksheet%) 3))
(cl-loop repeat 3
do (call-interactively #'ein:worksheet-kill-cell))
(should (equal (ein:worksheet-ncells ein:%worksheet%) 0))
(should (equal (length ein:kill-ring) 3))
(cl-loop repeat 3
do (call-interactively #'ein:worksheet-yank-cell))
(should (equal (ein:worksheet-ncells ein:%worksheet%) 3))
(cl-loop for cell in (ein:worksheet-get-cells ein:%worksheet%)
do (should (ein:codecell-p cell))
do (should (slot-boundp cell :kernel))
do (should (slot-boundp cell :events))))))
(ert-deftest ein:notebook-yank-cell-command-two-buffers ()
(let (ein:kill-ring ein:kill-ring-yank-pointer)
(with-current-buffer (ein:testing-notebook-make-empty "NB1")
(call-interactively #'ein:worksheet-insert-cell-above)
(should (equal (ein:worksheet-ncells ein:%worksheet%) 1))
(call-interactively #'ein:worksheet-kill-cell)
(should (equal (ein:worksheet-ncells ein:%worksheet%) 0))
(kill-buffer))
(with-current-buffer (ein:testing-notebook-make-empty "NB2")
(call-interactively #'ein:worksheet-yank-cell)
(should (equal (ein:worksheet-ncells ein:%worksheet%) 1)))))
(ert-deftest ein:notebook-toggle-cell-type-simple ()
(with-current-buffer (ein:testing-notebook-make-empty)
(call-interactively #'ein:worksheet-insert-cell-above)
(insert "some text")
(should (ein:codecell-p (ein:worksheet-get-current-cell)))
(should (slot-boundp (ein:worksheet-get-current-cell) :kernel))
;; toggle to markdown
(call-interactively #'ein:worksheet-toggle-cell-type)
(should (ein:markdowncell-p (ein:worksheet-get-current-cell)))
(should (looking-back "some text"))
;; toggle to raw
(call-interactively #'ein:worksheet-toggle-cell-type)
(should (ein:rawcell-p (ein:worksheet-get-current-cell)))
(should (looking-back "some text"))
(when (< (ein:$notebook-nbformat ein:%notebook%) 4)
;; toggle to heading
(call-interactively #'ein:worksheet-toggle-cell-type)
(should (looking-back "some text"))
;; toggle to code
(call-interactively #'ein:worksheet-toggle-cell-type)
(should (ein:codecell-p (ein:worksheet-get-current-cell)))
(should (slot-boundp (ein:worksheet-get-current-cell) :kernel))
(should (looking-back "some text")))))
(ert-deftest ein:notebook-change-cell-type-cycle-through ()
(with-current-buffer (ein:testing-notebook-make-empty)
(call-interactively #'ein:worksheet-insert-cell-above)
(insert "some text")
;; start with code cell
(should (ein:codecell-p (ein:worksheet-get-current-cell)))
(should (slot-boundp (ein:worksheet-get-current-cell) :kernel))
(let ((check
(lambda (type)
(let ((cell-p (intern (format "ein:%scell-p" type)))
(cell (ein:worksheet-get-current-cell)))
(ein:worksheet-change-cell-type ein:%worksheet% cell t)
(let ((new (ein:worksheet-get-current-cell)))
(should-not (eq new cell))
(should (funcall cell-p new)))
(should (looking-back "some text"))))))
;; change type: code (no change) -> markdown -> raw
(cl-loop for type in '("markdown" "raw" "code")
do (funcall check type))
;; back to code
(should (slot-boundp (ein:worksheet-get-current-cell) :kernel)))))
(defun eintest:notebook-split-cell-at-point
(insert-text search-text head-text tail-text &optional no-trim)
"Test `ein:notebook-split-cell-at-point' by the following procedure.
1. Insert, INSERT-TEXT.
2. Split cell just before SEARCH-TEXT.
3. Check that head cell has HEAD-TEXT.
4. Check that tail cell has TAIL-TEXT.
NO-TRIM is passed to `ein:notebook-split-cell-at-point'."
(with-current-buffer (ein:testing-notebook-make-empty)
(call-interactively #'ein:worksheet-insert-cell-above)
(insert insert-text)
(when search-text
(search-backward search-text))
;; do it
(let ((current-prefix-arg no-trim))
(call-interactively #'ein:worksheet-split-cell-at-point))
;; check the "tail" cell
(let ((cell (ein:worksheet-get-current-cell)))
(ein:cell-goto cell)
(should (equal (ein:cell-get-text cell) tail-text))
(should (ein:codecell-p cell))
(should (slot-boundp cell :kernel)))
;; check the "head" cell
(call-interactively #'ein:worksheet-goto-prev-input)
(let ((cell (ein:worksheet-get-current-cell)))
(ein:cell-goto cell)
(should (equal (ein:cell-get-text cell) head-text))
(should (ein:codecell-p cell))
(should (slot-boundp cell :kernel)))))
(ert-deftest ein:notebook-split-cell-at-point-before-newline ()
(eintest:notebook-split-cell-at-point
"some\ntext" "text" "some" "text"))
(ert-deftest ein:notebook-split-cell-at-point-after-newline ()
(eintest:notebook-split-cell-at-point
"some\ntext" "\ntext" "some" "text"))
(ert-deftest ein:notebook-split-cell-at-point-before-newline-no-trim ()
(eintest:notebook-split-cell-at-point
"some\ntext" "text" "some\n" "text" t))
(ert-deftest ein:notebook-split-cell-at-point-after-newline-no-trim ()
(eintest:notebook-split-cell-at-point
"some\ntext" "\ntext" "some" "\ntext" t))
(ert-deftest ein:notebook-split-cell-at-point-no-head ()
(eintest:notebook-split-cell-at-point
"some" "some" "" "some"))
(ert-deftest ein:notebook-split-cell-at-point-no-tail ()
(eintest:notebook-split-cell-at-point
"some" nil "some" ""))
(ert-deftest ein:notebook-merge-cell-command-next ()
(with-current-buffer (ein:testing-notebook-make-empty)
(call-interactively #'ein:worksheet-insert-cell-above)
(insert "Cell 1")
(call-interactively #'ein:worksheet-insert-cell-above)
(insert "Cell 0")
(let ((current-prefix-arg t))
(call-interactively #'ein:worksheet-merge-cell))
(ein:cell-goto (ein:worksheet-get-current-cell))
(should (looking-at "Cell 0\nCell 1"))))
(ert-deftest ein:notebook-merge-cell-command-prev ()
(with-current-buffer (ein:testing-notebook-make-empty)
(call-interactively #'ein:worksheet-insert-cell-below)
(insert "Cell 0")
(call-interactively #'ein:worksheet-insert-cell-below)
(insert "Cell 1")
(call-interactively #'ein:worksheet-merge-cell)
(ein:cell-goto (ein:worksheet-get-current-cell))
(should (looking-at "Cell 0\nCell 1"))))
;;; Cell selection.
(ert-deftest ein:notebook-goto-next-input-command-simple ()
(with-current-buffer (ein:testing-notebook-make-empty)
(cl-loop for i downfrom 2 to 0
do (call-interactively #'ein:worksheet-insert-cell-above)
do (insert (format "Cell %s" i)))
(should (equal (ein:worksheet-ncells ein:%worksheet%) 3))
;; (message "%s" (buffer-string))
(cl-loop for i from 0 below 2
do (beginning-of-line) ; This is required, I need to check why
do (should (looking-at (format "Cell %s" i)))
do (call-interactively #'ein:worksheet-goto-next-input)
do (should (looking-at (format "Cell %s" (1+ i)))))))
(ert-deftest ein:notebook-goto-prev-input-command-simple ()
(with-current-buffer (ein:testing-notebook-make-empty)
(cl-loop for i from 0 below 3
do (call-interactively #'ein:worksheet-insert-cell-below)
do (insert (format "Cell %s" i)))
(should (equal (ein:worksheet-ncells ein:%worksheet%) 3))
;; (message "%s" (buffer-string))
(cl-loop for i downfrom 2 to 1
do (beginning-of-line) ; This is required, I need to check why
do (should (looking-at (format "Cell %s" i)))
do (call-interactively #'ein:worksheet-goto-prev-input)
do (should (looking-at (format "Cell %s" (1- i)))))))
(defun ein:testing-beginning-of-cell-input (num-cells
initial-point
before-callback
arg
should-looking-at-this)
(with-current-buffer (ein:testing-notebook-make-empty)
(ein:testing-insert-cells-with-format num-cells)
(goto-char (point-min))
(search-forward initial-point)
(when before-callback (funcall before-callback))
(should-not (looking-at-p should-looking-at-this))
(ein:worksheet-beginning-of-cell-input arg)
(should (looking-at-p should-looking-at-this))))
(ert-deftest ein:worksheet-beginning-of-cell-input-with-no-arg ()
(ein:testing-beginning-of-cell-input 1 "Cell 0" nil nil "Cell 0"))
(ert-deftest ein:worksheet-beginning-of-cell-input-with-arg-two ()
(ein:testing-beginning-of-cell-input 2 "Cell 1" nil 2 "Cell 0"))
(ert-deftest ein:worksheet-beginning-of-cell-input-with-arg-minus-one ()
(ein:testing-beginning-of-cell-input 2 "Cell 0" nil -1 "Cell 1"))
(ert-deftest ein:worksheet-beginning-of-cell-input-with-no-arg-at-footer ()
(ein:testing-beginning-of-cell-input 1 "Cell 0"
#'forward-line
nil "Cell 0"))
(ert-deftest ein:worksheet-beginning-of-cell-input-with-arg-two-at-footer ()
(ein:testing-beginning-of-cell-input 2 "Cell 1"
#'forward-line
2 "Cell 0"))
(ert-deftest ein:worksheet-beginning-of-cell-input-with-arg-minus-one-at-footer
()
(ein:testing-beginning-of-cell-input 2 "Cell 0"
#'forward-line
-1 "Cell 1"))
(ert-deftest ein:worksheet-beginning-of-cell-input-with-no-arg-at-prompt ()
(ein:testing-beginning-of-cell-input 2 "Cell 1"
(lambda () (forward-line -1))
nil "Cell 0"))
(ert-deftest ein:worksheet-beginning-of-cell-input-with-arg-two-at-prompt ()
(ein:testing-beginning-of-cell-input 2 "Cell 1"
(lambda () (forward-line -1))
2 "Cell 0"))
(ert-deftest ein:worksheet-beginning-of-cell-input-with-arg-minus-one-at-prompt
()
(ein:testing-beginning-of-cell-input 2 "Cell 0"
;; I need two cells to make it fail
;; without (forward-line -1)
(lambda () (forward-line -1))
-1 "Cell 0"))
(ert-deftest ein:worksheet-beginning-of-cell-input-repeat ()
(with-current-buffer (ein:testing-notebook-make-empty)
(ein:testing-insert-cells-with-format 3)
(goto-char (point-min))
(search-forward "Cell 2")
(should-not (looking-at-p "Cell 2"))
(ein:worksheet-beginning-of-cell-input)
(should (looking-at-p "Cell 2"))
(should-not (looking-at-p "Cell 1"))
(ein:worksheet-beginning-of-cell-input)
(should (looking-at-p "Cell 1"))
(should-not (looking-at-p "Cell 0"))
(ein:worksheet-beginning-of-cell-input)
(should (looking-at-p "Cell 0"))))
(defun ein:testing-end-of-cell-input (num-cells
initial-point
before-callback
arg
should-looking-back-this)
(with-current-buffer (ein:testing-notebook-make-empty)
(ein:testing-insert-cells-with-format num-cells)
(goto-char (point-min))
(search-forward initial-point)
(beginning-of-line)
(when before-callback (funcall before-callback))
(should-not (looking-back should-looking-back-this))
(ein:worksheet-end-of-cell-input arg)
(should (looking-back should-looking-back-this))))
(ert-deftest ein:worksheet-end-of-cell-input-with-no-arg ()
(ein:testing-end-of-cell-input 1 "Cell 0" nil nil "Cell 0"))
(ert-deftest ein:worksheet-end-of-cell-input-with-arg-two ()
(ein:testing-end-of-cell-input 2 "Cell 0" nil 2 "Cell 1"))
(ert-deftest ein:worksheet-end-of-cell-input-with-arg-minus-one ()
(ein:testing-end-of-cell-input 2 "Cell 1" nil -1 "Cell 0"))
(ert-deftest ein:worksheet-end-of-cell-input-with-no-arg-at-footer ()
(ein:testing-end-of-cell-input 2 "Cell 0"
#'forward-line
nil "Cell 1"))
(ert-deftest ein:worksheet-end-of-cell-input-with-arg-two-at-footer ()
(ein:testing-end-of-cell-input 3 "Cell 0"
#'forward-line
2 "Cell 2"))
(ert-deftest ein:worksheet-end-of-cell-input-with-arg-minus-one-at-footer
()
(ein:testing-end-of-cell-input 2 "Cell 0"
#'forward-line
-1 "Cell 0"))
(ert-deftest ein:worksheet-end-of-cell-input-with-no-arg-at-prompt ()
(ein:testing-end-of-cell-input 2 "Cell 1"
(lambda () (forward-line -1))
nil "Cell 1"))
(ert-deftest ein:worksheet-end-of-cell-input-with-arg-two-at-prompt ()
(ein:testing-end-of-cell-input 3 "Cell 0"
(lambda () (forward-line -1))
2 "Cell 1"))
(ert-deftest ein:worksheet-end-of-cell-input-with-arg-minus-one-at-prompt
()
(ein:testing-end-of-cell-input 2 "Cell 1"
(lambda () (forward-line -1))
-1 "Cell 0"))
;;; Cell movement
(ert-deftest ein:notebook-move-cell-up-command-simple ()
(with-current-buffer (ein:testing-notebook-make-empty)
(cl-loop for i from 0 below 3
do (call-interactively #'ein:worksheet-insert-cell-below)
do (insert (format "Cell %s" i)))
(beginning-of-line)
(should (looking-at "Cell 2"))
(cl-loop repeat 2
do (call-interactively #'ein:worksheet-move-cell-up))
;; (message "%s" (buffer-string))
(beginning-of-line)
(should (looking-at "Cell 2"))
(should (search-forward "Cell 0" nil t))
(should (search-forward "Cell 1" nil t))
(should-not (search-forward "Cell 2" nil t))))
(ert-deftest ein:notebook-move-cell-down-command-simple ()
(with-current-buffer (ein:testing-notebook-make-empty)
(cl-loop for i from 0 below 3
do (call-interactively #'ein:worksheet-insert-cell-above)
do (insert (format "Cell %s" i)))
(cl-loop repeat 2
do (call-interactively #'ein:worksheet-move-cell-down))
(beginning-of-line)
(should (looking-at "Cell 2"))
(should (search-backward "Cell 0" nil t))
(should (search-backward "Cell 1" nil t))
(should-not (search-backward "Cell 2" nil t))))
;;; Cell collapsing and output clearing
(ert-deftest ein:worksheet-toggle-output ()
(with-current-buffer (ein:testing-notebook-make-empty)
(let ((cell (call-interactively #'ein:worksheet-insert-cell-below)))
(should-not (oref cell :collapsed))
(call-interactively #'ein:worksheet-toggle-output)
(should (oref cell :collapsed))
(call-interactively #'ein:worksheet-toggle-output)
(should-not (oref cell :collapsed)))))
(defun ein:testing-insert-cells (list-type-or-cell &optional pivot callback)
(cl-loop with ws = ein:%worksheet%
with cell = pivot
for type in list-type-or-cell
for i from 0
do (setq cell (ein:worksheet-insert-cell-below ws type cell t))
if callback
do (funcall callback i cell)))
(defun* ein:testing-insert-cells-with-format (num &optional
(format "Cell %s")
(type 'code))
(ein:testing-insert-cells (cl-loop repeat num collect type)
nil
(lambda (i &rest _) (insert (format format i))))
(should (equal (ein:worksheet-ncells ein:%worksheet%) num)))
(defun ein:testing-test-output-visibility-all (collapsed)
(mapc (lambda (cell) (should (eq (oref cell :collapsed) collapsed)))
(ein:worksheet-get-cells ein:%worksheet%)))
(ert-deftest ein:worksheet-set-output-visibility-all/visible-from-all-hidden ()
(with-current-buffer (ein:testing-notebook-make-empty)
;; Prepare cells
(ein:testing-insert-cells '(code code code))
(mapc (lambda (cell) (ein:cell-set-collapsed cell nil))
(ein:worksheet-get-cells ein:%worksheet%))
;; Call the command
(call-interactively #'ein:worksheet-set-output-visibility-all)
;; Check it worked
(ein:testing-test-output-visibility-all nil)))
(ert-deftest ein:worksheet-set-output-visibility-all/hidden-from-all-visible ()
(with-current-buffer (ein:testing-notebook-make-empty)
;; Prepare cells
(ein:testing-insert-cells '(code code code))
;; Call the command
(let ((current-prefix-arg t))
(call-interactively #'ein:worksheet-set-output-visibility-all))
;; Check it worked
(ein:testing-test-output-visibility-all t)))
(ert-deftest ein:worksheet-set-output-visibility-all/visible-from-part-hidden ()
(with-current-buffer (ein:testing-notebook-make-empty)
;; Prepare cells
(ein:testing-insert-cells '(code code code))
(ein:cell-set-collapsed
(nth 1 (ein:worksheet-get-cells ein:%worksheet%)) nil)
;; Call the command
(call-interactively #'ein:worksheet-set-output-visibility-all)
;; Check it worked
(ein:testing-test-output-visibility-all nil)))
(ert-deftest ein:worksheet-set-output-visibility-all/hidden-from-part-visible ()
(with-current-buffer (ein:testing-notebook-make-empty)
;; Prepare cells
(ein:testing-insert-cells '(code code code))
(ein:cell-set-collapsed
(nth 1 (ein:worksheet-get-cells ein:%worksheet%)) nil)
;; Call the command
(call-interactively #'ein:worksheet-set-output-visibility-all)
(let ((current-prefix-arg t))
(call-interactively #'ein:worksheet-set-output-visibility-all))
;; Check it worked
(ein:testing-test-output-visibility-all t)))
(defun ein:testing-assert-cell-output-num (cell num-outputs)
(should (ein:codecell-p cell))
(should (= (length (oref cell :outputs)) num-outputs)))
(ert-deftest ein:worksheet-clear-output/simple ()
(with-current-buffer
(ein:testing-make-notebook-with-outputs '(("'cell output'")
("'cell output'")))
(should (= (ein:worksheet-ncells ein:%worksheet%) 2))
(let* ((cells (ein:worksheet-get-cells ein:%worksheet%)))
(ein:testing-assert-cell-output-num (nth 0 cells) 1)
(ein:testing-assert-cell-output-num (nth 1 cells) 1)
(ein:cell-goto (nth 0 cells))
(call-interactively #'ein:worksheet-clear-output)
(ein:testing-assert-cell-output-num (nth 0 cells) 0) ; cleared
(ein:testing-assert-cell-output-num (nth 1 cells) 1))))
(ert-deftest ein:worksheet-clear-output/preserve-input-prompt ()
(with-current-buffer
(ein:testing-make-notebook-with-outputs '(("'cell output'")
("'cell output'")
("'cell output'")))
(should (= (ein:worksheet-ncells ein:%worksheet%) 3))
(let* ((cells (ein:worksheet-get-cells ein:%worksheet%)))
(ein:cell-set-input-prompt (nth 0 cells) 111)
(ein:cell-set-input-prompt (nth 1 cells) 222)
(ein:cell-set-input-prompt (nth 2 cells) 333)
;; Call `ein:worksheet-clear-output' with/without prefix argument.
(ein:cell-goto (nth 0 cells))
(call-interactively #'ein:worksheet-clear-output)
(ein:cell-goto (nth 2 cells))
(let ((current-prefix-arg '(4)))
(call-interactively #'ein:worksheet-clear-output))
;; Check cells' prompt number
(should (eq (oref (nth 0 cells) :input-prompt-number) nil))
(should (eq (oref (nth 1 cells) :input-prompt-number) 222))
(should (eq (oref (nth 2 cells) :input-prompt-number) 333)))))
(ert-deftest ein:worksheet-clear-all-output/simple ()
(with-current-buffer
(ein:testing-make-notebook-with-outputs '(("'cell output'")
("'cell output'")))
(should (= (ein:worksheet-ncells ein:%worksheet%) 2))
(let* ((cells (ein:worksheet-get-cells ein:%worksheet%)))
(ein:testing-assert-cell-output-num (nth 0 cells) 1)
(ein:testing-assert-cell-output-num (nth 1 cells) 1)
(call-interactively #'ein:worksheet-clear-all-output)
(ein:testing-assert-cell-output-num (nth 0 cells) 0)
(ein:testing-assert-cell-output-num (nth 1 cells) 0))))
;; Kernel related things
(defun eintest:notebook-check-kernel-and-codecell (kernel cell)
(should (ein:$kernel-p kernel))
(should (ein:codecell-p cell))
(should (ein:$kernel-p (oref cell :kernel))))
(defun eintest:notebook-fake-execution (kernel text msg-id callbacks)
(mocker-let ((ein:kernel-execute
(kernel code callbacks kwd-silent silent)
((:input (list kernel text callbacks :silent nil))))
(ein:kernel-live-p
(kernel)
((:input (list kernel) :output t))))
(call-interactively #'ein:worksheet-execute-cell))
(ein:kernel-set-callbacks-for-msg kernel msg-id callbacks))
(ert-deftest ein:notebook-execute-current-cell ()
(with-current-buffer (ein:testing-notebook-make-empty)
(call-interactively #'ein:worksheet-insert-cell-below)
(let* ((text "print 'Hello World'")
(cell (ein:worksheet-get-current-cell))
(kernel (ein:$notebook-kernel ein:%notebook%))
(msg-id "DUMMY-MSG-ID")
(callbacks (ein:cell-make-callbacks cell)))
(eintest:notebook-check-kernel-and-codecell kernel cell)
;; Execute
(insert text)
(eintest:notebook-fake-execution kernel text msg-id callbacks)
;; Execute reply
(should-error (eintest:check-search-forward-from (point-min) "In [1]:"))
(eintest:kernel-fake-execute-reply kernel msg-id 1)
(should (= (oref cell :input-prompt-number) 1))
(eintest:check-search-forward-from (point-min) "In [1]:")
;; Stream output
(eintest:kernel-fake-stream kernel msg-id "Hello World")
(should (= (ein:cell-num-outputs cell) 1))
(save-excursion
(goto-char (point-min))
(should (search-forward "In [1]:" nil t))
(should (search-forward "print 'Hello World'" nil t))
(should (search-forward "\nHello World\n" nil t)) ; stream output
(should-not (search-forward "Hello World" nil t))))))
(defmacro eintest:worksheet-execute-cell-and-*-deftest
(do-this cell-type has-next-p insert-p)
"Define:
ein:worksheet-execute-cell-and-{DO-THIS}/on-{CELL-TYPE}cell-{no,with}-next
For example, when `goto-next', \"code\", `nil', `nil' is given,
`ein:worksheet-execute-cell-and-goto-next/on-codecell-no-next' is
defined."
(let ((test-name
(intern (format "ein:worksheet-execute-cell-and-%s/on-%scell-%s"
do-this cell-type
(if has-next-p "with-next" "no-next"))))
(command
(intern (format "ein:worksheet-execute-cell-and-%s" do-this))))
`(ert-deftest ,test-name ()
(with-current-buffer (ein:testing-notebook-make-empty)
(let* ((ws ein:%worksheet%)
(current (ein:worksheet-insert-cell-below ws ,cell-type nil t))
,@(when has-next-p
'((next
(ein:worksheet-insert-cell-below ws "code" current)))))
(mocker-let ((ein:worksheet-execute-cell
(ws cell)
(,@(when (equal cell-type "code")
'((:input (list ein:%worksheet% current)))))))
(call-interactively #',command)
(let ((cell (ein:worksheet-get-current-cell)))
(should (eq (ein:cell-prev cell) current))
,(when has-next-p
(if insert-p
'(should-not (eq cell next))
'(should (eq cell next)))))))))))
(eintest:worksheet-execute-cell-and-*-deftest goto-next "code" nil t )
(eintest:worksheet-execute-cell-and-*-deftest goto-next "code" t nil)
(eintest:worksheet-execute-cell-and-*-deftest goto-next "markdown" nil t )
(eintest:worksheet-execute-cell-and-*-deftest goto-next "markdown" t nil)
(eintest:worksheet-execute-cell-and-*-deftest insert-below "code" nil t )
(eintest:worksheet-execute-cell-and-*-deftest insert-below "code" t t )
(eintest:worksheet-execute-cell-and-*-deftest insert-below "markdown" nil t )
(eintest:worksheet-execute-cell-and-*-deftest insert-below "markdown" t t )
;;; Persistence and loading
(defun ein:testin-notebook-close (num-ws num-ss)
(should (= num-ws 1)) ; currently EIN only supports 1 WS
(should (>= num-ss 0))
(let* ((buf (ein:testing-notebook-make-empty))
(buffers (with-current-buffer buf
(ein:notebook-buffer-list ein:%notebook%))))
(with-current-buffer buf
(dotimes (_ num-ss)
(ein:notebook-scratchsheet-render-new ein:%notebook%))
(should (= (+ num-ws num-ss)
(length (seq-filter
(lambda (b) (not (buffer-base-buffer (get-buffer b))))
(ein:notebook-buffer-list ein:%notebook%))))))
(kill-buffer buf)
(mapc (lambda (b) (should-not (get-buffer b))) buffers)))
(ert-deftest ein:notebook-close/one-ws-no-ss ()
(ein:testin-notebook-close 1 0))
(ert-deftest ein:notebook-close/one-ws-one-ss ()
(ein:testin-notebook-close 1 1))
(ert-deftest ein:notebook-close/one-ws-five-ss ()
(ein:testin-notebook-close 1 5))
(defun ein:testing-notebook-data-assert-nb3-worksheet-contents (notebook &optional text)
(let* ((data (ein:notebook-to-json notebook))
(worksheets (assoc-default 'worksheets data #'eq))
(cells (assoc-default 'cells (elt worksheets 0) #'eq)))
(should (= (length worksheets) 1))
(if text
(let ((cell-0 (elt cells 0))
(input (assoc-default 'input cell-0 #'eq)))
(should (equal input text)))
(should (= (length cells) 0)))))
(defun ein:testing-notebook-data-assert-nb4-worksheet-contents (notebook &optional text)
(let* ((data (ein:notebook-to-json notebook))
(cells (assoc-default 'cells data)))
(if text
(progn
(should (= (length cells) 1))
(should (equal (assoc-default 'source (elt cells 0) #'eq) text)))
(should (zerop (length cells))))))
(ert-deftest ein:notebook-saves-latin ()
(with-current-buffer (ein:testing-notebook-make-new)
(let ((latin "«utf-8 cannot handle these»"))
;; Edit notebook.
(ein:cell-goto (ein:get-cell-at-point))
(insert latin)
(should (ein:notebook-save-notebook (ein:get-notebook))))))
(ert-deftest ein:notebook-to-json-after-closing-a-worksheet ()
(with-current-buffer (ein:testing-notebook-make-new)
(let ((buffer (current-buffer))
(notebook ein:%notebook%))
;; Edit notebook.
(ein:cell-goto (ein:get-cell-at-point))
(insert "some text")
(if (< (ein:$notebook-nbformat notebook) 4)
(ein:testing-notebook-data-assert-nb3-worksheet-contents notebook "some text")
(ein:testing-notebook-data-assert-nb4-worksheet-contents notebook "some text"))
(should (ein:notebook-modified-p notebook))
(ein:notebook-save-notebook-success notebook)
(should-not (ein:notebook-modified-p notebook))
;; Kill scratch sheet.
(kill-buffer (ein:worksheet-buffer (ein:notebook-scratchsheet-open notebook)))
(should (ein:notebook-live-p notebook))
;; to-json should still work
(if (< (ein:$notebook-nbformat notebook) 4)
(ein:testing-notebook-data-assert-nb3-worksheet-contents notebook "some text")
(ein:testing-notebook-data-assert-nb4-worksheet-contents notebook "some text")))))
(ert-deftest ein:notebook-to-json-after-discarding-a-worksheet ()
(with-current-buffer (ein:testing-notebook-make-new)
(lexical-let (asked
(buffer (current-buffer))
(notebook ein:%notebook%))
;; Edit notebook.
(ein:cell-goto (ein:get-cell-at-point))
(insert "some text")
(if (< (ein:$notebook-nbformat notebook) 4)
(ein:testing-notebook-data-assert-nb3-worksheet-contents notebook "some text")
(ein:testing-notebook-data-assert-nb4-worksheet-contents notebook "some text"))
(should (ein:notebook-modified-p notebook))
(let ((ss-buf (save-current-buffer (ein:notebook-scratchsheet-open notebook))))
;; Discard a worksheet buffer
(cl-letf (((symbol-function 'y-or-n-p)
(lambda (&rest _args) (setq asked t) nil)))
(should-not (kill-buffer buffer)))
(should asked)
(should-not (buffer-live-p buffer))
(should-not (ein:notebook-live-p notebook))
(should-not (buffer-live-p ss-buf))))))
(defun ein:testing-notebook-should-be-closed (notebook buffer)
(should-not (buffer-live-p buffer))
(should-not (ein:notebook-live-p notebook)))
(ert-deftest ein:notebook-kill-kernel-then-close-when-its-alive ()
(with-current-buffer (ein:testing-notebook-make-new)
(let ((buffer (current-buffer))
(notebook ein:%notebook%))
(cl-letf (((symbol-function 'ein:kernel-live-p) (lambda (&rest args) t))
((symbol-function 'ein:kernel-delete-session)
(cl-function (lambda (callback &key kernel) (funcall callback kernel)))))
(call-interactively #'ein:notebook-kill-kernel-then-close-command))
(ein:testing-notebook-should-be-closed notebook buffer))))
(ert-deftest ein:notebook-kill-kernel-then-close-when-already-dead ()
(with-current-buffer (ein:testing-notebook-make-new)
(let ((buffer (current-buffer))
(notebook ein:%notebook%)
(kernel (ein:$notebook-kernel ein:%notebook%)))
(mocker-let
((ein:kernel-live-p
(kernel)
((:input (list kernel) :output nil))))
(call-interactively #'ein:notebook-kill-kernel-then-close-command))
(ein:testing-notebook-should-be-closed notebook buffer))))
;; Notebook undo
(defun eintest:notebook-undo-after-insert-above ()
(with-current-buffer (ein:testing-notebook-make-empty)
(let ((text "some text"))
(call-interactively #'ein:worksheet-insert-cell-above)
(insert text)
(undo-boundary)
(call-interactively #'ein:worksheet-insert-cell-above)
(call-interactively #'ein:worksheet-goto-next-input)
(should (equal (ein:cell-get-text (ein:worksheet-get-current-cell)) text))
(if ein:worksheet-enable-undo
(undo)
(should-error (undo)))
(when ein:worksheet-enable-undo
(should (equal (buffer-string) "
In [ ]:
In [ ]:
"))))))
(defun eintest:notebook-undo-after-split ()
(with-current-buffer (ein:testing-notebook-make-empty)
(let ((line-1 "first line")
(line-2 "second line"))
(call-interactively #'ein:worksheet-insert-cell-below)
(insert line-1 "\n" line-2)
(undo-boundary)
(move-beginning-of-line 1)
(call-interactively #'ein:worksheet-split-cell-at-point)
(undo-boundary)
(should (equal (ein:cell-get-text (ein:worksheet-get-current-cell))
line-2))
(if ein:worksheet-enable-undo
(undo)
(should-error (undo)))
(when ein:worksheet-enable-undo
(should (equal (buffer-string) "
In [ ]:
In [ ]:
first line
second line
"))))))
(defun eintest:notebook-undo-after-merge ()
(with-current-buffer (ein:testing-notebook-make-empty)
(let ((line-1 "first line")
(line-2 "second line"))
(call-interactively #'ein:worksheet-insert-cell-below)
(call-interactively #'ein:worksheet-insert-cell-below)
;; Extra cells to avoid "Changes to be undone are outside visible
;; portion of buffer" user-error:
(call-interactively #'ein:worksheet-insert-cell-below)
(call-interactively #'ein:worksheet-insert-cell-below)
(goto-char (point-min))
(call-interactively #'ein:worksheet-goto-next-input)
(insert line-1)
(undo-boundary)
(call-interactively #'ein:worksheet-goto-next-input)
(insert line-2)
(undo-boundary)
(call-interactively #'ein:worksheet-merge-cell)
(undo-boundary)
(should (equal (ein:cell-get-text (ein:worksheet-get-current-cell))
(concat line-1 "\n" line-2)))
(if (not ein:worksheet-enable-undo)
(should-error (undo))
(undo)
(should (equal (buffer-string) "
In [ ]:
second line
In [ ]:
In [ ]:
"))
(undo-more 1)
(should (equal (buffer-string) "
In [ ]:
In [ ]:
In [ ]:
"))))))
(defun eintest:notebook-undo-after-execution-1-cell ()
(with-current-buffer (ein:testing-notebook-make-empty)
(call-interactively #'ein:worksheet-insert-cell-below)
(let* ((text "print 'Hello World'")
(output-text "Hello World\n")
(cell (ein:worksheet-get-current-cell))
(kernel (ein:$notebook-kernel ein:%notebook%))
(msg-id "DUMMY-MSG-ID")
(callbacks (ein:cell-make-callbacks cell))
(check-output
(lambda ()
(eintest:cell-check-output cell output-text))))
(eintest:notebook-check-kernel-and-codecell kernel cell)
;; Execute
(insert text)
(undo-boundary)
(eintest:notebook-fake-execution kernel text msg-id callbacks)
(ein:kernel-set-callbacks-for-msg kernel msg-id callbacks)
;; Stream output
(eintest:kernel-fake-stream kernel msg-id output-text)
(funcall check-output)
;; Undo
(should (equal (ein:cell-get-text cell) text))
(if ein:worksheet-enable-undo
(undo)
(should-error (undo)))
(when ein:worksheet-enable-undo
(should (equal (ein:cell-get-text cell) ""))
(should (funcall check-output))))))
(defun eintest:notebook-undo-after-execution-2-cells ()
(with-current-buffer (ein:testing-notebook-make-empty)
(call-interactively #'ein:worksheet-insert-cell-below)
(call-interactively #'ein:worksheet-insert-cell-above)
(let* ((text "print 'Hello World\\n' * 10")
(next-text "something")
(output-text
(apply #'concat (cl-loop repeat 10 collect "Hello World\n")))
(cell (ein:worksheet-get-current-cell))
(next-cell (ein:cell-next cell))
(kernel (ein:$notebook-kernel ein:%notebook%))
(msg-id "DUMMY-MSG-ID")
(callbacks (ein:cell-make-callbacks cell))
(check-output
(lambda ()
(eintest:cell-check-output cell output-text))))
(eintest:notebook-check-kernel-and-codecell kernel cell)
;; Execute
(insert text)
(undo-boundary)
(let ((pos (point)))
;; Do not use `save-excursion' because it does not record undo.
(call-interactively #'ein:worksheet-goto-next-input)
(insert next-text)
(undo-boundary)
(goto-char pos))
(eintest:notebook-fake-execution kernel text msg-id callbacks)
(ein:kernel-set-callbacks-for-msg kernel msg-id callbacks)
;; Stream output
(eintest:kernel-fake-stream kernel msg-id output-text)
(funcall check-output)
;; Undo
(should (equal (ein:cell-get-text cell) text))
(should (equal (ein:cell-get-text next-cell) next-text))
(if ein:worksheet-enable-undo
(undo)
(should-error (undo)))
(when ein:worksheet-enable-undo
(should (equal (ein:cell-get-text cell) text))
(should (equal (ein:cell-get-text next-cell) ""))
(should (funcall check-output))))))
(defmacro eintest:notebook-undo-make-tests (name)
"Define two tests ein:NANE/no, ein:NANE/yes
from a function named eintest:NAME where `no'/`yes' is the
value of `ein:worksheet-enable-undo'."
(let ((func (intern (format "eintest:%s" name)))
(test/no (intern (format "ein:%s/no" name)))
(test/yes (intern (format "ein:%s/yes" name))))
`(progn
(ert-deftest ,test/no ()
(let ((ein:worksheet-enable-undo nil))
(,func)))
(ert-deftest ,test/yes ()
(let ((ein:worksheet-enable-undo t))
(,func)))
)))
(eintest:notebook-undo-make-tests notebook-undo-after-insert-above)
(eintest:notebook-undo-make-tests notebook-undo-after-split)
(eintest:notebook-undo-make-tests notebook-undo-after-merge)
(eintest:notebook-undo-make-tests notebook-undo-after-execution-1-cell)
(eintest:notebook-undo-make-tests notebook-undo-after-execution-2-cells)
;; Generic getter
(ert-deftest ein:get-url-or-port--notebook ()
(with-current-buffer (ein:testing-notebook-make-empty)
(should (equal (ein:get-url-or-port) ein:testing-notebook-dummy-url))))
(ert-deftest ein:get-notebook--notebook ()
(with-current-buffer (ein:testing-notebook-make-empty)
(should (eq (ein:get-notebook) ein:%notebook%))))
(ert-deftest ein:get-kernel--notebook ()
(with-current-buffer (ein:testing-notebook-make-empty)
(let ((kernel (ein:$notebook-kernel ein:%notebook%)))
(should (ein:$kernel-p kernel))
(should (eq (ein:get-kernel) kernel)))))
(ert-deftest ein:get-cell-at-point--notebook ()
(with-current-buffer (ein:testing-notebook-make-empty)
;; FIXME: write test with non-empty worksheet
(should-not (ein:get-cell-at-point))))
(ert-deftest ein:get-traceback-data--notebook ()
(with-current-buffer (ein:testing-notebook-make-empty)
;; FIXME: write test with non-empty TB
(should-not (ein:get-traceback-data))))
;; Notebook mode
(ert-deftest ein:notebook-ask-before-kill-emacs-simple ()
(let ((ein:notebook--opened-map (make-hash-table :test 'equal)))
(with-current-buffer
(ein:testing-notebook-make-empty "Modified Notebook.ipynb")
(call-interactively #'ein:worksheet-insert-cell-below)
(should (ein:notebook-modified-p)))
(with-current-buffer
(ein:testing-notebook-make-empty "Saved Notebook.ipynb")
(ein:notebook-save-notebook-success ein:%notebook%)
(should-not (ein:notebook-modified-p)))
(kill-buffer (ein:testing-notebook-make-empty "Killed Notebook.ipynb"))
(should (gethash `(,ein:testing-notebook-dummy-url "Modified Notebook.ipynb") ein:notebook--opened-map))
(should (gethash `(,ein:testing-notebook-dummy-url "Saved Notebook.ipynb") ein:notebook--opened-map))
(should-not (gethash `(,ein:testing-notebook-dummy-url "Killed Notebook.ipynb") ein:notebook--opened-map))
(should (= (hash-table-count ein:notebook--opened-map) 2))
(cl-letf (((symbol-function 'y-or-n-p) (lambda (&rest args) nil)))
(ein:notebook-close-notebooks t)
(should-not (ein:notebook-opened-notebooks)))))
;;; Buffer and kill hooks
(ert-deftest ein:notebook-ask-before-kill-buffer/no-ein-buffer ()
(with-temp-buffer
(should (kill-buffer))))
(ert-deftest ein:notebook-ask-before-kill-buffer/new-notebook ()
(with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
(lexical-let (asked
(buf (current-buffer)))
(cl-letf (((symbol-function 'y-or-n-p)
(lambda (&rest _args) (setq asked t) nil)))
(should-not (kill-buffer buf))
(should-not asked)
(should-not (buffer-live-p buf))))))
(ert-deftest ein:notebook-ask-before-kill-buffer/modified-notebook ()
(with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
(lexical-let (asked
(buffer (current-buffer)))
(cl-letf (((symbol-function 'y-or-n-p)
(lambda (&rest _args) (setq asked t) nil)))
(call-interactively #'ein:worksheet-insert-cell-below)
(should-not (kill-buffer buffer))
(should asked)
(should-not (buffer-live-p buffer))))))
(ert-deftest ein:notebook-ask-before-kill-buffer/modified-scratchsheet ()
(with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
(lexical-let (asked
(buf (current-buffer)))
(should (buffer-live-p buf))
(with-current-buffer (ein:worksheet-buffer
(ein:notebook-scratchsheet-open ein:%notebook%))
(lexical-let (asked
(buf2 (current-buffer)))
(should-not (eq buf buf2))
(should (= (ein:worksheet-ncells ein:%worksheet%) 1))
(call-interactively #'ein:worksheet-insert-cell-below)
(should (= (ein:worksheet-ncells ein:%worksheet%) 2))
(should (ein:worksheet-modified-p ein:%worksheet%))
(cl-letf (((symbol-function 'y-or-n-p)
(lambda (&rest _args) (setq asked t) nil)))
(should-not (kill-buffer buf2)))
(should-not asked)
(should-not (buffer-live-p buf2))))
(should-not (ein:worksheet-modified-p ein:%worksheet%))
(cl-letf (((symbol-function 'y-or-n-p)
(lambda (&rest _args) (setq asked t) nil)))
(should-not (kill-buffer buf)))
(should-not asked)
(should-not (buffer-live-p buf)))))
;; Misc unit tests
(ert-deftest ein:notebook-test-notebook-name-simple ()
(should-not (ein:notebook-test-notebook-name nil))
(should-not (ein:notebook-test-notebook-name ""))
(should-not (ein:notebook-test-notebook-name "/"))
(should-not (ein:notebook-test-notebook-name "\\"))
(should-not (ein:notebook-test-notebook-name ":"))
(should-not (ein:notebook-test-notebook-name "a/b"))
(should-not (ein:notebook-test-notebook-name "a\\b"))
(should-not (ein:notebook-test-notebook-name "a:b"))
(should (ein:notebook-test-notebook-name "This is a OK notebook name")))
(defun* eintest:notebook--check-nbformat (&optional orig_nbformat
orig_nbformat_minor
nbformat
nbformat_minor
&key data)
(let ((data (or data
(list :nbformat nbformat :nbformat_minor nbformat_minor
:orig_nbformat orig_nbformat
:orig_nbformat_minor orig_nbformat_minor))))
(ein:notebook--check-nbformat data)))
(ert-deftest ein:notebook--check-nbformat-nothing ()
(mocker-let ((ein:display-warning (message) ()))
(eintest:notebook--check-nbformat)
(eintest:notebook--check-nbformat :data nil)
(eintest:notebook--check-nbformat 2 0)
(eintest:notebook--check-nbformat 2 0 2)
(eintest:notebook--check-nbformat 2 0 2 0)))
(defmacro ein:notebook--check-nbformat-assert-match (regexp &rest args)
`(mocker-let ((ein:display-warning
(message)
((:input-matcher
(lambda (m) (string-match ,regexp m))))))
(eintest:notebook--check-nbformat ,@args)))
(ert-deftest ein:notebook--check-nbformat-warn-major ()
(ein:notebook--check-nbformat-assert-match "v2 -> v3" 2 nil 3)
(ein:notebook--check-nbformat-assert-match "v2 -> v3" 2 0 3 0))
(ert-deftest ein:notebook--check-nbformat-warn-minor ()
(ein:notebook--check-nbformat-assert-match
"version v2\\.1, [^\\.]* up to v2.0" 2 1 2 0))