diff options
Diffstat (limited to 'seam-test.el')
-rw-r--r-- | seam-test.el | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/seam-test.el b/seam-test.el new file mode 100644 index 0000000..e2ff971 --- /dev/null +++ b/seam-test.el @@ -0,0 +1,402 @@ +;;; seam-test.el --- Tests for Seam -*- lexical-binding: t -*- + +;; Copyright (c) 2025 Spencer Williams + +;; Author: Spencer Williams <spnw@plexwave.org> + +;; SPDX-License-Identifier: GPL-3.0-or-later + +;; This file is not part of GNU Emacs. + +;; 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: + +;; Tests for Seam. + +;;; Code: + +;;; FIXME: Tests can fail if certain buffer names are already in use +;;; on the test runner's system. + +(require 'seam) +(require 'seam-export) +(require 'ert) +(require 'cl-lib) + +(defvar seam-test-directory nil) + +(defmacro seam-test-environment (&rest body) + (declare (indent 0)) + `(let* ((seam-test-directory (make-temp-file "seam-test" t)) + (seam-note-directory seam-test-directory) + (default-directory seam-test-directory) + (seam-note-types '("private" "public")) + (seam-default-note-type "private") + (seam-title-formatter (lambda (title _type) title)) + (seam-export-template-file nil) + (seam-export-template-string seam-export-default-template-string) + (seam-export-alist + `((,(file-name-concat seam-test-directory "html") + :types ("public") + :root-path "/"))) + ;; Manually install hooks in test directory. + (dir-locals-directory-cache + (cons (list seam-test-directory 'seam-note-directory nil) + dir-locals-directory-cache))) + (unwind-protect + (progn ,@body) + (delete-directory seam-test-directory t)))) + +(defmacro seam-test-with-notes (options varlist &rest body) + (declare (indent 2)) + `(seam-test-environment + (let ,options + (let ,(cl-loop for (name . args) in varlist + collect `(,name (seam-make-note ,@args))) + (unwind-protect (progn ,@body) + (mapcar #'kill-buffer (list ,@(mapcar #'car varlist)))))))) + +(defun seam-test-remove-testdir (filename) + (string-remove-prefix (concat seam-test-directory "/") filename)) + +(defun seam-test-list-files () + (mapcar + #'seam-test-remove-testdir + (directory-files-recursively seam-test-directory ""))) + +(defun seam-test-add-contents (buffer contents) + (with-current-buffer buffer + (insert contents) + (insert "\n") + (save-buffer))) + +(defun seam-test-replace-contents (buffer contents) + (with-current-buffer buffer + (erase-buffer) + (insert contents) + (insert "\n") + (save-buffer))) + +(defun seam-test-link-to-buffer (buffer) + (format "[[seam:%s]]" (file-name-base (buffer-file-name buffer)))) + +(defun seam-test-links-from-html (file) + (with-temp-buffer + (insert-file-contents file) + (delete-dups + (cl-loop for ret = (re-search-forward "<a href=\"/\\(.*\\)?\">" nil t) + while ret collect (match-string 1))))) + +(ert-deftest seam-test-make-note-private () + (should + (equal + '("private/note.org") + (seam-test-with-notes () + ((note "Note")) + (seam-test-list-files))))) + +(ert-deftest seam-test-make-note-public () + (should + (equal + '("html/note.html" "public/note.org") + (seam-test-with-notes () + ((note "Note" "public")) + (seam-test-list-files))))) + +(ert-deftest seam-test-make-note-weird-filename () + (should + (equal + '("./Weird file name!" ("private/weird-file-name.org")) + (seam-test-with-notes () + ((weird "./Weird file name! ")) + (list (buffer-name weird) + (seam-test-list-files)))))) + +(ert-deftest seam-test-get-title-from-buffer () + (seam-test-with-notes () ((note "Note")) + (should + (equal + (buffer-name note) + (seam-get-title-from-buffer note))))) + +(ert-deftest seam-test-get-title-from-file () + (seam-test-with-notes () ((note "Note")) + (should + (equal + (buffer-name note) + (seam-get-title-from-file (buffer-file-name note)))))) + +(ert-deftest seam-test-make-note-invalid-type () + (should-error + (seam-test-environment + (kill-buffer (seam-make-note "Note" "invalid-type"))))) + +(ert-deftest seam-test-make-note-name-conflict () + (should-error + (seam-test-environment + (kill-buffer (seam-make-note " Note 1 ")) + (kill-buffer (seam-make-note "Note_1"))))) + +(ert-deftest seam-test-make-note-name-conflict-different-types () + (should-error + (seam-test-environment + (kill-buffer (seam-make-note "Note")) + (kill-buffer (seam-make-note "Note" "public"))))) + +(ert-deftest seam-test-rename-note () + (should + (equal + '("New name" ("private/new-name.org")) + (seam-test-with-notes () ((note "Note")) + (with-current-buffer note + (erase-buffer) + (insert "* New name\n") + (save-buffer) + (list (buffer-name) (seam-test-list-files))))))) + +(ert-deftest seam-test-rename-conflict () + (should-error + (seam-test-with-notes () + (let ((note1 "Note 1") + (note2 "Note 2")) + (with-current-buffer note2 + (erase-buffer) + (insert "* note_1!\n") + (unwind-protect + (save-buffer) + (set-buffer-modified-p nil))))))) + +(ert-deftest seam-test-buffer-name-format-default () + (should + (equal + "Note (private)" + (seam-test-with-notes ((seam-title-formatter #'seam-format-title-default)) + ((note "Note")) + (buffer-name note))))) + +(ert-deftest seam-test-buffer-name-format-custom () + (should + (equal + "[private] Note" + (seam-test-with-notes ((seam-title-formatter + (lambda (title type) (format "[%s] %s" type title)))) + ((note "Note")) + (buffer-name note))))) + +(ert-deftest seam-test-link-update () + "Test that renaming a note updates its HTML and that of notes which link to it." + (should + (equal '(("qux.html") + ("public/qux.org") + ("html/foo.html" "html/qux.html" "public/foo.org" "public/qux.org")) + (seam-test-with-notes () + ((foo "foo" "public") + (bar "bar" "public")) + (progn + (seam-test-add-contents foo (seam-test-link-to-buffer bar)) + (seam-test-replace-contents bar "* qux") + (list + (seam-test-links-from-html "html/foo.html") + (mapcar #'seam-test-remove-testdir (seam-get-links-from-file (buffer-file-name foo))) + (seam-test-list-files))))))) + +(ert-deftest seam-test-link-to-private () + "Test that a private link does not get exported in HTML." + (should-error + (seam-test-with-notes () + ((foo "foo" "public") + (bar "bar")) + (seam-test-add-contents foo (seam-test-link-to-buffer bar)) + (with-temp-buffer + (insert-file-contents "html/foo.html") + (buffer-string) + (re-search-forward "<a href=\"/bar.html\">"))))) + +(ert-deftest seam-test-link-no-extension () + "Test that the :no-extension option causes links to render without .html +extension." + (should + (identity + (seam-test-with-notes ((seam-export-alist + `((,(file-name-concat seam-test-directory "html") + :types ("public") + :root-path "/" + :no-extension t)))) + ((foo "foo" "public") + (bar "bar" "public")) + (seam-test-add-contents foo (seam-test-link-to-buffer bar)) + (with-temp-buffer + (insert-file-contents "html/foo.html") + (buffer-string) + (re-search-forward "<a href=\"/bar\">")))))) + +(ert-deftest seam-test-link-getters () + (should + (equal + '(("private/bar.org" "private/qux.org") + ("private/foo.org") + ("private/bar.org" "private/foo.org")) + (seam-test-with-notes () + ((foo "foo") + (bar "bar") + (qux "qux")) + (seam-test-add-contents foo (seam-test-link-to-buffer bar)) + (seam-test-add-contents foo (seam-test-link-to-buffer qux)) + (seam-test-add-contents bar (seam-test-link-to-buffer qux)) + (list + (mapcar #'seam-test-remove-testdir (seam-get-links-from-file (buffer-file-name foo))) + (mapcar #'seam-test-remove-testdir (seam-get-links-to-file (buffer-file-name bar))) + (mapcar #'seam-test-remove-testdir (seam-get-links-to-file (buffer-file-name qux)))))))) + +(ert-deftest seam-test-delete-note () + "Test that deleting a note also deletes its HTML and re-exports linking +notes such that they no longer link to it." + (should + (equal + '(nil ("html/foo.html" "public/foo.org")) + (seam-test-with-notes () + ((foo "foo" "public") + (bar "bar" "public")) + (with-current-buffer foo + (seam-test-add-contents foo (seam-test-link-to-buffer bar))) + (let ((delete-by-moving-to-trash nil)) + (seam-delete-note (buffer-file-name bar))) + (list + (seam-test-links-from-html "html/foo.html") + (seam-test-list-files)))))) + +(ert-deftest seam-test-backlinks-public () + "Test that linking to a note from a public note creates a backlink." + (should + (identity + (seam-test-with-notes ((seam-export-template-string "{{backlinks}}")) + ((foo "foo" "public") + (bar "bar" "public")) + (with-current-buffer foo + (seam-test-add-contents foo (seam-test-link-to-buffer bar))) + (with-temp-buffer + (insert-file-contents "html/bar.html") + (re-search-forward "<a href=\"/foo.html\">")))))) + +(ert-deftest seam-test-backlinks-private () + "Test that linking to a note from a private note does not create a +backlink." + (should-error + (seam-test-with-notes ((seam-export-template-string "{{backlinks}}")) + ((foo "foo") + (bar "bar" "public")) + (with-current-buffer foo + (seam-test-add-contents foo (seam-test-link-to-buffer bar))) + (with-temp-buffer + (insert-file-contents "html/bar.html") + (re-search-forward "<a href=\"/foo.html\">"))))) + +(ert-deftest seam-test-backlinks-delete () + "Test that deleting a note removes backlink." + (should-error + (seam-test-with-notes ((seam-export-template-string "{{backlinks}}")) + ((foo "foo" "public") + (bar "bar" "public")) + (with-current-buffer foo + (seam-test-add-contents foo (seam-test-link-to-buffer bar))) + (let ((delete-by-moving-to-trash nil)) + (seam-delete-note (buffer-file-name foo))) + (with-temp-buffer + (insert-file-contents "html/bar.html") + (re-search-forward "<a href=\"/foo.html\">"))))) + +(ert-deftest seam-test-set-type-private () + "Test that setting a public note to private will delete its HTML file and +update linking HTML files such that they no longer link to it." + (should + (equal + '(nil ("html/foo.html" "private/bar.org" "public/foo.org")) + (seam-test-with-notes () + ((foo "foo" "public") + (bar "bar" "public")) + (with-current-buffer foo + (seam-test-add-contents foo (seam-test-link-to-buffer bar))) + (seam-set-note-type (buffer-file-name bar) "private") + (list + (seam-test-links-from-html "html/foo.html") + (seam-test-list-files)))))) + +(ert-deftest seam-test-set-type-public () + "Test that setting a private note to public will export its HTML file and +update linking HTML files such that they link to it." + (should + (equal + '(("bar.html") + ("html/bar.html" "html/foo.html" "public/bar.org" "public/foo.org")) + (seam-test-with-notes () + ((foo "foo" "public") + (bar "bar")) + (with-current-buffer foo + (seam-test-add-contents foo (seam-test-link-to-buffer bar))) + (seam-set-note-type (buffer-file-name bar) "public") + (list + (seam-test-links-from-html "html/foo.html") + (seam-test-list-files)))))) + +(ert-deftest seam-test-set-type-invalid () + "Test that setting a note to an invalid type raises an error." + (should-error + (seam-test-with-notes () + ((foo "foo")) + (seam-set-note-type (buffer-file-name foo) "invalid-type")))) + +(ert-deftest seam-test-follow-link-existing () + "Test that following a link to an existing note opens that note." + (should + (equal + "bar" + (seam-test-with-notes () + ((foo "foo") + (bar "bar")) + (with-current-buffer foo + (seam-test-add-contents foo "[[seam:bar]]") + (org-previous-link) + (org-open-at-point) + (buffer-name)))))) + +(ert-deftest seam-test-follow-link-new () + "Test that following a link to an nonexistent note creates and opens that note." + (should + (equal + '("bar" ("private/bar.org" "private/foo.org")) + (seam-test-with-notes () + ((foo "foo")) + (with-current-buffer foo + (seam-test-add-contents foo "[[seam:bar]]") + (goto-char 1) + (org-next-link) + (org-open-at-point) + (unwind-protect + (list + (buffer-name) + (seam-test-list-files)) + (kill-buffer))))))) + +(ert-deftest seam-test-escape-title () + (should + (equal + "“quotes” & <symbols>\n" + (seam-test-with-notes ((seam-export-template-string "{{title}}")) + ((note "\"quotes\" & <symbols>" "public")) + (seam-export--file-string "html/quotes-symbols.html"))))) + +(provide 'seam-test) + +;;; seam-test.el ends here |