"""Tests for Nanoleaf extControl v2 per-panel streaming. The device-side (panelLayout fetch, extControl enable, UDP send) needs a real controller to validate; here we lock down the parts that DON'T: panel ordering, strip→panel resampling, the exact UDP packet framing, and the ``nanoleaf_per_panel`` config round-trip through the device store. """ from __future__ import annotations import struct from ledgrab.core.devices.nanoleaf_client import ( build_extcontrol_v2_packet, map_pixels_to_panels, order_panels, ) from ledgrab.storage.device_store import Device class TestOrderPanels: def test_sorts_by_x_then_y_and_drops_controller(self): position = [ {"panelId": 5, "x": 100, "y": 0}, {"panelId": 3, "x": 0, "y": 0}, {"panelId": 0, "x": 50, "y": 50}, # controller / rhythm → dropped {"panelId": 7, "x": 0, "y": 100}, ] assert order_panels(position) == [3, 7, 5] def test_ignores_non_integer_panel_ids(self): assert order_panels([{"panelId": "x", "x": 0, "y": 0}, {"x": 1, "y": 1}]) == [] class TestMapPixelsToPanels: def test_nearest_neighbour_resample(self): pixels = [[10, 10, 10], [20, 20, 20], [30, 30, 30], [40, 40, 40]] out = map_pixels_to_panels(pixels, [1, 2]) assert out == [(1, 10, 10, 10), (2, 30, 30, 30)] def test_empty_strip_is_black(self): assert map_pixels_to_panels([], [9, 8]) == [(9, 0, 0, 0), (8, 0, 0, 0)] def test_more_panels_than_pixels_repeats(self): out = map_pixels_to_panels([[255, 0, 0]], [1, 2, 3]) assert out == [(1, 255, 0, 0), (2, 255, 0, 0), (3, 255, 0, 0)] class TestPacket: def test_framing_is_byte_exact(self): panels = [(100, 10, 20, 30), (200, 40, 50, 60)] pkt = build_extcontrol_v2_packet(panels) assert len(pkt) == 2 + 2 * 8 # uint16 header + 8 bytes/panel assert struct.unpack(">H", pkt[0:2])[0] == 2 pid, r, g, b, w, trans = struct.unpack(">HBBBBH", pkt[2:10]) assert (pid, r, g, b, w, trans) == (100, 10, 20, 30, 0, 1) pid2, r2, g2, b2, w2, trans2 = struct.unpack(">HBBBBH", pkt[10:18]) assert (pid2, r2, g2, b2, w2, trans2) == (200, 40, 50, 60, 0, 1) def test_values_are_masked_to_byte_range(self): pkt = build_extcontrol_v2_packet([(70000, 300, -5, 256)]) pid, r, g, b, w, trans = struct.unpack(">HBBBBH", pkt[2:10]) assert pid == 70000 & 0xFFFF assert r == 300 & 0xFF and b == 256 & 0xFF class TestConfigRoundTrip: def _device(self, per_panel: bool) -> Device: return Device( device_id="d1", name="Shapes", url="nanoleaf://1.2.3.4", led_count=10, device_type="nanoleaf", nanoleaf_token="tok", nanoleaf_per_panel=per_panel, ) def test_per_panel_round_trips_through_store(self): d = self._device(True) assert d.to_dict().get("nanoleaf_per_panel") is True back = Device.from_dict(d.to_dict()) assert back.nanoleaf_per_panel is True assert back.to_config().nanoleaf_per_panel is True def test_default_off_is_omitted(self): d = self._device(False) assert "nanoleaf_per_panel" not in d.to_dict() assert d.to_config().nanoleaf_per_panel is False