formspec_ast/test.lua

811 lines
23 KiB
Lua

dofile('init.lua')
-- luacheck: read_globals assert it describe
local function test_parse(fs, expected_tree)
-- Make single elements lists and add formspec_version
if expected_tree.type then
expected_tree = {expected_tree}
end
if not expected_tree.formspec_version then
expected_tree.formspec_version = 1
end
local tree = assert(formspec_ast.parse(fs))
assert.same(tree, expected_tree)
end
local function test_parse_unparse(fs, expected_tree)
test_parse(fs, expected_tree)
local unparsed_fs = assert(formspec_ast.unparse(expected_tree))
assert.equals(fs, unparsed_fs)
end
it("can parse complex escapes", function()
test_parse_unparse([=[label[123,456;yay abc def\, ghi\; jkl mno \]\\]]=], {
formspec_version = 1,
{
type = 'label',
x = 123,
y = 456,
label = 'yay abc def, ghi; jkl mno ]\\'
}
})
end)
it("can round-trip most elements", function()
local fs = [[
formspec_version[2]
size[5,2]
padding[1,2]
no_prepend[]
container[1,1]
label[0,0;Containers are fun\]\\]
container[-1,-1]
button[0.5,0;4,1;name;Label]
container_end[]
label[0,1;Nested containers work too.]
scroll_container[0,2;1,1;scrollbar;vertical]
button[0.5,0;4,1;name;Label]
scroll_container_end[]
container_end[]
image[0,1;1,1;air.png]
set_focus[name;true]
dropdown[0,0;1;test;abc,def,ghi,jkl;2]
dropdown[0,0;1;test;abc,def,ghi,jkl;2;true]
field_close_on_enter[my-field;false]
bgcolor[blue]
bgcolor[blue;true]
bgcolor[blue;both;green]
tooltip[1,2;3,4;text]
tooltip[elem;text;bgcolor]
background9[1,2;3,4;bg.png;false;5]
background9[1,2;3,4;bg.png;false;5,6]
background9[1,2;3,4;bg.png;false;5,6,7,8]
tablecolumns[text;image;color,option=value;tree]
list[test;test2;1,2;3,4;5]
list[test6;test7;8,9;10,11]
image_button[1,2;3,4;img.png;name;label]
image_button[1,2;3,4;img.png;name;label;false;true]
image_button[1,2;3,4;img.png;name;label;true;false;img2.png]
image_button_exit[1,2;3,4;img.png;name;label]
image_button_exit[1,2;3,4;img.png;name;label;false;true]
image_button_exit[1,2;3,4;img.png;name;label;true;false;img2.png]
image[1,2;3,4;air.png;5]
image[1,2;3,4;air.png;5,6]
image[1,2;3,4;air.png;5,6,7,8]
field_enter_after_edit[test;true]
button_url[1,2;3,4;test;Test;https://example.com]
button_url_exit[1,2;3,4;test2;Test 2;https://example.com]
]]
fs = ('\n' .. fs):gsub('\n[ \n]*', '')
test_parse_unparse(fs, {
formspec_version = 2,
{
type = "size",
w = 5,
h = 2,
},
{
type = "padding",
x = 1,
y = 2,
},
{
type = "no_prepend"
},
{
type = "container",
x = 1,
y = 1,
{
type = "label",
x = 0,
y = 0,
label = "Containers are fun]\\",
},
{
type = "container",
x = -1,
y = -1,
{
type = "button",
x = 0.5,
y = 0,
w = 4,
h = 1,
name = "name",
label = "Label",
},
},
{
type = "label",
x = 0,
y = 1,
label = "Nested containers work too.",
},
{
type = "scroll_container",
x = 0,
y = 2,
w = 1,
h = 1,
scrollbar_name = "scrollbar",
orientation = "vertical",
-- scroll_factor = nil,
{
h = 1,
y = 0,
label = "Label",
w = 4,
name = "name",
x = 0.5,
type = "button"
},
},
},
{
type = "image",
x = 0,
y = 1,
w = 1,
h = 1,
texture_name = "air.png",
},
{
type = "set_focus",
name = "name",
force = true,
},
{
type = "dropdown",
x = 0,
y = 0,
w = 1,
name = "test",
items = {"abc", "def", "ghi", "jkl"},
selected_idx = 2,
},
{
type = "dropdown",
x = 0,
y = 0,
w = 1,
name = "test",
items = {"abc", "def", "ghi", "jkl"},
selected_idx = 2,
index_event = true,
},
{
type = "field_close_on_enter",
name = "my-field",
close_on_enter = false,
},
{
type = "bgcolor",
bgcolor = "blue",
},
{
type = "bgcolor",
bgcolor = "blue",
fullscreen = true,
},
{
type = "bgcolor",
bgcolor = "blue",
fullscreen = "both",
fbgcolor = "green",
},
{
type = "tooltip",
x = 1,
y = 2,
w = 3,
h = 4,
tooltip_text = "text",
},
{
type = "tooltip",
gui_element_name = "elem",
tooltip_text = "text",
bgcolor = "bgcolor",
},
{
type = "background9",
x = 1,
y = 2,
w = 3,
h = 4,
texture_name = "bg.png",
auto_clip = false,
middle_x = 5,
},
{
type = "background9",
x = 1,
y = 2,
w = 3,
h = 4,
texture_name = "bg.png",
auto_clip = false,
middle_x = 5,
middle_y = 6,
},
{
type = "background9",
x = 1,
y = 2,
w = 3,
h = 4,
texture_name = "bg.png",
auto_clip = false,
middle_x = 5,
middle_y = 6,
middle_x2 = 7,
middle_y2 = 8,
},
{
type = "tablecolumns",
tablecolumns = {
{type = "text", opts = {}},
{type = "image", opts = {}},
{type = "color", opts = {option = "value"}},
{type = "tree", opts = {}},
},
},
{
type = "list",
inventory_location = "test",
list_name = "test2",
x = 1,
y = 2,
w = 3,
h = 4,
starting_item_index = 5,
},
{
type = "list",
inventory_location = "test6",
list_name = "test7",
x = 8,
y = 9,
w = 10,
h = 11,
},
{
type = "image_button",
x = 1,
y = 2,
w = 3,
h = 4,
texture_name = "img.png",
name = "name",
label = "label",
},
{
type = "image_button",
x = 1,
y = 2,
w = 3,
h = 4,
texture_name = "img.png",
name = "name",
label = "label",
noclip = false,
drawborder = true,
},
{
type = "image_button",
x = 1,
y = 2,
w = 3,
h = 4,
texture_name = "img.png",
name = "name",
label = "label",
noclip = true,
drawborder = false,
pressed_texture_name = "img2.png",
},
{
type = "image_button_exit",
x = 1,
y = 2,
w = 3,
h = 4,
texture_name = "img.png",
name = "name",
label = "label",
},
{
type = "image_button_exit",
x = 1,
y = 2,
w = 3,
h = 4,
texture_name = "img.png",
name = "name",
label = "label",
noclip = false,
drawborder = true,
},
{
type = "image_button_exit",
x = 1,
y = 2,
w = 3,
h = 4,
texture_name = "img.png",
name = "name",
label = "label",
noclip = true,
drawborder = false,
pressed_texture_name = "img2.png",
},
{
type = "image",
x = 1,
y = 2,
w = 3,
h = 4,
texture_name = "air.png",
middle_x = 5,
},
{
type = "image",
x = 1,
y = 2,
w = 3,
h = 4,
texture_name = "air.png",
middle_x = 5,
middle_y = 6,
},
{
type = "image",
x = 1,
y = 2,
w = 3,
h = 4,
texture_name = "air.png",
middle_x = 5,
middle_y = 6,
middle_x2 = 7,
middle_y2 = 8,
},
{
type = "field_enter_after_edit",
name = "test",
enter_after_edit = true,
},
{
type = "button_url",
x = 1,
y = 2,
w = 3,
h = 4,
name = "test",
label = "Test",
url = "https://example.com",
},
{
type = "button_url_exit",
x = 1,
y = 2,
w = 3,
h = 4,
name = "test2",
label = "Test 2",
url = "https://example.com",
},
})
end)
local function remove_trailing_params(elem_s, elem, ...)
local res = {}
local strings = {}
local optional_params = {...}
for i = #optional_params, 1, -1 do
local p = optional_params[i]
local no_copy = {}
if type(p) == "table" then
for _, param in ipairs(p) do
no_copy[param] = true
end
else
no_copy[p] = true
end
res[i] = elem
strings[i] = elem_s
elem_s = elem_s:gsub(";[^;]+%]$", "]")
local old_elem = elem
elem = {}
for k, v in pairs(old_elem) do
if not no_copy[k] then
elem[k] = v
end
end
end
return table.concat(strings, ""), res
end
it("can parse model[]", function()
test_parse_unparse(remove_trailing_params(
"model[1,2;3,4;abc;def;ghi,jkl;5,6;true;false;7,8;9]",
{
type = "model",
x = 1,
y = 2,
w = 3,
h = 4,
name = "abc",
mesh = "def",
textures = {"ghi", "jkl"},
rotation_x = 5,
rotation_y = 6,
continuous = true,
mouse_control = false,
frame_loop_begin = 7,
frame_loop_end = 8,
animation_speed = 9
},
{"rotation_x", "rotation_y"}, "continuous", "mouse_control",
{"frame_loop_begin", "frame_loop_end"}, "animation_speed"
))
end)
it("can round-trip style[]", function()
local s = 'style[test1,test2;def=ghi]style_type[test;abc=def]'
assert.equals(s, assert(formspec_ast.interpret(s)))
test_parse('style[name,name2;bgcolor=blue;textcolor=yellow]', {
type = "style",
selectors = {
"name",
"name2",
},
props = {
bgcolor = "blue",
textcolor = "yellow",
},
})
end)
-- Test item/items compatibility
it("can parse dropdown[]", function()
local expected = 'dropdown[0,0;1,2;test;abc,def,ghi,jkl;2;true]'
assert.equals(
expected,
assert(formspec_ast.unparse({
{
type = "dropdown",
x = 0,
y = 0,
w = 1,
h = 2,
name = "test",
items = {"abc", "def", "ghi", "jkl"},
selected_idx = 2,
index_event = true,
},
}))
)
assert.equals(
expected,
assert(formspec_ast.unparse({
{
type = "dropdown",
x = 0,
y = 0,
w = 1,
h = 2,
name = "test",
item = {"abc", "def", "ghi", "jkl"},
selected_idx = 2,
index_event = true,
},
}))
)
end)
-- Ensure the style[] unparse compatibility works correctly
it("can unparse style with name", function()
local expected = 'style_type[test;abc=def]'
assert.equals(
expected,
assert(formspec_ast.unparse({
{
type = 'style_type',
name = 'test',
props = {
abc = 'def',
},
}
}))
)
assert.equals(
expected,
assert(formspec_ast.unparse({
{
type = 'style_type',
selectors = {
'test',
},
props = {
abc = 'def',
},
}
}))
)
end)
-- Ensure flatten works correctly
it("flattens formspecs correctly", function()
assert.equals(
'label[0,0;abc]label[2,2;def]scroll_container[1,1;2,2;test;vertical]' ..
'image[1,1;1,1;def]scroll_container_end[]',
formspec_ast.unparse(formspec_ast.flatten(assert(formspec_ast.parse([[
label[0,0;abc]
container[3,2]
container[-1,0]
label[0,0;def]
container_end[]
container_end[]
scroll_container[1,1;2,2;test;vertical]
image[1,1;1,1;def]
scroll_container_end[]
]]))))
)
end)
it("interprets invsize[] and escapes correctly", function()
assert.equals(assert(formspec_ast.interpret('invsize[12,34]')),
'size[12,34]')
assert.equals(assert(formspec_ast.interpret('label[1,2;abc\\')),
'label[1,2;abc]')
assert.equals(assert(formspec_ast.interpret('label[1,2;abc\\\\')),
'label[1,2;abc\\\\]')
assert.equals(formspec_ast.formspec_escape('label[1,2;abc\\def]'),
'label\\[1\\,2\\;abc\\\\def\\]')
end)
it("safe_interpret", function()
assert.equals(
formspec_ast.safe_interpret([[
formspec_version[5.1]
size[3,3]
label[0,0;Hi]
image[1,2;3,4;a^b\[c]
formspec_ast:crash[]
textlist[1,2;3,4;test;a,b,c]
]]),
'formspec_version[5]size[3,3]label[0,0;Hi]image[1,2;3,4;a^b]' ..
'textlist[1,2;3,4;test;a,b,c]'
)
end)
it("can parse tabheader", function()
local fs =
"tabheader[1,2;name;a,b,c;1]" ..
"tabheader[1,2;name;a,b,c;1;false;true]" ..
-- Width and height can only be specified if transparent and drawborder
-- are specified
"tabheader[1,2;3,4;name;a,b,c;1;false;true]" ..
"tabheader[1,2;3;name;a,b,c;1;false;true]" ..
"tabheader[1,2;3;name;a,b,c;1;;]"
test_parse_unparse(fs, {
{
type = "tabheader",
x = 1, y = 2,
name = "name",
captions = {"a", "b", "c"},
current_tab = 1
},
{
type = "tabheader",
x = 1, y = 2,
name = "name",
captions = {"a", "b", "c"},
current_tab = 1,
transparent = false,
draw_border = true
},
{
type = "tabheader",
x = 1, y = 2, w = 3, h = 4,
name = "name",
captions = {"a", "b", "c"},
current_tab = 1,
transparent = false,
draw_border = true
},
{
type = "tabheader",
x = 1, y = 2, h = 3,
name = "name",
captions = {"a", "b", "c"},
current_tab = 1,
transparent = false,
draw_border = true
},
{
type = "tabheader",
x = 1, y = 2, h = 3,
name = "name",
captions = {"a", "b", "c"},
current_tab = 1,
},
})
end)
it("does not parse invalid tabheader elements", function()
assert.is_nil(formspec_ast.parse("tabheader[1,2;name;a,b,c]"))
assert.is_nil(formspec_ast.parse("tabheader[1,2;name;a,b,c;1;false]"))
assert.is_nil(formspec_ast.parse("tabheader[1,2;3,4;name;a,b,c;1]"))
end)
describe("helpers", function ()
describe("walk", function ()
it("walks over every element", function ()
local tree = {
{ type = "box", color = "green" },
{ type = "label", label = "the text" },
{
type = "container",
{ type = "label", label = "the text" }
}
}
for node in formspec_ast.walk(tree) do
node.visited = true
end
assert.same({
{ type = "box", color = "green", visited = true },
{ type = "label", label = "the text", visited = true },
{
type = "container",
visited = true,
{ type = "label", label = "the text", visited = true }
}
}, tree)
end)
it("can be stopped", function ()
local tree = {
{ type = "box", color = "green" },
{ type = "label", label = "the text" },
{
type = "container",
{ type = "label", label = "the text" }
},
{ type = "label", label = "the text" }
}
local count = 0
for node in formspec_ast.walk(tree) do
count = count + 1
node.visited = true
if count == 3 then break end
end
assert.same({
{ type = "box", color = "green", visited = true },
{ type = "label", label = "the text", visited = true },
{
type = "container",
visited = true,
{ type = "label", label = "the text" }
},
{ type = "label", label = "the text" }
}, tree)
end)
it("can accept custom element list", function ()
local tree = {
{ type = "box", color = "green" },
{ type = "label", label = "the text" },
{
type = "nonsensewordhere",
{ type = "label", label = "the text" }
},
{
type = "container",
{ type = "label", label = "the text" }
},
{ type = "label", label = "the text" }
}
for node in formspec_ast.walk(tree, {nonsensewordhere = true}) do
node.visited = true
end
assert.same({
{ type = "box", color = "green", visited = true },
{ type = "label", label = "the text", visited = true },
{
type = "nonsensewordhere",
visited = true,
{ type = "label", label = "the text", visited = true }
},
{
type = "container",
visited = true,
{ type = "label", label = "the text" }
},
{ type = "label", label = "the text", visited = true }
}, tree)
end)
it("can provide parent info when walking", function ()
local tree = {
{ type = "box", color = "green" },
{ type = "label", label = "the text" },
{
type = "container",
{ type = "label", label = "the text" }
},
{ type = "label", label = "the text" }
}
local logged_indexes = {}
for node, parent, index in formspec_ast.walk(tree) do
node.visited = true
parent.parent_of = (parent.parent_of or 0) + 1
logged_indexes[#logged_indexes+1] = index
end
assert.same({
parent_of = 4,
{ type = "box", color = "green", visited = true },
{ type = "label", label = "the text", visited = true },
{
type = "container",
visited = true,
parent_of = 1,
{ type = "label", label = "the text", visited = true }
},
{ type = "label", label = "the text", visited = true },
}, tree)
-- NOTE: this is, in effect, asserting the order of the crawl
assert.same({ 1, 2, 3, 1, 4}, logged_indexes)
end)
it("parent info can be modified without failure", function ()
-- INFO: This test is a regression test.
local tree = {
{ type = "box", color = "green" },
{ type = "label", label = "the text" },
{
type = "container",
{ type = "label", label = "the text" }
}
}
local found_child = false
for node, parent in formspec_ast.walk(tree) do
if node.type == "container" then
node[#node+1] = { type = "label", label = "the new text" }
elseif parent.type == "container" then
node.is_child_thingy = true
if not found_child then
found_child = true
node.type = "container"
node[1] = { type = "box", color = "red" }
node.label = nil
end
end
end
assert.same({
{ type = "box", color = "green" },
{ type = "label", label = "the text" },
{
type = "container",
{
type = "container",
is_child_thingy = true,
{ type = "box", color = "red", is_child_thingy = true }
},
{
type = "label",
label = "the new text",
is_child_thingy = true
}
}
}, tree)
end)
end)
end)