diff --git a/.ert-runner b/.ert-runner
new file mode 100644
index 0000000..4a8a196
--- /dev/null
+++ b/.ert-runner
@@ -0,0 +1,3 @@
+-l test/test-load.el
+-L ./lisp
+-L ./test
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 50ccb9c..0db09fe 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,4 +20,4 @@ env:
- EVM_EMACS=emacs-git-snapshot-travis IPYCMD=jupyter JUPYTER=1.0.0
script:
- emacs --version
- - python tools/testein.py --emacs emacs --ipython $(which $IPYCMD)
+ - cask exec ert-runner
diff --git a/lisp/ein-kernel.el b/lisp/ein-kernel.el
index 87b7129..899fe57 100644
--- a/lisp/ein-kernel.el
+++ b/lisp/ein-kernel.el
@@ -119,6 +119,7 @@
,@(if kernelspec
`(("kernel" .
(("name" . ,(ein:$kernelspec-name kernelspec))))))))))
+ :sync ein:force-sync
:parser #'ein:json-read
:success (apply-partially #'ein:kernel--kernel-started kernel)
:error (apply-partially #'ein:kernel--start-failed kernel notebook))))))
diff --git a/test/ein-testing-cell.el b/test/ein-testing-cell.el
new file mode 100644
index 0000000..9194c8c
--- /dev/null
+++ b/test/ein-testing-cell.el
@@ -0,0 +1,76 @@
+;;; ein-testing-cell.el --- Testing utilities for cell module
+
+;; Copyright (C) 2012 Takafumi Arakaki
+
+;; Author: Takafumi Arakaki
+
+;; This file is NOT part of GNU Emacs.
+
+;; ein-testing-cell.el is free software: you can redistribute it
+;; and/or modify it under the terms of the GNU General Public License
+;; as published by the Free Software Foundation, either version 3 of
+;; the License, or (at your option) any later version.
+
+;; ein-testing-cell.el is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with ein-testing-cell.el.
+;; If not, see .
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'json)
+
+(defvar ein:testing-example-svg "\
+
+
+
+")
+
+(defun ein:testing-codecell-pyout-data (text &optional prompt-number)
+ "Create a plist representing JSON data for code-cell output.
+TEXT is a string and PROMPT-NUMBER is an integer."
+ (list :output_type "pyout"
+ :prompt_number (or prompt-number 0)
+ :text text))
+
+(defun ein:testing-codecell-data (&optional input prompt-number outputs)
+ "Create a plist representing JSON data for code-type cell.
+To make OUTPUTS data, use `ein:testing-codecell-pyout-data'."
+ (list :cell_type "code"
+ :source (or input "")
+ :language "python"
+ :outputs outputs
+ :metadata (list :collapsed json-false :autoscroll json-false)
+ :execution_count prompt-number))
+
+(defun ein:testing-textcell-data (&optional source cell-type)
+ (list :cell_type cell-type
+ :source (or source "")))
+
+(defun ein:testing-markdowncell-data (&optional source)
+ (ein:testing-textcell-data source "markdown"))
+
+(defun ein:testing-rawcell-data (&optional source)
+ (ein:testing-textcell-data source "raw"))
+
+(defun ein:testing-htmlcell-data (&optional source)
+ (ein:testing-textcell-data source "html"))
+
+(defun ein:testing-headingcell-data (&optional source level)
+ (append (ein:testing-textcell-data source "heading")
+ (list :level (or level 1))))
+
+(provide 'ein-testing-cell)
+
+;;; ein-testing-cell.el ends here
diff --git a/test/ein-testing-kernel.el b/test/ein-testing-kernel.el
new file mode 100644
index 0000000..73b2c60
--- /dev/null
+++ b/test/ein-testing-kernel.el
@@ -0,0 +1,85 @@
+;;; ein-testing-kernel.el --- Testing utilities for kernel module
+
+;; Copyright (C) 2012 Takafumi Arakaki
+
+;; Author: Takafumi Arakaki
+
+;; This file is NOT part of GNU Emacs.
+
+;; ein-testing-kernel.el is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; ein-testing-kernel.el is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with ein-testing-kernel.el. If not, see .
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein-kernel)
+
+
+
+;;; Test `ein:kernel-construct-help-string'
+
+(defvar ein:testing-kernel-construct-help-string-pcallsig-list
+ '(nil :call_def :init_definition :definition))
+
+(defvar ein:testing-kernel-construct-help-string-pdocstring-list
+ '(nil :call_docstring :init_docstring :docstring))
+
+(defun ein:testing-kernel-construct-help-string-test-func (content result)
+ (should (equal (ein:kernel-construct-help-string content) result)))
+
+(defun ein:testing-kernel-construct-help-string-loop
+ (&optional test pcallsig-list pdocstring-list)
+ "Run tests for `ein:kernel-construct-help-string-loop'.
+
+TEST
+ A function takes two arguments, namely CONTENT and RESULT.
+ CONTENT is the argument to `ein:kernel-construct-help-string' and
+ RESULT must match to its returned value. Use `should' to test
+ equality.
+PCALLSIG-LIST
+ `nil' or (subset of) `ein:testing-kernel-construct-help-string-pcallsig-list'.
+PDOCSTRING-LIST
+ `nil' or (subset of) `ein:testing-kernel-construct-help-string-pdocstring-list'.
+
+All combinations of PCALLSIG-LIST and PDOCSTRING-LIST are used to
+construct CONTENT and RESULT."
+ (unless test
+ (setq test #'ein:testing-kernel-construct-help-string-test-func))
+ (unless pcallsig-list
+ (setq pcallsig-list
+ ein:testing-kernel-construct-help-string-pcallsig-list))
+ (unless pdocstring-list
+ (setq pdocstring-list
+ ein:testing-kernel-construct-help-string-pdocstring-list))
+ (loop with callsig = "function(a=1, b=2, c=d)"
+ with docstring = "This function does what."
+ for pcallsig in pcallsig-list
+ do (loop for pdoc in pdocstring-list
+ for content = (append
+ (when pcallsig (list pcallsig callsig))
+ (when pdoc (list pdoc docstring)))
+ for result = (ein:aif (append
+ (when pcallsig (list callsig))
+ (when pdoc (list docstring)))
+ (ein:join-str "\n" it))
+ do (funcall test content result))))
+
+(provide 'ein-testing-kernel)
+
+;;; ein-testing-kernel.el ends here
diff --git a/test/ein-testing-notebook.el b/test/ein-testing-notebook.el
new file mode 100644
index 0000000..e58a711
--- /dev/null
+++ b/test/ein-testing-notebook.el
@@ -0,0 +1,122 @@
+;;; ein-testing-notebook.el --- Testing utilities for notebook module
+
+;; Copyright (C) 2012 Takafumi Arakaki
+
+;; Author: Takafumi Arakaki
+
+;; This file is NOT part of GNU Emacs.
+
+;; ein-testing-notebook.el is free software: you can redistribute it
+;; and/or modify it under the terms of the GNU General Public License
+;; as published by the Free Software Foundation, either version 3 of
+;; the License, or (at your option) any later version.
+
+;; ein-testing-notebook.el is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with ein-testing-notebook.el.
+;; If not, see .
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+
+(require 'ein-notebook)
+(require 'ein-testing-cell)
+
+(defun ein:testing-notebook-from-json (json-string &optional name path)
+ (let* ((data (ein:json-read-from-string json-string))
+ (name (plist-get data :name))
+ (path (plist-get data :path))
+ (kernelspec (make-ein:$kernelspec :name "python3"))
+ (content (make-ein:$content :url-or-port "DUMMY-URL"
+ :ipython-version 3
+ :path path)))
+ (unless name (setq name "NOTEBOOK-DUMMY"))
+ (unless path (setq path "NOTEBOOK-DUMMY"))
+ ;; cl-flet does not work correctly here!
+ (cl-flet ((pop-to-buffer (buf)
+ buf)
+ (ein:query-ipython-version (&optional url-or-port force)
+ 3)
+ (ein:notebook-start-kernel (notebook)
+ notebook))
+ (let ((notebook (ein:notebook-new "DUMMY-URL" path kernelspec)))
+ (setf (ein:$notebook-kernel notebook)
+ (ein:kernel-new 8888 "/kernels" (ein:$notebook-events notebook) (ein:query-ipython-version)))
+ (setf (ein:$kernel-events (ein:$notebook-kernel notebook))
+ (ein:events-new))
+ (ein:notebook-request-open-callback notebook (ein:new-content content nil :data data))
+ (ein:notebook-buffer notebook)))))
+
+(defun ein:testing-notebook-make-data (cells &optional name path)
+ (setq cells
+ (ein:testing-notebook--preprocess-cells-data-for-json-encode cells))
+ (unless name (setq name "Dummy Name.ipynb"))
+ (unless path (setq path "Dummy Name.ipynb"))
+ `((path . ,path)
+ (name . ,name)
+ (type . "notebook")
+ (format . "json")
+ (mimetype . nil)
+ (writeable . t)
+ (content (metadata . ())
+ (nbformat . 4)
+ (nbformat_minor . 0)
+ (cells . ,(apply #'vector cells)))))
+
+(defun ein:testing-notebook--preprocess-cells-data-for-json-encode (cells)
+ "Preprocess CELLS data to make it work nice with `json-encode'."
+ (mapcar (lambda (c)
+ (cond
+ ((equal (plist-get c :cell_type) "code")
+ ;; turn `:outputs' into an array.
+ (plist-put c :outputs (apply #'vector (plist-get c :outputs))))
+ (t c)))
+ cells))
+
+(defun ein:testing-notebook-make-new (&optional name cells-data)
+ "Make new notebook. One empty cell will be inserted
+automatically if CELLS-DATA is nil."
+ (ein:testing-notebook-from-json
+ (json-encode (ein:testing-notebook-make-data cells-data name))))
+
+(defun ein:testing-notebook-make-empty (&optional name)
+ "Make empty notebook and return its buffer.
+Automatically inserted cell for new notebook is deleted."
+ (let ((buffer (ein:testing-notebook-make-new name)))
+ (with-current-buffer buffer
+ (call-interactively #'ein:worksheet-delete-cell))
+ buffer))
+
+(defmacro ein:testing-with-one-cell (cell-type &rest body)
+ "Insert new cell of CELL-TYPE in a clean notebook and execute BODY.
+The new cell is bound to a variable `cell'."
+ (declare (indent 1))
+ `(with-current-buffer (ein:testing-notebook-make-empty)
+ (let ((cell (ein:worksheet-insert-cell-below ein:%worksheet%
+ ,cell-type nil t)))
+ ,@body)))
+
+(defun ein:testing-make-notebook-with-outputs (list-outputs)
+ "Make a new notebook with cells with output.
+LIST-OUTPUTS is a list of list of strings (pyout text). Number
+of LIST-OUTPUTS equals to the number cells to be contained in the
+notebook."
+ (ein:testing-notebook-make-new
+ nil
+ (mapcar (lambda (outputs)
+ (ein:testing-codecell-data
+ nil nil (mapcar #'ein:testing-codecell-pyout-data outputs)))
+ list-outputs)))
+
+(provide 'ein-testing-notebook)
+
+;;; ein-testing-notebook.el ends here
diff --git a/test/ein-testing.el b/test/ein-testing.el
new file mode 100644
index 0000000..091fa12
--- /dev/null
+++ b/test/ein-testing.el
@@ -0,0 +1,76 @@
+;;; ein-testing.el --- Tools for testing
+
+;; Copyright (C) 2012 Takafumi Arakaki
+
+;; Author: Takafumi Arakaki
+
+;; This file is NOT part of GNU Emacs.
+
+;; ein-testing.el is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; ein-testing.el is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with ein-testing.el. If not, see .
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'ein-log)
+
+(defmacro ein:setq-if-not (sym val)
+ `(unless ,sym (setq ,sym ,val)))
+
+(defvar ein:testing-dump-file-log nil
+ "File to save buffer specified by `ein:log-all-buffer-name'.")
+
+(defvar ein:testing-dump-file-messages nil
+ "File to save the ``*Messages*`` buffer.")
+
+(defvar ein:testing-dump-file-debug nil)
+
+(defvar ein:testing-dump-server-log nil)
+
+(defun ein:testing-save-buffer (buffer-or-name file-name)
+ (when (and buffer-or-name (get-buffer buffer-or-name) file-name)
+ (with-current-buffer buffer-or-name
+ (write-region (point-min) (point-max) file-name))))
+
+(defun ein:testing-dump-logs ()
+ (ein:testing-save-buffer "*Messages*" ein:testing-dump-file-messages)
+ (ein:testing-save-buffer "*ein:jupyter-server*" ein:testing-dump-server-log)
+ (ein:testing-save-buffer ein:log-all-buffer-name ein:testing-dump-file-log))
+
+(defvar ein:testing-dump-logs--saved nil)
+
+(defun ein:testing-dump-logs-noerror ()
+ (if ein:testing-dump-logs--saved
+ (message "EIN:TESTING-DUMP-LOGS-NOERROR called but already saved.")
+ (condition-case err
+ (progn (ein:testing-dump-logs)
+ (setq ein:testing-dump-logs--saved t))
+ (error
+ (message "Error while executing EIN:TESTING-DUMP-LOGS. err = %S"
+ err)
+ (when ein:testing-dump-file-debug
+ (signal (car err) (cdr err)))))))
+
+(defadvice ert-run-tests-batch (after ein:testing-dump-logs-hook activate)
+ "Hook `ein:testing-dump-logs-noerror' because `kill-emacs-hook'
+is not run in batch mode before Emacs 24.1."
+ (ein:testing-dump-logs-noerror))
+
+(add-hook 'kill-emacs-hook #'ein:testing-dump-logs-noerror)
+
+(provide 'ein-testing)
+
+;;; ein-testing.el ends here
diff --git a/test/func-test.el b/test/func-test.el
new file mode 100644
index 0000000..877ebc9
--- /dev/null
+++ b/test/func-test.el
@@ -0,0 +1,290 @@
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein-loaddefs)
+(require 'ein-notebooklist)
+(require 'ein-jupyter)
+(require 'wid-edit)
+(require 'ein-testing)
+(require 'ein-testing-cell)
+
+(let ((backend (getenv "EL_REQUEST_BACKEND")))
+ (when (and backend (not (equal backend "")))
+ (setq request-backend (intern backend))
+ (message "Using request-backend = %S" request-backend)))
+
+
+(ein:setq-if-not ein:testing-dump-file-log "func-test-batch-log.log")
+(ein:setq-if-not ein:testing-dump-file-messages "func-test-batch-messages.log")
+(ein:setq-if-not ein:testing-dump-server-log "func-test_server_batch_emacs.log")
+
+(setq message-log-max t)
+
+(defun ein:testing-wait-until (message predicate &optional predargs max-count)
+ "Wait until PREDICATE function returns non-`nil'.
+PREDARGS is argument list for the PREDICATE function.
+Make MAX-COUNT larger \(default 50) to wait longer before timeout."
+ (ein:log 'debug "TESTING-WAIT-UNTIL start")
+ (ein:log 'debug "TESTING-WAIT-UNTIL waiting on: %s" message)
+ (unless max-count (setq max-count 50))
+ (unless (loop repeat max-count
+ when (apply predicate predargs)
+ return t
+ ;; borrowed from `deferred:sync!':
+ do (sit-for 0.2)
+ do (sleep-for 0.2))
+ (error "Timeout"))
+ (ein:log 'debug "TESTING-WAIT-UNTIL end"))
+
+(defun ein:testing-get-notebook-by-name (url-or-port notebook-name &optional path)
+ (ein:log 'debug "TESTING-GET-NOTEBOOK-BY-NAME start")
+ (when path
+ (setq notebook-name (format "%s/%s" path notebook-name)))
+ (ein:notebooklist-open url-or-port path t)
+ (ein:testing-wait-until "ein:notebooklist-open"
+ (lambda () (and (bufferp (get-buffer (format ein:notebooklist-buffer-name-template url-or-port)))
+ (ein:notebooklist-get-buffer url-or-port))))
+ (with-current-buffer (ein:notebooklist-get-buffer url-or-port)
+ (prog1
+ (ignore-errors
+ (ein:notebooklist-open-notebook-by-name notebook-name url-or-port))
+ (ein:log 'debug "TESTING-GET-NOTEBOOK-BY-NAME end"))))
+
+(defun ein:testing-get-untitled0-or-create (url-or-port &optional path)
+ (ein:log 'debug "TESTING-GET-UNTITLED0-OR-CREATE start")
+ (let ((notebook (ein:testing-get-notebook-by-name url-or-port "Untitled.ipynb" path)))
+ (if notebook
+ (progn (ein:log 'debug
+ "TESTING-GET-UNTITLED0-OR-CREATE notebook already exists")
+ notebook)
+ (ein:log 'debug
+ "TESTING-GET-UNTITLED0-OR-CREATE creating notebook")
+ (let ((created nil)
+ (kernelspec (ein:get-kernelspec url-or-port "python3")))
+ (ein:notebooklist-new-notebook url-or-port kernelspec path
+ (lambda (&rest -ignore-)
+ (setq created t)))
+ (ein:testing-wait-until "ein:notebooklist-new-notebook"
+ (lambda () created)))
+ (prog1
+ (ein:testing-get-notebook-by-name url-or-port "Untitled.ipynb" path)
+ (ein:log 'debug "TESTING-GET-UNTITLED0-OR-CREATE end")))))
+
+(defvar ein:notebooklist-after-open-hook nil)
+
+(defadvice ein:notebooklist-url-retrieve-callback
+ (after ein:testing-notebooklist-url-retrieve-callback activate)
+ "Advice to add `ein:notebooklist-after-open-hook'."
+ (run-hooks 'ein:notebooklist-after-open-hook))
+
+(defun ein:testing-delete-notebook (url-or-port notebook &optional path)
+ (ein:log 'debug "TESTING-DELETE-NOTEBOOK start")
+ (ein:notebooklist-open url-or-port (ein:$notebook-notebook-path notebook) t)
+ (ein:testing-wait-until "ein:notebooklist-open"
+ (lambda ()
+ (bufferp (get-buffer (format ein:notebooklist-buffer-name-template url-or-port))))
+ nil 50)
+ (with-current-buffer (ein:notebooklist-get-buffer url-or-port)
+ (ein:testing-wait-until "ein:notebooklist-get-buffer"
+ (lambda () (eql major-mode 'ein:notebooklist-mode))
+ nil
+ 50)
+ (ein:log 'debug "TESTING-DELETE-NOTEBOOK deleting notebook")
+ (ein:notebooklist-delete-notebook (ein:$notebook-notebook-path notebook)))
+ (ein:log 'debug "TESTING-DELETE-NOTEBOOK end"))
+
+;; (ert-deftest 00-jupyter-start-server ()
+;; (ein:log 'verbose "ERT TESTING-JUPYTER-START-SERVER start")
+;; (condition-case err
+;; (ein:testing-start-server)
+;; (error (ein:log 'verbose "ERT TESTING-JUPYTER-START-SERVER error when launching: %s" err)
+;; (sit-for 10)
+;; (ein:jupyter-server-login-and-open)))
+;; (should (processp %ein:jupyter-server-session%))
+;; (ein:log 'verbose "ERT TESTING-JUPYTER-START-SERVER end"))
+
+(ert-deftest 01-open-notebooklist ()
+ (ein:log 'verbose "ERT OPEN-NOTEBOOKLIST start")
+ (ein:notebooklist-open *ein:testing-port* "/" t)
+ (ein:testing-wait-until
+ "ein:notebooklist-open"
+ (lambda ()
+ (ein:notebooklist-get-buffer *ein:testing-port*))
+ nil 5000)
+ (with-current-buffer (ein:notebooklist-get-buffer *ein:testing-port*)
+ (should (eql major-mode 'ein:notebooklist-mode))))
+
+
+(ert-deftest 00-query-kernelspecs ()
+ (ein:log 'verbose "ERT QUERY-KERNELSPECS")
+ (ein:log 'verbose (format "ERT QUERY-KERNELSPECS: Pre-query kernelspec count %s." (hash-table-count ein:available-kernelspecs)))
+ (ein:query-kernelspecs *ein:testing-port*)
+ (should (>= (hash-table-count ein:available-kernelspecs) 1))
+ (ein:log 'verbose (format "ERT QUERY-KERNELSPECS: Post-query kernelspec count %s." (hash-table-count ein:available-kernelspecs)))
+ )
+
+(ert-deftest 10-get-untitled0-or-create ()
+ (ein:log 'verbose "ERT TESTING-GET-UNTITLED0-OR-CREATE start")
+ (let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
+ (ein:testing-wait-until
+ "ein:testing-get-untitled0-or-create"
+ (lambda () (ein:aand (ein:$notebook-kernel notebook)
+ (ein:kernel-live-p it)))
+ nil 50000)
+ (with-current-buffer (ein:notebook-buffer notebook)
+ (should (equal (ein:$notebook-notebook-name ein:%notebook%)
+ "Untitled.ipynb"))))
+ (ein:log 'verbose "ERT TESTING-GET-UNTITLED0-OR-CREATE end"))
+
+(ert-deftest 20-delete-untitled0 ()
+ (ein:log 'verbose "----------------------------------")
+ (ein:log 'verbose "ERT TESTING-DELETE-UNTITLED0 start")
+ (ein:log 'verbose "ERT TESTING-DELETE-UNTITLED0 creating notebook")
+ (let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
+ (ein:testing-wait-until
+ "ein:test-get-untitled0-or-create"
+ (lambda ()
+ (ein:aand notebook
+ (ein:$notebook-kernel it)
+ (ein:kernel-live-p it)))
+ nil 50)
+ (ein:log 'verbose "ERT TESTING-DELETE-UNTITLED0 deleting notebook")
+ (ein:testing-delete-notebook *ein:testing-port* notebook))
+ (ein:log 'verbose
+ "ERT TESTING-DELETE-UNTITLED0 check that the notebook is deleted")
+ (let ((num-notebook
+ (length (ein:testing-get-notebook-by-name *ein:testing-port*
+ "Untitled.ipynb"
+ ""))))
+ (should (= num-notebook 0)))
+ (ein:log 'verbose "ERT TESTING-DELETE-UNTITLED0 end"))
+
+(ert-deftest 11-notebook-execute-current-cell-simple ()
+ (let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
+ (ein:testing-wait-until
+ "ein:testing-get-untitled0-or-create"
+ (lambda () (ein:aand (ein:$notebook-kernel notebook)
+ (ein:kernel-live-p it)))
+ nil 50)
+ (with-current-buffer (ein:notebook-buffer notebook)
+ (call-interactively #'ein:worksheet-insert-cell-below)
+ (insert "a = 100\na")
+ (let ((cell (call-interactively #'ein:worksheet-execute-cell)))
+ (ein:testing-wait-until "ein:worksheet-execute-cell"
+ (lambda () (not (slot-value cell 'running)))
+ nil
+ 50000))
+ ;; (message "%s" (buffer-string))
+ (save-excursion
+ (should (search-forward-regexp "Out \\[[0-9]+\\]" nil t))
+ (should (search-forward "100" nil t))))))
+
+(defun ein:testing-image-type (image)
+ "Return the type of IMAGE.
+See the definition of `create-image' for how it works."
+ (assert (and (listp image) (eq (car image) 'image)) nil
+ "%S is not an image." image)
+ (plist-get (cdr image) :type))
+
+(ert-deftest 12-notebook-execute-current-cell-pyout-image ()
+ (let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
+ (ein:testing-wait-until
+ "ein:testing-get-untitled0-or-create"
+ (lambda () (ein:aand (ein:$notebook-kernel notebook)
+ (ein:kernel-live-p it))))
+ (with-current-buffer (ein:notebook-buffer notebook)
+ (call-interactively #'ein:worksheet-insert-cell-below)
+ ;; Use IPython.core.display rather than IPython.display to
+ ;; test it with older (< 0.13) IPython.
+ (insert (concat "from IPython.core.display import SVG\n"
+ (format "SVG(data=\"\"\"%s\"\"\")"
+ ein:testing-example-svg)))
+ (let ((cell (call-interactively #'ein:worksheet-execute-cell)))
+ ;; It seems in this case, watching `:running' does not work
+ ;; well sometimes. Probably "output reply" (iopub) comes
+ ;; before "execute reply" in this case.
+ (ein:testing-wait-until "ein:worksheet-execute-cell"
+ (lambda () (slot-value cell 'outputs))
+ nil
+ 50)
+ ;; This cell has only one input
+ (should (= (length (oref cell :outputs)) 1))
+ ;; This output is a SVG image
+ (let ((out (nth 0 (oref cell :outputs))))
+ (should (equal (plist-get out :output_type) "execute_result"))
+ (should (plist-get out :svg))))
+ ;; Check the actual output in the buffer:
+ (save-excursion
+ (should (search-forward-regexp "Out \\[[0-9]+\\]" nil t))
+ (should (= (forward-line) 0))
+ (if (image-type-available-p 'svg)
+ (let ((image (get-text-property (point) 'display)))
+ (should (eq (ein:testing-image-type image) 'svg)))
+ (ein:log 'info
+ "Skipping image check as SVG image type is not available."))))))
+
+(ert-deftest 13-notebook-execute-current-cell-stream ()
+ (let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
+ (ein:testing-wait-until
+ "ein:testing-get-untitled0-or-create"
+ (lambda () (ein:aand (ein:$notebook-kernel notebook)
+ (ein:kernel-live-p it)))
+ nil 50)
+ (with-current-buffer (ein:notebook-buffer notebook)
+ (call-interactively #'ein:worksheet-insert-cell-below)
+ (insert "print('Hello')")
+ (let ((cell (call-interactively #'ein:worksheet-execute-cell)))
+ (ein:testing-wait-until "ein:worksheet-execute-cell"
+ (lambda () (not (oref cell :running)))
+ nil
+ 50000))
+ (save-excursion
+ (should-not (search-forward-regexp "Out \\[[0-9]+\\]" nil t))
+ (should (search-forward-regexp "^Hello$" nil t))))))
+
+(ert-deftest 14-notebook-execute-current-cell-question ()
+ (let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
+ (ein:testing-wait-until
+ "ein:testing-get-untitled0-or-create"
+ (lambda () (ein:aand (ein:$notebook-kernel notebook)
+ (ein:kernel-live-p it)))
+ nil 50)
+ (with-current-buffer (ein:notebook-buffer notebook)
+ (call-interactively #'ein:worksheet-insert-cell-below)
+ (insert "range?")
+ (let ((cell (call-interactively #'ein:worksheet-execute-cell)))
+ (ein:testing-wait-until
+ "ein:worksheet-execute-cell"
+ (lambda () (not (oref cell :running)))
+ nil 50))
+ (with-current-buffer (get-buffer (ein:$notebook-pager notebook))
+ (should (search-forward "Docstring:"))))))
+
+(ert-deftest 15-notebook-request-help ()
+ (let ((notebook (ein:testing-get-untitled0-or-create *ein:testing-port*)))
+ (ein:testing-wait-until
+ "ein:testing-get-untitled0-or-create"
+ (lambda () (ein:aand (ein:$notebook-kernel notebook)
+ (ein:kernel-live-p it)))
+ nil 50000)
+ (with-current-buffer (ein:notebook-buffer notebook)
+ (call-interactively #'ein:worksheet-insert-cell-below)
+ (let ((pager-name (ein:$notebook-pager ein:%notebook%)))
+ (ein:aif (get-buffer pager-name)
+ (kill-buffer it))
+ (insert "file")
+ (call-interactively #'ein:pytools-request-help)
+ ;; Pager buffer will be created when got the response
+ (ein:testing-wait-until
+ "ein:pythools-request-help"
+ (lambda () (get-buffer pager-name))
+ nil 50000)
+ (with-current-buffer (get-buffer pager-name)
+ (should (search-forward "Docstring:")))))))
+
+(ert-deftest 30-testing-jupyter-stop-server ()
+ (ein:log 'verbose "ERT TESTING-JUPYTER-STOP-SERVER start")
+ (cl-letf (((symbol-function 'y-or-n-p) #'ignore))
+ (ein:jupyter-server-stop t))
+ (should-not (processp %ein:jupyter-server-session%))
+ (ein:log 'verbose "ERT TESTING-JUPYTER-STOP-SERVER end"))
diff --git a/test/redirecting-server.py b/test/redirecting-server.py
new file mode 100644
index 0000000..7953070
--- /dev/null
+++ b/test/redirecting-server.py
@@ -0,0 +1,17 @@
+from flask import Flask, redirect
+
+app = Flask(__name__)
+
+@app.route('/')
+def jupyter_redirect():
+ return redirect("http://127.0.0.1:8888/", code=302)
+
+@app.route('/api')
+def api_check():
+ return redirect("http://127.0.0.1:8888/api", code=302)
+
+if __name__=='__main__':
+ port = int(8000)
+ app.run(host='127.0.0.1', port=port)
+
+
diff --git a/test/test-ein-ac.el b/test/test-ein-ac.el
new file mode 100644
index 0000000..c69236b
--- /dev/null
+++ b/test/test-ein-ac.el
@@ -0,0 +1,17 @@
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein-ac)
+(require 'ein-testing-kernel)
+
+
+(ert-deftest ein:ac-set-document ()
+ (let ((string "candidate string"))
+ (should-not (get-text-property 0 'document string))
+ (ein:testing-kernel-construct-help-string-loop
+ (lambda (content result)
+ (ein:ac-set-document string content '-not-used-)
+ (let ((props (text-properties-at 0 string)))
+ ;; document property may be nil, but must be set.
+ (should (member 'document props))
+ (should (equal (plist-get props 'document) result)))))))
diff --git a/test/test-ein-cell-notebook.el b/test/test-ein-cell-notebook.el
new file mode 100644
index 0000000..090b09d
--- /dev/null
+++ b/test/test-ein-cell-notebook.el
@@ -0,0 +1,249 @@
+;; Tests for cell function that requires notebook buffer
+
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein-notebook)
+(require 'ein-testing-notebook)
+
+
+;; ein:cell-location
+
+(ert-deftest ein:cell-location-codecell-prompt-beg ()
+ (ein:testing-with-one-cell 'code
+ (should (equal (marker-position (ein:cell-location cell :prompt))
+ (save-excursion
+ (goto-char (point-max))
+ (search-backward "In [ ]:")
+ (point))))))
+
+(ert-deftest ein:cell-location-codecell-prompt-end ()
+ (ein:testing-with-one-cell 'code
+ (should (equal (marker-position (ein:cell-location cell :prompt t))
+ (1- (point))))))
+
+(ert-deftest ein:cell-location-codecell-input-beg ()
+ (ein:testing-with-one-cell 'code
+ (insert "some text")
+ (should (equal (marker-position (ein:cell-location cell :input))
+ (1- (point-at-bol))))))
+
+(ert-deftest ein:cell-location-codecell-input-end ()
+ (ein:testing-with-one-cell 'code
+ (insert "some text")
+ (should (equal (marker-position (ein:cell-location cell :input t))
+ (1+ (point))))))
+
+
+;; from-json
+
+(ert-deftest eintest:cell-input-prompt-number ()
+ (ein:testing-with-one-cell
+ (ein:cell-from-json
+ (list :cell_type "code"
+ :source "some input"
+ :metadata (list :collapsed json-false :autoscroll json-false)
+ :execution_count 111)
+ :ewoc (oref ein:%worksheet% :ewoc))
+ (goto-char (ein:cell-location cell))
+ (should (looking-at "\
+In \\[111\\]:
+some input
+"))))
+
+(ert-deftest eintest:cell-input-prompt-star ()
+ (ein:testing-with-one-cell
+ (ein:cell-from-json
+ (list :cell_type "code"
+ :source "some input"
+ :metadata (list :collapsed json-false :autoscroll json-false)
+ :execution_count "*")
+ :ewoc (oref ein:%worksheet% :ewoc))
+ (goto-char (ein:cell-location cell))
+ (should (looking-at "\
+In \\[\\*\\]:
+some input
+"))))
+
+(ert-deftest eintest:cell-input-prompt-empty ()
+ (ein:testing-with-one-cell
+ (ein:cell-from-json
+ (list :cell_type "code"
+ :metadata (list :collapsed json-false :autoscroll json-false)
+ :source "some input")
+ :ewoc (oref ein:%worksheet% :ewoc))
+ (goto-char (ein:cell-location cell))
+ (should (looking-at "\
+In \\[ \\]:
+some input
+"))))
+
+
+;; Insert pyout/display_data
+
+(defun eintest:cell-insert-output (outputs regexp)
+ (let ((ein:output-type-preference
+ '(emacs-lisp image/svg image/png jpeg text/plain text/html text/latex text/javascript)))
+ (message "%S" (list :cell_type "code"
+ :outputs outputs
+ :source "Some input"
+ :metadata (list :collapsed json-false :autoscroll json-false)
+ :execution_count 111))
+ (ein:testing-with-one-cell
+ (ein:cell-from-json
+ (list :cell_type "code"
+ :outputs outputs
+ :source "some input"
+ :metadata (list :collapsed json-false :autoscroll json-false)
+ :execution_count 111)
+ :ewoc (oref ein:%worksheet% :ewoc))
+ (goto-char (ein:cell-location cell))
+ (should (looking-at (format "\
+In \\[111\\]:
+some input
+%s" regexp))))))
+
+(defmacro eintest:gene-test-cell-insert-output-pyout-and-display-data
+ (name regexps outputs)
+ (declare (indent defun))
+ (let ((test-pyout
+ (intern (format "ein:cell-insert-output-pyout-%s" name)))
+ (test-display-data
+ (intern (format "ein:cell-insert-output-display-data-%s" name)))
+ (outputs-pyout
+ (loop for i from 1
+ for x in outputs
+ collect
+ (append x (list :output_type "execute_result" :execution_count i :metadata nil))))
+ (outputs-display-data
+ (mapcar (lambda (x) (append '(:output_type "display_data" :metadata nil) x))
+ outputs))
+ (regexp-pyout
+ (ein:join-str
+ ""
+ (loop for i from 1
+ for x in regexps
+ collect (format "Out \\[%s\\]:\n%s\n" i x))))
+ (regexp-display-data
+ (concat (ein:join-str "\n" regexps) "\n")))
+ `(progn
+ (ert-deftest ,test-pyout ()
+ (eintest:cell-insert-output ',outputs-pyout
+ ,regexp-pyout))
+ (ert-deftest ,test-display-data ()
+ (eintest:cell-insert-output ',outputs-display-data
+ ,regexp-display-data)))))
+
+(eintest:gene-test-cell-insert-output-pyout-and-display-data
+ text ("some output") ((:data (:text/plain "some output"))))
+
+(eintest:gene-test-cell-insert-output-pyout-and-display-data
+ latex
+ ("some output \\\\LaTeX")
+ ((:data (:text/latex "some output \\LaTeX"))))
+
+(when (image-type-available-p 'svg)
+ (eintest:gene-test-cell-insert-output-pyout-and-display-data
+ svg
+ (" ")
+ ((:data (:text/plain "some output text" :svg ein:testing-example-svg)))))
+
+(eintest:gene-test-cell-insert-output-pyout-and-display-data
+ html
+ ("some output text")
+ ((:data (:text/plain ("some output text") :text/html ("not shown")))))
+
+(eintest:gene-test-cell-insert-output-pyout-and-display-data
+ javascript
+ ("some output text")
+ ((:data (:text/plain "some output text" :text/javascript "$.do.something()"))))
+
+(eintest:gene-test-cell-insert-output-pyout-and-display-data
+ text-two
+ ("first output text" "second output text")
+ ((:data (:text/plain "first output text")) (:text/plain "second output text")))
+
+(eintest:gene-test-cell-insert-output-pyout-and-display-data
+ text-javascript
+ ("first output text" "second output text")
+ ((:data (:text/plain "first output text"))
+ (:data (:text/plain "second output text" :text/javascript "$.do.something()"))))
+
+(when (image-type-available-p 'svg)
+ (eintest:gene-test-cell-insert-output-pyout-and-display-data
+ text-latex-svg
+ ("first output text" "second output \\\\LaTeX" " ")
+ ((:data (:text/plain "first output text"))
+ (:data (:text/latex "second output \\LaTeX"))
+ (:data (:text/plain "some output text" :image/svg ein:testing-example-svg)))))
+
+
+;; Insert pyerr
+
+(ert-deftest ein:cell-insert-output-pyerr-simple ()
+ (eintest:cell-insert-output
+ (list (list :output_type "pyerr"
+ :traceback '("some traceback 1"
+ "some traceback 2")))
+ "\
+some traceback 1
+some traceback 2
+"))
+
+
+;; Insert stream
+
+(ert-deftest ein:cell-insert-output-stream-simple-stdout ()
+ (eintest:cell-insert-output
+ (list (list :output_type "stream"
+ :name "stdout"
+ :text "some stdout 1"))
+ "\
+some stdout 1
+"))
+
+(ert-deftest ein:cell-insert-output-stream-stdout-stderr ()
+ (eintest:cell-insert-output
+ (list (list :output_type "stream"
+ :name "stdout"
+ :text "some stdout 1")
+ (list :output_type "stream"
+ :name "stderr"
+ :text "some stderr 1"))
+ "\
+some stdout 1
+some stderr 1
+"))
+
+(ert-deftest ein:cell-insert-output-stream-flushed-stdout ()
+ (eintest:cell-insert-output
+ (list (list :output_type "stream"
+ :name "stdout"
+ :text "some stdout 1")
+ (list :output_type "stream"
+ :name "stdout"
+ :text "some stdout 2"))
+ "\
+some stdout 1some stdout 2
+"))
+
+(ert-deftest ein:cell-insert-output-stream-flushed-stdout-and-stderr ()
+ (eintest:cell-insert-output
+ (list (list :output_type "stream"
+ :name "stdout"
+ :text "some stdout 1")
+ (list :output_type "stream"
+ :name "stderr"
+ :text "some stderr 1")
+ (list :output_type "stream"
+ :name "stdout"
+ :text "some stdout 2")
+ (list :output_type "stream"
+ :name "stderr"
+ :text "some stderr 2"))
+ "\
+some stdout 1
+some stderr 1
+some stdout 2
+some stderr 2
+"))
diff --git a/test/test-ein-cell.el b/test/test-ein-cell.el
new file mode 100644
index 0000000..532d164
--- /dev/null
+++ b/test/test-ein-cell.el
@@ -0,0 +1,270 @@
+(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-cell)
+(require 'ein-testing-cell)
+
+
+;;; ein:cell-from-json
+
+(defun eintest:cell-from-json (data &rest args)
+ (let ((cell (apply #'ein:cell-from-json data args)))
+ (should-not (ein:cell-active-p cell))
+ cell))
+
+(ert-deftest ein:cell-from-json-code ()
+ (let* ((input-prompt-number 111)
+ (output-prompt-number 222)
+ (input (ein:join-str "\n" '("first input" "second input")))
+ (output-0 (list :output_type "execute_result"
+ :prompt_number output-prompt-number
+ :text (list "first output"
+ "second output")))
+ (data (ein:testing-codecell-data
+ input input-prompt-number (list output-0)))
+ (cell (eintest:cell-from-json data)))
+ (should (ein:codecell-p cell))
+ (should (equal (oref cell :input-prompt-number) input-prompt-number))
+ (should (equal (oref cell :input) input))
+ (should (equal (car (oref cell :outputs)) output-0))
+ (should (equal (oref cell :collapsed) nil))))
+
+(ert-deftest ein:cell-from-json-text ()
+ (let* ((input (ein:join-str "\n" '("first input" "second input")))
+ (data (list :cell_type "text" :source input))
+ (cell (eintest:cell-from-json data)))
+ (should (ein:textcell-p cell))
+ (should (equal (oref cell :input) input))))
+
+(ert-deftest ein:cell-from-json-html ()
+ (let* ((input (ein:join-str "\n" '("first input" "second input")))
+ (data (list :cell_type "html" :source input))
+ (cell (eintest:cell-from-json data)))
+ (should (ein:htmlcell-p cell))
+ (should (equal (oref cell :input) input))))
+
+(ert-deftest ein:cell-from-json-markdown ()
+ (let* ((input (ein:join-str "\n" '("first input" "second input")))
+ (data (list :cell_type "markdown" :source input))
+ (cell (eintest:cell-from-json data)))
+ (should (ein:markdowncell-p cell))
+ (should (equal (oref cell :input) input))))
+
+(ert-deftest ein:cell-from-json-raw ()
+ (let* ((input (ein:join-str "\n" '("first input" "second input")))
+ (data (list :cell_type "raw" :source input))
+ (cell (eintest:cell-from-json data)))
+ (should (ein:rawcell-p cell))
+ (should (equal (oref cell :input) input))))
+
+(ert-deftest ein:cell-from-json-heading ()
+ (let* ((input (ein:join-str "\n" '("first input" "second input")))
+ (data (list :cell_type "heading" :source input))
+ (cell (eintest:cell-from-json data)))
+ (should (ein:headingcell-p cell))
+ (should (equal (oref cell :input) input))))
+
+
+;; ein:cell-to-json
+
+(defun eintest:cell-to-json (cell input &optional discard-output)
+ (mocker-let ((ein:cell-get-text
+ (cell)
+ ((:input (list cell) :output input))))
+ (ein:cell-to-json cell discard-output)))
+
+(ert-deftest ein:cell-to-json-code ()
+ (let* ((input-prompt-number 111)
+ (output-prompt-number 222)
+ (input (ein:join-str "\n" '("first input" "second input")))
+ (output-0 (list :output_type "execute_result"
+ :prompt_number output-prompt-number
+ :text (list "first output"
+ "second output")))
+ (data (ein:testing-codecell-data
+ input input-prompt-number (list output-0)))
+ (cell (eintest:cell-from-json data))
+ (alist (eintest:cell-to-json cell input)))
+ (should (equal (cdr (assq 'input alist)) "first input\nsecond input"))
+ (should (equal (cdr (assq 'cell_type alist)) "code"))
+ (should (equal (cdr (assq 'outputs alist)) `[,output-0]))
+ (should (equal (cdr (assq 'language alist)) "python"))
+ (should (equal (cdr (assq 'collapsed alist)) json-false))))
+
+(ert-deftest ein:cell-to-json-code-discard-output ()
+ (let* ((input-prompt-number 111)
+ (output-prompt-number 222)
+ (input (ein:join-str "\n" '("first input" "second input")))
+ (output-0 (list :output_type "execute_result"
+ :prompt_number output-prompt-number
+ :text (list "first output"
+ "second output")))
+ (data (ein:testing-codecell-data
+ input input-prompt-number (list output-0)))
+ (cell (eintest:cell-from-json data))
+ (alist (eintest:cell-to-json cell input t)))
+ (should (equal (cdr (assq 'input alist)) "first input\nsecond input"))
+ (should (equal (cdr (assq 'cell_type alist)) "code"))
+ (should (equal (cdr (assq 'outputs alist)) []))
+ (should (equal (cdr (assq 'language alist)) "python"))
+ (should (equal (cdr (assq 'collapsed alist)) json-false))))
+
+(ert-deftest ein:cell-to-json-text ()
+ (let* ((input (ein:join-str "\n" '("first input" "second input")))
+ (data (list :cell_type "text" :source input))
+ (cell (eintest:cell-from-json data))
+ (alist (eintest:cell-to-json cell input)))
+ (should (equal (cdr (assq 'cell_type alist)) "text"))
+ (should (equal (cdr (assq 'source alist)) "first input\nsecond input"))))
+
+(ert-deftest ein:cell-to-json-html ()
+ (let* ((input (ein:join-str "\n" '("first input" "second input")))
+ (data (list :cell_type "html" :source input))
+ (cell (eintest:cell-from-json data))
+ (alist (eintest:cell-to-json cell input)))
+ (should (equal (cdr (assq 'cell_type alist)) "html"))
+ (should (equal (cdr (assq 'source alist)) "first input\nsecond input"))))
+
+(ert-deftest ein:cell-to-json-markdown ()
+ (let* ((input (ein:join-str "\n" '("first input" "second input")))
+ (data (list :cell_type "markdown" :source input))
+ (cell (eintest:cell-from-json data))
+ (alist (eintest:cell-to-json cell input)))
+ (should (equal (cdr (assq 'cell_type alist)) "markdown"))
+ (should (equal (cdr (assq 'source alist)) "first input\nsecond input"))))
+
+(ert-deftest ein:cell-to-json-raw ()
+ (let* ((input (ein:join-str "\n" '("first input" "second input")))
+ (data (list :cell_type "raw" :source input))
+ (cell (eintest:cell-from-json data))
+ (alist (eintest:cell-to-json cell input)))
+ (should (equal (cdr (assq 'cell_type alist)) "raw"))
+ (should (equal (cdr (assq 'source alist)) "first input\nsecond input"))))
+
+(ert-deftest ein:cell-to-json-heading ()
+ (let* ((input (ein:join-str "\n" '("first input" "second input")))
+ (data (list :cell_type "heading" :source input))
+ (cell (eintest:cell-from-json data))
+ (alist (eintest:cell-to-json cell input)))
+ (should (equal (cdr (assq 'cell_type alist)) "heading"))
+ (should (equal (cdr (assq 'source alist)) "first input\nsecond input"))
+ (should (equal (cdr (assq 'level alist)) 1))))
+
+
+;;; ein:cell-convert/copy
+
+(ert-deftest ein:cell-convert-code-to-markdown ()
+ (let* ((input-prompt-number 111)
+ (output-prompt-number 222)
+ (input (ein:join-str "\n" '("first input" "second input")))
+ (output-0 (list :output_type "execute_result"
+ :prompt_number output-prompt-number
+ :text (list "first output"
+ "second output")))
+ (data (ein:testing-codecell-data
+ input input-prompt-number (list output-0)))
+ (dummy-ewoc (ewoc-create 'dummy))
+ (old (eintest:cell-from-json data :ewoc dummy-ewoc))
+ (new (ein:cell-convert old "markdown")))
+ (should (ein:codecell-p old))
+ (should (ein:markdowncell-p new))
+ (should (equal (oref new :input) input))))
+
+(ert-deftest ein:cell-convert-markdown-to-code ()
+ (let* ((input (ein:join-str "\n" '("first input" "second input")))
+ (dummy-ewoc (ewoc-create 'dummy))
+ (data (list :cell_type "markdown" :source input))
+ (old (eintest:cell-from-json data :ewoc dummy-ewoc))
+ (new (ein:cell-convert old "code")))
+ (should (ein:markdowncell-p old))
+ (should (ein:codecell-p new))
+ (should (equal (oref new :input) input))))
+
+(ert-deftest ein:cell-copy-code ()
+ (let* ((input-prompt-number 111)
+ (output-prompt-number 222)
+ (input (ein:join-str "\n" '("first input" "second input")))
+ (output-0 (list :output_type "execute_result"
+ :prompt_number output-prompt-number
+ :text (list "first output"
+ "second output")))
+ (data (ein:testing-codecell-data
+ input input-prompt-number (list output-0)))
+ (dummy-ewoc (ewoc-create 'dummy))
+ (old (eintest:cell-from-json data :ewoc dummy-ewoc))
+ (new (ein:cell-copy old)))
+ (should (ein:codecell-p old))
+ (should (ein:codecell-p new))
+ (should-not (equal (oref old :cell-id)
+ (oref new :cell-id)))
+ (should (equal (oref old :input) input))
+ (should (equal (oref new :input) input))))
+
+(ert-deftest ein:cell-copy-text-types ()
+ (loop for cell-type in '("text" "html" "markdown" "raw" "heading")
+ for cell-p = (intern (format "ein:%scell-p" cell-type))
+ do
+ (let* ((input (ein:join-str "\n" '("first input" "second input")))
+ (data (list :cell_type cell-type :source input :metadata nil))
+ (dummy-ewoc (ewoc-create 'dummy))
+ (old (eintest:cell-from-json data :ewoc dummy-ewoc))
+ (new (ein:cell-copy old)))
+ (should (funcall cell-p old))
+ (should (funcall cell-p new))
+ (should-not (equal (oref old :cell-id)
+ (oref new :cell-id)))
+ (should (equal (oref old :input) input))
+ (should (equal (oref new :input) input)))))
+
+
+;;; ein:cell-element-get
+
+(ert-deftest ein:cell-element-get-basecell ()
+ (let ((cell (ein:basecell "Cell")))
+ ;; it's not supported
+ (should-error (ein:cell-element-get :prompt))))
+
+(ert-deftest ein:cell-element-get-codecell ()
+ (let* ((element (list :prompt 1
+ :input 2
+ :output '(3 4)
+ :footer 5))
+ (cell (ein:cell-from-type "code" :element element)))
+ (mapc (lambda (kv)
+ (should (equal (ein:cell-element-get cell (car kv)) (cdr kv))))
+ (ein:plist-iter element))
+ (should (equal (ein:cell-element-get cell :output 0) 3))
+ (should (equal (ein:cell-element-get cell :output 1) 4))
+ (should (equal (ein:cell-element-get cell :output 2) nil))
+ (should (equal (ein:cell-element-get cell :after-input) 3))
+ (should (equal (ein:cell-element-get cell :after-output) 5))
+ (should (equal (ein:cell-element-get cell :before-input) 1))
+ (should (equal (ein:cell-element-get cell :before-output) 2))
+ (should (equal (ein:cell-element-get cell :last-output) 4))))
+
+(ert-deftest ein:cell-element-get-codecell-no-ouput ()
+ (let* ((element (list :prompt 1
+ :input 2
+ :footer 5))
+ (cell (ein:cell-from-type "code" :element element)))
+ (mapc (lambda (kv)
+ (should (equal (ein:cell-element-get cell (car kv)) (cdr kv))))
+ (ein:plist-iter element))
+ (should (equal (ein:cell-element-get cell :after-input) 5))
+ (should (equal (ein:cell-element-get cell :last-output) 2))))
+
+(ert-deftest ein:cell-element-get-textcell ()
+ (let* ((element (list :prompt 1
+ :input 2
+ :footer 5))
+ (cell (ein:cell-from-type "text" :element element)))
+ (mapc (lambda (kv)
+ (should (equal (ein:cell-element-get cell (car kv)) (cdr kv))))
+ (ein:plist-iter element))
+ (should (equal (ein:cell-element-get cell :after-input) 5))
+ (should (equal (ein:cell-element-get cell :before-input) 1))))
diff --git a/test/test-ein-completer.el b/test/test-ein-completer.el
new file mode 100644
index 0000000..5f52338
--- /dev/null
+++ b/test/test-ein-completer.el
@@ -0,0 +1,23 @@
+(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-completer)
+
+
+(ert-deftest ein:completer-finish-completing ()
+ (let* ((matched-text 'dummy-matched-text-value) ; value can be anything
+ (matches 'dummy-matches-value)
+ (content (list :matched_text matched-text
+ :matches matches))
+ (args '(:extend t)))
+ (mocker-let
+ ((ein:completer-choose () ((:output 'completer)))
+ (completer
+ (matched-text matches &rest args)
+ ((:input (list matched-text matches args)))))
+ (ein:completer-finish-completing args content '-not-used-))))
diff --git a/test/test-ein-connect.el b/test/test-ein-connect.el
new file mode 100644
index 0000000..555e850
--- /dev/null
+++ b/test/test-ein-connect.el
@@ -0,0 +1,47 @@
+(require 'ein-connect)
+(require 'ein-testing-notebook)
+
+(defmacro eintest:with-connected-buffer (&rest body)
+ (declare (indent 0))
+ `(let* ((notebook-buffer (ein:testing-notebook-make-empty))
+ (notebook (buffer-local-value 'ein:notebook notebook-buffer)))
+ (with-temp-buffer
+ (erase-buffer)
+ (ein:connect-buffer-to-notebook notebook)
+ ,@body)))
+
+(ert-deftest ein:get-url-or-port--connect ()
+ (eintest:with-connected-buffer
+ (should (equal (ein:get-url-or-port)
+ (ein:$notebook-url-or-port notebook)))))
+
+(ert-deftest ein:get-notebook--connect ()
+ (eintest:with-connected-buffer
+ (should (eq (ein:get-notebook) notebook))))
+
+(ert-deftest ein:get-kernel--connect ()
+ (eintest:with-connected-buffer
+ (should (eq (ein:get-kernel)
+ (ein:$notebook-kernel notebook)))))
+
+(ert-deftest ein:get-cell-at-point--connect ()
+ "`ein:get-cell-at-point' is in empty context in connected buffer."
+ (eintest:with-connected-buffer
+ (should-not (ein:get-cell-at-point))))
+
+(ert-deftest ein:get-traceback-data--connect ()
+ (eintest:with-connected-buffer
+ ;; FIXME: write test with non-empty TB
+ (should-not (ein:get-traceback-data))))
+
+(ert-deftest ein:connect-mode-revert-buffer-resistance ()
+ (let ((temp-file (make-temp-file "ein")))
+ (unwind-protect
+ (with-temp-buffer
+ (setq buffer-file-name temp-file)
+ (ein:connect-mode 1)
+ (setq ein:%connect% 'very-important-value)
+ (revert-buffer t t nil)
+ (should ein:connect-mode)
+ (should (eq ein:%connect% 'very-important-value)))
+ (delete-file temp-file))))
diff --git a/test/test-ein-console.el b/test/test-ein-console.el
new file mode 100644
index 0000000..c435567
--- /dev/null
+++ b/test/test-ein-console.el
@@ -0,0 +1,25 @@
+(require 'ein-console)
+
+(ert-deftest ein:console-security-dir-string ()
+ (let ((ein:console-security-dir "/some/dir/"))
+ (should (equal (ein:console-security-dir-get "DUMMY-URL-OR-PORT")
+ ein:console-security-dir))))
+
+(ert-deftest ein:console-security-dir-list ()
+ (let ((ein:console-security-dir
+ '((8888 . "/dir/8888/")
+ ("htttp://dummy.org" . "/dir/http/")
+ (7777 . my-secret-directory)
+ (default . "/dir/default/")))
+ (my-secret-directory "/dir/secret/"))
+ (should (equal (ein:console-security-dir-get 8888) "/dir/8888/"))
+ (should (equal (ein:console-security-dir-get "htttp://dummy.org")
+ "/dir/http/"))
+ (should (equal (ein:console-security-dir-get 7777) "/dir/secret/"))
+ (should (equal (ein:console-security-dir-get 9999) "/dir/default/"))))
+
+(ert-deftest ein:console-security-dir-func ()
+ (let ((ein:console-security-dir
+ '(lambda (x) (should (equal x "DUMMY-URL-OR-PORT")) "/dir/")))
+ (should (equal (ein:console-security-dir-get "DUMMY-URL-OR-PORT")
+ "/dir/"))))
diff --git a/test/test-ein-content.el b/test/test-ein-content.el
new file mode 100644
index 0000000..ac7c4be
--- /dev/null
+++ b/test/test-ein-content.el
@@ -0,0 +1,38 @@
+;;; test-ein-content.el --- Testing content interface
+
+;; Copyright (C) 2015 John Miller
+
+;; Authors: Takafumi Arakaki
+;; John M. Miller
+
+;; This file is NOT part of GNU Emacs.
+
+;; test-ein-content.el is free software: you can redistribute it
+;; and/or modify it under the terms of the GNU General Public License
+;; as published by the Free Software Foundation, either version 3 of
+;; the License, or (at your option) any later version.
+
+;; test-ein-content.el is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with ein-testing-notebook.el.
+;; If not, see .
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+
+(require 'ein-contents-api)
+
+(defvar *list-content-result* nil)
+
+(defun ein-test-list-contents-1 ()
+ (ein:content-list-contents "" )
+ )
diff --git a/test/test-ein-core.el b/test/test-ein-core.el
new file mode 100644
index 0000000..840d45e
--- /dev/null
+++ b/test/test-ein-core.el
@@ -0,0 +1,92 @@
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein-core)
+
+
+
+;;; `ein:version'
+
+(ert-deftest ein:version ()
+ "Check if `ein:version' can be parsed by `version-to-list'."
+ (version-to-list ein:version))
+
+(ert-deftest ein:version-func-prefix-is-the-variable ()
+ (should (string-prefix-p ein:version (ein:version)))
+ (let ((default-directory "/tmp/"))
+ (should (string-prefix-p ein:version (ein:version)))))
+
+(ert-deftest ein:version-func-outside-of-git-repo ()
+ (flet ((ein:git-root-p (dir) nil))
+ (should (equal (ein:version) ein:version)))
+ (flet ((ein:git-revision-dirty () nil))
+ (should (equal (ein:version) ein:version))))
+
+
+
+;; Generic getter
+
+(defmacro eintest:generic-getter-should-return-nil (func)
+ "In an \"empty\" context, generic getter should return nil."
+ `(ert-deftest ,(intern (format "%s--nil name" func)) ()
+ (with-temp-buffer
+ (should (not (,func))))))
+
+(eintest:generic-getter-should-return-nil ein:get-url-or-port)
+(eintest:generic-getter-should-return-nil ein:get-notebook)
+(eintest:generic-getter-should-return-nil ein:get-kernel)
+(eintest:generic-getter-should-return-nil ein:get-cell-at-point)
+(eintest:generic-getter-should-return-nil ein:get-traceback-data)
+
+
+
+;;; File name translation
+
+;; Requiring `tramp' during (inside of) tests yields error from
+;; MuMaMo. Although I don't understand the reason, requiring it
+;; before running tests workarounds this problem.
+(require 'tramp)
+
+(ert-deftest ein:filename-translations-from-to-tramp ()
+ (loop with ein:filename-translations =
+ `((8888 . ,(ein:tramp-create-filename-translator "HOST" "USER")))
+ with filename = "/file/name"
+ for port in '(7777 8888) ; check for the one w/o translation
+ for emacs-filename = (ein:filename-from-python port filename)
+ do (message "emacs-filename = %s" emacs-filename)
+ do (should
+ (equal (ein:filename-to-python port emacs-filename)
+ filename))))
+
+(ert-deftest ein:filename-translations-to-from-tramp ()
+ (loop with ein:filename-translations =
+ `((8888 . ,(ein:tramp-create-filename-translator "HOST" "USER")))
+ with filename = "/USER@HOST:/filename"
+ for port in '(8888)
+ do (should
+ (equal (ein:filename-from-python
+ port (ein:filename-to-python port filename))
+ filename))))
+
+(ert-deftest ein:filename-to-python-tramp ()
+ (let* ((port 8888)
+ (ein:filename-translations
+ `((,port . ,(ein:tramp-create-filename-translator "DUMMY")))))
+ (loop with python-filename = "/file/name"
+ for emacs-filename in '("/scpc:HOST:/file/name"
+ "/USER@HOST:/file/name")
+ do (should
+ (equal (ein:filename-to-python port emacs-filename)
+ python-filename)))
+ ;; Error: Not a Tramp file name: /file/name
+ (should-error (ein:filename-to-python port "/file/name"))))
+
+(ert-deftest ein:filename-from-python-tramp ()
+ (loop with ein:filename-translations =
+ `((8888 . ,(ein:tramp-create-filename-translator "HOST" "USER")))
+ with python-filename = "/file/name"
+ for emacs-filename in '("/USER@HOST:/file/name" "/file/name")
+ for port in '(8888 7777)
+ do (should
+ (equal (ein:filename-from-python port python-filename)
+ emacs-filename))))
diff --git a/test/test-ein-iexec.el b/test/test-ein-iexec.el
new file mode 100644
index 0000000..898aa5c
--- /dev/null
+++ b/test/test-ein-iexec.el
@@ -0,0 +1,43 @@
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein-iexec)
+(require 'ein-testing-notebook)
+
+
+;;; `ein:iexec-should-execute-p'
+
+(defun* eintest:iexec-should-execute-p (cell &key (this-command t) beg end)
+ "Simple wrapper for `ein:iexec-should-execute-p' which
+returns `t' by default, if the CELL is code cell."
+ (unless beg (setq beg (ein:cell-input-pos-min cell)))
+ (unless end (setq end (ein:cell-input-pos-max cell)))
+ (ein:iexec-should-execute-p cell beg end))
+
+;; cell types
+
+(ert-deftest ein:iexec-should-execute-p-codecell ()
+ (ein:testing-with-one-cell 'code
+ (should (eintest:iexec-should-execute-p cell))))
+
+(ert-deftest ein:iexec-should-execute-p-markdowncell ()
+ (ein:testing-with-one-cell 'markdown
+ (should-not (eintest:iexec-should-execute-p cell))))
+
+(ert-deftest ein:iexec-should-execute-p-dead-cell ()
+ (ein:testing-with-one-cell 'code
+ (should-not (eintest:iexec-should-execute-p (ein:cell-copy cell)))))
+
+;; other
+
+(ert-deftest ein:iexec-should-execute-p-non-interactive ()
+ (ein:testing-with-one-cell 'code
+ (should-not (eintest:iexec-should-execute-p cell :this-command nil))))
+
+(ert-deftest ein:iexec-should-execute-p-beg-too-small ()
+ (ein:testing-with-one-cell 'code
+ (should-not (eintest:iexec-should-execute-p cell :beg (point-min)))))
+
+(ert-deftest ein:iexec-should-execute-p-end-too-big ()
+ (ein:testing-with-one-cell 'code
+ (should-not (eintest:iexec-should-execute-p cell :end (point-max)))))
diff --git a/test/test-ein-kernel.el b/test/test-ein-kernel.el
new file mode 100644
index 0000000..be6a231
--- /dev/null
+++ b/test/test-ein-kernel.el
@@ -0,0 +1,80 @@
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein-kernel)
+(require 'ein-testing-kernel)
+
+
+(defun eintest:kernel-new (port)
+ (ein:kernel-new port "/api/kernels"
+ (get-buffer-create "*eintest: dummy for kernel test*")))
+
+(ert-deftest ein:kernel-start-check-url ()
+ (let* ((kernel (eintest:kernel-new 8888))
+ (notebook-id "NOTEBOOK-ID")
+ (desired-url "http://127.0.0.1:8888/api/sessions")
+ (dummy-response (make-request-response))
+ got-url)
+ (flet ((request (url &rest ignore) (setq got-url url) dummy-response)
+ (set-process-query-on-exit-flag (process flag)))
+ (ein:kernel-start kernel notebook-id)
+ (should (equal got-url desired-url)))))
+
+(ert-deftest ein:kernel-restart-check-url ()
+ (let* ((kernel (eintest:kernel-new 8888))
+ (kernel-id "KERNEL-ID")
+ (session-id "SESSION-ID")
+ (desired-url "http://127.0.0.1:8888/api/kernels/KERNEL-ID/restart")
+ (dummy-response (make-request-response))
+ got-url)
+ (flet ((request (url &rest ignore) (setq got-url url) dummy-response)
+ (set-process-query-on-exit-flag (process flag))
+ (ein:kernel-stop-channels (&rest ignore))
+ (ein:websocket (&rest ignore) (make-ein:$websocket))
+ (ein:events-trigger (&rest ignore)))
+ (ein:kernel--kernel-started
+ kernel :data (list :ws_url "ws://127.0.0.1:8888" :id kernel-id))
+ (ein:kernel-restart kernel)
+ (should (equal got-url desired-url)))))
+
+
+(ert-deftest ein:kernel-interrupt-check-url ()
+ (let* ((kernel (eintest:kernel-new 8888))
+ (kernel-id "KERNEL-ID")
+ (desired-url "http://127.0.0.1:8888/api/kernels/KERNEL-ID/interrupt")
+ (dummy-response (make-request-response))
+ got-url)
+ (flet ((request (url &rest ignore) (setq got-url url) dummy-response)
+ (set-process-query-on-exit-flag (process flag))
+ (ein:kernel-stop-channels (&rest ignore))
+ (ein:websocket (&rest ignore) (make-ein:$websocket)))
+ (ein:kernel--kernel-started
+ kernel :data (list :ws_url "ws://127.0.0.1:8888" :id kernel-id))
+ (ein:kernel-interrupt kernel)
+ (should (equal got-url desired-url)))))
+
+(ert-deftest ein:kernel-kill-check-url ()
+ (let* ((kernel (eintest:kernel-new 8888))
+ (kernel-id "KERNEL-ID")
+ (desired-url "http://127.0.0.1:8888/api/kernels/KERNEL-ID")
+ (dummy-response (make-request-response))
+ got-url)
+ (flet ((request (url &rest ignore) (setq got-url url) dummy-response)
+ (set-process-query-on-exit-flag (process flag))
+ (ein:kernel-stop-channels (&rest ignore))
+ (ein:websocket (&rest ignore) (make-ein:$websocket)))
+ (ein:kernel--kernel-started
+ kernel :data (list :ws_url "ws://127.0.0.1:8888" :id kernel-id))
+ (ein:kernel-kill kernel)
+ (should (equal got-url desired-url)))))
+
+
+;;; Test `ein:kernel-construct-help-string'
+
+(ert-deftest ein:kernel-construct-help-string-when-found ()
+ (ein:testing-kernel-construct-help-string-loop))
+
+(ert-deftest ein:kernel-construct-help-string-when-not-found ()
+ (should (equal (ein:kernel-construct-help-string nil) nil)))
+;; Included in `ein:kernel-construct-help-string-when-found', but test
+;; it explicitly to be sure.
diff --git a/test/test-ein-kill-ring.el b/test/test-ein-kill-ring.el
new file mode 100644
index 0000000..977df60
--- /dev/null
+++ b/test/test-ein-kill-ring.el
@@ -0,0 +1,41 @@
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein-kill-ring)
+
+(ert-deftest ein:kill-ring-simple ()
+ (let (ein:kill-ring
+ ein:kill-ring-yank-pointer)
+ (ein:kill-new 1)
+ (should (equal (ein:current-kill 0) 1))))
+
+(defun eintest:kill-ring-simple-repeat-setup ()
+ (loop for i from 0 below 5
+ do (ein:kill-new i)
+ do (should (equal (ein:current-kill 0) i))))
+
+(ert-deftest ein:kill-ring-simple-repeat ()
+ (let (ein:kill-ring
+ ein:kill-ring-yank-pointer)
+ (eintest:kill-ring-simple-repeat-setup)
+ (should (equal ein:kill-ring ein:kill-ring-yank-pointer))
+ (should (equal ein:kill-ring '(4 3 2 1 0)))))
+
+(ert-deftest ein:kill-ring-repeat-n-1 ()
+ (let (ein:kill-ring
+ ein:kill-ring-yank-pointer)
+ (eintest:kill-ring-simple-repeat-setup)
+ (loop for i in '(3 2 1 0 4 3 2)
+ do (should (equal (ein:current-kill 1) i)))
+ (should-not (equal ein:kill-ring ein:kill-ring-yank-pointer))
+ (should (equal ein:kill-ring '(4 3 2 1 0)))
+ (should (equal ein:kill-ring-yank-pointer '(2 1 0)))))
+
+(ert-deftest ein:kill-ring-exceeds-max ()
+ (let (ein:kill-ring
+ ein:kill-ring-yank-pointer
+ (ein:kill-ring-max 3))
+ (eintest:kill-ring-simple-repeat-setup)
+ (should (equal ein:kill-ring ein:kill-ring-yank-pointer))
+ (should (equal (length ein:kill-ring) ein:kill-ring-max))
+ (should (equal ein:kill-ring '(4 3 2)))))
diff --git a/test/test-ein-node.el b/test/test-ein-node.el
new file mode 100644
index 0000000..b4cca3b
--- /dev/null
+++ b/test/test-ein-node.el
@@ -0,0 +1,82 @@
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein-node)
+
+(defun ein:testing-node-dummy-ewco-node (data)
+ `[nil nil ,data])
+
+(defun ein:testing-node-ewoc-data (ewoc-node)
+ (ein:$node-data (ewoc-data ewoc-node)))
+
+(ert-deftest ein:testing-node-dummy-ewco-node ()
+ (let* ((obj "some-object")
+ (ewoc-node (ein:testing-node-dummy-ewco-node obj)))
+ (should (eq (ewoc-data ewoc-node) obj))))
+
+(ert-deftest ein:node-filter-is ()
+ (let ((en-list (mapcar
+ #'ein:testing-node-dummy-ewco-node
+ (list (ein:node-new nil "s" '(spam sag))
+ (ein:node-new nil "p" '(spam))
+ (ein:node-new nil "e" '(egg))
+ (ein:node-new nil "a" '(spam sag))
+ (ein:node-new nil "g" '(egg sag))
+ (ein:node-new nil "m" '(spam))
+ (ein:node-new nil "g" '(egg))))))
+ (should (equal (mapcar #'ein:testing-node-ewoc-data
+ (ein:node-filter en-list :is 'spam))
+ '("s" "p" "a" "m")))
+ (should (equal (mapcar #'ein:testing-node-ewoc-data
+ (ein:node-filter en-list :is 'egg))
+ '("e" "g" "g")))
+ (should (equal (mapcar #'ein:testing-node-ewoc-data
+ (ein:node-filter en-list :is 'sag))
+ '("s" "a" "g")))))
+
+(ert-deftest ein:node-filter-not ()
+ (let ((en-list (mapcar
+ #'ein:testing-node-dummy-ewco-node
+ (list (ein:node-new nil "s" '(spam sag))
+ (ein:node-new nil "p" '(spam))
+ (ein:node-new nil "e" '(egg))
+ (ein:node-new nil "a" '(spam sag))
+ (ein:node-new nil "g" '(egg sag))
+ (ein:node-new nil "m" '(spam))
+ (ein:node-new nil "g" '(egg))))))
+ (should (equal (mapcar #'ein:testing-node-ewoc-data
+ (ein:node-filter en-list :not 'spam))
+ '("e" "g" "g")))
+ (should (equal (mapcar #'ein:testing-node-ewoc-data
+ (ein:node-filter en-list :not 'egg))
+ '("s" "p" "a" "m")))
+ (should (equal (mapcar #'ein:testing-node-ewoc-data
+ (ein:node-filter en-list :not 'sag))
+ '("p" "e" "m" "g")))))
+
+(ert-deftest ein:node-filter-is-and-not ()
+ (let ((en-list (mapcar
+ #'ein:testing-node-dummy-ewco-node
+ (list (ein:node-new nil "s" '(spam sag))
+ (ein:node-new nil "p" '(spam))
+ (ein:node-new nil "e" '(egg))
+ (ein:node-new nil "a" '(spam sag))
+ (ein:node-new nil "g" '(egg sag))
+ (ein:node-new nil "m" '(spam))
+ (ein:node-new nil "g" '(egg))))))
+ (should (equal (mapcar #'ein:testing-node-ewoc-data
+ (ein:node-filter en-list :not 'spam :is 'sag))
+ '("g")))
+ (should (equal (mapcar #'ein:testing-node-ewoc-data
+ (ein:node-filter en-list :is 'sag :not 'spam))
+ '("g")))
+ (should (equal (mapcar #'ein:testing-node-ewoc-data
+ (ein:node-filter en-list :is 'spam :is 'sag))
+ '("s" "a")))
+ (should (equal (mapcar #'ein:testing-node-ewoc-data
+ (ein:node-filter en-list :is 'sag :not 'spam
+ :not 'not-existing))
+ '("g")))
+ (should (equal (mapcar #'ein:testing-node-ewoc-data
+ (ein:node-filter en-list :is 'sag :is 'spam))
+ '("s" "a")))))
diff --git a/test/test-ein-notebook.el b/test/test-ein-notebook.el
new file mode 100644
index 0000000..118ecfe
--- /dev/null
+++ b/test/test-ein-notebook.el
@@ -0,0 +1,1347 @@
+(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:notebook-enable-mode (buffer)
+ (with-current-buffer buffer (ein:notebook-plain-mode) buffer))
+
+(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 :data 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%) "Dummy Name.ipynb"))
+ (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
+ 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 "HTML text")
+ (ein:testing-headingcell-data "Heading 1" 1)
+ (ein:testing-headingcell-data "Heading 2" 2)
+ (ein:testing-headingcell-data "Heading 3" 3)
+ (ein:testing-headingcell-data "Heading 4" 4)
+ (ein:testing-headingcell-data "Heading 5" 5)
+ (ein:testing-headingcell-data "Heading 6" 6)))
+ (should (ein:$notebook-p ein:%notebook%))
+ (should (equal (ein:$notebook-notebook-name ein:%notebook%) "Dummy Name.ipynb"))
+ (should (equal (ein:worksheet-ncells ein:%worksheet%) 10))
+ (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)) "HTML text"))
+ (loop for i from 4 to 9
+ for level from 1
+ for cell = (nth i cells)
+ do (should (ein:headingcell-p cell))
+ do (should (equal (ein:cell-get-text cell)
+ (format "Heading %s" level)))
+ do (should (= (oref cell :level) level))))))
+
+
+;;; Destructor
+
+(defvar ein:testing-notebook-del-args-log 'nolog)
+
+(defadvice ein:notebook-del (before ein:testing-notebook-del activate)
+ "Log argument passed to"
+ (when (listp ein:testing-notebook-del-args-log)
+ (push (ad-get-args 0) ein:testing-notebook-del-args-log)))
+
+(defun ein:testing-assert-notebook-del-not-called ()
+ (should-not ein:testing-notebook-del-args-log))
+
+(defun ein:testing-assert-notebook-del-called-once-with (notebook)
+ (should (= (length ein:testing-notebook-del-args-log) 1))
+ (mapc (lambda (arg) (should (= (length arg) 1)))
+ ein:testing-notebook-del-args-log)
+ (should (eq (caar ein:testing-notebook-del-args-log) notebook)))
+
+(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))
+ (let ((notebook (buffer-local-value 'ein:%notebook%
+ (ein:testing-notebook-make-empty)))
+ ein:testing-notebook-del-args-log)
+ (symbol-macrolet ((ss-list (ein:$notebook-scratchsheets notebook)))
+ ;; Add scratchsheets. They can be just empty instance for this test.
+ (dotimes (_ num-open)
+ (setq ss-list
+ (append ss-list (list (make-instance 'ein:scratchsheet)))))
+ ;; Close worksheet
+ (let ((ws (car (ein:$notebook-worksheets notebook)))
+ (ein:notebook-kill-buffer-ask nil))
+ (ein:notebook-close-worksheet notebook ws)
+ (kill-buffer (ein:worksheet-buffer ws)))
+ ;; Make sure adding scratchsheet work.
+ (should (= (length ss-list) num-open))
+ (mapc (lambda (ws) (should (ein:scratchsheet-p ws))) ss-list)
+ ;; Close scratchsheets
+ (dotimes (_ num-close)
+ (ein:notebook-close-worksheet notebook (car ss-list)))
+ ;; Actual tests:
+ (should (= (length ss-list) (- num-open num-close)))
+ (if (= num-open num-close)
+ (ein:testing-assert-notebook-del-called-once-with notebook)
+ (ein:testing-assert-notebook-del-not-called)))))
+
+(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)
+ (loop repeat 3
+ do (call-interactively #'ein:worksheet-insert-cell-above))
+ (should (equal (ein:worksheet-ncells ein:%worksheet%) 3))
+ (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)
+ (loop repeat 3
+ do (call-interactively #'ein:worksheet-insert-cell-above))
+ (should (equal (ein:worksheet-ncells ein:%worksheet%) 3))
+ (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)
+ (loop repeat 3
+ do (call-interactively #'ein:worksheet-insert-cell-above))
+ (should (equal (ein:worksheet-ncells ein:%worksheet%) 3))
+ (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)
+ (loop repeat 3
+ do (call-interactively #'ein:worksheet-insert-cell-above))
+ (should (equal (ein:worksheet-ncells ein:%worksheet%) 3))
+ (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))
+ (loop repeat 3
+ do (call-interactively #'ein:worksheet-yank-cell))
+ (should (equal (ein:worksheet-ncells ein:%worksheet%) 3))
+ (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))
+ (flet ((y-or-n-p (&rest ignore) t)
+ (ein:notebook-del (&rest ignore)))
+ ;; FIXME: are there anyway to skip confirmation?
+ (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"))
+ ;; toggle to heading
+ (call-interactively #'ein:worksheet-toggle-cell-type)
+ (should (ein:headingcell-p (ein:worksheet-get-current-cell)))
+ (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 &optional level)
+ (let ((cell-p (intern (format "ein:%scell-p" type)))
+ (cell (ein:worksheet-get-current-cell)))
+ (ein:worksheet-change-cell-type ein:%worksheet% cell
+ type level 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
+ (loop for type in '("code" "markdown" "raw")
+ do (funcall check type))
+ ;; change level: 1 to 6
+ (loop for level from 1 to 6
+ do (funcall check "heading" level))
+ ;; back to code
+ (funcall check "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)
+ (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))
+ (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)
+ (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))
+ (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)
+ (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"))
+ (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)
+ (loop for i from 0 below 3
+ do (call-interactively #'ein:worksheet-insert-cell-above)
+ do (insert (format "Cell %s" i)))
+ (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)
+ (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 (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 ((notebook (buffer-local-value 'ein:%notebook%
+ (ein:testing-notebook-make-empty)))
+ ein:testing-notebook-del-args-log)
+ (dotimes (_ num-ss)
+ (ein:notebook-scratchsheet-render-new notebook))
+ (let ((buffers (ein:notebook-buffer-list notebook)))
+ (should (= (length buffers) (+ num-ws num-ss)))
+ (ein:notebook-close notebook)
+ (mapc (lambda (b) (should-not (buffer-live-p b))) buffers)
+ (ein:testing-assert-notebook-del-called-once-with notebook))))
+
+(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-one-worksheet-one-cell (notebook text)
+ (let* ((data (ein:notebook-to-json notebook))
+ (worksheets (assoc-default 'worksheets data #'eq))
+ (cells (assoc-default 'cells (elt worksheets 0) #'eq))
+ (cell-0 (elt cells 0))
+ (input (assoc-default 'input cell-0 #'eq)))
+ (should (= (length worksheets) 1))
+ (should (= (length cells) 1))
+ (should (equal input text))))
+
+(defun ein:testing-notebook-data-assert-one-worksheet-no-cell (notebook)
+ (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))
+ (should (= (length cells) 0))))
+
+(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")
+ (ein:testing-notebook-data-assert-one-worksheet-one-cell notebook
+ "some text")
+ (should (ein:notebook-modified-p notebook))
+ ;; Open scratch sheet.
+ (ein:notebook-scratchsheet-open notebook)
+ ;; Pretend that notebook is saved
+ (ein:notebook-save-notebook-success notebook)
+ (should-not (ein:notebook-modified-p notebook))
+ ;; Kill a worksheet buffer
+ (kill-buffer buffer)
+ (should (ein:notebook-live-p notebook))
+ ;; to-json should still work
+ (ein:testing-notebook-data-assert-one-worksheet-one-cell notebook
+ "some text"))))
+
+(ert-deftest ein:notebook-to-json-after-discarding-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")
+ (ein:testing-notebook-data-assert-one-worksheet-one-cell notebook
+ "some text")
+ (should (ein:notebook-modified-p notebook))
+ ;; Open scratch sheet.
+ (ein:notebook-scratchsheet-open notebook)
+ ;; Discard a worksheet buffer
+ (should (ein:notebook-modified-p notebook))
+ (let (ein:notebook-kill-buffer-ask)
+ (kill-buffer buffer))
+ (should (ein:notebook-live-p notebook))
+ ;; to-json should still work
+ (ein:testing-notebook-data-assert-one-worksheet-no-cell notebook))))
+
+(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%)
+ (kernel (ein:$notebook-kernel ein:%notebook%))
+ (ein:notebook-kill-buffer-ask nil))
+ (mocker-let
+ ((ein:kernel-live-p
+ (kernel)
+ ((:input (list kernel) :output t)))
+ (ein:kernel-kill
+ (kernel &optional callback cbargs)
+ ((:input (list kernel #'ein:notebook-close (list notebook))))))
+ (call-interactively #'ein:notebook-kill-kernel-then-close-command))
+ (should (buffer-live-p buffer))
+ ;; Pretend that `ein:notebook-close' is called.
+ (ein:notebook-close notebook)
+ (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%))
+ (ein:notebook-kill-buffer-ask nil))
+ (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 (eq ein:worksheet-enable-undo 'full)
+ (undo)
+ (should-error (undo)))
+ (when (eq ein:worksheet-enable-undo 'full)
+ ;; FIXME: Known bug. (this must succeed.)
+ (should-error (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 (eq ein:worksheet-enable-undo 'full)
+ (undo)
+ (should-error (undo)))
+ (when (eq ein:worksheet-enable-undo 'full)
+ (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 (eq ein:worksheet-enable-undo 'full))
+ (should-error (undo))
+ (undo)
+ (should (equal (buffer-string) "
+In [ ]:
+second line
+
+In [ ]:
+
+
+In [ ]:
+
+
+")))
+ (when (eq ein:worksheet-enable-undo 'yes)
+ ;; FIXME: `undo' should work...
+ (should-error (undo-more 1)))
+ (when (eq ein:worksheet-enable-undo 'full)
+ (undo)
+ ;; FIXME: Known bug... What should the result be?
+ (should-error (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 (eq ein:worksheet-enable-undo 'full)
+ (undo)
+ (should-error (undo)))
+ (when (eq ein:worksheet-enable-undo 'full)
+ (should (equal (ein:cell-get-text cell) ""))
+ ;; FIXME: Known bug. (it must succeed.)
+ (should-error (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 (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 (eq ein:worksheet-enable-undo 'full)
+ (undo)
+ (should-error (undo)))
+ (when (eq ein:worksheet-enable-undo 'full)
+ (should (equal (ein:cell-get-text cell) text))
+ ;; FIXME: Known bug. (these two must succeed.)
+ (should-error (should (equal (ein:cell-get-text next-cell) "")))
+ (should-error (funcall check-output))))))
+
+(defmacro eintest:notebook-undo-make-tests (name)
+ "Define three tests ein:NANE/no, ein:NANE/yes and ein:NANE/full
+from a function named eintest:NAME where `no'/`yes'/`full' 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)))
+ (test/full (intern (format "ein:%s/full" name))))
+ `(progn
+ (ert-deftest ,test/no ()
+ (let ((ein:worksheet-enable-undo 'no))
+ (,func)))
+ (ert-deftest ,test/yes ()
+ (let ((ein:worksheet-enable-undo 'yes))
+ (,func)))
+ (ert-deftest ,test/full ()
+ (let ((ein:worksheet-enable-undo 'full))
+ (,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)
+
+(ert-deftest ein:notebook-undo-via-events ()
+ (with-current-buffer (ein:testing-notebook-make-empty)
+ (call-interactively #'ein:worksheet-insert-cell-below)
+ (loop with events = (ein:$notebook-events ein:%notebook%)
+ for ein:worksheet-enable-undo in '(no yes full) do
+ (let ((buffer-undo-list '(dummy))
+ (cell (ein:worksheet-get-current-cell)))
+ (with-temp-buffer
+ (should-not (equal buffer-undo-list '(dummy)))
+ (ein:events-trigger events 'maybe_reset_undo.Worksheet cell))
+ (if (eq ein:worksheet-enable-undo 'yes)
+ (should (equal buffer-undo-list nil))
+ (should (equal buffer-undo-list '(dummy))))))))
+
+
+;; 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) "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)))
+ (should (ein:notebook-ask-before-kill-emacs))
+ (with-current-buffer
+ (eintest:notebook-enable-mode
+ (ein:testing-notebook-make-empty "Modified Notebook.ipynb"))
+ (call-interactively #'ein:worksheet-insert-cell-below)
+ (should (ein:notebook-modified-p)))
+ (with-current-buffer
+ (eintest:notebook-enable-mode
+ (ein:testing-notebook-make-empty "Saved Notebook.ipynb"))
+ (ein:notebook-save-notebook-success ein:%notebook%)
+ (should-not (ein:notebook-modified-p)))
+ (flet ((y-or-n-p (&rest ignore) t)
+ (ein:notebook-del (&rest ignore)))
+ (kill-buffer
+ (eintest:notebook-enable-mode
+ (ein:testing-notebook-make-empty "Killed Notebook.ipynb"))))
+ (should (gethash '("DUMMY-URL" "Modified Notebook.ipynb") ein:notebook--opened-map))
+ (should (gethash '("DUMMY-URL" "Saved Notebook.ipynb") ein:notebook--opened-map))
+ (should (gethash '("DUMMY-URL" "Killed Notebook.ipynb") ein:notebook--opened-map))
+ (should (= (hash-table-count ein:notebook--opened-map) 3))
+ (mocker-let ((y-or-n-p
+ (prompt)
+ ((:input '("You have 1 unsaved notebook(s). Discard changes?")
+ :output t))))
+ (should (ein:notebook-ask-before-kill-emacs)))))
+
+
+;;; Buffer and kill hooks
+
+(ert-deftest ein:notebook-ask-before-kill-buffer/no-ein-buffer ()
+ (with-temp-buffer
+ (mocker-let ((y-or-n-p (prompt) ()))
+ (should (ein:notebook-ask-before-kill-buffer)))))
+
+(ert-deftest ein:notebook-ask-before-kill-buffer/new-notebook ()
+ (with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
+ (mocker-let ((y-or-n-p (prompt) ()))
+ (should (ein:notebook-ask-before-kill-buffer)))))
+
+(ert-deftest ein:notebook-ask-before-kill-buffer/modified-notebook ()
+ (with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
+ (call-interactively #'ein:worksheet-insert-cell-below)
+ (mocker-let ((y-or-n-p
+ (prompt)
+ ((:input '("You have unsaved changes. Discard changes?")
+ :output t))))
+ (should (ein:notebook-ask-before-kill-buffer)))))
+
+(ert-deftest ein:notebook-ask-before-kill-buffer/modified-scratchsheet ()
+ (with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
+ (with-current-buffer (ein:worksheet-buffer
+ (ein:notebook-scratchsheet-open ein:%notebook%))
+ (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%))
+ (mocker-let ((y-or-n-p (prompt) ()))
+ (should (ein:notebook-ask-before-kill-buffer))))
+ (should-not (ein:worksheet-modified-p ein:%worksheet%))
+ (mocker-let ((y-or-n-p (prompt) ()))
+ (should (ein:notebook-ask-before-kill-buffer)))))
+
+
+;; 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))
diff --git a/test/test-ein-notebooklist.el b/test/test-ein-notebooklist.el
new file mode 100644
index 0000000..e6033a1
--- /dev/null
+++ b/test/test-ein-notebooklist.el
@@ -0,0 +1,24 @@
+(require 'ein-notebooklist)
+
+(defun eintest:notebooklist-make-empty (&optional url-or-port)
+ "Make empty notebook list buffer."
+ (ein:notebooklist-url-retrieve-callback (or url-or-port "DUMMY-URL")
+ (ein:query-ipython-version)
+ ""))
+
+(defmacro eintest:notebooklist-is-empty-context-of (func)
+ `(ert-deftest ,(intern (format "%s--notebooklist" func)) ()
+ (with-current-buffer (eintest:notebooklist-make-empty)
+ (should-not (,func)))))
+
+
+;; Generic getter
+
+(ert-deftest ein:get-url-or-port--notebooklist ()
+ (with-current-buffer (eintest:notebooklist-make-empty "DUMMY-URL")
+ (should (equal (ein:get-url-or-port) "DUMMY-URL"))))
+
+(eintest:notebooklist-is-empty-context-of ein:get-notebook)
+(eintest:notebooklist-is-empty-context-of ein:get-kernel)
+(eintest:notebooklist-is-empty-context-of ein:get-cell-at-point)
+(eintest:notebooklist-is-empty-context-of ein:get-traceback-data)
diff --git a/test/test-ein-notification.el b/test/test-ein-notification.el
new file mode 100644
index 0000000..9ee3ad3
--- /dev/null
+++ b/test/test-ein-notification.el
@@ -0,0 +1,79 @@
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein-notification)
+
+(defun ein:testing-notification-tab-mock ()
+ (make-instance 'ein:notification-tab
+ :get-list (lambda () '(a b c))
+ :get-current (lambda () 'a)
+ :get-name #'ignore))
+
+(ert-deftest ein:header-line-normal ()
+ (let* ((ein:%notification% (ein:notification "NotificationTest"))
+ (kernel (oref ein:%notification% :kernel)))
+ (oset ein:%notification% :tab (ein:testing-notification-tab-mock))
+ (should (equal (ein:header-line)
+ "IP[y]: /1\\ /2\\ /3\\ [+]"))))
+
+(ert-deftest ein:header-line-kernel-status-busy ()
+ (let* ((ein:%notification% (ein:notification "NotificationTest"))
+ (kernel (oref ein:%notification% :kernel)))
+ (oset ein:%notification% :tab (ein:testing-notification-tab-mock))
+ (ein:notification-status-set kernel
+ 'status_busy.Kernel)
+ (should (equal (ein:header-line)
+ "IP[y]: Kernel is busy... | /1\\ /2\\ /3\\ [+]"))))
+
+(ert-deftest ein:header-line-notebook-status-busy ()
+ (let* ((ein:%notification% (ein:notification "NotificationTest"))
+ (notebook (oref ein:%notification% :notebook)))
+ (oset ein:%notification% :tab (ein:testing-notification-tab-mock))
+ (ein:notification-status-set notebook
+ 'notebook_saved.Notebook)
+ (should (equal (ein:header-line)
+ "IP[y]: Notebook is saved | /1\\ /2\\ /3\\ [+]"))))
+
+(ert-deftest ein:header-line-notebook-complex ()
+ (let* ((ein:%notification% (ein:notification "NotificationTest"))
+ (kernel (oref ein:%notification% :kernel))
+ (notebook (oref ein:%notification% :notebook)))
+ (oset ein:%notification% :tab (ein:testing-notification-tab-mock))
+ (ein:notification-status-set kernel
+ 'status_dead.Kernel)
+ (ein:notification-status-set notebook
+ 'notebook_saving.Notebook)
+ (should (equal
+ (ein:header-line)
+ (concat "IP[y]: Saving Notebook... | "
+ "Kernel is dead. Need restart. | "
+ "/1\\ /2\\ /3\\ [+]")))))
+
+(ert-deftest ein:notification-and-events ()
+ (let* ((notification (ein:notification "NotificationTest"))
+ (kernel (oref notification :kernel))
+ (notebook (oref notification :notebook))
+ (events (ein:events-new))
+ (event-symbols
+ '(notebook_saved.Notebook
+ notebook_saving.Notebook
+ notebook_save_failed.Notebook
+ execution_count.Kernel
+ status_restarting.Kernel
+ status_idle.Kernel
+ status_busy.Kernel
+ status_dead.Kernel
+ ))
+ (callbacks (oref events :callbacks)))
+ (ein:notification-bind-events notification events)
+ (mapc (lambda (s) (should (gethash s callbacks))) event-symbols)
+ (should (= (hash-table-count callbacks) (length event-symbols)))
+ (should (equal (oref kernel :status) nil))
+ (loop for et in (mapcar #'car (oref kernel :s2m))
+ do (ein:events-trigger events et)
+ do (should (equal (oref kernel :status) et))
+ do (should (equal (oref notebook :status) nil)))
+ (loop for et in (mapcar #'car (oref notebook :s2m))
+ do (ein:events-trigger events et)
+ do (should (equal (oref kernel :status) 'status_dead.Kernel))
+ do (should (equal (oref notebook :status) et)))))
diff --git a/test/test-ein-output-area.el b/test/test-ein-output-area.el
new file mode 100644
index 0000000..688a81f
--- /dev/null
+++ b/test/test-ein-output-area.el
@@ -0,0 +1,44 @@
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein-output-area)
+
+(defun ein:testing-insert-html--fix-urls-do-test (source desired)
+ (setq source (ein:xml-parse-html-string source))
+ (setq desired (ein:xml-parse-html-string desired))
+ (ein:insert-html--fix-urls source 8888)
+ (should (equal source desired)))
+
+(defmacro ein:testing-insert-html--fix-urls-deftests (args-list)
+ `(progn
+ ,@(loop for i from 0
+ for args in args-list
+ for test = (intern (format "ein:insert-html--fix-urls/%s" i))
+ collect
+ `(ert-deftest ,test ()
+ (ein:testing-insert-html--fix-urls-do-test ,@args)))))
+
+(when (require 'shr nil t)
+ (ein:testing-insert-html--fix-urls-deftests
+ (;; Simple replaces
+ ("text"
+ "text")
+ ("text"
+ "text")
+ ("
"
+ "
")
+ ("
"
+ "
")
+ ;; Do not modify dom in these cases:
+ ("text"
+ "text")
+ ("text"
+ "text")
+ ("
"
+ "
")
+ ;; Bit more complicated cases:
+ ("link normal
"
+ "link normal
")
+ ("![]()
normal
"
+ "![]()
normal")
+ )))
diff --git a/test/test-ein-pytools.el b/test/test-ein-pytools.el
new file mode 100644
index 0000000..e38a30c
--- /dev/null
+++ b/test/test-ein-pytools.el
@@ -0,0 +1,27 @@
+(require 'ert)
+
+(when load-file-name
+ (add-to-list 'load-path
+ (concat (file-name-directory load-file-name) "mocker")))
+(require 'mocker)
+
+(require 'ein-pytools)
+(require 'ein-testing-kernel)
+
+
+(ert-deftest ein:pytools-finish-tooltip ()
+ (ein:testing-kernel-construct-help-string-loop
+ (lambda (content result)
+ (if result
+ (mocker-let
+ ((featurep
+ (feature)
+ ((:input '(pos-tip) :output t)))
+ (pos-tip-show
+ (string &optional tip-color pos window timeout)
+ ((:input (list result 'ein:pos-tip-face nil nil 0)))))
+ (let ((window-system t))
+ (ein:pytools-finish-tooltip '-not-used- content '-not-used-)))
+ (mocker-let
+ ((featurep (feature) ()))
+ (ein:pytools-finish-tooltip '-not-used- content '-not-used-))))))
diff --git a/test/test-ein-shared-output.el b/test/test-ein-shared-output.el
new file mode 100644
index 0000000..9a5d791
--- /dev/null
+++ b/test/test-ein-shared-output.el
@@ -0,0 +1,28 @@
+(require 'ein-shared-output)
+
+(defmacro eintest:shared-output-with-buffer (&rest body)
+ (declare (indent 0))
+ `(with-current-buffer (ein:shared-output-create-buffer)
+ (ein:shared-output-get-or-create)
+ ,@body))
+
+(defmacro eintest:shared-output-is-empty-context-of (func)
+ `(ert-deftest ,(intern (format "%s--shared-output" func)) ()
+ (eintest:shared-output-with-buffer
+ (should-not (,func)))))
+
+
+;; Generic getter
+
+(ert-deftest ein:get-cell-at-point--shared-output ()
+ (eintest:shared-output-with-buffer
+ (should (eq (ein:get-cell-at-point)
+ (ein:shared-output-get-cell))))
+ (with-temp-buffer
+ (should-not (ein:get-cell-at-point--shared-output))))
+
+;; FIXME: Add tests with non-empty shared output buffer.
+(eintest:shared-output-is-empty-context-of ein:get-url-or-port)
+(eintest:shared-output-is-empty-context-of ein:get-notebook)
+(eintest:shared-output-is-empty-context-of ein:get-kernel)
+(eintest:shared-output-is-empty-context-of ein:get-traceback-data)
diff --git a/test/test-ein-smartrep.el b/test/test-ein-smartrep.el
new file mode 100644
index 0000000..2c1b2e7
--- /dev/null
+++ b/test/test-ein-smartrep.el
@@ -0,0 +1,8 @@
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein-smartrep)
+
+(ert-deftest ein:smartrep-notebook-mode-alist-fboundp ()
+ (loop for (k . f) in ein:smartrep-notebook-mode-alist
+ do (should (fboundp f))))
diff --git a/test/test-ein-utils.el b/test/test-ein-utils.el
new file mode 100644
index 0000000..c57357c
--- /dev/null
+++ b/test/test-ein-utils.el
@@ -0,0 +1,182 @@
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein) ; for `ein:version'
+(require 'ein-utils)
+
+(ert-deftest ein-url-simple ()
+ (should (equal (ein:url 8888) "http://127.0.0.1:8888"))
+ (should (equal (ein:url "http://localhost") "http://localhost")))
+
+(ert-deftest ein-url-slashes ()
+ (loop for a in '("a" "a/" "/a")
+ do (loop for b in '("b" "/b")
+ do (should (equal (ein:url 8888 a b)
+ "http://127.0.0.1:8888/a/b")))
+ do (should (equal (ein:url 8888 a "b/")
+ "http://127.0.0.1:8888/a/b/"))))
+
+(ert-deftest ein-trim-simple ()
+ (should (equal (ein:trim "a") "a"))
+ (should (equal (ein:trim " a ") "a"))
+ (should (equal (ein:trim "\na\n") "a")))
+
+(ert-deftest ein-trim-middle-spaces ()
+ (should (equal (ein:trim "a b") "a b"))
+ (should (equal (ein:trim " a b ") "a b"))
+ (should (equal (ein:trim "\na b\n") "a b")))
+
+(ert-deftest ein-trim-left-simple ()
+ (should (equal (ein:trim-left "a") "a"))
+ (should (equal (ein:trim-left " a ") "a "))
+ (should (equal (ein:trim-left "\na\n") "a\n")))
+
+(ert-deftest ein-trim-right-simple ()
+ (should (equal (ein:trim-right "a") "a"))
+ (should (equal (ein:trim-right " a ") " a"))
+ (should (equal (ein:trim-right "\na\n") "\na")))
+
+(ert-deftest ein:trim-indent-empty ()
+ (should (equal (ein:trim-indent "") "")))
+
+(ert-deftest ein:trim-indent-one-line ()
+ (should (equal (ein:trim-indent "one line") "one line")))
+
+(ert-deftest ein:trim-indent-one-newline ()
+ (should (equal (ein:trim-indent "one line\n") "one line\n")))
+
+(ert-deftest ein:trim-indent-multi-lines-no-trim ()
+ (let ((original "\
+def func():
+ pass
+")
+ (trimmed "\
+def func():
+ pass
+"))
+ (should (equal (ein:trim-indent original) trimmed))))
+
+(ert-deftest ein:trim-indent-multi-lines-one-trim ()
+ (let ((original "\
+ def func():
+ pass
+")
+ (trimmed "\
+def func():
+ pass
+"))
+ (should (equal (ein:trim-indent original) trimmed))))
+
+(ert-deftest ein:trim-indent-multi-lines-with-empty-lines ()
+ (let ((original "\
+
+ def func():
+ pass
+
+")
+ (trimmed "\
+
+def func():
+ pass
+
+"))
+ (should (equal (ein:trim-indent original) trimmed))))
+
+
+;;; Text manipulation on buffer
+
+(ert-deftest ein:find-leftmot-column-simple-cases ()
+ (loop for (indent text) in
+ '(;; No indent
+ (0 "\
+def f():
+ pass")
+ ;; Indented python code
+ (4 "\
+ def f():
+ pass")
+ ;; Deeper indent can come first
+ (4 "\
+ # indent = 8
+ # indent 4")
+ ;; With empty lines
+ (4 "\
+
+ # indent = 8
+
+ # indent 4
+
+")
+ )
+ do (with-temp-buffer
+ (insert text)
+ (should (= (ein:find-leftmot-column (point-min) (point-max))
+ indent)))))
+
+
+;;; Misc
+
+(ert-deftest ein:list-insert-after ()
+ (should (equal (ein:list-insert-after '(a) 'a 'X) '(a X)))
+ (should (equal (ein:list-insert-after '(a b c) 'a 'X) '(a X b c)))
+ (should (equal (ein:list-insert-after '(a b c) 'b 'X) '(a b X c)))
+ (should (equal (ein:list-insert-after '(a b c) 'c 'X) '(a b c X)))
+ (should-error (ein:list-insert-after '(a b c) 'd 'X)))
+
+(ert-deftest ein:list-insert-before ()
+ (should (equal (ein:list-insert-before '(a) 'a 'X) '(X a)))
+ (should (equal (ein:list-insert-before '(a b c) 'a 'X) '(X a b c)))
+ (should (equal (ein:list-insert-before '(a b c) 'b 'X) '(a X b c)))
+ (should (equal (ein:list-insert-before '(a b c) 'c 'X) '(a b X c)))
+ (should-error (ein:list-insert-before '(a b c) 'd 'X)))
+
+(ert-deftest ein:list-move-left ()
+ (should (equal (ein:list-move-left '(a) 'a) '(a)))
+ (should (equal (ein:list-move-left '(a b) 'a) '(b a)))
+ (should (equal (ein:list-move-left '(a b) 'b) '(b a)))
+ (should (equal (ein:list-move-left '(a b c d) 'a) '(b c d a)))
+ (should (equal (ein:list-move-left '(a b c d) 'b) '(b a c d)))
+ (should (equal (ein:list-move-left '(a b c d) 'c) '(a c b d)))
+ (should (equal (ein:list-move-left '(a b c d) 'd) '(a b d c)))
+ (should-error (ein:list-move-left '(a b c d) 'X)))
+
+(ert-deftest ein:list-move-right ()
+ (should (equal (ein:list-move-right '(a) 'a) '(a)))
+ (should (equal (ein:list-move-right '(a b) 'a) '(b a)))
+ (should (equal (ein:list-move-right '(a b) 'b) '(b a)))
+ (should (equal (ein:list-move-right '(a b c d) 'a) '(b a c d)))
+ (should (equal (ein:list-move-right '(a b c d) 'b) '(a c b d)))
+ (should (equal (ein:list-move-right '(a b c d) 'c) '(a b d c)))
+ (should (equal (ein:list-move-right '(a b c d) 'd) '(d a b c)))
+ (should-error (ein:list-move-right '(a b c d) 'X)))
+
+(defun ein:testing-choose-setting-should-equal
+ (setting value desired &optional single-p)
+ (let ((setting setting))
+ (should (equal (ein:choose-setting 'setting value single-p) desired))))
+
+(ert-deftest ein:choose-setting-single-string ()
+ (let ((test 'ein:testing-choose-setting-should-equal))
+ (funcall test "a" nil "a")
+ (funcall test "a" 'whatever "a")))
+
+(ert-deftest ein:choose-setting-single-int ()
+ (let ((test #'ein:testing-choose-setting-should-equal))
+ (funcall test 1 nil 1 #'integerp)
+ (funcall test 1 'whatever 1 #'integerp)))
+
+(ert-deftest ein:choose-setting-alist ()
+ (let* ((test (lambda (&rest args)
+ (apply #'ein:testing-choose-setting-should-equal
+ '(("a" . 1) ("b" . 2) ("c" . 3))
+ args))))
+ (funcall test "a" 1)
+ (funcall test "b" 2)))
+
+(ert-deftest ein:choose-setting-func ()
+ (let* ((test (lambda (&rest args)
+ (apply #'ein:testing-choose-setting-should-equal
+ (lambda (x) 1)
+ args))))
+ (funcall test nil 1)
+ (funcall test 'whatever 1)))
diff --git a/test/test-ein-worksheet-notebook.el b/test/test-ein-worksheet-notebook.el
new file mode 100644
index 0000000..06cd3af
--- /dev/null
+++ b/test/test-ein-worksheet-notebook.el
@@ -0,0 +1,47 @@
+(eval-when-compile (require 'cl))
+(require 'ert)
+
+(require 'ein-notebook)
+(require 'ein-testing-notebook)
+
+
+;;; Event handler
+
+(defun ein:testing-worksheet-set-dirty
+ (pre-dirty value post-dirty fired-in)
+ (with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
+ (when pre-dirty
+ (ein:cell-goto (ein:worksheet-get-current-cell))
+ (insert "something"))
+ (should (equal (ein:worksheet-modified-p ein:%worksheet%) pre-dirty))
+ (with-current-buffer (funcall fired-in)
+ (let ((events (oref ein:%worksheet% :events))
+ (cell (ein:worksheet-get-current-cell)))
+ (ein:events-trigger events 'set_dirty.Worksheet
+ (list :cell cell :value value))))
+ (should (equal (ein:worksheet-modified-p ein:%worksheet%) post-dirty))))
+
+(defun ein:testing-scratchsheet-buffer ()
+ (ein:worksheet-buffer (ein:notebook-scratchsheet-open ein:%notebook%)))
+
+(defmacro ein:testing-worksheet-set-dirty-deftest
+ (pre-dirty value post-dirty &optional fired-in)
+ (let ((name (intern (format "ein:worksheet-set-dirty/%s-to-%s-fired-in-%s"
+ pre-dirty value
+ (or fired-in "current-buffer"))))
+ (fired-in-defun
+ (case fired-in
+ (scratchsheet 'ein:testing-scratchsheet-buffer)
+ (t 'current-buffer))))
+ `(ert-deftest ,name ()
+ (ein:testing-worksheet-set-dirty ,pre-dirty ,value ,post-dirty
+ #',fired-in-defun))))
+
+(ein:testing-worksheet-set-dirty-deftest t nil nil)
+(ein:testing-worksheet-set-dirty-deftest t t t )
+(ein:testing-worksheet-set-dirty-deftest nil nil nil)
+(ein:testing-worksheet-set-dirty-deftest nil t t )
+(ein:testing-worksheet-set-dirty-deftest t nil t scratchsheet)
+(ein:testing-worksheet-set-dirty-deftest t t t scratchsheet)
+(ein:testing-worksheet-set-dirty-deftest nil nil nil scratchsheet)
+(ein:testing-worksheet-set-dirty-deftest nil t nil scratchsheet)
diff --git a/test/test-ein-worksheet.el b/test/test-ein-worksheet.el
new file mode 100644
index 0000000..5065cbe
--- /dev/null
+++ b/test/test-ein-worksheet.el
@@ -0,0 +1,40 @@
+(require 'ert)
+
+(require 'ein-worksheet)
+(require 'ein-testing-cell)
+
+(defvar ein:testing-worksheet-example-data
+ (list (ein:testing-codecell-data "code example input")
+ (ein:testing-markdowncell-data "markdown example input")
+ (ein:testing-rawcell-data "raw example input")
+ (ein:testing-htmlcell-data "html example input")
+ (ein:testing-headingcell-data "heading example input")))
+
+(defun ein:testing-worksheet-new ()
+ (make-instance 'ein:worksheet
+ :discard-output-p (cons #'ignore nil)))
+
+(defun ein:testing-worksheet-to-json (cells &optional metadata)
+ (let* ((ws-0 (ein:worksheet-from-json (ein:testing-worksheet-new)
+ (list :cells cells
+ :metadata metadata)))
+ (ws-1 (ein:testing-worksheet-new))
+ (json-0 (ein:worksheet-to-json ws-0))
+ (json-1 (ein:worksheet-to-json
+ (ein:worksheet-from-json ws-1
+ (ein:json-read-from-string
+ (json-encode json-0))))))
+ (let* ((found (assoc 'metadata json-0)))
+ (when found
+ (should (cdr found))))
+ (should (equal json-0 json-1))))
+
+(ert-deftest ein:worksheet-to-json/empty ()
+ (ein:testing-worksheet-to-json nil))
+
+(ert-deftest ein:worksheet-to-json/example-data ()
+ (ein:testing-worksheet-to-json ein:testing-worksheet-example-data))
+
+(ert-deftest ein:worksheet-to-json/example-data-with-metadata ()
+ (ein:testing-worksheet-to-json ein:testing-worksheet-example-data
+ '(:name "Worksheet name")))
diff --git a/test/test-load.el b/test/test-load.el
new file mode 100644
index 0000000..1cf27c4
--- /dev/null
+++ b/test/test-load.el
@@ -0,0 +1,44 @@
+;; Load all test-ein-*.el files for interactive/batch use.
+
+;; Usage:
+;; emacs -Q -batch -L ... -l tests/test-load.el -f ert-run-tests-batch
+;; You will need to set load paths using `-L' switch.
+
+(prefer-coding-system 'utf-8)
+
+(require 'ein-dev)
+(require 'ein-testing)
+(require 'ein-jupyter)
+(require 'deferred)
+
+(ein:log 'info "Starting jupyter notebook server.")
+
+(defvar *ein:testing-jupyter-server-command* (or (getenv "JUPYTER_TESTING_COMMAND")
+ (executable-find "jupyter"))
+ "Path to command that starts the jupyter notebook server.")
+
+(defvar *ein:testing-jupyter-server-directory* (or (getenv "JUPYTER_TESTING_DIR") (concat default-directory "test"))
+ "Location where to start the jupyter notebook server.")
+
+(defvar *ein:testing-port* nil)
+(defvar *ein:testing-token* nil)
+
+(ein:setq-if-not ein:testing-dump-file-log "test-batch-log.log")
+(ein:setq-if-not ein:testing-dump-file-messages "test-batch-messages.log")
+(setq message-log-max t)
+(setq ein:force-sync t)
+
+(ein:log 'info "Staring local jupyter notebook server.")
+
+(setq ein:jupyter-server-args '("--no-browser" "--debug"))
+
+(deferred:sync! (ein:jupyter-server-start *ein:testing-jupyter-server-command* *ein:testing-jupyter-server-directory*))
+(multiple-value-bind (url token) (ein:jupyter-server-conn-info)
+ (ein:log 'info (format "testing-start-server url: %s, token: %s" url token))
+ (setq *ein:testing-port* url)
+ (setq *ein:testing-token* token)
+ (ein:log 'info "testing-start-server succesfully logged in."))
+
+(ein:load-files "^test-ein-.*\\.el$"
+ "./" ;(file-name-directory load-file-name)
+ t) ; ignore-compiled