;;; speed-type-test.el --- Tests for speed-type  -*- lexical-binding: t; -*-

;; Copyright (C) 2015 Gunther Hagleitner

;; Author: Gunther Hagleitner
;; Maintainer: Daniel Kraus <daniel@kraus.my>
;;      lordnik22
;; Version: 1.4.0
;; Keywords: games
;; URL: https://github.com/dakra/speed-type
;; Package-Requires: ((emacs "26.1") (compat "29.1.3"))
;; SPDX-License-Identifier: GPL-3.0-or-later

;; This file is NOT part of GNU Emacs.

;;; License:

;; This program 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.

;; This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; This file contains all the tests that ensure the functionality of speed-type

;;; Code:

(require 'ert)
(require 'speed-type)
(require 'cl-macs)

(ert-deftest speed-type-test/stop-words-p-supply-garbage ()
  (should-error (speed-type--stop-word-p nil))
  (should-error (speed-type--stop-word-p 1))
  (should-error (speed-type--stop-word-p 'symbol))
  (should-error (speed-type--stop-word-p '("asdf" "asdf")))
  (let ((speed-type-stop-words 'symbol))
    (should-error (speed-type--stop-word-p "word")))
  (let ((speed-type-stop-words 1))
    (should-error (speed-type--stop-word-p "word"))))

(ert-deftest speed-type-test/stop-words-p-is-t-when-found-in-list ()
  (setq speed-type-stop-words '("word"))
  (should (string= (speed-type--stop-word-p "word") "word")))

(ert-deftest speed-type-test/stop-words-p-is-t-when-found-in-file ()
  (setq speed-type-stop-words (concat (temporary-file-directory) "/stop-words.txt"))
  (let ((buffer (find-file-noselect speed-type-stop-words)))
    (unwind-protect
        (progn (with-current-buffer buffer
                 (insert "word\n")
                 (insert "otherWord\n")
                 (save-buffer))
               (should (string= (speed-type--stop-word-p "word") "word")))
      (delete-file (buffer-file-name buffer))
      (kill-buffer buffer))))

(ert-deftest speed-type-test/find-last-continue-at-point-in-stats-supply-garbage ()
  (should-error (speed-type--find-last-continue-at-point-in-stats 'symbol))
  (should-error (speed-type--find-last-continue-at-point-in-stats 1))
  (should-error (speed-type--find-last-continue-at-point-in-stats '("aqsf" "asdf"))))

(ert-deftest speed-type-test/find-last-continue-at-point-in-stats ()
  (let ((speed-type-statistic-filename (concat (temporary-file-directory) "speed-type-statistic.el")))
    (delete-file speed-type-statistic-filename)
    (should (eq (speed-type--find-last-continue-at-point-in-stats nil) nil))
    (should (null (speed-type--find-last-continue-at-point-in-stats "/tmp/101.txt")))
    (speed-type-save-stats
     speed-type-statistic-filename
     '((speed-type--continue-at-point . 2)
       (speed-type--file-name . "/tmp/101.txt")))
    (should (= (speed-type--find-last-continue-at-point-in-stats "/tmp/101.txt") 2))
    (speed-type-save-stats
     speed-type-statistic-filename
     '((speed-type--continue-at-point . 12)
       (speed-type--file-name . "/tmp/101.txt")))
    (speed-type-save-stats
     speed-type-statistic-filename
     '((speed-type--continue-at-point . 9)
       (speed-type--file-name . "/tmp/404.txt")))
    (should (= (speed-type--find-last-continue-at-point-in-stats "/tmp/101.txt") 12))
    (speed-type-save-stats
     speed-type-statistic-filename
     '((speed-type--continue-at-point . 22)
       (speed-type--file-name . "/tmp/101.txt")))
    (should (= (speed-type--find-last-continue-at-point-in-stats "/tmp/101.txt") 22))
    (should (= (speed-type--find-last-continue-at-point-in-stats "/tmp/404.txt") 9))
    (should (null (speed-type--find-last-continue-at-point-in-stats "/tmp/909.txt")))))

(ert-deftest speed-type-test/url-to-filename-supply-garbage ()
  (should (null (speed-type--url-to-filename nil)))
  (should (string= "" (speed-type--url-to-filename "")))
  (should (string= " " (speed-type--url-to-filename " ")))
  (should (string= (speed-type--url-to-filename "  a") (speed-type--url-to-filename " a"))))

(ert-deftest speed-type-test/url-to-filename-standard-cases ()
  (should (string= (speed-type--url-to-filename "https://github.com/dakra/speed-type") "https-github-com-dakra-speed-type-12b56e4"))
  (should (string= (speed-type--url-to-filename "https://en.wikipedia.org/wiki/John_von_Neumann") "https-en-wikipedia-org-wiki-John-von-Neumann-d9bd43e"))
  ;; hash-edge-case: `secure-hash-algorithms' is nil use \"no-hash\" for standard cases
  (cl-letf (((symbol-function 'secure-hash-algorithms) (lambda (&rest _) nil)))
    (should (string= (speed-type--url-to-filename "https://en.wikipedia.org/wiki/John_von_Neumann") "https-en-wikipedia-org-wiki-John-von-Neumann-no-hash"))))

(ert-deftest speed-type-test/url-to-filename-dash-logic ()
  "Rules:
- dash (-) are removed at begin and end
- multiples of dash(-) are replaced by single dash(-)"
  (should (string= (speed-type--url-to-filename "/path/to/file.sql") "path-to-file-sql-eacd04b"))
  (should (string= (speed-type--url-to-filename "https://github.com/dakra/speed-type///////////") "https-github-com-dakra-speed-type-244e51d"))
  (should (string= (speed-type--url-to-filename "https://github.com/dakra/speed-type///////////asdf") "https-github-com-dakra-speed-type-asdf-5e9d751")))

(ert-deftest speed-type-test/calc-median-supply-garbage ()
  (should-error (speed-type--calc-median "" '()))
  (should-error (speed-type--calc-median 1 '()))
  (should-error (speed-type--calc-median '() '()))
  (should-error (speed-type--calc-median 'a ""))
  (should-error (speed-type--calc-median 'a 'a))
  (should-error (speed-type--calc-median "" "")))

(ert-deftest speed-type-test/feeling-better? ()
  "Checks if it's a good day to program."
  (should (= 1 1)))

(defun speed-type-test-buffer (text test-in-buf)
  "Setup a speed-type-region for testing.

TEST-IN-BUF is a lambda which is executed within the speed-type-buffer."
  (let ((content text)
        (speed-type-save-statistic-option 'never)
        (speed-type-statistic-filename (concat (temporary-file-directory) "speed-type-statistic.el"))
        (speed-type-randomize t))
    (with-temp-buffer
      (insert content)
      (funcall 'fundamental-mode)
      (let ((buf (speed-type-buffer nil)))
        (with-current-buffer buf
          (funcall test-in-buf))
        (kill-buffer buf)))))

(defun speed-type-test-region (test-in-buf)
  "Setup a speed-type-region for testing.

TEST-IN-BUF is a lambda which is executed within the speed-type-buffer."
  (let ((content "abcde")
        (mode (nth (random 2) '(fundamental-mode emacs-lisp-mode)))
        (speed-type-provide-preview-option t)
        (speed-type-statistic-filename (concat (temporary-file-directory) "speed-type-statistic.el")))
    (with-temp-buffer
      (insert content)
      (funcall mode)
      (let ((buf (speed-type-region (point-min) (point-max))))
        (with-current-buffer buf
          (funcall test-in-buf))
        (kill-buffer buf)))))

(ert-deftest speed-type-test/times-is-empty-when-no-input ()
  "Test the time-register-variable is empty for flow: session-start -> complete."
  (speed-type-test-region
   (lambda ()
     (should (length= speed-type--time-register 0))
     (speed-type-complete)
     (should (length= speed-type--time-register 0)))))

(ert-deftest speed-type-test/times-stay-length-if-duplicate-call ()
  "Test the time-register-variable is empty for flow: session-start -> complete."
  (speed-type-test-region
   (lambda ()
     (should (length= speed-type--time-register 0))
     (insert "a")
     (dotimes (_ 3)
       (speed-type--resume)
       (should (length= speed-type--time-register 1)))))
  (speed-type-test-region
   (lambda ()
     (should (length= speed-type--time-register 0))
     (insert "a")
     (dotimes (_ 3)
       (speed-type-pause)
       (should (length= speed-type--time-register 2))))))

(ert-deftest speed-type-test/times-are-pushed-correctly-standard-flow ()
  "Test the time-register-variable in a standard flow: session-start -> first-input -> pause -> resume -> complete. After each state change there should be a new time pushed to the register."
  (speed-type-test-region
   (lambda ()
     (should (length= speed-type--time-register 0))
     (insert "a")
     (should (length= speed-type--time-register 1))
     (speed-type-pause)
     (should (length= speed-type--time-register 2))
     (insert "b")
     (should (length= speed-type--time-register 3))
     (speed-type-complete)
     (should (length= speed-type--time-register 4)))))

(ert-deftest speed-type-test/calculate-elasped-time ()
  "Test if calculation of duration of the typing-session."
  (should (= (speed-type--elapsed-time '()) 0))
  (should (eq (speed-type--elapsed-time '(1)) 'uneven))
  (should (= (speed-type--elapsed-time '(1 2)) 1))
  (should (= (speed-type--elapsed-time '(2 1)) -1))
  (should (eq (speed-type--elapsed-time '(1 2 3)) 'uneven))
  (should (= (speed-type--elapsed-time '(1 2 6 7)) 2))
  (should (=  (speed-type--elapsed-time '(1 2 7 6 8 9)) 1)))

(ert-deftest speed-type-test/point-motion-stay ()
  "Test if points stays and error are counted correctly."
  (let ((b-speed-type-point-motion-on-error speed-type-point-motion-on-error))
    (unwind-protect
        (progn
          (setq speed-type-point-motion-on-error 'point-stay)
          (speed-type-test-region
           (lambda ()
             (should (= (point) 1))
             (should (= speed-type--errors 0))
             (should (= speed-type--non-consecutive-errors 0))
             (insert "b")
             (should (= speed-type--errors 1))
             (should (= speed-type--non-consecutive-errors 1))
             (should (= (point) 1))
             (insert "c")
             (should (= speed-type--errors 2))
             (should (= speed-type--non-consecutive-errors 1))
             (should (= (point) 1))
             (insert "a")
             (should (= speed-type--errors 2))
             (should (= speed-type--non-consecutive-errors 1))
             (should (= (point) 2))
             (funcall (keymap-lookup nil "DEL") 1)
             (should (= (point) 1))
             (insert "a")
             (should (= speed-type--errors 2))
             (should (= speed-type--corrections 1))
             (should (= speed-type--non-consecutive-errors 1))
             (should (= (point) 2)))))
      (setq speed-type-point-motion-on-error b-speed-type-point-motion-on-error))))

(ert-deftest speed-type-test/point-motion-move ()
  "Test if points move and error are counted correctly."
  (let ((b-speed-type-point-motion-on-error speed-type-point-motion-on-error))
    (unwind-protect
        (progn
          (setq speed-type-point-motion-on-error 'point-move)
          (speed-type-test-region
           (lambda ()
             (should (= (point) 1))
             (should (= speed-type--errors 0))
             (should (= speed-type--non-consecutive-errors 0))
             (insert "b")
             (should (= speed-type--errors 1))
             (should (= speed-type--non-consecutive-errors 1))
             (should (= (point) 2))
             (insert "c")
             (should (= speed-type--errors 2))
             (should (= speed-type--non-consecutive-errors 1))
             (should (= (point) 3))
             (funcall (keymap-lookup nil "DEL") 1)
             (should (= (point) 2))
             (insert "b")
             (should (= speed-type--errors 2))
             (should (= speed-type--corrections 1))
             (should (= speed-type--non-consecutive-errors 1))
             (should (= (point) 3)))))
      (setq speed-type-point-motion-on-error b-speed-type-point-motion-on-error))))

(ert-deftest speed-type-test/test-chars-downcased ()
  "Test if text is downcased when speed-type-downcase is t.

Also assure when that added words are downcased too."
  (let ((speed-type-downcase t)
        (speed-type-add-extra-words-on-error 1)
        (speed-type-add-extra-words-on-non-consecutive-errors 0))
    (speed-type-test-buffer
     "ASDF"
     (lambda ()
       (should (string= "asdf" (buffer-string)))
       (insert "b")
       (sleep-for 0.3)
       (should (string= "asdf asdf" (buffer-string)))
       (with-current-buffer speed-type--content-buffer
         (should (string= "ASDF" (buffer-string))))))))

;; assure preview buffer in general region
;; test continue feature
;; complete a typing session and restart the same example
;; test variation with random
;; test top word iterator/calculation
;; test top word file and source file is written
(ert-deftest speed-type-test/general-region ()
  "Do a general test with `speed-type-region' with fundamental mode and a prog-mode, checking content, overlays, point and point-motion, buffer-variables and statistic file."
  (let ((content "abcde")
        (mode (nth (random 2) '(fundamental-mode emacs-lisp-mode)))
        (speed-type-statistic-filename (concat (temporary-file-directory) "speed-type-statistic.el")))
    (with-temp-buffer
      (insert content)
      (funcall mode)
      (let ((buf (speed-type-region (point-min) (point-max)))
            (content-buf speed-type--content-buffer))
        (unwind-protect
            (with-current-buffer buf
              (insert "a")
              (insert "b")
              (insert "a")
              (insert "a")
              (funcall (keymap-lookup nil "DEL") 1)
              (funcall (keymap-lookup nil "DEL") 1)
              (insert "c")
              (insert "!")
              (insert "!")
                                        ;       (should (= speed-type--start-time 1753299414.2124302))
              (should (eq speed-type--buffer (current-buffer)))
                                        ; (should (eq speed-type--content-buffer (get-buffer "*speed-type-content-buffer*")))
              (should (= speed-type--entries 5))
              (should (= speed-type--errors 4))
              (should (= speed-type--non-consecutive-errors 2))
              (should (= speed-type--corrections 1))
                                        ; (should (string= speed-type--title (buffer-name)))
              (should (string= speed-type--author (user-full-name)))
              (should (eq speed-type--lang nil))
              (should (eq speed-type--n-words nil))
              (should (eq speed-type--add-extra-word-content-fn nil))
              (should (eq speed-type--extra-words-animation-timer nil))
              (should (eq speed-type--extra-word-quote nil))
              (should (eq speed-type--go-next-fn nil))
              (should (eq speed-type--replay-fn 'speed-type--get-replay-fn))
              (should (eq speed-type--extra-word-quote nil))
              (dotimes (i 3)
                (should (eq (overlay-get (car (overlays-at (1+ i))) 'face) 'speed-type-correct-face)))
              (should (eq (overlay-get (car (overlays-at 4)) 'face) 'speed-type-error-face))
              (should (eq (overlay-get (car (overlays-at 5)) 'face) 'speed-type-consecutive-error-face))
              )
          (kill-buffer buf)
          (should (eq (buffer-live-p content-buf) nil)))))))

(ert-deftest speed-type-test/general-file-ref ()
  "Do a general test with `speed-type-region' and different modes, checking content, overlays, point and point-motion, buffer-variables and statistic file."
  (let ((content "abcde")
        (mode (nth 0 '(hexl-mode)))
        (speed-type-statistic-filename (concat (temporary-file-directory) "speed-type-statistic.el")))
    (with-temp-buffer
      (insert content)
      (write-file "test.bin")
      (funcall mode)
      (let ((buf (speed-type-region (point-min) (point-max))))
        (unwind-protect
            (with-current-buffer buf
              (insert "0")
              (insert "0")
              (insert "a")
              (insert "b")
              (funcall (keymap-lookup nil "DEL") 1)
              (funcall (keymap-lookup nil "DEL") 1)
              (insert "0")
              (insert "!")
              (insert "!")
              (should (eq speed-type--buffer (current-buffer)))
              (should (= speed-type--entries 5))
              (should (= speed-type--errors 4))
              (should (= speed-type--non-consecutive-errors 2))
              (should (= speed-type--corrections 1))
              (should (string= speed-type--author (user-full-name)))
              (should (eq speed-type--lang nil))
              (should (eq speed-type--n-words nil))
              (should (eq speed-type--add-extra-word-content-fn nil))
              (should (eq speed-type--extra-words-animation-timer nil))
              (should (eq speed-type--extra-word-quote nil))
              (should (eq speed-type--go-next-fn nil))
              (should (eq speed-type--replay-fn 'speed-type--get-replay-fn))
              (should (eq speed-type--extra-word-quote nil))
              (dotimes (i 3)
                (should (eq (overlay-get (car (overlays-at (1+ i))) 'face) 'speed-type-correct-face)))
              (should (eq (overlay-get (car (overlays-at 4)) 'face) 'speed-type-error-face))
              (should (eq (overlay-get (car (overlays-at 5)) 'face) 'speed-type-consecutive-error-face))
              (let ((offset 5))
                (dotimes (i (- 51 offset))
                  (should (eq (car (overlays-at (+ (1+ offset) i))) nil))))
              (speed-type-complete))
          (kill-buffer buf))))))

(ert-deftest speed-type--stats-test ()
  (should (= 3 (speed-type--seconds-to-minutes 180)))
  (should (= 30 (speed-type--gross-wpm 450 180)))
  (should (= 15 (speed-type--net-wpm 450 50 5 180)))
  (should (= 85 (speed-type--accuracy 100 90 5)))
  (should (string= "Rookie" (speed-type--skill 10)))
  (should (string= "Expert" (speed-type--skill 45)))
  (should (string= "Legend" (speed-type--skill 400))))

(ert-deftest speed-type--url-test ()
  (should (string= "https://www.gutenberg.org/cache/epub/1/pg1.txt"
                   (speed-type--gb-url 1))))

(ert-deftest speed-type--charfun-tests ()
  (should (speed-type--check-same 0 "\nfoo\n" "\nfoo\n"))
  (should (speed-type--check-same 1 "\nfoo\s" "\nfoo\n"))
  (should (speed-type--check-same 4 "\nfoo\s" "\nfoo\t"))
  (should (not (speed-type--check-same 2 "\nfoo\s" "\nfxo\n"))))

(ert-deftest speed-type-test/prepare-buffer-error-tests ()
  (with-temp-buffer ;; wrap with them buffer, because function may act on current-buffer
    (should-error (speed-type-prepare-buffer (point-min) (point-max) nil nil 0 '(speed-type--replace-string-hook) nil))))

(ert-deftest speed-type-test/prepare-buffer-minimal-tests ()
  (with-temp-buffer
    (insert "a a")
    (should (equal '(1 3 1) (speed-type-prepare-buffer 1 3 nil t nil nil)))
    (should (= 3 (point)))
    (should (equal '(speed-type-char-status ignore) (text-properties-at 2)))
    ;; point goes always to end
    (should (equal '(0 0 0) (speed-type-prepare-buffer 0 0 nil nil nil nil)))
    (should (= 1 (point)))
    ;; repeated call should result in same
    (should (equal '(1 3 1) (speed-type-prepare-buffer 1 3 nil t nil nil)))
    (should (= 3 (point)))
    (should (equal '(speed-type-char-status ignore) (text-properties-at 2)))))

(ert-deftest speed-type-test/prepare-buffer-special-case-transform-hook-makes-everything-empty ()
  "Stop at end."
  (with-temp-buffer
    (insert "  “Zweiter Theil” … Das Kind mit dem Spiegel Auf den glückseligen Inseln\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (insert "  Gesindel Von den Taranteln Von den berühmten Weisen Das Nachtlied Das …\n")
    (should (equal '(1 1 0)
                   (speed-type-prepare-buffer (point-min) (point-max) nil t
                                 (list (lambda (&rest _) (speed-type-re-forward-replace-map-adjust-properties '((".\\|\n" . "")) 'speed-type-orig-pos (point-max))))
                                 (make-speed-type-transform-context
                                  :major-mode 'fundamental-mode
                                  :text-type 'transform-text
                                  :start nil
                                  :end nil))))
    (should (string-empty-p (buffer-string)))))

(ert-deftest speed-type-test/prepare-buffer-act-on-region-tests ()
  (with-temp-buffer
    (insert "a a\n")
    (insert "b b\n")
    (insert "c c\n")
    (should (= 1 (caddr (speed-type-prepare-buffer 5 8 nil t nil nil))))
    (should (equal '(speed-type-char-status ignore) (text-properties-at 6)))
    (should (eq nil (text-properties-at 2)))
    (should (eq nil (text-properties-at 10)))))

(ert-deftest speed-type-test/prepare-buffer-default-hook-tests ()
  (with-temp-buffer
    (insert "  “Zweiter Theil” … Das Kind mit dem Spiegel Auf den glückseligen Inseln\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (insert "  Gesindel Von den Taranteln Von den berühmten Weisen Das Nachtlied Das …\n")
    (should (= 33 (caddr (speed-type-prepare-buffer
                          (point-min) (point-max) t t speed-type-transform-hook
                          (make-speed-type-transform-context
                           :major-mode 'fundamental-mode
                           :text-type 'transform-text
                           :start nil
                           :end nil)))))
    (should (string=
             (concat "\"Zweiter Theil\" ... Das Kind mit dem Spiegel Auf den glückseligen\n"
                     "Inseln Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom\n"
                     "Gesindel Von den Taranteln Von den berühmten Weisen Das Nachtlied Das\n"
                     "...")
             (buffer-string)))))

(ert-deftest speed-type-test/prepare-buffer-skip-content-test ()
  (with-temp-buffer
    (insert "  “Zweiter Theil” … Das Kind mit dem Spiegel Auf den glückseligen Inseln\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (insert "  Gesindel Von den Taranteln Von den berühmten Weisen Das Nachtlied Das …\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (should (> (point-max) 148)) ;; ensure final content-length shrinked due to transform
    (should (equal (list (point-min) 148 25)
                   (speed-type-prepare-buffer
                    (point-min) (point-max) t t
                    '((lambda (transform-context &rest _)
                        (speed-type-re-forward-replace-map-adjust-properties '((".*Von.*Vom.*\n" . "")) 'speed-type-orig-pos (speed-type-transform-context-end transform-context))))
                    (make-speed-type-transform-context
                     :major-mode 'fundamental-mode
                     :text-type 'transform-text
                     :start nil
                     :end nil))))
    (should (string=
             (concat "“Zweiter Theil” … Das Kind mit dem Spiegel Auf den glückseligen Inseln\n"
                     "  Gesindel Von den Taranteln Von den berühmten Weisen Das Nachtlied Das …")
             (buffer-string)))))

(ert-deftest speed-type-test/insert-text-default-hook-test ()
  (with-temp-buffer ;; acts as the content-buffer
    (insert "  “Zweiter Theil” … Das Kind mit dem Spiegel Auf den glückseligen Inseln\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (insert "  Gesindel Von den Taranteln Von den berühmten Weisen Das Nachtlied Das …\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (let ((content-buffer (current-buffer))
          (start (point-min))
          (end (point-max)))
      (with-temp-buffer ;; acts as the speed-type-buffer
        (speed-type-insert-text content-buffer start end t speed-type-transform-hook
                      (make-speed-type-transform-context
                       :major-mode 'fundamental-mode
                       :text-type 'transform-text
                       :start nil
                       :end nil))
        (should (>= (point-max) 250))))))

(ert-deftest speed-type-test/insert-text-section-tranform-hook-make-everything-empty ()
  (with-temp-buffer ;; acts as the content-buffer
    (insert "  “Zweiter Theil” … Das Kind mit dem Spiegel Auf den glückseligen Inseln\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (insert "  Gesindel Von den Taranteln Von den berühmten Weisen Das Nachtlied Das …\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (let ((content-buffer (current-buffer))
          (start (point-min))
          (end (point-max)))
      (with-temp-buffer ;; acts as the speed-type-buffer
        (speed-type-insert-text-section content-buffer start end 50 250 20 t
                              (list (lambda (&rest _) (speed-type-re-forward-replace-map-adjust-properties '((".\\|\n" . "")) 'speed-type-orig-pos (point-max))))
                 (make-speed-type-transform-context
                  :major-mode 'fundamental-mode
                  :text-type 'transform-text
                  :start nil
                  :end nil))
        (should (string-empty-p (buffer-string)))))))

(ert-deftest speed-type-test/insert-text-section-default-hook-test ()
  (with-temp-buffer ;; acts as the content-buffer
    (insert "  “Zweiter Theil” … Das Kind mit dem Spiegel Auf den glückseligen Inseln\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (insert "  Gesindel Von den Taranteln Von den berühmten Weisen Das Nachtlied Das …\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (let ((content-buffer (current-buffer))
          (start (point-min))
          (end (point-max)))
      (with-temp-buffer ;; acts as the speed-type-buffer
        (speed-type-insert-text-section content-buffer start end 50 250 20 t speed-type-transform-hook
                              (make-speed-type-transform-context
                               :major-mode 'fundamental-mode
                               :text-type 'continue-text-section
                               :start nil
                               :end nil))
        (should (equal (text-properties-at (point-min)) '(speed-type-orig-pos (3 . 11))))
        (should (>= (point-max) 50))))))

(ert-deftest speed-type-test/insert-wordlist-tranform-hook-make-everything-empty ()
  (with-temp-buffer ;; acts as the content-buffer
    (insert "  “Zweiter Theil” … Das Kind mit dem Spiegel Auf den glückseligen Inseln\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (insert "  Gesindel Von den Taranteln Von den berühmten Weisen Das Nachtlied Das …\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (let ((content-buffer (current-buffer))
          (start (point-min))
          (end (point-max)))
      (with-temp-buffer ;; acts as the speed-type-buffer
        (speed-type-insert-wordlist content-buffer start end 250 450 20 t
                          (list (lambda (&rest _) (speed-type-re-forward-replace-map-adjust-properties '((".\\|\n" . "")) 'speed-type-orig-pos (point-max))))
                              (make-speed-type-transform-context
                               :major-mode 'fundamental-mode
                               :text-type 'transform-text
                               :start nil
                               :end nil))
        (should (string-empty-p (buffer-string)))))))

(ert-deftest speed-type-test/insert-wordlist-one-word ()
  (with-temp-buffer ;; acts as the content-buffer
    (insert "Hello")
    (let ((content-buffer (current-buffer))
          (start (point-min))
          (end (point-max)))
      (with-temp-buffer ;; acts as the speed-type-buffer
        (speed-type-insert-wordlist content-buffer start end 250 450 20 t nil nil)
        (should (>= (point-max) 250))))))

(ert-deftest speed-type-test/insert-wordlist-default-hook-test ()
  (with-temp-buffer ;; acts as the content-buffer
    (insert "  “Zweiter Theil” … Das Kind mit dem Spiegel Auf den glückseligen Inseln\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (insert "  Gesindel Von den Taranteln Von den berühmten Weisen Das Nachtlied Das …\n")
    (insert "  Von den Mitleidigen Von den Priestern Von den Tugendhaften Vom    \n")
    (let ((content-buffer (current-buffer))
          (start (point-min))
          (end (point-max)))
      (with-temp-buffer ;; acts as the speed-type-buffer
        (speed-type-insert-wordlist content-buffer start end 250 450 20 t speed-type-transform-hook
                          (make-speed-type-transform-context
                           :major-mode 'fundamental-mode
                           :text-type 'random-wordlist
                           :start nil
                           :end nil))
        (should (equal (car (text-properties-at (point-min))) 'speed-type-orig-pos))
        (should (>= (point-max) 250))))))

(ert-deftest speed-type-test/prepare-buffer-trim-and-ignore-count-tests ()
  ;; minimal case expect count if ignore else 0, allow context nil
  (with-temp-buffer
    (insert "  ")
    (should (= 0 (caddr (speed-type-prepare-buffer (point-min) (point-max) nil nil nil nil))))
    (should (= 2 (caddr (speed-type-prepare-buffer (point-min) (point-max) nil t nil nil))))
    (should (= 0 (caddr (speed-type-prepare-buffer (point-min) (point-max) t nil nil nil)))))
  ;; in case of code we want to keep the indentation
  ;; count whitespace after trim
  (with-temp-buffer
    (insert "\n\nfoo\n\t\sbar\n\n\n")
    (should (= 3 (caddr (speed-type-prepare-buffer (point-min) (point-max) t t nil
                                         (make-speed-type-transform-context :major-mode 'emacs-lisp-mode)))))
    (should (string= "foo\n\t\sbar" (buffer-string))))
  ;; TODO comment in as soon as they have fixed the defstruct problem
  ;; (with-temp-buffer
  ;;   (insert "\n\tfoo\n\t\sbar\n\n\n\n")
  ;;   (should (= 4 (caddr (speed-type-prepare-buffer (point-min) (point-max) t t nil
  ;;                                        (make-speed-type-transform-context :major-mode 'emacs-lisp-mode)))))
  ;;   (should (string= "\tfoo\n\t\sbar" (buffer-string))))
  )
(defvar file-location nil)
(eval-and-compile
  (setq file-location (file-name-directory (or load-file-name buffer-file-name (temporary-file-directory)))))
(ert-deftest speed-type-test/setup-replay ()
  "Test that replay contains the exact same content again."
  (let ((speed-type-default-lang "de")
        (speed-type-stop-words nil)
        (speed-type-directory file-location)
        (speed-type-save-statistic-option 'never)
        (speed-type-randomize t)
        (speed-type-add-extra-words-on-error 0)
        (speed-type-add-extra-words-on-non-consecutive-errors 0))
    (dolist (cmd
             (list (list 'speed-type-text)
                   (list 'speed-type-text-top-x 50)
                   (list 'speed-type-top-x 50)
                   (list 'speed-type-top-100)
                   (list 'speed-type-top-1000)
                   ;; TODO Requires pandoc to be installed in testing-sandbox
                   ;; (list 'speed-type-pandoc "https://en.wikipedia.org/wiki/Haskell")
                   ;; (list 'speed-type-pandoc-top-x "https://en.wikipedia.org/wiki/Haskell" 50)
                   ))
      (message "Running %s..." (symbol-name (car cmd)))
      (with-current-buffer (if (cdr cmd) (apply (car cmd) (cdr cmd)) (funcall (car cmd)))
        (let ((before-content (replace-regexp-in-string "[[:space:]\n]" "" (buffer-substring-no-properties (point-min) (point-max)))))
          (insert "a")
          (with-current-buffer (speed-type-replay)
            (should (not (string-blank-p before-content)))
            (should (string= (replace-regexp-in-string "[[:space:]\n]" "" (buffer-substring-no-properties (point-min) (point-max))) before-content)))
          (speed-type-quit)))
      (message "Running %s...DONE" (symbol-name (car cmd))))))

(ert-deftest speed-type-mode-test()
  (let* ((hook-executed nil))
    (defun speed-type-mode-test-hook ()
      (setq hook-executed t))
    (unwind-protect
        (progn
          (add-hook 'speed-type-mode-hook 'speed-type-mode-test-hook)
          (with-temp-buffer
            (speed-type-mode)
            (kill-current-buffer))
          (should (equal hook-executed t)))
      (remove-hook 'speed-type-mode-hook 'speed-type-mode-test-hook))))

(defun speed-type--retrieve-non-existant-file-environment (filename-expected test)
  (let ((speed-type-directory (temporary-file-directory)))
    (unwind-protect
        (progn
          (kill-buffer (get-file-buffer filename-expected))
          (delete-file filename-expected)
          (funcall test))
      (kill-buffer (get-file-buffer filename-expected))
      (delete-file filename-expected))))

(defun speed-type--retrieve-existant-file-environment (filename-expected test)
  (let ((speed-type-directory (temporary-file-directory)))
    (unwind-protect
        (progn
          (kill-buffer (get-file-buffer filename-expected))
          (delete-file filename-expected)
          (with-temp-buffer
            (write-file filename-expected))
          (funcall test))
      (kill-buffer (get-file-buffer filename-expected))
      (delete-file filename-expected))))

(ert-deftest speed-type-test/pick-continue-error-cases ()
  "Should error if custom vars contain garbage or buffer is empty."
  ;; min or max contain something else than a number
  (should-error (speed-type--pick-continue-text-bounds "0" 0 1 100 0 nil))
  (should-error (speed-type--pick-continue-text-bounds 0 "0" 1 100 0 nil))
  (should-error (speed-type--pick-continue-text-bounds 0 0 "1" 100 0 nil))
  (should-error (speed-type--pick-continue-text-bounds 0 0 1 "100" 0 nil))
  (should-error (speed-type--pick-continue-text-bounds 0 0 1 100 "0" nil))
  (should-error (speed-type--pick-continue-text-bounds -1 0 1 100 0 nil))
  (should-error (speed-type--pick-continue-text-bounds 0 -1 1 100 0 nil))
  (should-error (speed-type--pick-continue-text-bounds 0 0 -1 100 0 nil))
  (should-error (speed-type--pick-continue-text-bounds 0 0 1 -100 0 nil))
  (should-error (speed-type--pick-continue-text-bounds 0 0 1 100 -1 nil)))

(ert-deftest speed-type-test/pick-continue-text-empty-cases ()
  "Cases in which the function should return empty string."
  (with-temp-buffer
    (insert "asdf")
    (should (string-empty-p (save-excursion (apply #'buffer-substring (speed-type--pick-continue-text-bounds 0 0 0 0 0 nil)))))
    (should (string-empty-p (save-excursion (apply #'buffer-substring (speed-type--pick-continue-text-bounds 0 0 1 100 0 nil)))))
    (should (string-empty-p (save-excursion (apply #'buffer-substring (speed-type--pick-continue-text-bounds 1 5 0 0 0 nil))))))
  (with-temp-buffer
    (should (string-empty-p (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) 1 100 0 nil))))))

(ert-deftest speed-type-test/normal-case-min-is-considered ()
  (with-temp-buffer
    (insert "The quick brown fox jumps over the lazy dog.")
    (should (string-prefix-p "The quick brown" (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) 10 20 5 t))))))

(ert-deftest speed-type-test/special-case-very-long-word-max-+-tolerance-is-not-exceeded ()
  (with-temp-buffer
    (insert "Antidisestablishmentarianism is a very long word.")
    (should (string= "Antidisestablishmentarianism" (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) 5 10 20 t))))))

(ert-deftest speed-type-test/special-case-very-very-long-word-split-to-not-exceed-max-+-tolerance ()
  (with-temp-buffer
    (insert "Supercalifragilisticexpialidocious phenomenon.")
    (should (string= "Supercalifragil" (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) 5 10 5 t))))))

(ert-deftest speed-type-test/special-case-end-is-reached-before-min-can-be-achieved ()
  (with-temp-buffer
    (insert "Short.")
    (should (string= "Short." (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) 20 30 10 t))))))

(ert-deftest speed-type-test/whitespace-case-whitespace-considered ()
  (with-temp-buffer
    (insert "This    text

has     lots
   of     whitespace.")
    (should (string-prefix-p "This    text" (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) 10 20 5 nil))))))

(ert-deftest speed-type-test/special-case-minimal-length-zero-tolerance ()
  (with-temp-buffer
    (insert "asdf")
    (should (string= "s" (apply #'buffer-substring (speed-type--pick-continue-text-bounds 2 3 1 1 0 t))))))

(ert-deftest speed-type-test/special-case-minimal-length-one-tolerance ()
  (with-temp-buffer
    (insert "asdf")
    (should (string= "as" (apply #'buffer-substring (speed-type--pick-continue-text-bounds 1 3 1 1 1 t))))))

(ert-deftest speed-type-test/special-case-tolerance-0-expect-exact-boundary-behavior ()
  (with-temp-buffer
    (insert "One two three four five six seven eight nine ten.")
    (should (string= "One two three f" (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) 15 15 0 nil))))))

(ert-deftest speed-type-test/multiple-runs-result-in-different-target-lengths ()
  (with-temp-buffer
    (insert "Lorem ipsum dolor sit amet consectetur adipiscing elit.")
    (let ((min 10)
          (max 40)
          (tolerance 5))
      (dotimes (_ 5)
        (let ((content (save-excursion (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) min max tolerance nil)))))
          (should (string-prefix-p "Lorem ipsum" content))
          (should (> (length content) min))
          (should (<= (length content) (+ max tolerance))))))))

(ert-deftest speed-type-test/special-case-leading-whitespace-at-the-start ()
  (with-temp-buffer
    (insert "      Leading whitespace should not break logic.")
    (should (string-prefix-p "      Leading whitespace" (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) 10 25 5 t))))))

(ert-deftest speed-type-test/51-words-expect-repeated-call-will-not-include-previous-chars ()
  (with-temp-buffer
    (dotimes (i 50)
      (insert (concat "Word" (number-to-string i) " ")))
    (insert "Word50.")
    (goto-char (point-min))
    ;; 51 words are 347 characters
    ;; min is 50 which leads to a maximum times of 7 (347 / 50= 6.94)
    ;; ensure function moves always forward and never takes characters from the previous call
    (let ((previous-last-char "")
          (min 50)
          (max 200)
          (tolerance 10))
      (dotimes (i 7)
        (let ((previous-point (point))
              (content (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point) (point-max) min max tolerance nil))))
          (message "Speed-type-test: Content is %s" content)
          (should (string= (buffer-substring previous-point (point)) content))

          (should (<= (length content) (+ max tolerance)))
          (unless (eobp)
            (should (> (point) (* (1+ i) min)))
            (should (> (length content) min))
            (when (string= previous-last-char (substring content 0 1))
              (message "Previous point was %d. Current point is %d. The same character is %s" previous-point (point) (substring content 0 1)))
            (should (not (string= previous-last-char (substring content 0 1))))
            (setq previous-last-char (substring content (- (length content) 1)))))))))

(ert-deftest speed-type-test/special-case-asdfasdfasdfasdfasdf1-excceds-max-but-tolerance-allows-longer-word ()
  (with-temp-buffer
    (insert "asdfasdfasdfasdfasdf1")
    (should (string= "asdfasdfasdfasdfasdf1" (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) 20 20 20 t))))))

(ert-deftest speed-type-test/special-case-1-included-even-though-it-exceeds-max-because-word-ends-within-tolerance ()
  (with-temp-buffer
    (insert "asdf asdf asdf asdf asdf1")
    (should (string= "asdf asdf asdf asdf asdf1" (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) 20 20 20 t))))))

(ert-deftest speed-type-test/special-case-1234bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb--second-last-word-and-must-be-split-because-tolerance-exceeded ()
  (with-temp-buffer
    (insert "asdf asdf 1234bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb asdf")
    (should (string= "asdf asdf 1234bbbbbbbbbbbbbbbbbbbbbbbbbb" (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) 20 20 20 nil))))))

(ert-deftest speed-type-test/special-case-1234aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-is-the-last-word-split-because-tolerance-exceeded ()
  (with-temp-buffer
    (insert "asdf asdf asdf asdf 1234aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
    (should (string= "asdf asdf asdf asdf 1234aaaaaaaaaaaaaaaa" (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) 20 20 20 nil))))))

(ert-deftest speed-type-test/special-case-1234aaaaaaaaaaaaa-is-a-whole-word-and-following-word-not-split-because-within-tolerance ()
  (with-temp-buffer
    (insert "asdf asdf asdf asdf 1234aaaaaaaaaaaaa asdf")
    (should (string= "asdf asdf asdf asdf 1234aaaaaaaaaaaaa" (apply #'buffer-substring (speed-type--pick-continue-text-bounds (point-min) (point-max) 20 20 20 t))))))

;; TODO For some reason it deletes a random buffer which is not intended
;; (ert-deftest speed-type--retrieve-test ()
;;   (let ((speed-type-directory-b speed-type-directory))
;;     (unwind-protect
;;         (progn (setq speed-type-directory "/tmp/")
;;                (let ((filename "speed-type--retrieve-test-file")
;;                      (filename-expected "/tmp/speed-type--retrieve-test-file.txt")
;;                      (url "https://www.google.com"))
;;                  (speed-type--retrieve-non-existant-file-environment
;;                   filename-expected
;;                   (lambda ()
;;                     (let ((response (speed-type--retrieve filename url)))
;;                       (should (bufferp response))
;;                       (should (string= (buffer-file-name response) filename-expected))
;;                       (should (file-exists-p filename-expected))
;;                       (should (file-readable-p filename-expected)))))
;;                  (speed-type--retrieve-existant-file-environment
;;                   filename-expected
;;                   (lambda ()
;;                     (let ((response (speed-type--retrieve filename url)))
;;                       (should (bufferp response))
;;                       (should (string= (buffer-file-name response) filename-expected))
;;                       (should (file-exists-p filename-expected))
;;                       (should (file-readable-p filename-expected))))))
;;                (let ((filename "speed-type--retrieve-test-file")
;;                      (filename-expected "/tmp/speed-type--retrieve-test-file.txt")
;;                      (url "https://www.google.com/nonexitanresource"))
;;                  (speed-type--retrieve-non-existant-file-environment
;;                   filename-expected
;;                   (lambda () (should-error (speed-type--retrieve filename url))))))
;;       (setq speed-type-directory speed-type-directory-b))))
;;; speed-type-test.el ends here
