Allow clearing To-do list item extended fields (#106208)
parent
c51ac7171a
commit
2497798b5d
|
@ -90,20 +90,6 @@ def _todo_item(resource: caldav.CalendarObjectResource) -> TodoItem | None:
|
|||
)
|
||||
|
||||
|
||||
def _to_ics_fields(item: TodoItem) -> dict[str, Any]:
|
||||
"""Convert a TodoItem to the set of add or update arguments."""
|
||||
item_data: dict[str, Any] = {}
|
||||
if summary := item.summary:
|
||||
item_data["summary"] = summary
|
||||
if status := item.status:
|
||||
item_data["status"] = TODO_STATUS_MAP_INV.get(status, "NEEDS-ACTION")
|
||||
if due := item.due:
|
||||
item_data["due"] = due
|
||||
if description := item.description:
|
||||
item_data["description"] = description
|
||||
return item_data
|
||||
|
||||
|
||||
class WebDavTodoListEntity(TodoListEntity):
|
||||
"""CalDAV To-do list entity."""
|
||||
|
||||
|
@ -140,9 +126,18 @@ class WebDavTodoListEntity(TodoListEntity):
|
|||
|
||||
async def async_create_todo_item(self, item: TodoItem) -> None:
|
||||
"""Add an item to the To-do list."""
|
||||
item_data: dict[str, Any] = {}
|
||||
if summary := item.summary:
|
||||
item_data["summary"] = summary
|
||||
if status := item.status:
|
||||
item_data["status"] = TODO_STATUS_MAP_INV.get(status, "NEEDS-ACTION")
|
||||
if due := item.due:
|
||||
item_data["due"] = due
|
||||
if description := item.description:
|
||||
item_data["description"] = description
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
partial(self._calendar.save_todo, **_to_ics_fields(item)),
|
||||
partial(self._calendar.save_todo, **item_data),
|
||||
)
|
||||
except (requests.ConnectionError, DAVError) as err:
|
||||
raise HomeAssistantError(f"CalDAV save error: {err}") from err
|
||||
|
@ -159,10 +154,17 @@ class WebDavTodoListEntity(TodoListEntity):
|
|||
except (requests.ConnectionError, DAVError) as err:
|
||||
raise HomeAssistantError(f"CalDAV lookup error: {err}") from err
|
||||
vtodo = todo.icalendar_component # type: ignore[attr-defined]
|
||||
updated_fields = _to_ics_fields(item)
|
||||
if "due" in updated_fields:
|
||||
todo.set_due(updated_fields.pop("due")) # type: ignore[attr-defined]
|
||||
vtodo.update(**updated_fields)
|
||||
vtodo["SUMMARY"] = item.summary or ""
|
||||
if status := item.status:
|
||||
vtodo["STATUS"] = TODO_STATUS_MAP_INV.get(status, "NEEDS-ACTION")
|
||||
if due := item.due:
|
||||
todo.set_due(due) # type: ignore[attr-defined]
|
||||
else:
|
||||
vtodo.pop("DUE", None)
|
||||
if description := item.description:
|
||||
vtodo["DESCRIPTION"] = description
|
||||
else:
|
||||
vtodo.pop("DESCRIPTION", None)
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
partial(
|
||||
|
|
|
@ -29,18 +29,20 @@ TODO_STATUS_MAP = {
|
|||
TODO_STATUS_MAP_INV = {v: k for k, v in TODO_STATUS_MAP.items()}
|
||||
|
||||
|
||||
def _convert_todo_item(item: TodoItem) -> dict[str, str]:
|
||||
def _convert_todo_item(item: TodoItem) -> dict[str, str | None]:
|
||||
"""Convert TodoItem dataclass items to dictionary of attributes the tasks API."""
|
||||
result: dict[str, str] = {}
|
||||
if item.summary is not None:
|
||||
result["title"] = item.summary
|
||||
result: dict[str, str | None] = {}
|
||||
result["title"] = item.summary
|
||||
if item.status is not None:
|
||||
result["status"] = TODO_STATUS_MAP_INV[item.status]
|
||||
else:
|
||||
result["status"] = TodoItemStatus.NEEDS_ACTION
|
||||
if (due := item.due) is not None:
|
||||
# due API field is a timestamp string, but with only date resolution
|
||||
result["due"] = dt_util.start_of_local_day(due).isoformat()
|
||||
if (description := item.description) is not None:
|
||||
result["notes"] = description
|
||||
else:
|
||||
result["due"] = None
|
||||
result["notes"] = item.description
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
"""A Local To-do todo platform."""
|
||||
|
||||
from collections.abc import Iterable
|
||||
import dataclasses
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from ical.calendar import Calendar
|
||||
from ical.calendar_stream import IcsCalendarStream
|
||||
from ical.exceptions import CalendarParseError
|
||||
from ical.store import TodoStore
|
||||
from ical.todo import Todo, TodoStatus
|
||||
|
||||
|
@ -59,26 +55,18 @@ async def async_setup_entry(
|
|||
async_add_entities([entity], True)
|
||||
|
||||
|
||||
def _todo_dict_factory(obj: Iterable[tuple[str, Any]]) -> dict[str, str]:
|
||||
"""Convert TodoItem dataclass items to dictionary of attributes for ical consumption."""
|
||||
result: dict[str, str] = {}
|
||||
for name, value in obj:
|
||||
if value is None:
|
||||
continue
|
||||
if name == "status":
|
||||
result[name] = ICS_TODO_STATUS_MAP_INV[value]
|
||||
else:
|
||||
result[name] = value
|
||||
return result
|
||||
|
||||
|
||||
def _convert_item(item: TodoItem) -> Todo:
|
||||
"""Convert a HomeAssistant TodoItem to an ical Todo."""
|
||||
try:
|
||||
return Todo(**dataclasses.asdict(item, dict_factory=_todo_dict_factory))
|
||||
except CalendarParseError as err:
|
||||
_LOGGER.debug("Error parsing todo input fields: %s (%s)", item, err)
|
||||
raise HomeAssistantError("Error parsing todo input fields") from err
|
||||
todo = Todo()
|
||||
if item.uid:
|
||||
todo.uid = item.uid
|
||||
if item.summary:
|
||||
todo.summary = item.summary
|
||||
if item.status:
|
||||
todo.status = ICS_TODO_STATUS_MAP_INV[item.status]
|
||||
todo.due = item.due
|
||||
todo.description = item.description
|
||||
return todo
|
||||
|
||||
|
||||
class LocalTodoListEntity(TodoListEntity):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""A shopping list todo platform."""
|
||||
|
||||
from typing import Any, cast
|
||||
from typing import cast
|
||||
|
||||
from homeassistant.components.todo import (
|
||||
TodoItem,
|
||||
|
@ -55,11 +55,10 @@ class ShoppingTodoListEntity(TodoListEntity):
|
|||
|
||||
async def async_update_todo_item(self, item: TodoItem) -> None:
|
||||
"""Update an item to the To-do list."""
|
||||
data: dict[str, Any] = {}
|
||||
if item.summary:
|
||||
data["name"] = item.summary
|
||||
if item.status:
|
||||
data["complete"] = item.status == TodoItemStatus.COMPLETED
|
||||
data = {
|
||||
"name": item.summary,
|
||||
"complete": item.status == TodoItemStatus.COMPLETED,
|
||||
}
|
||||
try:
|
||||
await self._data.async_update(item.uid, data)
|
||||
except NoMatchingShoppingListItem as err:
|
||||
|
|
|
@ -74,19 +74,19 @@ class TodoItemFieldDescription:
|
|||
TODO_ITEM_FIELDS = [
|
||||
TodoItemFieldDescription(
|
||||
service_field=ATTR_DUE_DATE,
|
||||
validation=cv.date,
|
||||
validation=vol.Any(cv.date, None),
|
||||
todo_item_field=ATTR_DUE,
|
||||
required_feature=TodoListEntityFeature.SET_DUE_DATE_ON_ITEM,
|
||||
),
|
||||
TodoItemFieldDescription(
|
||||
service_field=ATTR_DUE_DATETIME,
|
||||
validation=vol.All(cv.datetime, dt_util.as_local),
|
||||
validation=vol.Any(vol.All(cv.datetime, dt_util.as_local), None),
|
||||
todo_item_field=ATTR_DUE,
|
||||
required_feature=TodoListEntityFeature.SET_DUE_DATETIME_ON_ITEM,
|
||||
),
|
||||
TodoItemFieldDescription(
|
||||
service_field=ATTR_DESCRIPTION,
|
||||
validation=cv.string,
|
||||
validation=vol.Any(cv.string, None),
|
||||
todo_item_field=ATTR_DESCRIPTION,
|
||||
required_feature=TodoListEntityFeature.SET_DESCRIPTION_ON_ITEM,
|
||||
),
|
||||
|
@ -485,18 +485,22 @@ async def _async_update_todo_item(entity: TodoListEntity, call: ServiceCall) ->
|
|||
|
||||
_validate_supported_features(entity.supported_features, call.data)
|
||||
|
||||
await entity.async_update_todo_item(
|
||||
item=TodoItem(
|
||||
uid=found.uid,
|
||||
summary=call.data.get("rename"),
|
||||
status=call.data.get("status"),
|
||||
**{
|
||||
desc.todo_item_field: call.data[desc.service_field]
|
||||
for desc in TODO_ITEM_FIELDS
|
||||
if desc.service_field in call.data
|
||||
},
|
||||
)
|
||||
# Perform a partial update on the existing entity based on the fields
|
||||
# present in the update. This allows explicitly clearing any of the
|
||||
# extended fields present and set to None.
|
||||
updated_data = dataclasses.asdict(found)
|
||||
if summary := call.data.get("rename"):
|
||||
updated_data["summary"] = summary
|
||||
if status := call.data.get("status"):
|
||||
updated_data["status"] = status
|
||||
updated_data.update(
|
||||
{
|
||||
desc.todo_item_field: call.data[desc.service_field]
|
||||
for desc in TODO_ITEM_FIELDS
|
||||
if desc.service_field in call.data
|
||||
}
|
||||
)
|
||||
await entity.async_update_todo_item(item=TodoItem(**updated_data))
|
||||
|
||||
|
||||
async def _async_remove_todo_items(entity: TodoListEntity, call: ServiceCall) -> None:
|
||||
|
|
|
@ -34,19 +34,20 @@ async def async_setup_entry(
|
|||
|
||||
def _task_api_data(item: TodoItem) -> dict[str, Any]:
|
||||
"""Convert a TodoItem to the set of add or update arguments."""
|
||||
item_data: dict[str, Any] = {}
|
||||
if summary := item.summary:
|
||||
item_data["content"] = summary
|
||||
item_data: dict[str, Any] = {
|
||||
"content": item.summary,
|
||||
# Description needs to be empty string to be cleared
|
||||
"description": item.description or "",
|
||||
}
|
||||
if due := item.due:
|
||||
if isinstance(due, datetime.datetime):
|
||||
item_data["due"] = {
|
||||
"date": due.date().isoformat(),
|
||||
"datetime": due.isoformat(),
|
||||
}
|
||||
item_data["due_datetime"] = due.isoformat()
|
||||
else:
|
||||
item_data["due"] = {"date": due.isoformat()}
|
||||
if description := item.description:
|
||||
item_data["description"] = description
|
||||
item_data["due_date"] = due.isoformat()
|
||||
else:
|
||||
# Special flag "no date" clears the due date/datetime.
|
||||
# See https://developer.todoist.com/rest/v2/#update-a-task for more.
|
||||
item_data["due_string"] = "no date"
|
||||
return item_data
|
||||
|
||||
|
||||
|
@ -128,10 +129,16 @@ class TodoistTodoListEntity(CoordinatorEntity[TodoistCoordinator], TodoListEntit
|
|||
if update_data := _task_api_data(item):
|
||||
await self.coordinator.api.update_task(task_id=uid, **update_data)
|
||||
if item.status is not None:
|
||||
if item.status == TodoItemStatus.COMPLETED:
|
||||
await self.coordinator.api.close_task(task_id=uid)
|
||||
else:
|
||||
await self.coordinator.api.reopen_task(task_id=uid)
|
||||
# Only update status if changed
|
||||
for existing_item in self._attr_todo_items or ():
|
||||
if existing_item.uid != item.uid:
|
||||
continue
|
||||
|
||||
if item.status != existing_item.status:
|
||||
if item.status == TodoItemStatus.COMPLETED:
|
||||
await self.coordinator.api.close_task(task_id=uid)
|
||||
else:
|
||||
await self.coordinator.api.reopen_task(task_id=uid)
|
||||
await self.coordinator.async_refresh()
|
||||
|
||||
async def async_delete_todo_items(self, uids: list[str]) -> None:
|
||||
|
|
|
@ -69,6 +69,19 @@ STATUS:NEEDS-ACTION
|
|||
END:VTODO
|
||||
END:VCALENDAR"""
|
||||
|
||||
TODO_ALL_FIELDS = """BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//E-Corp.//CalDAV Client//EN
|
||||
BEGIN:VTODO
|
||||
UID:2
|
||||
DTSTAMP:20171125T000000Z
|
||||
SUMMARY:Cheese
|
||||
DESCRIPTION:Any kind will do
|
||||
STATUS:NEEDS-ACTION
|
||||
DUE:20171126
|
||||
END:VTODO
|
||||
END:VCALENDAR"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
|
@ -132,6 +145,18 @@ async def mock_add_to_hass(
|
|||
config_entry.add_to_hass(hass)
|
||||
|
||||
|
||||
IGNORE_COMPONENTS = ["BEGIN", "END", "DTSTAMP", "PRODID", "UID", "VERSION"]
|
||||
|
||||
|
||||
def compact_ics(ics: str) -> list[str]:
|
||||
"""Pull out parts of the rfc5545 content useful for assertions in tests."""
|
||||
return [
|
||||
line
|
||||
for line in ics.split("\n")
|
||||
if line and not any(filter(line.startswith, IGNORE_COMPONENTS))
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("todos", "expected_state"),
|
||||
[
|
||||
|
@ -292,45 +317,148 @@ async def test_add_item_failure(
|
|||
[
|
||||
(
|
||||
{"rename": "Swiss Cheese"},
|
||||
["SUMMARY:Swiss Cheese", "STATUS:NEEDS-ACTION"],
|
||||
[
|
||||
"DESCRIPTION:Any kind will do",
|
||||
"DUE;VALUE=DATE:20171126",
|
||||
"STATUS:NEEDS-ACTION",
|
||||
"SUMMARY:Swiss Cheese",
|
||||
],
|
||||
"1",
|
||||
{**RESULT_ITEM, "summary": "Swiss Cheese"},
|
||||
{
|
||||
"uid": "2",
|
||||
"summary": "Swiss Cheese",
|
||||
"status": "needs_action",
|
||||
"description": "Any kind will do",
|
||||
"due": "2017-11-26",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"status": "needs_action"},
|
||||
["SUMMARY:Cheese", "STATUS:NEEDS-ACTION"],
|
||||
[
|
||||
"DESCRIPTION:Any kind will do",
|
||||
"DUE;VALUE=DATE:20171126",
|
||||
"STATUS:NEEDS-ACTION",
|
||||
"SUMMARY:Cheese",
|
||||
],
|
||||
"1",
|
||||
RESULT_ITEM,
|
||||
{
|
||||
"uid": "2",
|
||||
"summary": "Cheese",
|
||||
"status": "needs_action",
|
||||
"description": "Any kind will do",
|
||||
"due": "2017-11-26",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"status": "completed"},
|
||||
["SUMMARY:Cheese", "STATUS:COMPLETED"],
|
||||
[
|
||||
"DESCRIPTION:Any kind will do",
|
||||
"DUE;VALUE=DATE:20171126",
|
||||
"STATUS:COMPLETED",
|
||||
"SUMMARY:Cheese",
|
||||
],
|
||||
"0",
|
||||
{**RESULT_ITEM, "status": "completed"},
|
||||
{
|
||||
"uid": "2",
|
||||
"summary": "Cheese",
|
||||
"status": "completed",
|
||||
"description": "Any kind will do",
|
||||
"due": "2017-11-26",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"rename": "Swiss Cheese", "status": "needs_action"},
|
||||
["SUMMARY:Swiss Cheese", "STATUS:NEEDS-ACTION"],
|
||||
[
|
||||
"DESCRIPTION:Any kind will do",
|
||||
"DUE;VALUE=DATE:20171126",
|
||||
"STATUS:NEEDS-ACTION",
|
||||
"SUMMARY:Swiss Cheese",
|
||||
],
|
||||
"1",
|
||||
{**RESULT_ITEM, "summary": "Swiss Cheese"},
|
||||
{
|
||||
"uid": "2",
|
||||
"summary": "Swiss Cheese",
|
||||
"status": "needs_action",
|
||||
"description": "Any kind will do",
|
||||
"due": "2017-11-26",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"due_date": "2023-11-18"},
|
||||
["SUMMARY:Cheese", "DUE;VALUE=DATE:20231118"],
|
||||
[
|
||||
"DESCRIPTION:Any kind will do",
|
||||
"DUE;VALUE=DATE:20231118",
|
||||
"STATUS:NEEDS-ACTION",
|
||||
"SUMMARY:Cheese",
|
||||
],
|
||||
"1",
|
||||
{**RESULT_ITEM, "due": "2023-11-18"},
|
||||
{
|
||||
"uid": "2",
|
||||
"summary": "Cheese",
|
||||
"status": "needs_action",
|
||||
"description": "Any kind will do",
|
||||
"due": "2023-11-18",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"due_datetime": "2023-11-18T08:30:00-06:00"},
|
||||
["SUMMARY:Cheese", "DUE;TZID=America/Regina:20231118T083000"],
|
||||
[
|
||||
"DESCRIPTION:Any kind will do",
|
||||
"DUE;TZID=America/Regina:20231118T083000",
|
||||
"STATUS:NEEDS-ACTION",
|
||||
"SUMMARY:Cheese",
|
||||
],
|
||||
"1",
|
||||
{**RESULT_ITEM, "due": "2023-11-18T08:30:00-06:00"},
|
||||
{
|
||||
"uid": "2",
|
||||
"summary": "Cheese",
|
||||
"status": "needs_action",
|
||||
"description": "Any kind will do",
|
||||
"due": "2023-11-18T08:30:00-06:00",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"due_datetime": None},
|
||||
[
|
||||
"DESCRIPTION:Any kind will do",
|
||||
"STATUS:NEEDS-ACTION",
|
||||
"SUMMARY:Cheese",
|
||||
],
|
||||
"1",
|
||||
{
|
||||
"uid": "2",
|
||||
"summary": "Cheese",
|
||||
"status": "needs_action",
|
||||
"description": "Any kind will do",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"description": "Make sure to get Swiss"},
|
||||
["SUMMARY:Cheese", "DESCRIPTION:Make sure to get Swiss"],
|
||||
[
|
||||
"DESCRIPTION:Make sure to get Swiss",
|
||||
"DUE;VALUE=DATE:20171126",
|
||||
"STATUS:NEEDS-ACTION",
|
||||
"SUMMARY:Cheese",
|
||||
],
|
||||
"1",
|
||||
{**RESULT_ITEM, "description": "Make sure to get Swiss"},
|
||||
{
|
||||
"uid": "2",
|
||||
"summary": "Cheese",
|
||||
"status": "needs_action",
|
||||
"due": "2017-11-26",
|
||||
"description": "Make sure to get Swiss",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"description": None},
|
||||
["DUE;VALUE=DATE:20171126", "STATUS:NEEDS-ACTION", "SUMMARY:Cheese"],
|
||||
"1",
|
||||
{
|
||||
"uid": "2",
|
||||
"summary": "Cheese",
|
||||
"status": "needs_action",
|
||||
"due": "2017-11-26",
|
||||
},
|
||||
),
|
||||
],
|
||||
ids=[
|
||||
|
@ -340,7 +468,9 @@ async def test_add_item_failure(
|
|||
"rename_status",
|
||||
"due_date",
|
||||
"due_datetime",
|
||||
"clear_due_date",
|
||||
"description",
|
||||
"clear_description",
|
||||
],
|
||||
)
|
||||
async def test_update_item(
|
||||
|
@ -355,7 +485,7 @@ async def test_update_item(
|
|||
) -> None:
|
||||
"""Test updating an item on the list."""
|
||||
|
||||
item = Todo(dav_client, None, TODO_NEEDS_ACTION, calendar, "2")
|
||||
item = Todo(dav_client, None, TODO_ALL_FIELDS, calendar, "2")
|
||||
calendar.search = MagicMock(return_value=[item])
|
||||
|
||||
await config_entry.async_setup(hass)
|
||||
|
@ -381,8 +511,7 @@ async def test_update_item(
|
|||
|
||||
assert dav_client.put.call_args
|
||||
ics = dav_client.put.call_args.args[1]
|
||||
for expected in expected_ics:
|
||||
assert expected in ics
|
||||
assert compact_ics(ics) == expected_ics
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
)
|
||||
# ---
|
||||
# name: test_create_todo_list_item[description].1
|
||||
'{"title": "Soda", "status": "needsAction", "notes": "6-pack"}'
|
||||
'{"title": "Soda", "status": "needsAction", "due": null, "notes": "6-pack"}'
|
||||
# ---
|
||||
# name: test_create_todo_list_item[due]
|
||||
tuple(
|
||||
|
@ -15,7 +15,7 @@
|
|||
)
|
||||
# ---
|
||||
# name: test_create_todo_list_item[due].1
|
||||
'{"title": "Soda", "status": "needsAction", "due": "2023-11-18T00:00:00-08:00"}'
|
||||
'{"title": "Soda", "status": "needsAction", "due": "2023-11-18T00:00:00-08:00", "notes": null}'
|
||||
# ---
|
||||
# name: test_create_todo_list_item[summary]
|
||||
tuple(
|
||||
|
@ -24,7 +24,7 @@
|
|||
)
|
||||
# ---
|
||||
# name: test_create_todo_list_item[summary].1
|
||||
'{"title": "Soda", "status": "needsAction"}'
|
||||
'{"title": "Soda", "status": "needsAction", "due": null, "notes": null}'
|
||||
# ---
|
||||
# name: test_delete_todo_list_item[_handler]
|
||||
tuple(
|
||||
|
@ -106,6 +106,24 @@
|
|||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_partial_update[clear_description]
|
||||
tuple(
|
||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||
'PATCH',
|
||||
)
|
||||
# ---
|
||||
# name: test_partial_update[clear_description].1
|
||||
'{"title": "Water", "status": "needsAction", "due": null, "notes": null}'
|
||||
# ---
|
||||
# name: test_partial_update[clear_due_date]
|
||||
tuple(
|
||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||
'PATCH',
|
||||
)
|
||||
# ---
|
||||
# name: test_partial_update[clear_due_date].1
|
||||
'{"title": "Water", "status": "needsAction", "due": null, "notes": null}'
|
||||
# ---
|
||||
# name: test_partial_update[description]
|
||||
tuple(
|
||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||
|
@ -113,7 +131,7 @@
|
|||
)
|
||||
# ---
|
||||
# name: test_partial_update[description].1
|
||||
'{"notes": "6-pack"}'
|
||||
'{"title": "Water", "status": "needsAction", "due": null, "notes": "At least one gallon"}'
|
||||
# ---
|
||||
# name: test_partial_update[due_date]
|
||||
tuple(
|
||||
|
@ -122,7 +140,16 @@
|
|||
)
|
||||
# ---
|
||||
# name: test_partial_update[due_date].1
|
||||
'{"due": "2023-11-18T00:00:00-08:00"}'
|
||||
'{"title": "Water", "status": "needsAction", "due": "2023-11-18T00:00:00-08:00", "notes": null}'
|
||||
# ---
|
||||
# name: test_partial_update[empty_description]
|
||||
tuple(
|
||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||
'PATCH',
|
||||
)
|
||||
# ---
|
||||
# name: test_partial_update[empty_description].1
|
||||
'{"title": "Water", "status": "needsAction", "due": null, "notes": ""}'
|
||||
# ---
|
||||
# name: test_partial_update[rename]
|
||||
tuple(
|
||||
|
@ -131,7 +158,7 @@
|
|||
)
|
||||
# ---
|
||||
# name: test_partial_update[rename].1
|
||||
'{"title": "Soda"}'
|
||||
'{"title": "Soda", "status": "needsAction", "due": null, "notes": null}'
|
||||
# ---
|
||||
# name: test_partial_update_status[api_responses0]
|
||||
tuple(
|
||||
|
@ -140,7 +167,7 @@
|
|||
)
|
||||
# ---
|
||||
# name: test_partial_update_status[api_responses0].1
|
||||
'{"status": "needsAction"}'
|
||||
'{"title": "Water", "status": "needsAction", "due": null, "notes": null}'
|
||||
# ---
|
||||
# name: test_update_todo_list_item[api_responses0]
|
||||
tuple(
|
||||
|
@ -149,5 +176,5 @@
|
|||
)
|
||||
# ---
|
||||
# name: test_update_todo_list_item[api_responses0].1
|
||||
'{"title": "Soda", "status": "completed"}'
|
||||
'{"title": "Soda", "status": "completed", "due": null, "notes": null}'
|
||||
# ---
|
||||
|
|
|
@ -48,6 +48,7 @@ LIST_TASKS_RESPONSE_WATER = {
|
|||
"id": "some-task-id",
|
||||
"title": "Water",
|
||||
"status": "needsAction",
|
||||
"description": "Any size is ok",
|
||||
"position": "00000000000000000001",
|
||||
},
|
||||
],
|
||||
|
@ -516,9 +517,19 @@ async def test_update_todo_list_item_error(
|
|||
[
|
||||
(UPDATE_API_RESPONSES, {"rename": "Soda"}),
|
||||
(UPDATE_API_RESPONSES, {"due_date": "2023-11-18"}),
|
||||
(UPDATE_API_RESPONSES, {"description": "6-pack"}),
|
||||
(UPDATE_API_RESPONSES, {"due_date": None}),
|
||||
(UPDATE_API_RESPONSES, {"description": "At least one gallon"}),
|
||||
(UPDATE_API_RESPONSES, {"description": ""}),
|
||||
(UPDATE_API_RESPONSES, {"description": None}),
|
||||
],
|
||||
ids=("rename", "due_date", "description"),
|
||||
ids=(
|
||||
"rename",
|
||||
"due_date",
|
||||
"clear_due_date",
|
||||
"description",
|
||||
"empty_description",
|
||||
"clear_description",
|
||||
),
|
||||
)
|
||||
async def test_partial_update(
|
||||
hass: HomeAssistant,
|
||||
|
|
|
@ -65,16 +65,27 @@ def set_time_zone(hass: HomeAssistant) -> None:
|
|||
hass.config.set_time_zone("America/Regina")
|
||||
|
||||
|
||||
EXPECTED_ADD_ITEM = {
|
||||
"status": "needs_action",
|
||||
"summary": "replace batteries",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("item_data", "expected_item_data"),
|
||||
[
|
||||
({}, {}),
|
||||
({"due_date": "2023-11-17"}, {"due": "2023-11-17"}),
|
||||
({}, EXPECTED_ADD_ITEM),
|
||||
({"due_date": "2023-11-17"}, {**EXPECTED_ADD_ITEM, "due": "2023-11-17"}),
|
||||
(
|
||||
{"due_datetime": "2023-11-17T11:30:00+00:00"},
|
||||
{"due": "2023-11-17T05:30:00-06:00"},
|
||||
{**EXPECTED_ADD_ITEM, "due": "2023-11-17T05:30:00-06:00"},
|
||||
),
|
||||
({"description": "Additional detail"}, {"description": "Additional detail"}),
|
||||
(
|
||||
{"description": "Additional detail"},
|
||||
{**EXPECTED_ADD_ITEM, "description": "Additional detail"},
|
||||
),
|
||||
({"description": ""}, {**EXPECTED_ADD_ITEM, "description": ""}),
|
||||
({"description": None}, EXPECTED_ADD_ITEM),
|
||||
],
|
||||
)
|
||||
async def test_add_item(
|
||||
|
@ -101,11 +112,10 @@ async def test_add_item(
|
|||
|
||||
items = await ws_get_items()
|
||||
assert len(items) == 1
|
||||
assert items[0]["summary"] == "replace batteries"
|
||||
assert items[0]["status"] == "needs_action"
|
||||
for k, v in expected_item_data.items():
|
||||
assert items[0][k] == v
|
||||
assert "uid" in items[0]
|
||||
item_data = items[0]
|
||||
assert "uid" in item_data
|
||||
del item_data["uid"]
|
||||
assert item_data == expected_item_data
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state
|
||||
|
@ -207,19 +217,29 @@ async def test_bulk_remove(
|
|||
assert state.state == "0"
|
||||
|
||||
|
||||
EXPECTED_UPDATE_ITEM = {
|
||||
"status": "needs_action",
|
||||
"summary": "soda",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("item_data", "expected_item_data", "expected_state"),
|
||||
[
|
||||
({"status": "completed"}, {"status": "completed"}, "0"),
|
||||
({"due_date": "2023-11-17"}, {"due": "2023-11-17"}, "1"),
|
||||
({"status": "completed"}, {**EXPECTED_UPDATE_ITEM, "status": "completed"}, "0"),
|
||||
(
|
||||
{"due_date": "2023-11-17"},
|
||||
{**EXPECTED_UPDATE_ITEM, "due": "2023-11-17"},
|
||||
"1",
|
||||
),
|
||||
(
|
||||
{"due_datetime": "2023-11-17T11:30:00+00:00"},
|
||||
{"due": "2023-11-17T05:30:00-06:00"},
|
||||
{**EXPECTED_UPDATE_ITEM, "due": "2023-11-17T05:30:00-06:00"},
|
||||
"1",
|
||||
),
|
||||
(
|
||||
{"description": "Additional detail"},
|
||||
{"description": "Additional detail"},
|
||||
{**EXPECTED_UPDATE_ITEM, "description": "Additional detail"},
|
||||
"1",
|
||||
),
|
||||
],
|
||||
|
@ -246,6 +266,7 @@ async def test_update_item(
|
|||
# Fetch item
|
||||
items = await ws_get_items()
|
||||
assert len(items) == 1
|
||||
|
||||
item = items[0]
|
||||
assert item["summary"] == "soda"
|
||||
assert item["status"] == "needs_action"
|
||||
|
@ -254,7 +275,7 @@ async def test_update_item(
|
|||
assert state
|
||||
assert state.state == "1"
|
||||
|
||||
# Mark item completed
|
||||
# Update item
|
||||
await hass.services.async_call(
|
||||
TODO_DOMAIN,
|
||||
"update_item",
|
||||
|
@ -268,14 +289,130 @@ async def test_update_item(
|
|||
assert len(items) == 1
|
||||
item = items[0]
|
||||
assert item["summary"] == "soda"
|
||||
for k, v in expected_item_data.items():
|
||||
assert items[0][k] == v
|
||||
assert "uid" in item
|
||||
del item["uid"]
|
||||
assert item == expected_item_data
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state
|
||||
assert state.state == expected_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("item_data", "expected_item_data"),
|
||||
[
|
||||
(
|
||||
{"status": "completed"},
|
||||
{
|
||||
"summary": "soda",
|
||||
"status": "completed",
|
||||
"description": "Additional detail",
|
||||
"due": "2024-01-01",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"due_date": "2024-01-02"},
|
||||
{
|
||||
"summary": "soda",
|
||||
"status": "needs_action",
|
||||
"description": "Additional detail",
|
||||
"due": "2024-01-02",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"due_date": None},
|
||||
{
|
||||
"summary": "soda",
|
||||
"status": "needs_action",
|
||||
"description": "Additional detail",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"due_datetime": "2024-01-01 10:30:00"},
|
||||
{
|
||||
"summary": "soda",
|
||||
"status": "needs_action",
|
||||
"description": "Additional detail",
|
||||
"due": "2024-01-01T10:30:00-06:00",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"due_datetime": None},
|
||||
{
|
||||
"summary": "soda",
|
||||
"status": "needs_action",
|
||||
"description": "Additional detail",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"description": "updated description"},
|
||||
{
|
||||
"summary": "soda",
|
||||
"status": "needs_action",
|
||||
"due": "2024-01-01",
|
||||
"description": "updated description",
|
||||
},
|
||||
),
|
||||
(
|
||||
{"description": None},
|
||||
{"summary": "soda", "status": "needs_action", "due": "2024-01-01"},
|
||||
),
|
||||
],
|
||||
ids=[
|
||||
"status",
|
||||
"due_date",
|
||||
"clear_due_date",
|
||||
"due_datetime",
|
||||
"clear_due_datetime",
|
||||
"description",
|
||||
"clear_description",
|
||||
],
|
||||
)
|
||||
async def test_update_existing_field(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: None,
|
||||
ws_get_items: Callable[[], Awaitable[dict[str, str]]],
|
||||
item_data: dict[str, Any],
|
||||
expected_item_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test updating a todo item."""
|
||||
|
||||
# Create new item
|
||||
await hass.services.async_call(
|
||||
TODO_DOMAIN,
|
||||
"add_item",
|
||||
{"item": "soda", "description": "Additional detail", "due_date": "2024-01-01"},
|
||||
target={"entity_id": TEST_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
# Fetch item
|
||||
items = await ws_get_items()
|
||||
assert len(items) == 1
|
||||
|
||||
item = items[0]
|
||||
assert item["summary"] == "soda"
|
||||
assert item["status"] == "needs_action"
|
||||
|
||||
# Perform update
|
||||
await hass.services.async_call(
|
||||
TODO_DOMAIN,
|
||||
"update_item",
|
||||
{"item": item["uid"], **item_data},
|
||||
target={"entity_id": TEST_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
# Verify item is updated
|
||||
items = await ws_get_items()
|
||||
assert len(items) == 1
|
||||
item = items[0]
|
||||
assert item["summary"] == "soda"
|
||||
assert "uid" in item
|
||||
del item["uid"]
|
||||
assert item == expected_item_data
|
||||
|
||||
|
||||
async def test_rename(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: None,
|
||||
|
|
|
@ -146,15 +146,19 @@ async def create_mock_platform(
|
|||
return config_entry
|
||||
|
||||
|
||||
@pytest.fixture(name="test_entity_items")
|
||||
def mock_test_entity_items() -> list[TodoItem]:
|
||||
"""Fixture that creates the items returned by the test entity."""
|
||||
return [
|
||||
TodoItem(summary="Item #1", uid="1", status=TodoItemStatus.NEEDS_ACTION),
|
||||
TodoItem(summary="Item #2", uid="2", status=TodoItemStatus.COMPLETED),
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(name="test_entity")
|
||||
def mock_test_entity() -> TodoListEntity:
|
||||
def mock_test_entity(test_entity_items: list[TodoItem]) -> TodoListEntity:
|
||||
"""Fixture that creates a test TodoList entity with mock service calls."""
|
||||
entity1 = MockTodoListEntity(
|
||||
[
|
||||
TodoItem(summary="Item #1", uid="1", status=TodoItemStatus.NEEDS_ACTION),
|
||||
TodoItem(summary="Item #2", uid="2", status=TodoItemStatus.COMPLETED),
|
||||
]
|
||||
)
|
||||
entity1 = MockTodoListEntity(test_entity_items)
|
||||
entity1.entity_id = "todo.entity1"
|
||||
entity1._attr_supported_features = (
|
||||
TodoListEntityFeature.CREATE_TODO_ITEM
|
||||
|
@ -504,7 +508,7 @@ async def test_update_todo_item_service_by_id_status_only(
|
|||
item = args.kwargs.get("item")
|
||||
assert item
|
||||
assert item.uid == "1"
|
||||
assert item.summary is None
|
||||
assert item.summary == "Item #1"
|
||||
assert item.status == TodoItemStatus.COMPLETED
|
||||
|
||||
|
||||
|
@ -530,7 +534,7 @@ async def test_update_todo_item_service_by_id_rename(
|
|||
assert item
|
||||
assert item.uid == "1"
|
||||
assert item.summary == "Updated item"
|
||||
assert item.status is None
|
||||
assert item.status == TodoItemStatus.NEEDS_ACTION
|
||||
|
||||
|
||||
async def test_update_todo_item_service_raises(
|
||||
|
@ -607,7 +611,7 @@ async def test_update_todo_item_service_by_summary_only_status(
|
|||
assert item
|
||||
assert item.uid == "1"
|
||||
assert item.summary == "Something else"
|
||||
assert item.status is None
|
||||
assert item.status == TodoItemStatus.NEEDS_ACTION
|
||||
|
||||
|
||||
async def test_update_todo_item_service_by_summary_not_found(
|
||||
|
@ -693,20 +697,32 @@ async def test_update_todo_item_field_unsupported(
|
|||
(
|
||||
TodoListEntityFeature.SET_DUE_DATE_ON_ITEM,
|
||||
{"due_date": "2023-11-13"},
|
||||
TodoItem(uid="1", due=datetime.date(2023, 11, 13)),
|
||||
TodoItem(
|
||||
uid="1",
|
||||
summary="Item #1",
|
||||
status=TodoItemStatus.NEEDS_ACTION,
|
||||
due=datetime.date(2023, 11, 13),
|
||||
),
|
||||
),
|
||||
(
|
||||
TodoListEntityFeature.SET_DUE_DATETIME_ON_ITEM,
|
||||
{"due_datetime": f"2023-11-13T17:00:00{TEST_OFFSET}"},
|
||||
TodoItem(
|
||||
uid="1",
|
||||
summary="Item #1",
|
||||
status=TodoItemStatus.NEEDS_ACTION,
|
||||
due=datetime.datetime(2023, 11, 13, 17, 0, 0, tzinfo=TEST_TIMEZONE),
|
||||
),
|
||||
),
|
||||
(
|
||||
TodoListEntityFeature.SET_DESCRIPTION_ON_ITEM,
|
||||
{"description": "Submit revised draft"},
|
||||
TodoItem(uid="1", description="Submit revised draft"),
|
||||
TodoItem(
|
||||
uid="1",
|
||||
summary="Item #1",
|
||||
status=TodoItemStatus.NEEDS_ACTION,
|
||||
description="Submit revised draft",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -736,6 +752,96 @@ async def test_update_todo_item_extended_fields(
|
|||
assert item == expected_update
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("test_entity_items", "update_data", "expected_update"),
|
||||
(
|
||||
(
|
||||
[TodoItem(uid="1", summary="Summary", description="description")],
|
||||
{"description": "Submit revised draft"},
|
||||
TodoItem(uid="1", summary="Summary", description="Submit revised draft"),
|
||||
),
|
||||
(
|
||||
[TodoItem(uid="1", summary="Summary", description="description")],
|
||||
{"description": ""},
|
||||
TodoItem(uid="1", summary="Summary", description=""),
|
||||
),
|
||||
(
|
||||
[TodoItem(uid="1", summary="Summary", description="description")],
|
||||
{"description": None},
|
||||
TodoItem(uid="1", summary="Summary"),
|
||||
),
|
||||
(
|
||||
[TodoItem(uid="1", summary="Summary", due=datetime.date(2024, 1, 1))],
|
||||
{"due_date": datetime.date(2024, 1, 2)},
|
||||
TodoItem(uid="1", summary="Summary", due=datetime.date(2024, 1, 2)),
|
||||
),
|
||||
(
|
||||
[TodoItem(uid="1", summary="Summary", due=datetime.date(2024, 1, 1))],
|
||||
{"due_date": None},
|
||||
TodoItem(uid="1", summary="Summary"),
|
||||
),
|
||||
(
|
||||
[TodoItem(uid="1", summary="Summary", due=datetime.date(2024, 1, 1))],
|
||||
{"due_datetime": datetime.datetime(2024, 1, 1, 10, 0, 0)},
|
||||
TodoItem(
|
||||
uid="1",
|
||||
summary="Summary",
|
||||
due=datetime.datetime(
|
||||
2024, 1, 1, 10, 0, 0, tzinfo=zoneinfo.ZoneInfo(key="America/Regina")
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
[
|
||||
TodoItem(
|
||||
uid="1",
|
||||
summary="Summary",
|
||||
due=datetime.datetime(2024, 1, 1, 10, 0, 0),
|
||||
)
|
||||
],
|
||||
{"due_datetime": None},
|
||||
TodoItem(uid="1", summary="Summary"),
|
||||
),
|
||||
),
|
||||
ids=[
|
||||
"overwrite_description",
|
||||
"overwrite_empty_description",
|
||||
"clear_description",
|
||||
"overwrite_due_date",
|
||||
"clear_due_date",
|
||||
"overwrite_due_date_with_time",
|
||||
"clear_due_date_time",
|
||||
],
|
||||
)
|
||||
async def test_update_todo_item_extended_fields_overwrite_existing_values(
|
||||
hass: HomeAssistant,
|
||||
test_entity: TodoListEntity,
|
||||
update_data: dict[str, Any],
|
||||
expected_update: TodoItem,
|
||||
) -> None:
|
||||
"""Test updating an item in a To-do list."""
|
||||
|
||||
test_entity._attr_supported_features |= (
|
||||
TodoListEntityFeature.SET_DESCRIPTION_ON_ITEM
|
||||
| TodoListEntityFeature.SET_DUE_DATE_ON_ITEM
|
||||
| TodoListEntityFeature.SET_DUE_DATETIME_ON_ITEM
|
||||
)
|
||||
await create_mock_platform(hass, [test_entity])
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
"update_item",
|
||||
{"item": "1", **update_data},
|
||||
target={"entity_id": "todo.entity1"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
args = test_entity.async_update_todo_item.call_args
|
||||
assert args
|
||||
item = args.kwargs.get("item")
|
||||
assert item == expected_update
|
||||
|
||||
|
||||
async def test_remove_todo_item_service_by_id(
|
||||
hass: HomeAssistant,
|
||||
test_entity: TodoListEntity,
|
||||
|
|
|
@ -80,7 +80,7 @@ async def test_todo_item_state(
|
|||
[],
|
||||
{},
|
||||
[make_api_task(id="task-id-1", content="Soda", is_completed=False)],
|
||||
{"content": "Soda"},
|
||||
{"content": "Soda", "due_string": "no date", "description": ""},
|
||||
{"uid": "task-id-1", "summary": "Soda", "status": "needs_action"},
|
||||
),
|
||||
(
|
||||
|
@ -94,7 +94,7 @@ async def test_todo_item_state(
|
|||
due=Due(is_recurring=False, date="2023-11-18", string="today"),
|
||||
)
|
||||
],
|
||||
{"due": {"date": "2023-11-18"}},
|
||||
{"description": "", "due_date": "2023-11-18"},
|
||||
{
|
||||
"uid": "task-id-1",
|
||||
"summary": "Soda",
|
||||
|
@ -119,7 +119,8 @@ async def test_todo_item_state(
|
|||
)
|
||||
],
|
||||
{
|
||||
"due": {"date": "2023-11-18", "datetime": "2023-11-18T06:30:00-06:00"},
|
||||
"description": "",
|
||||
"due_datetime": "2023-11-18T06:30:00-06:00",
|
||||
},
|
||||
{
|
||||
"uid": "task-id-1",
|
||||
|
@ -139,7 +140,7 @@ async def test_todo_item_state(
|
|||
is_completed=False,
|
||||
)
|
||||
],
|
||||
{"description": "6-pack"},
|
||||
{"description": "6-pack", "due_string": "no date"},
|
||||
{
|
||||
"uid": "task-id-1",
|
||||
"summary": "Soda",
|
||||
|
@ -264,11 +265,35 @@ async def test_update_todo_item_status(
|
|||
("tasks", "update_data", "tasks_after_update", "update_kwargs", "expected_item"),
|
||||
[
|
||||
(
|
||||
[make_api_task(id="task-id-1", content="Soda", is_completed=False)],
|
||||
[
|
||||
make_api_task(
|
||||
id="task-id-1",
|
||||
content="Soda",
|
||||
is_completed=False,
|
||||
description="desc",
|
||||
)
|
||||
],
|
||||
{"rename": "Milk"},
|
||||
[make_api_task(id="task-id-1", content="Milk", is_completed=False)],
|
||||
{"task_id": "task-id-1", "content": "Milk"},
|
||||
{"uid": "task-id-1", "summary": "Milk", "status": "needs_action"},
|
||||
[
|
||||
make_api_task(
|
||||
id="task-id-1",
|
||||
content="Milk",
|
||||
is_completed=False,
|
||||
description="desc",
|
||||
)
|
||||
],
|
||||
{
|
||||
"task_id": "task-id-1",
|
||||
"content": "Milk",
|
||||
"description": "desc",
|
||||
"due_string": "no date",
|
||||
},
|
||||
{
|
||||
"uid": "task-id-1",
|
||||
"summary": "Milk",
|
||||
"status": "needs_action",
|
||||
"description": "desc",
|
||||
},
|
||||
),
|
||||
(
|
||||
[make_api_task(id="task-id-1", content="Soda", is_completed=False)],
|
||||
|
@ -281,7 +306,12 @@ async def test_update_todo_item_status(
|
|||
due=Due(is_recurring=False, date="2023-11-18", string="today"),
|
||||
)
|
||||
],
|
||||
{"task_id": "task-id-1", "due": {"date": "2023-11-18"}},
|
||||
{
|
||||
"task_id": "task-id-1",
|
||||
"content": "Soda",
|
||||
"due_date": "2023-11-18",
|
||||
"description": "",
|
||||
},
|
||||
{
|
||||
"uid": "task-id-1",
|
||||
"summary": "Soda",
|
||||
|
@ -307,7 +337,9 @@ async def test_update_todo_item_status(
|
|||
],
|
||||
{
|
||||
"task_id": "task-id-1",
|
||||
"due": {"date": "2023-11-18", "datetime": "2023-11-18T06:30:00-06:00"},
|
||||
"content": "Soda",
|
||||
"due_datetime": "2023-11-18T06:30:00-06:00",
|
||||
"description": "",
|
||||
},
|
||||
{
|
||||
"uid": "task-id-1",
|
||||
|
@ -327,7 +359,12 @@ async def test_update_todo_item_status(
|
|||
is_completed=False,
|
||||
)
|
||||
],
|
||||
{"task_id": "task-id-1", "description": "6-pack"},
|
||||
{
|
||||
"task_id": "task-id-1",
|
||||
"content": "Soda",
|
||||
"description": "6-pack",
|
||||
"due_string": "no date",
|
||||
},
|
||||
{
|
||||
"uid": "task-id-1",
|
||||
"summary": "Soda",
|
||||
|
@ -335,8 +372,38 @@ async def test_update_todo_item_status(
|
|||
"description": "6-pack",
|
||||
},
|
||||
),
|
||||
(
|
||||
[
|
||||
make_api_task(
|
||||
id="task-id-1",
|
||||
content="Soda",
|
||||
description="6-pack",
|
||||
is_completed=False,
|
||||
)
|
||||
],
|
||||
{"description": None},
|
||||
[
|
||||
make_api_task(
|
||||
id="task-id-1",
|
||||
content="Soda",
|
||||
is_completed=False,
|
||||
description="",
|
||||
)
|
||||
],
|
||||
{
|
||||
"task_id": "task-id-1",
|
||||
"content": "Soda",
|
||||
"description": "",
|
||||
"due_string": "no date",
|
||||
},
|
||||
{
|
||||
"uid": "task-id-1",
|
||||
"summary": "Soda",
|
||||
"status": "needs_action",
|
||||
},
|
||||
),
|
||||
],
|
||||
ids=["rename", "due_date", "due_datetime", "description"],
|
||||
ids=["rename", "due_date", "due_datetime", "description", "clear_description"],
|
||||
)
|
||||
async def test_update_todo_items(
|
||||
hass: HomeAssistant,
|
||||
|
|
Loading…
Reference in New Issue