Files
dev/third_party/python/rpycdec/tests/test_translate.py

297 lines
10 KiB
Python

"""Tests for rpycdec.translate module"""
import os
import sys
import tempfile
import unittest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
import renpy.ast
from rpycdec.translate import (
DialogueTranslation,
StringTranslation,
TranslationExtractor,
elide_filename,
get_translation_filename,
quote_unicode,
unique_identifier,
write_dialogue_translations,
write_string_translations,
)
class TestGetTranslationFilename(unittest.TestCase):
"""Tests for get_translation_filename()"""
def test_rpyc_to_rpy(self):
self.assertEqual(get_translation_filename("script.rpyc"), "script.rpy")
def test_rpymc_to_rpym(self):
self.assertEqual(get_translation_filename("script.rpymc"), "script.rpym")
def test_subdirectory_preserved(self):
"""Directory structure should be preserved (was a bug: basename stripped dirs)"""
self.assertEqual(get_translation_filename("sub/intro.rpyc"), "sub/intro.rpy")
def test_nested_subdirectory_preserved(self):
self.assertEqual(
get_translation_filename("a/b/c/script.rpyc"), "a/b/c/script.rpy"
)
def test_rpym_unchanged(self):
self.assertEqual(get_translation_filename("script.rpym"), "script.rpym")
def test_rpy_unchanged(self):
self.assertEqual(get_translation_filename("script.rpy"), "script.rpy")
class TestElideFilename(unittest.TestCase):
def test_strips_game_dir(self):
result = elide_filename("/game/script.rpyc", "/game")
self.assertEqual(result, "script.rpy")
def test_rpymc_to_rpym(self):
result = elide_filename("/game/script.rpymc", "/game")
self.assertEqual(result, "script.rpym")
def test_no_game_dir(self):
result = elide_filename("script.rpyc")
self.assertEqual(result, "script.rpy")
class TestQuoteUnicode(unittest.TestCase):
def test_backslash(self):
self.assertEqual(quote_unicode("a\\b"), "a\\\\b")
def test_double_quote(self):
self.assertEqual(quote_unicode('say "hi"'), 'say \\"hi\\"')
def test_newline(self):
self.assertEqual(quote_unicode("line1\nline2"), "line1\\nline2")
def test_plain_string(self):
self.assertEqual(quote_unicode("hello"), "hello")
class TestUniqueIdentifier(unittest.TestCase):
def test_no_label(self):
result = unique_identifier(None, "abcd1234")
self.assertEqual(result, "abcd1234")
def test_with_label(self):
result = unique_identifier("start", "abcd1234")
self.assertEqual(result, "start_abcd1234")
def test_label_dot_replaced(self):
result = unique_identifier("chapter.one", "abcd1234")
self.assertEqual(result, "chapter_one_abcd1234")
def test_collision_appends_index(self):
existing = {"start_abcd1234"}
result = unique_identifier("start", "abcd1234", existing)
self.assertEqual(result, "start_abcd1234_1")
def test_multiple_collisions(self):
existing = {"start_abcd1234", "start_abcd1234_1"}
result = unique_identifier("start", "abcd1234", existing)
self.assertEqual(result, "start_abcd1234_2")
class TestTranslationExtractor(unittest.TestCase):
def _make_say_node(self, who, what, filename="script.rpyc", linenumber=1):
node = renpy.ast.Say(loc=(filename, linenumber))
node.who = who
node.what = what
node.interact = True
node.identifier = None
node.explicit_identifier = False
node.attributes = None
node.temporary_attributes = None
node.arguments = None
node.with_ = None
return node
def test_extract_say_dialogue(self):
extractor = TranslationExtractor()
node = self._make_say_node("e", "Hello world.")
extractor.extract_file("script.rpyc", [node])
self.assertEqual(len(extractor.dialogues), 1)
d = extractor.dialogues[0]
self.assertEqual(d.who, "e")
self.assertEqual(d.what, "Hello world.")
self.assertEqual(d.filename, "script.rpyc")
def test_extract_say_no_speaker(self):
extractor = TranslationExtractor()
node = self._make_say_node(None, "A narrator line.")
extractor.extract_file("script.rpyc", [node])
self.assertEqual(len(extractor.dialogues), 1)
self.assertIsNone(extractor.dialogues[0].who)
def test_no_say_no_dialogue(self):
extractor = TranslationExtractor()
extractor.extract_file("script.rpyc", [])
self.assertEqual(len(extractor.dialogues), 0)
self.assertEqual(len(extractor.strings), 0)
def test_identifier_generated(self):
extractor = TranslationExtractor()
node = self._make_say_node("e", "Hello.")
extractor.extract_file("script.rpyc", [node])
self.assertIsNotNone(extractor.dialogues[0].identifier)
self.assertIsInstance(extractor.dialogues[0].identifier, str)
class TestWriteDialogueTranslations(unittest.TestCase):
def test_writes_dialogue_file(self):
dialogues = [
DialogueTranslation(
identifier="start_abcd1234",
filename="script.rpyc",
linenumber=5,
who="e",
what="Hello.",
code='e "Hello."',
)
]
with tempfile.TemporaryDirectory() as tmpdir:
write_dialogue_translations(
dialogues, tmpdir, "chinese", empty_translation=False
)
tl_file = os.path.join(tmpdir, "script.rpy")
self.assertTrue(os.path.exists(tl_file))
with open(tl_file) as f:
content = f.read()
self.assertIn("translate chinese start_abcd1234:", content)
self.assertIn('e "Hello."', content)
def test_preserves_subdirectory_structure(self):
"""Translation files should mirror the source directory structure"""
dialogues = [
DialogueTranslation(
identifier="intro_abcd1234",
filename="sub/intro.rpyc",
linenumber=3,
who=None,
what="Welcome.",
code='"Welcome."',
)
]
with tempfile.TemporaryDirectory() as tmpdir:
write_dialogue_translations(
dialogues, tmpdir, "chinese", empty_translation=False
)
tl_file = os.path.join(tmpdir, "sub", "intro.rpy")
self.assertTrue(
os.path.exists(tl_file),
f"Expected {tl_file} to exist (subdirectory structure not preserved)",
)
def test_empty_translation(self):
dialogues = [
DialogueTranslation(
identifier="start_abcd1234",
filename="script.rpyc",
linenumber=1,
who="e",
what="Hello.",
code='e "Hello."',
)
]
with tempfile.TemporaryDirectory() as tmpdir:
write_dialogue_translations(
dialogues, tmpdir, "chinese", empty_translation=True
)
with open(os.path.join(tmpdir, "script.rpy")) as f:
content = f.read()
self.assertIn('e ""', content)
class TestWriteStringTranslations(unittest.TestCase):
def test_writes_strings_file(self):
strings = [StringTranslation(filename="script.rpyc", linenumber=2, text="Play")]
with tempfile.TemporaryDirectory() as tmpdir:
count = write_string_translations(
strings, tmpdir, "chinese", empty_translation=False
)
self.assertEqual(count, 1)
tl_file = os.path.join(tmpdir, "strings.rpy")
self.assertTrue(os.path.exists(tl_file))
with open(tl_file) as f:
content = f.read()
self.assertIn("translate chinese strings:", content)
self.assertIn('old "Play"', content)
self.assertIn('new "Play"', content)
def test_empty_strings_not_written(self):
with tempfile.TemporaryDirectory() as tmpdir:
count = write_string_translations([], tmpdir, "chinese")
self.assertEqual(count, 0)
self.assertFalse(os.path.exists(os.path.join(tmpdir, "strings.rpy")))
class TestCLIExtractTranslate(unittest.TestCase):
"""Integration tests for the extract-translate CLI command"""
def test_language_argument_recognized(self):
"""The --language argument must be recognized by the CLI parser"""
from rpycdec.cli import main
with tempfile.TemporaryDirectory() as tmpdir:
sys.argv = [
"rpycdec",
"extract-translate",
tmpdir,
"--language",
"chinese",
]
# Should not raise SystemExit with non-zero code (unrecognized arguments)
try:
main()
except SystemExit as e:
if e.code != 0:
self.fail(
f"CLI raised SystemExit({e.code}) - --language may not be recognized"
)
def test_language_short_flag(self):
"""The -l short flag must also work"""
from rpycdec.cli import main
with tempfile.TemporaryDirectory() as tmpdir:
sys.argv = ["rpycdec", "extract-translate", tmpdir, "-l", "japanese"]
try:
main()
except SystemExit as e:
if e.code != 0:
self.fail(
f"CLI raised SystemExit({e.code}) - -l flag may not be recognized"
)
def test_creates_tl_directory(self):
"""extract-translate must create tl/<language>/ directory in output"""
from rpycdec.cli import main
with tempfile.TemporaryDirectory() as game_dir:
with tempfile.TemporaryDirectory() as out_dir:
sys.argv = [
"rpycdec",
"extract-translate",
game_dir,
"--language",
"chinese",
"--output",
out_dir,
]
main()
tl_dir = os.path.join(out_dir, "tl", "chinese")
self.assertTrue(
os.path.isdir(tl_dir),
f"Expected tl/chinese/ directory at {tl_dir}",
)
if __name__ == "__main__":
unittest.main()