diff --git a/documentation/docs/changelog.md b/documentation/docs/changelog.md index e9d119b1..4490f7a8 100644 --- a/documentation/docs/changelog.md +++ b/documentation/docs/changelog.md @@ -1,6 +1,12 @@ # Changelog -## 2024.mm.yy - version 0.0.7-alpha +## 2024.04.03 - version 0.0.8-alpha + +* Add a new support class to handle bool values +* Add new support methods to Str +* Reset the library instance for each test method for better mocking and less garbage between test cases + +## 2024.03.29 - version 0.0.7-alpha * Add the Events facade class to serve as a way to improve event handling * Listen to `PLAYER_LOGIN` diff --git a/documentation/docs/resources/support/bool.md b/documentation/docs/resources/support/bool.md new file mode 100644 index 00000000..e9d4e9aa --- /dev/null +++ b/documentation/docs/resources/support/bool.md @@ -0,0 +1,28 @@ +# Bool + +The **Bool** support methods are focused on working and validating bool +values. + +Each helper method is well documented in the support source code and won't +be duplicated here. Please, refer to the `./src/Support/Bool.lua` for better +reference. + +## Methods + +* `Bool:isTrue()` - Determines whether a value represents true or not. + +:::note Note about the absence of isFalse() + +Developers may notice this class has no `isFalse()` method. + +In terms of determining if a value is true, there's a limited +range of values that can be considered true. On the other hand, +anything else **shouldn't be considered false.** Consumers of this +class can use `isTrue()` to determine if a value represents a true +value, but using a `isFalse()` method would be a bit inconsistent. +That said, instead of having a `isFalse()` method, consumers can +use the `not` operator to determine if a value is false, which +makes the code more readable, like: if this value is not true, +then do something. + +::: \ No newline at end of file diff --git a/documentation/docs/resources/support/str.md b/documentation/docs/resources/support/str.md index 03f23344..3aad9337 100644 --- a/documentation/docs/resources/support/str.md +++ b/documentation/docs/resources/support/str.md @@ -4,4 +4,29 @@ The **Str** methods are focused on manipulating strings. Each helper method is well documented in the support source code and won't be duplicated here. Please, refer to the `./src/Support/Src.lua` for better -reference. \ No newline at end of file +reference. + +## Methods + +* `Str:isEmpty()` - Determines whether a string is empty or not. + * By empty, it means that the string is nil, has no characters, or has + only whitespace characters. This last case is important because a + string with only whitespace characters is not considered empty by Lua's + standards, but it is by this function's standards. +* `Str:isNotEmpty()` - Determines whether a string is not empty. + * Read the `Str:isEmpty()` documentation above for clarification about + what this library considers when checking if a string is empty. +* `Str:isQuoted()` - Determines whether a string is quoted by " or '. +* `Str:isWrappedBy()` - Determines whether a string is wrapped by a prefix +and a suffix. +* `Str:removeQuotes()` - Removes quotes from a string. + * Addons shouldn't call `removeWrappers` twice for `"` and `'`, because + the string could be wrapped by one type of quote and contain the other + type inside it, so `removeQuotes` method first checks which type of + quote is wrapping the string and then removes it. +* `Str:removeWrappers()` - Removes the wrapping strings from a string. +* `Str:replaceAll()` - Replaces all occurrences of a substring in a string +with another substring. +* `Str:split()` - Splits a string in a table by breaking it where the separator is found. +* `Str:trim()` - Removes all whitespace from the beginning and end of a +string. \ No newline at end of file diff --git a/documentation/docs/testing/test-classes.md b/documentation/docs/testing/test-classes.md new file mode 100644 index 00000000..74f31183 --- /dev/null +++ b/documentation/docs/testing/test-classes.md @@ -0,0 +1,69 @@ +# Test Classes + +Test classes in the Stormwind Library are organized in a way that makes it +easy to run all tests at once although split into multiple files for each +class in the src directory. + +The `./tests/unit.lua` file is the entry point for running all tests and it +also defines a base test class that sets up the library before each test. +Setting up the library before each test ensures that the library is in a +clean state before each test is run, so mocking the library on tests won't +affect the results of other tests. + +## Writing a test class + +In order to write a test class, it's highly recommended to follow a couple +of standards to keep the tests organized, easy to read, and easy to +maintain. + +As an example, consider the support classes in the library as they are good +representatives of how a test class should be written. + +1. Start by creating a directory following the same structure as the src + directory, but inside tests +1. Create a test file for each class in the src directory +1. Define the test class starting with the **Test** prefix followed by the + name of the class being tested; this is important for the test runner to + identify the test class, otherwise it will be skipped +1. Define a method for each test case starting with the **test** prefix +1. Update the `./tests/unit.lua` file to include the test file, preferably + in alphabetical order + +## Getting the library instance in a test case + +A library instance is available in each test case through the global +variable `__` and it's ready to be used without any further setup. However, +if a test case requires a different setup, it's possible to instantiate a +new library instance in the test case by doing this: + +```lua +local __ = StormwindLibrary.new({ + name = 'TestSuite' +}) +``` + +In the example above, `StormwindLibrary` is an alias for the library version +being tested, so when new versions are released, it's only necessary to +update the alias in the `./tests/unit.lua` file. + +## Mocking library properties and methods + +The library is set up before each test in the base test class, so mocking +properties and methods of the library can be done in each test case without +affecting other tests. + +To mock a property or method of the library, simply assign a new value to +the property or method in the test case. For example, to mock the `name` +property of the library, you can do the following: + +```lua +__.name = 'MockedName' +``` + +To mock a method of the library, you can assign a new function to the +method in the test case. + +There's no need to revert the mocked properties and methods back to their +original values after the test case is run, unless the test method is +expected to be called multiple times in the same test class and with +different mocks. \ No newline at end of file diff --git a/src/Support/Bool.lua b/src/Support/Bool.lua new file mode 100644 index 00000000..445412e2 --- /dev/null +++ b/src/Support/Bool.lua @@ -0,0 +1,39 @@ +--[[ +The Bool support class contains helper functions to manipulate boolean +values. +]] +local Bool = {} + Bool.__index = Bool + Bool.__ = self + + --[[ + Determines whether a value represents true or not. + + This method checks if a value is in a range of possible values that + represent a true value. + + @NOTE: Developers may notice this class has no isFalse() method. + In terms of determining if a value is true, there's a limited + range of values that can be considered true. On the other hand, + anything else shouldn't be considered false. Consumers of this + class can use isTrue() to determine if a value represents a true + value, but using a isFalse() method would be a bit inconsistent. + That said, instead of having a isFalse() method, consumers can + use the not operator to determine if a value is false, which + makes the code more readable, like: if this value is not true, + then do something. + + @tparam mixed value + + @treturn bool + ]] + function Bool:isTrue(value) + local inArray, index = self.__.arr:inArray({1, "1", "true", true, "yes"}, value) + + -- it returns just the first inArray result, as the second value is the index + -- which makes no sense in this context + return inArray + end +-- end of Bool + +self.bool = Bool \ No newline at end of file diff --git a/src/Support/Str.lua b/src/Support/Str.lua index e4e593b2..65f9a1e3 100644 --- a/src/Support/Str.lua +++ b/src/Support/Str.lua @@ -4,6 +4,120 @@ The Str support class contains helper functions to manipulate strings. local Str = {} Str.__index = Str + --[[ + Determines whether a string is empty or not. + + By empty, it means that the string is nil, has no characters, or has only + whitespace characters. This last case is important because a string with + only whitespace characters is not considered empty by Lua's standards, + but it is by this function's standards. + + If a method shouldn't consider a string with only whitespace characters + as empty, please do not use this function. + + @tparam string value + + @treturn bool + ]] + function Str:isEmpty(value) + return value == nil or (string.len(self:trim(value)) == 0) + end + + --[[ + Determines whether a string is not empty. + + This function is the opposite of Str:isEmpty. + + @tparam string value + + @treturn bool + ]] + function Str:isNotEmpty(value) + return not self:isEmpty(value) + end + + --[[ + Determines whether a string is quoted by " or '. + + @tparam string value + + @treturn bool + ]] + function Str:isQuoted(value) + return self:isWrappedBy(value, '"') or self:isWrappedBy(value, "'") + end + + --[[ + Determines whether a string is wrapped by a prefix and a suffix. + + This function is useful to determine if a string is wrapped by a pair of + strings, like quotes, parentheses, brackets, etc. + + The third parameter is optional. If it is not provided, the function will + assume that the prefix and suffix are the same. + + Finally, this function will return true if the string contains only the + prefix and suffix, like "", "()", "[]", etc. That would mean that an + empty string is considered wrapped by something. + + @tparam string value + @tparam string wrapper + @tparam string endWrapper, optional + + @treturn bool + ]] + function Str:isWrappedBy(value, wrapper, endWrapper) + endWrapper = endWrapper or wrapper + + return + (value ~= nil) and + (wrapper ~= nil) and + (value ~= wrapper) and + (value:sub(1, #wrapper) == wrapper and value:sub(-#endWrapper) == endWrapper) + end + + --[[ + Removes quotes from a string. + + This method can't simply call removeWrappers twice for " or ', because + the string could be wrapped by one type of quote and contain the other + type inside it, so it first checks which type of quote is wrapping the + string and then removes it. + + @tparam string value + + @treturn string + ]] + function Str:removeQuotes(value) + if self:isWrappedBy(value, '"') then + return self:removeWrappers(value, '"') + end + + return self:removeWrappers(value, "'") + end + + --[[ + Removes the wrapping strings from a string. + + This function is useful to remove quotes, parentheses, brackets, etc, + from a string. + + Similarly to Str:isWrappedBy, the third parameter is optional. If it is + not provided, the function will assume that the prefix and suffix are + the same. + + @tparam string value + @tparam string wrapper + @tparam string endWrapper, optional + + @treturn string + ]] + function Str:removeWrappers(value, wrapper, endWrapper) + return self:isWrappedBy(value, wrapper, endWrapper) + and value:sub(#wrapper + 1, -#(endWrapper or wrapper) - 1) + or value + end + --[[ Replaces all occurrences of a substring in a string with another substring. @@ -37,6 +151,17 @@ local Str = {} end return values end + + --[[ + Removes all whitespace from the beginning and end of a string. + + @tparam string value + + @treturn string + ]] + function Str:trim(value) + return value and value:gsub("^%s*(.-)%s*$", "%1") or value + end -- end of Str self.str = Str \ No newline at end of file diff --git a/src/stormwind-library.lua b/src/stormwind-library.lua index 5913f7ae..f6e41baf 100644 --- a/src/stormwind-library.lua +++ b/src/stormwind-library.lua @@ -1,6 +1,7 @@ --- Library version = '0.0.7' +-- Library version = '0.0.8' -- import src/Support/Arr.lua +-- import src/Support/Bool.lua -- import src/Support/Str.lua -- import src/Core/AddonProperties.lua diff --git a/tests/Commands/CommandsHandlerTest.lua b/tests/Commands/CommandsHandlerTest.lua index c69b176c..8bb08e57 100644 --- a/tests/Commands/CommandsHandlerTest.lua +++ b/tests/Commands/CommandsHandlerTest.lua @@ -1,4 +1,4 @@ -TestCommandsHandler = {} +TestCommandsHandler = BaseTestClass:new() -- @covers StormwindLibrary:add() function TestCommandsHandler:testAdd() local handler = __.commands @@ -141,15 +141,11 @@ TestCommandsHandler = {} local outputInvoked = false - local originalOut = __.output.out - function __.output:out() outputInvoked = true end handler:printHelp() lu.assertEquals(shouldOutput, outputInvoked) - - __.output.out = originalOut end execution(nil, false) @@ -160,8 +156,6 @@ TestCommandsHandler = {} -- @covers StormwindLibrary:register() function TestCommandsHandler:testRegister() local function execution(command, expectedGlobalSlashCommandIndex, expectedSlashCommand, expectedSlashCmdListIndex) - -- save the current data to restore them after the test - local currentCommand = __.addon.command local currentSlashCmdList = SlashCmdList -- mocks the properties for this test @@ -179,8 +173,6 @@ TestCommandsHandler = {} lu.assertIsFunction(SlashCmdList[expectedSlashCmdListIndex]) end - -- restore the command after the test - __.addon.command = currentCommand SlashCmdList = currentSlashCmdList end diff --git a/tests/Commands/CommandsTest.lua b/tests/Commands/CommandsTest.lua index 14a46931..78035713 100644 --- a/tests/Commands/CommandsTest.lua +++ b/tests/Commands/CommandsTest.lua @@ -1,4 +1,4 @@ -TestCommand = {} +TestCommand = BaseTestClass:new() -- @covers Command:setCallback() -- @covers Command:setDescription() -- @covers Command:setOperation() diff --git a/tests/Core/AddonPropertiesTest.lua b/tests/Core/AddonPropertiesTest.lua index dc8f5076..268cb7e4 100644 --- a/tests/Core/AddonPropertiesTest.lua +++ b/tests/Core/AddonPropertiesTest.lua @@ -1,4 +1,4 @@ -TestAddonProperties = {} +TestAddonProperties = BaseTestClass:new() -- @covers AddonProperties.lua function TestAddonProperties:testPropertiesAreSet() local library = StormwindLibrary.new({ diff --git a/tests/Core/FactoryTest.lua b/tests/Core/FactoryTest.lua index b8e5aa44..7dd92864 100644 --- a/tests/Core/FactoryTest.lua +++ b/tests/Core/FactoryTest.lua @@ -1,10 +1,10 @@ -TestFactory = {} +TestFactory = BaseTestClass:new() --[[ @covers Factory.classes @covers Factory:addClass() @covers Factory:new() ]] - function TestFactory:testCanInstantiateClasses() + function TestFactory:testClassInstantiation() local MockClass = {} MockClass.__index = MockClass @@ -14,7 +14,9 @@ TestFactory = {} return self end - local library = newLibrary() + local library = StormwindLibrary.new({ + name = 'test-library' + }) library:addClass('MockClass', MockClass) lu.assertNotIsNil(library.classes) diff --git a/tests/Core/OutputTest.lua b/tests/Core/OutputTest.lua index e32b7ea8..ddb2cfd6 100644 --- a/tests/Core/OutputTest.lua +++ b/tests/Core/OutputTest.lua @@ -1,14 +1,10 @@ -TestOutput = {} +TestOutput = BaseTestClass:new() -- @covers Output:color() function TestOutput:testColor() local function execution(value, color, primaryColor, expectedOutput) - local originalPrimaryColor = __.addon.colors.primary - __.addon.colors.primary = primaryColor lu.assertEquals(expectedOutput, __.output:color(value, color)) - - __.addon.colors.primary = originalPrimaryColor end execution('test', nil, nil, 'test') diff --git a/tests/Facades/EventHandlers/PlayerLoginEventHandlerTest.lua b/tests/Facades/EventHandlers/PlayerLoginEventHandlerTest.lua index ab83465a..93046c86 100644 --- a/tests/Facades/EventHandlers/PlayerLoginEventHandlerTest.lua +++ b/tests/Facades/EventHandlers/PlayerLoginEventHandlerTest.lua @@ -1,4 +1,4 @@ -TestPlayerLoginEventHandler = {} +TestPlayerLoginEventHandler = BaseTestClass:new() -- @covers PlayerLoginEventHandler.lua function TestPlayerLoginEventHandler:testEventNameIsSet() lu.assertEquals('PLAYER_LOGIN', __.events.EVENT_NAME_PLAYER_LOGIN) diff --git a/tests/Facades/EventHandlers/TargetEventHandlerTest.lua b/tests/Facades/EventHandlers/TargetEventHandlerTest.lua index 97595f1f..09670252 100644 --- a/tests/Facades/EventHandlers/TargetEventHandlerTest.lua +++ b/tests/Facades/EventHandlers/TargetEventHandlerTest.lua @@ -1,4 +1,4 @@ -TestTargetEventHandler = {} +TestTargetEventHandler = BaseTestClass:new() -- @covers TargetEventHandler.lua function TestTargetEventHandler:testEventNamesAreSet() lu.assertEquals('PLAYER_TARGET', __.events.EVENT_NAME_PLAYER_TARGET) @@ -19,8 +19,6 @@ TestTargetEventHandler = {} -- @covers Events:playerTargetChangedListener() function TestTargetEventHandler:testPlayerTargetChangedListener() local function execution(playerHadTarget, playerHasTarget, expectedEvent, expectedPlayerHadTargetState) - __ = newLibrary() - local targetMock = __:new('Target') targetMock.hasTarget = function() return playerHasTarget end __.target = targetMock diff --git a/tests/Facades/EventsTest.lua b/tests/Facades/EventsTest.lua index 5398634e..ea3d9019 100644 --- a/tests/Facades/EventsTest.lua +++ b/tests/Facades/EventsTest.lua @@ -1,4 +1,4 @@ -TestEvents = {} +TestEvents = BaseTestClass:new() -- @covers Events:createFrame() function TestEvents:testCreateFrame() local events = __:new('Events') diff --git a/tests/Facades/TargetTest.lua b/tests/Facades/TargetTest.lua index 765805e0..8b9ea518 100644 --- a/tests/Facades/TargetTest.lua +++ b/tests/Facades/TargetTest.lua @@ -1,4 +1,4 @@ -TestTarget = {} +TestTarget = BaseTestClass:new() -- @covers StormwindLibrary.target function TestTarget:testGetTargetFacade() local target = __.target diff --git a/tests/Models/MacroTest.lua b/tests/Models/MacroTest.lua index cb4ad66b..4517898f 100644 --- a/tests/Models/MacroTest.lua +++ b/tests/Models/MacroTest.lua @@ -1,4 +1,4 @@ -TestMacro = {} +TestMacro = BaseTestClass:new() -- @covers Macro:__construct() function TestMacro:testInstantiate() local macro = __:new('Macro', 'test-macro') diff --git a/tests/Models/RaidMarkerTest.lua b/tests/Models/RaidMarkerTest.lua index 13778572..f690c6b3 100644 --- a/tests/Models/RaidMarkerTest.lua +++ b/tests/Models/RaidMarkerTest.lua @@ -1,4 +1,4 @@ -TestRaidMarker = {} +TestRaidMarker = BaseTestClass:new() -- @covers RaidMarker::getPrintableString() function TestRaidMarker:testGetPrintableString() lu.assertEquals('\124TInterface\\TargetingFrame\\UI-RaidTargetingIcon_8:0\124t', __.raidMarkers['skull']:getPrintableString()) diff --git a/tests/Support/ArrTest.lua b/tests/Support/ArrTest.lua index 68b28d71..cf909101 100644 --- a/tests/Support/ArrTest.lua +++ b/tests/Support/ArrTest.lua @@ -1,6 +1,13 @@ -TestArr = {} +TestArr = BaseTestClass:new() + -- @covers Arr + function TestArr:testArrInstanceIsSet() + local arr = __.arr + + lu.assertNotIsNil(arr) + end + -- @covers Arr:get() - function TestArr:testCanGet() + function TestArr:testGet() local function execution(list, key, default, expectedOutput) lu.assertEquals(expectedOutput, __.arr:get(list, key, default)) end @@ -19,15 +26,8 @@ TestArr = {} execution(listWithNestedKeys, 'test-a.test-b.test-c', nil, 'test') end - -- @covers Arr - function TestArr:testCanGetInstance() - local arr = __.arr - - lu.assertNotIsNil(arr) - end - -- @covers Arr:implode() - function TestArr:testCanImplode() + function TestArr:testImplode() local arr = __.arr local delimiter = ',' @@ -39,7 +39,7 @@ TestArr = {} end -- @covers Arr:implode() - function TestArr:testCanImplodeWithNonList() + function TestArr:testImplodeWithNonList() local arr = __.arr local text = 'test' @@ -48,61 +48,6 @@ TestArr = {} lu.assertEquals(text, result) end - -- @covers Arr:insertNotInArray() - function TestArr:testCanInsertNotInArray() - local function execution(list, value, expectedBooleanResult, expectedListResult) - local booleanResult = __.arr:insertNotInArray(list, value) - - lu.assertEquals(expectedBooleanResult, booleanResult) - lu.assertEquals(expectedListResult, list) - end - - execution('a', 'a', false, 'a') - execution({}, 'a', true, {'a'}) - execution({'a'}, 'a', false, {'a'}) - execution({'a'}, 'b', true, {'a', 'b'}) - end - - -- @covers Arr:map() - function TestArr:testCanMap() - local function execution(list, expectedOutput) - local arr = __.arr - - local results = __.arr:map(list, function (val, i) - return val .. '-' .. i - end) - - lu.assertEquals(expectedOutput, results) - end - - execution({}, {}) - execution({'test', 'test', 'test'}, {'test-1', 'test-2', 'test-3'}) - execution({['a'] = 'a', ['b'] = 'b', ['c'] = 'c'}, {['a'] = 'a-a', ['b'] = 'b-b', ['c'] = 'c-c'}) - end - - -- @covers Arr:set() - function TestArr:testCanSet() - local arr = __.arr - - local list = {} - list['a'] = {} - list['a']['b'] = 'test-initial' - - -- sanity checks to make sure the list is consistent - lu.assertEquals('test-initial', arr:get(list, 'a.b')) - lu.assertIsNil(arr:get(list, 'a.c')) - lu.assertIsNil(arr:get(list, 'x.y.z')) - - -- sets a couple of properties - arr:set(list, 'a.c', 'test-with-set') - arr:set(list, 'x.y.z', 'test-with-three-levels') - - -- checks if the property - lu.assertEquals('test-with-set', arr:get(list, 'a.c')) - lu.assertEquals('test-with-three-levels', arr:get(list, 'x.y.z')) - lu.assertEquals('test-initial', arr:get(list, 'a.b')) - end - -- @covers Arr:inArray() function TestArr:testInArray() local function execution(list, value, expectedResult, expectedIndex) @@ -136,6 +81,21 @@ TestArr = {} execution({objectA, objectB}, objectB, true, 2) end + -- @covers Arr:insertNotInArray() + function TestArr:testInsertNotInArray() + local function execution(list, value, expectedBooleanResult, expectedListResult) + local booleanResult = __.arr:insertNotInArray(list, value) + + lu.assertEquals(expectedBooleanResult, booleanResult) + lu.assertEquals(expectedListResult, list) + end + + execution('a', 'a', false, 'a') + execution({}, 'a', true, {'a'}) + execution({'a'}, 'a', false, {'a'}) + execution({'a'}, 'b', true, {'a', 'b'}) + end + -- @covers Arr:isArray() function TestArr:testIsArray() local arr = __.arr @@ -151,6 +111,23 @@ TestArr = {} lu.assertIsFalse(arr:isArray(tableWithStringKeys)) end + -- @covers Arr:map() + function TestArr:testMap() + local function execution(list, expectedOutput) + local arr = __.arr + + local results = __.arr:map(list, function (val, i) + return val .. '-' .. i + end) + + lu.assertEquals(expectedOutput, results) + end + + execution({}, {}) + execution({'test', 'test', 'test'}, {'test-1', 'test-2', 'test-3'}) + execution({['a'] = 'a', ['b'] = 'b', ['c'] = 'c'}, {['a'] = 'a-a', ['b'] = 'b-b', ['c'] = 'c-c'}) + end + -- @covers Arr:maybeInitialize() function TestArr:testMaybeInitialize() local function execution(list, key, value, expectedValue) @@ -199,6 +176,29 @@ TestArr = {} execution({a = 'a', b = 'b', c = 'c'}, 'a', {a = 'a', b = 'b', c = 'c'}) end + -- @covers Arr:set() + function TestArr:testSet() + local arr = __.arr + + local list = {} + list['a'] = {} + list['a']['b'] = 'test-initial' + + -- sanity checks to make sure the list is consistent + lu.assertEquals('test-initial', arr:get(list, 'a.b')) + lu.assertIsNil(arr:get(list, 'a.c')) + lu.assertIsNil(arr:get(list, 'x.y.z')) + + -- sets a couple of properties + arr:set(list, 'a.c', 'test-with-set') + arr:set(list, 'x.y.z', 'test-with-three-levels') + + -- checks if the property + lu.assertEquals('test-with-set', arr:get(list, 'a.c')) + lu.assertEquals('test-with-three-levels', arr:get(list, 'x.y.z')) + lu.assertEquals('test-initial', arr:get(list, 'a.b')) + end + -- @covers Arr:wrap() function TestArr:testWrap() local function execution(value, expectedOutput) diff --git a/tests/Support/BoolTest.lua b/tests/Support/BoolTest.lua new file mode 100644 index 00000000..b2f6330d --- /dev/null +++ b/tests/Support/BoolTest.lua @@ -0,0 +1,27 @@ +TestBool = BaseTestClass:new() + -- @covers StormwindLibrary.bool + function TestBool:testBoolInstanceIsSet() + local bool = __.bool + + lu.assertNotIsNil(bool) + end + + -- @covers Bool:isTrue() + function TestBool:testIsTrue() + local bool = __.bool + + lu.assertTrue(bool:isTrue(1)) + lu.assertTrue(bool:isTrue("1")) + lu.assertTrue(bool:isTrue('1')) + lu.assertTrue(bool:isTrue("true")) + lu.assertTrue(bool:isTrue(true)) + lu.assertTrue(bool:isTrue("yes")) + + lu.assertFalse(bool:isTrue(0)) + lu.assertFalse(bool:isTrue("0")) + lu.assertFalse(bool:isTrue("false")) + lu.assertFalse(bool:isTrue(false)) + lu.assertFalse(bool:isTrue("no")) + lu.assertFalse(bool:isTrue(nil)) + end +-- end of TestBool \ No newline at end of file diff --git a/tests/Support/StrTest.lua b/tests/Support/StrTest.lua index 9fc47c6f..466a5329 100644 --- a/tests/Support/StrTest.lua +++ b/tests/Support/StrTest.lua @@ -1,4 +1,113 @@ -TestStr = {} +TestStr = BaseTestClass:new() + -- @covers Str:isEmpty() + -- @covers Str:isNotEmpty() + function TestStr:testIsEmpty() + local function execution(value, expectedOutput) + lu.assertEquals(expectedOutput, __.str:isEmpty(value)) + lu.assertEquals(not expectedOutput, __.str:isNotEmpty(value)) + end + + execution(nil, true) + execution('', true) + execution(' ', true) + execution('a', false) + execution(' a', false) + execution('a ', false) + end + + -- @covers Str:isQuoted() + function TestStr:testIsQuoted() + local function execution(value, expectedOutput) + lu.assertEquals(expectedOutput, __.str:isQuoted(value)) + end + + execution(nil, false) + execution('', false) + execution(' ', false) + execution('a', false) + + execution('"a"', true) + execution("'a'", true) + + execution('""', true) + execution("''", true) + + execution('"a', false) + execution('a"', false) + end + + -- @covers Str:isWrappedBy() + function TestStr:testIsWrappedBy() + local function execution(value, wrapper, endWrapper, expectedOutput) + lu.assertEquals(expectedOutput, __.str:isWrappedBy(value, wrapper, endWrapper)) + end + + execution('', '<', '>', true) + execution('(a>', '(', ')', false) + execution('""', '"', nil, true) + execution('"a"', '"', nil, true) + execution("'a'", "'", nil, true) + execution("''", "'", nil, true) + execution('"a', '"', nil, false) + execution('a', '', nil, false) + + -- edge cases + execution('a', 'a', nil, false) + execution('', '', '', false) + execution(nil, '', '', false) + execution('', nil, '', false) + execution(nil, nil, nil, false) + end + + -- @covers Str:removeQuotes() + function TestStr:testRemoveQuotes() + local function execution(value, expectedOutput) + lu.assertEquals(expectedOutput, __.str:removeQuotes(value)) + end + + execution(nil, nil) + execution('', '') + execution(' ', ' ') + execution('a', 'a') + + execution('"a"', 'a') + execution("'a'", 'a') + execution('""', '') + execution("''", '') + + -- quoted quotes + execution('"\'\'"', "''") + execution("'\"\"'", '""') + execution('"\'a\'"', "'a'") + execution("'\"a\"'", '"a"') + end + + -- @covers Str:removeWrappers() + function TestStr:testRemoveWrappers() + local function execution(value, wrapper, endWrapper, expectedOutput) + lu.assertEquals(expectedOutput, __.str:removeWrappers(value, wrapper, endWrapper)) + end + + execution('', '<', '>', 'a') + execution('_(a)_', '_(', ')_', 'a') + execution('""', '"', nil, '') + execution('"a"', '"', nil, 'a') + execution("'a'", "'", nil, 'a') + execution("''", "'", nil, '') + execution('"a', '"', nil, '"a') + execution('a', '', nil, 'a') + + -- won't remove internal wrappers + execution('"a"b"c"', '"', nil, 'a"b"c') + + -- edge cases + execution('a', 'a', nil, 'a') + execution('', '', '', '') + execution(nil, '', '', nil) + execution('', nil, '', '') + execution(nil, nil, nil, nil) + end + -- @covers Str:replaceAll() function TestStr:testReplaceAll() local function execution(value, find, replace, expectedOutput) @@ -32,4 +141,24 @@ TestStr = {} execution('test-a.test-b.test-c', '.', {'test-a', 'test-b', 'test-c'}) execution('test-a test-b test-c', ' ', {'test-a', 'test-b', 'test-c'}) end + + -- @covers Str:trim() + function TestStr:testTrim() + local function execution(value, expectedOutput) + lu.assertEquals(expectedOutput, __.str:trim(value)) + end + + execution(nil, nil) + execution('', '') + execution(' ', '') + execution(' ', '') + execution(' ', '') + execution('a', 'a') + execution(' a', 'a') + execution('a ', 'a') + execution(' a ', 'a') + execution(' a ', 'a') + execution(' a b ', 'a b') + execution(' a b ', 'a b') + end -- end of TestStr \ No newline at end of file diff --git a/tests/unit.lua b/tests/unit.lua index 341835ad..056635de 100644 --- a/tests/unit.lua +++ b/tests/unit.lua @@ -14,42 +14,57 @@ end -- End dofile('./dist/stormwind-library.lua') -StormwindLibrary = StormwindLibrary_v0_0_7 -function newLibrary() return StormwindLibrary.new({ - name = 'TestSuite' -}) end +StormwindLibrary = StormwindLibrary_v0_0_8 --[[ -This allows the library to be reloaded between tests, which is useful when -mocking it on tests could affect the results of other tests. +This is a base test class that sets up the library before each test. -@NOTE: LuaUnit provides a setUp() method that could be used to reset the - library before each test, but it wasn't working properly. +Every test class should inherit from this class to have the library set up +before each test. That way, mocking the library on tests won't affect the +results of other tests. -@TODO: Use LuaUnit's setUp() method to reset the library before each test <2024.03.27> +The setUp() method is expected to be called before each test. ]] -function runTests(file) - __ = newLibrary() - dofile(file) -end +BaseTestClass = { + new = function(self) + local instance = {} + setmetatable(instance, self) + self.__index = self + return instance + end, + + setUp = function() + __ = StormwindLibrary.new({ + name = 'TestSuite' + }) + end, + + -- guarantees that every test class inherits from this class by forcing + -- the global library usages to throw an error if it's not set, so + -- tests that miss inheriting from this class will fail + tearDown = function() + __ = nil + end, +} -runTests('./tests/Commands/CommandsTest.lua') -runTests('./tests/Commands/CommandsHandlerTest.lua') +dofile('./tests/Commands/CommandsTest.lua') +dofile('./tests/Commands/CommandsHandlerTest.lua') -runTests('./tests/Core/AddonPropertiesTest.lua') -runTests('./tests/Core/FactoryTest.lua') -runTests('./tests/Core/OutputTest.lua') +dofile('./tests/Core/AddonPropertiesTest.lua') +dofile('./tests/Core/FactoryTest.lua') +dofile('./tests/Core/OutputTest.lua') -runTests('./tests/Facades/EventsTest.lua') -runTests('./tests/Facades/EventHandlers/PlayerLoginEventHandlerTest.lua') -runTests('./tests/Facades/EventHandlers/TargetEventHandlerTest.lua') -runTests('./tests/Facades/TargetTest.lua') +dofile('./tests/Facades/EventsTest.lua') +dofile('./tests/Facades/EventHandlers/PlayerLoginEventHandlerTest.lua') +dofile('./tests/Facades/EventHandlers/TargetEventHandlerTest.lua') +dofile('./tests/Facades/TargetTest.lua') -runTests('./tests/Models/MacroTest.lua') -runTests('./tests/Models/RaidMarkerTest.lua') +dofile('./tests/Models/MacroTest.lua') +dofile('./tests/Models/RaidMarkerTest.lua') -runTests('./tests/Support/ArrTest.lua') -runTests('./tests/Support/StrTest.lua') +dofile('./tests/Support/ArrTest.lua') +dofile('./tests/Support/BoolTest.lua') +dofile('./tests/Support/StrTest.lua') lu.ORDER_ACTUAL_EXPECTED=false