;;; ess-test-r.el --- ESS tests for R  -*- lexical-binding: t; -*-
;;
;; This file 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 2, or (at your option)
;; any later version.
;;
;; This file 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.
;;
;; A copy of the GNU General Public License is available at
;; https://www.r-project.org/Licenses/
;;
;;; Commentary:
;;

(require 'ert)
(require 'etest "etest/etest")
(require 'ess-r-mode)
(require 'ess-test-r-utils)
(require 'cc-mode)
(require 'imenu)
(with-no-warnings
  (when (< emacs-major-version 26)
    (require 'cl))) ;; FIXME: For?

;;; R

;;; Code:

(ert-deftest ess-r-inherits-prog-mode-test ()
  (let* ((pmh-was-run nil)
         (prog-mode-hook (lambda () (setq pmh-was-run t))))
    (with-ess-test-r-file nil
      (should (derived-mode-p 'prog-mode))
      (should pmh-was-run)
      (should
       ;; Test that prog-mode-map is a keymap-parent
       (let ((map (current-local-map))
             found)
         (while (and map (not found))
           (if (eq (keymap-parent map) prog-mode-map)
               (setq found t)
             (setq map (keymap-parent map))))
         found)))))

(ert-deftest ess-build-eval-command-R-test ()
  (let ((command "command(\"string\")")
        (ess-dialect "R"))
    (should (string= (ess-build-eval-command command)
                     "base::as.environment('ESSR')$.ess.eval(\"command(\\\"string\\\")\", visibly = FALSE, output = FALSE)\n"))
    (should (string= (ess-build-eval-command command nil t)
                     "base::as.environment('ESSR')$.ess.eval(\"command(\\\"string\\\")\", visibly = FALSE, output = TRUE)\n"))
    (should (string= (ess-build-eval-command command t t "file.ext" "foo")
                     "base::as.environment('ESSR')$.ess.ns_eval(\"command(\\\"string\\\")\", visibly = TRUE, output = TRUE, package = 'foo', verbose = TRUE, file = 'file.ext')\n"))))

(ert-deftest ess-build-load-command-R-test ()
  (let ((ess-dialect "R"))
    (should (string= (ess-build-load-command "file.ext")
                     "base::as.environment('ESSR')$.ess.source('file.ext', visibly = FALSE, output = FALSE)\n"))
    (should (string= (ess-build-load-command "file.ext" t t)
                     "base::as.environment('ESSR')$.ess.source('file.ext', visibly = TRUE, output = TRUE)\n"))
    (should (string= (ess-build-load-command "file.ext" nil t "foo")
                     "base::as.environment('ESSR')$.ess.ns_source('file.ext', visibly = FALSE, output = TRUE, package = 'foo', verbose = TRUE)\n"))))

(ert-deftest inferior-ess-inherits-from-comint-test ()
  (let ((inhibit-message ess-inhibit-message-in-tests))
    (with-temp-buffer
      (inferior-ess-r-mode)
      ;; Derive from comint
      (should (derived-mode-p 'comint-mode))
      (should
       ;; Test that comint-mode-map is a keymap-parent
       (let ((map (current-local-map))
             found)
         (while (and map (not found))
           (if (eq (keymap-parent map) comint-mode-map)
               (setq found t)
             (setq map (keymap-parent map))))
         found)))))

(ert-deftest ess-r-send-single-quoted-strings-test ()
  (ess-test-with-r-running nil
    (insert "'hop'\n")
    (let (ess-eval-visibly)
      (should (ess-test-output= (ess-eval-buffer nil)
                       "[1] \"hop\"")))))

(ert-deftest ess-r-send-double-quoted-strings-test ()
  (ess-test-with-r-running nil
    (insert "\"hop\"\n")
    (let (ess-eval-visibly)
      (should (ess-test-output= (ess-eval-buffer nil)
                       "[1] \"hop\"")))))

(ert-deftest ess-eval-line-test ()
  (ess-test-with-r-running nil
    (insert "1 + 1")
    (let (ess-eval-visibly)
      (should (ess-test-output= (ess-eval-line)
                       "[1] 2")))
    (let ((ess-eval-visibly t))
      (should (ess-test-output= (ess-eval-line)
                       "1 + 1\n[1] 2")))))

(ert-deftest ess-eval-region-test ()
  (ess-test-with-r-running nil
    (insert "1 + \n1")
    (let (ess-eval-visibly)
      (should (ess-test-output= (ess-eval-region (point-min) (point-max) nil)
                       "+ [1] 2")))
    (let ((ess-eval-visibly t))
      (should (ess-test-output= (ess-eval-region (point-min) (point-max) nil)
                       "1 + \n+ 1\n[1] 2")))))

(ert-deftest ess-eval-function ()
  (ess-test-with-r-running nil
    (let (ess-eval-visibly)
      (insert "x <- function(a){\n a + 1\n}")
      (forward-line -1)
      (ess-eval-function)
      (delete-region (progn (beginning-of-defun) (point))
                     (progn (end-of-defun) (point)))
      (insert "x(1)")
      (should (ess-test-output= (ess-eval-region (point-min) (point-max) nil)
                       "+ + > [1] 2")))))

(ert-deftest ess-r-eval-rectangle-mark-mode-test ()
  (ess-test-with-r-running nil
    (insert "x <- 1\nx\nx + 1\nx  +  2\n")
    (let (ess-eval-visibly)
      (should (ess-test-output= (progn
                         (goto-char (point-min))
                         (transient-mark-mode)
                         (rectangle-mark-mode)
                         (forward-line 3)
                         (end-of-line)
                         (ess-eval-region-or-line-and-step))
                       "> [1] 1\n> [1] 2\n> [1] 3")))))

(ert-deftest ess-set-working-directory-test ()
  (ess-test-with-r-running nil
    (ess-set-working-directory "/")
    (ess-eval-linewise "getwd()" 'invisible)
    (should (ess-test-output= (ess-eval-buffer nil)
                     "setwd('/')\n> [1] \"/\""))
    (should (string= (ess-get-process-variable 'default-directory) "/"))))

(ert-deftest ess-inferior-force-test ()
  (ess-test-with-r-running nil
    (should (equal (ess-get-words-from-vector "letters[1:2]\n")
                   (list "a" "b")))))

;;; Namespaced evaluation

(ert-deftest ess-r-run-presend-hooks-test ()
  (ess-test-with-r-running nil
    (let ((ess-presend-filter-functions (list (lambda (_string) "\"bar\"")))
          (ess-r-evaluation-env "base")
          ess-eval-visibly)
      (insert "\"foo\"\n")
      (should (ess-test-output= (ess-eval-region (point-min) (point-max) nil)
                       "[1] \"bar\"")))))

(ert-deftest ess-r-namespaced-eval-no-sourced-message-test ()
  (ess-test-with-r-running nil
    (let ((ess-r-evaluation-env "base")
          ess-eval-visibly)
      (insert "\"foo\"\n")
      (should (ess-test-output= (ess-eval-region (point-min) (point-max) nil)
                       "[1] \"foo\"")))))

(ert-deftest ess-r-namespaced-eval-no-srcref-in-errors-test ()
  ;; Fails since https://github.com/emacs-ess/ESS/commit/3a7d913
  (when nil
    (ess-test-with-r-running nil
      (let ((ess-r-evaluation-env "base")
            (error-msg "Error: unexpected symbol")
            ess-eval-visibly)
        (insert "(foo bar)\n")
        (let ((output (ess-test-output (ess-eval-region (point-min) (point-max) nil))))
          (should (string= (substring output 0 (length error-msg))
                           error-msg)))))))


;;; Misc

(ert-deftest ess-r-makevars-mode-test ()
  (save-window-excursion
    (mapc (lambda (file)
            (switch-to-buffer (find-file-noselect file))
            (should (eq major-mode 'makefile-mode)))
          `(,(expand-file-name "Makevars" ess-test-fixtures-directory)
            ,(expand-file-name "Makevars.win" ess-test-fixtures-directory)))))

(ert-deftest ess-find-newest-date-test ()
  (should (equal (ess-find-newest-date '(("2003-10-04" . "R-1.7")
                                         ("2006-11-19" . "R-2.2")
                                         ("2007-07-01" . "R-dev")
                                         ("-1"         . "R-broken")
                                         ("2005-12-30" . "R-2.0")))
                 "R-dev")))

(ert-deftest ess-skip-thing-test ()
  (should (eql 18
               (ess-r-test-with-temp-text "x <- function(x){\n mean(x)\n }\n \n \n x(3)\n "
                 (goto-char (point-min))
                 (ess-skip-thing 'line)
                 (point))))
  (should (eql 30
               (ess-r-test-with-temp-text "x <- function(x){\n mean(x)\n }\n \n \n x(3)\n "
                 (goto-char (point-min))
                 (ess-skip-thing 'function)
                 (point))))
  (should (eql 31
               (ess-r-test-with-temp-text "x <- function(x){\n mean(x)\n }\n \n \n x(3)\n "
                 (goto-char (point-min))
                 (ess-skip-thing 'paragraph)
                 (point))))

  ;; The following fails because end-of-defun assume that beginning-of-defun
  ;; always moves the pointer. We currently don't in ess-r-beginning-of-function
  ;; when there is no function. This might change when we have aproper
  ;; ess-r-beginning-of-defun.
  ;; (should (eql 1 (ess-r-test-with-temp-text "mean(1:10)"
  ;;                  (goto-char (point-min))
  ;;                  (ess-skip-thing 'function)
  ;;                  (point))))
  )

(ert-deftest ess-next-code-line-test ()
  (should (eql 5
               (ess-r-test-with-temp-text "1+1\n#2+2\n#3+3\n4+4"
                 (let ((ess-eval-empty t))
                   (goto-char (point-min))
                   (ess-next-code-line)
                   (point)))))
  (should (eql 15
               (ess-r-test-with-temp-text "1+1\n#2+2\n#3+3\n4+4"
                 (let (ess-eval-empty)
                   (goto-char (point-min))
                   (ess-next-code-line)
                   (point)))))
  (should (eql 5
               (ess-r-test-with-temp-text "1+1\n"
                 (let (ess-eval-empty)
                   (goto-char (point-min))
                   (end-of-line)
                   (ess-next-code-line)
                   (point))))))

(ert-deftest ess-Rout-file-test ()
  (let* ((file (expand-file-name "file.Rout" ess-test-fixtures-directory))
         (buf (or (find-buffer-visiting file)
                  (find-file-noselect file))))
    (with-current-buffer buf
      (should (eq major-mode 'ess-r-transcript-mode))
      (goto-char (point-min))
      (font-lock-default-fontify-buffer)
      (should (eq (face-at-point) 'font-lock-function-name-face)))))

(ert-deftest inferior-ess-r-fontification-test ()
  (ess-test-with-r-running nil
    (with-ess-process-buffer nil
      ;; Function-like keywords
      (should (eq major-mode 'inferior-ess-r-mode))
      (ess-test-insert-fontified "for")
      (should (not (ess-test-face-at -1)))
      (ess-test-insert-fontified "(")
      (should (eq (ess-test-face-at -2) 'ess-keyword-face))
      ;; `in` keyword
      (ess-test-insert-fontified "foo in bar)")
      (search-backward "in")
      (should (eq (face-at-point) 'ess-keyword-face))
      (erase-buffer)
      (ess-test-insert-fontified "for foo in bar")
      (search-backward "in")
      (should (not (face-at-point))))))

;; roxy
(ert-deftest ess-roxy-preview-Rd-test ()
  (let ((buf (generate-new-buffer " *temp*")))
    (ess-test-with-r-running buf
      (if (member "roxygen2" (ess-installed-packages))
          (should
           (string= "% Generated by roxygen2: do not edit by hand
\\name{add}
\\alias{add}
\\title{Add together two numbers. add(10, 1)}
\\usage{
add(x, y)
}
\\description{
Add together two numbers. add(10, 1)
} "
                    (with-current-buffer buf
                      (R-mode)
                      (ess-roxy-mode)
                      (insert
                       "##' Add together two numbers. add(10, 1)
add <- function(x, y) {
  x + y
}")
                      (goto-char (point-min))
                      (ess-roxy-preview-Rd)
                      ;; Delete the reference to the file which isn't
                      ;; reproducible across different test environments
                      (goto-char (point-min))
                      (forward-line 1)
                      (kill-whole-line)
                      (buffer-substring-no-properties (point-min) (point-max)))))
        (ert-skip "Roxygen2 not installed")))))

(ert-deftest ess-test-roxy-font-lock ()
  (ess-r-test-with-temp-text
      "#' a function
#'
#' @param x the param
#' @param y the other param
my_mean <- function(x, y){
  mean(x) + mean(y)
}"
    (font-lock-ensure)
    (goto-char (point-min))
    (search-forward "@par")
    (should (equal (face-at-point) 'font-lock-keyword-face))
    (search-forward "x")
    (forward-char -1)
    (should (equal (face-at-point) 'font-lock-variable-name-face))
    (search-forward "the para")
    (should (equal (face-at-point) 'font-lock-comment-face)))
  (ess-r-test-with-temp-text
      "#' @describeIn foo
#' @export
foo <- function(x){
  mean(x)
}"
    (font-lock-ensure)
    (goto-char (point-min))
    (search-forward "@des")
    (should (equal (face-at-point) 'font-lock-keyword-face))
    (search-forward "@exp")
    (should (equal (face-at-point) 'font-lock-variable-name-face))))

(ert-deftest ess-roxy-get-function-args-test ()
  (ess-r-test-with-temp-text
      "#' a function
#'
#' @param x the param
my_mean <- function(x, y){
  mean(x) + mean(y)
}

#' another function
#'
#' @param z the param
my_mean2 <- function(z){
  mean(z)
}"
    (should (equal (ess-roxy-get-function-args) '("x" "y")))
    (search-forward "param z the param")
    (should (equal (ess-roxy-get-function-args) '("z")))))

(ert-deftest ess-roxy-update-entry-test ()
  (should (string=
           "fun1 <- function(x) x
##' .. content for \\description{} (no empty lines) ..
##'
##' .. content for \\details{} ..
##' @title 
##' @param x 
##' @return 
##' @author Jane Doe
fun2 <- function(x) x"
           (ess-r-test-with-temp-text "fun1 <- function(x) x

¶fun2 <- function(x) x"
             (let ((ess-roxy-template-alist '(("description" . ".. content for \\description{} (no empty lines) ..")
                                              ("details" . ".. content for \\details{} ..")
                                              ("title" . "")
                                              ("param" . "")
                                              ("return" . "")
                                              ("author" . "Jane Doe"))))
               (ess-roxy-update-entry))
             (buffer-substring-no-properties (point-min) (point-max))))))

(ert-deftest ess-roxy-cpp-test ()
  ;; Test M-q
  (should (string=
           "//' Title
//'
//' @param Lorem ipsum dolor sit amet, consectetur adipiscing elit,
//'   sed do eiusmod.
//' @param Lorem ipsum dolor sit amet, consectetur adipiscing elit,
//'   sed do eiusmod.
//' @examples
//' mean()
"
           (ess-cpp-test-with-temp-text
               "//' Title
//'
//' @param Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.
//' @param ¶Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.
//' @examples
//' mean()
"
             (c-fill-paragraph)
             (buffer-substring-no-properties (point-min) (point-max)))))
  ;; Test newline
  (should (string=
           "//'\n//' "
           (ess-cpp-test-with-temp-text "//' ¶"
             (ess-roxy-newline)
             (buffer-substring-no-properties (point-min) (point-max)))))
  (should (string=
           "//\n"
           (ess-cpp-test-with-temp-text "//¶"
             (ess-roxy-newline)
             (buffer-substring-no-properties (point-min) (point-max))))))

;; Navigation
(ert-deftest ess-r-function-beg-end-test ()
  (ess-r-test-with-temp-text
      "x <- function(a){\n  a + 1\n}"
    (search-forward "(a)")
    (beginning-of-defun)
    (should (eql (point) 1))
    (search-forward "(a)")
    (end-of-defun)
    (should (eql (point) 28))))

(ert-deftest ess-r-beginning/defun-limits-test ()
  (with-ess-test-r-file (expand-file-name "navigation.R" ess-test-fixtures-directory)
    (goto-char (point-min))
    (end-of-defun 1)
    (should (looking-back "end of fn1\n"))
    (beginning-of-defun 1)
    (should (looking-at "fn1 <-"))
    (beginning-of-defun -1)
    (should (looking-at "fn1 <-"))
    (end-of-defun 3)
    (should (looking-back "end of fn3\n"))
    (beginning-of-defun)
    (should (looking-at "fn3 <-"))
    (end-of-defun 2)
    (should (looking-back "setMethod\n"))
    (end-of-defun 1)
    (should (looking-back "end of fn4\n"))
    (beginning-of-defun 1)
    (should (looking-at "fn4 <-"))
    (beginning-of-defun 1)
    (should (looking-at "setMethod("))
    (end-of-defun -1)
    (should (looking-back "fn3\n"))
    (re-search-forward "^f8 <-")
    (beginning-of-line)
    (end-of-defun 1)
    (should (looking-back "end of f8\n"))
    (end-of-defun -1)
    (should (looking-back "end of f5\n"))))

(ert-deftest ess-r-beginning/function-limits-test-1 ()
  (with-ess-test-r-file (expand-file-name "navigation.R" ess-test-fixtures-directory)
    (goto-char (point-min))
    (ess-r-end-of-function 1)
    (should (looking-at " ## end of fn1"))
    (ess-r-beginning-of-function 1)
    (should (looking-at "fn1 <-"))
    (ess-r-beginning-of-function -1)
    (should (looking-at "fn1 <-"))
    (ess-r-end-of-function)
    (ess-r-end-of-function)
    (ess-r-end-of-function)
    (should (looking-at " ## end of fn3\n"))
    (ess-r-beginning-of-function)
    (should (looking-at "fn3 <-"))
    (ess-r-end-of-function)
    (ess-r-end-of-function)
    (should (looking-at " ## end of setMethod"))
    (ess-r-end-of-function 1)
    (should (looking-at " ## end of fn4"))
    (ess-r-beginning-of-function 1)
    (should (looking-at "inner_fn5 <-"))
    (ess-r-beginning-of-function 1)
    (should (looking-at "fn4 <-"))
    (ess-r-beginning-of-function 1)
    (should (looking-at "setMethod("))
    (ess-r-end-of-function -1)
    (should (looking-at " ## end of fn3"))))

(ert-deftest ess-r-beginning/function-limits-test-2 ()
  (with-ess-test-r-file (expand-file-name "navigation.R" ess-test-fixtures-directory)
    (goto-char (point-min))
    (re-search-forward "after f5")
    (ess-r-end-of-function)
    (should (looking-at ".* end of f6$"))
    (ess-r-end-of-function)
    (should (looking-at ".* end of f7"))

    (goto-char (point-min))
    (re-search-forward "some_code6")
    (ess-r-beginning-of-function)
    (should (looking-at-p "f6 = function"))
    (re-search-forward "some_code7")
    (ess-r-beginning-of-function)
    (should (looking-at-p "f7 = function"))

    (goto-char (point-min))
    (search-forward "f8 <-")
    (ess-r-beginning-of-function)
    (should (looking-at-p "f8 <-"))

    (goto-char (point-min))
    (search-forward "f9 <-")
    (beginning-of-line 1)
    (ess-r-beginning-of-function)
    (should (looking-at-p "f8 <-"))))

(ert-deftest ess-r-goto-beginning/end-of-function-or-para-test ()
  (with-ess-test-r-file (expand-file-name "navigation.R" ess-test-fixtures-directory)
    (goto-char (point-min))
    (ess-goto-end-of-function-or-para)
    (should (looking-back "fn1\n"))
    (ess-goto-beginning-of-function-or-para)
    (should (looking-at "fn1 <-"))
    (ess-goto-end-of-function-or-para)
    (ess-goto-end-of-function-or-para)
    (should (looking-back "fn2\n"))
    (ess-goto-end-of-function-or-para)
    (should (looking-back "fn3\n"))
    (ess-goto-end-of-function-or-para)
    (should (looking-back "par1\n"))
    (ess-goto-end-of-function-or-para)
    (should (looking-back "setMethod\n"))
    (ess-goto-beginning-of-function-or-para)
    (should (looking-at "setMethod"))
    (ess-goto-beginning-of-function-or-para)
    (should (looking-at "par1 <-"))
    (ess-goto-beginning-of-function-or-para)
    (should (looking-at "fn3 <-"))))

(ert-deftest ess-r-beginning/end-of-defun-ignore-inner-fn-test ()
  (with-ess-test-r-file (expand-file-name "navigation.R" ess-test-fixtures-directory)
    (goto-char (point-min))
    (re-search-forward "fn5_body")
    (beginning-of-defun)
    (should (looking-at "fn4 <- "))
    (re-search-forward "fn5_body")
    (end-of-defun)
    (should (looking-back "fn4\n"))))

(ert-deftest ess-r-comment-dwim-test ()
  "Test `comment-dwim' and Bug #434."
  (let ((ess-style 'RRR))
    (ess-r-test-with-temp-text "#¶ "
      (let ((ess-indent-with-fancy-comments t))
        (comment-dwim nil)
        (should (eql 42 (current-column)))
        (ess-indent-or-complete)
        (should (eql 42 (current-column)))))
    (ess-r-test-with-temp-text "#¶ "
      (let ((ess-indent-with-fancy-comments nil))
        (comment-dwim nil)
        (should (eql 2 (current-column)))
        (ess-indent-or-complete)
        (should (eql 2 (current-column)))))))

;; imenu
(ert-deftest ess-imenu-test ()
  (ess-r-test-with-temp-text
      "
library(knitr)
x <- function(a) mean(a)

y = function(c){
  sum(c)
}

the_dat <- read.csv(\"foo.csv\")"
    (let ((result (funcall imenu-create-index-function)))
      (should (equal (car (nth 0 result)) "Data"))
      (should (equal (caadr (nth 0 result)) "the_dat"))
      (should (equal (car (nth 1 result)) "Package"))
      (should (equal (caadr (nth 1 result)) "knitr"))
      (should (equal (car (nth 2 result)) "Functions"))
      (should (equal (caadr (nth 2 result)) "x"))
      (should (equal (caaddr (nth 2 result)) "y")))))

(ert-deftest ess-narrow-to-defun-or-para-test ()
  (ess-r-test-with-temp-text
      "mean(1:10)

x <- function(x){
    mean(x)
})
"
    (goto-char (point-min))
    (search-forward "mean(x)")
    (ess-narrow-to-defun-or-para)
    (goto-char (point-min))
    (should (looking-at-p "x <- function(x){$"))))

(ert-deftest ess-test-r-help-mode ()
  (ess-test-with-r-running nil
    (let ((ess-pop-to-buffer t))
      (ess-display-help-on-object "plot")
      (should (equal ess-help-object "plot"))
      (should (derived-mode-p 'ess-r-help-mode))
      ;; Ensure help buffers after button presses are also in
      ;; `ess-r-help-mode', Bug#836
      (forward-button 2)
      (push-button)
      (should (or (equal ess-help-object "plot.default")
                  (equal ess-help-object "plot")))
      (should (derived-mode-p 'ess-r-help-mode)))))

(ert-deftest ess-test-r-index-mode ()
  (skip-unless (not noninteractive))
  (ess-test-with-r-running nil
    (let ((ess-pop-to-buffer t))
      (ess-display-package-index "stats")
      (should (equal ess-help-object "stats"))
      (should (derived-mode-p 'ess-r-help-mode))
      ;; Ensure help buffers after button presses are also in
      ;; `ess-r-help-mode', Bug#836
      (forward-button 2)
      (push-button)
      (should (equal ess-help-object "plot.default"))
      (should (derived-mode-p 'ess-r-help-mode)))))

(ert-deftest ess-transcript-comint-prompt-test ()
  ;; Bug#853
  (insert "> set.seed(1); (i <- rnorm(7))
[1] -0.6264538  0.1836433 -0.8356286  1.5952808  0.3295078 -0.8204684  0.4874291
> rep(i, 3)
 [1] -0.6264538  0.1836433 -0.8356286  1.5952808  0.3295078 -0.8204684  0.4874291
 [8] -0.6264538  0.1836433 -0.8356286  1.5952808  0.3295078 -0.8204684  0.4874291
[15] -0.6264538  0.1836433 -0.8356286  1.5952808  0.3295078 -0.8204684  0.4874291
> sum(i)
[1] 0.3133101")
  (ess-r-transcript-mode)
  (goto-char (point-min))
  (comint-next-prompt 1)
  (should (equal (line-number-at-pos) 3)))

(ert-deftest ess-test-r-startup-directory ()
  (let ((proj-dir (expand-file-name ".."))
        (cur-dir (directory-file-name (expand-file-name default-directory))))
    (let ((ess-startup-directory nil))
      (ess-test-with-r-running nil
        (with-current-buffer (ess-get-process-buffer)
          (should (string= proj-dir (directory-file-name (expand-file-name default-directory)))))))
    (let ((ess-startup-directory 'default-directory))
      (ess-test-with-r-running nil
        (with-current-buffer (ess-get-process-buffer)
          (should (string= cur-dir (directory-file-name (expand-file-name default-directory)))))))))

(ert-deftest ess-test-r-comint-input-ring-file-name ()
  (let ((ess-history-file t)
        (ess-startup-directory 'default-directory) ; don't start in the project
        ess-history-directory)
    (ess-test-with-r-running nil
      (should (string= (expand-file-name ".Rhistory" default-directory)
                       (buffer-local-value 'comint-input-ring-file-name (ess-get-process-buffer))))))
  (let ((ess-history-file "foo")
        (ess-history-directory temporary-file-directory))
    (ess-test-with-r-running nil
      (should (string= (expand-file-name "foo" temporary-file-directory)
                       (buffer-local-value 'comint-input-ring-file-name (ess-get-process-buffer))))))
  (let ((ess-history-file t)
        (ess-history-directory temporary-file-directory))
    (ess-test-with-r-running nil
      (should (string= (expand-file-name ".Rhistory" temporary-file-directory)
                       (buffer-local-value 'comint-input-ring-file-name (ess-get-process-buffer))))))
  (let (ess-history-file
        ess-history-directory)
    (ess-test-with-r-running nil
      (should-not (buffer-local-value 'comint-input-ring-file-name (ess-get-process-buffer))))))

(ert-deftest ess-test-roxy-prefix-strip ()
  "Only strip prefix if the whole string is prefixed (#1020)"
  (let ((ess-roxy-re "^#+'"))
    (should (equal (ess-roxy-remove-roxy-re "#' 1\n#' 2")
                   "1\n2"))
    (should (equal (ess-roxy-remove-roxy-re "#' 1\n#' 2\nNULL")
                   "#' 1\n#' 2\nNULL"))))

(defun ess-r-test-transcript-init ()
  (ess-r-transcript-mode)
  (read-only-mode -1)
  (ess-test-r-set-local-process))

(etest-deftest ess-r-transcript-motions-test ()
  "[enter] handles commands, non-commands, and prompts (#1013)."
  :init ((eval . (ess-r-test-transcript-init)))
  :case "
¶R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.
"
  "RET"
  :result "
R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.
¶"

  :inf-result "
> "

  :messages "No command at this point"

  :case "
> ¶for(i in 1:3) {
+   print(i)
+ }
[1] 1
[1] 2
[1] 3
"
  "RET"
  :result "
> for(i in 1:3) {
+   print(i)
+ }
[1] 1
[1] 2
[1] 3
¶"

  :inf-result "for(i in 1:3) {
+   print(i)
+ }
[1] 1
[1] 2
[1] 3
> ")

(etest-deftest ess-r-eval-linewise-visibly-test ()
  "C-c C-n always evaluates visibly.
https://github.com/emacs-ess/ESS/issues/725#issuecomment-431781558"
  :init ((mode . r)
         (eval . (ess-test-r-set-local-process)))
  :case "
¶1
2
3
"

  (setq-local ess-eval-visibly-p 'nowait)
  "C-c C-n"
  :result "
1
¶2
3
"
  :inf-result "1
[1] 1
> "

  (setq-local ess-eval-visibly-p nil)
  "C-c C-n"
  :result "
1
2
¶3
"
  :inf-result "2
[1] 2
> ")

(ert-deftest ess-r-commands-test ()
  "M-x R works (#1035)"
  (should (commandp 'R))
  (should (commandp 'R-newest)))

(etest-deftest ess-command-last-value-test ()
  "`ess-command` preserves `.Last.value (#1058)"
  :case "100¶"
  "C-c C-c"
  :inf-result "100
[1] 100
> "
  :case ".Last.value¶"
  "C-c C-c"
  :inf-result ".Last.value
[1] 100
> "
  (ess-command "-1\n")
  :case ".Last.value¶"
  "C-c C-c"
  :inf-result ".Last.value
[1] 100
> ")

(etest-deftest ess-r-pager-test ()
  (should (string= (car (ess-get-words-from-vector "getOption('pager')\n"))
                   "cat")))

;; rdired

(require 'ess-rdired)

(etest-deftest ess-rdired-test ()
  :inf-cleanup
  (progn
    (kill-buffer ess-rdired-buffer)
    (ess-command "rm(my_rdired_variable, envir = globalenv())\n"))

  (ess-command "assign('my_rdired_variable', TRUE, envir = globalenv())\n")
  (save-window-excursion
    (ess-rdired))
  (with-current-buffer ess-rdired-buffer
    (goto-char (point-min))
    (should (re-search-forward "my_rdired_variable" nil t))))

;; help

(ert-deftest ess-help-get-pkg-from-help-path-test ()
  "Handles packages with `.` in name (#1101)."
  (should (string= (ess-r-help--get-pkg-from-help-path "~/library/4.0/data.table/help/data.table")
                   "data.table")))

;; Utils for inferior R process

(ert-deftest ess-mock-remote-process ()
  ;; Check that our test is doing what we think it should, namely that it is
  ;; creating a mock remote process (i.e. a test of our test setup)
  (with-ess-test-r-file (ess-test-create-remote-path "dummy-pkg/R/test.R")
    (ess-test-with-r-running (current-buffer)
      (should (file-remote-p (ess-get-process-variable 'default-directory))))
    (kill-buffer)))

;; The assertions in this test do NOT require an internet connection
(ert-deftest ess-r-load-ESSR-github-fetch-no ()
  ;; Local ESSR load
  (ess--essr-load-or-throw-error "dummy-pkg/R/test.R"
                                 #'ess-r--load-ESSR-local)
  ;; Remote ESSR injection through process connection
  (ess--essr-load-or-throw-error (ess-test-create-remote-path "dummy-pkg/R/test.R")
                                 #'ess-r--load-ESSR-remote))

;; The assertions in this test DO require an internet connection
(ert-deftest ess-r-load-ESSR-github-fetch-yes ()
  ;; Skip test when performing as part of the CI
  (when (equal (getenv "TRAVIS") "true")
    (ert-skip "Running on TRAVIS"))
  (let* ((remote-file-path (ess-test-create-remote-path "dummy-pkg/R/test.R"))
         (essr-nm (concat "ESSRv" essr-version ".rds"))
         (essr-path (expand-file-name essr-nm "~/.config/ESSR")))
    ;; Ensure that ESSR doesn't currently exist on the machine which in turn
    ;; ensures that a GitHub fetch will be required to load ESSR. Note that for
    ;; Emacs <= 25 `delete-file' throws an error if the file doesn't exist.
    (ignore-errors (delete-file essr-path))
    ;; Remote ESSR load through GitHub fetch
    (ess--essr-load-or-throw-error remote-file-path #'ess-r--fetch-ESSR-remote)
    ;; ESSR has already been loaded on the remote machine, so this time the
    ;; function doesn't make a GitHub fetch
    (should (file-exists-p essr-path))
    (ess--essr-load-or-throw-error remote-file-path #'ess-r--fetch-ESSR-remote)))

(ert-deftest ess-r-failed-init-disable-bg-eval-test ()
  (ess-test-with-r-running nil
    (should-error (ess-r--init-error-handler))
    (should (not (ess-can-eval-in-background)))
    (let ((proc (ess-get-current-process)))
      (should (and proc (not (eq (ess-get-next-available-bg-process) proc))))))
  (unwind-protect
      (progn
        (setenv "ESSR_TEST_LOAD_ERROR" "true")
        (let ((err (should-error (progn
                                   (run-ess-test-r-vanilla)
                                   (with-current-buffer buf
                                     (ess-wait-for-process nil nil nil nil 2))))))
          (with-current-buffer inferior-ess--last-started-process-buffer
            (should (string-match-p "Loading failed with a nice message."
                                    (caddr err)))
            (should (not (ess-can-eval-in-background)))
            (should (not (ess-process-get 'format-command-alist)))
            (ess-send-string (ess-get-current-process) "Sys.unsetenv('ESSR_TEST_LOAD_ERROR')\n")
            ;; Can recover manually
            (ess-r-initialize)
            (should (ess-can-eval-in-background))
            (should (ess-process-get 'format-command-alist)))))
    (setenv "ESSR_TEST_LOAD_ERROR" nil)))

(etest-deftest ess-r-command-error-test ()
  (let ((err (should-error (ess-command "stop('bar')\n"))))
    (should (string-match-p "R error during background ESS command ‘stop('bar')’\nError: bar"
                            (cadr err))))
  :inf-result "")

(ert-deftest ess-r-post-run-hook-test ()
  "ESS waits before running user hook so that blocking
commands may be used."
  (let* (result
         (ess-r-post-run-hook
          (list (lambda () (setq result (ess-get-words-from-vector "'foo'\n"))))))
    (ess-test-with-r-running nil)
    (should (equal result (list "foo")))))

(provide 'ess-test-r)


;; Local Variables:
;; etest-local-config: etest-r-config
;; End:

;;; ess-test-r.el ends here
