seam

Personal wiki toolkit for Emacs
Log | Files | Refs | LICENSE

seam-test.el (19622B)


      1 ;;; seam-test.el --- Tests for Seam  -*- lexical-binding: t -*-
      2 
      3 ;; Copyright (C) 2025 Spencer Williams
      4 
      5 ;; Author: Spencer Williams <spnw@plexwave.org>
      6 
      7 ;; SPDX-License-Identifier: GPL-3.0-or-later
      8 
      9 ;; This file is not part of GNU Emacs.
     10 
     11 ;; This program is free software: you can redistribute it and/or modify
     12 ;; it under the terms of the GNU General Public License as published by
     13 ;; the Free Software Foundation, either version 3 of the License, or
     14 ;; (at your option) any later version.
     15 
     16 ;; This program is distributed in the hope that it will be useful,
     17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     19 ;; GNU General Public License for more details.
     20 
     21 ;; You should have received a copy of the GNU General Public License
     22 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
     23 
     24 ;;; Commentary:
     25 
     26 ;; Tests for Seam.
     27 
     28 ;;; Code:
     29 
     30 ;;; FIXME: Tests can fail if certain buffer names are already in use
     31 ;;; on the test runner's system.
     32 
     33 (require 'seam)
     34 (require 'seam-export)
     35 (require 'ert)
     36 (require 'cl-lib)
     37 
     38 (defvar seam-test-directory nil)
     39 
     40 (defmacro seam-test-environment (&rest body)
     41   (declare (indent 0))
     42   `(let* ((seam-test-directory (file-name-as-directory (make-temp-file "seam-test" t)))
     43           (seam-note-directory seam-test-directory)
     44           (default-directory seam-test-directory)
     45           (seam-create-as-draft nil)
     46           (seam-note-types '("private" "public"))
     47           (seam-default-note-type "private")
     48           (seam-title-formatter (lambda (title _type _draft-p) title))
     49           (seam-export-template-file nil)
     50           (seam-export-template-string seam-export-default-template-string)
     51           (seam-export-template-values nil)
     52           (seam-export-internal-link-class nil)
     53           (seam-export-alist
     54            `((,(file-name-concat seam-test-directory "html")
     55               :types ("public")
     56               :root-path "/")))
     57           ;; Manually install hooks in test directory.
     58           (dir-locals-directory-cache
     59            (cons (list seam-test-directory 'seam-note-directory nil)
     60                  dir-locals-directory-cache)))
     61      (unwind-protect
     62          (progn ,@body)
     63        (delete-directory seam-test-directory t))))
     64 
     65 (defmacro seam-test-with-notes (options varlist &rest body)
     66   (declare (indent 2))
     67   `(seam-test-environment
     68      (let ,options
     69        (let ,(cl-loop for (name . args) in varlist
     70                       collect `(,name (seam-create-note ,@args)))
     71          ;; FIXME: It's quite possible for tests to fail in such a way
     72          ;; that this does not kill the buffers.
     73          (unwind-protect (progn ,@body)
     74            (mapcar #'kill-buffer (list ,@(mapcar #'car varlist))))))))
     75 
     76 (defun seam-test-strip-testdir (filename)
     77   (string-remove-prefix seam-test-directory filename))
     78 
     79 (defun seam-test-list-files ()
     80   (mapcar
     81    #'seam-test-strip-testdir
     82    (directory-files-recursively seam-test-directory "")))
     83 
     84 (defun seam-test-add-contents (buffer contents)
     85   (with-current-buffer buffer
     86     (insert contents)
     87     (insert "\n")
     88     (save-buffer)))
     89 
     90 (defun seam-test-replace-contents (buffer contents)
     91   (with-current-buffer buffer
     92     (erase-buffer)
     93     (insert contents)
     94     (insert "\n")
     95     (save-buffer)))
     96 
     97 (defun seam-test-link-to-buffer (buffer)
     98   (format "[[seam:%s]]" (file-name-base (buffer-file-name buffer))))
     99 
    100 (defun seam-test-links-from-html (file)
    101   (with-temp-buffer
    102     (insert-file-contents file)
    103     (delete-dups
    104      (cl-loop for ret = (re-search-forward "<a href=\"/\\(.*\\)?\">" nil t)
    105               while ret collect (match-string 1)))))
    106 
    107 (ert-deftest seam-test-create-note-private ()
    108   (should
    109    (equal
    110     '("private/note.org")
    111     (seam-test-with-notes ()
    112         ((note "Note"))
    113       (seam-test-list-files)))))
    114 
    115 (ert-deftest seam-test-create-note-public ()
    116   (should
    117    (equal
    118     '("html/note.html" "public/note.org")
    119     (seam-test-with-notes ()
    120         ((note "Note" "public"))
    121       (seam-test-list-files)))))
    122 
    123 (ert-deftest seam-test-create-note-weird-filename ()
    124   (should
    125    (equal
    126     '("./Weir'd file name!" ("private/weird-file-name.org"))
    127     (seam-test-with-notes ()
    128         ((weird "./Weir'd file name! "))
    129       (list (buffer-name weird)
    130             (seam-test-list-files))))))
    131 
    132 (ert-deftest seam-test-get-title-from-buffer ()
    133   (seam-test-with-notes () ((note "Note"))
    134     (should
    135      (equal
    136       (buffer-name note)
    137       (seam-get-title-from-buffer note)))))
    138 
    139 (ert-deftest seam-test-get-title-from-buffer-narrowed ()
    140   (should
    141    (equal
    142     "foo"
    143     (seam-test-with-notes ()
    144         ((foo "foo"))
    145       (with-current-buffer foo
    146         (seam-test-add-contents foo "* My headline")
    147         (forward-line)
    148         (org-narrow-to-subtree)
    149         (seam-get-title-from-buffer))))))
    150 
    151 (ert-deftest seam-test-get-title-from-file ()
    152   (seam-test-with-notes () ((note "Note"))
    153     (should
    154      (equal
    155       (buffer-name note)
    156       (seam-get-title-from-file (buffer-file-name note))))))
    157 
    158 (ert-deftest seam-test-create-note-invalid-type ()
    159   (should-error
    160    (seam-test-environment
    161      (kill-buffer (seam-create-note "Note" "invalid-type")))))
    162 
    163 (ert-deftest seam-test-create-note-name-conflict ()
    164   (should-error
    165    (seam-test-environment
    166      (kill-buffer (seam-create-note " Note 1 "))
    167      (kill-buffer (seam-create-note "Note_1")))))
    168 
    169 (ert-deftest seam-test-create-note-name-conflict-different-types ()
    170   (should-error
    171    (seam-test-environment
    172      (kill-buffer (seam-create-note "Note"))
    173      (kill-buffer (seam-create-note "Note" "public")))))
    174 
    175 (ert-deftest seam-test-rename-note ()
    176   (should
    177    (equal
    178     '("New name" ("private/new-name.org"))
    179     (seam-test-with-notes () ((note "Note"))
    180       (with-current-buffer note
    181         (erase-buffer)
    182         (insert "* New name\n")
    183         (save-buffer)
    184         (list (buffer-name) (seam-test-list-files)))))))
    185 
    186 (ert-deftest seam-test-rename-conflict ()
    187   (should-error
    188    (seam-test-with-notes ()
    189        (let ((note1 "Note 1")
    190              (note2 "Note 2"))
    191          (with-current-buffer note2
    192            (erase-buffer)
    193            (insert "* note_1!\n")
    194            (unwind-protect
    195                (save-buffer)
    196              (set-buffer-modified-p nil)))))))
    197 
    198 (ert-deftest seam-test-buffer-name-format-default ()
    199   (should
    200    (equal
    201     "Note (private)"
    202     (seam-test-with-notes ((seam-title-formatter #'seam-format-title-default))
    203         ((note "Note"))
    204       (buffer-name note)))))
    205 
    206 (ert-deftest seam-test-buffer-name-format-custom ()
    207   (should
    208    (equal
    209     "[private draft] Note"
    210     (seam-test-with-notes ((seam-title-formatter
    211                             (lambda (title type draft-p)
    212                               (format "[%s%s] %s" type (if draft-p " draft" "") title))))
    213         ((note "Note" nil nil t))
    214       (buffer-name note)))))
    215 
    216 (ert-deftest seam-test-link-update ()
    217   "Test that renaming a note updates its HTML and that of notes
    218 which link to it."
    219   (should
    220    (equal '(("qux.html")
    221             ("public/qux.org")
    222             ("html/foo.html" "html/qux.html" "public/foo.org" "public/qux.org"))
    223           (seam-test-with-notes ()
    224               ((foo "foo" "public")
    225                (bar "bar" "public"))
    226             (progn
    227               (seam-test-add-contents foo (seam-test-link-to-buffer bar))
    228               (seam-test-replace-contents bar "* qux")
    229               (list
    230                (seam-test-links-from-html "html/foo.html")
    231                (mapcar #'seam-test-strip-testdir (seam-get-links-from-file (buffer-file-name foo)))
    232                (seam-test-list-files)))))))
    233 
    234 (ert-deftest seam-test-link-update-no-unnecessary-export ()
    235   "Test that updating the contents of a note does not unnecessarily
    236 re-export note to which it links."
    237   (should-not
    238    (member
    239     "html/bar.html"
    240     (seam-test-with-notes ()
    241         ((foo "foo" "public")
    242          (bar "bar" "public"))
    243       (seam-test-add-contents foo (seam-test-link-to-buffer bar))
    244       (delete-file "html/bar.html")
    245       (seam-test-add-contents foo "hello")
    246       (seam-test-list-files)))))
    247 
    248 (ert-deftest seam-test-link-to-private ()
    249   "Test that a private link does not get exported in HTML."
    250   (should-error
    251    (seam-test-with-notes ()
    252        ((foo "foo" "public")
    253         (bar "bar"))
    254      (seam-test-add-contents foo (seam-test-link-to-buffer bar))
    255      (with-temp-buffer
    256        (insert-file-contents "html/foo.html")
    257        (buffer-string)
    258        (re-search-forward "<a href=\"/bar.html\">")))))
    259 
    260 (ert-deftest seam-test-link-no-extension ()
    261   "Test that the :no-extension option causes links to render without
    262 .html extension."
    263   (should
    264    (identity
    265     (seam-test-with-notes ((seam-export-alist
    266                             `((,(file-name-concat seam-test-directory "html")
    267                                :types ("public")
    268                                :root-path "/"
    269                                :no-extension t))))
    270         ((foo "foo" "public")
    271          (bar "bar" "public"))
    272       (seam-test-add-contents foo (seam-test-link-to-buffer bar))
    273       (with-temp-buffer
    274         (insert-file-contents "html/foo.html")
    275         (buffer-string)
    276         (re-search-forward "<a href=\"/bar\">"))))))
    277 
    278 (ert-deftest seam-test-link-internal-class ()
    279   "Test that setting `seam-export-internal-link-class' correctly
    280 renders the class."
    281   (should
    282    (identity
    283     (seam-test-with-notes ((seam-export-internal-link-class "internal"))
    284         ((foo "foo" "public")
    285          (bar "bar" "public"))
    286       (seam-test-add-contents foo (seam-test-link-to-buffer bar))
    287       (with-temp-buffer
    288         (insert-file-contents "html/foo.html")
    289         (buffer-string)
    290         (re-search-forward "<a href=\"/bar.html\" class=\"internal\">"))))))
    291 
    292 (ert-deftest seam-test-link-getters ()
    293   (should
    294    (equal
    295     '(("private/bar.org" "private/qux.org")
    296       ("private/foo.org")
    297       ("private/bar.org" "private/foo.org"))
    298     (seam-test-with-notes ()
    299         ((foo "foo")
    300          (bar "bar")
    301          (qux "qux"))
    302       (seam-test-add-contents foo (seam-test-link-to-buffer bar))
    303       (seam-test-add-contents foo (seam-test-link-to-buffer qux))
    304       (seam-test-add-contents bar (seam-test-link-to-buffer qux))
    305       (list
    306        (mapcar #'seam-test-strip-testdir (seam-get-links-from-file (buffer-file-name foo)))
    307        (mapcar #'seam-test-strip-testdir (seam-get-links-to-file (buffer-file-name bar)))
    308        (mapcar #'seam-test-strip-testdir (seam-get-links-to-file (buffer-file-name qux))))))))
    309 
    310 (ert-deftest seam-test-delete-note ()
    311   "Test that deleting a note also deletes its HTML and re-exports
    312 linking notes such that they no longer link to it."
    313   (should
    314    (equal
    315     '(nil ("html/foo.html" "public/foo.org"))
    316     (seam-test-with-notes ()
    317         ((foo "foo" "public")
    318          (bar "bar" "public"))
    319       (with-current-buffer foo
    320         (seam-test-add-contents foo (seam-test-link-to-buffer bar)))
    321       (let ((delete-by-moving-to-trash nil))
    322         (seam-delete-note (buffer-file-name bar)))
    323       (list
    324        (seam-test-links-from-html "html/foo.html")
    325        (seam-test-list-files))))))
    326 
    327 (ert-deftest seam-test-backlinks-public ()
    328   "Test that linking to a note from a public note creates a
    329 backlink."
    330   (should
    331    (identity
    332     (seam-test-with-notes ((seam-export-template-string "{{{backlinks}}}"))
    333         ((foo "foo" "public")
    334          (bar "bar" "public"))
    335       (with-current-buffer foo
    336         (seam-test-add-contents foo (seam-test-link-to-buffer bar)))
    337       (with-temp-buffer
    338         (insert-file-contents "html/bar.html")
    339         (re-search-forward "<a href=\"/foo.html\">"))))))
    340 
    341 (ert-deftest seam-test-backlinks-private ()
    342   "Test that linking to a note from a private note does not create a
    343 backlink."
    344   (should
    345    (equal
    346     ""
    347     (seam-test-with-notes ((seam-export-template-string "{{{backlinks}}}"))
    348         ((foo "foo")
    349          (bar "bar" "public"))
    350       (with-current-buffer foo
    351         (seam-test-add-contents foo (seam-test-link-to-buffer bar)))
    352       (with-temp-buffer
    353         (insert-file-contents "html/bar.html")
    354         (buffer-string))))))
    355 
    356 (ert-deftest seam-test-backlinks-delete ()
    357   "Test that deleting a note removes backlink."
    358   (should
    359    (equal
    360     ""
    361     (seam-test-with-notes ((seam-export-template-string "{{{backlinks}}}"))
    362         ((foo "foo" "public")
    363          (bar "bar" "public"))
    364       (with-current-buffer foo
    365         (seam-test-add-contents foo (seam-test-link-to-buffer bar)))
    366       (let ((delete-by-moving-to-trash nil))
    367         (seam-delete-note (buffer-file-name foo)))
    368       (with-temp-buffer
    369         (insert-file-contents "html/bar.html")
    370         (buffer-string))))))
    371 
    372 (ert-deftest seam-test-backlinks-draft ()
    373   "Test that linking to a note from a draft note does not create a
    374 backlink."
    375   (should
    376    (equal
    377     ""
    378     (seam-test-with-notes ((seam-export-template-string "{{{backlinks}}}"))
    379         ((foo "foo" "public" nil t)
    380          (bar "bar" "public"))
    381       (with-current-buffer foo
    382         (seam-test-add-contents foo (seam-test-link-to-buffer bar)))
    383       (with-temp-buffer
    384         (insert-file-contents "html/bar.html")
    385         (buffer-string))))))
    386 
    387 (ert-deftest seam-test-set-type-private ()
    388   "Test that setting a public note to private will delete its HTML
    389 file and update linking HTML files such that they no longer link
    390 to it."
    391   (should
    392    (equal
    393     '(nil ("html/foo.html" "private/bar.org" "public/foo.org"))
    394     (seam-test-with-notes ()
    395         ((foo "foo" "public")
    396          (bar "bar" "public"))
    397       (with-current-buffer foo
    398         (seam-test-add-contents foo (seam-test-link-to-buffer bar)))
    399       (seam-set-note-type (buffer-file-name bar) "private")
    400       (list
    401        (seam-test-links-from-html "html/foo.html")
    402        (seam-test-list-files))))))
    403 
    404 (ert-deftest seam-test-set-type-public ()
    405   "Test that setting a private note to public will export its HTML file and
    406 update linking HTML files such that they link to it."
    407   (should
    408    (equal
    409     '(("bar.html")
    410       ("html/bar.html" "html/foo.html" "public/bar.org" "public/foo.org"))
    411     (seam-test-with-notes ()
    412         ((foo "foo" "public")
    413          (bar "bar"))
    414       (with-current-buffer foo
    415         (seam-test-add-contents foo (seam-test-link-to-buffer bar)))
    416       (seam-set-note-type (buffer-file-name bar) "public")
    417       (list
    418        (seam-test-links-from-html "html/foo.html")
    419        (seam-test-list-files))))))
    420 
    421 (ert-deftest seam-test-set-type-invalid ()
    422   "Test that setting a note to an invalid type raises an error."
    423   (should-error
    424    (seam-test-with-notes ()
    425        ((foo "foo"))
    426      (seam-set-note-type (buffer-file-name foo) "invalid-type"))))
    427 
    428 (ert-deftest seam-test-create-draft ()
    429   (should
    430    (equal
    431     '("public/-note.org")
    432     (seam-test-with-notes ((seam-create-as-draft t))
    433         ((note "Note" "public"))
    434       (seam-test-list-files)))))
    435 
    436 (ert-deftest seam-test-create-draft-override ()
    437   (should
    438    (equal
    439     '("public/-note.org")
    440     (seam-test-with-notes ((seam-note-types
    441                             '(("public" :create-as-draft t))))
    442         ((note "Note" "public"))
    443       (seam-test-list-files)))))
    444 
    445 (ert-deftest seam-test-set-draft ()
    446   "Test that toggling a note from non-draft to draft will delete its
    447 HTML file and update linking HTML files such that they no longer
    448 link to it."
    449   (should
    450    (equal
    451     '(nil ("html/foo.html" "public/-bar.org" "public/foo.org"))
    452     (seam-test-with-notes ()
    453         ((foo "foo" "public")
    454          (bar "bar" "public"))
    455       (with-current-buffer foo
    456         (seam-test-add-contents foo (seam-test-link-to-buffer bar)))
    457       (with-current-buffer bar
    458         (call-interactively 'seam-toggle-draft))
    459       (list
    460        (seam-test-links-from-html "html/foo.html")
    461        (seam-test-list-files))))))
    462 
    463 (ert-deftest seam-test-unset-draft ()
    464   "Test that toggling a note from draft to non-draft will export its
    465 HTML file and update linking HTML files such that they link to
    466 it."
    467   (should
    468    (equal
    469     '(("bar.html") ("html/bar.html" "html/foo.html" "public/bar.org" "public/foo.org"))
    470     (seam-test-with-notes ()
    471         ((foo "foo" "public")
    472          (bar "bar" "public" nil t))
    473       (with-current-buffer foo
    474         (seam-test-add-contents foo (seam-test-link-to-buffer bar)))
    475       (with-current-buffer bar
    476         (call-interactively 'seam-toggle-draft))
    477       (list
    478        (seam-test-links-from-html "html/foo.html")
    479        (seam-test-list-files))))))
    480 
    481 (ert-deftest seam-test-follow-link-existing ()
    482   "Test that following a link to an existing note opens that note."
    483   (should
    484    (equal
    485     "bar"
    486     (seam-test-with-notes ()
    487         ((foo "foo")
    488          (bar "bar"))
    489       (with-current-buffer foo
    490         (seam-test-add-contents foo "[[seam:bar]]")
    491         (org-previous-link)
    492         (org-open-at-point)
    493         (buffer-name))))))
    494 
    495 (ert-deftest seam-test-follow-link-new ()
    496   "Test that following a link to an nonexistent note creates and
    497 opens that note."
    498   (should
    499    (equal
    500     '("bar" ("private/bar.org" "private/foo.org"))
    501     (seam-test-with-notes ()
    502         ((foo "foo"))
    503       (with-current-buffer foo
    504         (seam-test-add-contents foo "[[seam:bar]]")
    505         (goto-char 1)
    506         (org-next-link)
    507         (org-open-at-point)
    508         (unwind-protect
    509             (list
    510              (buffer-name)
    511              (seam-test-list-files))
    512           (kill-buffer)))))))
    513 
    514 (ert-deftest seam-test-follow-link-new-draft ()
    515   "Test that following a link to an nonexistent draft note creates
    516 and opens that note."
    517   (should
    518    (equal
    519     '("-bar" ("private/-bar.org" "private/foo.org"))
    520     (seam-test-with-notes ()
    521         ((foo "foo"))
    522       (with-current-buffer foo
    523         (seam-test-add-contents foo "[[seam:-bar]]")
    524         (goto-char 1)
    525         (org-next-link)
    526         (org-open-at-point)
    527         (unwind-protect
    528             (list
    529              (buffer-name)
    530              (seam-test-list-files))
    531           (kill-buffer)))))))
    532 
    533 (ert-deftest seam-test-escape-title ()
    534   (should
    535    (equal
    536     "“quotes” &amp; &lt;symbols&gt;\n&ldquo;quotes&rdquo; &amp; &lt;symbols&gt;\n"
    537     (seam-test-with-notes ((seam-export-template-string "{{title}}\n{{{raw-title}}}"))
    538         ((note "\"quotes\" & <symbols>" "public"))
    539       (seam-export--file-string "html/quotes-symbols.html")))))
    540 
    541 (ert-deftest seam-test-custom-slug ()
    542   "Test that setting the SEAM_SLUG property saves and exports
    543 accordingly."
    544   (should
    545    (equal
    546     '("html/c-vs-cpp.html" "public/c-vs-cpp.org")
    547     (seam-test-with-notes ()
    548         ((note "C vs C++" "public"))
    549       (seam-test-add-contents note ":PROPERTIES:\n:SEAM_SLUG: c-vs-cpp\n:END:")
    550       (seam-test-list-files)))))
    551 
    552 (ert-deftest seam-test-removing-type-from-export-alist ()
    553   (should
    554    (equal
    555     '("public/note.org")
    556     (seam-test-with-notes ()
    557         ((note "Note" "public"))
    558       (setq seam-export-alist
    559             `((,(file-name-concat seam-test-directory "html")
    560                :types ("foo")
    561                :root-path "/")))
    562       (seam-export-all-notes)
    563       (seam-test-list-files)))))
    564 
    565 (ert-deftest seam-test-template-values ()
    566   "Test that custom variables can be used in templates, and that
    567 existing ones can be overridden."
    568   (should
    569    (equal
    570     "Qux\nhello, world\n"
    571     (seam-test-with-notes
    572         ((seam-export-template-values '(("title" . "Bar")
    573                                         ("greeting" . "hello, world")))
    574          (seam-export-template-string "{{title}}\n{{greeting}}")
    575          (seam-export-alist
    576           `((,(file-name-concat seam-test-directory "html")
    577              :types ("public")
    578              :root-path "/"
    579              :template-values (("title" . "Qux"))))))
    580         ((foo "Foo" "public"))
    581       (seam-export--file-string "html/foo.html")))))
    582 
    583 (provide 'seam-test)
    584 
    585 ;;; seam-test.el ends here