diff --git a/test/library/class_extended.lua b/test/library/class_extended.lua new file mode 100644 index 0000000000..a0c6db5338 --- /dev/null +++ b/test/library/class_extended.lua @@ -0,0 +1,296 @@ +config.target = 'core' + +function test.defclass_basic() + -- Test basic class creation + local MyClass = defclass(nil) + expect.eq(MyClass.__index, MyClass) + expect.table_eq(MyClass.ATTRS, {}) + expect.eq(MyClass.super, nil) + expect.ne(getmetatable(MyClass), nil) +end + +function test.defclass_with_parent() + -- Test class creation with parent + local ParentClass = defclass(nil) + local ChildClass = defclass(nil, ParentClass) + + expect.eq(ChildClass.super, ParentClass) + expect.eq(getmetatable(ChildClass).__index, ParentClass) +end + +function test.defclass_attrs() + -- Test class with ATTRS + local MyClass = defclass(nil) + MyClass.ATTRS { + attr1 = 'default1', + attr2 = 'default2', + } + + expect.eq(MyClass.ATTRS.attr1, 'default1') + expect.eq(MyClass.ATTRS.attr2, 'default2') +end + +function test.mkinstance() + -- Test basic instance creation + local MyClass = defclass(nil) + local instance = mkinstance(MyClass, {value = 100}) + + expect.eq(instance.value, 100) + expect.eq(getmetatable(instance), MyClass) +end + +function test.instance_inheritance() + -- Test instance method inheritance + local ParentClass = defclass(nil) + function ParentClass:parent_method() + return 'parent' + end + + local ChildClass = defclass(nil, ParentClass) + function ChildClass:child_method() + return 'child' + end + + local instance = ChildClass({}) + expect.eq(instance:parent_method(), 'parent') + expect.eq(instance:child_method(), 'child') +end + +function test.init_method() + -- Test init method + local MyClass = defclass(nil) + MyClass.ATTRS { + value = 0, + } + + function MyClass:init(init_table) + self.value = (init_table.value or 0) * 2 + end + + local instance = MyClass({value = 5}) + expect.eq(instance.value, 10) +end + +function test.preinit_postinit() + -- Test preinit and postinit methods + local call_order = {} + + local MyClass = defclass(nil) + function MyClass:preinit(init_table) + table.insert(call_order, 'preinit') + end + + function MyClass:init(init_table) + table.insert(call_order, 'init') + end + + function MyClass:postinit(init_table) + table.insert(call_order, 'postinit') + end + + MyClass({}) + + expect.eq(#call_order, 3) + expect.eq(call_order[1], 'preinit') + expect.eq(call_order[2], 'init') + expect.eq(call_order[3], 'postinit') +end + +function test.inheritance_init_order() + -- Test init order with inheritance + local call_order = {} + + local ParentClass = defclass(nil) + function ParentClass:preinit(init_table) + table.insert(call_order, 'parent_preinit') + end + + function ParentClass:init(init_table) + table.insert(call_order, 'parent_init') + end + + function ParentClass:postinit(init_table) + table.insert(call_order, 'parent_postinit') + end + + local ChildClass = defclass(nil, ParentClass) + function ChildClass:preinit(init_table) + table.insert(call_order, 'child_preinit') + end + + function ChildClass:init(init_table) + table.insert(call_order, 'child_init') + end + + function ChildClass:postinit(init_table) + table.insert(call_order, 'child_postinit') + end + + ChildClass({}) + + expect.eq(#call_order, 6) + expect.eq(call_order[1], 'child_preinit') + expect.eq(call_order[2], 'parent_preinit') + expect.eq(call_order[3], 'parent_init') + expect.eq(call_order[4], 'child_init') + expect.eq(call_order[5], 'parent_postinit') + expect.eq(call_order[6], 'child_postinit') +end + +function test.callback_method() + -- Test callback method + local MyClass = defclass(nil) + function MyClass:test_method(arg) + return arg * 2 + end + + local instance = MyClass({}) + local cb = instance:callback('test_method') + + expect.eq(cb(5), 10) +end + +function test.cb_getfield() + -- Test cb_getfield method + local MyClass = defclass(nil) + local instance = MyClass() + instance.value = 42 + + local getter = instance:cb_getfield('value') + expect.eq(getter(), 42) +end + +function test.cb_setfield() + -- Test cb_setfield method + local MyClass = defclass(nil) + local instance = MyClass() + instance.value = 42 + + local setter = instance:cb_setfield('value') + setter(100) + expect.eq(instance.value, 100) +end + +function test.assign_method() + -- Test assign method + local MyClass = defclass(nil) + local instance = MyClass({value = 1}) + + instance:assign({value = 2, new_field = 3}) + expect.eq(instance.value, 2) + expect.eq(instance.new_field, 3) +end + +-- function test.invoke_before() +-- -- Test invoke_before method +-- local call_order = {} + +-- local MyClass = defclass(nil) +-- function MyClass:test_method() +-- table.insert(call_order, 'original') +-- end + +-- function MyClass:invoke_before_test_method() +-- table.insert(call_order, 'before') +-- end + +-- local instance = MyClass({}) +-- instance:invoke_before('test_method') + +-- expect.eq(#call_order, 2) +-- expect.eq(call_order[1], 'before') +-- expect.eq(call_order[2], 'original') +-- end + +-- function test.invoke_after() +-- -- Test invoke_after method +-- local call_order = {} + +-- local MyClass = defclass(nil) +-- function MyClass:test_method() +-- table.insert(call_order, 'original') +-- end + +-- function MyClass:invoke_after_test_method() +-- table.insert(call_order, 'after') +-- end + +-- local instance = MyClass() +-- instance:invoke_after('test_method') + +-- expect.eq(#call_order, 2) +-- expect.eq(call_order[1], 'original') +-- expect.eq(call_order[2], 'after') +-- end + +function test.attrs_meta() + -- Test ATTRS metatable behavior + local MyClass = defclass(nil) + MyClass.ATTRS { + attr1 = 'value1', + attr2 = 'value2', + } + + -- Test that ATTRS can be called to add attributes + MyClass.ATTRS { + attr3 = 'value3', + } + + expect.eq(MyClass.ATTRS.attr1, 'value1') + expect.eq(MyClass.ATTRS.attr2, 'value2') + expect.eq(MyClass.ATTRS.attr3, 'value3') +end + +function test.default_nil() + -- Test DEFAULT_NIL behavior + local MyClass = defclass(nil) + MyClass.ATTRS { + optional = DEFAULT_NIL, + required = 'default', + } + + local instance1 = MyClass({}) + expect.eq(instance1.optional, nil) + expect.eq(instance1.required, 'default') + + local instance2 = MyClass({optional = 'provided'}) + expect.eq(instance2.optional, 'provided') + expect.eq(instance2.required, 'default') +end + +function test.class_reload() + -- Test that classes can be reloaded + local MyClass = defclass(nil) + MyClass.ATTRS { + version = 1, + } + + -- Simulate reload by updating ATTRS + MyClass.ATTRS { + version = 2, + new_attr = 'new', + } + + expect.eq(MyClass.ATTRS.version, 2) + expect.eq(MyClass.ATTRS.new_attr, 'new') +end + +function test.instance_field_access() + -- Test instance field access patterns + local MyClass = defclass(nil) + MyClass.ATTRS { + public_field = 'public', + } + + function MyClass:init(init_table) + self.private_field = 'private' + end + + local instance = MyClass({}) + expect.eq(instance.public_field, 'public') + expect.eq(instance.private_field, 'private') + + -- Test setting new fields + instance.dynamic_field = 'dynamic' + expect.eq(instance.dynamic_field, 'dynamic') +end diff --git a/test/library/dfhack_core.lua b/test/library/dfhack_core.lua new file mode 100644 index 0000000000..cdcadc4dce --- /dev/null +++ b/test/library/dfhack_core.lua @@ -0,0 +1,217 @@ +config.target = 'core' + +function test.safe_index() + -- Test basic indexing + local t = {a = 1, b = {c = 2}} + expect.eq(safe_index(t, 'a'), 1) + expect.eq(safe_index(t, 'b', 'c'), 2) + + -- Test nil handling + expect.eq(safe_index(nil, 'a'), nil) + expect.eq(safe_index(t, 'nonexistent'), nil) + expect.eq(safe_index(t, 'b', 'nonexistent'), nil) + + -- Test nested nil handling + expect.eq(safe_index(t, 'b', 'c', 'd'), nil) +end + +local function test_ensure_key() + local t = {} + + -- Test creating new key + local result = ensure_key(t, 'new_key') + expect.table_eq(result, {}) + expect.table_eq(t.new_key, {}) + + -- Test existing key + t.existing = {value = 1} + result = ensure_key(t, 'existing') + expect.eq(result, t.existing) + expect.eq(t.existing.value, 1) + + -- Test with default value + result = ensure_key(t, 'with_default', {default = true}) + expect.table_eq(result, {default = true}) +end + +function test.ensure_key() + test_ensure_key() +end + +-- local function test_ensure_keys() +-- local t = {} + +-- -- Test creating nested keys +-- local result = ensure_keys(t, 'level1', 'level2', 'level3') +-- expect.eq(result, t.level1.level2.level3) +-- expect.table_eq(t.level1, {}) +-- expect.table_eq(t.level1.level2, {}) +-- expect.table_eq(t.level1.level2.level3, {}) + +-- -- Test partial existing path +-- t.level1.level2.level3 = {existing = true} +-- result = ensure_keys(t, 'level1', 'level2', 'level3') +-- expect.eq(result, t.level1.level2.level3) +-- expect.true_(result.existing) +-- end +-- +-- function test.ensure_keys() +-- test_ensure_keys() +-- end + +local function test_copyall() + local t = {a = 1, b = 2, c = {nested = true}} + local copy = copyall(t) + + expect.table_eq(copy, t) + expect.ne(copy, t) -- Different table reference + expect.eq(copy.c, t.c) -- Shallow copy +end + +function test.copyall() + test_copyall() +end + +local function test_pos_functions() + -- Test pos2xyz + local pos = {x = 10, y = 20, z = 30} + local x, y, z = pos2xyz(pos) + expect.eq(x, 10) + expect.eq(y, 20) + expect.eq(z, 30) + + -- Test pos2xyz with invalid pos + local invalid_pos = {x = -30000} + expect.eq(pos2xyz(invalid_pos), nil) + + -- Test xyz2pos + local new_pos = xyz2pos(5, 10, 15) + expect.eq(new_pos.x, 5) + expect.eq(new_pos.y, 10) + expect.eq(new_pos.z, 15) + + -- Test xyz2pos with nil + local nil_pos = xyz2pos(nil, nil, nil) + expect.eq(nil_pos.x, -30000) + expect.eq(nil_pos.y, -30000) + expect.eq(nil_pos.z, -30000) + + -- Test same_xyz + expect.true_(same_xyz(pos, pos)) + expect.false_(same_xyz(pos, {x = 10, y = 20, z = 25})) + + -- Test pos2xy + local x2, y2 = pos2xy(pos) + expect.eq(x2, 10) + expect.eq(y2, 20) + + -- Test xy2pos + local pos2d = xy2pos(3, 7) + expect.eq(pos2d.x, 3) + expect.eq(pos2d.y, 7) + + -- Test same_xy + expect.true_(same_xy(pos, pos)) + expect.true_(same_xy(pos, {x = 10, y = 20, z = 99})) + expect.false_(same_xy(pos, {x = 10, y = 25, z = 30})) +end + +function test.pos_functions() + test_pos_functions() +end + +local function test_string_extensions() + -- Test startswith + expect.true_(("hello world"):startswith("hello")) + expect.false_(("hello world"):startswith("world")) + expect.true_(("test"):startswith("test")) + + -- Test endswith + expect.true_(("hello world"):endswith("world")) + expect.false_(("hello world"):endswith("hello")) + expect.true_(("test"):endswith("test")) + + -- Test split + local parts = ("a,b,c"):split(",") + expect.eq(parts[1], "a") + expect.eq(parts[2], "b") + expect.eq(parts[3], "c") + + -- Test split with default delimiter + local words = ("hello world test"):split() + expect.eq(words[1], "hello") + expect.eq(words[2], "world") + expect.eq(words[3], "test") + + -- Test trim + expect.eq((" hello "):trim(), "hello") + expect.eq(("\t\nworld\n\t"):trim(), "world") + + -- Test wrap + local wrapped = ("This is a long string that needs to be wrapped"):wrap(20) + expect.true_(#wrapped > 20) -- Should be multiple lines + + -- Test escape_pattern + local escaped = ("a+b*c"):escape_pattern() + expect.true_(escaped:find("%+")) + expect.true_(escaped:find("%*")) +end + +function test.string_extensions() + test_string_extensions() +end + +local function test_with_finalize() + local cleanup_called = false + local result + + dfhack.with_finalize( + function() cleanup_called = true end, + function() result = "success" end + ) + + expect.true_(cleanup_called) + expect.eq(result, "success") +end + +function test.with_finalize() + test_with_finalize() +end + +local function test_with_onerror() + local cleanup_called = false + local ok, err = pcall(function() + dfhack.with_onerror( + function() cleanup_called = true end, + function() error("test error") end + ) + end) + + expect.false_(ok) + expect.true_(cleanup_called) +end + +function test.with_onerror() + test_with_onerror() +end + +-- don't know how to test this function +-- local function test_mkmodule() +-- -- Test that mkmodule creates proper module structure +-- local test_module = mkmodule('test.temp_module') +-- expect.table_eq(test_module, {}) +-- end + +-- function test.mkmodule() +-- test_mkmodule() +-- end + +local function test_mkinstance() + local MyClass = defclass(nil) + local instance = mkinstance(MyClass, {value = 100}) + expect.eq(instance.value, 100) +end + +function test.mkinstance() + test_mkinstance() +end diff --git a/test/library/gui_basics.lua b/test/library/gui_basics.lua new file mode 100644 index 0000000000..451199edc8 --- /dev/null +++ b/test/library/gui_basics.lua @@ -0,0 +1,289 @@ +config.target = 'core' + +local gui = require 'gui' + +function test.mkdims_xy() + local dims = gui.mkdims_xy(10, 20, 30, 40) + expect.eq(dims.x1, 10) + expect.eq(dims.y1, 20) + expect.eq(dims.x2, 30) + expect.eq(dims.y2, 40) + expect.eq(dims.width, 21) -- 30-10+1 + expect.eq(dims.height, 21) -- 40-20+1 +end + +function test.mkdims_wh() + local dims = gui.mkdims_wh(10, 20, 15, 25) + expect.eq(dims.x1, 10) + expect.eq(dims.y1, 20) + expect.eq(dims.x2, 24) -- 10+15-1 + expect.eq(dims.y2, 44) -- 20+25-1 + expect.eq(dims.width, 15) + expect.eq(dims.height, 25) +end + +function test.parse_inset() + -- Test with table + local l,t,r,b = gui.parse_inset({l = 5, r = 10, t = 3, b = 7}) + expect.eq(l, 5); + expect.eq(t, 3) + expect.eq(r, 10) + expect.eq(b, 7) + + -- Test with single value + local l,t,r,b = gui.parse_inset(8) + expect.eq(l, 8) + expect.eq(t, 8) + expect.eq(r, 8) + expect.eq(b, 8) + + -- Test with x/y shorthand + local l,t,r,b = gui.parse_inset({x = 4, y = 6}) + expect.eq(l, 4) + expect.eq(t, 6) + expect.eq(r, 4) + expect.eq(b, 6) +end + +function test.inset_frame() + local rect = gui.mkdims_wh(0, 0, 100, 50) + local inset = gui.inset_frame(rect, {l = 10, r = 10, t = 5, b = 5}) + + expect.eq(inset.x1, 10) + expect.eq(inset.y1, 5) + expect.eq(inset.x2, 89) -- 100-10-1 + expect.eq(inset.y2, 44) -- 50-5-1 + expect.eq(inset.width, 80) + expect.eq(inset.height, 40) +end + +function test.is_in_rect() + local rect = gui.mkdims_wh(10, 10, 20, 20) + + expect.true_(gui.is_in_rect(rect, 15, 15)) + expect.true_(gui.is_in_rect(rect, 10, 10)) + expect.true_(gui.is_in_rect(rect, 29, 29)) + expect.false_(gui.is_in_rect(rect, 9, 15)) + expect.false_(gui.is_in_rect(rect, 15, 9)) + expect.false_(gui.is_in_rect(rect, 30, 15)) + expect.false_(gui.is_in_rect(rect, 15, 30)) +end + +function test.ViewRect_basic() + local view_rect = gui.ViewRect{rect = gui.mkdims_wh(0, 0, 50, 30)} + + expect.eq(view_rect.x1, 0) + expect.eq(view_rect.y1, 0) + expect.eq(view_rect.x2, 49) + expect.eq(view_rect.y2, 29) + expect.eq(view_rect.width, 50) + expect.eq(view_rect.height, 30) +end + +function test.ViewRect_isDefunct() + local valid_rect = gui.ViewRect{rect = gui.mkdims_wh(0, 0, 50, 30)} + expect.false_(valid_rect:isDefunct()) + + local defunct_rect = gui.ViewRect{rect = gui.mkdims_wh(0, 0, 0, 0)} + expect.true_(defunct_rect:isDefunct()) +end + +function test.ViewRect_inClipGlobalXY() + local view_rect = gui.ViewRect{rect = gui.mkdims_wh(10, 10, 20, 20)} + + expect.true_(view_rect:inClipGlobalXY(15, 15)) + expect.true_(view_rect:inClipGlobalXY(10, 10)) + expect.true_(view_rect:inClipGlobalXY(29, 29)) + expect.false_(view_rect:inClipGlobalXY(9, 15)) + expect.false_(view_rect:inClipGlobalXY(15, 30)) +end + +function test.ViewRect_inClipLocalXY() + local view_rect = gui.ViewRect{rect = gui.mkdims_wh(10, 10, 20, 20)} + + expect.true_(view_rect:inClipLocalXY(5, 5)) -- Global 15,15 + expect.true_(view_rect:inClipLocalXY(0, 0)) -- Global 10,10 + expect.false_(view_rect:inClipLocalXY(-1, 5)) -- Global 9,15 + expect.false_(view_rect:inClipLocalXY(5, 20)) -- Global 15,30 +end + +function test.ViewRect_localXY() + local view_rect = gui.ViewRect{rect = gui.mkdims_wh(10, 10, 20, 20)} + + local lx, ly = view_rect:localXY(15, 25) + expect.eq(lx, 5) + expect.eq(ly, 15) +end + +function test.ViewRect_globalXY() + local view_rect = gui.ViewRect{rect = gui.mkdims_wh(10, 10, 20, 20)} + + local gx, gy = view_rect:globalXY(5, 15) + expect.eq(gx, 15) + expect.eq(gy, 25) +end + +function test.ViewRect_viewport() + local view_rect = gui.ViewRect{rect = gui.mkdims_wh(0, 0, 100, 100)} + local viewport = view_rect:viewport(10, 10, 20, 20) + + expect.eq(viewport.x1, 10) + expect.eq(viewport.y1, 10) + expect.eq(viewport.x2, 29) + expect.eq(viewport.y2, 29) + expect.eq(viewport.width, 20) + expect.eq(viewport.height, 20) +end + +function test.Painter_basic() + local painter = gui.Painter{rect = gui.mkdims_wh(0, 0, 50, 30)} + + expect.eq(painter.x, 0) + expect.eq(painter.y, 0) + expect.true_(painter:isValidPos()) +end + +function test.Painter_cursor() + local painter = gui.Painter{rect = gui.mkdims_wh(10, 10, 50, 30)} + + local cx, cy = painter:cursor() + expect.eq(cx, 0) + expect.eq(cy, 0) + + painter:seek(5, 10) + cx, cy = painter:cursor() + expect.eq(cx, 5) + expect.eq(cy, 10) +end + +function test.Painter_seek() + local painter = gui.Painter{rect = gui.mkdims_wh(0, 0, 50, 30)} + + painter:seek(10, 20) + expect.eq(painter.x, 10) + expect.eq(painter.y, 20) + + painter:seek(5) -- Only x + expect.eq(painter.x, 5) + expect.eq(painter.y, 20) -- y unchanged +end + +function test.Painter_advance() + local painter = gui.Painter{rect = gui.mkdims_wh(0, 0, 50, 30)} + + painter:advance(10, 5) + expect.eq(painter.x, 10) + expect.eq(painter.y, 5) + + painter:advance(5) -- Only x + expect.eq(painter.x, 15) + expect.eq(painter.y, 5) -- y unchanged +end + +function test.Painter_newline() + local painter = gui.Painter{rect = gui.mkdims_wh(0, 0, 50, 30)} + + painter:seek(10, 5) + painter:newline(3) + + expect.eq(painter.x, 3) + expect.eq(painter.y, 6) +end + +function test.Painter_viewport() + local painter = gui.Painter{rect = gui.mkdims_wh(0, 0, 100, 100)} + local viewport_painter = painter:viewport(10, 10, 20, 20) + + expect.eq(viewport_painter.x, 10) + expect.eq(viewport_painter.y, 10) + expect.eq(viewport_painter.width, 20) + expect.eq(viewport_painter.height, 20) +end + +function test.View_basic() + local view = gui.View{} + + expect.table_eq(view.subviews, {}) + expect.eq(view.focus, false) + expect.eq(#view.focus_group, 1) +end + +function test.View_addviews() + local parent = gui.View{} + local child1 = gui.View{view_id = 'child1'} + local child2 = gui.View{view_id = 'child2'} + + parent:addviews({child1, child2}) + + expect.eq(#parent.subviews, 2) + expect.eq(parent.subviews.child1, child1) + expect.eq(parent.subviews.child2, child2) + expect.eq(child1.parent_view, parent) + expect.eq(child2.parent_view, parent) +end + +function test.View_getPreferredFocusState() + local view = gui.View{} + expect.false_(view:getPreferredFocusState()) +end + +function test.View_setFocus() + local view = gui.View{} + + expect.false_(view.focus) + view:setFocus(true) + expect.true_(view.focus) + view:setFocus(false) + expect.false_(view.focus) +end + +function test.View_assign() + local view = gui.View{} + view:assign({custom_field = 'value', another_field = 42}) + + expect.eq(view.custom_field, 'value') + expect.eq(view.another_field, 42) +end + +function test.View_callback() + local view = gui.View{} + function view:test_method(arg) + return arg * 2 + end + + local cb = view:callback('test_method') + expect.eq(cb(5), 10) +end + +function test.View_cb_getfield() + local view = gui.View{} + view.test_field = 'test_value' + + local getter = view:cb_getfield('test_field') + expect.eq(getter(), 'test_value') +end + +function test.View_cb_setfield() + local view = gui.View{} + view.test_field = 'initial' + + local setter = view:cb_setfield('test_field') + setter('updated') + expect.eq(view.test_field, 'updated') +end + +-- do not know how to test this function - it has no specified behavior and is not used +-- function test.compute_frame_rect() +-- local rect = gui.compute_frame_rect(100, 50, {w = 80, h = 40}) + +-- expect.eq(rect.width, 80) +-- expect.eq(rect.height, 40) +-- expect.ge(rect.x1, 0) +-- expect.ge(rect.y1, 0) +-- end + +function test.blink_visible() + -- Test that blink_visible returns boolean + local result = gui.blink_visible(100) + expect.eq(type(result), 'boolean') +end diff --git a/test/library/utils_extended.lua b/test/library/utils_extended.lua new file mode 100644 index 0000000000..1d6988c1e9 --- /dev/null +++ b/test/library/utils_extended.lua @@ -0,0 +1,289 @@ +config.target = 'core' + +local utils = require 'utils' + +function test.getval() + -- Test with static value + expect.eq(utils.getval(42), 42) + expect.eq(utils.getval("hello"), "hello") + + -- Test with function + expect.eq(utils.getval(function() return 100 end), 100) + expect.eq(utils.getval(function(x) return x * 2 end, 5), 10) +end + +function test.compare() + expect.eq(utils.compare(1, 2), -1) + expect.eq(utils.compare(2, 1), 1) + expect.eq(utils.compare(1, 1), 0) + + expect.eq(utils.compare('a', 'b'), -1) + expect.eq(utils.compare('b', 'a'), 1) + expect.eq(utils.compare('a', 'a'), 0) +end + +function test.compare_name() + expect.eq(utils.compare_name('', ''), 0) + expect.eq(utils.compare_name('', 'a'), 1) + expect.eq(utils.compare_name('a', ''), -1) + expect.eq(utils.compare_name('a', 'b'), -1) + expect.eq(utils.compare_name('b', 'a'), 1) +end + +function test.compare_field() + local cmp = utils.compare_field('value') + expect.lt(cmp({value = 1}, {value = 2}), 0) + expect.gt(cmp({value = 2}, {value = 1}), 0) + expect.eq(cmp({value = 1}, {value = 1}), 0) +end + +function test.make_index_sequence() + local seq = utils.make_index_sequence(1, 5) + expect.eq(#seq, 5) + expect.eq(seq[1], 1) + expect.eq(seq[5], 5) + + local seq2 = utils.make_index_sequence(10, 15) + expect.eq(#seq2, 6) + expect.eq(seq2[1], 10) + expect.eq(seq2[6], 15) +end + +function test.make_sort_order() + local data = {{value = 3}, {value = 1}, {value = 2}} + local ordering = {{key = function(item) return item.value end}} + + local order = utils.make_sort_order(data, ordering) + expect.eq(order[1], 2) -- Index of value 1 + expect.eq(order[2], 3) -- Index of value 2 + expect.eq(order[3], 1) -- Index of value 3 +end + +function test.clone() + -- Test shallow clone + local t = {a = 1, b = 2} + local cloned = utils.clone(t, false) + expect.table_eq(cloned, t) + expect.ne(cloned, t) + + -- Test deep clone + local nested = {a = {b = {c = 1}}} + local deep_cloned = utils.clone(nested, true) + expect.eq(deep_cloned.a.b.c, 1) + expect.ne(deep_cloned.a, nested.a) +end + +-- need to come up with a way to test this +-- function test.clone_with_default() +-- local obj = {a = 1, b = 2, c = 3} +-- local default = {a = 1, b = 2, c = 3, d = 4} + +-- local result = utils.clone_with_default(obj, default) +-- expect.eq(result.a, nil) -- Same as default +-- expect.eq(result.b, nil) -- Same as default +-- expect.eq(result.c, nil) -- Same as default +-- expect.eq(result.d, nil) -- Not in obj + +-- -- Test with different values +-- local obj2 = {a = 5, b = 2, c = 3} +-- local result2 = utils.clone_with_default(obj2, default) +-- expect.eq(result2.a, 5) -- Different from default +-- expect.eq(result2.b, nil) -- Same as default +-- end + +-- need to come up with a way to test this + +--function test.parse_bitfield_int() +-- local type_ref = {'flag1', 'flag2', 'flag3'} +-- +-- local result = utils.parse_bitfield_int(5, type_ref) -- Binary 101 +-- expect.true_(result.flag1) +-- expect.false_(result.flag2) +-- expect.true_(result.flag3) +-- +-- -- Test with zero +-- local zero_result = utils.parse_bitfield_int(0, type_ref) +-- expect.eq(zero_result, nil) +--end + +function test.list_bitfield_flags() + local bitfield = {flag1 = true, flag2 = false, flag3 = true} + local list = utils.list_bitfield_flags(bitfield) + + expect.eq(#list, 2) + expect.true_(list[1] == 'flag1' or list[1] == 'flag3') + expect.true_(list[2] == 'flag1' or list[2] == 'flag3') +end + +function test.linear_index() + local data = {{id = 1}, {id = 2}, {id = 3}} + local data2 = { 5, 4, 3, 2, 1 } + + local idx, obj = utils.linear_index(data, 2, 'id') + expect.eq(idx, 2) + expect.eq(obj.id, 2) + + -- Test without field + local idx2, obj2 = utils.linear_index(data2, 2) + expect.eq(idx2, 4) + expect.eq(obj2, 2) + + -- Test not found + local idx3, obj3 = utils.linear_index(data, 99, 'id') + expect.eq(idx3, nil) + expect.eq(obj3, nil) +end + +function test.binsearch() + local data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + + local item, found, pos = utils.binsearch(data, 5) + expect.eq(item, 5) + expect.true_(found) + expect.eq(pos, 5) + + -- Test not found + local item2, found2, pos2 = utils.binsearch(data, 11) + expect.eq(item2, nil) + expect.false_(found2) +end + +function test.insert_sorted() + local data = {1, 3, 5, 7, 9} + + local added, item, pos = utils.insert_sorted(data, 4) + expect.true_(added) + expect.eq(item, 4) + expect.eq(pos, 3) + expect.eq(#data, 6) + + -- Test duplicate + local added2, item2, pos2 = utils.insert_sorted(data, 5) + expect.false_(added2) + expect.eq(item2, 5) +end + +function test.insert_or_update() + local data = {1, 3, 5, 7, 9} + + local added, item, pos = utils.insert_or_update(data, 4) + expect.true_(added) + expect.eq(item, 4) + + -- Test update existing + local added2, item2, pos2 = utils.insert_or_update(data, 5) + expect.false_(added2) + expect.eq(item2, 5) +end + +function test.erase_sorted_key() + local data = {1, 3, 5, 7, 9} + + local found, item, pos = utils.erase_sorted_key(data, 5) + expect.true_(found) + expect.eq(item, 5) + expect.eq(#data, 4) + + -- Test not found + local found2, item2, pos2 = utils.erase_sorted_key(data, 10) + expect.false_(found2) +end + +function test.search_text() + local text = "Hello World Test" + + -- Test basic search + expect.true_(utils.search_text(text, "Hello")) + expect.true_(utils.search_text(text, "World")) + expect.false_(utils.search_text(text, "NotFound")) + + -- Test multiple tokens + expect.true_(utils.search_text(text, {"Hello", "World"})) + expect.false_(utils.search_text(text, {"Hello", "NotFound"})) +end + +function test.split_string() + local text = "a,b,c" + local parts = utils.split_string(text, ",") + + expect.eq(#parts, 3) + expect.eq(parts[1], "a") + expect.eq(parts[2], "b") + expect.eq(parts[3], "c") +end + +function test.normalizePath() +-- normalizePath doesn't currently switch slashes; add these if we ever add that functionality +-- expect.eq(utils.normalizePath("path\\to\\file"), "path/to/file") +-- expect.eq(utils.normalizePath("path//to//file"), "path/to/file") + expect.eq(utils.normalizePath("path/to/file"), "path/to/file") +end + +function test.invert() + local t = {a = 1, b = 2, c = 3} + local inverted = utils.invert(t) + + expect.eq(inverted[1], 'a') + expect.eq(inverted[2], 'b') + expect.eq(inverted[3], 'c') +end + +function test.tabulate() + local result = utils.tabulate(function(i) return i * 2 end, 1, 5) + + expect.eq(#result, 5) + expect.eq(result[1], 2) + expect.eq(result[5], 10) +end + +function test.fillTable() + local t1 = {a = 1} + local t2 = {b = 2, c = 3} + + utils.fillTable(t1, t2) + expect.eq(t1.a, 1) + expect.eq(t1.b, 2) + expect.eq(t1.c, 3) +end + +function test.unfillTable() + local t1 = {a = 1, b = 2, c = 3} + local t2 = {b = 2, c = 3} + + utils.unfillTable(t1, t2) + expect.eq(t1.a, 1) + expect.eq(t1.b, nil) + expect.eq(t1.c, nil) +end + +function test.df_shortcut_var() + -- Test global shortcut + local result = utils.df_shortcut_var('world') + expect.eq(result, df.global.world) + + -- Test non-existent + local result2 = utils.df_shortcut_var('nonexistent_var') + expect.eq(result2, nil) +end + +local function test_OrderedTable() + local t = utils.OrderedTable() + local keys = {'first', 'second', 'third', 'fourth'} + + for i, key in ipairs(keys) do + t[key] = i + end + + local collected_keys = {} + for k, v in pairs(t) do + table.insert(collected_keys, k) + end + + expect.eq(#collected_keys, 4) + expect.eq(collected_keys[1], 'first') + expect.eq(collected_keys[4], 'fourth') +end + +function test.OrderedTable() + test_OrderedTable() +end