이슈 #4 구현 #5
@@ -1,6 +1,6 @@
|
||||
# gitea-agent-testbed
|
||||
|
||||
Python 3.11 이상에서 실행하는 Textual 기반 대화형 TUI 스캐폴드입니다. 입력한 문자열을 세션 내 대화 영역에 누적 표시하며, LLM이나 외부 API는 호출하지 않습니다.
|
||||
Python 3.11 이상에서 실행하는 Textual 기반 대화형 TUI 스캐폴드입니다. 입력한 문자열을 `trim`한 뒤 세션 내 대화 영역에 누적 표시하고, 유효한 메시지마다 고정된 다섯 개 응답 중 하나를 표준 난수로 선택해 바로 뒤에 표시합니다. LLM이나 외부 API는 호출하지 않습니다.
|
||||
|
||||
## 설치
|
||||
|
||||
@@ -16,7 +16,7 @@ chat-tui
|
||||
python -m chat_tui
|
||||
```
|
||||
|
||||
입력창에서 메시지를 입력하고 Enter를 누르면 대화 영역에 표시됩니다. `Escape` 또는 `Ctrl+Q`로 종료합니다.
|
||||
입력창에서 메시지를 입력하고 Enter를 누르면 사용자 메시지와 무작위 응답이 대화 영역에 표시됩니다. 공백만 입력한 경우에는 아무 대화 항목도 추가되지 않습니다. `Escape` 또는 `Ctrl+Q`로 종료합니다.
|
||||
|
||||
## 테스트
|
||||
|
||||
|
||||
+33
-7
@@ -1,13 +1,39 @@
|
||||
# 데이터 모델
|
||||
|
||||
## input-display-state
|
||||
## 대화 상태
|
||||
|
||||
초기 구현의 상태는 `ChatApp.submitted_inputs`와 화면 위젯으로 구성한다.
|
||||
`ChatApp`은 세션 동안 다음 목록을 메모리에 유지한다.
|
||||
|
||||
- `Input.value`: 현재 입력창에 편집 중인 문자열이다.
|
||||
- `submitted_inputs`: 현재 애플리케이션 세션에서 제출된 문자열의 순서 있는 목록이다.
|
||||
- `#conversation`: 제출된 각 문자열을 `Label`로 표시하는 세션 내 대화 영역이다.
|
||||
- `submitted_inputs`: `trim`된 유효 사용자 메시지 목록
|
||||
- `selected_responses`: 각 사용자 메시지에 대응하는 선택 응답 목록
|
||||
|
||||
입력 제출이 성공하면 원문을 `submitted_inputs`에 추가하고 같은 문자열을 대화 영역에 표시한 뒤 입력창을 비운다. 빈 문자열은 추가하지 않는다.
|
||||
두 목록은 같은 인덱스를 사용하며, 유효한 제출 하나마다 항목 하나씩 추가된다. 공백 입력은 어느 목록도 변경하지 않는다.
|
||||
|
||||
이 모델은 프로세스 메모리에만 존재한다. 파일, 데이터베이스, 외부 서비스에는 기록하지 않으며 애플리케이션 종료 시 상태가 폐기된다. LLM 응답이나 메시지 작성자 구분은 초기 범위에 포함하지 않는다.
|
||||
## 고정 응답 목록
|
||||
|
||||
`src/chat_tui/app.py`의 `RESPONSE_MESSAGES` 상수는 다음 다섯 문자열을 순서대로 보유한다.
|
||||
|
||||
1. `안녕하세요! 무엇을 도와드릴까요?`
|
||||
2. `그것은 제가 처리하기 불가능합니다.`
|
||||
3. `맡겨만 주세요.`
|
||||
4. `지금 처리 중입니다.`
|
||||
5. `작업을 완료하였습니다.`
|
||||
|
||||
응답은 `random.choice(RESPONSE_MESSAGES)`로 선택한다. 난수 선택 자체는 테스트에서 `chat_tui.app.random.choice`를 대체할 수 있는 경계로 취급한다.
|
||||
|
||||
## 대화 항목
|
||||
|
||||
유효한 입력이 제출되면 대화 영역에 다음 두 `Label`을 순서대로 추가한다.
|
||||
|
||||
1. `user-message` 클래스의 사용자 메시지
|
||||
2. `response-message` 클래스의 선택 응답
|
||||
|
||||
사용자 메시지는 입력값을 `strip()`한 결과이며, 선택 응답은 고정 응답 목록의 문자열이다.
|
||||
|
||||
## 불변 조건
|
||||
|
||||
- 공백 제거 결과가 빈 문자열이면 상태와 화면을 변경하지 않는다.
|
||||
- 유효한 입력마다 사용자 메시지가 응답보다 먼저 추가된다.
|
||||
- `submitted_inputs`와 `selected_responses`의 길이는 항상 같다.
|
||||
- 응답은 고정 목록에 포함된 값이어야 한다.
|
||||
- 대화 기록은 애플리케이션 세션 안에서만 유지한다.
|
||||
|
||||
+28
-14
@@ -1,24 +1,38 @@
|
||||
# 인터페이스 계약
|
||||
|
||||
## tui-input-display
|
||||
## 메시지 제출 이벤트
|
||||
|
||||
### 화면
|
||||
Textual의 `Input.Submitted` 이벤트가 `#user-input`에서 발생하면 `ChatApp.on_input_submitted`가 처리한다.
|
||||
|
||||
- Textual 애플리케이션은 헤더, `#conversation` 대화 영역, `#user-input` 입력창을 제공한다.
|
||||
- 애플리케이션 시작 시 입력창에 포커스를 둔다.
|
||||
### 입력 정규화
|
||||
|
||||
### 입력 제출
|
||||
1. 이벤트의 `value`에 `strip()`을 적용한다.
|
||||
2. 입력 위젯의 값을 빈 문자열로 초기화한다.
|
||||
3. 정규화 결과가 빈 문자열이면 추가 처리를 중단한다.
|
||||
|
||||
- 사용자가 `#user-input`에서 Enter를 누르면 현재 값이 제출된다.
|
||||
- 비어 있지 않은 값은 변경 없이 대화 영역에 새 항목으로 추가된다.
|
||||
- 제출 후 입력창은 빈 값이 된다.
|
||||
- 대화 영역의 항목 순서는 제출 순서와 같다.
|
||||
공백만 포함한 입력은 사용자 메시지, 응답 선택, 대화 항목을 생성하지 않는다.
|
||||
|
||||
### 종료
|
||||
### 유효한 입력 처리
|
||||
|
||||
- `Escape`와 `Ctrl+Q`는 애플리케이션의 종료 동작을 호출한다.
|
||||
- 종료 시 세션 내 메모리 상태를 영구 저장하지 않는다.
|
||||
정규화 결과가 비어 있지 않으면 다음 순서로 처리한다.
|
||||
|
||||
### 경계
|
||||
1. 정규화된 사용자 메시지를 `submitted_inputs`에 추가한다.
|
||||
2. `random.choice(RESPONSE_MESSAGES)`로 응답을 선택한다.
|
||||
3. 선택 응답을 `selected_responses`에 추가한다.
|
||||
4. 대화 영역에 사용자 메시지 `Label`을 추가한다.
|
||||
5. 바로 이어서 응답 `Label`을 추가한다.
|
||||
6. 대화 영역을 마지막 항목까지 스크롤한다.
|
||||
|
||||
이 인터페이스는 화면 표시와 입력 상태 관리만 담당한다. LLM 클라이언트, 네트워크 요청, 인증 및 외부 서비스 연동은 제공하지 않는다.
|
||||
사용자 메시지와 응답은 별도 대화 항목이며, 화면 표시 순서는 항상 사용자 메시지 다음 응답이다.
|
||||
|
||||
## 화면 위젯 계약
|
||||
|
||||
- 대화 영역은 `#conversation` 식별자의 `VerticalScroll`이다.
|
||||
- 입력창은 `#user-input` 식별자의 `Input`이다.
|
||||
- 사용자 메시지는 `user-message` 클래스를 사용한다.
|
||||
- 응답은 `response-message` 클래스를 사용한다.
|
||||
- 애플리케이션 마운트 시 입력창에 포커스를 둔다.
|
||||
|
||||
## 종료 계약
|
||||
|
||||
`Escape`와 `Ctrl+Q`는 기존 `quit` 바인딩을 통해 애플리케이션 종료를 요청한다.
|
||||
|
||||
+29
-20
@@ -1,23 +1,32 @@
|
||||
# 프로젝트 구조
|
||||
|
||||
```text
|
||||
.
|
||||
├── pyproject.toml
|
||||
├── README.md
|
||||
├── src/
|
||||
│ └── chat_tui/
|
||||
│ ├── __init__.py
|
||||
│ ├── __main__.py
|
||||
│ └── app.py
|
||||
├── tests/
|
||||
│ └── test_app.py
|
||||
└── docs/
|
||||
├── data-model.md
|
||||
├── interface-contract.md
|
||||
├── project-structure.md
|
||||
├── requirements/
|
||||
│ └── req-001_project-scaffold.md
|
||||
└── runtime-flow.md
|
||||
```
|
||||
## 애플리케이션 구현
|
||||
|
||||
`pyproject.toml`은 Python 3.11 이상, Textual 런타임 의존성, 선택적 테스트 의존성, 콘솔 진입점을 선언한다. `src/chat_tui/app.py`는 Textual 애플리케이션과 실행 함수를 제공하고, `tests/test_app.py`는 입력 표시와 종료 계약을 검증한다.
|
||||
- `src/chat_tui/app.py`
|
||||
- `RESPONSE_MESSAGES`: 수정하지 않는 고정 응답 목록
|
||||
- `ChatApp.on_input_submitted`: 입력 `trim`, 유효성 판정, 난수 응답 선택, 상태 갱신 및 대화 항목 표시
|
||||
- `ChatApp.submitted_inputs`: 정규화된 사용자 메시지 세션 상태
|
||||
- `ChatApp.selected_responses`: 메시지별 선택 응답 세션 상태
|
||||
- 기존 Textual 위젯 구성과 `Escape`, `Ctrl+Q` 종료 바인딩
|
||||
|
||||
응답 기능은 별도 서비스나 외부 모듈을 만들지 않고 현재 단일 TUI 애플리케이션의 제출 이벤트 처리에 배치한다. 난수 선택은 표준 라이브러리 `random.choice`를 직접 사용한다.
|
||||
|
||||
## 테스트
|
||||
|
||||
- `tests/test_app.py`
|
||||
- 사용자 메시지와 응답의 제출 순서 및 화면 순서
|
||||
- `random.choice` 결과 제어와 호출 인자
|
||||
- `trim`된 입력 저장
|
||||
- 공백 입력 무시
|
||||
- 입력창 초기화
|
||||
- `Escape` 종료 요청
|
||||
|
||||
테스트는 Textual의 `run_test()`를 사용해 실제 위젯 트리와 이벤트 흐름을 검증하고, 난수 선택만 `unittest.mock.patch`로 제어한다.
|
||||
|
||||
## 문서
|
||||
|
||||
- `docs/requirements/req-001_project-scaffold.md`: 기능 범위와 수용 기준
|
||||
- `docs/data-model.md`: 응답 목록과 세션 상태 모델
|
||||
- `docs/interface-contract.md`: 제출 이벤트와 화면 표시 계약
|
||||
- `docs/project-structure.md`: 기능 구성 요소와 책임
|
||||
- `docs/runtime-flow.md`: 실행 시나리오와 처리 순서
|
||||
|
||||
@@ -8,19 +8,37 @@ Python 3.11 이상과 표준 `pyproject.toml`을 사용하는 Textual 기반 TUI
|
||||
|
||||
1. `chat-tui` 콘솔 명령과 `python -m chat_tui` 실행을 지원한다.
|
||||
2. Textual 화면에 사용자 입력창과 대화 영역을 표시한다.
|
||||
3. 사용자가 입력을 제출하면 입력한 문자열을 세션 내 대화 영역에 순서대로 누적한다.
|
||||
4. 제출 뒤 입력창을 비운다.
|
||||
5. `Escape` 또는 `Ctrl+Q`로 애플리케이션 종료를 요청할 수 있다.
|
||||
6. 입력 표시와 종료 동작을 자동 테스트로 검증한다.
|
||||
3. 사용자가 입력을 제출하면 입력값을 `trim`한 뒤 비어 있지 않은 사용자 메시지를 제출 순서대로 대화 영역에 누적한다.
|
||||
4. 유효한 사용자 메시지 제출 후 고정된 다섯 개 응답 목록에서 표준 난수 선택으로 하나의 응답을 결정한다.
|
||||
5. 선택한 응답을 해당 사용자 메시지 바로 뒤에 별도의 대화 항목으로 표시한다.
|
||||
6. 유효한 입력 처리 뒤 입력창을 비운다.
|
||||
7. 빈 입력 또는 공백만 포함한 입력은 사용자 메시지와 응답을 대화 기록에 추가하지 않는다.
|
||||
8. `Escape` 또는 `Ctrl+Q`로 애플리케이션 종료를 요청할 수 있다.
|
||||
9. 입력 누적, 응답 선택과 표시, 공백 입력 무시, 입력창 초기화 및 종료 동작을 자동 테스트로 검증한다.
|
||||
|
||||
## 고정 응답 목록
|
||||
|
||||
응답은 다음 다섯 문구 중 하나를 사용한다.
|
||||
|
||||
- 안녕하세요! 무엇을 도와드릴까요?
|
||||
- 그것은 제가 처리하기 불가능합니다.
|
||||
- 맡겨만 주세요.
|
||||
- 지금 처리 중입니다.
|
||||
- 작업을 완료하였습니다.
|
||||
|
||||
응답 선택에는 표준 라이브러리의 난수 선택 기능을 사용하며, 외부 서비스나 LLM은 호출하지 않는다.
|
||||
|
||||
## 제외 범위
|
||||
|
||||
LLM 연동, 응답 생성, 외부 API 호출, 인증, 영구 대화 기록 저장은 구현하지 않는다.
|
||||
LLM 연동, 외부 API 호출, 인증, 영구 대화 기록 저장, 사용자별 응답 기록, 응답 목록 편집, 사용자 입력에 따른 맞춤형 응답 생성은 구현하지 않는다.
|
||||
|
||||
## 수용 기준
|
||||
|
||||
- Python 3.11 이상 환경에서 의존성을 설치할 수 있다.
|
||||
- TUI가 실행되고 입력창에 포커스가 배치된다.
|
||||
- 두 번 이상 제출한 입력이 제출 순서대로 모두 표시된다.
|
||||
- 빈 입력은 대화 기록에 추가되지 않는다.
|
||||
- 자동 테스트가 입력 누적 표시와 종료 요청을 검증한다.
|
||||
- 두 번 이상 제출한 입력이 `trim`된 값으로 제출 순서대로 표시된다.
|
||||
- 각 유효한 사용자 메시지 바로 뒤에 고정 목록에서 선택된 응답이 표시된다.
|
||||
- 자동 테스트에서 난수 선택 결과를 제어하고 선택 호출 및 표시 순서를 검증할 수 있다.
|
||||
- 빈 입력과 공백만 포함한 입력은 사용자 메시지, 응답 및 응답 선택 결과를 남기지 않는다.
|
||||
- 유효한 입력 제출 뒤 입력창이 비워진다.
|
||||
- `Escape` 및 `Ctrl+Q`가 애플리케이션 종료를 요청한다.
|
||||
|
||||
+36
-10
@@ -1,13 +1,39 @@
|
||||
# 실행 흐름
|
||||
# 런타임 흐름
|
||||
|
||||
## tui-input-loop
|
||||
## 정상 입력 시나리오
|
||||
|
||||
1. `chat-tui` 또는 `python -m chat_tui`가 `main()`을 호출한다.
|
||||
2. `ChatApp`이 Textual 이벤트 루프를 시작하고 화면을 구성한다.
|
||||
3. 입력창에 포커스를 둔 뒤 사용자의 입력을 기다린다.
|
||||
4. 사용자가 Enter를 누르면 `Input.Submitted` 이벤트를 처리한다.
|
||||
5. 비어 있지 않은 입력을 세션 목록에 추가하고 같은 값을 대화 영역의 새 `Label`로 마운트한다.
|
||||
6. 입력창을 비우고 대화 영역을 마지막 항목으로 스크롤한 뒤 다시 입력을 기다린다.
|
||||
7. 사용자가 `Escape` 또는 `Ctrl+Q`를 누르면 Textual 종료 동작을 호출한다.
|
||||
1. 애플리케이션이 `#user-input`에 포커스를 둔다.
|
||||
2. 사용자가 입력하고 Enter를 누른다.
|
||||
3. `Input.Submitted` 이벤트에서 입력값을 `strip()`한다.
|
||||
4. 입력창 값을 즉시 빈 문자열로 초기화한다.
|
||||
5. `trim` 결과가 유효하면 사용자 메시지를 세션 상태에 기록한다.
|
||||
6. `random.choice(RESPONSE_MESSAGES)`가 고정 응답 하나를 선택한다.
|
||||
7. 선택 응답을 세션 상태에 기록한다.
|
||||
8. 대화 영역에 사용자 메시지 `Label`을 추가한다.
|
||||
9. 사용자 메시지 바로 뒤에 응답 `Label`을 추가한다.
|
||||
10. 대화 영역을 마지막 항목으로 스크롤한다.
|
||||
|
||||
이 흐름에는 LLM 호출이나 외부 네트워크 요청이 없다. 애플리케이션 종료 뒤 세션 목록과 화면 항목은 보존되지 않는다.
|
||||
여러 번 제출하면 각 사용자 메시지와 응답 쌍이 제출 순서대로 누적된다.
|
||||
|
||||
## 공백 입력 시나리오
|
||||
|
||||
1. 사용자가 공백만 포함한 값을 제출한다.
|
||||
2. 입력값을 `strip()`한다.
|
||||
3. 결과가 빈 문자열임을 확인한다.
|
||||
4. 입력창은 비우되 상태, 난수 선택 결과 및 대화 영역은 변경하지 않는다.
|
||||
|
||||
## 종료 시나리오
|
||||
|
||||
- `Escape`를 누르면 기존 종료 바인딩이 실행된다.
|
||||
- `Ctrl+Q`를 누르면 기존 종료 바인딩이 실행된다.
|
||||
- 두 동작 모두 `quit` 액션을 통해 애플리케이션 종료를 요청한다.
|
||||
|
||||
## 검증 시나리오
|
||||
|
||||
자동 테스트는 결정 가능한 `random.choice` 결과를 사용해 다음을 확인한다.
|
||||
|
||||
- 입력 `trim` 결과가 사용자 메시지로 저장되고 표시되는지
|
||||
- 사용자 메시지 다음에 응답이 표시되는지
|
||||
- 공백 입력에서 `random.choice`가 호출되지 않는지
|
||||
- 입력창이 제출 후 비워지는지
|
||||
- 기존 종료 동작이 유지되는지
|
||||
|
||||
+22
-5
@@ -1,12 +1,23 @@
|
||||
"""사용자 입력을 세션 내 대화 영역에 표시하는 Textual 애플리케이션."""
|
||||
"""사용자 입력과 고정 무작위 응답을 표시하는 Textual 애플리케이션."""
|
||||
|
||||
import random
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import VerticalScroll
|
||||
from textual.widgets import Header, Input, Label
|
||||
|
||||
|
||||
RESPONSE_MESSAGES = (
|
||||
"안녕하세요! 무엇을 도와드릴까요?",
|
||||
"그것은 제가 처리하기 불가능합니다.",
|
||||
"맡겨만 주세요.",
|
||||
"지금 처리 중입니다.",
|
||||
"작업을 완료하였습니다.",
|
||||
)
|
||||
|
||||
|
||||
class ChatApp(App[None]):
|
||||
"""LLM 호출 없이 사용자의 제출 입력만 표시하는 초기 TUI."""
|
||||
"""사용자 메시지 뒤에 고정 목록에서 선택한 응답을 표시하는 TUI."""
|
||||
|
||||
CSS = """
|
||||
Screen {
|
||||
@@ -32,6 +43,7 @@ class ChatApp(App[None]):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.submitted_inputs: list[str] = []
|
||||
self.selected_responses: list[str] = []
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header(show_clock=False)
|
||||
@@ -45,15 +57,20 @@ class ChatApp(App[None]):
|
||||
self.query_one("#user-input", Input).focus()
|
||||
|
||||
def on_input_submitted(self, event: Input.Submitted) -> None:
|
||||
"""제출된 입력을 기록하고 대화 영역에 누적 표시한다."""
|
||||
value = event.value
|
||||
"""입력을 정규화하고 사용자 메시지와 무작위 응답을 표시한다."""
|
||||
value = event.value.strip()
|
||||
event.input.value = ""
|
||||
|
||||
if not value:
|
||||
return
|
||||
|
||||
response = random.choice(RESPONSE_MESSAGES)
|
||||
self.submitted_inputs.append(value)
|
||||
self.selected_responses.append(response)
|
||||
|
||||
conversation = self.query_one("#conversation", VerticalScroll)
|
||||
conversation.mount(Label(value, classes="user-message"))
|
||||
event.input.value = ""
|
||||
conversation.mount(Label(response, classes="response-message"))
|
||||
conversation.scroll_end(animate=False)
|
||||
|
||||
|
||||
|
||||
+62
-17
@@ -3,29 +3,73 @@ from unittest.mock import patch
|
||||
|
||||
from textual.widgets import Input, Label
|
||||
|
||||
from chat_tui.app import ChatApp
|
||||
from chat_tui.app import RESPONSE_MESSAGES, ChatApp
|
||||
|
||||
|
||||
class ChatAppTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_submitted_input_is_displayed_and_accumulated(self) -> None:
|
||||
async def test_submitted_input_and_response_are_displayed_in_order(self) -> None:
|
||||
app = ChatApp()
|
||||
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()
|
||||
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()
|
||||
input_widget.value = "두 번째 입력"
|
||||
await pilot.press("enter")
|
||||
await pilot.pause()
|
||||
|
||||
self.assertEqual(
|
||||
app.submitted_inputs,
|
||||
["첫 번째 입력", "두 번째 입력"],
|
||||
)
|
||||
displayed = [label.renderable for label in app.query("#conversation Label")]
|
||||
self.assertEqual(displayed, ["첫 번째 입력", "두 번째 입력"])
|
||||
self.assertEqual(input_widget.value, "")
|
||||
self.assertEqual(
|
||||
app.submitted_inputs,
|
||||
["첫 번째 입력", "두 번째 입력"],
|
||||
)
|
||||
self.assertEqual(
|
||||
app.selected_responses,
|
||||
[RESPONSE_MESSAGES[2], RESPONSE_MESSAGES[2]],
|
||||
)
|
||||
displayed = [
|
||||
label.renderable
|
||||
for label in app.query("#conversation Label")
|
||||
]
|
||||
self.assertEqual(
|
||||
displayed,
|
||||
[
|
||||
"첫 번째 입력",
|
||||
RESPONSE_MESSAGES[2],
|
||||
"두 번째 입력",
|
||||
RESPONSE_MESSAGES[2],
|
||||
],
|
||||
)
|
||||
self.assertEqual(input_widget.value, "")
|
||||
|
||||
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(len(app.query("#conversation Label")), 0)
|
||||
choice_mock.assert_not_called()
|
||||
|
||||
async def test_empty_input_is_not_added(self) -> None:
|
||||
app = ChatApp()
|
||||
@@ -34,6 +78,7 @@ class ChatAppTest(unittest.IsolatedAsyncioTestCase):
|
||||
await pilot.pause()
|
||||
|
||||
self.assertEqual(app.submitted_inputs, [])
|
||||
self.assertEqual(app.selected_responses, [])
|
||||
self.assertEqual(len(app.query("#conversation Label")), 0)
|
||||
|
||||
async def test_escape_requests_application_exit(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user