From 723aa00cf80bcdda10224c00b9e3f5e693f8fcaa Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Mon, 27 Oct 2025 20:57:51 +0100 Subject: [PATCH 1/6] add unittest for demos --- ultraplot/tests/test_demos.py | 108 ++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 ultraplot/tests/test_demos.py diff --git a/ultraplot/tests/test_demos.py b/ultraplot/tests/test_demos.py new file mode 100644 index 000000000..11e47f7f1 --- /dev/null +++ b/ultraplot/tests/test_demos.py @@ -0,0 +1,108 @@ +# ultraplot/ultraplot/tests/test_demos.py +import math +import numpy as np +import pytest +import ultraplot as plt +import matplotlib.font_manager as mfonts + +import ultraplot.demos as demos + + +def test_show_channels_requires_arg(): + """show_channels should raise when no positional colormap is provided.""" + with pytest.raises(ValueError): + demos.show_channels() + + +def test_show_channels_basic(): + """Basic invocation of show_channels returns a figure and axes of expected length.""" + # Request fewer samples and fewer channels to keep the figure small + fig, axs = demos.show_channels("viridis", N=20, saturation=False, rgb=False) + # Expect three channel plots: Hue, Chroma, Luminance + assert fig is not None + assert hasattr(axs, "__len__") + assert len(axs) == 3 + # Each axis should have a title set + for ax in axs: + assert isinstance(ax.get_title(), str) + + +def test_show_colorspaces_default_and_options(): + """show_colorspaces should create three panels (hcl/hsl/hpl).""" + fig, axs = demos.show_colorspaces() + assert fig is not None + assert hasattr(axs, "__len__") + assert len(axs) == 3 + # Titles should include the space names + titles = [ax.get_title().upper() for ax in axs] + assert any("HCL" in t or "HSL" in t or "HPL" in t for t in titles) + + +def test_show_cmaps_and_cycles_return_fig_and_axes(): + """show_cmaps and show_cycles should return a figure and axes collection.""" + fig_c, axs_c = demos.show_cmaps() + fig_y, axs_y = demos.show_cycles() + assert fig_c is not None and fig_y is not None + assert hasattr(axs_c, "__len__") and hasattr(axs_y, "__len__") + assert len(axs_c) > 0 + assert len(axs_y) > 0 + + +def test__filter_colors_behavior(): + """Unit tests for the color filtering helper.""" + # When ihue == 0, function should return True for gray colors (sat <= minsat) + hcl_gray = (10.0, 5.0, 50.0) # hue, sat, lum + assert demos._filter_colors(hcl_gray, ihue=0, nhues=8, minsat=10) + + # Non-gray color should be filtered according to hue buckets + nhues = 4 + hcl_color = (100.0, 50.0, 50.0) + breakpoints = np.linspace(0, 360, nhues) + found = False + for ihue in range(1, nhues): + low = breakpoints[ihue - 1] + high = breakpoints[ihue] + if low <= hcl_color[0] < high or (ihue == nhues - 1 and hcl_color[0] == high): + assert demos._filter_colors(hcl_color, ihue=ihue, nhues=nhues, minsat=10) + found = True + break + assert found, "Did not find a matching hue interval for the test hue" + + # Hue at the endpoint should be included for last bucket + hcl_endpoint = (360.0, 50.0, 50.0) + # For nhues=4 the last bucket index is 3; the endpoint logic should include it + assert not demos._filter_colors(hcl_endpoint, ihue=3, nhues=4, minsat=10) + + +def test_show_colors_basic_and_titles(): + """show_colors generates axes with titles for requested categories.""" + # Reduce nhues to make test faster and deterministic + fig, axs = demos.show_colors(nhues=4, minsat=10) + assert fig is not None + assert hasattr(axs, "__len__") + assert len(axs) > 0 + for ax in axs: + # Titles should be non-empty strings (e.g., "CSS4 colors", "Base colors", ...) + title = ax.get_title() + assert isinstance(title, str) + assert title != "" + + +def test_show_fonts_with_existing_font(): + """show_fonts should accept a real font name from the system and return a figure.""" + # Pick a font that is available in the matplotlib font manager + ttflist = mfonts.fontManager.ttflist + # If no fonts are present, skip the test + if not ttflist: + pytest.skip("No system fonts available for testing show_fonts.") + font_name = ttflist[0].name + fig, axs = demos.show_fonts(font_name) + assert fig is not None + # When a single font is requested, we expect a single row (len(props)) of axes + assert hasattr(axs, "__len__") + assert len(axs) >= 1 + # Basic sanity: each axis should contain at least one text artist + for ax in axs: + texts = ax.texts + assert hasattr(texts, "__len__") + assert len(texts) >= 1 From 0450409cd6433fba376c5221a7a90c4485d67f43 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Mon, 27 Oct 2025 20:59:13 +0100 Subject: [PATCH 2/6] update imports --- ultraplot/tests/test_demos.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ultraplot/tests/test_demos.py b/ultraplot/tests/test_demos.py index 11e47f7f1..b0516fc52 100644 --- a/ultraplot/tests/test_demos.py +++ b/ultraplot/tests/test_demos.py @@ -1,10 +1,6 @@ # ultraplot/ultraplot/tests/test_demos.py -import math -import numpy as np -import pytest -import ultraplot as plt +import numpy as np, pytest, ultraplot as plt import matplotlib.font_manager as mfonts - import ultraplot.demos as demos From e0cd143e69005d7ca3858502e2ecc2366b9dd47d Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Mon, 27 Oct 2025 20:59:34 +0100 Subject: [PATCH 3/6] remove comment --- ultraplot/tests/test_demos.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ultraplot/tests/test_demos.py b/ultraplot/tests/test_demos.py index b0516fc52..6df98d1f2 100644 --- a/ultraplot/tests/test_demos.py +++ b/ultraplot/tests/test_demos.py @@ -1,4 +1,3 @@ -# ultraplot/ultraplot/tests/test_demos.py import numpy as np, pytest, ultraplot as plt import matplotlib.font_manager as mfonts import ultraplot.demos as demos From 7dc31a60be295811a652c85460a73a0baedad70a Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Mon, 27 Oct 2025 21:08:14 +0100 Subject: [PATCH 4/6] add image comp --- ultraplot/tests/test_demos.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/ultraplot/tests/test_demos.py b/ultraplot/tests/test_demos.py index 6df98d1f2..ebaf9a24d 100644 --- a/ultraplot/tests/test_demos.py +++ b/ultraplot/tests/test_demos.py @@ -9,6 +9,7 @@ def test_show_channels_requires_arg(): demos.show_channels() +@pytest.mark.mpl_image_compare def test_show_channels_basic(): """Basic invocation of show_channels returns a figure and axes of expected length.""" # Request fewer samples and fewer channels to keep the figure small @@ -20,8 +21,10 @@ def test_show_channels_basic(): # Each axis should have a title set for ax in axs: assert isinstance(ax.get_title(), str) + return fig +@pytest.mark.mpl_image_compare def test_show_colorspaces_default_and_options(): """show_colorspaces should create three panels (hcl/hsl/hpl).""" fig, axs = demos.show_colorspaces() @@ -31,16 +34,19 @@ def test_show_colorspaces_default_and_options(): # Titles should include the space names titles = [ax.get_title().upper() for ax in axs] assert any("HCL" in t or "HSL" in t or "HPL" in t for t in titles) + return fig -def test_show_cmaps_and_cycles_return_fig_and_axes(): +@pytest.mark.parametrize("demo", ["show_cmaps", "show_cycles"]) +@pytest.mark.mpl_image_compare +def test_show_cmaps_and_cycles_return_fig_and_axes(demo): """show_cmaps and show_cycles should return a figure and axes collection.""" - fig_c, axs_c = demos.show_cmaps() - fig_y, axs_y = demos.show_cycles() - assert fig_c is not None and fig_y is not None - assert hasattr(axs_c, "__len__") and hasattr(axs_y, "__len__") - assert len(axs_c) > 0 - assert len(axs_y) > 0 + fig, axs = getattr(demos, demo)() + assert fig is not None + assert hasattr(axs, "__len__") + assert len(axs) > 0 + # Return a figure for image comparison (use the colormap figure) + return fig def test__filter_colors_behavior(): @@ -69,10 +75,12 @@ def test__filter_colors_behavior(): assert not demos._filter_colors(hcl_endpoint, ihue=3, nhues=4, minsat=10) -def test_show_colors_basic_and_titles(): +@pytest.mark.mpl_image_compare +@pytest.mark.parametrize("nhues,minsat", [(4, 10), (8, 15)]) +def test_show_colors_basic_and_titles(nhues, minsat): """show_colors generates axes with titles for requested categories.""" - # Reduce nhues to make test faster and deterministic - fig, axs = demos.show_colors(nhues=4, minsat=10) + # Use parameterized nhues/minsat to exercise behavior deterministically + fig, axs = demos.show_colors(nhues=nhues, minsat=minsat) assert fig is not None assert hasattr(axs, "__len__") assert len(axs) > 0 @@ -81,8 +89,10 @@ def test_show_colors_basic_and_titles(): title = ax.get_title() assert isinstance(title, str) assert title != "" + return fig +@pytest.mark.mpl_image_compare def test_show_fonts_with_existing_font(): """show_fonts should accept a real font name from the system and return a figure.""" # Pick a font that is available in the matplotlib font manager @@ -101,3 +111,4 @@ def test_show_fonts_with_existing_font(): texts = ax.texts assert hasattr(texts, "__len__") assert len(texts) >= 1 + return fig From 25213616eb70c67063155e7b7795d3f64558d9cb Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Mon, 27 Oct 2025 21:52:59 +0100 Subject: [PATCH 5/6] add copy array --- ultraplot/demos.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ultraplot/demos.py b/ultraplot/demos.py index 9b306420b..5145644ba 100644 --- a/ultraplot/demos.py +++ b/ultraplot/demos.py @@ -957,6 +957,7 @@ def show_colors(*, nhues=17, minsat=10, unknown="User", include=None, ignore=Non names = np.array([name for ipairs in hclpairs for name, _ in ipairs]) ncols, nrows = 4, len(names) // 4 + 1 + names = np.asarray(names).copy() names.resize((ncols, nrows)) # fill empty slots with empty string namess[cat] = names From e76db50867d22dfeb11e161594cc167392570dad Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Mon, 27 Oct 2025 22:17:45 +0100 Subject: [PATCH 6/6] change to resize for backwardscompat --- ultraplot/demos.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ultraplot/demos.py b/ultraplot/demos.py index 5145644ba..c535329fb 100644 --- a/ultraplot/demos.py +++ b/ultraplot/demos.py @@ -957,8 +957,7 @@ def show_colors(*, nhues=17, minsat=10, unknown="User", include=None, ignore=Non names = np.array([name for ipairs in hclpairs for name, _ in ipairs]) ncols, nrows = 4, len(names) // 4 + 1 - names = np.asarray(names).copy() - names.resize((ncols, nrows)) # fill empty slots with empty string + names = np.resize(names, (ncols, nrows)) # fill empty slots with empty string namess[cat] = names # Draw figures for different groups of colors