mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 18:41:51 +00:00
add 'test' library for e2e tests purposes
This commit is contained in:
parent
d2f272e403
commit
3429435ff5
7 changed files with 313 additions and 96 deletions
136
res/modules/internal/test.lua
Normal file
136
res/modules/internal/test.lua
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
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(node.id)))
|
||||
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 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ extern const luaL_Reg posteffectslib[]; // gfx.posteffects
|
|||
extern const luaL_Reg quatlib[];
|
||||
extern const luaL_Reg randomlib[];
|
||||
extern const luaL_Reg compressionlib[];
|
||||
extern const luaL_Reg testlib[];
|
||||
extern const luaL_Reg text3dlib[]; // gfx.text3d
|
||||
extern const luaL_Reg timelib[];
|
||||
extern const luaL_Reg tomllib[];
|
||||
|
|
|
|||
45
src/logic/scripting/lua/libs/libtest.cpp
Normal file
45
src/logic/scripting/lua/libs/libtest.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#include "api_lua.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
#include "window/input.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
static int l_set_button_pressed(lua::State* L) {
|
||||
int button = lua::touinteger(L, 1);
|
||||
int x = lua::tointeger(L, 2);
|
||||
int y = lua::tointeger(L, 3);
|
||||
bool pressed = lua::toboolean(L, 4);
|
||||
|
||||
auto& input = engine->getInput();
|
||||
input.simulateCursorPos(x, y);
|
||||
input.simulateClick(button, pressed);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_key_pressed(lua::State* L) {
|
||||
auto key = lua::require_string(L, 1);
|
||||
bool pressed = lua::toboolean(L, 2);
|
||||
|
||||
auto& input = engine->getInput();
|
||||
input.simulateKey(input_util::keycode_from(key), pressed);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_enter_text(lua::State* L) {
|
||||
auto string = lua::require_wstring(L, 1);
|
||||
|
||||
auto& input = engine->getInput();
|
||||
for (auto chr : string) {
|
||||
input.simulateCodepoint(chr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg testlib[] = {
|
||||
{"set_button_pressed", lua::wrap<l_set_button_pressed>},
|
||||
{"set_key_pressed", lua::wrap<l_set_key_pressed>},
|
||||
{"enter_text", lua::wrap<l_enter_text>},
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
|
|
@ -19,6 +19,7 @@ namespace {
|
|||
debug::Logger logger("lua-state");
|
||||
lua::State* main_thread = nullptr;
|
||||
bool headless_mode = false;
|
||||
bool test_mode = false;
|
||||
const std::unordered_map<std::string, std::string>* project_args;
|
||||
}
|
||||
|
||||
|
|
@ -69,12 +70,12 @@ static void create_libs(State* L, StateType stateType) {
|
|||
openlib(L, "yaml", yamllib);
|
||||
|
||||
openlib(L, "__vc_app", applib);
|
||||
lua::getglobal(L, "__vc_app");
|
||||
lua::setregistry(L, "app");
|
||||
getglobal(L, "__vc_app");
|
||||
setregistry(L, "app");
|
||||
|
||||
if (stateType == StateType::SCRIPT) {
|
||||
lua::getregistry(L, "app");
|
||||
lua::setglobal(L, "app");
|
||||
getregistry(L, "app");
|
||||
setglobal(L, "app");
|
||||
}
|
||||
if (stateType == StateType::BASE || stateType == StateType::SCRIPT) {
|
||||
openlib(L, "assets", assetslib);
|
||||
|
|
@ -99,6 +100,10 @@ static void create_libs(State* L, StateType stateType) {
|
|||
openlib(L, "__transform", transformlib);
|
||||
}
|
||||
|
||||
if (::test_mode) {
|
||||
openlib(L, "test", testlib);
|
||||
}
|
||||
|
||||
addfunc(L, "print", lua::wrap<l_print>);
|
||||
addfunc(L, "crc32", lua::wrap<l_crc32>);
|
||||
}
|
||||
|
|
@ -191,6 +196,7 @@ void lua::initialize(const EnginePaths& paths, const CoreParameters& params) {
|
|||
logger.info() << LUAJIT_VERSION;
|
||||
|
||||
headless_mode = params.headless;
|
||||
test_mode = params.testMode;
|
||||
project_args = ¶ms.projectArgs;
|
||||
main_thread = create_state(
|
||||
paths, params.headless ? StateType::SCRIPT : StateType::BASE
|
||||
|
|
|
|||
|
|
@ -165,17 +165,127 @@ inline constexpr short MOUSE_KEYS_OFFSET = 1024;
|
|||
|
||||
static GLFWcursor* standard_cursors[static_cast<int>(CursorShape::LAST) + 1] = {};
|
||||
|
||||
class GLFWInput : public Input {
|
||||
class BaseInput : public Input {
|
||||
public:
|
||||
std::vector<uint> codepoints;
|
||||
int scroll = 0;
|
||||
|
||||
void onKeyCallback(int key, bool pressed) {
|
||||
bool prevPressed = keys[key];
|
||||
keys[key] = pressed;
|
||||
frames[key] = currentFrame;
|
||||
if (pressed && !prevPressed) {
|
||||
const auto& callbacks = keyCallbacks.find(static_cast<Keycode>(key));
|
||||
if (callbacks != keyCallbacks.end()) {
|
||||
callbacks->second.notify();
|
||||
}
|
||||
}
|
||||
if (pressed && key < MOUSE_KEYS_OFFSET) {
|
||||
pressedKeys.push_back(static_cast<Keycode>(key));
|
||||
}
|
||||
}
|
||||
|
||||
void onMouseCallback(int button, bool pressed) {
|
||||
int key = button + MOUSE_KEYS_OFFSET;
|
||||
onKeyCallback(key, pressed);
|
||||
}
|
||||
|
||||
bool isCursorLocked() const override {
|
||||
return cursorLocked;
|
||||
}
|
||||
|
||||
void setCursorPosition(double xpos, double ypos) {
|
||||
if (cursorDrag) {
|
||||
delta.x += xpos - cursor.x;
|
||||
delta.y += ypos - cursor.y;
|
||||
} else {
|
||||
cursorDrag = true;
|
||||
}
|
||||
cursor.x = xpos;
|
||||
cursor.y = ypos;
|
||||
}
|
||||
|
||||
Bindings& getBindings() override {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
const Bindings& getBindings() const override {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
ObserverHandler addKeyCallback(Keycode key, KeyCallback callback) override {
|
||||
return keyCallbacks[key].add(std::move(callback));
|
||||
}
|
||||
|
||||
const std::vector<Keycode>& getPressedKeys() const override {
|
||||
return pressedKeys;
|
||||
}
|
||||
|
||||
const std::vector<uint>& getCodepoints() const override {
|
||||
return codepoints;
|
||||
}
|
||||
|
||||
CursorState getCursor() const override {
|
||||
return {isCursorLocked(), cursor, delta};
|
||||
}
|
||||
|
||||
int getScroll() override {
|
||||
return scroll;
|
||||
}
|
||||
|
||||
bool pressed(Keycode key) const override {
|
||||
int keycode = static_cast<int>(key);
|
||||
if (keycode < 0 || keycode >= KEYS_BUFFER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
return keys[keycode];
|
||||
}
|
||||
bool jpressed(Keycode keycode) const override {
|
||||
return pressed(keycode) &&
|
||||
frames[static_cast<int>(keycode)] == currentFrame;
|
||||
}
|
||||
|
||||
bool clicked(Mousecode code) const override {
|
||||
return pressed(
|
||||
static_cast<Keycode>(MOUSE_KEYS_OFFSET + static_cast<int>(code))
|
||||
);
|
||||
}
|
||||
bool jclicked(Mousecode code) const override {
|
||||
return clicked(code) &&
|
||||
frames[static_cast<int>(code) + MOUSE_KEYS_OFFSET] ==
|
||||
currentFrame;
|
||||
}
|
||||
|
||||
void simulateKey(Keycode key, bool pressed) override {
|
||||
onKeyCallback(static_cast<int>(key), pressed);
|
||||
}
|
||||
|
||||
void simulateClick(int button, bool pressed) override {
|
||||
onMouseCallback(static_cast<int>(button), pressed);
|
||||
}
|
||||
|
||||
void simulateCursorPos(double xpos, double ypos) override {
|
||||
setCursorPosition(xpos, ypos);
|
||||
}
|
||||
|
||||
void simulateCodepoint(uint codepoint) override {
|
||||
codepoints.push_back(codepoint);
|
||||
}
|
||||
protected:
|
||||
uint currentFrame = 0;
|
||||
uint frames[KEYS_BUFFER_SIZE] {};
|
||||
std::vector<uint> codepoints;
|
||||
std::vector<Keycode> pressedKeys;
|
||||
Bindings bindings;
|
||||
bool keys[KEYS_BUFFER_SIZE] {};
|
||||
std::unordered_map<Keycode, util::HandlersList<>> keyCallbacks;
|
||||
bool cursorLocked = false;
|
||||
bool cursorDrag = false;
|
||||
glm::vec2 delta {};
|
||||
glm::vec2 cursor {};
|
||||
};
|
||||
|
||||
class GLFWInput : public BaseInput {
|
||||
public:
|
||||
GLFWInput(GLFWwindow* window)
|
||||
: window(window) {
|
||||
}
|
||||
|
|
@ -225,26 +335,6 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void onKeyCallback(int key, bool pressed) {
|
||||
bool prevPressed = keys[key];
|
||||
keys[key] = pressed;
|
||||
frames[key] = currentFrame;
|
||||
if (pressed && !prevPressed) {
|
||||
const auto& callbacks = keyCallbacks.find(static_cast<Keycode>(key));
|
||||
if (callbacks != keyCallbacks.end()) {
|
||||
callbacks->second.notify();
|
||||
}
|
||||
}
|
||||
if (pressed && key < MOUSE_KEYS_OFFSET) {
|
||||
pressedKeys.push_back(static_cast<Keycode>(key));
|
||||
}
|
||||
}
|
||||
|
||||
void onMouseCallback(int button, bool pressed) {
|
||||
int key = button + MOUSE_KEYS_OFFSET;
|
||||
onKeyCallback(key, pressed);
|
||||
}
|
||||
|
||||
const char* getClipboardText() const override {
|
||||
return glfwGetClipboardString(window);
|
||||
}
|
||||
|
|
@ -253,41 +343,6 @@ public:
|
|||
glfwSetClipboardString(window, text);
|
||||
}
|
||||
|
||||
int getScroll() override {
|
||||
return scroll;
|
||||
}
|
||||
|
||||
bool pressed(Keycode key) const override {
|
||||
int keycode = static_cast<int>(key);
|
||||
if (keycode < 0 || keycode >= KEYS_BUFFER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
return keys[keycode];
|
||||
}
|
||||
bool jpressed(Keycode keycode) const override {
|
||||
return pressed(keycode) &&
|
||||
frames[static_cast<int>(keycode)] == currentFrame;
|
||||
}
|
||||
|
||||
bool clicked(Mousecode code) const override {
|
||||
return pressed(
|
||||
static_cast<Keycode>(MOUSE_KEYS_OFFSET + static_cast<int>(code))
|
||||
);
|
||||
}
|
||||
bool jclicked(Mousecode code) const override {
|
||||
return clicked(code) &&
|
||||
frames[static_cast<int>(code) + MOUSE_KEYS_OFFSET] ==
|
||||
currentFrame;
|
||||
}
|
||||
|
||||
CursorState getCursor() const override {
|
||||
return {isCursorLocked(), cursor, delta};
|
||||
}
|
||||
|
||||
bool isCursorLocked() const override {
|
||||
return cursorLocked;
|
||||
}
|
||||
|
||||
void toggleCursor() override {
|
||||
cursorDrag = false;
|
||||
if (cursorLocked) {
|
||||
|
|
@ -298,43 +353,8 @@ public:
|
|||
}
|
||||
cursorLocked = !cursorLocked;
|
||||
}
|
||||
|
||||
void setCursorPosition(double xpos, double ypos) {
|
||||
if (cursorDrag) {
|
||||
delta.x += xpos - cursor.x;
|
||||
delta.y += ypos - cursor.y;
|
||||
} else {
|
||||
cursorDrag = true;
|
||||
}
|
||||
cursor.x = xpos;
|
||||
cursor.y = ypos;
|
||||
}
|
||||
|
||||
Bindings& getBindings() override {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
const Bindings& getBindings() const override {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
ObserverHandler addKeyCallback(Keycode key, KeyCallback callback) override {
|
||||
return keyCallbacks[key].add(std::move(callback));
|
||||
}
|
||||
|
||||
const std::vector<Keycode>& getPressedKeys() const override {
|
||||
return pressedKeys;
|
||||
}
|
||||
|
||||
const std::vector<uint>& getCodepoints() const override {
|
||||
return codepoints;
|
||||
}
|
||||
private:
|
||||
GLFWwindow* window;
|
||||
bool cursorLocked = false;
|
||||
bool cursorDrag = false;
|
||||
glm::vec2 delta {};
|
||||
glm::vec2 cursor {};
|
||||
};
|
||||
static_assert(!std::is_abstract<GLFWInput>());
|
||||
|
||||
|
|
|
|||
|
|
@ -274,6 +274,11 @@ public:
|
|||
virtual bool clicked(Mousecode mousecode) const = 0;
|
||||
virtual bool jclicked(Mousecode mousecode) const = 0;
|
||||
|
||||
virtual void simulateKey(Keycode key, bool pressed) = 0;
|
||||
virtual void simulateClick(int button, bool pressed) = 0;
|
||||
virtual void simulateCursorPos(double xpos, double ypos) = 0;
|
||||
virtual void simulateCodepoint(uint codepoint) = 0;
|
||||
|
||||
virtual CursorState getCursor() const = 0;
|
||||
|
||||
virtual bool isCursorLocked() const = 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue