486 lines
16 KiB
Lua
486 lines
16 KiB
Lua
require("tests")
|
|
local str = require("string")
|
|
|
|
-- Test data
|
|
local test_string = "Hello, World!"
|
|
local multi_line = "Line 1\nLine 2\nLine 3"
|
|
local padded_string = " Hello World "
|
|
|
|
-- ======================================================================
|
|
-- BASIC STRING OPERATIONS
|
|
-- ======================================================================
|
|
|
|
test("String Split and Join", function()
|
|
local parts = str.split("a,b,c,d", ",")
|
|
assert_equal("table", type(parts))
|
|
assert_equal(4, #parts)
|
|
assert_equal("a", parts[1])
|
|
assert_equal("d", parts[4])
|
|
|
|
local joined = str.join(parts, "-")
|
|
assert_equal("a-b-c-d", joined)
|
|
|
|
-- Test empty split
|
|
local empty_parts = str.split("", ",")
|
|
assert_equal(1, #empty_parts)
|
|
assert_equal("", empty_parts[1])
|
|
end)
|
|
|
|
test("String Trim Operations", function()
|
|
assert_equal("Hello World", str.trim(padded_string))
|
|
assert_equal("Hello World ", str.trim_left(padded_string))
|
|
assert_equal(" Hello World", str.trim_right(padded_string))
|
|
|
|
-- Custom cutset
|
|
assert_equal("Helloxxx", str.trim_left("xxxHelloxxx", "x"))
|
|
assert_equal("xxxHello", str.trim_right("xxxHelloxxx", "x"))
|
|
end)
|
|
|
|
test("Case Operations", function()
|
|
assert_equal("HELLO", str.upper("hello"))
|
|
assert_equal("hello", str.lower("HELLO"))
|
|
assert_equal("Hello World", str.title("hello world"))
|
|
|
|
-- Test with mixed content
|
|
assert_equal("HELLO123!", str.upper("Hello123!"))
|
|
assert_equal("hello123!", str.lower("HELLO123!"))
|
|
end)
|
|
|
|
test("String Contains and Position", function()
|
|
assert_equal(true, str.contains(test_string, "World"))
|
|
assert_equal(false, str.contains(test_string, "world"))
|
|
assert_equal(true, str.starts_with(test_string, "Hello"))
|
|
assert_equal(false, str.starts_with(test_string, "hello"))
|
|
assert_equal(true, str.ends_with(test_string, "!"))
|
|
assert_equal(false, str.ends_with(test_string, "?"))
|
|
end)
|
|
|
|
test("String Replace", function()
|
|
assert_equal("hi world hi", str.replace("hello world hello", "hello", "hi"))
|
|
assert_equal("hi world hello", str.replace_n("hello world hello", "hello", "hi", 1))
|
|
|
|
-- Test with no matches
|
|
assert_equal("hello", str.replace("hello", "xyz", "abc"))
|
|
end)
|
|
|
|
test("String Index Operations", function()
|
|
assert_equal(7, str.index("hello world", "world"))
|
|
assert_equal(nil, str.index("hello world", "xyz"))
|
|
assert_equal(7, str.last_index("hello hello", "hello"))
|
|
assert_equal(3, str.count("hello hello hello", "hello"))
|
|
end)
|
|
|
|
test("String Repeat and Reverse", function()
|
|
assert_equal("abcabcabc", str.repeat_("abc", 3))
|
|
assert_equal("", str.repeat_("x", 0))
|
|
assert_equal("olleh", str.reverse("hello"))
|
|
assert_equal("", str.reverse(""))
|
|
end)
|
|
|
|
test("String Length Operations", function()
|
|
assert_equal(5, str.length("hello"))
|
|
assert_equal(5, str.byte_length("hello"))
|
|
assert_equal(0, str.length(""))
|
|
|
|
-- Test Unicode
|
|
local unicode_str = "héllo"
|
|
assert_equal(5, str.length(unicode_str))
|
|
assert_equal(6, str.byte_length(unicode_str)) -- é takes 2 bytes in UTF-8
|
|
end)
|
|
|
|
test("String Lines and Words", function()
|
|
local lines = str.lines(multi_line)
|
|
assert_equal(3, #lines)
|
|
assert_equal("Line 1", lines[1])
|
|
assert_equal("Line 3", lines[3])
|
|
|
|
local words = str.words("Hello world test")
|
|
assert_equal(3, #words)
|
|
assert_equal("Hello", words[1])
|
|
assert_equal("test", words[3])
|
|
|
|
-- Test with extra whitespace
|
|
local words2 = str.words(" Hello world ")
|
|
assert_equal(2, #words2)
|
|
end)
|
|
|
|
test("String Padding", function()
|
|
assert_equal(" hi", str.pad_left("hi", 5))
|
|
assert_equal("hi ", str.pad_right("hi", 5))
|
|
assert_equal("000hi", str.pad_left("hi", 5, "0"))
|
|
assert_equal("hi***", str.pad_right("hi", 5, "*"))
|
|
|
|
-- Test when string is already long enough
|
|
assert_equal("hello", str.pad_left("hello", 3))
|
|
end)
|
|
|
|
test("String Slice", function()
|
|
assert_equal("ell", str.slice("hello", 2, 4))
|
|
assert_equal("ello", str.slice("hello", 2))
|
|
assert_equal("", str.slice("hello", 10))
|
|
assert_equal("h", str.slice("hello", 1, 1))
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- REGULAR EXPRESSIONS
|
|
-- ======================================================================
|
|
|
|
test("Regex Match", function()
|
|
assert_equal(true, str.match("\\d+", "hello123"))
|
|
assert_equal(false, str.match("\\d+", "hello"))
|
|
assert_equal(true, str.match("^[a-z]+$", "hello"))
|
|
assert_equal(false, str.match("^[a-z]+$", "Hello"))
|
|
end)
|
|
|
|
test("Regex Find", function()
|
|
assert_equal("123", str.find("\\d+", "hello123world"))
|
|
assert_equal(nil, str.find("\\d+", "hello"))
|
|
|
|
local matches = str.find_all("\\d+", "123 and 456 and 789")
|
|
assert_equal(3, #matches)
|
|
assert_equal("123", matches[1])
|
|
assert_equal("789", matches[3])
|
|
end)
|
|
|
|
test("Regex Replace", function()
|
|
assert_equal("helloXXXworldXXX", str.gsub("\\d+", "hello123world456", "XXX"))
|
|
assert_equal("hello world", str.gsub("\\s+", "hello world", " "))
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- TYPE CONVERSION & VALIDATION
|
|
-- ======================================================================
|
|
|
|
test("String to Number", function()
|
|
assert_equal(123, str.to_number("123"))
|
|
assert_equal(123.45, str.to_number("123.45"))
|
|
assert_equal(-42, str.to_number("-42"))
|
|
assert_equal(nil, str.to_number("not_a_number"))
|
|
end)
|
|
|
|
test("String Validation", function()
|
|
assert_equal(true, str.is_numeric("123"))
|
|
assert_equal(true, str.is_numeric("123.45"))
|
|
assert_equal(false, str.is_numeric("abc"))
|
|
|
|
assert_equal(true, str.is_alpha("hello"))
|
|
assert_equal(false, str.is_alpha("hello123"))
|
|
assert_equal(false, str.is_alpha(""))
|
|
|
|
assert_equal(true, str.is_alphanumeric("hello123"))
|
|
assert_equal(false, str.is_alphanumeric("hello!"))
|
|
assert_equal(false, str.is_alphanumeric(""))
|
|
|
|
assert_equal(true, str.is_empty(""))
|
|
assert_equal(true, str.is_empty(nil))
|
|
assert_equal(false, str.is_empty("hello"))
|
|
|
|
assert_equal(true, str.is_blank(""))
|
|
assert_equal(true, str.is_blank(" "))
|
|
assert_equal(false, str.is_blank("hello"))
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- ADVANCED STRING OPERATIONS
|
|
-- ======================================================================
|
|
|
|
test("Case Conversion Functions", function()
|
|
assert_equal("Hello World", str.capitalize("hello world"))
|
|
assert_equal("helloWorld", str.camel_case("hello world"))
|
|
assert_equal("HelloWorld", str.pascal_case("hello world"))
|
|
assert_equal("hello_world", str.snake_case("Hello World"))
|
|
assert_equal("hello-world", str.kebab_case("Hello World"))
|
|
assert_equal("HELLO_WORLD", str.screaming_snake_case("hello world"))
|
|
end)
|
|
|
|
test("String Center and Truncate", function()
|
|
assert_equal(" hi ", str.center("hi", 6))
|
|
assert_equal("**hi***", str.center("hi", 7, "*"))
|
|
assert_equal("hello", str.center("hello", 3)) -- Already longer
|
|
|
|
assert_equal("hello...", str.truncate("hello world", 8))
|
|
assert_equal("hello>>", str.truncate("hello world", 8, ">>"))
|
|
assert_equal("hi", str.truncate("hi", 10)) -- Shorter than limit
|
|
end)
|
|
|
|
test("String Wrap", function()
|
|
local wrapped = str.wrap("The quick brown fox jumps over the lazy dog", 10)
|
|
assert_equal("table", type(wrapped))
|
|
assert(#wrapped > 1, "should wrap into multiple lines")
|
|
|
|
-- Each line should be within limit
|
|
for _, line in ipairs(wrapped) do
|
|
assert(str.length(line) <= 10, "line should be within width limit")
|
|
end
|
|
end)
|
|
|
|
test("String Dedent", function()
|
|
local indented = " line1\n line2\n line3"
|
|
local dedented = str.dedent(indented)
|
|
local lines = str.lines(dedented)
|
|
|
|
assert_equal("line1", lines[1])
|
|
assert_equal("line2", lines[2])
|
|
assert_equal("line3", lines[3])
|
|
end)
|
|
|
|
test("Escape and Quote Functions", function()
|
|
assert_equal("hello\\.world", str.escape_regex("hello.world"))
|
|
assert_equal("a\\+b\\*c\\?", str.escape_regex("a+b*c?"))
|
|
|
|
assert_equal("'hello world'", str.shell_quote("hello world"))
|
|
assert_equal("'it'\"'\"'s great'", str.shell_quote("it's great"))
|
|
end)
|
|
|
|
test("URL Encoding", function()
|
|
assert_equal("hello%20world", str.url_encode("hello world"))
|
|
assert_equal("caf%C3%A9", str.url_encode("café"))
|
|
|
|
local encoded = str.url_encode("hello world")
|
|
assert_equal("hello world", str.url_decode(encoded))
|
|
|
|
assert_equal("hello world", str.url_decode("hello+world"))
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- STRING COMPARISON
|
|
-- ======================================================================
|
|
|
|
test("String Comparison", function()
|
|
assert_equal(true, str.iequals("Hello", "HELLO"))
|
|
assert_equal(false, str.iequals("Hello", "world"))
|
|
|
|
-- Test distance and similarity
|
|
assert_equal(3, str.distance("kitten", "sitting"))
|
|
assert_equal(0, str.distance("hello", "hello"))
|
|
|
|
local similarity = str.similarity("hello", "hallo")
|
|
assert(similarity > 0.5 and similarity < 1, "should be partial similarity")
|
|
assert_equal(1, str.similarity("hello", "hello"))
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- TEMPLATE FUNCTIONS
|
|
-- ======================================================================
|
|
|
|
test("Template Functions", function()
|
|
local simple_template = "Hello ${name}, you are ${age} years old"
|
|
local vars = {name = "John", age = 25}
|
|
|
|
assert_equal("Hello John, you are 25 years old", str.template(simple_template, vars))
|
|
|
|
-- Test with missing variables
|
|
local incomplete = str.template("Hello ${name} and ${unknown}", {name = "John"})
|
|
assert_equal("Hello John and ", incomplete)
|
|
|
|
-- Advanced template
|
|
local context = {
|
|
user = {name = "Jane", role = "admin"},
|
|
count = 5
|
|
}
|
|
local advanced = str.template_advanced("User ${user.name} (${user.role}) has ${count} items", context)
|
|
assert_equal("User Jane (admin) has 5 items", advanced)
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- UTILITY FUNCTIONS
|
|
-- ======================================================================
|
|
|
|
test("Whitespace Functions", function()
|
|
assert_equal(true, str.is_whitespace(" "))
|
|
assert_equal(true, str.is_whitespace(""))
|
|
assert_equal(false, str.is_whitespace("hello"))
|
|
|
|
assert_equal("hello", str.strip_whitespace("h e l l o"))
|
|
assert_equal("hello world test", str.normalize_whitespace("hello world test"))
|
|
end)
|
|
|
|
test("Number Extraction", function()
|
|
local numbers = str.extract_numbers("The price is $123.45 and tax is 8.5%")
|
|
assert_equal(2, #numbers)
|
|
assert_close(123.45, numbers[1])
|
|
assert_close(8.5, numbers[2])
|
|
|
|
local negative_nums = str.extract_numbers("Temperature: -15.5 degrees")
|
|
assert_equal(1, #negative_nums)
|
|
assert_close(-15.5, negative_nums[1])
|
|
end)
|
|
|
|
test("Accent Removal", function()
|
|
assert_equal("cafe", str.remove_accents("café"))
|
|
assert_equal("resume", str.remove_accents("résumé"))
|
|
assert_equal("naive", str.remove_accents("naïve"))
|
|
assert_equal("hello", str.remove_accents("hello"))
|
|
end)
|
|
|
|
test("Random String Generation", function()
|
|
local random1 = str.random(10)
|
|
local random2 = str.random(10)
|
|
|
|
assert_equal(10, str.length(random1))
|
|
assert_equal(10, str.length(random2))
|
|
assert(random1 ~= random2, "random strings should be different")
|
|
|
|
-- Custom charset
|
|
local custom = str.random(5, "abc")
|
|
assert_equal(5, str.length(custom))
|
|
assert(str.match("^[abc]+$", custom), "should only contain specified characters")
|
|
end)
|
|
|
|
test("UTF-8 Validation", function()
|
|
assert_equal(true, str.is_utf8("hello"))
|
|
assert_equal(true, str.is_utf8("café"))
|
|
assert_equal(true, str.is_utf8(""))
|
|
|
|
-- Note: This test depends on the actual UTF-8 validation implementation
|
|
-- Some invalid UTF-8 sequences might still pass depending on the system
|
|
end)
|
|
|
|
test("Slug Generation", function()
|
|
assert_equal("hello-world", str.slug("Hello World"))
|
|
assert_equal("cafe-restaurant", str.slug("Café & Restaurant"))
|
|
assert_equal("specialcharacters", str.slug("Special!@#$%Characters"))
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- EDGE CASES AND ERROR HANDLING
|
|
-- ======================================================================
|
|
|
|
test("Empty String Handling", function()
|
|
assert_table_equal({""}, str.split("", ","))
|
|
assert_equal("", str.join({}, ","))
|
|
assert_equal("", str.trim(""))
|
|
assert_equal("", str.reverse(""))
|
|
assert_equal("", str.repeat_("", 5))
|
|
assert_table_equal({""}, str.lines(""))
|
|
assert_table_equal({}, str.words(""))
|
|
end)
|
|
|
|
test("Large String Handling", function()
|
|
local large_string = string.rep("test ", 1000)
|
|
|
|
assert_equal(5000, str.length(large_string))
|
|
assert_equal(1000, str.count(large_string, "test"))
|
|
|
|
local words = str.words(large_string)
|
|
assert_equal(1000, #words)
|
|
|
|
local trimmed = str.trim(large_string)
|
|
assert_equal(true, str.ends_with(trimmed, "test"))
|
|
end)
|
|
|
|
test("Unicode Handling", function()
|
|
local unicode_string = "Hello 🌍 World 🚀"
|
|
|
|
-- Basic operations should work with Unicode
|
|
assert_equal(true, str.contains(unicode_string, "🌍"))
|
|
assert_equal(str.upper(unicode_string), str.upper(unicode_string)) -- Should not crash
|
|
|
|
local parts = str.split(unicode_string, " ")
|
|
assert_equal(4, #parts)
|
|
assert_equal("🌍", parts[2])
|
|
end)
|
|
|
|
test("Regex Error Handling", function()
|
|
-- Invalid regex pattern - check if it actually fails
|
|
local success, result = pcall(str.match, "\\", "test")
|
|
if success then
|
|
-- If it doesn't fail, just verify it works with valid patterns
|
|
assert_equal(true, str.match("test", "test"))
|
|
else
|
|
assert_equal(false, success)
|
|
end
|
|
|
|
local success2, result2 = pcall(str.find, "\\", "test")
|
|
if success2 then
|
|
-- If it doesn't fail, just verify it works with valid patterns
|
|
assert(str.find("test", "test") ~= nil)
|
|
else
|
|
assert_equal(false, success2)
|
|
end
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- PERFORMANCE TESTS
|
|
-- ======================================================================
|
|
|
|
test("Performance Test", function()
|
|
local large_text = string.rep("The quick brown fox jumps over the lazy dog. ", 1000)
|
|
|
|
local start = os.clock()
|
|
local words = str.words(large_text)
|
|
local words_time = os.clock() - start
|
|
|
|
start = os.clock()
|
|
local lines = str.lines(large_text)
|
|
local lines_time = os.clock() - start
|
|
|
|
start = os.clock()
|
|
local replaced = str.replace(large_text, "fox", "cat")
|
|
local replace_time = os.clock() - start
|
|
|
|
start = os.clock()
|
|
local parts = str.split(large_text, " ")
|
|
local split_time = os.clock() - start
|
|
|
|
print(string.format(" Extract %d words: %.3fs", #words, words_time))
|
|
print(string.format(" Extract %d lines: %.3fs", #lines, lines_time))
|
|
print(string.format(" Replace in %d chars: %.3fs", str.length(large_text), replace_time))
|
|
print(string.format(" Split into %d parts: %.3fs", #parts, split_time))
|
|
|
|
assert(#words > 8000, "should extract many words")
|
|
assert(str.contains(replaced, "cat"), "replacement should work")
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- INTEGRATION TESTS
|
|
-- ======================================================================
|
|
|
|
test("String Processing Pipeline", function()
|
|
local messy_input = " HELLO, world! How ARE you? "
|
|
|
|
-- Clean and normalize
|
|
local cleaned = str.normalize_whitespace(str.trim(messy_input))
|
|
local lowered = str.lower(cleaned)
|
|
local words = str.words(lowered)
|
|
local filtered = {}
|
|
|
|
for _, word in ipairs(words) do
|
|
-- Remove punctuation from word before checking length
|
|
local clean_word = str.gsub("[[:punct:]]", word, "")
|
|
if str.length(clean_word) > 2 then
|
|
table.insert(filtered, clean_word)
|
|
end
|
|
end
|
|
|
|
local result = str.join(filtered, "-")
|
|
|
|
assert_equal("hello-world-how-are-you", result)
|
|
end)
|
|
|
|
test("Text Analysis", function()
|
|
local text = "The quick brown fox jumps over the lazy dog. The dog was sleeping."
|
|
|
|
local word_count = #str.words(text)
|
|
local sentence_count = str.count(text, ".")
|
|
local the_count = str.count(str.lower(text), "the")
|
|
|
|
assert_equal(13, word_count)
|
|
assert_equal(2, sentence_count)
|
|
assert_equal(3, the_count)
|
|
|
|
-- Extract all words starting with vowels
|
|
local words = str.words(str.lower(text))
|
|
local vowel_words = {}
|
|
for _, word in ipairs(words) do
|
|
local clean_word = str.replace(word, "%p", "") -- Remove punctuation
|
|
if str.match("^[aeiou]", clean_word) then
|
|
table.insert(vowel_words, clean_word)
|
|
end
|
|
end
|
|
|
|
assert(#vowel_words >= 1, "should find words starting with vowels")
|
|
end)
|
|
|
|
summary()
|
|
test_exit() |