feat(spec): Create visual error for widget layout tests (#3530)

The assert for `widget:layout` now visualizes the expected and actual
widget structure for comparison.

Signed-off-by: Lucas Schwiderski <lucas@lschwiderski.de>
This commit is contained in:
Lucas Schwiderski 2021-12-21 23:51:22 +01:00 committed by GitHub
parent f353499230
commit 1ab0f892d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 119 additions and 14 deletions

View File

@ -39,21 +39,128 @@ end
say:set("assertion.widget_fit.positive", "Offering (%s, %s) to widget and expected (%s, %s), but got (%s, %s)") say:set("assertion.widget_fit.positive", "Offering (%s, %s) to widget and expected (%s, %s), but got (%s, %s)")
assert:register("assertion", "widget_fit", widget_fit, "assertion.widget_fit.positive", "assertion.widget_fit.positive") assert:register("assertion", "widget_fit", widget_fit, "assertion.widget_fit.positive", "assertion.widget_fit.positive")
local function draw_layout_comparison(actual, expected)
local lines = {
"Widget layout does not match:\n",
}
local left, right = {}, {}
local function times(char, len)
local chars = {}
for _ = 1, len do
table.insert(chars, char)
end
return table.concat(chars, "")
end
local function wrap_line(width, x, line)
-- Two columns occupied by the `|` characters
local buffer = width - 2 - #line
local buffer_left = math.floor(buffer / 2)
local buffer_right = buffer - buffer_left
return string.format(
"%s|%s%s%s|",
times(" ", x), times(" ", buffer_left), line, times(" ", buffer_right)
)
end
local function draw_widget(widget)
local height = widget._height
local width = widget._width
local x = widget._matrix.x0
-- Two lines removed for the horizontal lines,
-- three for the lines with text
local fixed = 5
local buffer_top = math.floor((height - fixed) / 2)
local buffer_bottom = height - fixed - buffer_top
local widget_lines = {times("-", width)}
for _ = 1, buffer_top do
table.insert(widget_lines, wrap_line(width, x, " "))
end
table.insert(widget_lines, wrap_line(width, x, widget._name or "Widget"))
table.insert(widget_lines, wrap_line(width, x, string.format(
"Size %d,%d",
widget._width, widget._height
)))
table.insert(widget_lines, wrap_line(width, x, string.format(
"Pos %d,%d",
widget._matrix.x0, widget._matrix.y0
)))
for _ = 1, buffer_bottom do
table.insert(widget_lines, wrap_line(width, x, " "))
end
table.insert(widget_lines, times("-", width))
return widget_lines
end
local max_len = 0
for _, widget in ipairs(actual) do
for _, line in ipairs(draw_widget(widget)) do
max_len = math.max(max_len, #line)
table.insert(left, line)
end
end
for _, widget in ipairs(expected) do
for _, line in ipairs(draw_widget(widget)) do
table.insert(right, line)
end
end
table.insert(lines, string.format(
"%-"..max_len.."s %-"..max_len.."s",
"Actual:", "Expected:"
))
for i = 1, math.max(#left, #right) do
table.insert(lines, string.format("%-"..max_len.."s %s", left[i] or "", right[i] or ""))
end
-- Force one additional newline. Due to how the error message has to be set
-- up, we want to force `say`'s trailing quote into a new line.
table.insert(lines, "")
return table.concat(lines, "\n")
end
local function widget_layout(state, arguments) local function widget_layout(state, arguments)
if #arguments ~= 3 then if #arguments ~= 3 then
error("Have " .. #arguments .. " arguments, but need 3") error("Have " .. #arguments .. " arguments, but need 3")
end end
local widget = arguments[1] local widget = arguments[1]
local given = arguments[2] local width_actual, height_actual = arguments[2][1], arguments[2][2]
local expected = arguments[3] local expected = arguments[3]
local children = widget.layout and widget:layout({ "fake context" }, given[1], given[2]) or {}
local fits = true if not widget.layout then
if #children ~= #expected then arguments[1] = "Method `layout` is missing. Is this even a widget?"
fits = false return false
else end
for i = 1, #children do
local children = widget:layout({"fake context"}, width_actual, height_actual)
if type(children) ~= "table" then
arguments[1] = string.format(
"Expected table from widget:layout(), got %s",
type(children)
)
return
end
local num_children = #children
local fits = num_children == #expected
-- If the numbers don't match, we can skip checking them individually
if fits then
for i = 1, num_children do
local child, exp = children[i], expected[i] local child, exp = children[i], expected[i]
if child._widget ~= exp._widget or if child._widget ~= exp._widget or
child._width ~= exp._width or child._width ~= exp._width or
@ -69,15 +176,13 @@ local function widget_layout(state, arguments)
return true return true
end end
-- For proper error message, mess with the arguments arguments[1] = draw_layout_comparison(children, expected)
arguments[1] = expected
arguments[2] = children
arguments[3] = given[1]
arguments[4] = given[2]
return false return false
end end
say:set("assertion.widget_layout.positive", "Expected:\n%s\nbut got:\n%s\nwhen offering (%s, %s) to widget")
-- Hack the API by using the first argument as arbitrary string.
-- This gives actual flexibility in the error message.
say:set("assertion.widget_layout.positive", "%s")
assert:register("assertion", assert:register("assertion",
"widget_layout", "widget_layout",
widget_layout, widget_layout,