Skip to content
Open
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
20 changes: 10 additions & 10 deletions tests/widget/test_widget.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from typing import Dict, List, Union
from typing import Dict, List

import pytest
from pytest import param as case

from zulipterminal.api_types import PollOptionInfo, Submessage, TodoTaskInfo
from zulipterminal.widget import (
Submessage,
find_widget_type,
process_poll_widget,
process_todo_widget,
Expand Down Expand Up @@ -342,12 +342,12 @@ def test_find_widget_type(
def test_process_todo_widget(
submessages: List[Submessage],
expected_title: str,
expected_tasks: Dict[str, Dict[str, Union[str, bool]]],
expected_tasks: Dict[str, TodoTaskInfo],
) -> None:
title, tasks = process_todo_widget(submessages)
result = process_todo_widget(submessages)

assert title == expected_title
assert tasks == expected_tasks
assert result["title"] == expected_title
assert result["tasks"] == expected_tasks


@pytest.mark.parametrize(
Expand Down Expand Up @@ -657,9 +657,9 @@ def test_process_todo_widget(
def test_process_poll_widget(
submessages: List[Submessage],
expected_poll_question: str,
expected_options: Dict[str, Dict[str, Union[str, List[str]]]],
expected_options: Dict[str, PollOptionInfo],
) -> None:
poll_question, options = process_poll_widget(submessages)
result = process_poll_widget(submessages)

assert poll_question == expected_poll_question
assert options == expected_options
assert result["question"] == expected_poll_question
assert result["options"] == expected_options
109 changes: 108 additions & 1 deletion zulipterminal/api_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,113 @@ class SubscriptionSettingChange(TypedDict):

## TODO: Improve this typing to split private and stream message data

###############################################################################
# Submessage (widget) types
# Used in messages for polls, todos, and other interactive widgets


class Submessage(TypedDict):
type: Literal["submessage"]
msg_type: str
message_id: int
submessage_id: int
sender_id: int
content: str


# Widget content types for deserialized JSON content


class TodoTask(TypedDict):
task: str
desc: str


class TodoWidgetData(TypedDict):
widget_type: Literal["todo"]
extra_data: "TodoExtraData"


class TodoExtraData(TypedDict, total=False):
task_list_title: str
tasks: List[TodoTask]


class NewTodoTask(TypedDict):
type: Literal["new_task"]
key: int
task: str
desc: str
completed: bool


class TodoStrike(TypedDict):
type: Literal["strike"]
key: str


class NewTodoTitle(TypedDict):
type: Literal["new_task_list_title"]
title: str


class PollOption(TypedDict):
pass # Option text is just a string in the "options" list


class PollWidgetData(TypedDict):
widget_type: Literal["poll"]
extra_data: "PollExtraData"


class PollExtraData(TypedDict):
question: str
options: List[str]


class NewPollOption(TypedDict):
type: Literal["new_option"]
idx: int
option: str


class PollQuestion(TypedDict):
type: Literal["question"]
question: str


class PollVote(TypedDict):
type: Literal["vote"]
key: str
vote: Literal[1, -1]


# Return types for widget processing functions


class TodoTaskInfo(TypedDict):
task: str
desc: str
completed: bool


class TodoWidgetResult(TypedDict):
title: str
tasks: Dict[str, TodoTaskInfo]


class PollOptionInfo(TypedDict):
option: str
votes: List[int]


class PollWidgetResult(TypedDict):
question: str
options: Dict[str, PollOptionInfo]


###############################################################################


class Message(TypedDict, total=False):
id: int
Expand All @@ -195,7 +302,7 @@ class Message(TypedDict, total=False):
subject_links: List[str]
is_me_message: bool
reactions: List[Dict[str, Any]]
submessages: List[Dict[str, Any]]
submessages: List[Submessage]
flags: List[MessageFlag]
sender_full_name: str
sender_email: str
Expand Down
4 changes: 3 additions & 1 deletion zulipterminal/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,9 @@ def show_typing_notification(self) -> None:

# Until conversation becomes "inactive" like when a `stop` event is sent
while self.active_conversation_info:
sender_name = self.active_conversation_info["sender_name"]
sender_name = self.active_conversation_info.get("sender_name")
if not sender_name:
break
self.view.set_footer_text(
[
("footer_contrast", " " + sender_name + " "),
Expand Down
2 changes: 1 addition & 1 deletion zulipterminal/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1889,7 +1889,7 @@ def _handle_submessage_event(self, event: Event) -> None:
message = self.index["messages"][message_id]
message["submessages"].append(
{
"type": event["type"],
"type": "submessage",
"msg_type": event["msg_type"],
"message_id": event["message_id"],
"submessage_id": event["submessage_id"],
Expand Down
10 changes: 6 additions & 4 deletions zulipterminal/ui_tools/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,9 @@ def main_view(self) -> List[Any]:
widget_type = find_widget_type(self.message.get("submessages", []))

if widget_type == "todo":
title, tasks = process_todo_widget(self.message.get("submessages", []))
todo_result = process_todo_widget(self.message.get("submessages", []))
title = todo_result["title"]
tasks = todo_result["tasks"]

todo_widget = "<strong>To-do</strong>\n" + f"<strong>{title}</strong>"

Expand All @@ -758,9 +760,9 @@ def main_view(self) -> List[Any]:
self.message["content"] = todo_widget

elif widget_type == "poll":
poll_question, poll_options = process_poll_widget(
self.message.get("submessages", [])
)
poll_result = process_poll_widget(self.message.get("submessages", []))
poll_question = poll_result["question"]
poll_options = poll_result["options"]

# TODO: ZT doesn't yet support adding poll questions after the
# creation of the poll. So, if the poll question is not provided,
Expand Down
35 changes: 18 additions & 17 deletions zulipterminal/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,34 @@
"""

import json
from typing import Dict, List, Tuple, Union
from typing import Dict, List


Submessage = Dict[str, Union[int, str]]
from zulipterminal.api_types import (
PollOptionInfo,
PollWidgetResult,
Submessage,
TodoTaskInfo,
TodoWidgetResult,
)


def find_widget_type(submessages: List[Submessage]) -> str:
if submessages and "content" in submessages[0]:
content = submessages[0]["content"]

if isinstance(content, str):
try:
loaded_content = json.loads(content)
return loaded_content.get("widget_type", "unknown")
except json.JSONDecodeError:
return "unknown"
else:
try:
loaded_content = json.loads(content)
return loaded_content.get("widget_type", "unknown")
except json.JSONDecodeError:
return "unknown"
else:
return "unknown"


def process_todo_widget(
todo_list: List[Submessage],
) -> Tuple[str, Dict[str, Dict[str, Union[str, bool]]]]:
) -> TodoWidgetResult:
title = ""
tasks = {}
tasks: Dict[str, TodoTaskInfo] = {}

for entry in todo_list:
content = entry.get("content")
Expand Down Expand Up @@ -73,14 +74,14 @@ def process_todo_widget(
elif widget.get("type") == "new_task_list_title":
title = widget["title"]

return title, tasks
return {"title": title, "tasks": tasks}


def process_poll_widget(
poll_content: List[Submessage],
) -> Tuple[str, Dict[str, Dict[str, Union[str, List[str]]]]]:
) -> PollWidgetResult:
poll_question = ""
options = {}
options: Dict[str, PollOptionInfo] = {}

for entry in poll_content:
content = entry["content"]
Expand Down Expand Up @@ -115,4 +116,4 @@ def process_poll_widget(
option_id = f"{sender_id},{idx}"
options[option_id] = {"option": new_option, "votes": []}

return poll_question, options
return {"question": poll_question, "options": options}
Loading