Add an option to serve ES6 JS to clients (#10474)
* Add an option to serve ES6 JS to clients * Rename es6 to latest * Fixes * Serve JS vrsions from separate dirs * Revert websocket API change * Update frontend to 20171110.0 * websocket: move request to constructor
This commit is contained in:
parent
1c36e2f586
commit
5e92fa3404
10 changed files with 150 additions and 65 deletions
|
@ -9,6 +9,7 @@ import hashlib
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from aiohttp import web
|
||||
import voluptuous as vol
|
||||
|
@ -21,21 +22,19 @@ from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED
|
|||
from homeassistant.core import callback
|
||||
from homeassistant.loader import bind_hass
|
||||
|
||||
REQUIREMENTS = ['home-assistant-frontend==20171106.0']
|
||||
REQUIREMENTS = ['home-assistant-frontend==20171110.0']
|
||||
|
||||
DOMAIN = 'frontend'
|
||||
DEPENDENCIES = ['api', 'websocket_api']
|
||||
DEPENDENCIES = ['api', 'websocket_api', 'http']
|
||||
|
||||
URL_PANEL_COMPONENT = '/frontend/panels/{}.html'
|
||||
URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html'
|
||||
|
||||
POLYMER_PATH = os.path.join(os.path.dirname(__file__),
|
||||
'home-assistant-polymer/')
|
||||
FINAL_PATH = os.path.join(POLYMER_PATH, 'final')
|
||||
|
||||
CONF_THEMES = 'themes'
|
||||
CONF_EXTRA_HTML_URL = 'extra_html_url'
|
||||
CONF_FRONTEND_REPO = 'development_repo'
|
||||
CONF_JS_VERSION = 'javascript_version'
|
||||
JS_DEFAULT_OPTION = 'es5'
|
||||
JS_OPTIONS = ['es5', 'latest', 'auto']
|
||||
|
||||
DEFAULT_THEME_COLOR = '#03A9F4'
|
||||
|
||||
|
@ -61,6 +60,7 @@ for size in (192, 384, 512, 1024):
|
|||
|
||||
DATA_FINALIZE_PANEL = 'frontend_finalize_panel'
|
||||
DATA_PANELS = 'frontend_panels'
|
||||
DATA_JS_VERSION = 'frontend_js_version'
|
||||
DATA_EXTRA_HTML_URL = 'frontend_extra_html_url'
|
||||
DATA_THEMES = 'frontend_themes'
|
||||
DATA_DEFAULT_THEME = 'frontend_default_theme'
|
||||
|
@ -68,8 +68,6 @@ DEFAULT_THEME = 'default'
|
|||
|
||||
PRIMARY_COLOR = 'primary-color'
|
||||
|
||||
# To keep track we don't register a component twice (gives a warning)
|
||||
# _REGISTERED_COMPONENTS = set()
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
|
@ -80,6 +78,8 @@ CONFIG_SCHEMA = vol.Schema({
|
|||
}),
|
||||
vol.Optional(CONF_EXTRA_HTML_URL):
|
||||
vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_JS_VERSION, default=JS_DEFAULT_OPTION):
|
||||
vol.In(JS_OPTIONS)
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
@ -102,8 +102,9 @@ class AbstractPanel:
|
|||
# Title to show in the sidebar (optional)
|
||||
sidebar_title = None
|
||||
|
||||
# Url to the webcomponent
|
||||
webcomponent_url = None
|
||||
# Url to the webcomponent (depending on JS version)
|
||||
webcomponent_url_es5 = None
|
||||
webcomponent_url_latest = None
|
||||
|
||||
# Url to show the panel in the frontend
|
||||
frontend_url_path = None
|
||||
|
@ -135,16 +136,20 @@ class AbstractPanel:
|
|||
'get', '/{}/{{extra:.+}}'.format(self.frontend_url_path),
|
||||
index_view.get)
|
||||
|
||||
def as_dict(self):
|
||||
def to_response(self, hass, request):
|
||||
"""Panel as dictionary."""
|
||||
return {
|
||||
result = {
|
||||
'component_name': self.component_name,
|
||||
'icon': self.sidebar_icon,
|
||||
'title': self.sidebar_title,
|
||||
'url': self.webcomponent_url,
|
||||
'url_path': self.frontend_url_path,
|
||||
'config': self.config,
|
||||
}
|
||||
if _is_latest(hass.data[DATA_JS_VERSION], request):
|
||||
result['url'] = self.webcomponent_url_latest
|
||||
else:
|
||||
result['url'] = self.webcomponent_url_es5
|
||||
return result
|
||||
|
||||
|
||||
class BuiltInPanel(AbstractPanel):
|
||||
|
@ -170,15 +175,19 @@ class BuiltInPanel(AbstractPanel):
|
|||
|
||||
if frontend_repository_path is None:
|
||||
import hass_frontend
|
||||
import hass_frontend_es5
|
||||
|
||||
self.webcomponent_url = \
|
||||
'/static/panels/ha-panel-{}-{}.html'.format(
|
||||
self.webcomponent_url_latest = \
|
||||
'/frontend_latest/panels/ha-panel-{}-{}.html'.format(
|
||||
self.component_name,
|
||||
hass_frontend.FINGERPRINTS[panel_path])
|
||||
|
||||
self.webcomponent_url_es5 = \
|
||||
'/frontend_es5/panels/ha-panel-{}-{}.html'.format(
|
||||
self.component_name,
|
||||
hass_frontend_es5.FINGERPRINTS[panel_path])
|
||||
else:
|
||||
# Dev mode
|
||||
self.webcomponent_url = \
|
||||
self.webcomponent_url_es5 = self.webcomponent_url_latest = \
|
||||
'/home-assistant-polymer/panels/{}/ha-panel-{}.html'.format(
|
||||
self.component_name, self.component_name)
|
||||
|
||||
|
@ -208,18 +217,20 @@ class ExternalPanel(AbstractPanel):
|
|||
"""
|
||||
try:
|
||||
if self.md5 is None:
|
||||
yield from hass.async_add_job(_fingerprint, self.path)
|
||||
self.md5 = yield from hass.async_add_job(
|
||||
_fingerprint, self.path)
|
||||
except OSError:
|
||||
_LOGGER.error('Cannot find or access %s at %s',
|
||||
self.component_name, self.path)
|
||||
hass.data[DATA_PANELS].pop(self.frontend_url_path)
|
||||
return
|
||||
|
||||
self.webcomponent_url = \
|
||||
self.webcomponent_url_es5 = self.webcomponent_url_latest = \
|
||||
URL_PANEL_COMPONENT_FP.format(self.component_name, self.md5)
|
||||
|
||||
if self.component_name not in self.REGISTERED_COMPONENTS:
|
||||
hass.http.register_static_path(
|
||||
self.webcomponent_url, self.path,
|
||||
self.webcomponent_url_latest, self.path,
|
||||
# if path is None, we're in prod mode, so cache static assets
|
||||
frontend_repository_path is None)
|
||||
self.REGISTERED_COMPONENTS.add(self.component_name)
|
||||
|
@ -281,31 +292,50 @@ def async_setup(hass, config):
|
|||
|
||||
repo_path = conf.get(CONF_FRONTEND_REPO)
|
||||
is_dev = repo_path is not None
|
||||
hass.data[DATA_JS_VERSION] = js_version = conf.get(CONF_JS_VERSION)
|
||||
|
||||
if is_dev:
|
||||
hass.http.register_static_path(
|
||||
"/home-assistant-polymer", repo_path, False)
|
||||
hass.http.register_static_path(
|
||||
"/static/translations",
|
||||
os.path.join(repo_path, "build/translations"), False)
|
||||
sw_path = os.path.join(repo_path, "build/service_worker.js")
|
||||
os.path.join(repo_path, "build-translations"), False)
|
||||
sw_path_es5 = os.path.join(repo_path, "build-es5/service_worker.js")
|
||||
sw_path_latest = os.path.join(repo_path, "build/service_worker.js")
|
||||
static_path = os.path.join(repo_path, 'hass_frontend')
|
||||
frontend_es5_path = os.path.join(repo_path, 'build-es5')
|
||||
frontend_latest_path = os.path.join(repo_path, 'build')
|
||||
else:
|
||||
import hass_frontend
|
||||
frontend_path = hass_frontend.where()
|
||||
sw_path = os.path.join(frontend_path, "service_worker.js")
|
||||
static_path = frontend_path
|
||||
import hass_frontend_es5
|
||||
sw_path_es5 = os.path.join(hass_frontend_es5.where(),
|
||||
"service_worker.js")
|
||||
sw_path_latest = os.path.join(hass_frontend.where(),
|
||||
"service_worker.js")
|
||||
# /static points to dir with files that are JS-type agnostic.
|
||||
# ES5 files are served from /frontend_es5.
|
||||
# ES6 files are served from /frontend_latest.
|
||||
static_path = hass_frontend.where()
|
||||
frontend_es5_path = hass_frontend_es5.where()
|
||||
frontend_latest_path = static_path
|
||||
|
||||
hass.http.register_static_path("/service_worker.js", sw_path, False)
|
||||
hass.http.register_static_path(
|
||||
"/service_worker_es5.js", sw_path_es5, False)
|
||||
hass.http.register_static_path(
|
||||
"/service_worker.js", sw_path_latest, False)
|
||||
hass.http.register_static_path(
|
||||
"/robots.txt", os.path.join(static_path, "robots.txt"), not is_dev)
|
||||
hass.http.register_static_path("/static", static_path, not is_dev)
|
||||
hass.http.register_static_path(
|
||||
"/frontend_latest", frontend_latest_path, not is_dev)
|
||||
hass.http.register_static_path(
|
||||
"/frontend_es5", frontend_es5_path, not is_dev)
|
||||
|
||||
local = hass.config.path('www')
|
||||
if os.path.isdir(local):
|
||||
hass.http.register_static_path("/local", local, not is_dev)
|
||||
|
||||
index_view = IndexView(is_dev)
|
||||
index_view = IndexView(is_dev, js_version)
|
||||
hass.http.register_view(index_view)
|
||||
|
||||
@asyncio.coroutine
|
||||
|
@ -405,7 +435,7 @@ class IndexView(HomeAssistantView):
|
|||
requires_auth = False
|
||||
extra_urls = ['/states', '/states/{extra}']
|
||||
|
||||
def __init__(self, use_repo):
|
||||
def __init__(self, use_repo, js_option):
|
||||
"""Initialize the frontend view."""
|
||||
from jinja2 import FileSystemLoader, Environment
|
||||
|
||||
|
@ -416,27 +446,37 @@ class IndexView(HomeAssistantView):
|
|||
os.path.join(os.path.dirname(__file__), 'templates/')
|
||||
)
|
||||
)
|
||||
self.js_option = js_option
|
||||
|
||||
@asyncio.coroutine
|
||||
def get(self, request, extra=None):
|
||||
"""Serve the index view."""
|
||||
hass = request.app['hass']
|
||||
latest = _is_latest(self.js_option, request)
|
||||
compatibility_url = None
|
||||
|
||||
if self.use_repo:
|
||||
core_url = '/home-assistant-polymer/build/core.js'
|
||||
compatibility_url = \
|
||||
'/home-assistant-polymer/build/compatibility.js'
|
||||
core_url = '/home-assistant-polymer/{}/core.js'.format(
|
||||
'build' if latest else 'build-es5')
|
||||
ui_url = '/home-assistant-polymer/src/home-assistant.html'
|
||||
icons_fp = ''
|
||||
icons_url = '/static/mdi.html'
|
||||
else:
|
||||
if latest:
|
||||
import hass_frontend
|
||||
core_url = '/frontend_latest/core-{}.js'.format(
|
||||
hass_frontend.FINGERPRINTS['core.js'])
|
||||
ui_url = '/frontend_latest/frontend-{}.html'.format(
|
||||
hass_frontend.FINGERPRINTS['frontend.html'])
|
||||
else:
|
||||
import hass_frontend_es5
|
||||
core_url = '/frontend_es5/core-{}.js'.format(
|
||||
hass_frontend_es5.FINGERPRINTS['core.js'])
|
||||
compatibility_url = '/frontend_es5/compatibility-{}.js'.format(
|
||||
hass_frontend_es5.FINGERPRINTS['compatibility.js'])
|
||||
ui_url = '/frontend_es5/frontend-{}.html'.format(
|
||||
hass_frontend_es5.FINGERPRINTS['frontend.html'])
|
||||
import hass_frontend
|
||||
core_url = '/static/core-{}.js'.format(
|
||||
hass_frontend.FINGERPRINTS['core.js'])
|
||||
compatibility_url = '/static/compatibility-{}.js'.format(
|
||||
hass_frontend.FINGERPRINTS['compatibility.js'])
|
||||
ui_url = '/static/frontend-{}.html'.format(
|
||||
hass_frontend.FINGERPRINTS['frontend.html'])
|
||||
icons_fp = '-{}'.format(hass_frontend.FINGERPRINTS['mdi.html'])
|
||||
icons_url = '/static/mdi{}.html'.format(icons_fp)
|
||||
|
||||
|
@ -447,8 +487,10 @@ class IndexView(HomeAssistantView):
|
|||
|
||||
if panel == 'states':
|
||||
panel_url = ''
|
||||
elif latest:
|
||||
panel_url = hass.data[DATA_PANELS][panel].webcomponent_url_latest
|
||||
else:
|
||||
panel_url = hass.data[DATA_PANELS][panel].webcomponent_url
|
||||
panel_url = hass.data[DATA_PANELS][panel].webcomponent_url_es5
|
||||
|
||||
no_auth = 'true'
|
||||
if hass.config.api.api_password and not is_trusted_ip(request):
|
||||
|
@ -468,7 +510,10 @@ class IndexView(HomeAssistantView):
|
|||
panel_url=panel_url, panels=hass.data[DATA_PANELS],
|
||||
dev_mode=self.use_repo,
|
||||
theme_color=MANIFEST_JSON['theme_color'],
|
||||
extra_urls=hass.data[DATA_EXTRA_HTML_URL])
|
||||
extra_urls=hass.data[DATA_EXTRA_HTML_URL],
|
||||
latest=latest,
|
||||
service_worker_name='/service_worker.js' if latest else
|
||||
'/service_worker_es5.js')
|
||||
|
||||
return web.Response(text=resp, content_type='text/html')
|
||||
|
||||
|
@ -509,3 +554,20 @@ def _fingerprint(path):
|
|||
"""Fingerprint a file."""
|
||||
with open(path) as fil:
|
||||
return hashlib.md5(fil.read().encode('utf-8')).hexdigest()
|
||||
|
||||
|
||||
def _is_latest(js_option, request):
|
||||
"""
|
||||
Return whether we should serve latest untranspiled code.
|
||||
|
||||
Set according to user's preference and URL override.
|
||||
"""
|
||||
if request is None:
|
||||
return js_option == 'latest'
|
||||
latest_in_query = 'latest' in request.query or (
|
||||
request.headers.get('Referer') and
|
||||
'latest' in urlparse(request.headers['Referer']).query)
|
||||
es5_in_query = 'es5' in request.query or (
|
||||
request.headers.get('Referer') and
|
||||
'es5' in urlparse(request.headers['Referer']).query)
|
||||
return latest_in_query or (not es5_in_query and js_option == 'latest')
|
||||
|
|
|
@ -78,11 +78,11 @@
|
|||
<a href='/'>TRY AGAIN</a>
|
||||
</div>
|
||||
</div>
|
||||
<home-assistant icons='{{ icons }}'></home-assistant>
|
||||
{# <script src='/static/home-assistant-polymer/build/_demo_data_compiled.js'></script> #}
|
||||
<home-assistant></home-assistant>
|
||||
{# <script src='/static/home-assistant-polymer/build/_demo_data_compiled.js'></script> -#}
|
||||
{% if not latest -%}
|
||||
<script>
|
||||
var compatibilityRequired = (
|
||||
typeof Object.assign != 'function');
|
||||
var compatibilityRequired = (typeof Object.assign != 'function');
|
||||
if (compatibilityRequired) {
|
||||
var e = document.createElement('script');
|
||||
e.onerror = initError;
|
||||
|
@ -90,10 +90,11 @@
|
|||
document.head.appendChild(e);
|
||||
}
|
||||
</script>
|
||||
{% endif -%}
|
||||
<script src='{{ core_url }}'></script>
|
||||
{% if not dev_mode %}
|
||||
<script src='/static/custom-elements-es5-adapter.js'></script>
|
||||
{% endif %}
|
||||
{% if not dev_mode and not latest -%}
|
||||
<script src='/frontend_es5/custom-elements-es5-adapter.js'></script>
|
||||
{% endif -%}
|
||||
<script>
|
||||
var webComponentsSupported = (
|
||||
'customElements' in window &&
|
||||
|
@ -105,6 +106,11 @@
|
|||
e.src = '/static/webcomponents-lite.js';
|
||||
document.head.appendChild(e);
|
||||
}
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('{{ service_worker_name }}');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<link rel='import' href='{{ ui_url }}' onerror='initError()'>
|
||||
{% if panel_url -%}
|
||||
|
|
|
@ -262,7 +262,6 @@ class HomeAssistantWSGI(object):
|
|||
resource = CachingStaticResource
|
||||
else:
|
||||
resource = web.StaticResource
|
||||
|
||||
self.app.router.register_resource(resource(url_path, path))
|
||||
return
|
||||
|
||||
|
|
|
@ -65,7 +65,8 @@ class CachingFileResponse(FileResponse):
|
|||
@asyncio.coroutine
|
||||
def staticresource_middleware(request, handler):
|
||||
"""Middleware to strip out fingerprint from fingerprinted assets."""
|
||||
if not request.path.startswith('/static/'):
|
||||
path = request.path
|
||||
if not path.startswith('/static/') and not path.startswith('/frontend'):
|
||||
return handler(request)
|
||||
|
||||
fingerprinted = _FINGERPRINT.match(request.match_info['filename'])
|
||||
|
|
|
@ -202,15 +202,16 @@ class WebsocketAPIView(HomeAssistantView):
|
|||
def get(self, request):
|
||||
"""Handle an incoming websocket connection."""
|
||||
# pylint: disable=no-self-use
|
||||
return ActiveConnection(request.app['hass']).handle(request)
|
||||
return ActiveConnection(request.app['hass'], request).handle()
|
||||
|
||||
|
||||
class ActiveConnection:
|
||||
"""Handle an active websocket client connection."""
|
||||
|
||||
def __init__(self, hass):
|
||||
def __init__(self, hass, request):
|
||||
"""Initialize an active connection."""
|
||||
self.hass = hass
|
||||
self.request = request
|
||||
self.wsock = None
|
||||
self.event_listeners = {}
|
||||
self.to_write = asyncio.Queue(maxsize=MAX_PENDING_MSG, loop=hass.loop)
|
||||
|
@ -259,8 +260,9 @@ class ActiveConnection:
|
|||
self._writer_task.cancel()
|
||||
|
||||
@asyncio.coroutine
|
||||
def handle(self, request):
|
||||
def handle(self):
|
||||
"""Handle the websocket connection."""
|
||||
request = self.request
|
||||
wsock = self.wsock = web.WebSocketResponse()
|
||||
yield from wsock.prepare(request)
|
||||
self.debug("Connected")
|
||||
|
@ -350,7 +352,7 @@ class ActiveConnection:
|
|||
if wsock.closed:
|
||||
self.debug("Connection closed by client")
|
||||
else:
|
||||
self.log_error("Unexpected TypeError", msg)
|
||||
_LOGGER.exception("Unexpected TypeError: %s", msg)
|
||||
|
||||
except ValueError as err:
|
||||
msg = "Received invalid JSON"
|
||||
|
@ -483,9 +485,14 @@ class ActiveConnection:
|
|||
Async friendly.
|
||||
"""
|
||||
msg = GET_PANELS_MESSAGE_SCHEMA(msg)
|
||||
panels = {
|
||||
panel:
|
||||
self.hass.data[frontend.DATA_PANELS][panel].to_response(
|
||||
self.hass, self.request)
|
||||
for panel in self.hass.data[frontend.DATA_PANELS]}
|
||||
|
||||
self.to_write.put_nowait(result_message(
|
||||
msg['id'], self.hass.data[frontend.DATA_PANELS]))
|
||||
msg['id'], panels))
|
||||
|
||||
def handle_ping(self, msg):
|
||||
"""Handle ping command.
|
||||
|
|
|
@ -330,7 +330,7 @@ hipnotify==1.0.8
|
|||
holidays==0.8.1
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20171106.0
|
||||
home-assistant-frontend==20171110.0
|
||||
|
||||
# homeassistant.components.camera.onvif
|
||||
http://github.com/tgaugry/suds-passworddigest-py3/archive/86fc50e39b4d2b8997481967d6a7fe1c57118999.zip#suds-passworddigest-py3==0.1.2a
|
||||
|
|
|
@ -74,7 +74,7 @@ hbmqtt==0.8
|
|||
holidays==0.8.1
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20171106.0
|
||||
home-assistant-frontend==20171110.0
|
||||
|
||||
# homeassistant.components.influxdb
|
||||
# homeassistant.components.sensor.influxdb
|
||||
|
|
|
@ -52,7 +52,7 @@ def test_frontend_and_static(mock_http_client):
|
|||
|
||||
# Test we can retrieve frontend.js
|
||||
frontendjs = re.search(
|
||||
r'(?P<app>\/static\/frontend-[A-Za-z0-9]{32}.html)', text)
|
||||
r'(?P<app>\/frontend_es5\/frontend-[A-Za-z0-9]{32}.html)', text)
|
||||
|
||||
assert frontendjs is not None
|
||||
resp = yield from mock_http_client.get(frontendjs.groups(0)[0])
|
||||
|
@ -63,6 +63,10 @@ def test_frontend_and_static(mock_http_client):
|
|||
@asyncio.coroutine
|
||||
def test_dont_cache_service_worker(mock_http_client):
|
||||
"""Test that we don't cache the service worker."""
|
||||
resp = yield from mock_http_client.get('/service_worker_es5.js')
|
||||
assert resp.status == 200
|
||||
assert 'cache-control' not in resp.headers
|
||||
|
||||
resp = yield from mock_http_client.get('/service_worker.js')
|
||||
assert resp.status == 200
|
||||
assert 'cache-control' not in resp.headers
|
||||
|
|
|
@ -33,7 +33,7 @@ class TestPanelIframe(unittest.TestCase):
|
|||
'panel_iframe': conf
|
||||
})
|
||||
|
||||
@patch.dict('hass_frontend.FINGERPRINTS',
|
||||
@patch.dict('hass_frontend_es5.FINGERPRINTS',
|
||||
{'panels/ha-panel-iframe.html': 'md5md5'})
|
||||
def test_correct_config(self):
|
||||
"""Test correct config."""
|
||||
|
@ -55,20 +55,20 @@ class TestPanelIframe(unittest.TestCase):
|
|||
|
||||
panels = self.hass.data[frontend.DATA_PANELS]
|
||||
|
||||
assert panels.get('router').as_dict() == {
|
||||
assert panels.get('router').to_response(self.hass, None) == {
|
||||
'component_name': 'iframe',
|
||||
'config': {'url': 'http://192.168.1.1'},
|
||||
'icon': 'mdi:network-wireless',
|
||||
'title': 'Router',
|
||||
'url': '/static/panels/ha-panel-iframe-md5md5.html',
|
||||
'url': '/frontend_es5/panels/ha-panel-iframe-md5md5.html',
|
||||
'url_path': 'router'
|
||||
}
|
||||
|
||||
assert panels.get('weather').as_dict() == {
|
||||
assert panels.get('weather').to_response(self.hass, None) == {
|
||||
'component_name': 'iframe',
|
||||
'config': {'url': 'https://www.wunderground.com/us/ca/san-diego'},
|
||||
'icon': 'mdi:weather',
|
||||
'title': 'Weather',
|
||||
'url': '/static/panels/ha-panel-iframe-md5md5.html',
|
||||
'url': '/frontend_es5/panels/ha-panel-iframe-md5md5.html',
|
||||
'url_path': 'weather',
|
||||
}
|
||||
|
|
|
@ -290,7 +290,7 @@ def test_get_panels(hass, websocket_client):
|
|||
"""Test get_panels command."""
|
||||
yield from hass.components.frontend.async_register_built_in_panel(
|
||||
'map', 'Map', 'mdi:account-location')
|
||||
|
||||
hass.data[frontend.DATA_JS_VERSION] = 'es5'
|
||||
websocket_client.send_json({
|
||||
'id': 5,
|
||||
'type': wapi.TYPE_GET_PANELS,
|
||||
|
@ -300,8 +300,14 @@ def test_get_panels(hass, websocket_client):
|
|||
assert msg['id'] == 5
|
||||
assert msg['type'] == wapi.TYPE_RESULT
|
||||
assert msg['success']
|
||||
assert msg['result'] == {url: panel.as_dict() for url, panel
|
||||
in hass.data[frontend.DATA_PANELS].items()}
|
||||
assert msg['result'] == {'map': {
|
||||
'component_name': 'map',
|
||||
'url_path': 'map',
|
||||
'config': None,
|
||||
'url': None,
|
||||
'icon': 'mdi:account-location',
|
||||
'title': 'Map',
|
||||
}}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
|
|
Loading…
Add table
Reference in a new issue