From 378a708bf7967edcc0c7e5226dfbc3a33a87d2b9 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 24 Nov 2023 03:54:49 -0800 Subject: [PATCH] Only show Google Tasks that are parents and fix ordering (#103820) --- homeassistant/components/google_tasks/todo.py | 17 ++- .../google_tasks/snapshots/test_todo.ambr | 24 ++++ tests/components/google_tasks/test_todo.py | 113 ++++++++++++++++-- 3 files changed, 145 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/google_tasks/todo.py b/homeassistant/components/google_tasks/todo.py index 01ceb0349e6..e5c90523a18 100644 --- a/homeassistant/components/google_tasks/todo.py +++ b/homeassistant/components/google_tasks/todo.py @@ -2,7 +2,7 @@ from __future__ import annotations from datetime import timedelta -from typing import cast +from typing import Any, cast from homeassistant.components.todo import ( TodoItem, @@ -96,7 +96,7 @@ class GoogleTaskTodoListEntity( item.get("status"), TodoItemStatus.NEEDS_ACTION # type: ignore[arg-type] ), ) - for item in self.coordinator.data + for item in _order_tasks(self.coordinator.data) ] async def async_create_todo_item(self, item: TodoItem) -> None: @@ -121,3 +121,16 @@ class GoogleTaskTodoListEntity( """Delete To-do items.""" await self.coordinator.api.delete(self._task_list_id, uids) await self.coordinator.async_refresh() + + +def _order_tasks(tasks: list[dict[str, Any]]) -> list[dict[str, Any]]: + """Order the task items response. + + All tasks have an order amongst their sibblings based on position. + + Home Assistant To-do items do not support the Google Task parent/sibbling + relationships and the desired behavior is for them to be filtered. + """ + parents = [task for task in tasks if task.get("parent") is None] + parents.sort(key=lambda task: task["position"]) + return parents diff --git a/tests/components/google_tasks/snapshots/test_todo.ambr b/tests/components/google_tasks/snapshots/test_todo.ambr index 98b59b7697b..73289b313d9 100644 --- a/tests/components/google_tasks/snapshots/test_todo.ambr +++ b/tests/components/google_tasks/snapshots/test_todo.ambr @@ -14,6 +14,30 @@ 'POST', ) # --- +# name: test_parent_child_ordering[api_responses0] + list([ + dict({ + 'status': 'needs_action', + 'summary': 'Task 1', + 'uid': 'task-1', + }), + dict({ + 'status': 'needs_action', + 'summary': 'Task 2', + 'uid': 'task-2', + }), + dict({ + 'status': 'needs_action', + 'summary': 'Task 3 (Parent)', + 'uid': 'task-3', + }), + dict({ + 'status': 'needs_action', + 'summary': 'Task 4', + 'uid': 'task-4', + }), + ]) +# --- # name: test_partial_update_status[api_responses0] tuple( 'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json', diff --git a/tests/components/google_tasks/test_todo.py b/tests/components/google_tasks/test_todo.py index 70309e64222..8b0b49ee109 100644 --- a/tests/components/google_tasks/test_todo.py +++ b/tests/components/google_tasks/test_todo.py @@ -45,14 +45,34 @@ BOUNDARY = "batch_00972cc8-75bd-11ee-9692-0242ac110002" # Arbitrary uuid LIST_TASKS_RESPONSE_WATER = { "items": [ - {"id": "some-task-id", "title": "Water", "status": "needsAction"}, + { + "id": "some-task-id", + "title": "Water", + "status": "needsAction", + "position": "00000000000000000001", + }, ], } LIST_TASKS_RESPONSE_MULTIPLE = { "items": [ - {"id": "some-task-id-1", "title": "Water", "status": "needsAction"}, - {"id": "some-task-id-2", "title": "Milk", "status": "needsAction"}, - {"id": "some-task-id-3", "title": "Cheese", "status": "needsAction"}, + { + "id": "some-task-id-2", + "title": "Milk", + "status": "needsAction", + "position": "00000000000000000002", + }, + { + "id": "some-task-id-1", + "title": "Water", + "status": "needsAction", + "position": "00000000000000000001", + }, + { + "id": "some-task-id-3", + "title": "Cheese", + "status": "needsAction", + "position": "00000000000000000003", + }, ], } @@ -182,8 +202,18 @@ def mock_http_response(response_handler: list | Callable) -> Mock: LIST_TASK_LIST_RESPONSE, { "items": [ - {"id": "task-1", "title": "Task 1", "status": "needsAction"}, - {"id": "task-2", "title": "Task 2", "status": "completed"}, + { + "id": "task-1", + "title": "Task 1", + "status": "needsAction", + "position": "0000000000000001", + }, + { + "id": "task-2", + "title": "Task 2", + "status": "completed", + "position": "0000000000000002", + }, ], }, ] @@ -541,7 +571,7 @@ async def test_partial_update_status( LIST_TASK_LIST_RESPONSE, LIST_TASKS_RESPONSE_MULTIPLE, [EMPTY_RESPONSE, EMPTY_RESPONSE, EMPTY_RESPONSE], # Delete batch - LIST_TASKS_RESPONSE, # refresh after create + LIST_TASKS_RESPONSE, # refresh after delete ] ) ) @@ -697,3 +727,72 @@ async def test_delete_server_error( target={"entity_id": "todo.my_tasks"}, blocking=True, ) + + +@pytest.mark.parametrize( + "api_responses", + [ + [ + LIST_TASK_LIST_RESPONSE, + { + "items": [ + { + "id": "task-3-2", + "title": "Child 2", + "status": "needsAction", + "parent": "task-3", + "position": "0000000000000002", + }, + { + "id": "task-3", + "title": "Task 3 (Parent)", + "status": "needsAction", + "position": "0000000000000003", + }, + { + "id": "task-2", + "title": "Task 2", + "status": "needsAction", + "position": "0000000000000002", + }, + { + "id": "task-1", + "title": "Task 1", + "status": "needsAction", + "position": "0000000000000001", + }, + { + "id": "task-3-1", + "title": "Child 1", + "status": "needsAction", + "parent": "task-3", + "position": "0000000000000001", + }, + { + "id": "task-4", + "title": "Task 4", + "status": "needsAction", + "position": "0000000000000004", + }, + ], + }, + ] + ], +) +async def test_parent_child_ordering( + hass: HomeAssistant, + setup_credentials: None, + integration_setup: Callable[[], Awaitable[bool]], + ws_get_items: Callable[[], Awaitable[dict[str, str]]], + snapshot: SnapshotAssertion, +) -> None: + """Test getting todo list items.""" + + assert await integration_setup() + + state = hass.states.get("todo.my_tasks") + assert state + assert state.state == "4" + + items = await ws_get_items() + assert items == snapshot