import unittest from unittest.mock import patch from textual.widgets import Input, Label from chat_tui.app import ( RESPONSE_MESSAGES, ChatApp, ChatMessage, MessageRole, ) class ChatAppTest(unittest.IsolatedAsyncioTestCase): async def test_submitted_input_and_response_are_displayed_in_order(self) -> None: app = ChatApp() with patch("chat_tui.app.random.choice", return_value=RESPONSE_MESSAGES[2]): async with app.run_test() as pilot: input_widget = app.query_one("#user-input", Input) input_widget.value = " 첫 번째 입력 " await pilot.press("enter") await pilot.pause() input_widget.value = "두 번째 입력" await pilot.press("enter") await pilot.pause() self.assertEqual( app.submitted_inputs, ["첫 번째 입력", "두 번째 입력"], ) self.assertEqual( app.selected_responses, [RESPONSE_MESSAGES[2], RESPONSE_MESSAGES[2]], ) self.assertEqual( app.messages, [ ChatMessage(MessageRole.USER, "첫 번째 입력"), ChatMessage(MessageRole.AGENT, RESPONSE_MESSAGES[2]), ChatMessage(MessageRole.USER, "두 번째 입력"), ChatMessage(MessageRole.AGENT, RESPONSE_MESSAGES[2]), ], ) displayed = [ str(label.renderable) for label in app.query("#conversation Label") ] self.assertEqual( displayed, [ "사용자: 첫 번째 입력", f"에이전트: {RESPONSE_MESSAGES[2]}", "사용자: 두 번째 입력", f"에이전트: {RESPONSE_MESSAGES[2]}", ], ) self.assertEqual(input_widget.value, "") async def test_messages_have_explicit_roles_labels_and_structural_styles( self, ) -> None: app = ChatApp() with patch("chat_tui.app.random.choice", return_value=RESPONSE_MESSAGES[0]): async with app.run_test() as pilot: input_widget = app.query_one("#user-input", Input) input_widget.value = "역할 확인" await pilot.press("enter") await pilot.pause() labels = list(app.query("#conversation Label")) self.assertEqual(len(labels), 2) self.assertEqual( labels[0].classes, frozenset({"chat-message", "user-message", "user-role"}), ) self.assertEqual( labels[1].classes, frozenset({"chat-message", "response-message", "agent-role"}), ) self.assertIn("사용자:", str(labels[0].renderable)) self.assertIn("에이전트:", str(labels[1].renderable)) self.assertIn("border-left", app.CSS) self.assertIn("border-right", app.CSS) self.assertIn("align: left middle", app.CSS) self.assertIn("align: right middle", app.CSS) async def test_response_selection_uses_standard_random_choice(self) -> None: app = ChatApp() with patch( "chat_tui.app.random.choice", return_value=RESPONSE_MESSAGES[4], ) as choice_mock: async with app.run_test() as pilot: input_widget = app.query_one("#user-input", Input) input_widget.value = "처리할 입력" await pilot.press("enter") await pilot.pause() choice_mock.assert_called_once_with(RESPONSE_MESSAGES) async def test_whitespace_input_is_not_added(self) -> None: app = ChatApp() with patch("chat_tui.app.random.choice") as choice_mock: async with app.run_test() as pilot: input_widget = app.query_one("#user-input", Input) input_widget.value = " \t \n" await pilot.press("enter") await pilot.pause() self.assertEqual(app.submitted_inputs, []) self.assertEqual(app.selected_responses, []) self.assertEqual(app.messages, []) self.assertEqual(len(app.query("#conversation Label")), 0) choice_mock.assert_not_called() async def test_empty_input_is_not_added(self) -> None: app = ChatApp() async with app.run_test() as pilot: await pilot.press("enter") await pilot.pause() self.assertEqual(app.submitted_inputs, []) self.assertEqual(app.selected_responses, []) self.assertEqual(app.messages, []) self.assertEqual(len(app.query("#conversation Label")), 0) async def test_escape_requests_application_exit(self) -> None: app = ChatApp() with patch.object(app, "exit") as exit_mock: async with app.run_test() as pilot: await pilot.press("escape") await pilot.pause() exit_mock.assert_called_once_with()