mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +00:00
Add config_flow to shopping_list (#32388)
* Add config_flow to shopping_list * Fix pylint unused import error * Use _abort_if_unique_id_configured * Remove SHOPPING_LIST const * Use const.py for DOMAIN and CONF_TYPE * Fix tests * Remove unchanged variable _errors * Revert CONF_TYPE (wrong usage) * Use consts in test * Remove import check * Remove data={} * Remove parameters and default values * Re-add data={}, because it's needed * Unique ID checks and reverts for default parameters * remove pylint comment * Remove block till done * Address change requests * Update homeassistant/components/shopping_list/strings.json Co-Authored-By: Quentame <polletquentin74@me.com> * Update homeassistant/components/shopping_list/strings.json Co-Authored-By: Quentame <polletquentin74@me.com> * Update tests/components/shopping_list/test_config_flow.py Co-Authored-By: Quentame <polletquentin74@me.com> * Update tests/components/shopping_list/test_config_flow.py Co-Authored-By: Quentame <polletquentin74@me.com> * Update tests/components/shopping_list/test_config_flow.py Co-Authored-By: Quentame <polletquentin74@me.com> * Update tests/components/shopping_list/test_config_flow.py Co-Authored-By: Quentame <polletquentin74@me.com> * Only test config_flow * Generate translations * Move data to end * @asyncio.coroutine --> async def, yield from --> await * @asyncio.coroutine --> async def, yield from --> await (tests) * Remove init in config flow * remove if not hass.config_entries.async_entries(DOMAIN) * Add DOMAIN not in config * Fix tests * Update homeassistant/components/shopping_list/config_flow.py Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io> * Fix tests * Update homeassistant/components/shopping_list/__init__.py Co-Authored-By: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Quentame <polletquentin74@me.com> Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
e1cc2acdf9
commit
8f2567f30d
14
homeassistant/components/shopping_list/.translations/en.json
Normal file
14
homeassistant/components/shopping_list/.translations/en.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "The shopping list is already configured."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Do you want to configure the shopping list?",
|
||||
"title": "Shopping List"
|
||||
}
|
||||
},
|
||||
"title": "Shopping List"
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
"""Support to manage a shopping list."""
|
||||
import asyncio
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import http, websocket_api
|
||||
from homeassistant.components.http.data_validator import RequestDataValidator
|
||||
from homeassistant.const import HTTP_BAD_REQUEST, HTTP_NOT_FOUND
|
||||
@ -12,9 +12,10 @@ from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
ATTR_NAME = "name"
|
||||
|
||||
DOMAIN = "shopping_list"
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA)
|
||||
EVENT = "shopping_list_updated"
|
||||
@ -53,20 +54,32 @@ SCHEMA_WEBSOCKET_CLEAR_ITEMS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
|
||||
)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Initialize the shopping list."""
|
||||
|
||||
@asyncio.coroutine
|
||||
def add_item_service(call):
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up shopping list from config flow."""
|
||||
|
||||
async def add_item_service(call):
|
||||
"""Add an item with `name`."""
|
||||
data = hass.data[DOMAIN]
|
||||
name = call.data.get(ATTR_NAME)
|
||||
if name is not None:
|
||||
data.async_add(name)
|
||||
|
||||
@asyncio.coroutine
|
||||
def complete_item_service(call):
|
||||
async def complete_item_service(call):
|
||||
"""Mark the item provided via `name` as completed."""
|
||||
data = hass.data[DOMAIN]
|
||||
name = call.data.get(ATTR_NAME)
|
||||
@ -80,7 +93,7 @@ def async_setup(hass, config):
|
||||
data.async_update(item["id"], {"name": name, "complete": True})
|
||||
|
||||
data = hass.data[DOMAIN] = ShoppingData(hass)
|
||||
yield from data.async_load()
|
||||
await data.async_load()
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_ADD_ITEM, add_item_service, schema=SERVICE_ITEM_SCHEMA
|
||||
@ -206,8 +219,7 @@ class CreateShoppingListItemView(http.HomeAssistantView):
|
||||
name = "api:shopping_list:item"
|
||||
|
||||
@RequestDataValidator(vol.Schema({vol.Required("name"): str}))
|
||||
@asyncio.coroutine
|
||||
def post(self, request, data):
|
||||
async def post(self, request, data):
|
||||
"""Create a new shopping list item."""
|
||||
item = request.app["hass"].data[DOMAIN].async_add(data["name"])
|
||||
request.app["hass"].bus.async_fire(EVENT)
|
||||
|
24
homeassistant/components/shopping_list/config_flow.py
Normal file
24
homeassistant/components/shopping_list/config_flow.py
Normal file
@ -0,0 +1,24 @@
|
||||
"""Config flow to configure ShoppingList component."""
|
||||
from homeassistant import config_entries
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
class ShoppingListFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Config flow for ShoppingList component."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow initialized by the user."""
|
||||
# Check if already configured
|
||||
await self.async_set_unique_id(DOMAIN)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="Shopping List", data=user_input)
|
||||
|
||||
return self.async_show_form(step_id="user")
|
||||
|
||||
async_step_import = async_step_user
|
2
homeassistant/components/shopping_list/const.py
Normal file
2
homeassistant/components/shopping_list/const.py
Normal file
@ -0,0 +1,2 @@
|
||||
"""All constants related to the shopping list component."""
|
||||
DOMAIN = "shopping_list"
|
@ -5,5 +5,6 @@
|
||||
"requirements": [],
|
||||
"dependencies": ["http"],
|
||||
"codeowners": [],
|
||||
"config_flow": true,
|
||||
"quality_scale": "internal"
|
||||
}
|
||||
|
14
homeassistant/components/shopping_list/strings.json
Normal file
14
homeassistant/components/shopping_list/strings.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Shopping List",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Shopping List",
|
||||
"description": "Do you want to configure the shopping list?"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "The shopping list is already configured."
|
||||
}
|
||||
}
|
||||
}
|
@ -82,6 +82,7 @@ FLOWS = [
|
||||
"samsungtv",
|
||||
"sense",
|
||||
"sentry",
|
||||
"shopping_list",
|
||||
"simplisafe",
|
||||
"smartthings",
|
||||
"smhi",
|
||||
|
@ -1,10 +1,10 @@
|
||||
"""Shopping list test helpers."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from asynctest import patch
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.shopping_list import intent as sl_intent
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@ -19,5 +19,10 @@ def mock_shopping_list_io():
|
||||
@pytest.fixture
|
||||
async def sl_setup(hass):
|
||||
"""Set up the shopping list."""
|
||||
assert await async_setup_component(hass, "shopping_list", {})
|
||||
|
||||
entry = MockConfigEntry(domain="shopping_list")
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
|
||||
await sl_intent.async_setup_intents(hass)
|
||||
|
36
tests/components/shopping_list/test_config_flow.py
Normal file
36
tests/components/shopping_list/test_config_flow.py
Normal file
@ -0,0 +1,36 @@
|
||||
"""Test config flow."""
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.shopping_list.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
|
||||
|
||||
|
||||
async def test_import(hass):
|
||||
"""Test entry will be imported."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_IMPORT}, data={}
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
|
||||
|
||||
async def test_user(hass):
|
||||
"""Test we can start a config flow."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
|
||||
async def test_user_confirm(hass):
|
||||
"""Test we can finish a config flow."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data={}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["result"].data == {}
|
@ -1,36 +1,33 @@
|
||||
"""Test shopping list component."""
|
||||
import asyncio
|
||||
|
||||
from homeassistant.components.websocket_api.const import TYPE_RESULT
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_add_item(hass, sl_setup):
|
||||
async def test_add_item(hass, sl_setup):
|
||||
"""Test adding an item intent."""
|
||||
|
||||
response = yield from intent.async_handle(
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
|
||||
assert response.speech["plain"]["speech"] == "I've added beer to your shopping list"
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_recent_items_intent(hass, sl_setup):
|
||||
async def test_recent_items_intent(hass, sl_setup):
|
||||
"""Test recent items."""
|
||||
|
||||
yield from intent.async_handle(
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
yield from intent.async_handle(
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
|
||||
)
|
||||
yield from intent.async_handle(
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "soda"}}
|
||||
)
|
||||
|
||||
response = yield from intent.async_handle(hass, "test", "HassShoppingListLastItems")
|
||||
response = await intent.async_handle(hass, "test", "HassShoppingListLastItems")
|
||||
|
||||
assert (
|
||||
response.speech["plain"]["speech"]
|
||||
@ -38,22 +35,21 @@ def test_recent_items_intent(hass, sl_setup):
|
||||
)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_deprecated_api_get_all(hass, hass_client, sl_setup):
|
||||
async def test_deprecated_api_get_all(hass, hass_client, sl_setup):
|
||||
"""Test the API."""
|
||||
|
||||
yield from intent.async_handle(
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
yield from intent.async_handle(
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
|
||||
)
|
||||
|
||||
client = yield from hass_client()
|
||||
resp = yield from client.get("/api/shopping_list")
|
||||
client = await hass_client()
|
||||
resp = await client.get("/api/shopping_list")
|
||||
|
||||
assert resp.status == 200
|
||||
data = yield from resp.json()
|
||||
data = await resp.json()
|
||||
assert len(data) == 2
|
||||
assert data[0]["name"] == "beer"
|
||||
assert not data[0]["complete"]
|
||||
@ -88,35 +84,34 @@ async def test_ws_get_items(hass, hass_ws_client, sl_setup):
|
||||
assert not data[1]["complete"]
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_deprecated_api_update(hass, hass_client, sl_setup):
|
||||
async def test_deprecated_api_update(hass, hass_client, sl_setup):
|
||||
"""Test the API."""
|
||||
|
||||
yield from intent.async_handle(
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
yield from intent.async_handle(
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
|
||||
)
|
||||
|
||||
beer_id = hass.data["shopping_list"].items[0]["id"]
|
||||
wine_id = hass.data["shopping_list"].items[1]["id"]
|
||||
|
||||
client = yield from hass_client()
|
||||
resp = yield from client.post(
|
||||
client = await hass_client()
|
||||
resp = await client.post(
|
||||
"/api/shopping_list/item/{}".format(beer_id), json={"name": "soda"}
|
||||
)
|
||||
|
||||
assert resp.status == 200
|
||||
data = yield from resp.json()
|
||||
data = await resp.json()
|
||||
assert data == {"id": beer_id, "name": "soda", "complete": False}
|
||||
|
||||
resp = yield from client.post(
|
||||
resp = await client.post(
|
||||
"/api/shopping_list/item/{}".format(wine_id), json={"complete": True}
|
||||
)
|
||||
|
||||
assert resp.status == 200
|
||||
data = yield from resp.json()
|
||||
data = await resp.json()
|
||||
assert data == {"id": wine_id, "name": "wine", "complete": True}
|
||||
|
||||
beer, wine = hass.data["shopping_list"].items
|
||||
@ -166,23 +161,20 @@ async def test_ws_update_item(hass, hass_ws_client, sl_setup):
|
||||
assert wine == {"id": wine_id, "name": "wine", "complete": True}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_api_update_fails(hass, hass_client, sl_setup):
|
||||
async def test_api_update_fails(hass, hass_client, sl_setup):
|
||||
"""Test the API."""
|
||||
|
||||
yield from intent.async_handle(
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
|
||||
client = yield from hass_client()
|
||||
resp = yield from client.post(
|
||||
"/api/shopping_list/non_existing", json={"name": "soda"}
|
||||
)
|
||||
client = await hass_client()
|
||||
resp = await client.post("/api/shopping_list/non_existing", json={"name": "soda"})
|
||||
|
||||
assert resp.status == 404
|
||||
|
||||
beer_id = hass.data["shopping_list"].items[0]["id"]
|
||||
resp = yield from client.post(
|
||||
resp = await client.post(
|
||||
"/api/shopping_list/item/{}".format(beer_id), json={"name": 123}
|
||||
)
|
||||
|
||||
@ -212,29 +204,28 @@ async def test_ws_update_item_fail(hass, hass_ws_client, sl_setup):
|
||||
assert msg["success"] is False
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_deprecated_api_clear_completed(hass, hass_client, sl_setup):
|
||||
async def test_deprecated_api_clear_completed(hass, hass_client, sl_setup):
|
||||
"""Test the API."""
|
||||
|
||||
yield from intent.async_handle(
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
yield from intent.async_handle(
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
|
||||
)
|
||||
|
||||
beer_id = hass.data["shopping_list"].items[0]["id"]
|
||||
wine_id = hass.data["shopping_list"].items[1]["id"]
|
||||
|
||||
client = yield from hass_client()
|
||||
client = await hass_client()
|
||||
|
||||
# Mark beer as completed
|
||||
resp = yield from client.post(
|
||||
resp = await client.post(
|
||||
"/api/shopping_list/item/{}".format(beer_id), json={"complete": True}
|
||||
)
|
||||
assert resp.status == 200
|
||||
|
||||
resp = yield from client.post("/api/shopping_list/clear_completed")
|
||||
resp = await client.post("/api/shopping_list/clear_completed")
|
||||
assert resp.status == 200
|
||||
|
||||
items = hass.data["shopping_list"].items
|
||||
@ -272,15 +263,14 @@ async def test_ws_clear_items(hass, hass_ws_client, sl_setup):
|
||||
assert items[0] == {"id": wine_id, "name": "wine", "complete": False}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_deprecated_api_create(hass, hass_client, sl_setup):
|
||||
async def test_deprecated_api_create(hass, hass_client, sl_setup):
|
||||
"""Test the API."""
|
||||
|
||||
client = yield from hass_client()
|
||||
resp = yield from client.post("/api/shopping_list/item", json={"name": "soda"})
|
||||
client = await hass_client()
|
||||
resp = await client.post("/api/shopping_list/item", json={"name": "soda"})
|
||||
|
||||
assert resp.status == 200
|
||||
data = yield from resp.json()
|
||||
data = await resp.json()
|
||||
assert data["name"] == "soda"
|
||||
assert data["complete"] is False
|
||||
|
||||
@ -290,12 +280,11 @@ def test_deprecated_api_create(hass, hass_client, sl_setup):
|
||||
assert items[0]["complete"] is False
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_deprecated_api_create_fail(hass, hass_client, sl_setup):
|
||||
async def test_deprecated_api_create_fail(hass, hass_client, sl_setup):
|
||||
"""Test the API."""
|
||||
|
||||
client = yield from hass_client()
|
||||
resp = yield from client.post("/api/shopping_list/item", json={"name": 1234})
|
||||
client = await hass_client()
|
||||
resp = await client.post("/api/shopping_list/item", json={"name": 1234})
|
||||
|
||||
assert resp.status == 400
|
||||
assert len(hass.data["shopping_list"].items) == 0
|
||||
|
Loading…
x
Reference in New Issue
Block a user