Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: CI
on:
pull_request:
push:
branches: [master]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
emacs_version: ['26.1', '28.2', '29.4', '30.2']
steps:
- uses: actions/checkout@v4
- uses: jcs090218/setup-emacs@master
with:
version: ${{ matrix.emacs_version }}
- uses: emacs-eask/setup-eask@master
with:
version: 'snapshot'
- run: eask package
- run: eask install-deps
- run: eask compile
- run: eask lint package
- run: eask test buttercup
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.elc
.deps
.compile
.eask/
.cask/
dist/
17 changes: 17 additions & 0 deletions Easkfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
(package "ttl-mode"
"0.2.0"
"Major mode for Turtle (and Notation 3)")

(website-url "https://github.com/raszi/ttl-mode")
(keywords "languages" "data")

(package-file "ttl-mode.el")

(source "gnu")
(source "melpa")

(depends-on "emacs" "26.1")

(development
(depends-on "package-lint")
(depends-on "buttercup"))
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.PHONY: lint test clean

.deps: Easkfile
eask install-deps
@touch .deps

.compile: .deps ttl-mode.el
eask compile
@touch .compile

lint: .deps
eask lint package

test: .compile
eask test buttercup

clean:
eask clean all
@rm -f .deps .compile
209 changes: 209 additions & 0 deletions test/ttl-mode-test.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
;;; ttl-mode-test.el --- Tests for ttl-mode -*- lexical-binding: t; -*-

;;; Commentary:

;; Buttercup tests for ttl-mode.

;;; Code:

(require 'buttercup)
(require 'ttl-mode)

(defun ttl-lines (&rest lines)
"Join LINES with newlines into one buffer-ready string.
The result ends with a newline, like a text file, so fixtures can be
written one line per string instead of as a flush-left literal."
(concat (mapconcat #'identity lines "\n") "\n"))

(defun ttl-test-reindent (text)
"Return TEXT after re-indenting every line in `ttl-mode'."
(with-temp-buffer
(insert text)
(ttl-mode)
(indent-region (point-min) (point-max))
(buffer-string)))

(defun ttl-test-face-at (text search)
"Fontify TEXT in `ttl-mode' and return the face where SEARCH begins."
(with-temp-buffer
(insert text)
(ttl-mode)
(font-lock-ensure)
(goto-char (point-min))
(search-forward search)
(get-text-property (match-beginning 0) 'face)))

(describe "ttl-mode indentation is idempotent"
(dolist (case `((:desc "prefixes and a simple statement"
:text ,(ttl-lines
"@prefix ex: <http://example.org/> ."
"@prefix sh: <http://www.w3.org/ns/shacl#> ."
""
"ex:s a ex:Thing ;"
" ex:p ex:o ;"
" ex:q 1 ."))
(:desc "blank nodes written inline with the predicate"
:text ,(ttl-lines
":Shape a sh:NodeShape ;"
" sh:property [ sh:datatype xsd:string ;"
" sh:maxCount 1 ;"
" sh:path rdfs:label ],"
" [ sh:datatype xsd:double ;"
" sh:path :rate ] ;"
" sh:targetClass :X ."))
(:desc "a blank node opened at end of line"
:text ,(ttl-lines
"ex:s ex:p ["
" ex:a 1 ;"
" ex:b 2 ] ."))
(:desc "a TriG named graph"
:text ,(ttl-lines
"ex:g {"
" ex:s ex:p ex:o ;"
" ex:q ex:r ."
"}"))
(:desc "a comment line between statements"
:text ,(ttl-lines
"ex:s ex:p ex:o ;"
" # a comment"
" ex:q 1 ."))
(:desc "a hash inside a resource is not a comment"
:text ,(ttl-lines
"ex:s ex:p <http://example.org/thing#frag> ;"
" ex:q 1 ."))
(:desc "a closing bracket on its own line aligns under its opener"
:text ,(ttl-lines
"ex:s ex:p ["
" ex:a 1 ;"
" ex:b 2"
"] ."))
(:desc "several brackets opened on one line count as one step"
:text ,(ttl-lines
"ex:s ex:p [ ex:q [ ex:a 1 ;"
" ex:b 2 ] ] ."))))
(let ((desc (plist-get case :desc))
(text (plist-get case :text)))
(it desc
(expect (ttl-test-reindent text) :to-equal text)))))

(describe "ttl-mode indentation converges"
(it "restores nesting a flattened blank-node list lost"
(expect
(ttl-test-reindent
(ttl-lines
":Shape a sh:NodeShape ;"
" sh:property [ sh:datatype xsd:string ;"
" sh:maxCount 1 ;"
" sh:path rdfs:label ],"
" [ sh:datatype xsd:double ;"
" sh:path :rate ] ;"
" sh:targetClass :X ."))
:to-equal
(ttl-lines
":Shape a sh:NodeShape ;"
" sh:property [ sh:datatype xsd:string ;"
" sh:maxCount 1 ;"
" sh:path rdfs:label ],"
" [ sh:datatype xsd:double ;"
" sh:path :rate ] ;"
" sh:targetClass :X .")))

(it "moves a mis-indented first line to column 0"
(expect (ttl-test-reindent " ex:s ex:p ex:o .\n")
:to-equal "ex:s ex:p ex:o .\n"))

(it "snaps a mis-indented @prefix to column 0"
(expect (ttl-test-reindent " @prefix ex: <http://example.org/> .\n")
:to-equal "@prefix ex: <http://example.org/> .\n")))

(describe "ttl-mode comment syntax"
(it "treats a hash after whitespace as a comment"
(with-temp-buffer
(insert "ex:s ex:p ex:o . # a comment\n")
(ttl-mode)
(goto-char (point-min))
(search-forward "a comment")
(expect (ttl-in-comment) :to-be-truthy)))

(it "leaves a hash inside a resource alone"
(with-temp-buffer
(insert "ex:s ex:p <http://example.org/#frag> .\n")
(ttl-mode)
(goto-char (point-min))
(search-forward "#frag")
(expect (ttl-in-comment) :to-be nil)))

(it "treats a hash at the very start of the buffer as a comment"
(with-temp-buffer
(insert "# leading comment\nex:s ex:p ex:o .\n")
(ttl-mode)
(goto-char (point-min))
(search-forward "leading")
(expect (ttl-in-comment) :to-be-truthy))))

(describe "ttl-in-blank-node"
(it "is truthy inside square brackets"
(with-temp-buffer
(insert "ex:s ex:p [ ex:a 1 ] .\n")
(ttl-mode)
(goto-char (point-min))
(search-forward "ex:a")
(expect (ttl-in-blank-node) :to-be-truthy)))

(it "is nil in an ordinary statement"
(with-temp-buffer
(insert "ex:s ex:p ex:o .\n")
(ttl-mode)
(goto-char (point-min))
(search-forward "ex:o")
(expect (ttl-in-blank-node) :to-be nil))))

(describe "ttl-in-string"
(it "treats single-quoted text as a string"
(with-temp-buffer
(insert "ex:s ex:p 'hello world' .\n")
(ttl-mode)
(goto-char (point-min))
(search-forward "hello")
(expect (ttl-in-string) :to-be-truthy)))

(it "is nil outside strings"
(with-temp-buffer
(insert "ex:s ex:p ex:o .\n")
(ttl-mode)
(goto-char (point-min))
(search-forward "ex:o")
(expect (ttl-in-string) :to-be nil))))

(describe "ttl-electric-dot"
(it "refuses to insert a period inside a blank node"
(with-temp-buffer
(insert "ex:s ex:p [ ex:a 1 ")
(ttl-mode)
(goto-char (point-max))
(let ((before (buffer-string)))
(ttl-electric-dot)
(expect (buffer-string) :to-equal before))))

;; jeeger/ttl-mode issue #3: a dot belongs in a string even in a blank node.
(it "allows a period inside a string within a blank node"
(with-temp-buffer
(insert "ex:s ex:p [ ex:a 'text")
(ttl-mode)
(goto-char (point-max))
(let ((before (buffer-string)))
(ttl-electric-dot)
(expect (buffer-string) :to-equal (concat before "."))))))

(describe "ttl-mode highlighting"
;; jeeger/ttl-mode issue #5: a dash is a valid prefix character.
(it "highlights a prefix containing a dash"
(expect (ttl-test-face-at "my-prefix:thing a ex:Y ." "my-prefix")
:to-be 'font-lock-type-face))

;; jeeger/ttl-mode issue #4: quoted literals are strings.
(it "highlights a single-quoted literal as a string"
(expect (ttl-test-face-at "ex:s ex:p 'a literal' ." "literal")
:to-be 'font-lock-string-face)))

;;; ttl-mode-test.el ends here
Loading