More fixes

* Use word_wrap()

* add_proper_report utility fn; have addCombatReportAuto use this

* Update Lua API.rst

* Update Gui.cpp
develop
Ryan Williams 2022-05-28 12:35:49 -07:00 committed by GitHub
parent 7d2ecae8b4
commit 2b29431806
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 273 additions and 198 deletions

@ -8,9 +8,9 @@
DFHack is a Dwarf Fortress memory access library, distributed with scripts
and plugins implementing a wide variety of useful functions and tools.
The full documentation [is available online here](https://dfhack.readthedocs.org),
from the README.html page in the DFHack distribution, or as raw text in the `./docs` folder.
The full documentation [is available online here](https://dfhack.readthedocs.org).
It is also accessible via the README.html page in the DFHack distribution or as raw text in the `./docs` folder.
If you're an end-user, modder, or interested in contributing to DFHack -
go read those docs.
If that's unclear, or you need more help checkout our [support page](https://docs.dfhack.org/en/latest/docs/Support.html) for up-to-date options.
If the docs are unclear or you need more help, please check out our [support page](https://docs.dfhack.org/en/latest/docs/Support.html) for ways to contact the DFHack developers.

@ -240,7 +240,7 @@ end
-- output doesn't trigger its own dfhack.printerr usage detection (see
-- detect_printerr below)
local orig_printerr = dfhack.printerr
local function wrap_expect(func, private)
local function wrap_expect(func, private, path)
return function(...)
private.checks = private.checks + 1
local ret = {func(...)}
@ -269,7 +269,7 @@ local function wrap_expect(func, private)
end
-- Skip any frames corresponding to C calls, or Lua functions defined in another file
-- these could include pcall(), with_finalize(), etc.
if info.what == 'Lua' and info.short_src == caller_src then
if info.what == 'Lua' and (info.short_src == caller_src or info.short_src == path) then
orig_printerr((' at %s:%d'):format(info.short_src, info.currentline))
end
frame = frame + 1
@ -278,7 +278,7 @@ local function wrap_expect(func, private)
end
end
local function build_test_env()
local function build_test_env(path)
local env = {
test = utils.OrderedTable(),
-- config values can be overridden in the test file to define
@ -309,7 +309,7 @@ local function build_test_env()
checks_ok = 0,
}
for name, func in pairs(expect) do
env.expect[name] = wrap_expect(func, private)
env.expect[name] = wrap_expect(func, private, path)
end
setmetatable(env, {__index = _G})
return env, private
@ -345,9 +345,9 @@ local function finish_tests(done_command)
end
local function load_tests(file, tests)
local short_filename = file:sub((file:find('test') or -4)+5, -1)
local short_filename = file:sub((file:find('test') or -4) + 5, -1)
print('Loading file: ' .. short_filename)
local env, env_private = build_test_env()
local env, env_private = build_test_env(file)
local code, err = loadfile(file, 't', env)
if not code then
dfhack.printerr('Failed to load file: ' .. tostring(err))

@ -1 +1 @@
Subproject commit 3e877cbb3c9bc8f22946053e70490d2e5431f4d5
Subproject commit 6ac8628a3c7a1677b27fb007db96f665b684a183

@ -1007,17 +1007,19 @@ Fortress mode
* ``dfhack.gui.pauseRecenter(pos[,pause])``
``dfhack.gui.pauseRecenter(x,y,z[,pause])``
Same as ``resetDwarfmodeView``, but also recenter if ``x`` isn't ``-30000``, and respects
RECENTER_INTERFACE_SHUTDOWN_MS (the delay before input is recognized when a recenter occurs) in DF's init.txt.
Same as ``resetDwarfmodeView``, but also recenter if ``x`` isn't ``-30000``. Respects
``RECENTER_INTERFACE_SHUTDOWN_MS`` in DF's ``init.txt`` (the delay before input is recognized when a recenter occurs.)
* ``dfhack.gui.recenterViewscreen(pos[,zoom])``
``dfhack.gui.recenterViewscreen(x,y,z[,zoom])``
``dfhack.gui.recenterViewscreen([zoom])``
Recenter the view on a position using a specific zoom type. If no position is given,
recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type`` (see: `enum definition <https://github.com/DFHack/df-structures/blob/master/df.announcements.xml#L438>`_),
where ``Generic`` skips recentering and enforces valid view bounds (the same as x = -30000,) ``Item`` brings
the position onscreen without centering, and ``Unit`` centers the screen on the position. Default zoom type is Item.
recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type``
(see: `enum definition <https://github.com/DFHack/df-structures/blob/master/df.announcements.xml>`_),
where ``df.report_zoom_type.Generic`` skips recentering and enforces valid view bounds (the same as x = -30000,)
``df.report_zoom_type.Item`` brings the position onscreen without centering, and
``df.report_zoom_type.Unit`` centers the screen on the position. Default zoom type is ``df.report_zoom_type.Item``.
* ``dfhack.gui.revealInDwarfmodeMap(pos)``
@ -1091,11 +1093,15 @@ Announcements
* ``dfhack.gui.autoDFAnnouncement(report,text)``
``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring])``
Takes a ``df.report_init`` (see: `structure definition <https://github.com/DFHack/df-structures/blob/master/df.announcements.xml#L451>`_)
and a string and processes them just like DF does. Sometimes this means the announcement won't occur.
Can also be built from parameters instead of a ``report_init``. Setting ``is_sparring`` to ``true`` means the report
will be added to sparring logs (if applicable) rather than hunting or combat. Text is parsed using ``&`` as an escape character, with ``&r``
adding a blank line (equivalent to ``\n \n``,) ``&&`` being just ``&``, and any other combination causing neither character to display.
Takes a ``df.report_init`` (see: `structure definition <https://github.com/DFHack/df-structures/blob/master/df.announcements.xml>`_)
and a string and processes them just like DF does. Can also be built from parameters instead of a ``report_init``.
Setting ``is_sparring`` to *true* means the report will be added to sparring logs (if applicable) rather than hunting or combat.
The announcement will not display if units are involved and the player can't see them (or hear, for adventure mode sound announcement types.)
Text is parsed using ``&`` as an escape character, with ``&r`` adding a blank line (equivalent to ``\n \n``,)
``&&`` being just ``&``, and any other combination causing neither character to display.
If you want a guaranteed announcement without parsing, use ``dfhack.gui.showAutoAnnouncement`` instead.
Other
~~~~~

@ -36,7 +36,13 @@ end
function MessageBox:getWantedFrameSize()
local label = self.subviews.label
local width = math.max(self.frame_width or 0, 20, #(self.frame_title or '') + 4)
return math.max(width, label:getTextWidth()), label:getTextHeight()
local text_area_width = label:getTextWidth()
if label.frame_inset then
-- account for scroll icons
text_area_width = text_area_width + (label.frame_inset.l or 0)
text_area_width = text_area_width + (label.frame_inset.r or 0)
end
return math.max(width, text_area_width), label:getTextHeight()
end
function MessageBox:onRenderFrame(dc,rect)

@ -478,8 +478,16 @@ function Label:render_scroll_icons(dc, x, y1, y2)
end
end
function Label:postComputeFrame()
self:update_scroll_inset()
function Label:computeFrame(parent_rect)
local frame_rect,body_rect = Label.super.computeFrame(self, parent_rect)
self.frame_rect = frame_rect
self.frame_body = parent_rect:viewport(body_rect or frame_rect)
self:update_scroll_inset() -- frame_body is now set
-- recalc with updated frame_inset
return Label.super.computeFrame(self, parent_rect)
end
function Label:preUpdateLayout()

@ -32,12 +32,17 @@ function _patch_impl(patches_raw, callback, restore_only)
end
--[[
Replaces `table[key]` with `value`, calls `callback()`, then restores the
original value of `table[key]`.
Usage:
patch(table, key, value, callback)
patch({
{table, key, value},
{table2, key2, value2},
}, callback)
]]
function mock.patch(...)
local args = {...}
@ -57,12 +62,18 @@ function mock.patch(...)
end
--[[
Restores the original value of `table[key]` after calling `callback()`.
Equivalent to: patch(table, key, table[key], callback)
Usage:
restore(table, key, callback)
restore({
{table, key},
{table2, key2},
}, callback)
]]
function mock.restore(...)
local args = {...}
@ -81,9 +92,19 @@ function mock.restore(...)
return _patch_impl(patches, callback, true)
end
function mock.func(...)
--[[
Returns a callable object that tracks the arguments it is called with, then
passes those arguments to `callback()`.
The returned object has the following properties:
- `call_count`: the number of times the object has been called
- `call_args`: a table of function arguments (shallow-copied) corresponding
to each time the object was called
]]
function mock.observe_func(callback)
local f = {
return_values = {...},
call_count = 0,
call_args = {},
}
@ -101,11 +122,36 @@ function mock.func(...)
end
end
table.insert(self.call_args, args)
return table.unpack(self.return_values)
return callback(...)
end,
})
return f
end
--[[
Returns a callable object similar to `mock.observe_func()`, but which when
called, only returns the given `return_value`(s) with no additional side effects.
Intended for use by `patch()`.
Usage:
func(return_value [, return_value2 ...])
See `observe_func()` for a description of the return value.
The return value also has an additional `return_values` field, which is a table
of values returned when the object is called. This can be modified.
]]
function mock.func(...)
local f
f = mock.observe_func(function()
return table.unpack(f.return_values)
end)
f.return_values = {...}
return f
end
return mock

@ -1370,46 +1370,15 @@ DFHACK_EXPORT void Gui::writeToGamelog(std::string message)
namespace
{ // Utility functions for reports
/*bool parseReportString(std::vector<std::string> *out, const std::string &str, size_t line_length = 73)
{
bool parseReportString(std::vector<std::string> *out, const std::string &str, size_t line_length = 73)
{ // parse a string into output strings like DF does for reports
if (str.empty() || line_length == 0)
return false;
string parsed = "";
string parsed;
size_t i = 0;
while (i < str.length())
{
if (str[i] == '&') // escape character
{
i++; // ignore the '&' itself
if (i >= str.length())
break;
if (str[i] == 'r') // "&r" adds a blank line
parsed += "\n \n"; // DF adds a line with a space for some reason
else if (str[i] == '&') // "&&" is '&'
parsed += "&";
// else next char is ignored
}
else
{
parsed += str[i];
}
i++;
}
return word_wrap(out, parsed, line_length, true);
}*/
/*bool parseReportString(std::vector<std::string> *out, const std::string &str, size_t line_length = 73)
{
if (str.empty() || line_length == 0)
return false;
string parsed = "";
size_t i = 0;
while (i < str.length())
do
{
if (str[i] == '&') // escape character
{
@ -1419,95 +1388,21 @@ namespace
if (str[i] == 'r') // "&r" adds a blank line
{
word_wrap(out, parsed, line_length, true);
word_wrap(out, parsed, line_length, false, true);
out->push_back(" "); // DF adds a line with a space for some reason
parsed = "";
parsed.clear();
}
else if (str[i] == '&') // "&&" is '&'
parsed += "&";
parsed.push_back('&');
// else next char is ignored
}
else
{
parsed += str[i];
}
i++;
parsed.push_back(str[i]);
}
while (++i < str.length());
if (parsed != "")
word_wrap(out, parsed, line_length, true);
return true;
}*/
bool parseReportString(std::vector<std::string> *out, const std::string &str, size_t line_length)
{ // out vector will contain strings cut to line_length, avoiding cutting up words
// Reverse-engineered from DF announcement code, fixes applied
if (str.empty() || line_length == 0)
return false;
bool ignore_space = false;
string current_line = "";
size_t iter = 0;
do
{
if (ignore_space)
{
if (str[iter] == ' ')
continue;
ignore_space = false;
}
if (str[iter] == '&') // escape character
{
iter++; // ignore the '&' itself
if (iter >= str.length())
break;
if (str[iter] == 'r') // "&r" adds a blank line
{
if (!current_line.empty())
{
out->push_back(string(current_line));
current_line = "";
}
out->push_back(" "); // DF adds a line with a space for some reason
continue; // don't add 'r' to current_line
}
else if (str[iter] != '&')
{ // not "&&", don't add character to current_line
continue;
}
}
current_line += str[iter];
if (current_line.length() > line_length)
{
size_t i = current_line.length(); // start of current word
size_t j; // end of previous word
while (--i > 0 && current_line[i] != ' '); // find start of current word
if (i == 0)
{ // need to push at least one char
j = i = line_length; // last char ends up on next line
}
else
{
j = i;
while (j > 1 && current_line[j - 1] == ' ')
j--; // consume excess spaces at the split point
}
out->push_back(current_line.substr(0, j)); // push string before j
if (current_line[i] == ' ')
i++; // don't keep this space
current_line.erase(0, i); // current_line now starts at last word or is empty
ignore_space = current_line.empty(); // ignore leading spaces on new line
}
} while (++iter < str.length());
if (!current_line.empty())
out->push_back(current_line);
if (parsed.length())
word_wrap(out, parsed, line_length, false, true);
return true;
}
@ -1549,7 +1444,7 @@ namespace
}
int32_t check_repeat_report(vector<string> &results)
{
{ // returns the new repeat count, else 0
if (*gamemode == game_mode::DWARF && !results.empty() && world->status.reports.size() >= results.size())
{
auto &reports = world->status.reports;
@ -1697,6 +1592,19 @@ bool Gui::addCombatReport(df::unit *unit, df::unit_report_type slot, int report_
return true;
}
namespace
{ // An additional utility function for reports
bool add_proper_report(df::unit *unit, bool is_sparring, int report_index)
{
if (is_sparring)
return addCombatReport(unit, unit_report_type::Sparring, report_index);
else if (unit->job.current_job != NULL && unit->job.current_job->job_type == job_type::Hunt)
return addCombatReport(unit, unit_report_type::Hunting, report_index);
else
return addCombatReport(unit, unit_report_type::Combat, report_index);
}
}
bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int report_index)
{
using df::global::world;
@ -1710,14 +1618,7 @@ bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int r
bool ok = false;
if (mode.bits.UNIT_COMBAT_REPORT)
{
if (unit->flags2.bits.sparring)
ok |= addCombatReport(unit, unit_report_type::Sparring, report_index);
else if (unit->job.current_job && unit->job.current_job->job_type == job_type::Hunt)
ok |= addCombatReport(unit, unit_report_type::Hunting, report_index);
else
ok |= addCombatReport(unit, unit_report_type::Combat, report_index);
}
ok |= add_proper_report(unit, unit->flags2.bits.sparring, report_index);
if (mode.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE)
{
@ -1783,28 +1684,24 @@ void Gui::showAutoAnnouncement(
bool Gui::autoDFAnnouncement(df::report_init r, string message)
{ // Reverse-engineered from DF announcement code
if (!world->allow_announcements)
{
DEBUG(gui).print("Skipped announcement because world->allow_announcements is false:\n%s\n", message.c_str());
return false;
}
df::announcement_flags a_flags;
if (is_valid_enum_item(r.type))
a_flags = df::global::d_init->announcements.flags[r.type];
else
}
else if (!is_valid_enum_item(r.type))
{
WARN(gui).print("Invalid announcement type:\n%s\n", message.c_str());
return false;
}
if (message.empty())
else if (message.empty())
{
Core::printerr("Empty announcement %u\n", r.type); // DF would print this to errorlog.txt
return false;
}
df::announcement_flags a_flags = df::global::d_init->announcements.flags[r.type];
// Check if the announcement will actually be announced
if (*gamemode == game_mode::ADVENTURE)
{
@ -1814,11 +1711,13 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message)
r.type != announcement_type::CONFLICT_CONVERSATION &&
r.type != announcement_type::MECHANISM_SOUND)
{ // If not sound, make sure we can see pos
if ((world->units.active.empty() || (r.unit1 != world->units.active[0] && r.unit2 != world->units.active[0])) &&
((Maps::getTileDesignation(r.pos)->whole & 0x10) == 0x0)) // Adventure mode uses this bit to determine current visibility
{
DEBUG(gui).print("Adventure mode announcement not heard:\n%s\n", message.c_str());
return false;
if (world->units.active.empty() || (r.unit1 != world->units.active[0] && r.unit2 != world->units.active[0]))
{ // Adventure mode reuses a dwarf mode digging designation bit to determine current visibility
if (!Maps::isValidTilePos(r.pos) || (Maps::getTileDesignation(r.pos)->whole & 0x10) == 0x0)
{
DEBUG(gui).print("Adventure mode announcement not detected:\n%s\n", message.c_str());
return false;
}
}
}
}
@ -1826,7 +1725,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message)
{ // Dwarf mode
if ((r.unit1 != NULL || r.unit2 != NULL) && (r.unit1 == NULL || Units::isHidden(r.unit1)) && (r.unit2 == NULL || Units::isHidden(r.unit2)))
{
DEBUG(gui).print("Dwarf mode announcement not heard:\n%s\n", message.c_str());
DEBUG(gui).print("Dwarf mode announcement not detected:\n%s\n", message.c_str());
return false;
}
@ -1927,24 +1826,10 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message)
if (a_flags.bits.UNIT_COMBAT_REPORT)
{
if (r.unit1 != NULL)
{
if (r.flags.bits.hostile_combat)
success |= addCombatReport(r.unit1, unit_report_type::Combat, new_report_index);
else if (r.unit1->job.current_job != NULL && r.unit1->job.current_job->job_type == job_type::Hunt)
success |= addCombatReport(r.unit1, unit_report_type::Hunting, new_report_index);
else
success |= addCombatReport(r.unit1, unit_report_type::Sparring, new_report_index);
}
success |= add_proper_report(r.unit1, !r.flags.bits.hostile_combat, new_report_index);
if (r.unit2 != NULL)
{
if (r.flags.bits.hostile_combat)
success |= addCombatReport(r.unit2, unit_report_type::Combat, new_report_index);
else if (r.unit2->job.current_job != NULL && r.unit2->job.current_job->job_type == job_type::Hunt)
success |= addCombatReport(r.unit2, unit_report_type::Hunting, new_report_index);
else
success |= addCombatReport(r.unit2, unit_report_type::Sparring, new_report_index);
}
success |= add_proper_report(r.unit2, !r.flags.bits.hostile_combat, new_report_index);
}
if (a_flags.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE)
@ -1953,6 +1838,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message)
{
if (recent_report(r.unit1, slot))
success |= addCombatReport(r.unit1, slot, new_report_index);
if (recent_report(r.unit2, slot))
success |= addCombatReport(r.unit2, slot, new_report_index);
}
@ -2044,9 +1930,7 @@ df::coord Gui::getCursorPos()
}
void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_type zoom)
{
// Reverse-engineered from DF announcement code, also used when scrolling
{ // Reverse-engineered from DF announcement code, also used when scrolling
auto dims = getDwarfmodeViewDims();
int32_t w = dims.map_x2 - dims.map_x1 + 1;
int32_t h = dims.map_y2 - dims.map_y1 + 1;
@ -2062,7 +1946,7 @@ void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_ty
}
else // report_zoom_type::Item
{
if (new_win_x > (x - 5))
if (new_win_x > (x - 5)) // equivalent to: "while (new_win_x > x - 5) new_win_x -= 10;"
new_win_x -= (new_win_x - (x - 5) - 1) / 10 * 10 + 10;
if (new_win_y > (y - 5))
new_win_y -= (new_win_y - (y - 5) - 1) / 10 * 10 + 10;
@ -2072,32 +1956,28 @@ void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_ty
new_win_y += ((y + 5 - h) - new_win_y - 1) / 10 * 10 + 10;
}
if (new_win_z != z)
ui_sidebar_menus->minimap.need_scan = true;
new_win_z = z;
}
*df::global::window_x = clip_range(new_win_x, 0, (world->map.x_count - w));
*df::global::window_y = clip_range(new_win_y, 0, (world->map.y_count - h));
*df::global::window_z = clip_range(new_win_z, 0, (world->map.z_count - 1));
ui_sidebar_menus->minimap.need_render = true;
ui_sidebar_menus->minimap.need_scan = true;
return;
}
void Gui::pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause)
{
// Reverse-engineered from DF announcement code
{ // Reverse-engineered from DF announcement code
if (*gamemode != game_mode::DWARF)
return;
resetDwarfmodeView(pause);
if (x != -30000)
{
recenterViewscreen(x, y, z, report_zoom_type::Item);
ui_sidebar_menus->minimap.need_render = true;
ui_sidebar_menus->minimap.need_scan = true;
}
if (init->input.pause_zoom_no_interface_ms > 0)
{

@ -1 +1 @@
Subproject commit a59495e8f72115909772e6df20a7b9dec272f14c
Subproject commit 1dfe6c5ab9887507cdcdebdd9390352fe0bba2dd

@ -1 +1 @@
Subproject commit 05d46b32a3aff4f5f98534fdccfbf9ae88dd31a3
Subproject commit 741c84ada2ec7fdd0083744afab294d9a1b6e370

@ -0,0 +1,94 @@
-- test -dhack/scripts/devel/tests -twidgets.Label
local gui = require('gui')
local widgets = require('gui.widgets')
local xtest = {} -- use to temporarily disable tests (change `function xtest.somename` to `function xxtest.somename`)
local wait = function(n)
delay(n or 30) -- enable for debugging the tests
end
local fs = defclass(fs, gui.FramedScreen)
fs.ATTRS = {
frame_style = gui.GREY_LINE_FRAME,
frame_title = 'TestFramedScreen',
frame_width = 10,
frame_height = 10,
frame_inset = 0,
focus_path = 'test-framed-screen',
}
function test.Label_correct_frame_body_with_scroll_icons()
local t = {}
for i = 1, 12 do
t[#t+1] = tostring(i)
t[#t+1] = NEWLINE
end
function fs:init(args)
self:addviews{
widgets.Label{
view_id = 'text',
frame_inset = 0,
text = t,
--show_scroll_icons = 'right',
},
}
end
local o = fs{}
--o:show()
--wait()
expect.eq(o.subviews.text.frame_body.width, 9, "Label's frame_body.x2 and .width should be one smaller because of show_scroll_icons.")
--o:dismiss()
end
function test.Label_correct_frame_body_with_few_text_lines()
local t = {}
for i = 1, 10 do
t[#t+1] = tostring(i)
t[#t+1] = NEWLINE
end
function fs:init(args)
self:addviews{
widgets.Label{
view_id = 'text',
frame_inset = 0,
text = t,
--show_scroll_icons = 'right',
},
}
end
local o = fs{}
--o:show()
--wait()
expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.")
--o:dismiss()
end
function test.Label_correct_frame_body_without_show_scroll_icons()
local t = {}
for i = 1, 12 do
t[#t+1] = tostring(i)
t[#t+1] = NEWLINE
end
function fs:init(args)
self:addviews{
widgets.Label{
view_id = 'text',
frame_inset = 0,
text = t,
show_scroll_icons = false,
},
}
end
local o = fs{}
--o:show()
--wait()
expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.")
--o:dismiss()
end

@ -208,9 +208,44 @@ function test.func_call_return_value()
end
function test.func_call_return_multiple_values()
local f = mock.func(7,5,{imatable='snarfsnarf'})
local f = mock.func(7, 5, {imatable='snarfsnarf'})
local a, b, c = f()
expect.eq(7, a)
expect.eq(5, b)
expect.table_eq({imatable='snarfsnarf'}, c)
end
function test.observe_func()
-- basic end-to-end test for common cases;
-- most edge cases are covered by mock.func() tests
local counter = 0
local function target()
counter = counter + 1
return counter
end
local observer = mock.observe_func(target)
expect.eq(observer(), 1)
expect.eq(counter, 1)
expect.eq(observer.call_count, 1)
expect.table_eq(observer.call_args, {{}})
expect.eq(observer('x', 'y'), 2)
expect.eq(counter, 2)
expect.eq(observer.call_count, 2)
expect.table_eq(observer.call_args, {{}, {'x', 'y'}})
end
function test.observe_func_error()
local function target()
error('asdf')
end
local observer = mock.observe_func(target)
expect.error_match('asdf', function()
observer('x')
end)
-- make sure the call was still tracked
expect.eq(observer.call_count, 1)
expect.table_eq(observer.call_args, {{'x'}})
end