Merge remote-tracking branch 'upstream/client-side-tests' into io-streams-enhance

# Conflicts:
#	src/logic/scripting/descriptors_manager.cpp
#	src/logic/scripting/descriptors_manager.hpp
This commit is contained in:
Onran 2026-06-21 23:55:08 +09:00
commit db014cca09
46 changed files with 1172 additions and 282 deletions

View file

@ -19,7 +19,7 @@ local function update_line(line, uptime)
end
end
events.on("core:chat", function(message)
local core_chat_handler = events.on("core:chat", function(message)
while #lines >= max_lines do
document[lines[1][1]]:destruct()
table.remove(lines, 1)
@ -36,7 +36,7 @@ end)
function on_open()
if not initialized then
initialized = true
document.root:setInterval(1/animation_fps * 1000, function ()
local uptime = time.uptime()
for _, line in ipairs(lines) do
@ -52,3 +52,7 @@ function on_open()
end)
end
end
function on_destroy()
events.remove("core:chat", core_chat_handler)
end

View file

@ -148,3 +148,29 @@ function error(message, level)
end
__error(message, level)
end
local LoggerClass = {
__index = {
info = function (self, text)
debug.log(string.format("[%s] %s", self.name, text))
end,
warning = function (self, text)
debug.warning(string.format("[%s] %s", self.name, text))
end,
error = function (self, text)
debug.error(string.format("[%s] %s", self.name, text))
end
}
}
local _getinfo = debug.getinfo
function debug.Logger(name)
if not name then
local source = _getinfo(2, "S").source
name = file.remove_ext(source)
else
name = string.format("%s:%s", file.prefix(_getinfo(2, "S").source), name)
end
return setmetatable({name=name}, LoggerClass)
end

View file

@ -14,6 +14,7 @@ function events.on(event, func)
events.handlers[event] = {}
end
table.insert(events.handlers[event], func)
return func
end
function events.reset(event, func)
@ -24,6 +25,14 @@ function events.reset(event, func)
end
end
function events.remove(event, handler)
local handlers = events.handlers[event]
if not handlers then
return
end
table.remove_value(handlers, handler)
end
function events.remove_by_prefix(prefix)
for name, handlers in pairs(events.handlers) do
local actualname = name

View file

@ -23,6 +23,10 @@ function file.parent(path)
return dir
end
function file.remove_ext(path)
return string.format("%s/%s", file.parent(path), file.stem(path))
end
function file.path(path)
local pos = path:find(':')
return path:sub(pos + 1)

View file

@ -59,11 +59,7 @@ end
function table.extend(t1, t2)
for i, v in pairs(t2) do
if type(i) == "number" then
t1[#t1 + 1] = v
else
t1[i] = v
end
t1[i] = v
end
return t1
@ -201,3 +197,11 @@ function table.tostring(t)
end
return s..']'
end
function table.keys(t)
local keys = {}
for key, _ in pairs(t) do
table.insert(keys, key)
end
return keys
end

View file

@ -77,15 +77,18 @@ function Element.new(docname, name)
end
-- the engine automatically creates an instance for every ui document (layout)
local Document = {}
function Document.new(docname)
return setmetatable({name=docname}, {
__index=function(self, k)
local elem = Element.new(self.name, k)
rawset(self, k, elem)
return elem
local Document = {
__index=function(self, k)
if type(k) ~= "string" then
error("element id is not a string")
end
})
local elem = Element.new(self.name, k)
rawset(self, k, elem)
return elem
end
}
function Document.new(docname)
return setmetatable({name=docname}, Document)
end
local RadioGroup = {}

View file

@ -4,16 +4,27 @@ local lib = {
read = file.__read_descriptor,
write = file.__write_descriptor,
seek = file.__seek_descriptor,
tell = file.__tell_descriptor,
flush = file.__flush_descriptor,
available = file.__available_descriptor,
is_alive = file.__has_descriptor,
close = file.__close_descriptor
}
local __open_descriptor = file.__open_descriptor
file.__open_descriptor = nil
file.__read_descriptor = nil
file.__write_descriptor = nil
file.__seek_descriptor = nil
file.__tell_descriptor = nil
file.__flush_descriptor = nil
file.__has_descriptor = nil
file.__close_descriptor = nil
return function(path, mode)
return io_stream.new(
file.__open_descriptor(path, mode),
__open_descriptor(path, mode),
mode:find('b') ~= nil,
lib
)
end
end

View file

@ -0,0 +1,139 @@
local __app = __vc_app
local function get_node_center(node)
local pos = node.wpos
local size = node.size
return pos[1] + size[1] / 2, pos[2] + size[2] / 2
end
local function types_check_enum(value, enum)
if value == nil then
return true, nil
end
if type(value) ~= "string" then
return false, string.format("string expected [%s]", table.concat(table.keys(enum), "|"))
end
return true, nil
end
local function types_check_number(value)
if value == nil then
return true, nil
end
local value_type = type(value)
if value_type ~= "number" then
return false, string.format("number expected, got %s", value_type)
end
end
local function check_option(t, key, func, ...)
local value = t[key]
if value == nil then
return nil
end
local success, message = func(value, ...)
if not success then
error(string.format("invalid option '%s': %s", key, message))
end
end
local buttons_enum = {
left = 0,
right = 1,
middle = 2,
}
local no_options = {}
local _tick = __app.tick
local _set_button_pressed = test.set_button_pressed
local _set_key_pressed = test.set_key_pressed
local _enter_text = test.enter_text
test.set_button_pressed = nil
test.set_key_pressed = nil
test.enter_text = nil
function test.click(node, options)
options = options or no_options
check_option(options, 'button', types_check_enum, buttons_enum)
check_option(options, 'timeout', types_check_number)
local button = buttons_enum[options.button or 'left']
local timeout = options.timeout or 1000
local tm = time.precise_time()
while (time.precise_time() - tm) * 1000 < timeout do
if node.exists then
break
end
_tick()
end
if not node.exists then
error(string.format("timeout %s ms exceeded: element %s does not exists",
math.floor(timeout), string.escape(rawget(node, 'name'))))
end
local center_x, center_y = get_node_center(node)
_set_button_pressed(button, center_x, center_y, true)
_tick()
_set_button_pressed(button, center_x, center_y, false)
_tick()
end
-- TODO: replace with resolving in C++ side
function test.find_by_attr(node, attr, value)
if getmetatable(node) == Document then
return test.find_by_attr(node.root, attr, value)
end
if node[attr] == value then
return node
end
local i = 1
while true do
local subnode = node[i]
if subnode == nil then
break
end
local found = test.find_by_attr(subnode, attr, value)
if found then
return found
end
i = i + 1
end
end
function test.find_by_text(node, text)
return test.find_by_attr(node, "text", text)
end
function test.find_by_attr_presence(node, attr)
if node[attr] then
return node
end
local i = 1
while true do
local subnode = node[i]
if subnode == nil then
break
end
local found = test.find_by_attr_presence(subnode, attr)
if found then
return found
end
i = i + 1
end
end
function test.fill(node, text, options)
test.click(node, options)
__app.tick()
_enter_text(text)
test.press("enter")
end
function test.press(key)
_set_key_pressed(key, true)
__app.tick()
_set_key_pressed(key, false)
__app.tick()
end

View file

@ -388,6 +388,10 @@ function io_stream:seek(mode, offset)
self.ioLib.seek(self.descriptor, mode, offset)
end
function io_stream:tell()
return self.ioLib.tell(self.descriptor)
end
function io_stream:is_alive()
return self.ioLib.is_alive(self.descriptor)
end
@ -414,4 +418,4 @@ function io_stream:flush()
if self.flushMode ~= FLUSH_MODE_ONLY_BUFFER then self.ioLib.flush(self.descriptor) end
end
return io_stream
return io_stream

View file

@ -127,6 +127,10 @@ require "core:internal/extensions/inventory"
asserts = require "core:internal/asserts"
events = require "core:internal/events"
if test then
require "core:internal/test"
end
function pack.unload(prefix)
events.remove_by_prefix(prefix)
end