diff --git a/acandy.lua b/acandy.lua
index 3a16a36..bf10a7e 100644
--- a/acandy.lua
+++ b/acandy.lua
@@ -27,7 +27,8 @@ local VOID_ELEMS, HTML_ELEMS, RAW_TEXT_ELEMS = (function ()
local config = require('.elem_config')
return config.VOID_ELEMS, config.HTML_ELEMS, config.RAW_TEXT_ELEMS
end)()
-
+local classes = require('.classes')
+local node_mts = classes.node_mts
---@class Symbol
@@ -61,47 +62,51 @@ end
---@type metatable
local Fragment_mt
----Append the serialized string of the Fragment to `strs`.
----Use len to avoid calling `#strs` repeatedly. This improves performance by
+---Append the serialized string of the Fragment to `buff`.
+---Use len to avoid calling `#buff` repeatedly. This improves performance by
---~1/3.
----@param strs table
+---@param buff table
---@param frag table
----@param strs_len? integer number of `strs`, used to optimize performance.
+---@param buff_len? integer length of `buff`, used to optimize performance.
---@param no_encode? boolean true to prevent encoding strings, e.g. when in ')), '')
+ end)
+
it('has properties', function ()
local li = a.li 'item 1'
local list = a.ol {id = "my-id", li}
@@ -249,6 +257,11 @@ end)
describe('String escaping', function ()
local str = '& \194\160 " \' < > & " ' < >'
+ local function func_returns_string()
+ return str
+ end
+ -- issue: #11
+ local table_with_tostring = setmetatable({}, {__tostring = func_returns_string})
it('replaces `& NBSP " < >` in attribute values with named references', function ()
local answer = '
'
assert.are.equal(tostring(a.div[str]), answer)
assert.are.equal(tostring(a.div[{class = str}]), answer)
+ assert.are.equal(tostring(a.div[{class = table_with_tostring}]), answer)
assert.are.equal(tostring(a.div {class = str}), answer)
+ assert.are.equal(tostring(a.div {class = table_with_tostring}), answer)
end)
it('replaces `& NBSP < >` in texts other than attribute value with named references', function ()
@@ -266,6 +281,20 @@ describe('String escaping', function ()
..'& " ' < >'
..''
assert.are.equal(tostring(a.div(str)), answer)
+ assert.are.equal(tostring(a.div(func_returns_string)), answer)
+ assert.are.equal(tostring(a.div(table_with_tostring)), answer)
+ end)
+
+ it('does not escape string from acandy nodes', function ()
+ local node1 = a.div
+ assert.are.equal(tostring(a.div(node1)), ''..tostring(node1)..'
')
+ local node2 = acandy.Doctype.HTML
+ assert.are.equal(
+ tostring(Fragment {node2, a.html {a.head, a.body}}),
+ tostring(node2)..''
+ )
+ local node3 = acandy.Comment(str)
+ assert.are.equal(tostring(a.div(node3)), ''..tostring(node3)..'
')
end)
it("does not replace characters in an object's properties", function ()
@@ -276,18 +305,94 @@ describe('String escaping', function ()
assert.are.equal(elem.class, str)
assert.are.equal(elem[1], str)
end)
+
+ it("does not encode any character in raw text elements", function ()
+ assert.are.equal(tostring(a.style(str)), '')
+ assert.are.equal(tostring(a.script(str)), '')
+ end)
end)
-describe('Raw text element', function ()
+describe('`acandy.Comment`', function ()
+ -- spec: https://html.spec.whatwg.org/#comments
+
+ local Comment = acandy.Comment
+ it('arg is content', function ()
+ local comment = Comment('<>&"')
+ assert.are.equal(tostring(comment), '')
+ assert.are.equal(tostring(a.div(comment)), '')
+ end)
+
+ it('raises an error when content is invalid', function ()
+ -- the text must not start with the string ">" or "->",
+ -- nor contain the strings "", or "--!>",
+ -- nor end with the string "', '>text', '->', '->text',
+ '', 'text-->', '-->text', 'text-->text',
+ '--!>', 'text--!>', '--!>text', 'text--!>text',
+ '', 'text>text', 'text->', 'text->text',
+ ' and .
+ '')
+ end
+ end)
+end)
+
+describe('`acandy.Doctype`', function ()
+ -- spec: https://html.spec.whatwg.org/#the-doctype
+
+ local Doctype = acandy.Doctype
+ it('has HTML5 doctype', function ()
+ assert.are.equal(tostring(Doctype.HTML), '')
+ assert.are.equal(tostring(Fragment {Doctype.HTML, a.html}), '')
+ end)
+end)
+
+describe('`acandy.Raw`', function ()
local str = '& \194\160 " \' < > & " ' < >'
+ it('is not escaped as a child node', function ()
+ local raw = acandy.Raw(str)
+ assert.are.equal(tostring(raw), str)
+ assert.are.equal(tostring(a.div(raw)), ''..str..'
')
+ assert.are.equal(tostring(a.div {raw}), ''..str..'
')
+ end)
- it("does not encode any character in text child", function ()
- assert.are.equal(tostring(a.style(str)), '')
- assert.are.equal(tostring(a.script(str)), '')
+ it('can concat with another `Raw`, returns `Raw`', function ()
+ local str1, str2 = '1'..str, '2'..str
+ local raw1 = acandy.Raw(str1)
+ local raw2 = acandy.Raw(str2)
+ assert.are.equal(getmetatable(raw1), getmetatable(raw2), getmetatable(raw1..raw2))
+ assert.are.equal(tostring(raw1..raw2), str1..str2)
+ assert.are.equal(tostring(a.div(raw1..raw2)), ''..str1..str2..'
')
+ assert.are.equal(tostring(raw1..raw2..raw1), str1..str2..str1)
+ assert.are.equal(tostring(a.div(raw1..raw2..raw1)), ''..str1..str2..str1..'
')
end)
- it("does not check end tag", function ()
- assert.are.equal(tostring(a.style('')), '')
- assert.are.equal(tostring(a.script('')), '')
+ it('cannot concat with a string or number', function ()
+ local raw = acandy.Raw(str)
+ assert.has.error(function ()
+ local never = raw..str
+ end)
+ assert.has.error(function ()
+ local never = str..raw
+ end)
+ assert.has.error(function ()
+ local never = raw..1
+ end)
+ assert.has.error(function ()
+ local never = (1)..raw
+ end)
end)
end)
diff --git a/utils.lua b/utils.lua
index 0c83015..78e5d0e 100644
--- a/utils.lua
+++ b/utils.lua
@@ -203,10 +203,10 @@ function utils.parse_shorthand_attrs(str)
local id = nil
str = s_gsub(str, '#([^%s#]*)', function (s)
if s == '' then
- error('empty id found in '..string.format('%q', str), 4)
+ error('empty id found in '..('%q'):format(str), 4)
end
if id then
- error('two or more ids found in '..string.format('%q', str), 4)
+ error('two or more ids found in '..('%q'):format(str), 4)
end
id = s
return ''