[tests] moved tests to Org files

Upside - no more jumping between definitions and tests

Downside - if we ship the Org file, users will have to download some more bytes
This commit is contained in:
contrapunctus 2021-02-22 18:02:39 +05:30
parent 36e17fb4d7
commit 3ea6df7f20
8 changed files with 236 additions and 514 deletions

View File

@ -38,6 +38,7 @@
keys)
plist))
(defun chronometrist-history-prep (key history-table)
"Prepare history hash tables for use in prompts.
Each value in hash table TABLE must be a list. Each value will be
@ -119,6 +120,7 @@ HISTORY-TABLE must be a hash table. (see `chronometrist-tags-history')"
history-table))))
(chronometrist-history-prep task history-table))
(defvar chronometrist--tag-suggestions nil
"Suggestions for tags.
Used as history by `chronometrist-tags-prompt'.")
@ -179,7 +181,8 @@ _ARGS are ignored. This function always returns t, so it can be
used in `chronometrist-before-out-functions'."
(let* ((last-expr (chronometrist-last))
(last-name (plist-get last-expr :name))
(_history (chronometrist-tags-history-populate last-name chronometrist-tags-history chronometrist-file))
(_history (chronometrist-tags-history-populate last-name
chronometrist-tags-history chronometrist-file))
(last-tags (plist-get last-expr :tags))
(input (->> (chronometrist-maybe-symbol-to-string last-tags)
(-interpose ",")
@ -191,7 +194,9 @@ used in `chronometrist-before-out-functions'."
(reverse it)
(cl-remove-duplicates it :test #'equal)
(reverse it)
(chronometrist-plist-update it nil)))
(list :tags it)
(chronometrist-plist-update (chronometrist-sexp-last) it)
(chronometrist-sexp-replace-last it)))
t))
(defgroup chronometrist-key-values nil
@ -232,6 +237,7 @@ HISTORY-TABLE must be a hash table (see `chronometrist-key-history')."
history-table))))
(chronometrist-history-prep task history-table))
(defvar chronometrist-value-history
(make-hash-table :test #'equal)
"Hash table to store previously-used values for user-keys.
@ -261,6 +267,7 @@ HISTORY-TABLE must be a hash table. (see `chronometrist-value-history')"
(chronometrist-history-prep key history-table))
history-table))
(defvar chronometrist--value-suggestions nil
"Suggestions for values.
Used as history by `chronometrist-value-prompt'.")
@ -389,7 +396,8 @@ used in `chronometrist-before-out-functions'."
(setq user-kv-expr (ignore-errors (read (current-buffer))))
(kill-buffer chronometrist-kv-buffer-name))
(if user-kv-expr
(chronometrist-plist-update nil user-kv-expr)
(chronometrist-sexp-replace-last
(chronometrist-plist-update (chronometrist-sexp-last) user-kv-expr))
(chronometrist-refresh))))
(defun chronometrist-kv-reject ()

View File

@ -1,5 +1,5 @@
#+TODO: TODO WIP | REVIEW
#+PROPERTY: header-args :tangle yes
#+PROPERTY: header-args :tangle yes :load yes
* chronometrist-key-values
** TODO [50%]
@ -86,6 +86,38 @@ Further details are stored in properties -
keys)
plist))
#+END_SRC
#+BEGIN_SRC emacs-lisp :load test
(ert-deftest chronometrist-plist-remove ()
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :a)
'(:b 2 :c 3 :d 4)))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :b)
'(:a 1 :c 3 :d 4)))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :c)
'(:a 1 :b 2 :d 4)))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :d)
'(:a 1 :b 2 :c 3)))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :a :b)
'(:c 3 :d 4)))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :a :d)
'(:b 2 :c 3)))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :c :d)
'(:a 1 :b 2)))
(should (equal
(chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :a :b :c :d)
nil))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :d :a)
'(:b 2 :c 3))))
#+END_SRC
**** chronometrist-history-prep :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-history-prep (key history-table)
@ -185,6 +217,27 @@ HISTORY-TABLE must be a hash table. (see `chronometrist-tags-history')"
history-table))))
(chronometrist-history-prep task history-table))
#+END_SRC
***** tests
#+BEGIN_SRC emacs-lisp :load test
(ert-deftest chronometrist-tags-history ()
(progn
(clrhash chronometrist-tags-history)
(cl-loop for task in '("Guitar" "Programming") do
(chronometrist-tags-history-populate task chronometrist-tags-history "test.sexp")))
(should
(= (hash-table-count chronometrist-tags-history) 2))
(should
(cl-loop for task being the hash-keys of chronometrist-tags-history
always (stringp task)))
(should
(equal (gethash "Guitar" chronometrist-tags-history)
'((classical solo)
(classical warm-up))))
(should
(equal (gethash "Programming" chronometrist-tags-history)
'((reading) (bug-hunting)))))
#+END_SRC
**** chronometrist--tag-suggestions :variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist--tag-suggestions nil
@ -257,7 +310,8 @@ _ARGS are ignored. This function always returns t, so it can be
used in `chronometrist-before-out-functions'."
(let* ((last-expr (chronometrist-last))
(last-name (plist-get last-expr :name))
(_history (chronometrist-tags-history-populate last-name chronometrist-tags-history chronometrist-file))
(_history (chronometrist-tags-history-populate last-name
chronometrist-tags-history chronometrist-file))
(last-tags (plist-get last-expr :tags))
(input (->> (chronometrist-maybe-symbol-to-string last-tags)
(-interpose ",")
@ -269,7 +323,9 @@ used in `chronometrist-before-out-functions'."
(reverse it)
(cl-remove-duplicates it :test #'equal)
(reverse it)
(chronometrist-plist-update it nil)))
(list :tags it)
(chronometrist-plist-update (chronometrist-sexp-last) it)
(chronometrist-sexp-replace-last it)))
t))
#+END_SRC
*** Key-Values
@ -322,6 +378,17 @@ HISTORY-TABLE must be a hash table (see `chronometrist-key-history')."
history-table))))
(chronometrist-history-prep task history-table))
#+END_SRC
***** tests
#+BEGIN_SRC emacs-lisp :load test
(ert-deftest chronometrist-key-history ()
(progn
(clrhash chronometrist-key-history)
(cl-loop for task in '("Programming" "Arrangement/new edition") do
(chronometrist-key-history-populate task chronometrist-key-history "test.sexp")))
(should (= (hash-table-count chronometrist-key-history) 2))
(should (= (length (gethash "Programming" chronometrist-key-history)) 3))
(should (= (length (gethash "Arrangement/new edition" chronometrist-key-history)) 2)))
#+END_SRC
**** chronometrist-value-history :variable:
:PROPERTIES:
:VALUE: hash table
@ -359,6 +426,18 @@ HISTORY-TABLE must be a hash table. (see `chronometrist-value-history')"
(chronometrist-history-prep key history-table))
history-table))
#+END_SRC
***** tests
#+BEGIN_SRC emacs-lisp :load test
(ert-deftest chronometrist-value-history ()
(progn
(clrhash chronometrist-value-history)
(chronometrist-value-history-populate chronometrist-value-history "test.sexp"))
(should (= (hash-table-count chronometrist-value-history) 5))
(should
(cl-loop for task being the hash-keys of chronometrist-value-history
always (stringp task))))
#+END_SRC
**** chronometrist--value-suggestions :variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist--value-suggestions nil
@ -505,7 +584,8 @@ used in `chronometrist-before-out-functions'."
(setq user-kv-expr (ignore-errors (read (current-buffer))))
(kill-buffer chronometrist-kv-buffer-name))
(if user-kv-expr
(chronometrist-plist-update nil user-kv-expr)
(chronometrist-sexp-replace-last
(chronometrist-plist-update (chronometrist-sexp-last) user-kv-expr))
(chronometrist-refresh))))
#+END_SRC
**** chronometrist-kv-reject :command:
@ -598,7 +678,7 @@ Return t, to permit use in `chronometrist-before-out-functions'."
t))
#+END_SRC
**** WIP chronometrist-kv-prompt-helper :function:
#+BEGIN_SRC emacs-lisp :tangle no
#+BEGIN_SRC emacs-lisp :tangle no :load no
(defun chronometrist-kv-prompt-helper (mode task)
(let ((table (case mode
(:tag chronometrist-tags-history)

View File

@ -1,3 +1,5 @@
;;; chronometrist.el --- A time tracker with a nice interface -*- lexical-binding: t; -*-
;; Author: contrapunctus <xmpp:contrapunctus@jabber.fr>
@ -66,6 +68,8 @@
(defvar chronometrist-mode-map)
(require 'subr-x))
(defcustom chronometrist-sexp-pretty-print-function #'chronometrist-plist-pp
"Function used to pretty print plists in `chronometrist-file'.
Like `pp', it must accept an OBJECT and optionally a
@ -344,6 +348,7 @@ considers it an alist."
'not-plist)))
(null list))
(defun chronometrist-plist-pp-longest-keyword-length ()
"Find the length of the longest keyword in a plist.
This assumes there is a single plist in the current buffer, and
@ -391,6 +396,7 @@ The list must be on a single line, as emitted by `prin1'."
(when (not (eobp))
(forward-char)))))
(defun chronometrist-plist-pp-buffer-plist (&optional inside-sublist-p)
"Indent a single plist after point."
(down-list)
@ -1113,100 +1119,6 @@ value of `revert-buffer-function'."
(chronometrist-maybe-start-timer)
(set-window-point window point)))))
(defvar chronometrist--file-state nil
"List containing the state of `chronometrist-file'.
`chronometrist-refresh-file' sets this to a plist in the form
\(:last (LAST-START LAST-END) :rest (REST-START REST-END HASH))
\(see `chronometrist-file-hash')
LAST-START and LAST-END represent the start and the end of the
last s-expression.
REST-START and REST-END represent the start of the file and the
end of the second-last s-expression.")
(defun chronometrist-file-hash (&optional start end hash)
"Calculate hash of `chronometrist-file' between START and END.
START can be
a number or marker,
:before-last - the position at the start of the last s-expression
nil or any other value - the value of `point-min'.
END can be
a number or marker,
:before-last - the position at the end of the second-last s-expression,
nil or any other value - the position at the end of the last s-expression.
Return (START END) if HASH is nil, else (START END HASH).
Return a list in the form (A B HASH), where A and B are markers
in `chronometrist-file' describing the region for which HASH was calculated."
(chronometrist-sexp-in-file chronometrist-file
(let* ((start (cond ((number-or-marker-p start) start)
((eq :before-last start)
(goto-char (point-max))
(backward-list))
(t (point-min))))
(end (cond ((number-or-marker-p end) end)
((eq :before-last end)
(goto-char (point-max))
(backward-list 2)
(forward-list))
(t (goto-char (point-max))
(backward-list)
(forward-list)))))
(if hash
(--> (buffer-substring-no-properties start end)
(secure-hash 'sha1 it)
(list start end it))
(list start end)))))
(defun chronometrist-read-from (position)
(chronometrist-sexp-in-file chronometrist-file
(goto-char (if (number-or-marker-p position)
position
(funcall position)))
(ignore-errors (read (current-buffer)))))
(defun chronometrist-file-change-type (state)
"Determine the type of change made to `chronometrist-file'.
STATE must be a plist. (see `chronometrist--file-state')
Return
:append if a new s-expression was added to the end,
:modify if the last s-expression was modified,
:remove if the last s-expression was removed,
nil if the contents didn't change, and
t for any other change."
(-let*
(((last-start last-end) (plist-get state :last))
((rest-start rest-end rest-hash) (plist-get state :rest))
(last-expr-file (chronometrist-read-from last-start))
(last-expr-ht (chronometrist-events-last))
(last-same-p (equal last-expr-ht last-expr-file))
(file-new-length (chronometrist-sexp-in-file chronometrist-file (point-max)))
(rest-same-p (unless (< file-new-length rest-end)
(--> (chronometrist-file-hash rest-start rest-end t)
(cl-third it)
(equal rest-hash it)))))
;; (message "chronometrist - last-start\nlast-expr-file - %S\nlast-expr-ht - %S"
;; last-expr-file
;; last-expr-ht)
;; (message "chronometrist - last-same-p - %S, rest-same-p - %S"
;; last-same-p rest-same-p)
(cond ((not rest-same-p) t)
(last-same-p
(when (chronometrist-read-from last-end) :append))
((not (chronometrist-read-from last-start))
:remove)
((not (chronometrist-read-from
(lambda ()
(progn (goto-char last-start)
(forward-list)))))
:modify))))
(defun chronometrist-refresh-file (fs-event)
"Re-read `chronometrist-file' and refresh the `chronometrist' buffer.
Argument _FS-EVENT is ignored."

View File

@ -572,6 +572,14 @@ considers it an alist."
'not-plist)))
(null list))
#+END_SRC
***** tests
#+BEGIN_SRC emacs-lisp :load test
(ert-deftest plist-p ()
(should (eq t (chronometrist-plist-pp-plist-p '(:a 1 :b 2))))
(should (eq nil (chronometrist-plist-pp-plist-p '(0 :a 1 :b 2))))
(should (eq nil (chronometrist-plist-pp-plist-p '(:a 1 :b 2 3)))))
#+END_SRC
**** longest-keyword-length :reader:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-plist-pp-longest-keyword-length ()
@ -627,6 +635,102 @@ The list must be on a single line, as emitted by `prin1'."
(when (not (eobp))
(forward-char)))))
#+END_SRC
***** tests
#+BEGIN_SRC emacs-lisp :load test
(ert-deftest plist-pp-buffer ()
(should
(equal
(chronometrist-plist-pp-to-string
'(:name "Task"
:tags (foo bar)
:comment ((70 . "baz")
"zot"
(16 . "frob")
(20 20 "quux"))
:start "2020-06-25T19:27:57+0530"
:stop "2020-06-25T19:43:30+0530"))
(concat
"(:name \"Task\"\n"
" :tags (foo bar)\n"
" :comment ((70 . \"baz\")\n"
" \"zot\"\n"
" (16 . \"frob\")\n"
" (20 20 \"quux\"))\n"
" :start \"2020-06-25T19:27:57+0530\"\n"
" :stop \"2020-06-25T19:43:30+0530\")")))
(should
(equal
(chronometrist-plist-pp-to-string
'(:name "Singing"
:tags (classical solo)
:piece ((:composer "Gioachino Rossini"
:name "Il barbiere di Siviglia"
:aria ("All'idea di quel metallo" "Dunque io son"))
(:composer "Ralph Vaughan Williams"
:name "Songs of Travel"
:movement ((4 . "Youth and Love")
(5 . "In Dreams")
(7 . "Wither Must I Wander?")))
(:composer "Ralph Vaughan Williams"
:name "Merciless Beauty"
:movement 1)
(:composer "Franz Schubert"
:name "Winterreise"
:movement ((1 . "Gute Nacht")
(2 . "Die Wetterfahne")
(4 . "Erstarrung"))))
:start "2020-11-01T12:01:20+0530"
:stop "2020-11-01T13:08:32+0530"))
(concat
"(:name \"Singing\"\n"
" :tags (classical solo)\n"
" :piece ((:composer \"Gioachino Rossini\"\n"
" :name \"Il barbiere di Siviglia\"\n"
" :aria (\"All'idea di quel metallo\" \"Dunque io son\"))\n"
" (:composer \"Ralph Vaughan Williams\"\n"
" :name \"Songs of Travel\"\n"
" :movement ((4 . \"Youth and Love\")\n"
" (5 . \"In Dreams\")\n"
" (7 . \"Wither Must I Wander?\")))\n"
" (:composer \"Ralph Vaughan Williams\"\n"
" :name \"Merciless Beauty\"\n"
" :movement 1)\n"
" (:composer \"Franz Schubert\"\n"
" :name \"Winterreise\"\n"
" :movement ((1 . \"Gute Nacht\")\n"
" (2 . \"Die Wetterfahne\")\n"
" (4 . \"Erstarrung\"))))\n"
" :start \"2020-11-01T12:01:20+0530\"\n"
" :stop \"2020-11-01T13:08:32+0530\")")))
(should (equal
(chronometrist-plist-pp-to-string
'(:name "Cooking"
:tags (lunch)
:recipe (:name "moong-masoor ki dal"
:url "https://www.mirchitales.com/moong-masoor-dal-red-and-yellow-lentil-curry/")
:start "2020-09-23T15:22:39+0530"
:stop "2020-09-23T16:29:49+0530"))
(concat
"(:name \"Cooking\"\n"
" :tags (lunch)\n"
" :recipe (:name \"moong-masoor ki dal\"\n"
" :url \"https://www.mirchitales.com/moong-masoor-dal-red-and-yellow-lentil-curry/\")\n"
" :start \"2020-09-23T15:22:39+0530\"\n"
" :stop \"2020-09-23T16:29:49+0530\")")))
(should (equal
(chronometrist-plist-pp-to-string
'(:name "Exercise"
:tags (warm-up)
:start "2018-11-21T15:35:04+0530"
:stop "2018-11-21T15:38:41+0530"
:comment ("stretching" (25 10 "push-ups"))))
(concat
"(:name \"Exercise\"\n"
" :tags (warm-up)\n"
" :start \"2018-11-21T15:35:04+0530\"\n"
" :stop \"2018-11-21T15:38:41+0530\"\n"
" :comment (\"stretching\" (25 10 \"push-ups\")))"))))
#+END_SRC
**** buffer-plist :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-plist-pp-buffer-plist (&optional inside-sublist-p)
@ -1579,123 +1683,6 @@ value of `revert-buffer-function'."
(chronometrist-maybe-start-timer)
(set-window-point window point)))))
#+END_SRC
***** file-state :internal:variable:
:PROPERTIES:
:VALUE: list
:END:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist--file-state nil
"List containing the state of `chronometrist-file'.
`chronometrist-refresh-file' sets this to a plist in the form
\(:last (LAST-START LAST-END) :rest (REST-START REST-END HASH))
\(see `chronometrist-file-hash')
LAST-START and LAST-END represent the start and the end of the
last s-expression.
REST-START and REST-END represent the start of the file and the
end of the second-last s-expression.")
#+END_SRC
***** file-hash :reader:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-file-hash (&optional start end hash)
"Calculate hash of `chronometrist-file' between START and END.
START can be
a number or marker,
:before-last - the position at the start of the last s-expression
nil or any other value - the value of `point-min'.
END can be
a number or marker,
:before-last - the position at the end of the second-last s-expression,
nil or any other value - the position at the end of the last s-expression.
Return (START END) if HASH is nil, else (START END HASH).
Return a list in the form (A B HASH), where A and B are markers
in `chronometrist-file' describing the region for which HASH was calculated."
(chronometrist-sexp-in-file chronometrist-file
(let* ((start (cond ((number-or-marker-p start) start)
((eq :before-last start)
(goto-char (point-max))
(backward-list))
(t (point-min))))
(end (cond ((number-or-marker-p end) end)
((eq :before-last end)
(goto-char (point-max))
(backward-list 2)
(forward-list))
(t (goto-char (point-max))
(backward-list)
(forward-list)))))
(if hash
(--> (buffer-substring-no-properties start end)
(secure-hash 'sha1 it)
(list start end it))
(list start end)))))
#+END_SRC
***** read-from :reader:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-read-from (position)
(chronometrist-sexp-in-file chronometrist-file
(goto-char (if (number-or-marker-p position)
position
(funcall position)))
(ignore-errors (read (current-buffer)))))
#+END_SRC
***** file-change-type :reader:
The initial idea was to use two hashes, one for the content between the start of the file up to the last expression, and the other for the last expression itself. However, in the latter case, this can cause issues -
+ the expression may shrink, and if we try to compute the hash of the previously-known region again, we will get an args-out-of-range error.
+ false negatives for whitespace/indentation differences.
Possible states
: <rest-start> <rest-end> <last-start> <last-end>
1. :append - rest same, last same, new expr after last-end
2. :modify - rest same, last not same, no expr after last-end
3. :remove - rest same, last not same, no expr after last-start
4. nil - rest same, last same, no expr after last-end
5. t - rest changed
#+BEGIN_SRC emacs-lisp
(defun chronometrist-file-change-type (state)
"Determine the type of change made to `chronometrist-file'.
STATE must be a plist. (see `chronometrist--file-state')
Return
:append if a new s-expression was added to the end,
:modify if the last s-expression was modified,
:remove if the last s-expression was removed,
nil if the contents didn't change, and
t for any other change."
(-let*
(((last-start last-end) (plist-get state :last))
((rest-start rest-end rest-hash) (plist-get state :rest))
(last-expr-file (chronometrist-read-from last-start))
(last-expr-ht (chronometrist-events-last))
(last-same-p (equal last-expr-ht last-expr-file))
(file-new-length (chronometrist-sexp-in-file chronometrist-file (point-max)))
(rest-same-p (unless (< file-new-length rest-end)
(--> (chronometrist-file-hash rest-start rest-end t)
(cl-third it)
(equal rest-hash it)))))
;; (message "chronometrist - last-start\nlast-expr-file - %S\nlast-expr-ht - %S"
;; last-expr-file
;; last-expr-ht)
;; (message "chronometrist - last-same-p - %S, rest-same-p - %S"
;; last-same-p rest-same-p)
(cond ((not rest-same-p) t)
(last-same-p
(when (chronometrist-read-from last-end) :append))
((not (chronometrist-read-from last-start))
:remove)
((not (chronometrist-read-from
(lambda ()
(progn (goto-char last-start)
(forward-list)))))
:modify))))
#+END_SRC
***** refresh-file :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-refresh-file (fs-event)
@ -2484,34 +2471,34 @@ value of `revert-buffer-function'."
#+END_SRC
***** statistics-mode :major:mode:
#+BEGIN_SRC emacs-lisp
(define-derived-mode chronometrist-statistics-mode tabulated-list-mode "Chronometrist-Statistics"
"Major mode for `chronometrist-statistics'."
(make-local-variable 'tabulated-list-format)
(setq tabulated-list-format
[("Task" 25 t)
("Active days" 12 t)
("%% of days active" 17 t)
("Average time" 12 t)
;; ("Current streak" 10 t)
;; ("Last streak" 10 t)
;; ("Longest streak" 10 t)
])
(make-local-variable 'tabulated-list-entries)
(setq tabulated-list-entries 'chronometrist-statistics-entries)
(make-local-variable 'tabulated-list-sort-key)
(setq tabulated-list-sort-key '("Task" . nil))
(tabulated-list-init-header)
;; (chronometrist-maybe-start-timer)
(add-hook 'chronometrist-timer-hook
(lambda ()
(when (get-buffer-window chronometrist-statistics-buffer-name)
(chronometrist-statistics-refresh))))
(setq revert-buffer-function #'chronometrist-statistics-refresh)
(unless chronometrist--fs-watch
(setq chronometrist--fs-watch
(file-notify-add-watch chronometrist-file
'(change)
#'chronometrist-refresh-file))))
(define-derived-mode chronometrist-statistics-mode tabulated-list-mode "Chronometrist-Statistics"
"Major mode for `chronometrist-statistics'."
(make-local-variable 'tabulated-list-format)
(setq tabulated-list-format
[("Task" 25 t)
("Active days" 12 t)
("%% of days active" 17 t)
("Average time" 12 t)
;; ("Current streak" 10 t)
;; ("Last streak" 10 t)
;; ("Longest streak" 10 t)
])
(make-local-variable 'tabulated-list-entries)
(setq tabulated-list-entries 'chronometrist-statistics-entries)
(make-local-variable 'tabulated-list-sort-key)
(setq tabulated-list-sort-key '("Task" . nil))
(tabulated-list-init-header)
;; (chronometrist-maybe-start-timer)
(add-hook 'chronometrist-timer-hook
(lambda ()
(when (get-buffer-window chronometrist-statistics-buffer-name)
(chronometrist-statistics-refresh))))
(setq revert-buffer-function #'chronometrist-statistics-refresh)
(unless chronometrist--fs-watch
(setq chronometrist--fs-watch
(file-notify-add-watch chronometrist-file
'(change)
#'chronometrist-refresh-file))))
#+END_SRC
***** chronometrist-statistics :command:
#+BEGIN_SRC emacs-lisp
@ -2538,8 +2525,8 @@ specified by `chronometrist-statistics--ui-state'."
(t ;; (delete-other-windows)
(unless preserve-state
(setq chronometrist-statistics--ui-state `(:mode week
:start ,week-start
:end ,week-end)))
:start ,week-start
:end ,week-end)))
(chronometrist-common-create-file)
(chronometrist-statistics-mode)
(switch-to-buffer buffer)

View File

@ -1,90 +0,0 @@
;; -*- lexical-binding: t; -*-
(require 'chronometrist)
(defvar chronometrist-test-file
;; `buffer-file-name' returns nil when Emacs is run in batch mode
(concat (or (ignore-errors (file-name-directory (buffer-file-name)))
default-directory)
"test.sexp"))
(ert-deftest task-list ()
(let ((task-list (chronometrist-task-list)))
(should (listp task-list))
(should (seq-every-p #'stringp task-list))))
(ert-deftest file-hash ()
(-let* ((chronometrist-file chronometrist-test-file)
((last-start last-end)
(chronometrist-file-hash :before-last nil))
((rest-start rest-end rest-hash)
(chronometrist-file-hash nil :before-last t)))
(should (= 1 rest-start))
(should (= 1247 rest-end))
(should (= 1249 last-start))
(should (= 1419 last-end))))
(defmacro chronometrist-tests--change-type-and-update (state)
`(prog1 (chronometrist-file-change-type ,state)
(setq ,state
(list :last (chronometrist-file-hash :before-last nil)
:rest (chronometrist-file-hash nil :before-last t)))))
;; ;; TODO
;; add newline after last expression and save => nil
;; remove newline after last expession and save => nil
(ert-deftest file-change-type ()
(let* ((chronometrist-file chronometrist-test-file)
(test-contents (with-current-buffer (find-file-noselect chronometrist-file)
(buffer-substring (point-min) (point-max))))
(chronometrist--file-state-old chronometrist--file-state)
(chronometrist--file-state (list :last (chronometrist-file-hash :before-last nil)
:rest (chronometrist-file-hash nil :before-last t)))
(chronometrist-events-old chronometrist-events))
(chronometrist-events-populate)
(unwind-protect
(progn
(should (eq nil (chronometrist-file-change-type chronometrist--file-state)))
(should (eq :append
(progn
(chronometrist-sexp-new
'(:name "Append Test"
:start "2021-02-01T13:06:46+0530"
:stop "2021-02-01T13:06:49+0530"))
(chronometrist-tests--change-type-and-update chronometrist--file-state))))
(should (eq :modify
(progn
(chronometrist-sexp-replace-last
'(:name "Modify Test"
:tags (some tags)
:start "2021-02-01T13:06:46+0530"
:stop "2021-02-01T13:06:49+0530"))
(chronometrist-tests--change-type-and-update chronometrist--file-state))))
(should (eq :remove
(progn
(chronometrist-sexp-in-file chronometrist-file
(goto-char (point-max))
(backward-list 1)
(chronometrist-sexp-delete-list 1)
(save-buffer))
(chronometrist-tests--change-type-and-update chronometrist--file-state))))
(should (eq t
(progn
(chronometrist-sexp-in-file chronometrist-file
(goto-char (point-min))
(chronometrist-plist-pp '(:name "Other Change Test"
:start "2021-02-02T17:39:40+0530"
:stop "2021-02-02T17:39:44+0530")
(current-buffer))
(save-buffer))
(chronometrist-tests--change-type-and-update chronometrist--file-state)))))
(with-current-buffer (find-file-noselect chronometrist-file)
(delete-region (point-min) (point-max))
(insert test-contents)
(save-buffer))
(setq chronometrist--file-state chronometrist--file-state-old
chronometrist-events chronometrist-events-old))))
;; Local Variables:
;; nameless-current-name: "chronometrist"
;; End:

View File

@ -1,70 +0,0 @@
(require 'chronometrist-key-values)
(ert-deftest chronometrist-plist-remove ()
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :a)
'(:b 2 :c 3 :d 4)))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :b)
'(:a 1 :c 3 :d 4)))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :c)
'(:a 1 :b 2 :d 4)))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :d)
'(:a 1 :b 2 :c 3)))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :a :b)
'(:c 3 :d 4)))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :a :d)
'(:b 2 :c 3)))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :c :d)
'(:a 1 :b 2)))
(should (equal
(chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :a :b :c :d)
nil))
(should
(equal (chronometrist-plist-remove '(:a 1 :b 2 :c 3 :d 4) :d :a)
'(:b 2 :c 3))))
(ert-deftest chronometrist-tags-history ()
(progn
(clrhash chronometrist-tags-history)
(cl-loop for task in '("Guitar" "Programming") do
(chronometrist-tags-history-populate task chronometrist-tags-history "test.sexp")))
(should
(= (hash-table-count chronometrist-tags-history) 2))
(should
(cl-loop for task being the hash-keys of chronometrist-tags-history
always (stringp task)))
(should
(equal (gethash "Guitar" chronometrist-tags-history)
'((classical solo)
(classical warm-up))))
(should
(equal (gethash "Programming" chronometrist-tags-history)
'((reading) (bug-hunting)))))
(ert-deftest chronometrist-key-history ()
(progn
(clrhash chronometrist-key-history)
(cl-loop for task in '("Programming" "Arrangement/new edition") do
(chronometrist-key-history-populate task chronometrist-key-history "test.sexp")))
(should (= (hash-table-count chronometrist-key-history) 2))
(should (= (length (gethash "Programming" chronometrist-key-history)) 3))
(should (= (length (gethash "Arrangement/new edition" chronometrist-key-history)) 2)))
(ert-deftest chronometrist-value-history ()
(progn
(clrhash chronometrist-value-history)
(chronometrist-value-history-populate chronometrist-value-history "test.sexp"))
(should (= (hash-table-count chronometrist-value-history) 5))
(should
(cl-loop for task being the hash-keys of chronometrist-value-history
always (stringp task))))
;; Local Variables:
;; nameless-current-name: "chronometrist"
;; End:

View File

@ -1,105 +0,0 @@
;; -*- lexical-binding: t; -*-
(require 'chronometrist)
(ert-deftest plist-p ()
(should (eq t (chronometrist-plist-pp-plist-p '(:a 1 :b 2))))
(should (eq nil (chronometrist-plist-pp-plist-p '(0 :a 1 :b 2))))
(should (eq nil (chronometrist-plist-pp-plist-p '(:a 1 :b 2 3)))))
(ert-deftest plist-pp-buffer ()
(should
(equal
(chronometrist-plist-pp-to-string
'(:name "Task"
:tags (foo bar)
:comment ((70 . "baz")
"zot"
(16 . "frob")
(20 20 "quux"))
:start "2020-06-25T19:27:57+0530"
:stop "2020-06-25T19:43:30+0530"))
(concat
"(:name \"Task\"\n"
" :tags (foo bar)\n"
" :comment ((70 . \"baz\")\n"
" \"zot\"\n"
" (16 . \"frob\")\n"
" (20 20 \"quux\"))\n"
" :start \"2020-06-25T19:27:57+0530\"\n"
" :stop \"2020-06-25T19:43:30+0530\")")))
(should
(equal
(chronometrist-plist-pp-to-string
'(:name "Singing"
:tags (classical solo)
:piece ((:composer "Gioachino Rossini"
:name "Il barbiere di Siviglia"
:aria ("All'idea di quel metallo" "Dunque io son"))
(:composer "Ralph Vaughan Williams"
:name "Songs of Travel"
:movement ((4 . "Youth and Love")
(5 . "In Dreams")
(7 . "Wither Must I Wander?")))
(:composer "Ralph Vaughan Williams"
:name "Merciless Beauty"
:movement 1)
(:composer "Franz Schubert"
:name "Winterreise"
:movement ((1 . "Gute Nacht")
(2 . "Die Wetterfahne")
(4 . "Erstarrung"))))
:start "2020-11-01T12:01:20+0530"
:stop "2020-11-01T13:08:32+0530"))
(concat
"(:name \"Singing\"\n"
" :tags (classical solo)\n"
" :piece ((:composer \"Gioachino Rossini\"\n"
" :name \"Il barbiere di Siviglia\"\n"
" :aria (\"All'idea di quel metallo\" \"Dunque io son\"))\n"
" (:composer \"Ralph Vaughan Williams\"\n"
" :name \"Songs of Travel\"\n"
" :movement ((4 . \"Youth and Love\")\n"
" (5 . \"In Dreams\")\n"
" (7 . \"Wither Must I Wander?\")))\n"
" (:composer \"Ralph Vaughan Williams\"\n"
" :name \"Merciless Beauty\"\n"
" :movement 1)\n"
" (:composer \"Franz Schubert\"\n"
" :name \"Winterreise\"\n"
" :movement ((1 . \"Gute Nacht\")\n"
" (2 . \"Die Wetterfahne\")\n"
" (4 . \"Erstarrung\"))))\n"
" :start \"2020-11-01T12:01:20+0530\"\n"
" :stop \"2020-11-01T13:08:32+0530\")")))
(should (equal
(chronometrist-plist-pp-to-string
'(:name "Cooking"
:tags (lunch)
:recipe (:name "moong-masoor ki dal"
:url "https://www.mirchitales.com/moong-masoor-dal-red-and-yellow-lentil-curry/")
:start "2020-09-23T15:22:39+0530"
:stop "2020-09-23T16:29:49+0530"))
(concat
"(:name \"Cooking\"\n"
" :tags (lunch)\n"
" :recipe (:name \"moong-masoor ki dal\"\n"
" :url \"https://www.mirchitales.com/moong-masoor-dal-red-and-yellow-lentil-curry/\")\n"
" :start \"2020-09-23T15:22:39+0530\"\n"
" :stop \"2020-09-23T16:29:49+0530\")")))
(should (equal
(chronometrist-plist-pp-to-string
'(:name "Exercise"
:tags (warm-up)
:start "2018-11-21T15:35:04+0530"
:stop "2018-11-21T15:38:41+0530"
:comment ("stretching" (25 10 "push-ups"))))
(concat
"(:name \"Exercise\"\n"
" :tags (warm-up)\n"
" :start \"2018-11-21T15:35:04+0530\"\n"
" :stop \"2018-11-21T15:38:41+0530\"\n"
" :comment (\"stretching\" (25 10 \"push-ups\")))"))))
;; Local Variables:
;; nameless-current-name: "chronometrist-plist-pp"
;; End: