Skip to content

Commit

Permalink
Add tests for new SVG camera and remove old ones
Browse files Browse the repository at this point in the history
  • Loading branch information
jdejaegh committed Jan 2, 2024
1 parent c9ec30b commit b7d39bf
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 76 deletions.
1 change: 0 additions & 1 deletion custom_components/irm_kmi/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ def camera_image(self,
width: int | None = None,
height: int | None = None) -> bytes | None:
"""Return still image to be used as thumbnail."""
# TODO make it a still image to avoid cuts in playback on the dashboard
return self.coordinator.data.get('animation', {}).get('svg_still')

async def async_camera_image(
Expand Down
1 change: 0 additions & 1 deletion custom_components/irm_kmi/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ class CurrentWeatherData(TypedDict, total=False):
pressure: float | None


# TODO cleanup useless fields
class AnimationFrameData(TypedDict, total=False):
"""Holds one single frame of the radar camera, along with the timestamp of the frame"""
time: datetime | None
Expand Down
4 changes: 1 addition & 3 deletions custom_components/irm_kmi/rain_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@ def __init__(self,
self.draw_location()
self._dwg_still = self._dwg

self._dwg_animated.saveas("animated_rain.svg")

def draw_svg_frame(self):
"""Create the global area to draw the other items"""
self._dwg.embed_font(name="Roboto Medium", filename='custom_components/irm_kmi/resources/roboto_medium.ttf')
Expand Down Expand Up @@ -330,4 +328,4 @@ def draw_location(self):
self._dwg.add(image)

def get_dwg(self):
return copy.deepcopy(self._dwg)
return copy.deepcopy(self._dwg)
71 changes: 0 additions & 71 deletions tests/test_coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,74 +107,3 @@ def test_hourly_forecast() -> None:
)

assert result[8] == expected


@freeze_time(datetime.fromisoformat("2023-12-28T15:30:00+01:00"))
@pytest.mark.skip(reason="Outdated test, cannot be tested this way with the new camera features")
async def test_get_image_nl(
hass: HomeAssistant,
mock_image_irm_kmi_api: AsyncMock,
mock_config_entry: MockConfigEntry) -> None:
api_data = get_api_data("forecast_nl.json")
coordinator = IrmKmiCoordinator(hass, mock_config_entry)

result = await coordinator._async_animation_data(api_data)

# Construct the expected image for the most recent one
tz = pytz.timezone(hass.config.time_zone)
background = Image.open("custom_components/irm_kmi/resources/nl.png").convert('RGBA')
layer = Image.open("tests/fixtures/clouds_nl.png").convert('RGBA')
localisation = Image.open("tests/fixtures/loc_layer_nl.png").convert('RGBA')
temp = Image.alpha_composite(background, layer)
expected = Image.alpha_composite(temp, localisation)
draw = ImageDraw.Draw(expected)
font = ImageFont.truetype("custom_components/irm_kmi/resources/roboto_medium.ttf", 16)
time_image = (datetime.fromisoformat("2023-12-28T14:25:00+00:00")
.astimezone(tz=tz))
time_str = time_image.isoformat(sep=' ', timespec='minutes')
draw.text((4, 4), time_str, (0, 0, 0), font=font)

result_image = Image.open(BytesIO(result['sequence'][-1]['image'])).convert('RGBA')

assert list(result_image.getdata()) == list(expected.getdata())
most_recent_image = result['sequence'][result['most_recent_image_idx']]['image']
thumb_image = Image.open(BytesIO(most_recent_image)).convert('RGBA')
assert list(thumb_image.getdata()) == list(expected.getdata())

assert result['hint'] == "No rain forecasted shortly"


@freeze_time(datetime.fromisoformat("2023-12-26T18:31:00+01:00"))
@pytest.mark.skip(reason="Outdated test, cannot be tested this way with the new camera features")
async def test_get_image_be(
hass: HomeAssistant,
mock_image_irm_kmi_api: AsyncMock,
mock_config_entry: MockConfigEntry
) -> None:
api_data = get_api_data("forecast.json")
coordinator = IrmKmiCoordinator(hass, mock_config_entry)

result = await coordinator._async_animation_data(api_data)

# Construct the expected image for the most recent one
tz = pytz.timezone(hass.config.time_zone)
background = Image.open("custom_components/irm_kmi/resources/be_black.png").convert('RGBA')
layer = Image.open("tests/fixtures/clouds_be.png").convert('RGBA')
localisation = Image.open("tests/fixtures/loc_layer_be_n.png").convert('RGBA')
temp = Image.alpha_composite(background, layer)
expected = Image.alpha_composite(temp, localisation)
draw = ImageDraw.Draw(expected)
font = ImageFont.truetype("custom_components/irm_kmi/resources/roboto_medium.ttf", 16)
time_image = (datetime.fromisoformat("2023-12-26T18:30:00+01:00")
.astimezone(tz=tz))
time_str = time_image.isoformat(sep=' ', timespec='minutes')
draw.text((4, 4), time_str, (255, 255, 255), font=font)

result_image = Image.open(BytesIO(result['sequence'][9]['image'])).convert('RGBA')

assert list(result_image.getdata()) == list(expected.getdata())
most_recent_image = result['sequence'][result['most_recent_image_idx']]['image']
thumb_image = Image.open(BytesIO(most_recent_image)).convert('RGBA')
assert list(thumb_image.getdata()) == list(expected.getdata())

assert result['hint'] == "No rain forecasted shortly"
278 changes: 278 additions & 0 deletions tests/test_rain_graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
import base64
from datetime import datetime, timedelta

from custom_components.irm_kmi.data import (AnimationFrameData,
RadarAnimationData)
from custom_components.irm_kmi.rain_graph import RainGraph


def get_radar_animation_data() -> RadarAnimationData:
with open("tests/fixtures/clouds_be.png", "rb") as file:
image_data = file.read()
with open("tests/fixtures/loc_layer_be_n.png", "rb") as file:
location = file.read()

sequence = [
AnimationFrameData(
time=datetime.fromisoformat("2023-12-26T18:30:00+00:00") + timedelta(minutes=10 * i),
image=image_data,
value=2,
position=.5,
position_lower=.4,
position_higher=.6
)
for i in range(10)
]

return RadarAnimationData(
sequence=sequence,
most_recent_image_idx=2,
hint="Testing SVG camera",
unit="mm/10min",
location=location
)


def test_svg_frame_setup():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)

rain_graph.draw_svg_frame()

svg_str = rain_graph.get_dwg().tostring()

with open("custom_components/irm_kmi/resources/roboto_medium.ttf", "rb") as file:
font_b64 = base64.b64encode(file.read()).decode('utf-8')

assert '#385E95' in svg_str
assert 'font-family: "Roboto Medium";' in svg_str
assert font_b64 in svg_str


def test_svg_hint():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)

rain_graph.write_hint()

svg_str = rain_graph.get_dwg().tostring()

assert "Testing SVG camera" in svg_str


def test_svg_time_bars():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)

rain_graph.draw_hour_bars()

svg_str = rain_graph.get_dwg().tostring()

assert "19h" in svg_str
assert "20h" in svg_str

assert "<line" in svg_str
assert 'stroke="white"' in svg_str


def test_draw_chances_path():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)

rain_graph.draw_chances_path()

svg_str = rain_graph.get_dwg().tostring()

assert 'fill="#63c8fa"' in svg_str
assert 'opacity="0.3"' in svg_str
assert 'stroke="none"' in svg_str
assert '<path ' in svg_str


def test_draw_data_line():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)

rain_graph.draw_data_line()

svg_str = rain_graph.get_dwg().tostring()

assert 'fill="none"' in svg_str
assert 'stroke-width="2"' in svg_str
assert 'stroke="#63c8fa"' in svg_str
assert '<path ' in svg_str


def test_insert_background():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)

rain_graph.insert_background()

with open("custom_components/irm_kmi/resources/be_white.png", "rb") as file:
png_b64 = base64.b64encode(file.read()).decode('utf-8')

svg_str = rain_graph.get_dwg().tostring()

assert png_b64 in svg_str
assert "<image " in svg_str
assert 'height="490"' in svg_str
assert 'width="640"' in svg_str
assert 'x="0"' in svg_str
assert 'y="0"' in svg_str


def test_draw_current_frame_line_moving():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)

rain_graph.draw_current_fame_line()

str_svg = rain_graph.get_dwg().tostring()

assert '<line' in str_svg
assert 'id="now"' in str_svg
assert 'opacity="1"' in str_svg
assert 'stroke="white"' in str_svg
assert 'stroke-width="2"' in str_svg
assert 'x1="50' in str_svg
assert 'x2="50' in str_svg
assert 'y1="520' in str_svg
assert 'y2="670' in str_svg

assert 'animateTransform' in str_svg
assert 'attributeName="transform"' in str_svg
assert 'repeatCount="indefinite"' in str_svg
assert 'type="translate"' in str_svg


def test_draw_current_frame_line_index():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)

rain_graph.draw_current_fame_line(0)

str_svg = rain_graph.get_dwg().tostring()

assert '<line' in str_svg
assert 'id="now"' in str_svg
assert 'opacity="1"' in str_svg
assert 'stroke="white"' in str_svg
assert 'stroke-width="2"' in str_svg
assert 'x1="50' in str_svg
assert 'x2="50' in str_svg
assert 'y1="520' in str_svg
assert 'y2="670' in str_svg

assert 'animateTransform' not in str_svg
assert 'attributeName="transform"' not in str_svg
assert 'repeatCount="indefinite"' not in str_svg
assert 'type="translate"' not in str_svg


def test_draw_description_text():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)

rain_graph.draw_description_text()

str_svg = rain_graph.get_dwg().tostring()

assert "18:30" in str_svg
assert "18:40" in str_svg
assert "18:50" in str_svg
assert "19:00" in str_svg
assert "19:10" in str_svg
assert "19:20" in str_svg
assert "19:30" in str_svg
assert "19:40" in str_svg
assert "19:50" in str_svg
assert "20:00" in str_svg

assert str_svg.count("2mm/10") == 10
assert 'class="roboto"' in str_svg


def test_draw_cloud_layer():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)

rain_graph.insert_cloud_layer()

str_svg = rain_graph.get_dwg().tostring()

with open("tests/fixtures/clouds_be.png", "rb") as file:
png_b64 = base64.b64encode(file.read()).decode('utf-8')

assert str_svg.count(png_b64) == 10
assert str_svg.count('height="490"') == 10
assert str_svg.count('width="640"') == 11 # Is also the width of the SVG itself


def test_draw_location_layer():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)

rain_graph.draw_location()

str_svg = rain_graph.get_dwg().tostring()

with open("tests/fixtures/loc_layer_be_n.png", "rb") as file:
png_b64 = base64.b64encode(file.read()).decode('utf-8')

assert png_b64 in str_svg

0 comments on commit b7d39bf

Please sign in to comment.