branch: elpa/adoc-mode
commit c709a481cf707bf8bef6402907a32452b8121466
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>
Add a font-lock smoke test over a complex sample document
Add test/resources/sample.adoc, a single construct-dense AsciiDoc document
(all title levels, every delimited-block type incl. source blocks, tables,
all list flavours, admonitions, the full inline-formatting set, macros,
anchors, directives, breaks), and a smoke test that fontifies it and asserts
font-lock runs without error, is idempotent, applies the expected faces, and
exercises native code-block fontification. Guards against regressions that
only surface on large, complex documents.
---
test/adoc-mode-smoke-test.el | 82 +++++++++++++++++
test/adoc-mode-test-helpers.el | 23 +++++
test/resources/sample.adoc | 197 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 302 insertions(+)
diff --git a/test/adoc-mode-smoke-test.el b/test/adoc-mode-smoke-test.el
new file mode 100644
index 0000000000..4a46e11d6f
--- /dev/null
+++ b/test/adoc-mode-smoke-test.el
@@ -0,0 +1,82 @@
+;;; adoc-mode-smoke-test.el --- Font-lock smoke test for adoc-mode -*-
lexical-binding: t; -*-
+
+;; Copyright © 2025-2026 Bozhidar Batsov
+
+;;; Commentary:
+
+;; A smoke test that fontifies a large, construct-dense real-world-style
+;; document (test/resources/sample.adoc) and checks that adoc-mode handles
+;; it without error, produces a stable (idempotent) fontification, and
+;; actually applies the expected faces. This guards against regressions
+;; that only show up on complex documents (runaway multiline state,
+;; under-fontification, errors in keyword matchers).
+
+;;; Code:
+
+(require 'adoc-mode-test-helpers)
+
+(describe "adoc-mode font-lock smoke test"
+
+ (defun adoc-test--sample-buffer ()
+ "Return a fontified `adoc-mode' buffer with the sample document."
+ (let ((buf (generate-new-buffer " *adoc-sample*")))
+ (with-current-buffer buf
+ (insert-file-contents (adoc-test-resource "sample.adoc"))
+ (adoc-mode)
+ (font-lock-ensure))
+ buf))
+
+ (it "fontifies the whole sample document without error"
+ (let ((buf (adoc-test--sample-buffer)))
+ (unwind-protect
+ (with-current-buffer buf
+ ;; re-fontifying an already-fontified buffer must not error either
+ (expect (font-lock-ensure) :not :to-throw)
+ (expect (> (point-max) 1000) :to-be-truthy))
+ (kill-buffer buf))))
+
+ (it "produces a stable (idempotent) fontification"
+ (let ((buf (adoc-test--sample-buffer)))
+ (unwind-protect
+ (with-current-buffer buf
+ (cl-flet ((face-vector ()
+ (let (acc (pos (point-min)))
+ (while (< pos (point-max))
+ (push (get-text-property pos 'face) acc)
+ (setq pos (1+ pos)))
+ (nreverse acc))))
+ (let ((faces-before (face-vector)))
+ (font-lock-flush)
+ (font-lock-ensure)
+ (expect (face-vector) :to-equal faces-before))))
+ (kill-buffer buf))))
+
+ (it "applies the expected faces across the document"
+ (let ((buf (adoc-test--sample-buffer)))
+ (unwind-protect
+ (with-current-buffer buf
+ (let ((faces (adoc-test-buffer-faces)))
+ ;; structural + inline + block + macro faces should all appear
+ (dolist (expected '(adoc-title-0-face
+ adoc-title-1-face
+ adoc-bold-face
+ adoc-emphasis-face
+ adoc-highlight-face
+ adoc-list-face
+ adoc-checkbox-face
+ adoc-table-face
+ adoc-anchor-face
+ adoc-complex-replacement-face
+ adoc-metadata-key-face
+ adoc-command-face
+ adoc-comment-face))
+ (expect (member expected (mapcar (lambda (f) (if (listp f)
(car f) f))
+ faces))
+ :to-be-truthy))
+ ;; native code-block fontification should be present (ruby/shell)
+ (expect (seq-find (lambda (f) (and (listp f) (memq
'adoc-native-code-face f)))
+ faces)
+ :not :to-be nil)))
+ (kill-buffer buf)))))
+
+;;; adoc-mode-smoke-test.el ends here
diff --git a/test/adoc-mode-test-helpers.el b/test/adoc-mode-test-helpers.el
index 25301f03bc..496d6d63b5 100644
--- a/test/adoc-mode-test-helpers.el
+++ b/test/adoc-mode-test-helpers.el
@@ -15,6 +15,29 @@
(require 'adoc-mode)
(require 'cl-lib)
+;;;; Test resources
+
+(defconst adoc-test-resources-directory
+ (expand-file-name
+ "resources"
+ (file-name-directory (or load-file-name buffer-file-name
default-directory)))
+ "Absolute path to the test resources directory (test/resources/).")
+
+(defun adoc-test-resource (name)
+ "Return the absolute path of resource file NAME under test/resources/."
+ (expand-file-name name adoc-test-resources-directory))
+
+(defun adoc-test-buffer-faces ()
+ "Return the set of distinct `face' values present in the current buffer."
+ (let ((faces '())
+ (pos (point-min)))
+ (while (< pos (point-max))
+ (let ((f (get-text-property pos 'face)))
+ (when (and f (not (member f faces)))
+ (push f faces)))
+ (setq pos (1+ pos)))
+ faces))
+
;;;; String helpers
(defun adoc-test--dedent (string)
diff --git a/test/resources/sample.adoc b/test/resources/sample.adoc
new file mode 100644
index 0000000000..8696c1fcc8
--- /dev/null
+++ b/test/resources/sample.adoc
@@ -0,0 +1,197 @@
+= Sample AsciiDoc Document
+:author: Bozhidar Batsov
+:email: [email protected]
+:toc: left
+:sample-attr: a reusable value
+
+This fixture exercises a broad cross-section of AsciiDoc constructs. It is
+used by the font-lock smoke test to make sure a realistic, construct-dense
+document fontifies without error, stably, and with the expected faces.
+
+A reference to an attribute: {sample-attr}.
+
+== Inline formatting
+
+Constrained: *bold*, _italic_, `monospace`, #highlight#, and ^super^/~sub~.
+
+Unconstrained: **b**old, __i__talic, ##mark##ed.
+
+Passthroughs are not monospace: +plain text+ and ++unconstrained++, while a
++++raw +++ and $$literal$$ pass content through.
+
+Curved quotes: "`a quotation`" and '`an aside`'.
+
+Escapes stay literal: \*not bold* and \`not code`.
+
+Roles layer on quotes: [.line-through]#struck#, [.underline]#lined#,
+[.overline]#over#.
+
+Replacements: (C), (R), (TM), an ellipsis ..., and arrows -> => <- <=.
+
+A forced line break here +
+continues on the next line.
+
+== Section level one
+
+=== Section level two
+
+==== Section level three
+
+===== Section level four
+
+====== Section level five
+
+[#custom-id]
+== Section with an id shorthand
+
+See <<custom-id>> and <<custom-id,a caption>> and xref:other.adoc[an xref].
+
+[[block-anchor]]
+A block anchor target, referenced via <<block-anchor>>.
+
+[[[bib-entry]]] A bibliography entry.
+
+== Lists
+
+.An unordered list
+* level one
+** level two
+*** level three
+- a dash bullet
+
+.An ordered list
+. implicit one
+. implicit two
+.. implicit nested
+1. explicit arabic
+a. explicit alpha
+
+.A checklist
+* [ ] todo item
+* [x] done item
+* [*] also done
+
+.A description list
+First term:: Its definition.
+Second term:: Another definition.
+
+.A callout list
+[source,ruby]
+----
+puts "hello" # <1>
+----
+<1> Prints a greeting.
+
+== Admonitions
+
+NOTE: This is an admonition paragraph.
+
+[WARNING]
+====
+This is an admonition block with a delimited body.
+====
+
+== Delimited blocks
+
+.A listing block
+----
+plain listing, no language
+----
+
+.A Ruby source block
+[source,ruby]
+----
+def greet(name)
+ # say hello
+ puts "Hello, #{name}!"
+end
+----
+
+.A shell source block
+[source,shell]
+----
+rubocop --auto-correct --format simple
+----
+
+.A literal block
+....
+literal text, rendered verbatim
+....
+
+.A quote block
+[quote, Anonymous]
+____
+The body of a quotation.
+____
+
+.A sidebar
+****
+An aside in a sidebar.
+****
+
+.A passthrough block
+++++
+<custom-html>passed through</custom-html>
+++++
+
+.An open block
+--
+An open block body.
+--
+
+////
+A comment block that should be ignored entirely.
+////
+
+// a single-line comment
+
+== Tables
+
+.A small table
+[cols="1,1",options="header"]
+|===
+| Name | Value
+
+| alpha
+| one
+
+| beta
+| two
+|===
+
+== Macros
+
+A link: https://example.com/page[the docs] and a bare URL
+https://example.com/raw and an email [email protected].
+
+An image: image:logo.png[Logo,32,32] and a block image:
+
+image::diagram.png[Architecture diagram]
+
+A footnote.footnote:[The footnote text.] and a UI macro kbd:[C-c C-c],
+a button btn:[OK], a menu menu:File[Save As], and an icon icon:heart[2x].
+
+Math via STEM: stem:[a^2 + b^2 = c^2], latexmath:[E = mc^2],
+asciimath:[sqrt(x)].
+
+An index term (((indexing, terms))) appears here.
+
+A passthrough macro: pass:[<b>raw</b>].
+
+== Directives
+
+ifdef::sample-attr[Shown when the attribute is set.]
+
+include::partial.adoc[]
+
+== Breaks
+
+A thematic break:
+
+'''
+
+A page break:
+
+<<<
+
+The end.