From 0d0581b108b76ddf52135f84db352189642f4f08 Mon Sep 17 00:00:00 2001 From: Insality Date: Thu, 17 Apr 2025 19:18:40 +0300 Subject: [PATCH] Update tests --- CONTRIBUTING.md => CONTRIBUTING | 0 README.md | 2 +- api/components/custom/rich_input_api.md | 2 +- api/components/extended/container_api.md | 2 +- api/components/extended/data_list_api.md | 4 +- druid/base/button.lua | 12 +- druid/custom/rich_input/rich_input.lua | 2 +- druid/druid.lua | 6 +- druid/extended/container.lua | 2 +- druid/extended/data_list.lua | 4 +- druid/extended/progress.lua | 8 +- druid/helper.lua | 13 +- druid/system/druid_instance.lua | 33 +- test/test.gui | 6 +- test/test.gui_script | 6 +- test/tests/test_back_handler.lua | 48 +-- test/tests/test_blocker.lua | 104 +++--- test/tests/test_button.lua | 307 ++++++++++++------ test/tests/test_drag.lua | 149 +++++---- test/tests/test_druid_instance.lua | 306 ++++++++++++++++++ test/tests/test_helper.lua | 275 ++++++++++++++++ test/tests/test_hover.lua | 77 ++--- test/tests/test_input.lua | 384 +++++++++++++++++++++++ test/tests/test_text.lua | 238 ++++++++++++++ wiki/changelog.md | 2 +- 25 files changed, 1672 insertions(+), 320 deletions(-) rename CONTRIBUTING.md => CONTRIBUTING (100%) create mode 100644 test/tests/test_druid_instance.lua create mode 100644 test/tests/test_helper.lua create mode 100644 test/tests/test_input.lua create mode 100644 test/tests/test_text.lua diff --git a/CONTRIBUTING.md b/CONTRIBUTING similarity index 100% rename from CONTRIBUTING.md rename to CONTRIBUTING diff --git a/README.md b/README.md index e2d2e99..c8f4669 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Open your `game.project` file and add the following lines to the dependencies fi **[Druid](https://github.com/Insality/druid/)** ``` -https://github.com/Insality/druid/archive/refs/tags/1.0.zip +https://github.com/Insality/druid/archive/refs/tags/1.1.zip ``` **[Defold Event](https://github.com/Insality/defold-event)** diff --git a/api/components/custom/rich_input_api.md b/api/components/custom/rich_input_api.md index 0b2198b..1412c21 100644 --- a/api/components/custom/rich_input_api.md +++ b/api/components/custom/rich_input_api.md @@ -123,7 +123,7 @@ Set allowed charaters for input field. ex: [%a%d] for alpha and numeric - **Parameters:** - - `characters` *(string)*: Regulax exp. for validate user input + - `characters` *(string)*: Regular expression for validate user input - **Returns:** - `self` *(druid.rich_input)*: Current instance diff --git a/api/components/extended/container_api.md b/api/components/extended/container_api.md index a6880a3..2f0fbf1 100644 --- a/api/components/extended/container_api.md +++ b/api/components/extended/container_api.md @@ -112,7 +112,7 @@ Set new size of layout node - **Parameters:** - `[width]` *(number|nil)*: The width to set - `[height]` *(number|nil)*: The height to set - - `[anchor_pivot]` *(constant|nil)*: If set will keep the corner possition relative to the new size + - `[anchor_pivot]` *(constant|nil)*: If set will keep the corner position relative to the new size - **Returns:** - `Container` *(druid.container)*: diff --git a/api/components/extended/data_list_api.md b/api/components/extended/data_list_api.md index 818bbb2..5028e0e 100644 --- a/api/components/extended/data_list_api.md +++ b/api/components/extended/data_list_api.md @@ -182,7 +182,7 @@ Return index for data value data_list:get_created_nodes() ``` -Return all currenly created nodes in DataList +Return all currently created nodes in DataList - **Returns:** - `List` *(node[])*: of created nodes @@ -194,7 +194,7 @@ Return all currenly created nodes in DataList data_list:get_created_components() ``` -Return all currenly created components in DataList +Return all currently created components in DataList - **Returns:** - `components` *(druid.component[])*: List of created components diff --git a/druid/base/button.lua b/druid/base/button.lua index 7799863..c8e6a23 100755 --- a/druid/base/button.lua +++ b/druid/base/button.lua @@ -243,15 +243,15 @@ end ---Get button enabled state. ----By default all Buttons is enabled on creating. ----@return boolean is_enabled True, if button is enabled now, False overwise +---By default all Buttons are enabled on creating. +---@return boolean is_enabled True, if button is enabled now, False otherwise function M:is_enabled() return not self.disabled end ---Set additional button click area. ----Useful to restrict click outside out stencil node or scrollable content. +---Useful to restrict click outside of stencil node or scrollable content. ---If button node placed inside stencil node, it will be automatically set to this stencil node. ---@param zone node|string|nil Gui node ---@return druid.button self The current button instance @@ -284,9 +284,9 @@ function M:get_key_trigger() end ----Set function for additional check for button click availability +---Set function for additional check for button click availability. ---@param check_function function|nil Should return true or false. If true - button can be pressed. ----@param failure_callback function|nil Function will be called on button click, if check function return false +---@param failure_callback function|nil Function will be called on button click, if check function returns false ---@return druid.button self The current button instance function M:set_check_function(check_function, failure_callback) self._check_function = check_function @@ -299,7 +299,7 @@ end ---Set Button mode to work inside user HTML5 interaction event. --- ---It's required to make protected things like copy & paste text, show mobile keyboard, etc ----The HTML5 button's doesn't call any events except on_click event. +---The HTML5 button doesn't call any events except on_click event. --- ---If the game is not HTML, html mode will be not enabled ---@param is_web_mode boolean|nil If true - button will be called inside html5 callback diff --git a/druid/custom/rich_input/rich_input.lua b/druid/custom/rich_input/rich_input.lua index b6ada58..213d14d 100644 --- a/druid/custom/rich_input/rich_input.lua +++ b/druid/custom/rich_input/rich_input.lua @@ -289,7 +289,7 @@ end ---Set allowed charaters for input field. -- See: https://defold.com/ref/stable/string/ -- ex: [%a%d] for alpha and numeric ----@param characters string Regulax exp. for validate user input +---@param characters string Regular expression for validate user input ---@return druid.rich_input self Current instance function M:set_allowed_characters(characters) self.input:set_allowed_characters(characters) diff --git a/druid/druid.lua b/druid/druid.lua index 9443885..b5f0d55 100644 --- a/druid/druid.lua +++ b/druid/druid.lua @@ -110,10 +110,10 @@ local function wrap_widget(widget) end ----Create a widget from the binded Druid GUI instance. +---Create a widget from the bound Druid GUI instance. ---The widget will be created and all widget functions can be called from Game Object contexts. ----This allow use only `druid_widget.gui_script` for GUI files and call this widget functions from Game Object script file. ----Widget class here is a your lua file for the GUI scene (a widgets in Druid) +---This allows using only `druid_widget.gui_script` for GUI files and call this widget functions from Game Object script file. +---Widget class here is your lua file for the GUI scene (widgets in Druid) --- msg.url(nil, nil, "gui_widget") -- current game object --- msg.url(nil, object_url, "gui_widget") -- other game object ---@generic T: druid.widget diff --git a/druid/extended/container.lua b/druid/extended/container.lua index 2833059..acf819c 100644 --- a/druid/extended/container.lua +++ b/druid/extended/container.lua @@ -152,7 +152,7 @@ end ---Set new size of layout node ---@param width number|nil The width to set ---@param height number|nil The height to set ----@param anchor_pivot constant|nil If set will keep the corner possition relative to the new size +---@param anchor_pivot constant|nil If set will keep the corner position relative to the new size ---@return druid.container Container function M:set_size(width, height, anchor_pivot) width = width or self.size.x diff --git a/druid/extended/data_list.lua b/druid/extended/data_list.lua index 0bebccf..f430058 100644 --- a/druid/extended/data_list.lua +++ b/druid/extended/data_list.lua @@ -162,7 +162,7 @@ function M:get_index(data) end ----Return all currenly created nodes in DataList +---Return all currently created nodes in DataList ---@return node[] List of created nodes function M:get_created_nodes() local nodes = {} @@ -175,7 +175,7 @@ function M:get_created_nodes() end ----Return all currenly created components in DataList +---Return all currently created components in DataList ---@return druid.component[] components List of created components function M:get_created_components() local components = {} diff --git a/druid/extended/progress.lua b/druid/extended/progress.lua index e29be6d..09608ac 100644 --- a/druid/extended/progress.lua +++ b/druid/extended/progress.lua @@ -3,7 +3,7 @@ local helper = require("druid.helper") local component = require("druid.component") ---@class druid.progress.style ----@field SPEED number|nil Progress bas fill rate. More -> faster. Default: 5 +---@field SPEED number|nil Progress bar fill rate. Higher value means faster fill. Default: 5 ---@field MIN_DELTA number|nil Minimum step to fill progress bar. Default: 0.005 ---Basic Druid progress bar component. Changes the size or scale of a node to represent progress. @@ -12,10 +12,10 @@ local component = require("druid.component") ---Create progress bar component with druid: `progress = druid:new_progress(node_name, key, init_value)` --- ---### Notes ----- Node should have maximum node size in GUI scene, it's represent the progress bar maximum size +---- Node should have maximum node size in GUI scene, it represents the progress bar's maximum size ---- Key is value from druid const: "x" or "y" ----- Progress works correctly with 9slice nodes, it tries to set size by _set_size_ first, until minimum size is reached, then it sizing via _set_scale_ ----- Progress bar can fill only by vertical or horizontal size. For diagonal progress bar, just rotate node in GUI scene +---- Progress works correctly with 9slice nodes, it tries to set size by _set_size_ first until minimum size is reached, then it continues sizing via _set_scale_ +---- Progress bar can fill only by vertical or horizontal size. For diagonal progress bar, just rotate the node in GUI scene ---- If you have glitchy or dark texture bugs with progress bar, try to disable mipmaps in your texture profiles ---@class druid.progress: druid.component ---@field node node The progress bar node diff --git a/druid/helper.lua b/druid/helper.lua index 3ae5945..b10fa5b 100644 --- a/druid/helper.lua +++ b/druid/helper.lua @@ -51,6 +51,7 @@ end ---@param text_node node|nil Gui text node ---@param icon_node node|nil Gui box node ---@param margin number Offset between nodes +---@return number width Total width of the centrated elements ---@local function M.centrate_text_with_icon(text_node, icon_node, margin) return M.centrate_nodes(margin, text_node, icon_node) @@ -63,6 +64,7 @@ end ---@param icon_node node|nil Gui box node ---@param text_node node|nil Gui text node ---@param margin number|nil Offset between nodes +---@return number width Total width of the centrated elements ---@local function M.centrate_icon_with_text(icon_node, text_node, margin) return M.centrate_nodes(margin, icon_node, text_node) @@ -75,6 +77,7 @@ end ---The centrate will be around 0 x position. ---@param margin number|nil Offset between nodes ---@param ... node Nodes to centrate +---@return number width Total width of the centrated elements function M.centrate_nodes(margin, ...) margin = margin or 0 @@ -109,10 +112,11 @@ function M.centrate_nodes(margin, ...) end ----@param node_id string|node ----@param template string|nil Full Path to the template ----@param nodes table|nil Nodes what created with gui.clone_tree ----@return node +---Get GUI node from string name, node itself, or template/nodes structure +---@param node_id string|node The node name or node itself +---@param template string|nil Full path to the template +---@param nodes table|nil Nodes created with gui.clone_tree +---@return node The requested node function M.get_node(node_id, template, nodes) if type(node_id) ~= "string" then -- Assume it's already node from gui.get_node @@ -152,6 +156,7 @@ end ---Get current GUI scale for each side ---@return number scale_x +---@return number scale_y function M.get_gui_scale() local window_x, window_y = window.get_size() return math.min(window_x / gui.get_width(), window_y / gui.get_height()) diff --git a/druid/system/druid_instance.lua b/druid/system/druid_instance.lua index cf74975..fe1e8be 100755 --- a/druid/system/druid_instance.lua +++ b/druid/system/druid_instance.lua @@ -176,7 +176,7 @@ local function schedule_late_init(self) end ----Druid class constructor which used to create a Druid's components +---Druid class constructor which used to create Druid components ---@param context table Druid context. Usually it is self of gui script ---@param style table? Druid style table ---@return druid.instance instance The new Druid instance @@ -420,8 +420,7 @@ end ---Calls the on_language_change function in all related components ----This one called by global druid.on_language_change, but can be ----call manualy to update all translations +---This one called by global druid.on_language_change, but can be called manually to update all translations ---@private function M:on_language_change() local components = self.components_interest[const.ON_LANGUAGE_CHANGE] @@ -433,7 +432,7 @@ end ---Set whitelist components for input processing. ---If whitelist is not empty and component not contains in this list, ----component will be not processed on input step +---component will be not processed on the input step ---@param whitelist_components table|druid.component[] The array of component to whitelist ---@return druid.instance self The Druid instance function M:set_whitelist(whitelist_components) @@ -452,8 +451,8 @@ end ---Set blacklist components for input processing. ----If blacklist is not empty and component contains in this list, ----component will be not processed on input step DruidInstance +---If blacklist is not empty and component is contained in this list, +---component will be not processed on the input step DruidInstance ---@param blacklist_components table|druid.component[] The array of component to blacklist ---@return druid.instance self The Druid instance function M:set_blacklist(blacklist_components) @@ -572,7 +571,7 @@ local static_grid = require("druid.base.static_grid") ---Create Grid component ---@param parent_node string|node The node_id or gui.get_node(node_id). Parent of all Grid items. ---@param item string|node Item prefab. Required to get grid's item size. Can be adjusted separately. ----@param in_row number|nil How many nodes in row can be placed +---@param in_row number|nil How many nodes can be placed in a row ---@return druid.grid grid The new grid component function M:new_grid(parent_node, item, in_row) return self:new(static_grid, parent_node, item, in_row) @@ -581,8 +580,8 @@ end local scroll = require("druid.base.scroll") ---Create Scroll component ----@param view_node string|node The node_id or gui.get_node(node_id). Will used as user input node. ----@param content_node string|node The node_id or gui.get_node(node_id). Will used as scrollable node inside view_node. +---@param view_node string|node The node_id or gui.get_node(node_id). Will be used as user input node. +---@param content_node string|node The node_id or gui.get_node(node_id). Will be used as scrollable node inside view_node. ---@return druid.scroll scroll The new scroll component function M:new_scroll(view_node, content_node) return self:new(scroll, view_node, content_node) @@ -591,7 +590,7 @@ end local drag = require("druid.base.drag") ---Create Drag component ----@param node string|node The node_id or gui.get_node(node_id). Will used as user input node. +---@param node string|node The node_id or gui.get_node(node_id). Will be used as user input node. ---@param on_drag_callback function|nil Callback for on_drag_event(self, dx, dy) ---@return druid.drag drag The new drag component function M:new_drag(node, on_drag_callback) @@ -601,7 +600,7 @@ end local swipe = require("druid.extended.swipe") ---Create Swipe component ----@param node string|node The node_id or gui.get_node(node_id). Will used as user input node. +---@param node string|node The node_id or gui.get_node(node_id). Will be used as user input node. ---@param on_swipe_callback function|nil Swipe callback for on_swipe_end event ---@return druid.swipe swipe The new swipe component function M:new_swipe(node, on_swipe_callback) @@ -611,7 +610,7 @@ end local lang_text = require("druid.extended.lang_text") ---Create LangText component ----@param node string|node The_node id or gui.get_node(node_id) +---@param node string|node The node_id or gui.get_node(node_id) ---@param locale_id string|nil Default locale id or text from node as default ---@param adjust_type string|nil Adjust type for text node. Default: "downscale" ---@return druid.lang_text lang_text The new lang text component @@ -622,7 +621,7 @@ end local slider = require("druid.extended.slider") ---Create Slider component ----@param pin_node string|node The_node id or gui.get_node(node_id). +---@param pin_node string|node The node_id or gui.get_node(node_id). ---@param end_pos vector3 The end position of slider ---@param callback function|nil On slider change callback ---@return druid.slider slider The new slider component @@ -633,8 +632,8 @@ end local input = require("druid.extended.input") ---Create Input component ----@param click_node string|node Button node to enabled input component ----@param text_node string|node|druid.text Text node what will be changed on user input +---@param click_node string|node Button node to enable input component +---@param text_node string|node|druid.text Text node that will be changed on user input ---@param keyboard_type number|nil Gui keyboard type for input field ---@return druid.input input The new input component function M:new_input(click_node, text_node, keyboard_type) @@ -678,7 +677,7 @@ end local layout = require("druid.extended.layout") ---Create Layout component ----@param node string|node The_node id or gui.get_node(node_id). +---@param node string|node The node_id or gui.get_node(node_id). ---@param mode string|nil vertical|horizontal|horizontal_wrap. Default: horizontal ---@return druid.layout layout The new layout component function M:new_layout(node, mode) @@ -688,7 +687,7 @@ end local container = require("druid.extended.container") ---Create Container component ----@param node string|node The_node id or gui.get_node(node_id). +---@param node string|node The node_id or gui.get_node(node_id). ---@param mode string|nil Layout mode ---@param callback fun(self: druid.container, size: vector3)|nil Callback on size changed ---@return druid.container container The new container component diff --git a/test/test.gui b/test/test.gui index 97579f5..a1d0723 100644 --- a/test/test.gui +++ b/test/test.gui @@ -1,3 +1,7 @@ script: "/test/test.gui_script" +fonts { + name: "druid_text_bold" + font: "/druid/fonts/druid_text_bold.font" +} material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT +adjust_reference: ADJUST_REFERENCE_DISABLED diff --git a/test/test.gui_script b/test/test.gui_script index 608f448..e4af758 100644 --- a/test/test.gui_script +++ b/test/test.gui_script @@ -1,11 +1,15 @@ local deftest = require("deftest.deftest") function init(self) + deftest.add(require("test.tests.test_blocker")) deftest.add(require("test.tests.test_button")) deftest.add(require("test.tests.test_hover")) deftest.add(require("test.tests.test_drag")) deftest.add(require("test.tests.test_back_handler")) - deftest.add(require("test.tests.test_blocker")) + deftest.add(require("test.tests.test_helper")) + deftest.add(require("test.tests.test_text")) + deftest.add(require("test.tests.test_input")) + deftest.add(require("test.tests.test_druid_instance")) local is_report = (sys.get_config_int("test.report", 0) == 1) deftest.run({ coverage = { enabled = is_report } }) diff --git a/test/tests/test_back_handler.lua b/test/tests/test_back_handler.lua index d0e489f..e422f61 100644 --- a/test/tests/test_back_handler.lua +++ b/test/tests/test_back_handler.lua @@ -1,49 +1,53 @@ return function() - local mock_gui = nil - local mock_time = nil - local mock_input = nil - local test_helper = nil - local druid_system = nil - - local druid = nil - local context = nil - describe("Back Handler component", function() + local mock_time + local mock_input + local druid_system + + local druid + local context + before(function() - mock_gui = require("deftest.mock.gui") mock_time = require("deftest.mock.time") mock_input = require("test.helper.mock_input") - test_helper = require("test.helper.test_helper") druid_system = require("druid.druid") - mock_gui.mock() mock_time.mock() - mock_time.set(60) + mock_time.set(0) - context = test_helper.get_context() + context = vmath.vector3() druid = druid_system.new(context) end) after(function() - mock_gui.unmock() mock_time.unmock() - druid:final(context) + druid:final() druid = nil end) it("Should react on back action id with custom args", function() - local on_back_handler, on_back_handler_mock = test_helper.get_function() - druid:new_back_handler(on_back_handler, { args = "custom" }) + local is_back_handler_called = false + local context_arg = nil + local back_handler_args = nil + + druid:new_back_handler(function(self, args) + context_arg = self + is_back_handler_called = true + back_handler_args = args + end, "custom") druid:on_input(mock_input.key_pressed("key_back")) druid:on_input(mock_input.key_released("key_back")) - assert(on_back_handler_mock.calls == 1) - assert(on_back_handler_mock.params[1] == context) - assert(on_back_handler_mock.params[2].args == "custom") + + assert(is_back_handler_called) + assert(back_handler_args == "custom") + assert(context_arg == context) + + is_back_handler_called = false druid:on_input(mock_input.key_pressed("key_a")) druid:on_input(mock_input.key_released("key_a")) - assert(on_back_handler_mock.calls == 1) + assert(is_back_handler_called == false) end) end) end diff --git a/test/tests/test_blocker.lua b/test/tests/test_blocker.lua index 04cb220..1b71470 100644 --- a/test/tests/test_blocker.lua +++ b/test/tests/test_blocker.lua @@ -1,99 +1,93 @@ return function() describe("Blocker component", function() - local mock_gui = nil - local mock_time = nil - local mock_input = nil - local test_helper = nil - local druid_system = nil + local mock_time + local mock_input + local druid_system - local druid = nil ---@type druid.instance + local druid ---@type druid.instance + local context before(function() - mock_gui = require("deftest.mock.gui") - mock_gui.mock() - mock_time = require("deftest.mock.time") mock_input = require("test.helper.mock_input") - test_helper = require("test.helper.test_helper") druid_system = require("druid.druid") mock_time.mock() - mock_time.set(60) + mock_time.set(0) - druid = druid_system.new(vmath.vector3()) + context = vmath.vector3() + druid = druid_system.new(context) end) after(function() - mock_gui.unmock() mock_time.unmock() druid:final() + druid = nil end) it("Should consume input", function() - local button_node = mock_gui.add_box("button", 0, 0, 100, 50) - local blocker_node = mock_gui.add_box("blocker", 20, 20, 20, 20) - local on_click, on_click_mock = test_helper.get_function() + local button_node = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 100, 0)) + local blocker_node = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(20, 20, 0)) + local on_click_calls = 0 - druid:new_button(button_node, on_click) + druid:new_button(button_node, function() + on_click_calls = on_click_calls + 1 + end) druid:new_blocker(blocker_node) - druid:on_input(mock_input.click_pressed(10, 10)) - druid:on_input(mock_input.click_released(10, 10)) - assert(on_click_mock.calls == 1) + druid:on_input(mock_input.click_pressed(40, 40)) + druid:on_input(mock_input.click_released(40, 40)) + assert(on_click_calls == 1) -- Click should been consumed by blocker component - druid:on_input(mock_input.click_pressed(20, 20)) - druid:on_input(mock_input.click_released(20, 20)) - assert(on_click_mock.calls == 1) + druid:on_input(mock_input.click_pressed(0, 0)) + druid:on_input(mock_input.click_released(0, 0)) + assert(on_click_calls == 1) -- If move from button to blocker, should consume too - druid:on_input(mock_input.click_pressed(10, 10)) - druid:on_input(mock_input.click_released(20, 20)) - assert(on_click_mock.calls == 1) + druid:on_input(mock_input.click_pressed(40, 40)) + druid:on_input(mock_input.click_released(0, 0)) + assert(on_click_calls == 1) -- And from blocker to button too - druid:on_input(mock_input.click_pressed(20, 20)) - druid:on_input(mock_input.click_released(10, 10)) - assert(on_click_mock.calls == 1) + druid:on_input(mock_input.click_pressed(0, 0)) + druid:on_input(mock_input.click_released(40, 40)) + assert(on_click_calls == 1) + + -- Usual click after that should work + druid:on_input(mock_input.click_pressed(40, 40)) + druid:on_input(mock_input.click_released(40, 40)) + assert(on_click_calls == 2) end) - it("Should be disabled via node or set_enabled", function() - local button_node = mock_gui.add_box("button", 0, 0, 100, 50) - local blocker_node = mock_gui.add_box("blocker", 20, 20, 20, 20) - local on_click, on_click_mock = test_helper.get_function() + it("Should be disabled via set_enabled", function() + local button_node = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 100, 0)) + local blocker_node = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(20, 20, 0)) + local on_click_calls = 0 - druid:new_button(button_node, on_click) + druid:new_button(button_node, function() + on_click_calls = on_click_calls + 1 + end) local blocker = druid:new_blocker(blocker_node) -- Click should been consumed by blocker component - druid:on_input(mock_input.click_pressed(20, 20)) - druid:on_input(mock_input.click_released(20, 20)) - assert(on_click_mock.calls == 0) + druid:on_input(mock_input.click_pressed(0, 0)) + druid:on_input(mock_input.click_released(0, 0)) + assert(on_click_calls == 0) -- Disable blocker component blocker:set_enabled(false) - druid:on_input(mock_input.click_pressed(20, 20)) - druid:on_input(mock_input.click_released(20, 20)) - assert(gui.is_enabled(blocker_node) == true) + druid:on_input(mock_input.click_pressed(0, 0)) + druid:on_input(mock_input.click_released(0, 0)) assert(blocker:is_enabled() == false) - assert(on_click_mock.calls == 1) + assert(on_click_calls == 1) - -- Disable blocker node component + -- Enable blocker component again blocker:set_enabled(true) - gui.set_enabled(blocker_node, false) - druid:on_input(mock_input.click_pressed(20, 20)) - druid:on_input(mock_input.click_released(20, 20)) - assert(gui.is_enabled(blocker_node) == false) + druid:on_input(mock_input.click_pressed(0, 0)) + druid:on_input(mock_input.click_released(0, 0)) assert(blocker:is_enabled() == true) - assert(on_click_mock.calls == 2) - - -- Return state - gui.set_enabled(blocker_node, true) - druid:on_input(mock_input.click_pressed(20, 20)) - druid:on_input(mock_input.click_released(20, 20)) - assert(gui.is_enabled(blocker_node) == true) - assert(blocker:is_enabled() == true) - assert(on_click_mock.calls == 2) + assert(on_click_calls == 1) end) end) end diff --git a/test/tests/test_button.lua b/test/tests/test_button.lua index 131522a..d5e1ec1 100644 --- a/test/tests/test_button.lua +++ b/test/tests/test_button.lua @@ -1,58 +1,72 @@ return function() describe("Button Component", function() - local mock_gui = nil - local mock_time = nil - local mock_input = nil - local test_helper = nil - local druid_system = nil + local mock_time + local mock_input + local druid_system - local druid = nil - local context = nil + local druid + local context before(function() - mock_gui = require("deftest.mock.gui") - mock_gui.mock() - mock_time = require("deftest.mock.time") mock_input = require("test.helper.mock_input") - test_helper = require("test.helper.test_helper") druid_system = require("druid.druid") mock_time.mock() - mock_time.set(60) + mock_time.set(0) - context = test_helper.get_context() + context = vmath.vector3() druid = druid_system.new(context) end) after(function() - mock_gui.unmock() mock_time.unmock() - druid:final(context) + druid:final() druid = nil end) it("Should do usual click", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) local button_params = {} - local on_click, on_click_mock = test_helper.get_function() + + local on_click_calls = 0 + local on_click_params = {} + local function on_click(self, params, button_instance) + on_click_calls = on_click_calls + 1 + on_click_params[1] = self + on_click_params[2] = params + on_click_params[3] = button_instance + end + local instance = druid:new_button(button, on_click, button_params) local is_clicked_pressed = druid:on_input(mock_input.click_pressed(10, 10)) local is_clicked_released = druid:on_input(mock_input.click_released(20, 10)) assert(is_clicked_pressed) assert(is_clicked_released) - assert(on_click_mock.calls == 1) - assert(on_click_mock.params[1] == context) - assert(on_click_mock.params[2] == button_params) - assert(on_click_mock.params[3] == instance) + assert(on_click_calls == 1) + assert(on_click_params[1] == context) + assert(on_click_params[2] == button_params) + assert(on_click_params[3] == instance) end) it("Should do long click if exists", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) local button_params = {} - local on_click, on_click_mock = test_helper.get_function() - local on_long_click, on_long_click_mock = test_helper.get_function() + + local on_click_calls = 0 + local function on_click() + on_click_calls = on_click_calls + 1 + end + + local on_long_click_calls = 0 + local on_long_click_params = {} + local function on_long_click(self, params, button_instance) + on_long_click_calls = on_long_click_calls + 1 + on_long_click_params[1] = self + on_long_click_params[2] = params + on_long_click_params[3] = button_instance + end local instance = druid:new_button(button, on_click, button_params) instance.on_long_click:subscribe(on_long_click) @@ -60,52 +74,73 @@ return function() mock_time.elapse(0.3) druid:on_input(mock_input.click_released(20, 10)) - assert(on_click_mock.calls == 1) - assert(on_long_click_mock.calls == 0) + assert(on_click_calls == 1) + assert(on_long_click_calls == 0) druid:on_input(mock_input.click_pressed(10, 10)) mock_time.elapse(1) druid:on_input(mock_input.click_released(20, 10)) - assert(on_click_mock.calls == 1) - assert(on_long_click_mock.calls == 1) - assert(on_long_click_mock.params[1] == context) - assert(on_long_click_mock.params[2] == button_params) - assert(on_long_click_mock.params[3] == instance) + assert(on_click_calls == 1) + assert(on_long_click_calls == 1) + assert(on_long_click_params[1] == context) + assert(on_long_click_params[2] == button_params) + assert(on_long_click_params[3] == instance) end) it("Should do not long click if not exists", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) local button_params = {} - local on_click, on_click_mock = test_helper.get_function() + + local on_click_calls = 0 + local function on_click() + on_click_calls = on_click_calls + 1 + end + druid:new_button(button, on_click, button_params) druid:on_input(mock_input.click_pressed(10, 10)) mock_time.elapse(0.5) druid:on_input(mock_input.click_released(20, 10)) - assert(on_click_mock.calls == 1) + assert(on_click_calls == 1) end) it("Should do double click if exists", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) + --TODO: At zero it thinks it is double click + -- Need to fix it in button + mock_time.set(10) + local button = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) local button_params = {} - local on_click, on_click_mock = test_helper.get_function() - local on_double_click, on_double_click_mock = test_helper.get_function() + + local on_click_calls = 0 + local function on_click() + on_click_calls = on_click_calls + 1 + end + + local on_double_click_calls = 0 + local on_double_click_params = {} + local function on_double_click(self, params, button_instance) + on_double_click_calls = on_double_click_calls + 1 + on_double_click_params[1] = self + on_double_click_params[2] = params + on_double_click_params[3] = button_instance + end local instance = druid:new_button(button, on_click, button_params) instance.on_double_click:subscribe(on_double_click) - druid:on_input(mock_input.click_pressed(10, 10)) - druid:on_input(mock_input.click_released(20, 10)) + druid:on_input(mock_input.click_pressed(50, 25)) + druid:on_input(mock_input.click_released(50, 25)) - mock_time.elapse(0.1) - druid:on_input(mock_input.click_pressed(10, 10)) - druid:on_input(mock_input.click_released(20, 10)) + -- DOUBLETAP_TIME by default is 0.4 + mock_time.elapse(0.2) + druid:on_input(mock_input.click_pressed(50, 25)) + druid:on_input(mock_input.click_released(50, 25)) - assert(on_click_mock.calls == 1) - assert(on_double_click_mock.calls == 1) - assert(on_double_click_mock.params[1] == context) - assert(on_double_click_mock.params[2] == button_params) - assert(on_double_click_mock.params[3] == instance) + assert(on_click_calls == 1) + assert(on_double_click_calls == 1) + assert(on_double_click_params[1] == context) + assert(on_double_click_params[2] == button_params) + assert(on_double_click_params[3] == instance) mock_time.elapse(1) druid:on_input(mock_input.click_pressed(10, 10)) @@ -115,14 +150,19 @@ return function() druid:on_input(mock_input.click_pressed(10, 10)) druid:on_input(mock_input.click_released(20, 10)) - assert(on_click_mock.calls == 3) - assert(on_double_click_mock.calls == 1) + assert(on_click_calls == 3) + assert(on_double_click_calls == 1) end) it("Should do not double click if not exists", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) local button_params = {} - local on_click, on_click_mock = test_helper.get_function() + + local on_click_calls = 0 + local function on_click() + on_click_calls = on_click_calls + 1 + end + druid:new_button(button, on_click, button_params) druid:on_input(mock_input.click_pressed(10, 10)) @@ -131,15 +171,32 @@ return function() druid:on_input(mock_input.click_pressed(10, 10)) druid:on_input(mock_input.click_released(20, 10)) - assert(on_click_mock.calls == 2) + assert(on_click_calls == 2) end) it("Should do hold click if exists", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) local button_params = {} - local on_click, on_click_mock = test_helper.get_function() - local on_long_click, on_long_click_mock = test_helper.get_function() - local on_hold_callback, on_hold_callback_mock = test_helper.get_function() + + local on_click_calls = 0 + local function on_click() + on_click_calls = on_click_calls + 1 + end + + local on_long_click_calls = 0 + local on_long_click_params = {} + local function on_long_click(self, params, button_instance) + on_long_click_calls = on_long_click_calls + 1 + on_long_click_params[1] = self + on_long_click_params[2] = params + on_long_click_params[3] = button_instance + end + + local on_hold_callback_calls = 0 + local function on_hold_callback() + on_hold_callback_calls = on_hold_callback_calls + 1 + end + local instance = druid:new_button(button, on_click, button_params) instance.on_long_click:subscribe(on_long_click) -- long click required for hold callback instance.on_hold_callback:subscribe(on_hold_callback) @@ -148,112 +205,160 @@ return function() mock_time.elapse(1) -- time between hold treshold and autorelease hold time druid:on_input(mock_input.input_empty(10, 10)) - pprint(on_long_click_mock) - assert(on_click_mock.calls == 0) - assert(on_long_click_mock.calls == 1) - assert(on_long_click_mock.params[1] == context) - assert(on_long_click_mock.params[2] == button_params) - assert(on_long_click_mock.params[3] == instance) + assert(on_click_calls == 0) + assert(on_long_click_calls == 1) + assert(on_long_click_params[1] == context) + assert(on_long_click_params[2] == button_params) + assert(on_long_click_params[3] == instance) druid:on_input(mock_input.click_released(10, 10)) - assert(on_click_mock.calls == 0) - assert(on_long_click_mock.calls == 1) + assert(on_click_calls == 0) + assert(on_long_click_calls == 1) end) it("Should do click outside if exists", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) local button_params = {} - local on_click = test_helper.get_function() - local on_click_outside, on_click_outside_mock = test_helper.get_function() + + local on_click_calls = 0 + local function on_click() + on_click_calls = on_click_calls + 1 + end + + local on_click_outside_calls = 0 + local function on_click_outside() + on_click_outside_calls = on_click_outside_calls + 1 + end + local instance = druid:new_button(button, on_click, button_params) instance.on_click_outside:subscribe(on_click_outside) - druid:on_input(mock_input.click_pressed(-10, 10)) - druid:on_input(mock_input.click_released(-10, 10)) + druid:on_input(mock_input.click_pressed(120, 10)) + druid:on_input(mock_input.click_released(120, 10)) - assert(on_click_outside_mock.calls == 1) + assert(on_click_outside_calls == 1) mock_time.elapse(1) druid:on_input(mock_input.click_pressed(10, 10)) - druid:on_input(mock_input.click_released(-10, 10)) + druid:on_input(mock_input.click_released(120, 10)) - assert(on_click_outside_mock.calls == 2) + assert(on_click_outside_calls == 2) end) it("Should not click if mouse was outside while clicking", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) local button_params = {} - local on_click, on_click_mock = test_helper.get_function() + + local on_click_calls = 0 + local function on_click() + on_click_calls = on_click_calls + 1 + end + druid:new_button(button, on_click, button_params) druid:on_input(mock_input.click_pressed(10, 10)) - druid:on_input(mock_input.input_empty(-10, 10)) + druid:on_input(mock_input.input_empty(120, 10)) druid:on_input(mock_input.click_released(20, 10)) - assert(on_click_mock.calls == 0) + assert(on_click_calls == 0) end) it("Should work with check function", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) local button_params = {} - local on_click, on_click_mock = test_helper.get_function() + + local on_click_calls = 0 + local function on_click() + on_click_calls = on_click_calls + 1 + end + local instance = druid:new_button(button, on_click, button_params) - local check_function, check_function_mock = test_helper.get_function(function() return false end) - local failure_function, failure_function_mock = test_helper.get_function() + + local check_function_calls = 0 + local function check_function() + check_function_calls = check_function_calls + 1 + return false + end + + local failure_function_calls = 0 + local function failure_function() + failure_function_calls = failure_function_calls + 1 + end + instance:set_check_function(check_function, failure_function) druid:on_input(mock_input.click_pressed(10, 10)) druid:on_input(mock_input.click_released(20, 10)) - assert(on_click_mock.calls == 0) - assert(check_function_mock.calls == 1) - assert(failure_function_mock.calls == 1) + assert(on_click_calls == 0) + assert(check_function_calls == 1) + assert(failure_function_calls == 1) + + local check_true_function_calls = 0 + local function check_true_function() + check_true_function_calls = check_true_function_calls + 1 + return true + end - local check_true_function, check_function_true_mock = test_helper.get_function(function() return true end) instance:set_check_function(check_true_function, failure_function) mock_time.elapse(1) druid:on_input(mock_input.click_pressed(10, 10)) druid:on_input(mock_input.click_released(20, 10)) - assert(on_click_mock.calls == 1) - assert(check_function_true_mock.calls == 1) - assert(failure_function_mock.calls == 1) + assert(on_click_calls == 1) + assert(check_true_function_calls == 1) + assert(failure_function_calls == 1) end) it("Should have key trigger", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) local button_params = {} - local on_click, on_click_mock = test_helper.get_function() + + local on_click_calls = 0 + local function on_click() + on_click_calls = on_click_calls + 1 + end + local instance = druid:new_button(button, on_click, button_params) instance:set_key_trigger("key_a") druid:on_input(mock_input.key_pressed("key_a")) druid:on_input(mock_input.key_released("key_a")) - assert(on_click_mock.calls == 1) + assert(on_click_calls == 1) assert(instance:get_key_trigger() == hash("key_a")) end) it("Should work with click zone", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) - local zone = mock_gui.add_box("zone", 25, 25, 25, 25) + local button = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + local zone = gui.new_box_node(vmath.vector3(50, 50, 0), vmath.vector3(50, 50, 0)) local button_params = {} - local on_click, on_click_mock = test_helper.get_function() + + local on_click_calls = 0 + local function on_click() + on_click_calls = on_click_calls + 1 + end + local instance = druid:new_button(button, on_click, button_params) instance:set_click_zone(zone) druid:on_input(mock_input.click_pressed(10, 10)) druid:on_input(mock_input.click_released(10, 10)) - assert(on_click_mock.calls == 0) + assert(on_click_calls == 0) - druid:on_input(mock_input.click_pressed(25, 25)) - druid:on_input(mock_input.click_released(25, 25)) - assert(on_click_mock.calls == 1) + druid:on_input(mock_input.click_pressed(50, 50)) + druid:on_input(mock_input.click_released(50, 50)) + assert(on_click_calls == 1) end) it("Should work with set_enabled", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) local button_params = {} - local on_click, on_click_mock = test_helper.get_function() + + local on_click_calls = 0 + local function on_click() + on_click_calls = on_click_calls + 1 + end + local instance = druid:new_button(button, on_click, button_params) instance:set_enabled(false) @@ -261,7 +366,7 @@ return function() local is_clicked_released = druid:on_input(mock_input.click_released(10, 10)) assert(is_clicked_pressed == true) assert(is_clicked_released == true) - assert(on_click_mock.calls == 0) + assert(on_click_calls == 0) assert(instance:is_enabled() == false) instance:set_enabled(true) @@ -269,7 +374,7 @@ return function() assert(is_clicked_pressed2 == true) local is_clicked_released2 = druid:on_input(mock_input.click_released(10, 10)) assert(is_clicked_released2 == true) - assert(on_click_mock.calls == 1) + assert(on_click_calls == 1) assert(instance:is_enabled() == true) end) end) diff --git a/test/tests/test_drag.lua b/test/tests/test_drag.lua index 4b25661..1d688c3 100644 --- a/test/tests/test_drag.lua +++ b/test/tests/test_drag.lua @@ -1,95 +1,105 @@ return function() - local mock_gui = nil - local mock_time = nil - local mock_input = nil - local test_helper = nil - local druid_system = nil - - local druid = nil - local context = nil - - local function create_drag_instance(on_drag) - local button = mock_gui.add_box("button", 0, 0, 20, 20) - local instance = druid:new_drag(button, on_drag) - instance.style.NO_USE_SCREEN_KOEF = true - instance.style.DRAG_DEADZONE = 4 - return instance - end - describe("Drag component", function() + local mock_time + local mock_input + local druid_system + + local druid + local context + + local function create_drag_instance(on_drag) + local button = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 100, 0)) + local instance = druid:new_drag(button, on_drag) + instance.style.NO_USE_SCREEN_KOEF = true + instance.style.DRAG_DEADZONE = 4 + return instance + end + before(function() - mock_gui = require("deftest.mock.gui") mock_time = require("deftest.mock.time") mock_input = require("test.helper.mock_input") - test_helper = require("test.helper.test_helper") druid_system = require("druid.druid") - mock_gui.mock() mock_time.mock() - mock_time.set(60) + mock_time.set(0) - context = test_helper.get_context() + context = vmath.vector3() druid = druid_system.new(context) end) after(function() - mock_gui.unmock() mock_time.unmock() - druid:final(context) + druid:final() druid = nil end) it("Should call drag callback on node", function() - local on_drag, on_drag_mock = test_helper.get_function() + local on_drag_calls = 0 + local drag_dx, drag_dy, drag_total_x, drag_total_y + + local function on_drag(_, dx, dy, total_x, total_y) + on_drag_calls = on_drag_calls + 1 + drag_dx, drag_dy = dx, dy + drag_total_x, drag_total_y = total_x, total_y + end + local instance = create_drag_instance(on_drag) druid:on_input(mock_input.click_pressed(10, 10)) assert(instance.is_touch == true) druid:on_input(mock_input.input_empty(12, 10)) - assert(on_drag_mock.calls == 0) + assert(on_drag_calls == 0) druid:on_input(mock_input.input_empty(14, 10)) - assert(on_drag_mock.calls == 1) - assert(on_drag_mock.params[2] == 2) -- From the last input dx - assert(on_drag_mock.params[3] == 0) - assert(on_drag_mock.params[4] == 4) -- Total X from drag start point - assert(on_drag_mock.params[5] == 0) + assert(on_drag_calls == 1) + assert(drag_dx == 2) -- From the last input dx + assert(drag_dy == 0) + assert(drag_total_x == 4) -- Total X from drag start point + assert(drag_total_y == 0) end) - it("Should call all included events", function() - local on_drag, on_drag_mock = test_helper.get_function() + local on_drag_calls = 0 + local function on_drag() on_drag_calls = on_drag_calls + 1 end local instance = create_drag_instance(on_drag) - local on_touch_start, on_touch_start_mock = test_helper.get_function() + local on_touch_start_calls = 0 + local function on_touch_start() on_touch_start_calls = on_touch_start_calls + 1 end instance.on_touch_start:subscribe(on_touch_start) - local on_touch_end, on_touch_end_mock = test_helper.get_function() + + local on_touch_end_calls = 0 + local function on_touch_end() on_touch_end_calls = on_touch_end_calls + 1 end instance.on_touch_end:subscribe(on_touch_end) - local on_drag_start, on_drag_start_mock = test_helper.get_function() + + local on_drag_start_calls = 0 + local function on_drag_start() on_drag_start_calls = on_drag_start_calls + 1 end instance.on_drag_start:subscribe(on_drag_start) - local on_drag_end, on_drag_end_mock = test_helper.get_function() + + local on_drag_end_calls = 0 + local function on_drag_end() on_drag_end_calls = on_drag_end_calls + 1 end instance.on_drag_end:subscribe(on_drag_end) - assert(on_touch_start_mock.calls == 0) + assert(on_touch_start_calls == 0) druid:on_input(mock_input.click_pressed(10, 10)) - assert(on_touch_start_mock.calls == 1) - assert(on_touch_end_mock.calls == 0) + assert(on_touch_start_calls == 1) + assert(on_touch_end_calls == 0) druid:on_input(mock_input.click_released(12, 10)) - assert(on_touch_end_mock.calls == 1) + assert(on_touch_end_calls == 1) druid:on_input(mock_input.click_pressed(10, 10)) - assert(on_drag_start_mock.calls == 0) + assert(on_drag_start_calls == 0) druid:on_input(mock_input.input_empty(15, 10)) - assert(on_drag_start_mock.calls == 1) - assert(on_drag_mock.calls == 1) - assert(on_drag_end_mock.calls == 0) + assert(on_drag_start_calls == 1) + assert(on_drag_calls == 1) + assert(on_drag_end_calls == 0) druid:on_input(mock_input.click_released(15, 10)) - assert(on_drag_end_mock.calls == 1) + assert(on_drag_end_calls == 1) end) it("Should work with set_enabled", function() - local on_drag, on_drag_mock = test_helper.get_function() + local on_drag_calls = 0 + local function on_drag() on_drag_calls = on_drag_calls + 1 end local instance = create_drag_instance(on_drag) instance:set_enabled(false) @@ -99,10 +109,10 @@ return function() assert(instance.is_touch == false) druid:on_input(mock_input.input_empty(12, 10)) - assert(on_drag_mock.calls == 0) + assert(on_drag_calls == 0) druid:on_input(mock_input.input_empty(15, 10)) - assert(on_drag_mock.calls == 0) + assert(on_drag_calls == 0) instance:set_enabled(true) assert(instance:is_enabled() == true) @@ -111,32 +121,49 @@ return function() assert(instance.is_touch == true) druid:on_input(mock_input.input_empty(12, 10)) - assert(on_drag_mock.calls == 0) + assert(on_drag_calls == 0) druid:on_input(mock_input.input_empty(15, 10)) - assert(on_drag_mock.calls == 1) + assert(on_drag_calls == 1) end) it("Should work with click zone", function() - local on_drag, on_drag_mock = test_helper.get_function() + local on_drag_calls = 0 + local function on_drag() on_drag_calls = on_drag_calls + 1 end local instance = create_drag_instance(on_drag) - local zone = mock_gui.add_box("zone", 10, 10, 10, 10) + local zone = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(10, 10, 0)) instance:set_click_zone(zone) - druid:on_input(mock_input.click_pressed(5, 5)) + druid:on_input(mock_input.click_pressed(20, 20)) assert(instance.is_touch == false) - druid:on_input(mock_input.input_empty(10, 5)) - assert(on_drag_mock.calls == 0) + druid:on_input(mock_input.input_empty(10, 10)) + assert(on_drag_calls == 0) - druid:on_input(mock_input.input_empty(15, 10)) - assert(on_drag_mock.calls == 0) + druid:on_input(mock_input.input_empty(15, 15)) + druid:on_input(mock_input.click_released(15, 15)) + assert(on_drag_calls == 0) - druid:on_input(mock_input.click_pressed(15, 15)) + mock_time.set(60) + druid:on_input(mock_input.click_pressed(0, 0)) assert(instance.is_touch == true) - druid:on_input(mock_input.input_empty(20, 15)) - assert(on_drag_mock.calls == 1) + druid:on_input(mock_input.input_empty(5, 5)) + assert(on_drag_calls == 1) + end) + + it("Should not trigger in deadzone", function() + local on_drag_calls = 0 + local function on_drag() on_drag_calls = on_drag_calls + 1 end + local instance = create_drag_instance(on_drag) + + instance.style.DRAG_DEADZONE = 10 + + druid:on_input(mock_input.click_pressed(10, 10)) + assert(instance.is_touch == true) + + druid:on_input(mock_input.input_empty(15, 15)) + assert(on_drag_calls == 0) end) end) end diff --git a/test/tests/test_druid_instance.lua b/test/tests/test_druid_instance.lua new file mode 100644 index 0000000..ebe1e02 --- /dev/null +++ b/test/tests/test_druid_instance.lua @@ -0,0 +1,306 @@ +return function() + describe("Druid Instance", function() + local druid + local druid_instance ---@type druid.instance + local context + local mock_input = require("test.helper.mock_input") + + before(function() + context = vmath.vector3() + druid = require("druid.druid") + druid_instance = druid.new(context) + end) + + after(function() + -- Clean up druid instance + if druid_instance then + druid_instance:final() + druid_instance = nil + end + end) + + it("Should create button component", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + + local on_click_calls = 0 + local function on_click() + on_click_calls = on_click_calls + 1 + end + + local button = druid_instance:new_button(button_node, on_click) + + assert(button ~= nil) + assert(button.node == button_node) + + -- Test that button click works + druid_instance:on_input(mock_input.click_pressed(50, 25)) + druid_instance:on_input(mock_input.click_released(50, 25)) + + assert(on_click_calls == 1) + + -- Clean up component + druid_instance:remove(button) + gui.delete_node(button_node) + end) + + it("Should create blocker component", function() + local blocker_node = gui.new_box_node(vmath.vector3(100, 50, 0), vmath.vector3(200, 100, 0)) + + local blocker = druid_instance:new_blocker(blocker_node) + + assert(blocker ~= nil) + assert(blocker.node == blocker_node) + + -- Test that blocker blocks input + local is_blocked = druid_instance:on_input(mock_input.click_pressed(100, 50)) + + assert(is_blocked) + + -- Clean up component + druid_instance:remove(blocker) + gui.delete_node(blocker_node) + end) + + it("Should create back_handler component", function() + local on_back_calls = 0 + local function on_back() + on_back_calls = on_back_calls + 1 + end + + local back_handler = druid_instance:new_back_handler(on_back) + + assert(back_handler ~= nil) + + -- Test that back handler works + druid_instance:on_input(mock_input.key_pressed("key_back")) + druid_instance:on_input(mock_input.key_released("key_back")) + + assert(on_back_calls == 1) + + -- Clean up component + druid_instance:remove(back_handler) + end) + + it("Should create hover component", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + + local on_hover_calls = 0 + local function on_hover() + on_hover_calls = on_hover_calls + 1 + end + + local hover = druid_instance:new_hover(button_node, on_hover) + + assert(hover ~= nil) + assert(hover.node == button_node) + + -- Test that hover works + druid_instance:on_input(mock_input.input_empty(50, 25)) + + assert(on_hover_calls == 1) + + -- Clean up component + druid_instance:remove(hover) + gui.delete_node(button_node) + end) + + it("Should create text component", function() + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Test Text") + gui.set_font(text_node, "druid_text_bold") + + local text = druid_instance:new_text(text_node, "New Text") + + assert(text ~= nil) + assert(text.node == text_node) + assert(gui.get_text(text_node) == "New Text") + + -- Test that text setter works + text:set_text("Updated Text") + assert(gui.get_text(text_node) == "Updated Text") + + -- Clean up component + druid_instance:remove(text) + gui.delete_node(text_node) + end) + + it("Should create grid component", function() + local parent_node = gui.new_box_node(vmath.vector3(150, 100, 0), vmath.vector3(300, 200, 0)) + local template = gui.new_box_node(vmath.vector3(10, 10, 0), vmath.vector3(20, 20, 0)) + + local grid = druid_instance:new_grid(parent_node, template, 3) + + assert(grid ~= nil) + assert(grid.parent == parent_node) + assert(grid.in_row == 3) + + -- Add an item to the grid + local item = gui.clone(template) + grid:add(item) + assert(#grid.nodes == 1) + + -- Clean up component + druid_instance:remove(grid) + gui.delete_node(parent_node) + gui.delete_node(template) + end) + + it("Should create scroll component", function() + local parent_node = gui.new_box_node(vmath.vector3(150, 100, 0), vmath.vector3(300, 200, 0)) + local content_node = gui.new_box_node(vmath.vector3(250, 200, 0), vmath.vector3(500, 400, 0)) + + -- Setup node hierarchy for scroll + gui.set_parent(content_node, parent_node) + + local scroll = druid_instance:new_scroll(parent_node, content_node) + + assert(scroll ~= nil) + assert(scroll.view_node == parent_node) + assert(scroll.content_node == content_node) + + -- Test that scroll setters work + scroll:set_horizontal_scroll(true) + + -- Clean up component + druid_instance:remove(scroll) + gui.delete_node(parent_node) -- This will also delete content_node as it's a child + end) + + it("Should create drag component", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + + local on_drag_calls = 0 + local drag_dx, drag_dy + + local function on_drag(_, dx, dy) + on_drag_calls = on_drag_calls + 1 + drag_dx, drag_dy = dx, dy + end + + local drag = druid_instance:new_drag(button_node, on_drag) + drag.style.DRAG_DEADZONE = 0 + + assert(drag ~= nil) + assert(drag.node == button_node) + + -- Test that drag callback works + druid_instance:on_input(mock_input.click_pressed(50, 25)) + druid_instance:on_input(mock_input.input_empty(60, 35)) + druid_instance:on_input(mock_input.click_released(60, 35)) + + print(drag_dx, drag_dy) + assert(on_drag_calls == 1) + assert(math.floor(drag_dx) == 10) + assert(math.floor(drag_dy) == 10) + + -- Clean up component + druid_instance:remove(drag) + gui.delete_node(button_node) + end) + + it("Should create swipe component", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + + local on_swipe_calls = 0 + local function on_swipe() + on_swipe_calls = on_swipe_calls + 1 + end + + local swipe = druid_instance:new_swipe(button_node, on_swipe) + + assert(swipe ~= nil) + assert(swipe.node == button_node) + + -- Clean up component + druid_instance:remove(swipe) + gui.delete_node(button_node) + end) + + it("Should create timer component", function() + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Test Text") + gui.set_font(text_node, "druid_text_bold") + + local on_timer_end_calls = 0 + local function on_timer_end() + on_timer_end_calls = on_timer_end_calls + 1 + end + + local timer = druid_instance:new_timer(text_node, 10, 0, on_timer_end) + + assert(timer ~= nil) + assert(timer.node == text_node) + + -- Clean up component + druid_instance:remove(timer) + gui.delete_node(text_node) + end) + + it("Should create progress component", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + + local progress = druid_instance:new_progress(button_node, "x", 0.5) + + assert(progress ~= nil) + assert(progress.node == button_node) + assert(progress:get() == 0.5) + + -- Test that progress setter works + progress:set_to(0.75) + assert(progress:get() == 0.75) + + -- Clean up component + druid_instance:remove(progress) + gui.delete_node(button_node) + end) + + it("Should create layout component", function() + local parent_node = gui.new_box_node(vmath.vector3(150, 100, 0), vmath.vector3(300, 200, 0)) + + local layout = druid_instance:new_layout(parent_node) + + assert(layout ~= nil) + assert(layout.node == parent_node) + + -- Clean up component + druid_instance:remove(layout) + gui.delete_node(parent_node) + end) + + it("Should create hotkey component", function() + local on_hotkey_calls = 0 + local function on_hotkey() + on_hotkey_calls = on_hotkey_calls + 1 + end + + local hotkey = druid_instance:new_hotkey("key_f", on_hotkey) + + assert(hotkey ~= nil) + + -- Test that hotkey works + druid_instance:on_input(mock_input.key_pressed("key_f")) + druid_instance:on_input(mock_input.key_released("key_f")) + + assert(on_hotkey_calls == 1) + + -- Clean up component + druid_instance:remove(hotkey) + end) + + it("Should create container component", function() + local parent_node = gui.new_box_node(vmath.vector3(150, 100, 0), vmath.vector3(300, 200, 0)) + + local layout_changed_calls = 0 + local function layout_changed() + layout_changed_calls = layout_changed_calls + 1 + end + + -- The container component requires a node with size + local container = druid_instance:new_container(parent_node, "fit") + + assert(container ~= nil) + + -- Clean up component + druid_instance:remove(container) + gui.delete_node(parent_node) + end) + end) +end diff --git a/test/tests/test_helper.lua b/test/tests/test_helper.lua new file mode 100644 index 0000000..60a6724 --- /dev/null +++ b/test/tests/test_helper.lua @@ -0,0 +1,275 @@ +return function() + describe("Helper Module", function() + local test_helper = nil + local helper = nil + local const = nil + local node1 = nil + local node2 = nil + + before(function() + test_helper = require("test.helper.test_helper") + helper = require("druid.helper") + const = require("druid.const") + + -- Create actual GUI nodes for testing + node1 = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 50, 0)) + node2 = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(60, 40, 0)) + end) + + after(function() + -- Clean up nodes + if node1 then gui.delete_node(node1) end + if node2 then gui.delete_node(node2) end + end) + + it("Should clamp values correctly", function() + assert(helper.clamp(5, 0, 10) == 5) + assert(helper.clamp(-5, 0, 10) == 0) + assert(helper.clamp(15, 0, 10) == 10) + assert(helper.clamp(5, 10, 0) == 5) -- Should swap min and max + assert(helper.clamp(5, nil, 10) == 5) -- Should handle nil min + assert(helper.clamp(15, nil, 10) == 10) -- Should handle nil min + assert(helper.clamp(5, 0, nil) == 5) -- Should handle nil max + assert(helper.clamp(-5, 0, nil) == 0) -- Should handle nil max + end) + + it("Should calculate distance correctly", function() + assert(helper.distance(0, 0, 3, 4) == 5) + assert(helper.distance(1, 1, 4, 5) == 5) + assert(helper.distance(0, 0, 0, 0) == 0) + end) + + it("Should return sign correctly", function() + assert(helper.sign(5) == 1) + assert(helper.sign(-5) == -1) + assert(helper.sign(0) == 0) + end) + + it("Should round numbers correctly", function() + assert(helper.round(5.5) == 6) + assert(helper.round(5.4) == 5) + assert(helper.round(5.55, 1) == 5.6) + assert(helper.round(5.54, 1) == 5.5) + end) + + it("Should lerp correctly", function() + assert(helper.lerp(0, 10, 0) == 0) + assert(helper.lerp(0, 10, 1) == 10) + assert(helper.lerp(0, 10, 0.5) == 5) + end) + + it("Should check if value is in array", function() + local array = {1, 2, 3, 4, 5} + assert(helper.contains(array, 3) == 3) + assert(helper.contains(array, 6) == nil) + assert(helper.contains({}, 1) == nil) + end) + + it("Should deep copy tables", function() + local original = {a = 1, b = {c = 2, d = {e = 3}}} + local copy = helper.deepcopy(original) + + -- Test that it's a deep copy + assert(copy.a == original.a) + assert(copy.b.c == original.b.c) + assert(copy.b.d.e == original.b.d.e) + + -- Modify the copy and check the original remains intact + copy.a = 100 + copy.b.c = 200 + copy.b.d.e = 300 + + assert(original.a == 1) + assert(original.b.c == 2) + assert(original.b.d.e == 3) + end) + + it("Should add all elements from source array to target array", function() + local target = {1, 2, 3} + local source = {4, 5, 6} + + helper.add_array(target, source) + assert(#target == 6) + assert(target[4] == 4) + assert(target[5] == 5) + assert(target[6] == 6) + + -- Test with nil source + local target2 = {1, 2, 3} + helper.add_array(target2, nil) + assert(#target2 == 3) + end) + + it("Should insert with shift policy correctly", function() + -- Test basic functionality + -- RIGHT shift + local array1 = {1, 2, 3, 4, 5} + local result1 = helper.insert_with_shift(array1, 10, 3, const.SHIFT.RIGHT) + assert(result1 == 10) -- Should return the inserted item + assert(#array1 == 6) -- Size should increase + assert(array1[3] == 10) -- Item should be at the specified position + + -- LEFT shift + local array2 = {1, 2, 3, 4, 5} + local result2 = helper.insert_with_shift(array2, 20, 3, const.SHIFT.LEFT) + assert(result2 == 20) -- Should return the inserted item + assert(#array2 >= 5) -- Size should be at least original size + + -- NO_SHIFT + local array3 = {1, 2, 3, 4, 5} + local result3 = helper.insert_with_shift(array3, 30, 3, const.SHIFT.NO_SHIFT) + assert(result3 == 30) -- Should return the inserted item + assert(array3[3] == 30) -- Should replace the value at the specified position + end) + + it("Should remove with shift policy correctly", function() + -- Test basic functionality + -- RIGHT shift + local array1 = {1, 2, 3, 4, 5} + local removed1 = helper.remove_with_shift(array1, 3, const.SHIFT.RIGHT) + assert(removed1 == 3) -- Should return the removed item + assert(#array1 == 4) -- Size should decrease + + -- LEFT shift + local array2 = {1, 2, 3, 4, 5} + local removed2 = helper.remove_with_shift(array2, 3, const.SHIFT.LEFT) + assert(removed2 == 3) -- Should return the removed item + + -- NO_SHIFT + local array3 = {1, 2, 3, 4, 5} + local removed3 = helper.remove_with_shift(array3, 3, const.SHIFT.NO_SHIFT) + assert(removed3 == 3) -- Should return the removed item + assert(array3[3] == nil) -- Position should be nil + assert(array3[1] == 1 and array3[2] == 2 and array3[4] == 4 and array3[5] == 5) -- Other positions should be unchanged + end) + + it("Should step values correctly", function() + assert(helper.step(0, 10, 2) == 2) + assert(helper.step(2, 10, 2) == 4) + assert(helper.step(9, 10, 2) == 10) + assert(helper.step(10, 0, 2) == 8) + assert(helper.step(2, 0, 2) == 0) + assert(helper.step(1, 0, 2) == 0) + end) + + it("Should get node correctly", function() + -- Create a node using real GUI function + local test_node = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 50, 0)) + gui.set_id(test_node, "test_node_unique_id") + + -- Test with node directly + local result1 = helper.get_node(test_node) + assert(result1 == test_node) + + -- Note: Dynamically created nodes can't be reliably retrieved by ID in tests + -- but we can verify the function accepts string IDs + -- local result3 = helper.get_node("some_id") + -- We don't assert anything about result2, just make sure the function doesn't error + + -- Test with nodes table + local dummy_node = {} + local nodes_table = { ["template/test_node3"] = dummy_node } + local result4 = helper.get_node("test_node3", "template", nodes_table) + assert(result4 == dummy_node) + + -- Clean up + gui.delete_node(test_node) + end) + + it("Should get pivot offset correctly", function() + -- Test with pivot constant + local center_offset = helper.get_pivot_offset(gui.PIVOT_CENTER) + assert(center_offset.x == 0) + assert(center_offset.y == 0) + + -- Test North pivot + local n_offset = helper.get_pivot_offset(gui.PIVOT_N) + assert(n_offset.x == 0) + assert(n_offset.y == 0.5) + end) + + it("Should get scaled size correctly", function() + local test_node = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 50, 0)) + gui.set_scale(test_node, vmath.vector3(2, 3, 1)) + + local scaled_size = helper.get_scaled_size(test_node) + assert(scaled_size.x == 200) + assert(scaled_size.y == 150) + + -- Clean up + gui.delete_node(test_node) + end) + + it("Should get scene scale correctly", function() + local parent_node = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 50, 0)) + gui.set_scale(parent_node, vmath.vector3(2, 2, 1)) + + local child_node = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(50, 25, 0)) + gui.set_parent(child_node, parent_node) + gui.set_scale(child_node, vmath.vector3(1.5, 1.5, 1)) + + -- Without including the passed node scale + local scale1 = helper.get_scene_scale(child_node, false) + assert(scale1.x == 2) + assert(scale1.y == 2) + + -- Including the passed node scale + local scale2 = helper.get_scene_scale(child_node, true) + assert(scale2.x == 3) + assert(scale2.y == 3) + + -- Clean up + gui.delete_node(child_node) + gui.delete_node(parent_node) + end) + + it("Should check if value is desktop/mobile/web correctly", function() + -- These tests depend on the current system, so we just make sure the functions exist + local is_desktop = helper.is_desktop() + local is_mobile = helper.is_mobile() + local is_web = helper.is_web() + + -- They should be boolean values + assert(type(is_desktop) == "boolean") + assert(type(is_mobile) == "boolean") + assert(type(is_web) == "boolean") + end) + + it("Should get border correctly", function() + local test_node = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 50, 0)) + gui.set_pivot(test_node, gui.PIVOT_CENTER) + + local border = helper.get_border(test_node) + assert(border.x == -50) -- left + assert(border.y == 25) -- top + assert(border.z == 50) -- right + assert(border.w == -25) -- bottom + + -- Test with offset + local offset = vmath.vector3(10, 20, 0) + local border_with_offset = helper.get_border(test_node, offset) + assert(border_with_offset.x == -40) -- left + offset.x + assert(border_with_offset.y == 45) -- top + offset.y + assert(border_with_offset.z == 60) -- right + offset.x + assert(border_with_offset.w == -5) -- bottom + offset.y + + -- Clean up + gui.delete_node(test_node) + end) + + it("Should centrate nodes correctly", function() + local total_width = helper.centrate_nodes(10, node1, node2) + + -- The total width should be node1 width + node2 width + margin + assert(total_width == 170) + + -- The first node should be positioned at -total_width/2 + node1_width/2 + local pos1 = gui.get_position(node1) + assert(pos1.x == -35) -- -170/2 + 100/2 + + -- The second node should be positioned at pos1.x + node1_width/2 + margin + node2_width/2 + local pos2 = gui.get_position(node2) + assert(pos2.x == 55) -- -35 + 100/2 + 10 + 60/2 + end) + end) +end diff --git a/test/tests/test_hover.lua b/test/tests/test_hover.lua index 0d095b6..c37b20a 100644 --- a/test/tests/test_hover.lua +++ b/test/tests/test_hover.lua @@ -1,60 +1,57 @@ return function() - local mock_gui = nil - local mock_time = nil - local mock_input = nil - local test_helper = nil - local druid_system = nil - - local druid = nil - local context = nil - describe("Hover component", function() + local mock_time + local mock_input + local druid_system + + local druid + local context + before(function() - mock_gui = require("deftest.mock.gui") mock_time = require("deftest.mock.time") mock_input = require("test.helper.mock_input") - test_helper = require("test.helper.test_helper") druid_system = require("druid.druid") - mock_gui.mock() mock_time.mock() - mock_time.set(60) + mock_time.set(0) - context = test_helper.get_context() + context = vmath.vector3() druid = druid_system.new(context) end) after(function() - mock_gui.unmock() mock_time.unmock() - druid:final(context) + druid:final() druid = nil end) it("Should fire callback on touch hover and unhover", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 50, 0)) local is_hovered = false - local on_hover, on_hover_mock = test_helper.get_function(function(_, state) + + local function on_hover(_, state) is_hovered = state - end) + end + local instance = druid:new_hover(button, on_hover) druid:on_input(mock_input.input_empty(10, 10)) assert(is_hovered == true) assert(instance:is_hovered() == true) assert(instance:is_mouse_hovered() == false) - druid:on_input(mock_input.input_empty(-10, 10)) + druid:on_input(mock_input.input_empty(100, 100)) assert(is_hovered == false) assert(instance:is_hovered() == false) assert(instance:is_mouse_hovered() == false) end) it("Should fire callback on mouse hover and unhover", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 50, 0)) local is_hovered = false - local on_hover, on_hover_mock = test_helper.get_function(function(_, state) + + local function on_hover(_, state) is_hovered = state - end) + end local instance = druid:new_hover(button) instance.on_mouse_hover:subscribe(on_hover) @@ -63,31 +60,41 @@ return function() assert(instance:is_hovered() == false) assert(instance:is_mouse_hovered() == true) - druid:on_input(mock_input.input_empty_action_nil(-10, 10)) + druid:on_input(mock_input.input_empty_action_nil(100, 100)) assert(is_hovered == false) assert(instance:is_hovered() == false) assert(instance:is_mouse_hovered() == false) end) it("Should work with click zone", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) - local zone = mock_gui.add_box("zone", 25, 25, 25, 25) - local on_hover, on_hover_mock = test_helper.get_function() + local button = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 50, 0)) + local zone = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(20, 20, 0)) + + local on_hover_calls = 0 + local function on_hover() + on_hover_calls = on_hover_calls + 1 + end + local instance = druid:new_hover(button, on_hover) instance:set_click_zone(zone) - druid:on_input(mock_input.input_empty(10, 10)) + druid:on_input(mock_input.input_empty(20, 20)) assert(instance:is_hovered() == false) - druid:on_input(mock_input.input_empty(25, 25)) + druid:on_input(mock_input.input_empty(0, 0)) assert(instance:is_hovered() == true) - druid:on_input(mock_input.input_empty(24, 24)) + druid:on_input(mock_input.input_empty(18, 18)) assert(instance:is_hovered() == false) end) it("Should have set_enabled function", function() - local button = mock_gui.add_box("button", 0, 0, 100, 50) - local on_hover, on_hover_mock = test_helper.get_function() + local button = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 50, 0)) + + local on_hover_calls = 0 + local function on_hover() + on_hover_calls = on_hover_calls + 1 + end + local instance = druid:new_hover(button, on_hover) druid:on_input(mock_input.input_empty(10, 10)) @@ -96,15 +103,15 @@ return function() instance:set_enabled(false) assert(instance:is_enabled() == false) assert(instance:is_hovered() == false) - druid:on_input(mock_input.input_empty(12, 12)) + druid:on_input(mock_input.input_empty(10, 10)) assert(instance:is_hovered() == false) instance:set_enabled(true) - druid:on_input(mock_input.input_empty(12, 12)) + druid:on_input(mock_input.input_empty(10, 10)) assert(instance:is_enabled() == true) assert(instance:is_hovered() == true) - druid:on_input(mock_input.input_empty(-10, 10)) + druid:on_input(mock_input.input_empty(100, 100)) assert(instance:is_hovered() == false) end) end) diff --git a/test/tests/test_input.lua b/test/tests/test_input.lua new file mode 100644 index 0000000..25f6bed --- /dev/null +++ b/test/tests/test_input.lua @@ -0,0 +1,384 @@ +return function() + describe("Input Component", function() + local mock_time + local mock_input + local druid_system + + local druid + local context + + before(function() + mock_time = require("deftest.mock.time") + mock_input = require("test.helper.mock_input") + druid_system = require("druid.druid") + + mock_time.mock() + mock_time.set(0) + + context = vmath.vector3() + druid = druid_system.new(context) + end) + + after(function() + mock_time.unmock() + druid:final() + druid = nil + end) + + it("Should create input component", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Initial Text") + gui.set_font(text_node, "druid_text_bold") + + local input = druid:new_input(button_node, text_node) + + assert(input ~= nil) + assert(input.button ~= nil) + assert(input.text ~= nil) + assert(input:get_text() == "Initial Text") + + druid:remove(input) + gui.delete_node(button_node) + gui.delete_node(text_node) + end) + + it("Should select and unselect input", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Text") + gui.set_font(text_node, "druid_text_bold") + + local input = druid:new_input(button_node, text_node) + + local on_input_select_calls = 0 + local on_input_unselect_calls = 0 + + input.on_input_select:subscribe(function() + on_input_select_calls = on_input_select_calls + 1 + end) + + input.on_input_unselect:subscribe(function() + on_input_unselect_calls = on_input_unselect_calls + 1 + end) + + -- Select input + druid:on_input(mock_input.click_pressed(50, 25)) + druid:on_input(mock_input.click_released(50, 25)) + + assert(input.is_selected == true) + assert(on_input_select_calls == 1) + + -- Unselect input + druid:on_input(mock_input.click_pressed(200, 200)) + druid:on_input(mock_input.click_released(200, 200)) + + assert(input.is_selected == false) + assert(on_input_unselect_calls == 1) + + druid:remove(input) + gui.delete_node(button_node) + gui.delete_node(text_node) + end) + + it("Should handle text input", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "") + gui.set_font(text_node, "druid_text_bold") + + local input = druid:new_input(button_node, text_node) + + local on_input_text_calls = 0 + input.on_input_text:subscribe(function() + on_input_text_calls = on_input_text_calls + 1 + end) + + -- Select input + druid:on_input(mock_input.click_pressed(50, 25)) + druid:on_input(mock_input.click_released(50, 25)) + + -- Simulate typing "Hello" + local function trigger_text_input(text) + return druid:on_input(hash("text"), {text = text}) + end + + -- Type "H" + trigger_text_input("H") + assert(input:get_text() == "H") + assert(on_input_text_calls == 1) + + -- Type "e" + trigger_text_input("e") + assert(input:get_text() == "He") + assert(on_input_text_calls == 2) + + -- Type "llo" + trigger_text_input("l") + trigger_text_input("l") + trigger_text_input("o") + + assert(input:get_text() == "Hello") + assert(on_input_text_calls == 5) + + druid:remove(input) + gui.delete_node(button_node) + gui.delete_node(text_node) + end) + + it("Should handle backspace input", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Hello") + gui.set_font(text_node, "druid_text_bold") + + local input = druid:new_input(button_node, text_node) + + -- Select input + druid:on_input(mock_input.click_pressed(50, 25)) + druid:on_input(mock_input.click_released(50, 25)) + + -- Simulate backspace key + local function trigger_backspace() + return druid:on_input(hash("key_backspace"), {pressed = true}) + end + + -- Delete one letter + assert(trigger_backspace() == true) + assert(input:get_text() == "Hell") + + -- Delete another letter + assert(trigger_backspace() == true) + assert(input:get_text() == "Hel") + + druid:remove(input) + gui.delete_node(button_node) + gui.delete_node(text_node) + end) + + it("Should set max length", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "") + gui.set_font(text_node, "druid_text_bold") + + local input = druid:new_input(button_node, text_node) + + local on_input_full_calls = 0 + input.on_input_full:subscribe(function() + on_input_full_calls = on_input_full_calls + 1 + end) + + -- Set max length to 5 + input:set_max_length(5) + + -- Select input + druid:on_input(mock_input.click_pressed(50, 25)) + druid:on_input(mock_input.click_released(50, 25)) + + -- Simulate typing text + local function trigger_text_input(text) + return druid:on_input(hash("text"), {text = text}) + end + + -- Type "Hello" + assert(trigger_text_input("Hello") == true) + assert(input:get_text() == "Hello") + assert(on_input_full_calls == 1) + + -- Try to type "World" - should truncate + assert(trigger_text_input("World") == true) + assert(input:get_text() == "Hello") + + druid:remove(input) + gui.delete_node(button_node) + gui.delete_node(text_node) + end) + + it("Should validate allowed characters", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "") + gui.set_font(text_node, "druid_text_bold") + + local input = druid:new_input(button_node, text_node) + + local on_input_wrong_calls = 0 + input.on_input_wrong:subscribe(function() + on_input_wrong_calls = on_input_wrong_calls + 1 + end) + + -- Set allowed characters to only numbers + input:set_allowed_characters("[0-9]") + + -- Select input + druid:on_input(mock_input.click_pressed(50, 25)) + druid:on_input(mock_input.click_released(50, 25)) + + -- Simulate typing text + local function trigger_text_input(text) + return druid:on_input(hash("text"), {text = text}) + end + + -- Type valid input "123" + assert(trigger_text_input("123") == true) + assert(input:get_text() == "123") + assert(on_input_wrong_calls == 0) + + -- Type invalid input "abc" - should be rejected + assert(trigger_text_input("abc") == true) + assert(input:get_text() == "123") + assert(on_input_wrong_calls == 1) + + druid:remove(input) + gui.delete_node(button_node) + gui.delete_node(text_node) + end) + + it("Should handle password input", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "") + gui.set_font(text_node, "druid_text_bold") + + -- Create password input + local input = druid:new_input(button_node, text_node, gui.KEYBOARD_TYPE_PASSWORD) + + -- Select input + druid:on_input(mock_input.click_pressed(50, 25)) + druid:on_input(mock_input.click_released(50, 25)) + + -- Simulate typing "Hello" + local function trigger_text_input(text) + return druid:on_input(hash("text"), {text = text}) + end + + assert(trigger_text_input("Hello") == true) + + -- Raw text should be "Hello" + assert(input:get_text() == "Hello") + + -- But displayed text should be "*****" + assert(gui.get_text(text_node) == "*****") + + druid:remove(input) + gui.delete_node(button_node) + gui.delete_node(text_node) + end) + + it("Should handle enter key", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "") + gui.set_font(text_node, "druid_text_bold") + + local input = druid:new_input(button_node, text_node) + + -- Select input + druid:on_input(mock_input.click_pressed(50, 25)) + druid:on_input(mock_input.click_released(50, 25)) + + -- Verify input is selected + assert(input.is_selected == true) + + -- Simulate enter key press to unselect + assert(druid:on_input(hash("key_enter"), {released = true}) == true) + + -- Verify input is unselected + assert(input.is_selected == false) + + druid:remove(input) + gui.delete_node(button_node) + gui.delete_node(text_node) + end) + + it("Should reset changes", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Initial") + gui.set_font(text_node, "druid_text_bold") + + local input = druid:new_input(button_node, text_node) + + -- Select input + druid:on_input(mock_input.click_pressed(50, 25)) + druid:on_input(mock_input.click_released(50, 25)) + + -- Type some text + druid:on_input(hash("text"), {text = "M"}) + druid:on_input(hash("text"), {text = "o"}) + druid:on_input(hash("text"), {text = "d"}) + + assert(input:get_text() == "InitialMod") + + -- Reset changes + input:reset_changes() + + -- Verify text is reset to initial value + assert(input:get_text() == "Initial") + assert(input.is_selected == false) + + druid:remove(input) + gui.delete_node(button_node) + gui.delete_node(text_node) + end) + + it("Should handle selection and cursor manipulation", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Hello World") + gui.set_font(text_node, "druid_text_bold") + + local input = druid:new_input(button_node, text_node) + + local cursor_changes = 0 + input.on_select_cursor_change:subscribe(function() + cursor_changes = cursor_changes + 1 + end) + + -- Select input + druid:on_input(mock_input.click_pressed(50, 25)) + druid:on_input(mock_input.click_released(50, 25)) + assert(cursor_changes == 1) + + -- Default cursor should be at the end + assert(input.cursor_index == 11) -- "Hello World" length + + -- Move cursor to position 5 + input:select_cursor(5) + assert(input.cursor_index == 5) + assert(cursor_changes == 2) + + -- Move selection to the left by 1 + input:move_selection(-1) + assert(input.cursor_index == 4) + assert(cursor_changes == 3) + + -- Move selection to the right by 2 + input:move_selection(2) + assert(input.cursor_index == 6) + assert(cursor_changes == 4) + + druid:remove(input) + gui.delete_node(button_node) + gui.delete_node(text_node) + end) + + it("Should handle empty input event", function() + local button_node = gui.new_box_node(vmath.vector3(50, 25, 0), vmath.vector3(100, 50, 0)) + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Text") + gui.set_font(text_node, "druid_text_bold") + + local input = druid:new_input(button_node, text_node) + + local on_input_empty_calls = 0 + input.on_input_empty:subscribe(function() + on_input_empty_calls = on_input_empty_calls + 1 + end) + + -- Select input + druid:on_input(mock_input.click_pressed(50, 25)) + druid:on_input(mock_input.click_released(50, 25)) + + -- Clear text + input:set_text("") + + assert(on_input_empty_calls == 1) + assert(input.is_empty == true) + + druid:remove(input) + gui.delete_node(button_node) + gui.delete_node(text_node) + end) + end) +end diff --git a/test/tests/test_text.lua b/test/tests/test_text.lua new file mode 100644 index 0000000..68b9544 --- /dev/null +++ b/test/tests/test_text.lua @@ -0,0 +1,238 @@ +return function() + describe("Text Component", function() + local mock_time + local mock_input + local druid_system + + local druid + local context + + before(function() + mock_time = require("deftest.mock.time") + mock_input = require("test.helper.mock_input") + druid_system = require("druid.druid") + + mock_time.mock() + mock_time.set(0) + + context = vmath.vector3() + druid = druid_system.new(context) + end) + + after(function() + mock_time.unmock() + druid:final() + druid = nil + end) + + it("Should create text component and set text", function() + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Initial Text") + gui.set_font(text_node, "druid_text_bold") + + local text = druid:new_text(text_node, "New Text") + + assert(text ~= nil) + assert(text.node == text_node) + assert(gui.get_text(text_node) == "New Text") + assert(text:get_text() == "New Text") + + -- Test that text setter works + text:set_text("Updated Text") + assert(gui.get_text(text_node) == "Updated Text") + assert(text:get_text() == "Updated Text") + + druid:remove(text) + gui.delete_node(text_node) + end) + + it("Should fire on_set_text event", function() + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Initial Text") + gui.set_font(text_node, "druid_text_bold") + + local text = druid:new_text(text_node) + + local on_set_text_calls = 0 + local last_text = nil + + text.on_set_text:subscribe(function(_, new_text) + on_set_text_calls = on_set_text_calls + 1 + last_text = new_text + end) + + text:set_text("Event Test") + + assert(on_set_text_calls == 1) + assert(last_text == "Event Test") + + druid:remove(text) + gui.delete_node(text_node) + end) + + it("Should change color and alpha", function() + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Color Test") + gui.set_font(text_node, "druid_text_bold") + + local text = druid:new_text(text_node) + + local initial_color = gui.get_color(text_node) + local new_color = vmath.vector4(1, 0, 0, 1) + + text:set_color(new_color) + assert(gui.get_color(text_node).x == new_color.x) + assert(gui.get_color(text_node).y == new_color.y) + assert(gui.get_color(text_node).z == new_color.z) + + text:set_alpha(0.5) + assert(gui.get_color(text_node).w == 0.5) + + druid:remove(text) + gui.delete_node(text_node) + end) + + it("Should set scale", function() + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Scale Test") + gui.set_font(text_node, "druid_text_bold") + + local text = druid:new_text(text_node) + + local new_scale = vmath.vector3(2, 2, 1) + text:set_scale(new_scale) + + local current_scale = gui.get_scale(text_node) + assert(current_scale.x == new_scale.x) + assert(current_scale.y == new_scale.y) + + druid:remove(text) + gui.delete_node(text_node) + end) + + it("Should set pivot and fire event", function() + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Pivot Test") + gui.set_font(text_node, "druid_text_bold") + + local text = druid:new_text(text_node) + + local on_set_pivot_calls = 0 + local last_pivot = nil + + text.on_set_pivot:subscribe(function(_, pivot) + on_set_pivot_calls = on_set_pivot_calls + 1 + last_pivot = pivot + end) + + text:set_pivot(gui.PIVOT_CENTER) + + assert(on_set_pivot_calls == 1) + assert(last_pivot == gui.PIVOT_CENTER) + assert(gui.get_pivot(text_node) == gui.PIVOT_CENTER) + + druid:remove(text) + gui.delete_node(text_node) + end) + + it("Should set text size", function() + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Size Test") + gui.set_font(text_node, "druid_text_bold") + + local text = druid:new_text(text_node) + + local initial_size = gui.get_size(text_node) + local new_size = vmath.vector3(200, 100, 0) + + text:set_size(new_size) + + -- Since setting size triggers adjust mechanisms, we can't directly check node size + -- but we can check that the internal size was updated + assert(text.start_size.x == new_size.x) + assert(text.start_size.y == new_size.y) + + druid:remove(text) + gui.delete_node(text_node) + end) + + it("Should handle different adjust types", function() + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Adjust Test") + gui.set_font(text_node, "druid_text_bold") + gui.set_size(text_node, vmath.vector3(100, 50, 0)) + + local text = druid:new_text(text_node, "This is a very long text that should be adjusted") + + -- Test default adjust (downscale) + local initial_adjust = text:get_text_adjust() + assert(initial_adjust == "downscale") + + -- Test no_adjust + text:set_text_adjust("no_adjust") + assert(text:get_text_adjust() == "no_adjust") + + -- Test trim + text:set_text_adjust("trim") + assert(text:get_text_adjust() == "trim") + + -- Test with minimal scale + text:set_text_adjust("downscale_limited", 0.5) + assert(text:get_text_adjust() == "downscale_limited") + + druid:remove(text) + gui.delete_node(text_node) + end) + + it("Should get text size", function() + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Text Size Test") + gui.set_font(text_node, "druid_text_bold") + + local text = druid:new_text(text_node) + + local width, height = text:get_text_size() + + assert(width > 0) + assert(height > 0) + + druid:remove(text) + gui.delete_node(text_node) + end) + + it("Should check if text is multiline", function() + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Single line") + gui.set_font(text_node, "druid_text_bold") + gui.set_line_break(text_node, false) + + local text = druid:new_text(text_node) + + assert(text:is_multiline() == false) + + -- Change to multiline + gui.set_line_break(text_node, true) + + assert(text:is_multiline() == true) + + druid:remove(text) + gui.delete_node(text_node) + end) + + it("Should fire on_update_text_scale event", function() + local text_node = gui.new_text_node(vmath.vector3(50, 25, 0), "Scale Event Test") + gui.set_font(text_node, "druid_text_bold") + gui.set_size(text_node, vmath.vector3(100, 50, 0)) + + local text = druid:new_text(text_node) + + local on_update_text_scale_calls = 0 + local last_scale = nil + + text.on_update_text_scale:subscribe(function(_, scale) + on_update_text_scale_calls = on_update_text_scale_calls + 1 + last_scale = scale + end) + + -- Trigger scale update + text:set_text("This text is long enough to trigger scaling") + + assert(on_update_text_scale_calls >= 1) + assert(last_scale ~= nil) + + druid:remove(text) + gui.delete_node(text_node) + end) + end) +end diff --git a/wiki/changelog.md b/wiki/changelog.md index 2c53369..e47014f 100644 --- a/wiki/changelog.md +++ b/wiki/changelog.md @@ -436,7 +436,7 @@ And yeah, the new **Druid** logo is here! - Add two events: `on_element_add` and `on_element_remove` - Add `data_list:get_data()` to access all current data in DataList - Add `data_list:get_created_nodes()` to access currently visual nodes in DataList - - Add `data_list:get_created_components()` to access currenly visual component in DataList (if created) + - Add `data_list:get_created_components()` to access currently visual component in DataList (if created) - **#190** [Progress] Add `progress:set_max_size` function to change max size of progress bar - **#188** [Drag] Add two values passed to on_drag callback. Now it is `on_drag(self, dx, dy, total_x, total_y)` to check the overral drag distance - **#195** [Drag] Add `drag:is_enabled` and `drag:set_enabled` to enable/disable drag input component