Fix tabheader parsing and use busted for tests

This commit is contained in:
luk3yx 2023-04-19 12:50:03 +12:00
parent 68712195f3
commit c7629b718d
8 changed files with 654 additions and 630 deletions

View File

@ -4,8 +4,17 @@ jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@v3
- name: lint - name: lint
uses: Roang-zero1/factorio-mod-luacheck@master uses: Roang-zero1/factorio-mod-luacheck@master
with: with:
luacheckrc_url: "" luacheckrc_url: ""
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run unit tests
uses: lunarmodules/busted@v2.1.2
with:
args: --verbose test.lua

1
.gitignore vendored
View File

@ -1,2 +1 @@
__pycache__/ __pycache__/
test.lua

View File

@ -48,6 +48,10 @@ all attributes are lowercase.
While I try to reduce backwards incompatibilities, sometimes they are necessary While I try to reduce backwards incompatibilities, sometimes they are necessary
to either fix bugs in formspec_ast or for implementing new formspec features. to either fix bugs in formspec_ast or for implementing new formspec features.
#### April 2023
- The `current_tab` value of dropdowns is now parsed as a number.
#### February 2022 #### February 2022
- The value of scrollbars is now a number instead of a string. - The value of scrollbars is now a number instead of a string.

View File

@ -54,7 +54,7 @@ a[47] = {{{"listelems", "string"}, "..."}}
a[48] = {"selected_idx", "number"} a[48] = {"selected_idx", "number"}
a[49] = {"transparent", "boolean"} a[49] = {"transparent", "boolean"}
a[50] = {{{"captions", "string"}, "..."}} a[50] = {{{"captions", "string"}, "..."}}
a[51] = {"current_tab", "string"} a[51] = {"current_tab", "number"}
a[52] = {"draw_border", "boolean"} a[52] = {"draw_border", "boolean"}
a[53] = {{{"items", "string"}, "..."}} a[53] = {{{"items", "string"}, "..."}}
a[54] = {"index_event", "boolean"} a[54] = {"index_event", "boolean"}
@ -64,4 +64,4 @@ a[57] = {{a[56]}}
a[58] = {{"props", "table"}, "..."} a[58] = {{"props", "table"}, "..."}
a[59] = {a[26]} a[59] = {a[26]}
a[60] = {{{{{"selectors", "string"}, "..."}}, a[58]}, {a[59], a[58]}} a[60] = {{{{{"selectors", "string"}, "..."}}, a[58]}, {a[59], a[58]}}
return {["formspec_version"] = {{{"version", "number"}}}, ["size"] = {{a[3]}, {{a[1], a[2], {"fixed_size", "boolean"}}}}, ["position"] = a[5], ["anchor"] = a[5], ["padding"] = a[5], ["no_prepend"] = a[7], ["real_coordinates"] = {{{"bool", "boolean"}}}, ["container"] = a[5], ["container_end"] = a[7], ["scroll_container"] = {{a[4], a[3], a[8], a[9], {"scroll_factor", "number"}}, {a[4], a[3], a[8], a[9]}}, ["scroll_container_end"] = a[7], ["list"] = {{a[10], a[11], a[4], a[3], {"starting_item_index", "number"}}, {a[10], a[11], a[4], a[3]}}, ["listring"] = {{a[10], a[11]}, a[6]}, ["listcolors"] = {{a[12], a[13], a[14], {"tooltip_bgcolor", "string"}, {"tooltip_fontcolor", "string"}}, {a[12], a[13], a[14]}, {a[12], a[13]}}, ["tooltip"] = {{a[4], a[3], a[15], a[16], a[17]}, {a[4], a[3], a[15], a[16]}, {a[18], a[15], a[16], a[17]}, {a[4], a[3], a[15]}, {a[18], a[15], a[16]}, {a[18], a[15]}}, ["image"] = {{a[4], a[3], a[19], a[22]}, {a[4], a[3], a[19], a[23]}, {a[4], a[3], a[19], a[24]}, a[25]}, ["animated_image"] = {{a[4], a[3], a[26], a[19], a[27], a[28], a[29], a[22]}, {a[4], a[3], a[26], a[19], a[27], a[28], a[29], a[23]}, {a[4], a[3], a[26], a[19], a[27], a[28], a[29], a[24]}, {a[4], a[3], a[26], a[19], a[27], a[28], a[29]}, {a[4], a[3], a[26], a[19], a[27], a[28]}}, ["model"] = {{a[4], a[3], a[26], a[30], a[31], a[32], a[33], a[34], a[35], {"animation_speed", "number"}}, {a[4], a[3], a[26], a[30], a[31], a[32], a[33], a[34], a[35]}, {a[4], a[3], a[26], a[30], a[31], a[32], a[33], a[34]}, {a[4], a[3], a[26], a[30], a[31], a[32], a[33]}, {a[4], a[3], a[26], a[30], a[31], a[32]}, {a[4], a[3], a[26], a[30], a[31]}}, ["item_image"] = {{a[4], a[3], a[36]}}, ["bgcolor"] = {{a[16], a[37], {"fbgcolor", "string"}}, {a[16], a[37]}, {a[16]}}, ["background"] = {{a[4], a[3], a[19], a[38]}, a[25]}, ["background9"] = {{a[4], a[3], a[19], a[38], a[22]}, {a[4], a[3], a[19], a[38], a[23]}, {a[4], a[3], a[19], a[38], a[24]}}, ["pwdfield"] = a[40], ["field"] = {a[42], {a[26], a[39], a[41]}}, ["field_close_on_enter"] = {{a[26], {"close_on_enter", "boolean"}}}, ["textarea"] = {a[42]}, ["label"] = a[43], ["hypertext"] = {{a[4], a[3], a[26], {"text", "string"}}}, ["vertlabel"] = a[43], ["button"] = a[40], ["image_button"] = a[46], ["item_image_button"] = {{a[4], a[3], a[36], a[26], a[39]}}, ["button_exit"] = a[40], ["image_button_exit"] = a[46], ["textlist"] = {{a[4], a[3], a[26], a[47], a[48], a[49]}, {a[4], a[3], a[26], a[47], a[48]}, {a[4], a[3], a[26], a[47]}}, ["tabheader"] = {{a[4], a[2], a[26], a[50], a[51], a[49], a[52]}, {a[4], a[3], a[26], a[50], a[51], a[49], a[52]}, {a[4], a[26], a[50], a[51], a[49], a[52]}, {a[4], a[2], a[26], a[50], a[51], a[49]}, {a[4], a[3], a[26], a[50], a[51], a[49]}, {a[4], a[26], a[50], a[51], a[49]}, {a[4], a[2], a[26], a[50], a[51]}, {a[4], a[3], a[26], a[50], a[51]}, {a[4], a[26], a[50], a[51]}}, ["box"] = {{a[4], a[3], {"color", "string"}}}, ["dropdown"] = {{a[4], a[3], a[26], a[53], a[48], a[54]}, {a[4], a[1], a[26], a[53], a[48], a[54]}, {a[4], a[3], a[26], a[53], a[48]}, {a[4], a[1], a[26], a[53], a[48]}}, ["checkbox"] = {{a[4], a[26], a[39], {"selected", "boolean"}}, {a[4], a[26], a[39]}}, ["scrollbar"] = {{a[4], a[3], a[9], a[26], a[55], {{"scrollbar_bg", "string"}, {"slider", "string"}, {"arrow_up", "string"}, {"arrow_down", "string"}}}, {a[4], a[3], a[9], a[26], a[55]}}, ["scrollbaroptions"] = a[57], ["table"] = {{a[4], a[3], a[26], {{{"cells", "string"}, "..."}}, a[48]}}, ["tableoptions"] = a[57], ["tablecolumns"] = {{{{{"type", "string"}, a[56]}, "..."}}}, ["style"] = a[60], ["style_type"] = a[60], ["set_focus"] = {{a[26], {"force", "boolean"}}, a[59]}} return {["formspec_version"] = {{{"version", "number"}}}, ["size"] = {{a[3]}, {{a[1], a[2], {"fixed_size", "boolean"}}}}, ["position"] = a[5], ["anchor"] = a[5], ["padding"] = a[5], ["no_prepend"] = a[7], ["real_coordinates"] = {{{"bool", "boolean"}}}, ["container"] = a[5], ["container_end"] = a[7], ["scroll_container"] = {{a[4], a[3], a[8], a[9], {"scroll_factor", "number"}}, {a[4], a[3], a[8], a[9]}}, ["scroll_container_end"] = a[7], ["list"] = {{a[10], a[11], a[4], a[3], {"starting_item_index", "number"}}, {a[10], a[11], a[4], a[3]}}, ["listring"] = {{a[10], a[11]}, a[6]}, ["listcolors"] = {{a[12], a[13], a[14], {"tooltip_bgcolor", "string"}, {"tooltip_fontcolor", "string"}}, {a[12], a[13], a[14]}, {a[12], a[13]}}, ["tooltip"] = {{a[4], a[3], a[15], a[16], a[17]}, {a[4], a[3], a[15], a[16]}, {a[18], a[15], a[16], a[17]}, {a[4], a[3], a[15]}, {a[18], a[15], a[16]}, {a[18], a[15]}}, ["image"] = {{a[4], a[3], a[19], a[22]}, {a[4], a[3], a[19], a[23]}, {a[4], a[3], a[19], a[24]}, a[25]}, ["animated_image"] = {{a[4], a[3], a[26], a[19], a[27], a[28], a[29], a[22]}, {a[4], a[3], a[26], a[19], a[27], a[28], a[29], a[23]}, {a[4], a[3], a[26], a[19], a[27], a[28], a[29], a[24]}, {a[4], a[3], a[26], a[19], a[27], a[28], a[29]}, {a[4], a[3], a[26], a[19], a[27], a[28]}}, ["model"] = {{a[4], a[3], a[26], a[30], a[31], a[32], a[33], a[34], a[35], {"animation_speed", "number"}}, {a[4], a[3], a[26], a[30], a[31], a[32], a[33], a[34], a[35]}, {a[4], a[3], a[26], a[30], a[31], a[32], a[33], a[34]}, {a[4], a[3], a[26], a[30], a[31], a[32], a[33]}, {a[4], a[3], a[26], a[30], a[31], a[32]}, {a[4], a[3], a[26], a[30], a[31]}}, ["item_image"] = {{a[4], a[3], a[36]}}, ["bgcolor"] = {{a[16], a[37], {"fbgcolor", "string"}}, {a[16], a[37]}, {a[16]}}, ["background"] = {{a[4], a[3], a[19], a[38]}, a[25]}, ["background9"] = {{a[4], a[3], a[19], a[38], a[22]}, {a[4], a[3], a[19], a[38], a[23]}, {a[4], a[3], a[19], a[38], a[24]}}, ["pwdfield"] = a[40], ["field"] = {a[42], {a[26], a[39], a[41]}}, ["field_close_on_enter"] = {{a[26], {"close_on_enter", "boolean"}}}, ["textarea"] = {a[42]}, ["label"] = a[43], ["hypertext"] = {{a[4], a[3], a[26], {"text", "string"}}}, ["vertlabel"] = a[43], ["button"] = a[40], ["image_button"] = a[46], ["item_image_button"] = {{a[4], a[3], a[36], a[26], a[39]}}, ["button_exit"] = a[40], ["image_button_exit"] = a[46], ["textlist"] = {{a[4], a[3], a[26], a[47], a[48], a[49]}, {a[4], a[3], a[26], a[47], a[48]}, {a[4], a[3], a[26], a[47]}}, ["tabheader"] = {{a[4], a[2], a[26], a[50], a[51], a[49], a[52]}, {a[4], a[3], a[26], a[50], a[51], a[49], a[52]}, {a[4], a[26], a[50], a[51], a[49], a[52]}, {a[4], a[26], a[50], a[51]}}, ["box"] = {{a[4], a[3], {"color", "string"}}}, ["dropdown"] = {{a[4], a[3], a[26], a[53], a[48], a[54]}, {a[4], a[1], a[26], a[53], a[48], a[54]}, {a[4], a[3], a[26], a[53], a[48]}, {a[4], a[1], a[26], a[53], a[48]}}, ["checkbox"] = {{a[4], a[26], a[39], {"selected", "boolean"}}, {a[4], a[26], a[39]}}, ["scrollbar"] = {{a[4], a[3], a[9], a[26], a[55], {{"scrollbar_bg", "string"}, {"slider", "string"}, {"arrow_up", "string"}, {"arrow_down", "string"}}}, {a[4], a[3], a[9], a[26], a[55]}}, ["scrollbaroptions"] = a[57], ["table"] = {{a[4], a[3], a[26], {{{"cells", "string"}, "..."}}, a[48]}}, ["tableoptions"] = a[57], ["tablecolumns"] = {{{{{"type", "string"}, a[56]}, "..."}}}, ["style"] = a[60], ["style_type"] = a[60], ["set_focus"] = {{a[26], {"force", "boolean"}}, a[59]}}

View File

@ -483,7 +483,7 @@ tabheader:
- [name, string] - [name, string]
- - - [captions, string] - - - [captions, string]
- '...' - '...'
- [current_tab, string] - [current_tab, number]
- [transparent, boolean] - [transparent, boolean]
- [draw_border, boolean] - [draw_border, boolean]
- - - [x, number] - - - [x, number]
@ -493,7 +493,7 @@ tabheader:
- [name, string] - [name, string]
- - - [captions, string] - - - [captions, string]
- '...' - '...'
- [current_tab, string] - [current_tab, number]
- [transparent, boolean] - [transparent, boolean]
- [draw_border, boolean] - [draw_border, boolean]
- - - [x, number] - - - [x, number]
@ -501,54 +501,15 @@ tabheader:
- [name, string] - [name, string]
- - - [captions, string] - - - [captions, string]
- '...' - '...'
- [current_tab, string] - [current_tab, number]
- [transparent, boolean] - [transparent, boolean]
- [draw_border, boolean] - [draw_border, boolean]
- - - [x, number] - - - [x, number]
- [y, number] - [y, number]
- [h, number]
- [name, string] - [name, string]
- - - [captions, string] - - - [captions, string]
- '...' - '...'
- [current_tab, string] - [current_tab, number]
- [transparent, boolean]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- - - [captions, string]
- '...'
- [current_tab, string]
- [transparent, boolean]
- - - [x, number]
- [y, number]
- [name, string]
- - - [captions, string]
- '...'
- [current_tab, string]
- [transparent, boolean]
- - - [x, number]
- [y, number]
- [h, number]
- [name, string]
- - - [captions, string]
- '...'
- [current_tab, string]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- - - [captions, string]
- '...'
- [current_tab, string]
- - - [x, number]
- [y, number]
- [name, string]
- - - [captions, string]
- '...'
- [current_tab, string]
table: table:
- - - [x, number] - - - [x, number]
- [y, number] - [y, number]

View File

@ -14,7 +14,7 @@ def _make_known(**kwargs):
return known return known
_known = _make_known( _known = _make_known(
number=('x', 'y', 'w', 'h', 'selected_idx', 'version', number=('x', 'y', 'w', 'h', 'selected_idx', 'version', 'current_tab',
'starting_item_index', 'scroll_factor', 'frame_count', 'starting_item_index', 'scroll_factor', 'frame_count',
'frame_duration', 'frame_start', 'animation_speed', 'value'), 'frame_duration', 'frame_start', 'animation_speed', 'value'),
boolean=('auto_clip', 'fixed_size', 'transparent', 'draw_border', 'bool', boolean=('auto_clip', 'fixed_size', 'transparent', 'draw_border', 'bool',
@ -193,6 +193,19 @@ def _image_button_hook(params):
params.append(('pressed_texture_name', 'string')) params.append(('pressed_texture_name', 'string'))
yield params yield params
# Work around tabheader's documentation
@hook('tabheader')
def _tabheader_hook(params):
yield params
if len(params) == 6:
assert params[4:] == [
('transparent', 'boolean'),
('draw_border', 'boolean'),
]
yield params[:4]
# Support MultiCraft's non-standard scrollbar styling # Support MultiCraft's non-standard scrollbar styling
# WARNING: This may be removed or broken without notice # WARNING: This may be removed or broken without notice
@hook('scrollbar') @hook('scrollbar')
@ -283,7 +296,7 @@ def parse(data):
return res return res
URL = 'https://github.com/minetest/minetest/raw/master/doc/lua_api.txt' URL = 'https://github.com/minetest/minetest/raw/master/doc/lua_api.md'
def fetch_and_parse(*, url=URL): def fetch_and_parse(*, url=URL):
with urllib.request.urlopen(url) as f: with urllib.request.urlopen(url) as f:
raw = f.read() raw = f.read()

619
test.lua Normal file
View File

@ -0,0 +1,619 @@
dofile('init.lua')
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]
]]
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,
},
})
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)

581
tests.lua
View File

@ -1,581 +0,0 @@
dofile('init.lua')
local function dump(obj)
if type(obj) == 'string' then
return ('%q'):format(obj)
elseif type(obj) == 'table' then
local entries = {}
for k, v in pairs(obj) do
if type(k) == 'string' and k:match('^[a-zA-Z_][a-zA-Z0-9_]*$') then
entries[#entries + 1] = k .. ' = ' .. dump(v)
else
entries[#entries + 1] = '[' .. dump(k) .. '] = ' .. dump(v)
end
end
table.sort(entries)
return '{' .. table.concat(entries, ', ') .. '}'
end
return tostring(obj)
end
local function equal(t1, t2)
if type(t1) ~= 'table' or type(t2) ~= 'table' then
return t1 == t2
end
for k, v in pairs(t1) do
if not equal(v, t2[k]) then
print(k, v, dump(t1), dump(t2))
return false
end
end
for k in pairs(t2) do
if t1[k] == nil then
return false
end
end
return true
end
local function assert_equal(obj1, ...)
for i = 1, select('#', ...) do
local objn = select(i, ...)
if not equal(obj1, objn) then
error(('%s ~= %s'):format(obj1, objn))
end
end
end
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_equal(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_equal(fs, unparsed_fs)
end
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 ]\\'
}
})
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]
]]
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,
},
})
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
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"
))
-- Make sure style[] (un)parses correctly
local s = 'style[test1,test2;def=ghi]style_type[test;abc=def]'
assert_equal(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",
},
})
-- Test item/items compatibility
assert_equal(
'dropdown[0,0;1,2;test;abc,def,ghi,jkl;2;true]',
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(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,
},
}))
)
-- Ensure the style[] unparse compatibility works correctly
assert_equal(
'style_type[test;abc=def]',
assert(formspec_ast.unparse({
{
type = 'style_type',
name = 'test',
props = {
abc = 'def',
},
}
})),
assert(formspec_ast.unparse({
{
type = 'style_type',
selectors = {
'test',
},
props = {
abc = 'def',
},
}
}))
)
-- Ensure flatten works correctly
assert_equal(
'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[]
]]))))
)
-- Ensure invsize[] is converted to size[]
assert_equal(assert(formspec_ast.interpret('invsize[12,34]')), 'size[12,34]')
assert_equal(assert(formspec_ast.interpret('label[1,2;abc\\')),
'label[1,2;abc]')
assert_equal(assert(formspec_ast.interpret('label[1,2;abc\\\\')),
'label[1,2;abc\\\\]')
assert_equal(formspec_ast.formspec_escape('label[1,2;abc\\def]'),
'label\\[1\\,2\\;abc\\\\def\\]')
assert_equal(
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]'
)
print('Tests pass')