"""Tests for mcgibs.colormaps -- XML parsing and natural-language explanations.""" import pytest from mcgibs.colormaps import ( _describe_rgb, _parse_interval_value, explain_colormap, parse_colormap, ) # --------------------------------------------------------------------------- # parse_colormap # --------------------------------------------------------------------------- def test_parse_colormap_map_count(colormap_xml: str): """Sample XML contains exactly 2 ColorMap elements.""" result = parse_colormap(colormap_xml) assert len(result.maps) == 2 def test_parse_colormap_data_entries(colormap_xml: str): """First ColorMap has 14 data entries, correct title and units.""" result = parse_colormap(colormap_xml) first = result.maps[0] assert len(first.entries) == 14 assert first.title == "Surface Air Temperature" assert first.units == "K" def test_parse_colormap_nodata_entry(colormap_xml: str): """Second ColorMap has a nodata entry labelled 'Missing Data'.""" result = parse_colormap(colormap_xml) second = result.maps[1] nodata = [e for e in second.entries if e.nodata] assert len(nodata) == 1 assert nodata[0].label == "Missing Data" def test_parse_colormap_rgb_values(colormap_xml: str): """First entry of the first ColorMap has rgb (227, 245, 255).""" result = parse_colormap(colormap_xml) first_entry = result.maps[0].entries[0] assert first_entry.rgb == (227, 245, 255) def test_parse_colormap_value_intervals(colormap_xml: str): """First entry value is '[-INF,200.0)', last entry is '[320.0,+INF)'.""" result = parse_colormap(colormap_xml) entries = result.maps[0].entries assert entries[0].value == "[-INF,200.0)" assert entries[-1].value == "[320.0,+INF)" def test_parse_colormap_legend(colormap_xml: str): """First ColorMap has legend entries with expected tooltips.""" result = parse_colormap(colormap_xml) legend = result.maps[0].legend assert len(legend) == 5 tooltips = [le.tooltip for le in legend] assert "< 200 K" in tooltips assert "200 - 210 K" in tooltips assert "> 320 K" in tooltips # --------------------------------------------------------------------------- # _parse_interval_value # --------------------------------------------------------------------------- def test_parse_interval_value_bounded(): """Bounded interval '[200.0,200.5)' parses to (200.0, 200.5).""" assert _parse_interval_value("[200.0,200.5)") == (200.0, 200.5) def test_parse_interval_value_neg_inf(): """Negative infinity '[-INF,200.0)' parses to (None, 200.0).""" assert _parse_interval_value("[-INF,200.0)") == (None, 200.0) def test_parse_interval_value_pos_inf(): """Positive infinity '[320.0,+INF)' parses to (320.0, None).""" assert _parse_interval_value("[320.0,+INF)") == (320.0, None) def test_parse_interval_value_single(): """Single value '[42]' parses to (42.0, 42.0).""" assert _parse_interval_value("[42]") == (42.0, 42.0) # --------------------------------------------------------------------------- # explain_colormap # --------------------------------------------------------------------------- def test_explain_colormap_includes_title(colormap_xml: str): """Explanation text contains the layer title.""" cms = parse_colormap(colormap_xml) text = explain_colormap(cms) assert "Surface Air Temperature" in text def test_explain_colormap_includes_units(colormap_xml: str): """Explanation mentions the native unit and Celsius conversion.""" cms = parse_colormap(colormap_xml) text = explain_colormap(cms) assert "(K)" in text assert "C)" in text # Celsius conversion appears as e.g. "(-73 C)" def test_explain_colormap_nodata_mention(colormap_xml: str): """Explanation mentions 'Missing Data' from the nodata entry.""" cms = parse_colormap(colormap_xml) text = explain_colormap(cms) assert "Missing Data" in text # --------------------------------------------------------------------------- # _describe_rgb # --------------------------------------------------------------------------- @pytest.mark.parametrize( ("rgb", "expected_substring"), [ ((255, 0, 0), "red"), ((0, 128, 0), "green"), ((0, 0, 255), "blue"), ((255, 255, 255), "white"), ((0, 0, 0), "black"), ((255, 255, 0), "yellow"), ], ) def test_describe_rgb_basic(rgb: tuple[int, int, int], expected_substring: str): """Known RGB triples produce color names containing the expected word.""" result = _describe_rgb(rgb) assert expected_substring in result.lower() # --------------------------------------------------------------------------- # _parse_rgb error handling (H3 Hamilton fix) # --------------------------------------------------------------------------- def test_parse_rgb_valid(): from mcgibs.colormaps import _parse_rgb assert _parse_rgb("255,128,0") == (255, 128, 0) def test_parse_rgb_too_few_parts(): from mcgibs.colormaps import _parse_rgb assert _parse_rgb("255,128") == (0, 0, 0) def test_parse_rgb_empty_string(): from mcgibs.colormaps import _parse_rgb assert _parse_rgb("") == (0, 0, 0) def test_parse_rgb_non_numeric(): from mcgibs.colormaps import _parse_rgb assert _parse_rgb("abc,def,ghi") == (0, 0, 0) # --------------------------------------------------------------------------- # _parse_interval_value edge cases # --------------------------------------------------------------------------- def test_parse_interval_value_bare_number(): """Bare number without brackets (used in sea ice colormaps).""" assert _parse_interval_value("42") == (42.0, 42.0) def test_parse_interval_value_empty_string(): assert _parse_interval_value("") == (None, None) def test_parse_interval_value_scientific_notation(): low, high = _parse_interval_value("[1.5e2,2.0e2)") assert low == 150.0 assert high == 200.0 # --------------------------------------------------------------------------- # explain_colormap: classification colormap # --------------------------------------------------------------------------- def test_explain_colormap_classification(): """Classification colormaps should produce categorical descriptions.""" from mcgibs.models import ColorMap, ColorMapEntry, ColorMapSet entries = [ ColorMapEntry(rgb=(0, 0, 255), label="Water"), ColorMapEntry(rgb=(0, 128, 0), label="Forest"), ColorMapEntry(rgb=(255, 255, 0), label="Desert"), ] cm = ColorMap(title="Land Cover", legend_type="classification", entries=entries) cms = ColorMapSet(maps=[cm]) text = explain_colormap(cms) assert "Land Cover" in text assert "classification" in text assert "Water" in text assert "Forest" in text