diff --git a/README.md b/README.md
index 4fd85a6..f2acff6 100644
--- a/README.md
+++ b/README.md
@@ -86,6 +86,150 @@ use the built-in services to add, remove and update items from your synchronized
- `todo.remove_item`
- `todo.update_item`
+## Events
+
+Google recently removed third party integrations from Google Assistant. Now you can re-create these integrations.
+
+When a new item is added to a synced list via Google Assistant or from Google Keep, an `add_item` service call event will be triggered. This allows Home Assistant to pick up new items from Google Keep and sync them with third party systems such as Trello, Bring, Anylist etc.
+
+This plugin extends Home Assistant's events so that the `add_item` service call is fired regardless of where the new item was added. The `origin` field in the event will be `REMOTE` if the item was added remotely to Google Keep, or `LOCAL` if it was added within Home Assistant.
+
+Note: Only new items that are not completed at the time of syncing will trigger the event.
+
+Below are some examples of how to do this, click to expand.
+
+
+Sync Google Todo List with Trello via email
+
+1. Install and configure this plugin
+
+1. Install and configure the Anylist plugin
+
+1. Create an email notify service in Home assistant
+
+ 1. Create a new [app password](https://myaccount.google.com/apppasswords>) in your Gmail.
+
+ 1. Setup [creating cards by email]([https://support.atlassian.com/trello/docs/creating-cards-by-email/) in Trello
+
+ 1. Add the following to your config.yaml file.
+
+ ```yaml
+ notify:
+ - name: "Email to Trello Todo"
+ platform: smtp
+ server: "smtp.gmail.com"
+ port: 587
+ timeout: 15
+ encryption: starttls
+ sender: "your_email@gmail.com"
+ username: "your_email@gmail.com"
+ password: "app password"
+ sender_name: "your name"
+ recipient: "your_cards_by_email_address@boards.trello.com"
+ ```
+
+1. Create an Automation in Home Assistant:
+
+ ```yaml
+ alias: Google Todo List
+ description: Send new items added to Google's Todo List to Trello
+ trigger:
+ - platform: event
+ event_type: call_service
+ event_data:
+ domain: todo
+ service: add_item
+ variables:
+ item_name: "{{ trigger.event.data.service_data.item }}"
+ list_name: "{{state_attr((trigger.event.data.service_data.entity_id)[0],'friendly_name')}}"
+ list_entity_id: "{{ (trigger.event.data.service_data.entity_id)[0] }}"
+ origin: "{{ trigger.event.origin }}"
+ condition:
+ # Update this to the name of your To-do list in Home Assistant.
+ - condition: template
+ value_template: "{{ list_entity_id == 'todo.google_keep_to_do' }}"
+ action:
+ # Optional: Send a notification of new item in HA.
+ - service: notify.persistent_notification
+ data:
+ message: "'{{item_name}}' was just added to the '{{list_name}}' list."
+ # Call Home Assistant Notify service to send item to Trello Board.
+ - service: notify.email_to_trello_todo
+ data:
+ title: "{{item_name}}"
+ message:
+ # Complete item from google shopping list. Can also call todo.remove_item to delete it from the list.
+ # Update entity_id to the id of your google list in Home Assistant.
+ - service: todo.update_item
+ target:
+ entity_id: todo.google_keep_to_do
+ data:
+ status: completed
+ item: "{{item_name}}"
+ # The maximum number of updates you want to process each update. If you make frequent changes, increase this number.
+ mode: parallel
+ max: 50
+ ```
+
+
+
+
+Sync Google Shopping List with Anylist
+
+The same process works for Bring Shopping list or any other integrated list to Home Assistant.
+
+1. Install and configure this plugin
+
+1. Install and configure the Anylist plugin
+
+1. Create an Automation in Home Assistant:
+
+ ```yaml
+ alias: Google Shopping List
+ description: Sync Google Shopping List with Anylist
+ trigger:
+ - platform: event
+ event_type: call_service
+ event_data:
+ domain: todo
+ service: add_item
+ variables:
+ item_name: "{{ trigger.event.data.service_data.item }}"
+ list_name: "{{state_attr((trigger.event.data.service_data.entity_id)[0],'friendly_name')}}"
+ list_entity_id: "{{ (trigger.event.data.service_data.entity_id)[0] }}"
+ origin: "{{ trigger.event.origin }}"
+ condition:
+ # Update this to the name of your To-do list in Home Assistant.
+ - condition: template
+ value_template: "{{ list_entity_id == 'todo.google_keep_to_do' }}"
+ action:
+ # Optional: Send a notification of new item in Home Assistant.
+ - service: notify.persistent_notification
+ data:
+ message: "'{{item_name}}' was just added to the '{{list_name}}' list."
+ # Add new item to your Anylist list
+ # Update the entity_id list name to your list in Home Assistant.
+ - service: todo.add_item
+ data:
+ item: "{{item_name}}"
+ target:
+ entity_id: todo.anylist_alexa_shopping_list
+ enabled: true
+ # Complete item from google shopping list. Can also call todo.remove_item to delete it from the list
+ # Update entity_id to the id of your google list in Home Assistant.
+ - service: todo.update_item
+ target:
+ entity_id: todo.google_keep_shopping_list
+ data:
+ status: completed
+ item: "{{item_name}}"
+ # The maximum number of updates you want to process each update. If you make frequent changes, increase this number.
+ mode: parallel
+ max: 50
+ ```
+
+
+
## Limitations
- **Polling Interval**: While changes made in Home Assistant are instantly reflected in Google Keep, changes made in Google Keep are not instantly reflected in Home Assistant. The integration polls Google Keep for updates every 15 minutes. Therefore, any changes made directly in Google Keep will be visible in Home Assistant after the next polling interval.
@@ -116,10 +260,10 @@ To generate a token:
1. In a environment with Docker installed, enter the following commands.
- ```bash
- docker pull breph/ha-google-home_get-token:latest
- docker run -it -d breph/ha-google-home_get-token
- ```
+ ```bash
+ docker pull breph/ha-google-home_get-token:latest
+ docker run -it -d breph/ha-google-home_get-token
+ ```
2. Copy the returned container ID to use in the following command.
diff --git a/custom_components/google_keep_sync/__init__.py b/custom_components/google_keep_sync/__init__.py
index 55c08c9..61b4b73 100644
--- a/custom_components/google_keep_sync/__init__.py
+++ b/custom_components/google_keep_sync/__init__.py
@@ -3,15 +3,14 @@
from __future__ import annotations
import logging
-from datetime import timedelta
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
-from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .api import GoogleKeepAPI
from .const import DOMAIN
+from .coordinator import GoogleKeepSyncCoordinator
PLATFORMS: list[Platform] = [Platform.TODO]
@@ -28,42 +27,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_LOGGER.error("Failed to authenticate Google Keep API")
return False # Exit early if authentication fails
- # Define the update method for the coordinator
- async def async_update_data():
- """Fetch data from API."""
- try:
- # Directly call the async_sync_data method
- lists_to_sync = entry.data.get("lists_to_sync", [])
-
- # Check if we need to sort the lists
- auto_sort = entry.data.get("list_auto_sort", False)
-
- return await api.async_sync_data(lists_to_sync, auto_sort)
- except Exception as error:
- raise UpdateFailed(f"Error communicating with API: {error}") from error
-
# Create the coordinator
- coordinator = DataUpdateCoordinator(
- hass,
- _LOGGER,
- name="Google Keep",
- update_method=async_update_data,
- update_interval=timedelta(minutes=15),
- )
+ coordinator = GoogleKeepSyncCoordinator(hass, api, entry)
# Start the data update coordinator
- await coordinator.async_refresh()
+ await coordinator.async_config_entry_first_refresh()
- # Store the API and coordinator objects in hass.data
- hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
- "api": api,
- "coordinator": coordinator,
- }
+ # Store the coordinator object in hass.data
+ hass.data.setdefault(DOMAIN, {})
+ hass.data[DOMAIN][entry.entry_id] = coordinator
# Forward the setup to the todo platform
- hass.async_create_task(
- hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
- )
+ await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
diff --git a/custom_components/google_keep_sync/const.py b/custom_components/google_keep_sync/const.py
index 4032325..b27a5c2 100644
--- a/custom_components/google_keep_sync/const.py
+++ b/custom_components/google_keep_sync/const.py
@@ -1,3 +1,6 @@
"""Constants for the Google Keep Sync integration."""
+from datetime import timedelta
+
DOMAIN = "google_keep_sync"
+SCAN_INTERVAL = timedelta(minutes=15)
diff --git a/custom_components/google_keep_sync/coordinator.py b/custom_components/google_keep_sync/coordinator.py
new file mode 100644
index 0000000..a2754d3
--- /dev/null
+++ b/custom_components/google_keep_sync/coordinator.py
@@ -0,0 +1,144 @@
+"""DataUpdateCoordinator for the Google Keep Sync component."""
+
+import logging
+from collections import namedtuple
+
+from gkeepapi.node import List as GKeepList
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import EVENT_CALL_SERVICE, Platform
+from homeassistant.core import EventOrigin, HomeAssistant
+from homeassistant.helpers import entity_registry
+from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
+
+from .api import GoogleKeepAPI
+from .const import DOMAIN, SCAN_INTERVAL
+
+_LOGGER = logging.getLogger(__name__)
+TodoItem = namedtuple("TodoItem", ["summary", "checked"])
+TodoList = namedtuple("TodoList", ["name", "items"])
+TodoItemData = namedtuple("TodoItemData", ["item", "entity_id"])
+
+
+class GoogleKeepSyncCoordinator(DataUpdateCoordinator[list[GKeepList]]):
+ """Coordinator for updating task data from Google Keep."""
+
+ def __init__(
+ self,
+ hass: HomeAssistant,
+ api: GoogleKeepAPI,
+ entry: ConfigEntry,
+ ) -> None:
+ """Initialize the Google Keep Todo coordinator."""
+ super().__init__(
+ hass,
+ _LOGGER,
+ name=DOMAIN,
+ update_interval=SCAN_INTERVAL,
+ )
+ self.api = api
+ self.config_entry = entry
+
+ # Define the update method for the coordinator
+ async def _async_update_data(self) -> list[GKeepList]:
+ """Fetch data from API."""
+ try:
+ # save lists prior to syncing
+ original_lists = await self._parse_gkeep_data_dict()
+
+ # Sync data with Google Keep
+ lists_to_sync = self.config_entry.data.get("lists_to_sync", [])
+ auto_sort = self.config_entry.data.get("list_auto_sort", False)
+ result = await self.api.async_sync_data(lists_to_sync, auto_sort)
+
+ # save lists after syncing
+ updated_lists = await self._parse_gkeep_data_dict()
+ # compare both list for changes, and fire event for changes
+ new_items = await self._get_new_items_added(
+ original_lists,
+ updated_lists,
+ )
+ await self._notify_new_items(new_items)
+
+ return result
+ except Exception as error:
+ raise UpdateFailed(f"Error communicating with API: {error}") from error
+
+ async def _parse_gkeep_data_dict(self) -> dict[str, TodoList]:
+ """Parse unchecked gkeep data to a dictionary, with the list id as the key."""
+ all_keep_lists = {}
+
+ # for each list
+ for keep_list in self.data or []:
+ # get all the unchecked items only
+ items = {
+ item.id: TodoItem(summary=item.text, checked=item.checked)
+ for item in keep_list.items
+ if not item.checked
+ }
+ all_keep_lists[keep_list.id] = TodoList(name=keep_list.title, items=items)
+ return all_keep_lists
+
+ async def _get_new_items_added(
+ self,
+ original_lists: dict[str, TodoList],
+ updated_lists: dict[str, TodoList],
+ ) -> list[TodoItemData]:
+ """Compare original and updated lists to find new TodoItems.
+
+ For each new TodoItem found, call the provided on_new_item callback.
+
+ :param original_lists: The original todo list data.
+ :param updated_lists: The updated todo list data.
+ # :param on_new_item: Callback function to execute for each new item found.
+ """
+ new_items = []
+ # for each list
+ for updated_list_id, updated_list in updated_lists.items():
+ if updated_list_id not in original_lists:
+ _LOGGER.debug("Found new list not in original: %s", updated_list.name)
+ continue
+
+ # for each todo item in the list
+ updated_list_item: TodoItem
+ for (
+ updated_list_item_id,
+ updated_list_item,
+ ) in updated_list.items.items():
+ # get original list with updated_list_id
+ original_list = original_lists[updated_list_id]
+
+ # if todo is not in original list, then it is new
+ if updated_list_item_id not in original_list.items:
+ # Get HA List entity_id for _gkeep_list_id
+ entity_reg = entity_registry.async_get(self.hass)
+ uuid = f"{DOMAIN}.list.{updated_list_id}"
+ list_entity_id = entity_reg.async_get_entity_id(
+ Platform.TODO, DOMAIN, uuid
+ )
+ new_items.append(
+ TodoItemData(
+ item=updated_list_item.summary, entity_id=list_entity_id
+ )
+ )
+
+ _LOGGER.debug(
+ "Found new TodoItem: '%s' in List entity_id: '%s'",
+ updated_list_item.summary,
+ list_entity_id,
+ )
+ return new_items
+
+ async def _notify_new_items(self, new_items: list[TodoItemData]) -> None:
+ """Emit add_item service call event for new remote Todo items."""
+ for new_item in new_items:
+ event_data = {
+ "domain": "todo",
+ "service": "add_item",
+ "service_data": {
+ "item": new_item.item,
+ "entity_id": [new_item.entity_id],
+ },
+ }
+ self.hass.bus.async_fire(
+ EVENT_CALL_SERVICE, event_data, origin=EventOrigin.remote
+ )
diff --git a/custom_components/google_keep_sync/todo.py b/custom_components/google_keep_sync/todo.py
index a624289..7e105c7 100644
--- a/custom_components/google_keep_sync/todo.py
+++ b/custom_components/google_keep_sync/todo.py
@@ -1,7 +1,6 @@
"""Platform for creating to do list entries based on Google Keep lists."""
import logging
-from datetime import timedelta
import gkeepapi
from homeassistant.components.todo import (
@@ -18,15 +17,15 @@
DataUpdateCoordinator,
)
-from .api import GoogleKeepAPI
from .const import DOMAIN
-
-SCAN_INTERVAL = timedelta(minutes=15)
+from .coordinator import GoogleKeepSyncCoordinator
_LOGGER = logging.getLogger(__name__)
-class GoogleKeepTodoListEntity(CoordinatorEntity, TodoListEntity):
+class GoogleKeepTodoListEntity(
+ CoordinatorEntity[GoogleKeepSyncCoordinator], TodoListEntity
+):
"""A To-do List representation of a Google Keep List."""
_attr_has_entity_name = True
@@ -38,23 +37,27 @@ class GoogleKeepTodoListEntity(CoordinatorEntity, TodoListEntity):
def __init__(
self,
- api: GoogleKeepAPI,
coordinator: DataUpdateCoordinator,
gkeep_list: gkeepapi.node.List,
list_prefix: str,
):
"""Initialize the Google Keep Todo List Entity."""
super().__init__(coordinator)
- self.api = api
+ self.api = coordinator.api
self._gkeep_list = gkeep_list
self._gkeep_list_id = gkeep_list.id
self._attr_name = (
f"{list_prefix} " if list_prefix else ""
) + f"{gkeep_list.title}"
self._attr_unique_id = f"{DOMAIN}.list.{gkeep_list.id}"
- self.entity_id = self._get_entity_id(gkeep_list.title)
- def _get_entity_id(self, title: str) -> str:
+ # Set the default entity ID based on the list title.
+ # We use a prefix to avoid conflicts with todo entities from other
+ # integrations, and so the entities specific to this integration
+ # can be filtered easily in developer tools.
+ self.entity_id = self._get_default_entity_id(gkeep_list.title)
+
+ def _get_default_entity_id(self, title: str) -> str:
"""Return the entity ID for the given title."""
return f"todo.google_keep_{title.lower().replace(' ', '_')}"
@@ -144,22 +147,19 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Google Keep todo platform."""
- api: GoogleKeepAPI = hass.data[DOMAIN][entry.entry_id]["api"]
- coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][
- "coordinator"
- ]
+ coordinator: GoogleKeepSyncCoordinator = hass.data[DOMAIN][entry.entry_id]
# Retrieve user-selected lists from the configuration
selected_lists = entry.data.get("lists_to_sync", [])
list_prefix = entry.data.get("list_prefix", "")
# Filter Google Keep lists based on user selection
- all_lists = await api.fetch_all_lists()
+ all_lists = await coordinator.api.fetch_all_lists()
lists_to_sync = [lst for lst in all_lists if lst.id in selected_lists]
async_add_entities(
[
- GoogleKeepTodoListEntity(api, coordinator, list, list_prefix)
+ GoogleKeepTodoListEntity(coordinator, list, list_prefix)
for list in lists_to_sync
]
)
diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py
new file mode 100644
index 0000000..5895eb0
--- /dev/null
+++ b/tests/test_coordinator.py
@@ -0,0 +1,178 @@
+"""Unit tests for the todo component."""
+
+from unittest.mock import AsyncMock, MagicMock, patch
+
+import pytest
+from homeassistant.const import EVENT_CALL_SERVICE
+from homeassistant.core import EventOrigin
+from homeassistant.helpers import entity_registry
+from pytest_homeassistant_custom_component.common import MockConfigEntry
+
+from custom_components.google_keep_sync.coordinator import (
+ GoogleKeepSyncCoordinator,
+ TodoItem,
+ TodoItemData,
+ TodoList,
+)
+
+
+@pytest.fixture
+def mock_hass():
+ """Fixture for mocking Home Assistant."""
+ mock_hass = MagicMock()
+ mock_hass.async_add_executor_job.side_effect = lambda f, *args, **kwargs: f(
+ *args, **kwargs
+ )
+ return mock_hass
+
+
+@pytest.fixture
+def mock_api():
+ """Return a mocked GoogleKeepAPI."""
+ api = MagicMock()
+ api.async_create_todo_item = MagicMock()
+ return api
+
+
+async def test_async_update_data(
+ mock_api: MagicMock, mock_hass: MagicMock, mock_config_entry: MockConfigEntry
+):
+ """Test update_data method."""
+ with patch.object(mock_api, "async_sync_data", AsyncMock()):
+ mock_api.async_sync_data.return_value = ["list1", "list2"]
+
+ coordinator = GoogleKeepSyncCoordinator(mock_hass, mock_api, mock_config_entry)
+ coordinator.config_entry = mock_config_entry
+
+ result = await coordinator._async_update_data()
+
+ assert result == ["list1", "list2"]
+
+
+async def test_parse_gkeep_data_dict_empty(
+ mock_api: MagicMock, mock_hass: MagicMock, mock_config_entry: MockConfigEntry
+):
+ """Test _parse_gkeep_data_dict when empty."""
+ test_input: dict = {}
+ expected: dict = {}
+ coordinator = GoogleKeepSyncCoordinator(mock_hass, mock_api, mock_config_entry)
+ coordinator.data = test_input
+
+ actual = await coordinator._parse_gkeep_data_dict()
+ assert actual == expected
+
+
+async def test_parse_gkeep_data_dict_normal(
+ mock_api: MagicMock, mock_hass: MagicMock, mock_config_entry: MockConfigEntry
+):
+ """Test _parse_gkeep_data_dict with data."""
+ mock_list = MagicMock(id="grocery_list_id", title="Grocery List")
+ mock_item = MagicMock(id="milk_item_id", text="Milk", checked=False)
+ mock_list.items = [mock_item]
+ expected = {
+ "grocery_list_id": TodoList(
+ name="Grocery List",
+ items={"milk_item_id": TodoItem(summary="Milk", checked=False)},
+ )
+ }
+
+ coordinator = GoogleKeepSyncCoordinator(mock_hass, mock_api, mock_config_entry)
+ coordinator.data = [mock_list]
+
+ actual = await coordinator._parse_gkeep_data_dict()
+ assert actual == expected
+
+
+async def test_get_new_items_added(
+ mock_api: MagicMock,
+ mock_hass: MagicMock,
+ mock_config_entry: MockConfigEntry,
+):
+ """Test handling new items added to a list."""
+ # Set up coordinator and mock API
+ coordinator = GoogleKeepSyncCoordinator(mock_hass, mock_api, mock_config_entry)
+
+ list1 = {
+ "grocery_list_id": TodoList(
+ name="Grocery List",
+ items={"milk_item_id": TodoItem(summary="Milk", checked=False)},
+ )
+ }
+ list2 = {
+ "grocery_list_id": TodoList(
+ name="Grocery List",
+ items={
+ "milk_item_id": TodoItem(summary="Milk", checked=False),
+ "bread_item_id": TodoItem(summary="Bread", checked=False),
+ },
+ )
+ }
+
+ with patch.object(entity_registry, "async_get") as er:
+ instance = er.return_value
+ instance.async_get_entity_id.return_value = "list_entity_id"
+
+ # Call method under test
+ # callback = MagicMock()
+ new_items = await coordinator._get_new_items_added(list1, list2)
+
+ # Assertions
+ expected = [
+ TodoItemData(
+ item="Bread",
+ entity_id="list_entity_id",
+ )
+ ]
+ assert new_items == expected
+
+
+async def test_get_new_items_not_added(
+ mock_api: MagicMock, mock_hass: MagicMock, mock_config_entry: MockConfigEntry
+):
+ """Test handling when no new items are added to a list."""
+ # Set up coordinator and mock API
+ coordinator = GoogleKeepSyncCoordinator(mock_hass, mock_api, mock_config_entry)
+
+ list1 = {
+ "grocery_list_id": TodoList(
+ name="Grocery List",
+ items={"milk_item_id": TodoItem(summary="Milk", checked=False)},
+ )
+ }
+
+ # Call method under test
+ new_items = await coordinator._get_new_items_added(list1, list1)
+
+ # Assertions
+ assert new_items == []
+
+
+async def test_notify_new_items(
+ mock_api: MagicMock, mock_hass: MagicMock, mock_config_entry: MockConfigEntry
+):
+ """Test sending notifications of new items added to a list."""
+ # Set up coordinator and mock API
+ coordinator = GoogleKeepSyncCoordinator(mock_hass, mock_api, mock_config_entry)
+
+ new_items = [
+ TodoItemData(
+ item="Bread",
+ entity_id="list_entity_id",
+ )
+ ]
+ # Call method under test
+ await coordinator._notify_new_items(new_items)
+
+ expected = {
+ "domain": "todo",
+ "service": "add_item",
+ "service_data": {
+ "item": "Bread",
+ "entity_id": ["list_entity_id"],
+ },
+ }
+
+ # Assertions
+ mock_hass.bus.async_fire.assert_called_once_with(
+ EVENT_CALL_SERVICE, expected, origin=EventOrigin.remote
+ )
diff --git a/tests/test_todo.py b/tests/test_todo.py
index 60a4094..af723ad 100644
--- a/tests/test_todo.py
+++ b/tests/test_todo.py
@@ -17,7 +17,7 @@
def mock_api():
"""Return a mocked Google Keep API."""
with patch(
- "custom_components.google_keep_sync.todo.GoogleKeepAPI"
+ "custom_components.google_keep_sync.api.GoogleKeepAPI"
) as mock_api_class:
mock_api = mock_api_class.return_value
mock_api.async_create_todo_item = AsyncMock(return_value="new_item_id")
@@ -46,9 +46,7 @@ async def test_async_setup_entry(
):
"""Test platform setup of todo."""
mock_config_entry.add_to_hass(hass)
- hass.data[DOMAIN] = {
- mock_config_entry.entry_id: {"api": mock_api, "coordinator": mock_coordinator}
- }
+ hass.data[DOMAIN] = {mock_config_entry.entry_id: mock_coordinator}
with patch(
"homeassistant.helpers.entity_platform.AddEntitiesCallback"
@@ -65,6 +63,7 @@ async def test_create_todo_item(hass: HomeAssistant, mock_api, mock_coordinator)
list_prefix = ""
# Initialize the coordinator data
+ mock_coordinator.api = mock_api
mock_coordinator.data = [
{"id": "grocery_list", "title": "Grocery List", "items": []}
]
@@ -88,9 +87,7 @@ def async_refresh_side_effect():
mock_coordinator.async_refresh = AsyncMock(side_effect=async_refresh_side_effect)
# Create the entity and add a new item
- entity = GoogleKeepTodoListEntity(
- mock_api, mock_coordinator, grocery_list, list_prefix
- )
+ entity = GoogleKeepTodoListEntity(mock_coordinator, grocery_list, list_prefix)
await entity.async_create_todo_item(TodoItem(summary="Milk"))
# Ensure the proper methods were called
@@ -110,6 +107,7 @@ async def test_update_todo_item(hass: HomeAssistant, mock_api, mock_coordinator)
initial_item = {"id": "milk_item", "text": "Milk", "checked": False}
grocery_list.items = [initial_item]
+ mock_coordinator.api = mock_api
mock_coordinator.data = [
{"id": "grocery_list", "title": "Grocery List", "items": [initial_item]}
]
@@ -131,9 +129,7 @@ def async_refresh_side_effect():
mock_coordinator.async_refresh = AsyncMock(side_effect=async_refresh_side_effect)
# Create the entity
- entity = GoogleKeepTodoListEntity(
- mock_api, mock_coordinator, grocery_list, list_prefix
- )
+ entity = GoogleKeepTodoListEntity(mock_coordinator, grocery_list, list_prefix)
entity.hass = hass
# update item
@@ -162,6 +158,7 @@ async def test_delete_todo_items(hass: HomeAssistant, mock_api, mock_coordinator
{"id": "eggs_item", "text": "Eggs", "checked": False},
]
grocery_list.items = initial_items
+ mock_coordinator.api = mock_api
mock_coordinator.data = [
{"id": "grocery_list", "title": "Grocery List", "items": initial_items}
]
@@ -181,9 +178,7 @@ def async_refresh_side_effect():
mock_coordinator.async_refresh = AsyncMock(side_effect=async_refresh_side_effect)
# Create the entity
- entity = GoogleKeepTodoListEntity(
- mock_api, mock_coordinator, grocery_list, list_prefix
- )
+ entity = GoogleKeepTodoListEntity(mock_coordinator, grocery_list, list_prefix)
entity.hass = hass
# Delete item
@@ -203,9 +198,7 @@ async def test_default_list_prefix(hass, mock_api, mock_coordinator):
list_prefix = ""
grocery_list = MagicMock(id="grocery_list", title="Grocery List")
- entity = GoogleKeepTodoListEntity(
- mock_api, mock_coordinator, grocery_list, list_prefix
- )
+ entity = GoogleKeepTodoListEntity(mock_coordinator, grocery_list, list_prefix)
# Test default prefix
assert entity.name == "Grocery List"
@@ -217,7 +210,5 @@ async def test_custom_list_prefix(hass, mock_api, mock_coordinator):
grocery_list = MagicMock(id="grocery_list", title="Grocery List")
# Test custom prefix
- entity = GoogleKeepTodoListEntity(
- mock_api, mock_coordinator, grocery_list, list_prefix
- )
+ entity = GoogleKeepTodoListEntity(mock_coordinator, grocery_list, list_prefix)
assert entity.name == "Foo Grocery List"