;;; ste-lint-tests.el --- Tests for the STE linter -*- lexical-binding: t; -*- ;;; Commentary: ;; These tests lint small Org texts in temporary buffers and files. Run them ;; with the runner script in this directory: ;; ;; ./run-tests.sh ;;; Code: (require 'ert) (require 'ste-lint) ;;; Helpers (defun ste-lint-tests--findings (text) "Return the findings of the Org TEXT as (CATEGORY MATCH LINE) lists." (let ((file (make-temp-file "ste-lint-" nil ".org" text))) (unwind-protect (mapcar (lambda (entry) (let ((finding (nth 2 entry))) (list (ste-lint-finding-category finding) (ste-lint-finding-match finding) (nth 0 entry)))) (ste-lint-file file)) (delete-file file)))) (defun ste-lint-tests--categories (text) "Return the categories of the findings of the Org TEXT." (mapcar #'car (ste-lint-tests--findings text))) (defun ste-lint-tests--words (count) "Return a sentence of COUNT words that ends with a period." (concat (mapconcat (lambda (_) "word") (number-sequence 1 count) " ") ".")) ;;; Rules (ert-deftest ste-lint-test-contraction () (should (equal (ste-lint-tests--findings "We can't stop.\n") '(("contraction" "can't" 1)))) (should (equal (ste-lint-tests--categories "We don’t stop.\n") '("contraction")))) (ert-deftest ste-lint-test-possessive-is-not-contraction () (should (null (ste-lint-tests--findings "The user's file is open.\n")))) (ert-deftest ste-lint-test-latin-abbreviation-ignores-case () (should (equal (ste-lint-tests--findings "Use a tool, E.g. a linter.\n") '(("latin-abbreviation" "E.g." 1))))) (ert-deftest ste-lint-test-semicolon-and-idiom () (should (equal (ste-lint-tests--categories "Stop it; start it.\n") '("semicolon"))) (should (null (ste-lint-tests--findings "Read the TL;DR first.\n")))) (ert-deftest ste-lint-test-substitution () (should (equal (ste-lint-tests--findings "Utilize the tool.\n") '(("wordy-phrase" "Utilize" 1))))) (ert-deftest ste-lint-test-phrase-across-line-break () (should (equal (ste-lint-tests--findings "Read the file in order\n to see the rules.\n") '(("wordy-phrase" "in order to" 1))))) (ert-deftest ste-lint-test-instruction-mood () (should (equal (ste-lint-tests--categories "You should run the script.\n") '("instruction-mood")))) ;;; Prose selection (ert-deftest ste-lint-test-inline-markup-is-masked () (should (null (ste-lint-tests--findings "Run =don't;= and ~a;b~ at <2026-10-05 Mon>.\n")))) (ert-deftest ste-lint-test-link-label-is-linted-and-target-is-not () (should (null (ste-lint-tests--findings "Read [[https://a.example/x;y][the page]] now.\n"))) (should (equal (ste-lint-tests--categories "Read [[https://a.example/x][the page; now]].\n") '("semicolon")))) (ert-deftest ste-lint-test-non-prose-elements-are-skipped () (should (null (ste-lint-tests--findings (concat "#+TITLE: Don't; skip\n" "* Plain title\n" "SCHEDULED: <2026-10-05 Mon>\n" ":PROPERTIES:\n:ID: a;b\n:END:\n" ":LOGBOOK:\n- Note: don't; skip.\n:END:\n" "#+BEGIN_SRC sh\necho \"don't;\"\n#+END_SRC\n" "#+BEGIN_EXAMPLE\ndon't;\n#+END_EXAMPLE\n" "#+BEGIN_QUOTE\nHe said don't.\n#+END_QUOTE\n" "| don't; | cell |\n" "# don't; comment\n"))))) (ert-deftest ste-lint-test-headline-title-only () (should (equal (ste-lint-tests--findings "* TODO [#A] Don't wait [1/2] :he:\n") '(("contraction" "Don't" 1))))) (ert-deftest ste-lint-test-headline-has-no-length-limit () (should (null (ste-lint-tests--findings (concat "* " (ste-lint-tests--words 30) "\n"))))) ;;; Sentence length (ert-deftest ste-lint-test-sentence-length-description () (should (null (ste-lint-tests--findings (concat (ste-lint-tests--words 25) "\n")))) (should (equal (ste-lint-tests--findings (concat "Intro.\n" (ste-lint-tests--words 26) "\n")) '(("sentence-length" "26 words" 2))))) (ert-deftest ste-lint-test-sentence-length-spans-lines () (let ((text (replace-regexp-in-string " word word word word word word word word " "\nword word word word word word word word\n" (ste-lint-tests--words 30) t t))) (should (equal (ste-lint-tests--categories (concat text "\n")) '("sentence-length"))))) (ert-deftest ste-lint-test-sentence-length-procedure () (should (equal (ste-lint-tests--categories (concat "1. " (ste-lint-tests--words 21) "\n")) '("sentence-length"))) (should (null (ste-lint-tests--findings (concat "- " (ste-lint-tests--words 21) "\n"))))) (ert-deftest ste-lint-test-masked-code-counts-as-one-word () (should (null (ste-lint-tests--findings (concat (ste-lint-tests--words 24) " =a b c d e=.\n"))))) ;;; Positions and entry points (ert-deftest ste-lint-test-exact-position () (let ((file (make-temp-file "ste-lint-" nil ".org" "First line.\nThe tool can't run.\n"))) (unwind-protect (let ((entry (car (ste-lint-file file)))) (should (equal (list (nth 0 entry) (nth 1 entry)) '(2 10)))) (delete-file file)))) (ert-deftest ste-lint-test-flymake-backend () (with-temp-buffer (let ((org-mode-hook nil)) (org-mode)) (insert "We can't stop.\nUtilize the tool.\n") (let (diagnostics) (ste-lint-flymake (lambda (reported) (setq diagnostics reported))) (should (equal (mapcar #'flymake-diagnostic-type diagnostics) '(:error :warning))) (should (equal (buffer-substring (flymake-diagnostic-beg (car diagnostics)) (flymake-diagnostic-end (car diagnostics))) "can't"))))) (provide 'ste-lint-tests) ;;; ste-lint-tests.el ends here