diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..a7b7452
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,15 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: gensyn
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
+polar: # Replace with a single Polar username
+buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
+thanks_dev: # Replace with a single thanks.dev username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/README.md b/README.md
index 6ab3819..4666662 100644
--- a/README.md
+++ b/README.md
@@ -58,11 +58,13 @@
- Disable `key_down` by mapping it to `None`
### 🎨 Show an Icon
-- Display entity icons or custom icons from [Material Design Icons](https://pictogrammers.com/library/mdi/)
+- Display entity icons or custom icons from [Material Design Icons](https://pictogrammers.com/library/mdi/) as well as images and GIFs
- Customize icon appearance:
- 🎨 Color
- 📏 Scale
- 🌫️ Opacity
+- Customize image appearance:
+ - 📏 Scale
- **Dynamic customization** based on state or attribute values
- Automatic updates when entity state changes
@@ -396,12 +398,11 @@ Contributions are welcome! Feel free to:
- 📝 Improve documentation
- 🔧 Submit pull requests
----
-
+---
-**Made with ❤️ for the Home Assistant and StreamController communities**
+## ⭐ Support
-[](https://github.com/gensyn/HomeAssistantPlugin)
+If you find HomeAssistantPlugin useful, please consider giving it a star on GitHub! It helps others discover the project.
-
+[](https://ko-fi.com/M5G61ZZY8D)
diff --git a/actions/const.py b/actions/const.py
index 72ffb2b..e087bab 100644
--- a/actions/const.py
+++ b/actions/const.py
@@ -15,6 +15,7 @@
LABEL_NO_ENTITY = "actions.home_assistant.customization.no_entity.label"
SETTING_VERSION = "version"
+SETTING_VERSION_NUMBER = 1
SETTING_ENTITY = "entity"
SETTING_DOMAIN = "domain"
diff --git a/actions/cores/base_core/base_core.py b/actions/cores/base_core/base_core.py
index 2f6c41e..2b42d10 100644
--- a/actions/cores/base_core/base_core.py
+++ b/actions/cores/base_core/base_core.py
@@ -1,8 +1,10 @@
"""The module for the Home Assistant action that is loaded in StreamController."""
import gi
-from GtkHelper.GenerativeUI.ComboRow import ComboRow
from HomeAssistantPlugin.actions import const
+from HomeAssistantPlugin.actions.cores.base_core.migrate import migrate_settings
+
+from GtkHelper.GenerativeUI.ComboRow import ComboRow
from src.backend.PluginManager.ActionCore import ActionCore
gi.require_version('Gtk', '4.0')
@@ -48,6 +50,7 @@ def __init__(self, settings_implementation, track_entity: bool, *args, **kwargs)
def on_ready(self) -> None:
"""Set up action when StreamController has finished loading."""
+ migrate_settings(self)
self.settings = self.settings_implementation(self)
self.initialized = True
@@ -114,7 +117,11 @@ def on_change_domain(self, _, domain, old_domain):
if entity and self.track_entity:
self.plugin_base.backend.remove_tracked_entity(entity, self.refresh)
self.settings.reset(domain)
+ # save entities from the combo to a temporary variable to keep them alive while we clear the combo
+ _temp_keep_alive = [self.entity_combo.get_item(i) for i in range(self.entity_combo.get_n_items())]
self.entity_combo.remove_all_items()
+ # now the entities can be removed
+ del _temp_keep_alive
self._last_loaded_entities = None
if domain:
diff --git a/actions/cores/base_core/base_settings.py b/actions/cores/base_core/base_settings.py
index 2755b24..62eb14e 100644
--- a/actions/cores/base_core/base_settings.py
+++ b/actions/cores/base_core/base_settings.py
@@ -3,6 +3,7 @@
from HomeAssistantPlugin.actions import const
DEFAULT_SETTINGS = {
+ const.SETTING_VERSION: const.SETTING_VERSION_NUMBER,
const.SETTING_DOMAIN: const.EMPTY_STRING,
const.SETTING_ENTITY: const.EMPTY_STRING
}
diff --git a/actions/cores/base_core/migrate.py b/actions/cores/base_core/migrate.py
new file mode 100644
index 0000000..c122dfe
--- /dev/null
+++ b/actions/cores/base_core/migrate.py
@@ -0,0 +1,27 @@
+from HomeAssistantPlugin.actions import const
+from HomeAssistantPlugin.actions.cores.customization_core import customization_const
+from HomeAssistantPlugin.actions.show_icon import icon_const
+from HomeAssistantPlugin.actions.show_icon.icon_settings import ShowIconSettings
+
+
+def migrate_settings(action) -> None:
+ settings = action.get_settings()
+
+ if settings.get(const.SETTING_VERSION) is not None:
+ return
+
+ migrate_v0_v1(action)
+
+def migrate_v0_v1(action) -> None:
+ """
+ Migrate from no version to v1.
+ There is actually nothing to do yet - this just introduces the version field.
+ """
+ settings = action.get_settings()
+
+ if not settings:
+ # settings are empty - this is a brand-new action
+ return
+
+ settings[const.SETTING_VERSION] = const.SETTING_VERSION_NUMBER
+ action.set_settings(settings)
\ No newline at end of file
diff --git a/actions/show_icon/icon_action.py b/actions/show_icon/icon_action.py
index 3a2fdb6..c715d83 100644
--- a/actions/show_icon/icon_action.py
+++ b/actions/show_icon/icon_action.py
@@ -2,6 +2,16 @@
The module for the Home Assistant action that is loaded in StreamController.
"""
+import os
+
+import gi
+
+gi.require_version("Gtk", "4.0")
+from gi.repository import Gio
+from gi.repository.Gtk import Align, Button, FileDialog, FileFilter
+
+from loguru import logger as log
+
from HomeAssistantPlugin.actions.cores.base_core.base_core import requires_initialization
from HomeAssistantPlugin.actions.cores.customization_core.customization_core import CustomizationCore
from HomeAssistantPlugin.actions.show_icon import icon_const, icon_helper
@@ -43,6 +53,11 @@ def create_ui_elements(self) -> None:
complex_var_name=True
)
+ browse_button = Button(label=self.lm.get(icon_const.LABEL_ICON_BROWSE))
+ browse_button.set_valign(Align.CENTER)
+ browse_button.connect("clicked", self._on_browse_clicked)
+ self.icon.widget.add_suffix(browse_button)
+
self.color: ColorButtonRow = ColorButtonRow(
self, icon_const.SETTING_ICON_COLOR, icon_const.DEFAULT_ICON_COLOR,
title=icon_const.LABEL_ICON_COLOR, on_change=self._reload,
@@ -63,6 +78,7 @@ def create_ui_elements(self) -> None:
can_reset=False, complex_var_name=True
)
+
@requires_initialization
def set_enabled_disabled(self) -> None:
"""
@@ -87,17 +103,22 @@ def set_enabled_disabled(self) -> None:
self.opacity.widget.set_sensitive(False)
self.opacity.widget.set_subtitle(self.lm.get(icon_const.LABEL_ICON_NO_ENTITY))
+
else:
+ icon_value = self.settings.get_icon()
+ has_icon = bool(icon_value) and icon_value in icon_helper.MDI_ICONS
+ not_supported = self.lm.get(icon_const.LABEL_ICON_ONLY_SUPPORTED_FOR_ICONS) if not has_icon else icon_const.EMPTY_STRING
+
self.icon.widget.set_sensitive(True)
- self.color.widget.set_sensitive(True)
- self.color.widget.set_subtitle(icon_const.EMPTY_STRING)
+ self.color.widget.set_sensitive(has_icon)
+ self.color.widget.set_subtitle(not_supported)
self.scale.widget.set_sensitive(True)
self.scale.widget.set_subtitle(icon_const.EMPTY_STRING)
- self.opacity.widget.set_sensitive(True)
- self.opacity.widget.set_subtitle(icon_const.EMPTY_STRING)
+ self.opacity.widget.set_sensitive(has_icon)
+ self.opacity.widget.set_subtitle(not_supported)
def refresh(self, state: dict = None) -> None:
"""
@@ -123,6 +144,37 @@ def refresh(self, state: dict = None) -> None:
self._load_customizations()
self.set_enabled_disabled()
+ def _on_browse_clicked(self, *_) -> None:
+ dialog = FileDialog()
+ dialog.set_title(self.lm.get(icon_const.LABEL_ICON_IMAGE))
+
+ filter_images = FileFilter()
+ filter_images.set_name("Images")
+ for mime in ("image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"):
+ filter_images.add_mime_type(mime)
+
+ filters = Gio.ListStore.new(FileFilter)
+ filters.append(filter_images)
+ dialog.set_filters(filters)
+ dialog.set_default_filter(filter_images)
+
+ current_path = self.icon.widget.get_text()
+ if current_path:
+ current_dir = os.path.dirname(current_path)
+ if os.path.isdir(current_dir):
+ dialog.set_initial_folder(Gio.File.new_for_path(current_dir))
+
+ dialog.open(None, None, self._on_file_chosen)
+
+ def _on_file_chosen(self, dialog, result) -> None:
+ try:
+ file = dialog.open_finish(result)
+ if file:
+ self.icon.widget.set_text(file.get_path())
+ self._reload()
+ except Exception as e:
+ log.error(f"Error choosing file: {e}")
+
def _get_domains(self) -> list[str]:
"""This class needs all domains that provide actions in Home Assistant."""
return self.plugin_base.backend.get_domains_for_entities()
diff --git a/actions/show_icon/icon_const.py b/actions/show_icon/icon_const.py
index fef9395..cdbd391 100644
--- a/actions/show_icon/icon_const.py
+++ b/actions/show_icon/icon_const.py
@@ -31,6 +31,11 @@
CUSTOM_COLOR = "color"
CUSTOM_SCALE = "scale"
CUSTOM_OPACITY = "opacity"
+CUSTOM_IMAGE = "image"
+
+LABEL_ICON_IMAGE = "actions.home_assistant.icon.image.label"
+LABEL_ICON_BROWSE = "actions.home_assistant.icon.browse.label"
+LABEL_ICON_ONLY_SUPPORTED_FOR_ICONS = "actions.home_assistant.icon.only_supported_for_icons.label"
MDI_SVG_JSON = "assets/mdi-svg.json"
ICON_COLOR_RED = "#ff0000"
@@ -39,4 +44,4 @@
ATTRIBUTES = "attributes"
ATTRIBUTE_ICON = "icon"
STATE = "state"
-ERROR = "error"
\ No newline at end of file
+ERROR = "error"
diff --git a/actions/show_icon/icon_customization.py b/actions/show_icon/icon_customization.py
index 93ec74f..606b0c4 100644
--- a/actions/show_icon/icon_customization.py
+++ b/actions/show_icon/icon_customization.py
@@ -73,4 +73,4 @@ def export(self) -> dict[str, Any]:
icon_const.CUSTOM_COLOR: self.color,
icon_const.CUSTOM_SCALE: self.scale,
icon_const.CUSTOM_OPACITY: self.opacity
- }
+ }
\ No newline at end of file
diff --git a/actions/show_icon/icon_helper.py b/actions/show_icon/icon_helper.py
index d052add..8d39c1c 100644
--- a/actions/show_icon/icon_helper.py
+++ b/actions/show_icon/icon_helper.py
@@ -29,6 +29,10 @@ def get_icon(state: dict, settings: ShowIconSettings, is_connected: bool) -> tup
name, color, scale, opacity = _get_icon_settings(state, settings)
+ if name not in MDI_ICONS:
+ # color and opacity only have an effect on icons
+ return name, scale
+
# convert RGB color to hex
color = customization_helper.convert_color_list_to_hex(color)
@@ -39,14 +43,11 @@ def get_icon(state: dict, settings: ShowIconSettings, is_connected: bool) -> tup
def _get_icon_settings(state: dict, settings: ShowIconSettings) -> tuple[str, str, float, float]:
# default value for the icon is the icon set in HA
- name = state.get(icon_const.ATTRIBUTES, {}).get(icon_const.ATTRIBUTE_ICON, icon_const.EMPTY_STRING)
+ name = settings.get_icon() or state.get(icon_const.ATTRIBUTES, {}).get(icon_const.ATTRIBUTE_ICON, icon_const.EMPTY_STRING).replace("mdi:", icon_const.EMPTY_STRING)
color = settings.get_color()
scale = settings.get_scale()
opacity = settings.get_opacity()
- if settings.get_icon() in MDI_ICONS.keys():
- name = settings.get_icon()
-
#
# Begin custom icon
#
@@ -88,8 +89,7 @@ def _get_icon_settings(state: dict, settings: ShowIconSettings) -> tuple[str, st
or (operator == "<=" and value <= custom_icon_value)
or (operator == ">" and value > custom_icon_value)
or (operator == ">=" and value >= custom_icon_value)):
- name, color, scale, opacity = _replace_values(name, color, scale, opacity,
- customization)
+ name, color, scale, opacity = _replace_values(name, color, scale, opacity, customization)
#
# End custom icon
@@ -154,4 +154,4 @@ def _get_icon_svg(name: str) -> str:
return (f'')
+ f'/>')
\ No newline at end of file
diff --git a/actions/show_icon/icon_settings.py b/actions/show_icon/icon_settings.py
index 68304d1..523f5c0 100644
--- a/actions/show_icon/icon_settings.py
+++ b/actions/show_icon/icon_settings.py
@@ -54,4 +54,4 @@ def get_opacity(self) -> int:
Get the opacity.
:return: the opacity
"""
- return int(self._action.get_settings()[icon_const.SETTING_ICON][icon_const.SETTING_OPACITY])
+ return int(self._action.get_settings()[icon_const.SETTING_ICON][icon_const.SETTING_OPACITY])
\ No newline at end of file
diff --git a/actions/show_icon/icon_window.py b/actions/show_icon/icon_window.py
index c4b8215..31f81f4 100644
--- a/actions/show_icon/icon_window.py
+++ b/actions/show_icon/icon_window.py
@@ -2,9 +2,17 @@
The module for the Home Assistant customization icon window.
"""
+import os
from functools import partial
from typing import Callable
+import gi
+gi.require_version("Gtk", "4.0")
+from gi.repository import Gio
+from gi.repository.Gtk import Button, FileDialog, FileFilter
+
+from loguru import logger as log
+
from HomeAssistantPlugin.actions import const as base_const
from HomeAssistantPlugin.actions.cores.customization_core import customization_helper
from HomeAssistantPlugin.actions.cores.customization_core.customization_window import CustomizationWindow
@@ -39,6 +47,15 @@ def __init__(self, lm, attributes: list, callback: Callable,
self.icon.set_margin_end(self.default_margin)
self.connect_rows.append(
partial(self.icon.connect, base_const.CONNECT_ACTIVATE, self.on_widget_changed))
+ self.connect_rows.append(
+ partial(self.icon.connect, base_const.CONNECT_CHANGED, self._on_icon_changed))
+
+ browse_button = Button(label=self.lm.get(icon_const.LABEL_ICON_BROWSE))
+ browse_button.set_margin_top(self.default_margin)
+ browse_button.set_margin_bottom(self.default_margin)
+ browse_button.set_margin_start(self.default_margin)
+ browse_button.set_margin_end(15)
+ self.connect_rows.append(partial(browse_button.connect, base_const.CONNECT_CLICKED, self._on_browse_clicked))
self.color = self._create_color_button(self.check_color)
@@ -75,6 +92,7 @@ def __init__(self, lm, attributes: list, callback: Callable,
self.grid_fields.attach(self.check_icon, 2, 2, 1, 1)
self.grid_fields.attach(label_icon, 3, 2, 1, 1)
self.grid_fields.attach(self.icon, 4, 2, 1, 1)
+ self.grid_fields.attach(browse_button, 5, 2, 1, 1)
self.grid_fields.attach(self.check_color, 2, 3, 1, 1)
self.grid_fields.attach(label_color, 3, 3, 1, 1)
@@ -112,6 +130,7 @@ def set_current_values(self) -> None:
self.icon.set_text(self.current.get_icon() or icon_const.EMPTY_STRING)
self.check_icon.set_active(self.current.get_icon() is not None)
+ self._on_icon_changed()
if self.current.get_color():
rgba = customization_helper.convert_color_list_to_rgba(self.current.get_color())
@@ -134,18 +153,12 @@ def on_add_button(self, *_, **__) -> None:
if not super().on_add_button():
return
- if self.check_icon.get_active():
- icon = self.icon.get_text()
-
- if icon.startswith("mdi:"):
- icon = icon[4:]
-
- if icon not in self.icons:
- self.icon.add_css_class(icon_const.ERROR)
- return
+ if self.check_icon.get_active() and not self.icon.get_text():
+ self.icon.add_css_class(icon_const.ERROR)
+ return
if (not self.check_icon.get_active() and not self.check_color.get_active() and not
- self.check_scale.get_active() and not self.check_opacity.get_active()):
+ self.check_scale.get_active() and not self.check_opacity.get_active()):
self.check_icon.add_css_class(icon_const.ERROR)
self.check_color.add_css_class(icon_const.ERROR)
self.check_scale.add_css_class(icon_const.ERROR)
@@ -156,8 +169,7 @@ def on_add_button(self, *_, **__) -> None:
color = self.color.get_rgba() if self.check_color.get_active() else None
color_list = customization_helper.convert_rgba_to_color_list(color) if color else None
scale = int(self.scale.get_value()) if self.check_scale.get_active() else None
- opacity = int(
- self.opacity.get_value()) if self.check_opacity.get_active() else None
+ opacity = int(self.opacity.get_value()) if self.check_opacity.get_active() else None
self.callback(customization=IconCustomization(
attribute=self.condition_attribute.get_selected_item().get_string(),
@@ -167,6 +179,43 @@ def on_add_button(self, *_, **__) -> None:
self.destroy()
+ def _on_icon_changed(self, *_) -> None:
+ icon_value = self.icon.get_text()
+ has_icon = bool(icon_value) and icon_value in icon_helper.MDI_ICONS
+ for widget in [self.check_color, self.color, self.check_opacity, self.opacity, self.opacity_entry]:
+ widget.set_sensitive(has_icon)
+
+ def _on_browse_clicked(self, *_) -> None:
+ dialog = FileDialog()
+ dialog.set_title(self.lm.get(icon_const.LABEL_ICON_IMAGE))
+
+ filter_images = FileFilter()
+ filter_images.set_name("Images")
+ for mime in ("image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"):
+ filter_images.add_mime_type(mime)
+
+ filters = Gio.ListStore.new(FileFilter)
+ filters.append(filter_images)
+ dialog.set_filters(filters)
+ dialog.set_default_filter(filter_images)
+
+ if self.icon.get_text():
+ current_dir = os.path.dirname(self.icon.get_text())
+ if os.path.isdir(current_dir):
+ dialog.set_initial_folder(Gio.File.new_for_path(current_dir))
+
+ dialog.open(self, None, self._on_file_chosen)
+
+ def _on_file_chosen(self, dialog, result) -> None:
+ try:
+ file = dialog.open_finish(result)
+ if file:
+ self.icon.set_text(file.get_path())
+ self.check_icon.set_active(True)
+ self._on_icon_changed()
+ except Exception as e:
+ log.error(f"Error choosing file: {e}")
+
def on_widget_changed(self, *_, **__) -> None:
super().on_widget_changed()
diff --git a/assets/show_icon.png b/assets/show_icon.png
index d858b9f..56856ca 100644
Binary files a/assets/show_icon.png and b/assets/show_icon.png differ
diff --git a/assets/show_icon_customize.png b/assets/show_icon_customize.png
index 3c63760..eda68dd 100644
Binary files a/assets/show_icon_customize.png and b/assets/show_icon_customize.png differ
diff --git a/locales/de_DE.json b/locales/de_DE.json
index b889e60..47ebac7 100644
--- a/locales/de_DE.json
+++ b/locales/de_DE.json
@@ -28,6 +28,9 @@
"actions.home_assistant.icon.opacity.label": "Deckkraft:",
"actions.home_assistant.icon.scale.label": "Größe:",
"actions.home_assistant.icon.custom_icon.label": "Anpassen:",
+ "actions.home_assistant.icon.image.label": "Bild:",
+ "actions.home_assistant.icon.browse.label": "Durchsuchen...",
+ "actions.home_assistant.icon.only_supported_for_icons.label": "Nur für Icons unterstützt",
"actions.home_assistant.icon.no_entity.label": "Keine Entität ausgewählt",
"actions.home_assistant.text.show_text.label": "Text anzeigen:",
diff --git a/locales/en_US.json b/locales/en_US.json
index d921814..1ad2f41 100644
--- a/locales/en_US.json
+++ b/locales/en_US.json
@@ -28,6 +28,9 @@
"actions.home_assistant.icon.opacity.label": "Opacity:",
"actions.home_assistant.icon.scale.label": "Scale:",
"actions.home_assistant.icon.custom_icon.label": "Customize:",
+ "actions.home_assistant.icon.browse.label": "Browse...",
+ "actions.home_assistant.icon.image.label": "Image:",
+ "actions.home_assistant.icon.only_supported_for_icons.label": "Only supported for icons",
"actions.home_assistant.icon.no_entity.label": "No entity selected",
"actions.home_assistant.text.show_text.label": "Show text:",
diff --git a/test/actions/cores/base_core/test_base_core_on_change_domain.py b/test/actions/cores/base_core/test_base_core_on_change_domain.py
index 8e34a95..faff3a6 100644
--- a/test/actions/cores/base_core/test_base_core_on_change_domain.py
+++ b/test/actions/cores/base_core/test_base_core_on_change_domain.py
@@ -58,6 +58,7 @@ def test_on_change_domain_no_entity(self, set_enabled_disabled_mock, load_entiti
entity_combo_mock = Mock()
entity_combo_mock.remove_all_items = remove_all_items_mock
+ entity_combo_mock.get_n_items = Mock(return_value=0)
domain = "light"
@@ -89,6 +90,7 @@ def test_on_change_domain_enity_not_tracked(self, set_enabled_disabled_mock, loa
entity_combo_mock = Mock()
entity_combo_mock.remove_all_items = remove_all_items_mock
+ entity_combo_mock.get_n_items = Mock(return_value=0)
domain = "light"
@@ -120,6 +122,7 @@ def test_on_change_domain_no_new_domain(self, set_enabled_disabled_mock, load_en
entity_combo_mock = Mock()
entity_combo_mock.remove_all_items = remove_all_items_mock
+ entity_combo_mock.get_n_items = Mock(return_value=0)
domain = None
@@ -151,6 +154,7 @@ def test_on_change_domain_success(self, set_enabled_disabled_mock, load_entities
entity_combo_mock = Mock()
entity_combo_mock.remove_all_items = remove_all_items_mock
+ entity_combo_mock.get_n_items = Mock(return_value=0)
domain = "light"
@@ -180,6 +184,7 @@ def test_on_change_domain_resets_entity_cache(self, set_enabled_disabled_mock, l
settings_mock.reset = Mock()
entity_combo_mock = Mock()
+ entity_combo_mock.get_n_items = Mock(return_value=0)
instance = BaseCore(Mock(), True)
instance.initialized = True
diff --git a/test/actions/cores/base_core/test_base_core_on_ready.py b/test/actions/cores/base_core/test_base_core_on_ready.py
index 1dc9076..0c06631 100644
--- a/test/actions/cores/base_core/test_base_core_on_ready.py
+++ b/test/actions/cores/base_core/test_base_core_on_ready.py
@@ -18,7 +18,8 @@ class TestBaseCoreOnReady(unittest.TestCase):
@patch.object(BaseCore, "_create_event_assigner")
@patch.object(BaseCore, "_load_domains")
@patch.object(BaseCore, "_load_entities")
- def test_on_read_no_entity(self, load_entities_mock, load_domains_mock, _, __):
+ @patch('HomeAssistantPlugin.actions.cores.base_core.base_core.migrate_settings')
+ def test_on_read_no_entity(self, migrate_settings_mock, load_entities_mock, load_domains_mock, _, __):
track_entity = True
settings_implementation = Mock()
@@ -28,6 +29,7 @@ def test_on_read_no_entity(self, load_entities_mock, load_domains_mock, _, __):
instance = BaseCore(settings_implementation, track_entity)
instance.on_ready()
+ migrate_settings_mock.assert_called_once_with(instance)
instance.plugin_base.backend.add_action_ready_callback.assert_called_once_with(instance.on_ready)
settings_implementation.get_entity.assert_called_once()
instance.plugin_base.backend.add_tracked_entity.assert_not_called()
@@ -38,7 +40,8 @@ def test_on_read_no_entity(self, load_entities_mock, load_domains_mock, _, __):
@patch.object(BaseCore, "_create_event_assigner")
@patch.object(BaseCore, "_load_domains")
@patch.object(BaseCore, "_load_entities")
- def test_on_read_entity_not_tracked(self, load_entities_mock, load_domains_mock, _, __):
+ @patch('HomeAssistantPlugin.actions.cores.base_core.base_core.migrate_settings')
+ def test_on_read_entity_not_tracked(self, migrate_settings_mock, load_entities_mock, load_domains_mock, _, __):
track_entity = False
settings_implementation = Mock()
@@ -48,6 +51,7 @@ def test_on_read_entity_not_tracked(self, load_entities_mock, load_domains_mock,
instance = BaseCore(settings_implementation, track_entity)
instance.on_ready()
+ migrate_settings_mock.assert_called_once_with(instance)
instance.plugin_base.backend.add_action_ready_callback.assert_called_once_with(instance.on_ready)
settings_implementation.get_entity.assert_called_once()
instance.plugin_base.backend.add_tracked_entity.assert_not_called()
@@ -58,7 +62,8 @@ def test_on_read_entity_not_tracked(self, load_entities_mock, load_domains_mock,
@patch.object(BaseCore, "_create_event_assigner")
@patch.object(BaseCore, "_load_domains")
@patch.object(BaseCore, "_load_entities")
- def test_on_read_success(self, load_entities_mock, load_domains_mock, _, __):
+ @patch('HomeAssistantPlugin.actions.cores.base_core.base_core.migrate_settings')
+ def test_on_read_success(self, migrate_settings_mock, load_entities_mock, load_domains_mock, _, __):
track_entity = True
settings_implementation = Mock()
@@ -68,9 +73,9 @@ def test_on_read_success(self, load_entities_mock, load_domains_mock, _, __):
instance = BaseCore(settings_implementation, track_entity)
instance.on_ready()
+ migrate_settings_mock.assert_called_once_with(instance)
instance.plugin_base.backend.add_action_ready_callback.assert_called_once_with(instance.on_ready)
settings_implementation.get_entity.assert_called_once()
instance.plugin_base.backend.add_tracked_entity.assert_called_once_with("entity", instance.refresh)
load_entities_mock.assert_called_once()
load_domains_mock.assert_called_once()
-
diff --git a/test/actions/show_icon/test_icon_action.py b/test/actions/show_icon/test_icon_action.py
index cb8ff5a..176c760 100644
--- a/test/actions/show_icon/test_icon_action.py
+++ b/test/actions/show_icon/test_icon_action.py
@@ -10,6 +10,7 @@
sys.path.insert(0, absolute_plugin_path)
from HomeAssistantPlugin.actions.show_icon import icon_const
+from HomeAssistantPlugin.actions.show_icon import icon_helper
from HomeAssistantPlugin.actions.show_icon.icon_action import ShowIcon
from HomeAssistantPlugin.actions.show_icon.icon_customization import IconCustomization
from HomeAssistantPlugin.actions.show_icon.icon_row import IconRow
@@ -68,9 +69,12 @@ def test_get_config_rows(self):
@patch('HomeAssistantPlugin.actions.show_icon.icon_action.EntryRow')
@patch('HomeAssistantPlugin.actions.show_icon.icon_action.ColorButtonRow')
@patch('HomeAssistantPlugin.actions.show_icon.icon_action.ScaleRow')
- def testcreate_ui_elements(self, scale_row_mock, color_button_row_mock, entry_row_mock, super_create_ui_elements_mock):
+ @patch('HomeAssistantPlugin.actions.show_icon.icon_action.Button')
+ def testcreate_ui_elements(self, button_mock, scale_row_mock, color_button_row_mock, entry_row_mock, super_create_ui_elements_mock):
instance = ShowIcon.__new__(ShowIcon)
instance._reload = Mock()
+ instance.lm = Mock()
+ instance.lm.get.return_value = "Browse"
instance.create_ui_elements()
@@ -162,10 +166,12 @@ def test_set_enabled_disabled_success(self, super_set_enabled_disabled_mock):
instance.settings = Mock()
instance.settings.get_domain.return_value = "domain"
instance.settings.get_entity.return_value = "entity"
+ instance.settings.get_icon.return_value = next(iter(icon_helper.MDI_ICONS))
instance.icon = Mock()
instance.color = Mock()
instance.scale = Mock()
instance.opacity = Mock()
+ instance.lm = Mock()
instance.set_enabled_disabled()
@@ -273,4 +279,4 @@ def test_get_domains(self):
result = instance._get_domains()
instance.plugin_base.backend.get_domains_for_entities.assert_called_once()
- self.assertEqual(result, ["domain1", "domain2"])
\ No newline at end of file
+ self.assertEqual(result, ["domain1", "domain2"])
diff --git a/test/actions/show_icon/test_icon_window.py b/test/actions/show_icon/test_icon_window.py
index 4c6180c..a716bce 100644
--- a/test/actions/show_icon/test_icon_window.py
+++ b/test/actions/show_icon/test_icon_window.py
@@ -24,7 +24,8 @@ class TestIconWindow(unittest.TestCase):
@patch.object(IconWindow, "_create_scale")
@patch.object(IconWindow, "_create_scale_entry")
@patch.object(IconWindow, "_create_label")
- def test_init(self, create_label_mock, create_scale_entry_mock, create_scale_mock, create_color_button_mock,
+ @patch('HomeAssistantPlugin.actions.show_icon.icon_window.Button')
+ def test_init(self, button_mock, create_label_mock, create_scale_entry_mock, create_scale_mock, create_color_button_mock,
create_entry_mock, create_check_button_mock, after_init_mock, set_title_mock,
customization_window_init_mock):
lm = Mock()
@@ -51,7 +52,7 @@ def super_init(instance, lm, *args, **kwargs):
customization_window_init_mock.assert_called_once_with(instance, lm, attributes, callback, current,
index) # with instance because of autospec
set_title_mock.assert_called_once_with("Test Title")
- self.assertEqual(14, instance.grid_fields.attach.call_count)
+ self.assertEqual(15, instance.grid_fields.attach.call_count)
after_init_mock.assert_called_once()
@patch(
@@ -132,15 +133,14 @@ def test_on_add_button_super_false(self, super_on_add_button_mock):
@patch(
'HomeAssistantPlugin.actions.cores.customization_core.customization_window.CustomizationWindow.on_add_button')
- def test_on_add_button_icon_not_valid(self, super_on_add_button_mock):
+ def test_on_add_button_icon_checked_but_empty(self, super_on_add_button_mock):
super_on_add_button_mock.return_value = True
instance = IconWindow.__new__(IconWindow)
instance.check_icon = Mock()
instance.check_icon.get_active.return_value = True
instance.icon = Mock()
- instance.icon.get_text.return_value = "mdi:home"
- instance.icons = ["not_home"]
+ instance.icon.get_text.return_value = ""
instance.on_add_button()
super_on_add_button_mock.assert_called_once()