Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ full = [
"sefr_cut>=1.1",
"symspellpy==6.9.0",
"thai-nner==0.3",
"tltk>=1.6.8,<2",
"tltk>=1.10,<2",
"torch>=1.13.1,<3",
"transformers==4.57.6",
"ufal.chu-liu-edmonds==1.0.3",
Expand Down
4 changes: 2 additions & 2 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ supported Python versions and operating systems:

The CI/CD test workflow is at
<https://github.com/PyThaiNLP/pythainlp/blob/dev/.github/workflows/unittest.yml>.

## Core tests (test_*.py)

- Run `unittest tests.core`
Expand Down Expand Up @@ -65,7 +65,7 @@ The CI/CD test workflow is at
A comprehensive test suite within core tests that tests edge cases important
for real-world usage:

- Empty strings and various whitespace handling (spaces, tabs, unicode spaces)
- Empty strings and various whitespace handling (spaces, tabs, Unicode spaces)
- Special characters from encoding issues, BOM, terminal copy/paste
- Truncated/malformed Unicode and surrogate pairs
- Emoji and modern Unicode sequences (ZWJ, modifiers, flags)
Expand Down
61 changes: 60 additions & 1 deletion tests/compact/testc_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@

import numpy as np

from pythainlp.tools.misspell import misspell
from pythainlp.tools.misspell import (
find_misspell_candidates,
misspell,
search_location_of_character,
)


def _count_difference(st1: str, st2: str) -> int:
Expand Down Expand Up @@ -85,3 +89,58 @@ def test_misspell_with_ratio_100_percent(self):
2,
f"expect len(text)-2 misspells with ratio=1.5. (Δ={diff})",
)

def test_search_location_of_character(self):
"""Test search_location_of_character function."""
# Test Thai characters
loc = search_location_of_character("ก")
self.assertIsNotNone(loc)
self.assertEqual(len(loc), 4) # (language_ix, is_shift, row, pos)

# Test English characters
loc = search_location_of_character("a")
self.assertIsNotNone(loc)
self.assertEqual(len(loc), 4)

# Test shifted characters
loc = search_location_of_character("A")
self.assertIsNotNone(loc)

# Test numbers
loc = search_location_of_character("1")
self.assertIsNotNone(loc)

# Test character not in keyboard
loc = search_location_of_character("€")
self.assertIsNone(loc)

# Test empty string
# Note: Empty string returns a location because Python's "in" operator
# matches empty string at the beginning of any string
loc = search_location_of_character("")
self.assertIsNotNone(loc)

def test_find_misspell_candidates(self):
"""Test find_misspell_candidates function."""
# Test Thai character
candidates = find_misspell_candidates("ก")
self.assertIsNotNone(candidates)
self.assertIsInstance(candidates, list)
self.assertGreater(len(candidates), 0)

# Test English character
candidates = find_misspell_candidates("a")
self.assertIsNotNone(candidates)
self.assertIsInstance(candidates, list)
self.assertGreater(len(candidates), 0)

# Test character not in keyboard
candidates = find_misspell_candidates("€")
self.assertIsNone(candidates)

# Test that candidates are different from input
candidates = find_misspell_candidates("ด")
if candidates:
for candidate in candidates:
# Candidates should be strings
self.assertIsInstance(candidate, str)
26 changes: 26 additions & 0 deletions tests/core/test_ancient.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ def test_aksonhan_to_current(self):
self.assertEqual(aksonhan_to_current("บงงคบบ"), "บังคับ")
self.assertEqual(aksonhan_to_current("สรรเพชญ"), "สรรเพชญ")

# Edge cases
self.assertEqual(aksonhan_to_current(""), "") # empty string
self.assertEqual(aksonhan_to_current("ก"), "ก") # single char
self.assertEqual(aksonhan_to_current("กา"), "กา") # two chars

def test_convert_currency(self):
self.assertEqual(
convert_currency(80, "บาท")["ตำลึง"],
Expand All @@ -44,3 +49,24 @@ def test_convert_currency(self):
convert_currency(1,"ชั่ง")["ชั่ง"],
1.0
)

# Test all supported units
result = convert_currency(1, "บาท")
self.assertIn("เบี้ย", result)
self.assertIn("อัฐ", result)
self.assertIn("ไพ", result)
self.assertIn("เฟื้อง", result)
self.assertIn("สลึง", result)
self.assertIn("ตำลึง", result)

# Test with zero value
result = convert_currency(0, "บาท")
self.assertEqual(result["บาท"], 0.0)

# Test with fractional value
result = convert_currency(0.5, "บาท")
self.assertEqual(result["บาท"], 0.5)

# Test invalid unit
with self.assertRaises(NotImplementedError):
convert_currency(1, "invalid_unit")
68 changes: 68 additions & 0 deletions tests/core/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import os
import tempfile
import unittest
import warnings

from pythainlp.tools import (
get_full_data_path,
get_pythainlp_data_path,
get_pythainlp_path,
)
from pythainlp.tools.core import safe_print, warn_deprecation


class ToolsTestCase(unittest.TestCase):
Expand Down Expand Up @@ -57,3 +59,69 @@ def test_custom_data_dir(self):
os.environ["PYTHAINLP_DATA_DIR"] = original_value
elif "PYTHAINLP_DATA_DIR" in os.environ:
del os.environ["PYTHAINLP_DATA_DIR"]

def test_warn_deprecation(self):
"""Test deprecation warning function."""
# Test basic deprecation warning
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
warn_deprecation("old_func")
self.assertEqual(len(w), 1)
self.assertTrue(issubclass(w[0].category, DeprecationWarning))
self.assertIn("old_func", str(w[0].message))
self.assertIn("deprecated", str(w[0].message))

# Test with replacement function
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
warn_deprecation("old_func", replacing_func="new_func")
self.assertEqual(len(w), 1)
self.assertIn("old_func", str(w[0].message))
self.assertIn("new_func", str(w[0].message))

# Test with version information
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
warn_deprecation(
"old_func",
deprecated_version="1.0",
removal_version="2.0"
)
self.assertEqual(len(w), 1)
self.assertIn("1.0", str(w[0].message))
self.assertIn("2.0", str(w[0].message))

# Test with all parameters
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
warn_deprecation(
"old_func",
replacing_func="new_func",
deprecated_version="1.0",
removal_version="2.0"
)
self.assertEqual(len(w), 1)
message = str(w[0].message)
self.assertIn("old_func", message)
self.assertIn("new_func", message)
self.assertIn("1.0", message)
self.assertIn("2.0", message)

def test_safe_print(self):
"""Test safe_print function."""
# Test normal printing
safe_print("Hello, World!")
safe_print("สวัสดีครับ")

# Test with Unicode characters
safe_print("Hello 👋 World")
safe_print("ภาษาไทย")

# Test with empty string
safe_print("")

# Test with special characters
safe_print("\n\t")

# Note: Testing actual UnicodeEncodeError is environment-dependent
# and would require mocking sys.stdout.encoding
39 changes: 35 additions & 4 deletions tests/core/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ def test_collate(self):
collate(["ไก่", "เป็ด", "หมู", "วัว"], reverse=True),
["หมู", "วัว", "เป็ด", "ไก่"],
)
# Edge cases: mixed Thai and numbers
self.assertEqual(collate(["ก", "1", "ข"]), ["1", "ก", "ข"])
# Edge cases: with spaces (spaces sort before letters)
result = collate([" ก", "ก", " ก"])
self.assertEqual(len(result), 3)
self.assertIn(" ก", result)
self.assertIn("ก", result)
self.assertIn(" ก", result)

# ### pythainlp.util.numtoword

Expand Down Expand Up @@ -720,6 +728,11 @@ def test_emoji_to_thai(self):
":ธง_ไทย: นี่คือธงประเทศไทย",
)

# Edge cases
self.assertEqual(emoji_to_thai(""), "") # empty string
self.assertEqual(emoji_to_thai("no emoji"), "no emoji") # no emoji
self.assertEqual(emoji_to_thai("ไม่มีอีโมจิ"), "ไม่มีอีโมจิ") # Thai no emoji

def test_sound_syllable(self):
test = [
("มา", "live"),
Expand Down Expand Up @@ -863,6 +876,10 @@ def test_syllable_open_close_detector(self):

def test_to_idna(self):
self.assertEqual(to_idna("คนละครึ่ง.com"), "xn--42caj4e6bk1f5b1j.com")
# Additional test cases for IDNA encoding
self.assertEqual(to_idna("ไทย.com"), "xn--o3cw4h.com")
self.assertEqual(to_idna("example.com"), "example.com") # ASCII unchanged
self.assertEqual(to_idna("ภาษาไทย.th"), "xn--o3crh0a8bb0k.th")

def test_thai_strptime(self):
self.assertIsNotNone(
Expand Down Expand Up @@ -923,6 +940,11 @@ def test_tis620_to_utf8(self):
self.assertEqual(
tis620_to_utf8("¡ÃзÃÇ§ÍØµÊÒË¡ÃÃÁ"), "กระทรวงอุตสาหกรรม"
)
# Additional test cases
self.assertEqual(tis620_to_utf8("»ÃÐà·Èä·Â"), "ประเทศไทย")
self.assertEqual(tis620_to_utf8("ÀÒÉÒä·Â"), "ภาษาไทย")
# Empty string
self.assertEqual(tis620_to_utf8(""), "")

def test_remove_repeat_consonants(self):
# update of pythainlp.copus.thai_words() able to break this
Expand Down Expand Up @@ -988,11 +1010,20 @@ def test_longest_common_subsequence(self):
self.assertEqual(longest_common_subsequence("ABCBDAB", "BDCAB"), "BDAB")
self.assertEqual(longest_common_subsequence("AGGTAB", "GXTXAYB"), "GTAB")
self.assertEqual(longest_common_subsequence("ABCDGH", "AEDFHR"), "ADH")

# Edge cases
self.assertEqual(longest_common_subsequence("", ""), "") # empty strings
self.assertEqual(longest_common_subsequence("ABC", ""), "") # one empty
self.assertEqual(longest_common_subsequence("", "ABC"), "") # other empty
self.assertEqual(longest_common_subsequence("A", "A"), "A") # single char match
self.assertEqual(longest_common_subsequence("A", "B"), "") # single char no match
self.assertEqual(longest_common_subsequence("ABC", "ABC"), "ABC") # identical
self.assertEqual(longest_common_subsequence("ABC", "XYZ"), "") # no common chars
self.assertEqual(longest_common_subsequence("ABC", "AC"), "AC")
self.assertEqual(longest_common_subsequence("ABC", "DEF"), "")
self.assertEqual(longest_common_subsequence("", "ABC"), "")
self.assertEqual(longest_common_subsequence("ABC", ""), "")
self.assertEqual(longest_common_subsequence("", ""), "")

# Thai text
self.assertEqual(longest_common_subsequence("ไทย", "ไทย"), "ไทย")
self.assertEqual(longest_common_subsequence("ภาษาไทย", "ไทย"), "ไทย")

def test_analyze_thai_text(self):
self.assertEqual(
Expand Down
Loading