# # Copyright (c) 2024-2026, Daily # # SPDX-License-Identifier: BSD 2-Clause License # import unittest from pipecat.utils.text.word_timestamp_utils import merge_punct_tokens class TestMergePunctTokens(unittest.TestCase): def test_empty_list(self): self.assertEqual(merge_punct_tokens([]), []) def test_all_alnum_words_pass_through(self): input = [("hello", 0.0), ("world", 1.0)] self.assertEqual(merge_punct_tokens(input), [("hello", 0.0), ("world", 1.0)]) def test_trailing_space_merged_and_stripped(self): input = [("I", 0.0), (" ", 0.2)] self.assertEqual(merge_punct_tokens(input), [("I", 0.0)]) def test_comma_space_merged_and_stripped(self): input = [("questions", 1.0), (", ", 1.2), ("explain", 1.4)] self.assertEqual(merge_punct_tokens(input), [("questions,", 1.0), ("explain", 1.4)]) def test_leading_space_with_no_preceding_word_discarded(self): input = [(" ", 0.0), ("hello", 0.5)] self.assertEqual(merge_punct_tokens(input), [("hello", 0.5)]) def test_leading_empty_string_discarded(self): input = [("", 0.0), ("hello", 0.5)] self.assertEqual(merge_punct_tokens(input), [("hello", 0.5)]) def test_multiple_consecutive_punct_tokens_merged_and_stripped(self): input = [("word", 0.0), (",", 0.1), (" ", 0.2), ("next", 0.3)] self.assertEqual(merge_punct_tokens(input), [("word,", 0.0), ("next", 0.3)]) def test_timestamp_of_preceding_word_is_kept(self): """Merged punct tokens adopt the preceding word's timestamp.""" input = [("hello", 2.5), (",", 2.7)] result = merge_punct_tokens(input) self.assertEqual(result, [("hello,", 2.5)]) def test_xml_tag_only_token_is_treated_as_punct(self): """A token that is only an XML tag (no alnum chars) merges into the preceding word.""" input = [("word", 0.0), ("", 0.1), ("next", 0.3)] self.assertEqual(merge_punct_tokens(input), [("word", 0.0), ("next", 0.3)]) def test_xml_tag_with_alnum_content_passes_through(self): """A token like '123' has alnum chars after stripping tags.""" input = [("123", 0.0), ("and", 0.5)] self.assertEqual(merge_punct_tokens(input), [("123", 0.0), ("and", 0.5)]) def test_inworld_style_full_stream(self): """Full Inworld-style raw stream produces expected merged and stripped output.""" raw = [ ("", 0.0), ("I", 0.1), (" ", 0.2), ("can", 0.3), (" ", 0.4), ("answer", 0.5), (" ", 0.6), ("questions", 0.7), (", ", 0.8), ("explain", 0.9), (" ", 1.0), ("things", 1.1), (".", 1.2), ] expected = [ ("I", 0.1), ("can", 0.3), ("answer", 0.5), ("questions,", 0.7), ("explain", 0.9), ("things.", 1.1), ] self.assertEqual(merge_punct_tokens(raw), expected) def test_only_punct_tokens_returns_empty(self): """A list containing only punct/space tokens produces an empty result.""" input = [(" ", 0.0), (",", 0.1), (".", 0.2)] self.assertEqual(merge_punct_tokens(input), []) if __name__ == "__main__": unittest.main()