Repository: lgienapp/aquarel (language: python)

Issue:
Add unit tests
Each `set_` method should have an associated unit test that checks if the underlying `matplotlib.rcparams` are updated successfully and correctly.


Discussion / hints:

Fix patch:
diff --git a/aquarel/theme.py b/aquarel/theme.py
index 0d00b47..3a27cb9 100644
--- a/aquarel/theme.py
+++ b/aquarel/theme.py
@@ -51,7 +51,7 @@ class Theme:
         "xx-large",
     ]
     # Options for font family
-    _font_family_options = ["serif", "sans_serif", "monospace", "cursive", "fantasy"]
+    _font_family_options = ["serif", "sans-serif", "monospace", "cursive", "fantasy"]
     # Options for font style
     _font_style_options = ["normal", "roman", "italic", "oblique"]
     # Options for font stretch. This is currently not implemented by matplotlib.
@@ -111,36 +111,39 @@ class Theme:
     # Mapping from aquarel keys to matplotlib rcparams
     _rcparams_mapping = {
         "title": {
-            "location": "axes.titlelocation",
-            "pad": "axes.titlepad",
-            "size": "axes.titlesize",
-            "weight": "axes.titleweight",
+            "location": ["axes.titlelocation"],
+            "pad": ["axes.titlepad"],
+            "size": ["axes.titlesize"],
+            "weight": ["axes.titleweight"],
         },
         "grid": {
             "draw": ["polaraxes.grid", "axes.grid", "axes3d.grid"],
-            "axis": "axes.grid.axis",
-            "ticks": "axes.grid.which",
-            "alpha": "grid.alpha",
-            "style": "grid.linestyle",
-            "width": "grid.linewidth",
+            "axis": ["axes.grid.axis"],
+            "ticks": ["axes.grid.which"],
+            "alpha": ["grid.alpha"],
+            "style": ["grid.linestyle"],
+            "width": ["grid.linewidth"],
+        },
+        "lines": {
+            "style": ["lines.linestyle"],
+            "width": ["lines.linewidth"]
         },
-        "lines": {"style": "lines.linestyle", "width": "lines.linewidth"},
         "fonts": {
-            "family": "font.family",
-            "cursive": "font.cursive",
-            "fantasy": "font.fantasy",
-            "monospace": "font.monospace",
-            "sans-serif": "font.sans-serif",
-            "serif": "font.serif",
-            "size": "font.size",
-            "stretch": "font.stretch",
-            "style": "font.style",
-            "variant": "font.variant",
-            "weight": "font.weight",
+            "family": ["font.family"],
+            "cursive": ["font.cursive"],
+            "fantasy": ["font.fantasy"],
+            "monospace": ["font.monospace"],
+            "sans-serif": ["font.sans-serif"],
+            "serif": ["font.serif"],
+            "size": ["font.size"],
+            "stretch": ["font.stretch"],
+            "style": ["font.style"],
+            "variant": ["font.variant"],
+            "weight": ["font.weight"],
         },
         "colors": {
-            "figure_background_color": "figure.facecolor",
-            "plot_background_color": "axes.facecolor",
+            "figure_background_color": ["figure.facecolor"],
+            "plot_background_color": ["axes.facecolor"],
             "axes_color": ["axes.edgecolor", "figure.edgecolor"],
             "line_color": [
                 "boxplot.flierprops.color",
@@ -155,27 +158,27 @@ class Theme:
                 "boxplot.meanprops.markeredgecolor",
             ],
             "text_color": ["text.color", "axes.titlecolor", "axes.labelcolor"],
-            "grid_color": "grid.color",
+            "grid_color": ["grid.color"],
             "tick_color": ["xtick.color", "ytick.color"],
             "tick_label_color": ["xtick.labelcolor", "ytick.labelcolor"],
-            "legend_background_color": "legend.facecolor",
-            "legend_border_color": "legend.edgecolor",
-            "axis_label_color": "axes.labelcolor",
-            "palette": "axes.prop_cycle",
+            "legend_background_color": ["legend.facecolor"],
+            "legend_border_color": ["legend.edgecolor"],
+            "axes_label_color": ["axes.labelcolor"],
+            "palette": "axes.prop_cycle", # This should be just a string unlike others, otherwise set_color(palette=...) won't work.
         },
         "axes": {
-            "width": "axes.linewidth",
-            "bottom": "axes.spines.bottom",
-            "left": "axes.spines.left",
-            "right": "axes.spines.right",
-            "top": "axes.spines.top",
-            "xmargin": "axes.xmargin",
-            "ymargin": "axes.ymargin",
-            "zmargin": "axes.zmargin",
+            "width": ["axes.linewidth"],
+            "bottom": ["axes.spines.bottom"],
+            "left": ["axes.spines.left"],
+            "right": ["axes.spines.right"],
+            "top": ["axes.spines.top"],
+            "xmargin": ["axes.xmargin"],
+            "ymargin": ["axes.ymargin"],
+            "zmargin": ["axes.zmargin"],
         },
         "ticks": {
-            "x_align": "xtick.alignment",
-            "y_align:": "ytick.alignment",
+            "x_align": ["xtick.alignment"],
+            "y_align": ["ytick.alignment"],
             "direction": ["xtick.direction", "ytick.direction"],
             "draw_minor": ["xtick.minor.visible", "ytick.minor.visible"],
             "width_minor": ["xtick.minor.width", "ytick.minor.width"],
@@ -186,28 +189,28 @@ class Theme:
             "pad_minor": ["xtick.minor.pad", "ytick.minor.pad"],
         },
         "axis_labels": {
-            "pad": "axes.labelpad",
-            "size": "axes.labelsize",
-            "weight": "axes.labelweight",
+            "pad": ["axes.labelpad"],
+            "size": ["axes.labelsize"],
+            "weight": ["axes.labelweight"],
         },
         "tick_labels": {
             "location": ["xaxis.labellocation", "yaxis.labellocation"],
             "size": ["xtick.labelsize", "ytick.labelsize"],
-            "bottom": "xtick.labelbottom",
-            "top": "xtick.labeltop",
-            "left": "ytick.labelleft",
-            "right": "ytick.labelright",
+            "bottom": ["xtick.labelbottom"],
+            "top": ["xtick.labeltop"],
+            "left": ["ytick.labelleft"],
+            "right": ["ytick.labelright"],
         },
         "legend": {
-            "location": "legend.loc",
-            "round": "legend.fancybox",
-            "shadow": "legend.shadow",
-            "title_size": "legend.title_fontsize",
-            "text_size": "legend.fontsize",
-            "alpha": "legend.framealpha",
-            "marker_scale": "legend.markerscale",
-            "padding": "legend.borderpad",
-            "margin": "legend.borderaxespad",
+            "location": ["legend.loc"],
+            "round": ["legend.fancybox"],
+            "shadow": ["legend.shadow"],
+            "title_size": ["legend.title_fontsize"],
+            "text_size": ["legend.fontsize"],
+            "alpha": ["legend.framealpha"],
+            "marker_scale": ["legend.markerscale"],
+            "padding": ["legend.borderpad"],
+            "margin": ["legend.borderaxespad"],
             "spacing": ["legend.handletextpad", "legend.labelspacing"]
         }
     }
@@ -401,7 +404,7 @@ def set_title(
         self._update_params(
             "title",
             {
-                "location": location if location in self._location_options else None,
+                "location": location if location in self._horizontal_alignment_options else None,
                 "pad": pad,
                 "size": size
                 if (size in self._font_size_options or type(size) == float)
@@ -626,7 +629,7 @@ def set_ticks(
                 "x_align": x_align
                 if x_align in self._horizontal_alignment_options
                 else None,
-                "y_align:": y_align
+                "y_align": y_align
                 if y_align in self._vertical_alignment_options
                 else None,
                 "direction": direction


Test patch:
diff --git a/tests/test_theme.py b/tests/test_theme.py
new file mode 100644
index 0000000..f656305
--- /dev/null
+++ b/tests/test_theme.py
@@ -0,0 +1,232 @@
+import sys
+sys.path.append('../aquarel')
+
+import unittest
+import matplotlib.pyplot as plt
+from aquarel import Theme
+from cycler import cycler
+
+class TestTheme(unittest.TestCase):
+    def setUp(self):
+        self.theme = Theme(name="test", description="A test theme.")
+        print("\n***** set up *****")
+        print("> set up a test theme")
+
+
+    def test_set_overrides(self):
+        print(f"\n***** set overrides *****")
+        options = [{"lines.dash_joinstyle": "bevel"}, {"boxplot.flierprops.color": "red", "ytick.minor.visible": False}]
+        for option in options:
+            for key, value in option.items():
+                print(f'> set "{key}" to be {value}')
+                with self.theme.set_overrides(option):
+                    print(f'>> check if plt.rcParams["{key}"] == {value}')
+                    self.assertEqual(value, plt.rcParams[key])   
+
+
+    def test_set_title(self):
+        def title_test(parameter, options):
+            print(f"\n***** title.{parameter} *****")
+            for option in options:
+                print(f"> set title.{parameter} to be {option}")
+                with self.theme.set_title(**{parameter: option}):
+                    for param in self.theme._rcparams_mapping["title"][parameter]:
+                        print(f'>> check if plt.rcParams["{param}"] == {option}')
+                        self.assertEqual(option, plt.rcParams[param])   
+
+        title_test("location", self.theme._horizontal_alignment_options)
+        title_test("pad", [0, 0.5, 1.5, 5])
+        title_test("size", self.theme._font_size_options)
+        title_test("weight", self.theme._font_weight_options)
+
+
+    def test_set_grid(self):
+        def grid_test(parameter, options):
+            print(f"\n***** grid.{parameter} *****")
+            for option in options:
+                print(f"> set grid.{parameter} to be {option}")
+                with self.theme.set_grid(**{parameter: option}):
+                    for param in self.theme._rcparams_mapping["grid"][parameter]:
+                        print(f'>> check if plt.rcParams["{param}"] == {option}')
+                        self.assertEqual(option, plt.rcParams[param])   
+
+        grid_test("draw", [True, False])
+        grid_test("axis", self.theme._axis_options)
+        grid_test("ticks", self.theme._tick_options)
+        grid_test("alpha", [0, 0.5, 1])
+        grid_test("style", self.theme._line_style_options)
+        grid_test("width", [0, 0.5, 1.5, 5])
+
+
+    def test_set_axes(self):
+        def axes_test(parameter, options):
+            print(f"\n***** axes.{parameter} *****")
+            for option in options:
+                print(f"> set axes.{parameter} to be {option}")
+                with self.theme.set_axes(**{parameter: option}):
+                    for param in self.theme._rcparams_mapping["axes"][parameter]:
+                        print(f'>> check if plt.rcParams["{param}"] == {option}')
+                        self.assertEqual(option, plt.rcParams[param])   
+
+        axes_test("width", [0, 0.5, 1.5, 5])
+        axes_test("top", [True, False])
+        axes_test("bottom", [True, False])
+        axes_test("left", [True, False])
+        axes_test("right", [True, False])
+        axes_test("xmargin", [0.01, 0.05, 0.15, 0.5])
+        axes_test("ymargin", [0.01, 0.05, 0.15, 0.5])
+        axes_test("zmargin", [0.01, 0.05, 0.15, 0.5])
+
+
+    def test_set_color(self):
+        def color_test(parameter, options):
+            print(f"\n***** colors.{parameter} *****")
+            for option in options:
+                print(f"> set colors.{parameter} to be {option}")
+                with self.theme.set_color(**{parameter: option}):
+                    for param in self.theme._rcparams_mapping["colors"][parameter]:
+                        print(f'>> check if plt.rcParams["{param}"] == {option}')
+                        self.assertEqual(option, plt.rcParams[param])   
+
+        color_test("figure_background_color", ["red", "#ff0000"])
+        color_test("plot_background_color", ["red", "#ff0000"])
+        color_test("text_color", ["red", "#ff0000"])
+        color_test("axes_color", ["red", "#ff0000"])
+        color_test("axes_label_color", ["red", "#ff0000"])
+        color_test("line_color", ["red", "#ff0000"])
+        color_test("grid_color", ["red", "#ff0000"])
+        color_test("tick_color", ["red", "#ff0000"])
+        color_test("tick_label_color", ["red", "#ff0000"])
+        color_test("legend_border_color", ["red", "#ff0000"])
+        color_test("legend_background_color", ["red", "#ff0000"])
+
+        # color_test("palette", [cycler("color", ["red", "green"])]) doesn't work for some reason
+        print(f"\n***** colors.palette *****")
+        for option in [cycler("color", ["red", "green"]), cycler("color", ["#ff0000", "#00ff00"])]:
+            print(f"> set colors.palette to be {option}")
+            with self.theme.set_color(**{"palette": option}):
+                param = self.theme._rcparams_mapping["colors"]["palette"]
+                print(f'>> check if plt.rcParams["{param}"] == {option}')
+                self.assertEqual(option, plt.rcParams[param])   
+
+
+    def test_set_axis_labels(self):
+        def axis_labels_test(parameter, options):
+            print(f"\n***** axis_labels.{parameter} *****")
+            for option in options:
+                print(f"> set axis_labels.{parameter} to be {option}")
+                with self.theme.set_axis_labels(**{parameter: option}):
+                    for param in self.theme._rcparams_mapping["axis_labels"][parameter]:
+                        print(f'>> check if plt.rcParams["{param}"] == {option}')
+                        self.assertEqual(option, plt.rcParams[param])   
+
+        axis_labels_test("pad", [0, 5, 10])
+        axis_labels_test("size", self.theme._font_size_options)
+        axis_labels_test("weight", self.theme._font_weight_options)
+ 
+
+    def test_set_tick_labels(self):
+        def tick_labels_test(parameter, options):
+            print(f"\n***** tick_labels.{parameter} *****")
+            for option in options:
+                print(f"> set tick_labels.{parameter} to be {option}")
+                with self.theme.set_tick_labels(**{parameter: option}):
+                    for param in self.theme._rcparams_mapping["tick_labels"][parameter]:
+                        print(f'>> check if plt.rcParams["{param}"] == {option}')
+                        self.assertEqual(option, plt.rcParams[param])   
+
+        # This test does not work and it is mentioned in issue #25
+        # tick_labels_test("location", self.theme._location_options)
+        tick_labels_test("size", self.theme._font_size_options)
+        tick_labels_test("top", [True, False])
+        tick_labels_test("bottom", [True, False])
+        tick_labels_test("left", [True, False])
+        tick_labels_test("right", [True, False])
+
+    
+    def test_set_ticks(self):
+        def ticks_test(parameter, options):
+            print(f"\n***** ticks.{parameter} *****")
+            for option in options:
+                print(f"> set ticks.{parameter} to be {option}")
+                with self.theme.set_ticks(**{parameter: option}):
+                    for param in self.theme._rcparams_mapping["ticks"][parameter]:
+                        print(f'>> check if plt.rcParams["{param}"] == {option}')
+                        self.assertEqual(option, plt.rcParams[param])   
+
+        ticks_test("x_align", self.theme._horizontal_alignment_options)
+        ticks_test("y_align", self.theme._vertical_alignment_options)
+        ticks_test("direction", self.theme._direction_options)
+        ticks_test("draw_minor", [True, False])
+        ticks_test("width_major", [0, 0.5, 1.5, 5])
+        ticks_test("width_minor", [0, 0.5, 1.5, 5])
+        ticks_test("size_major", [0, 0.5, 1.5, 5])
+        ticks_test("size_major", [0, 0.5, 1.5, 5])
+        ticks_test("pad_minor", [0, 0.5, 1.5, 5])
+        ticks_test("pad_minor", [0, 0.5, 1.5, 5])
+  
+
+    def test_set_lines(self):
+        def lines_test(parameter, options):
+            print(f"\n***** lines.{parameter} *****")
+            for option in options:
+                print(f"> set lines.{parameter} to be {option}")
+                with self.theme.set_lines(**{parameter: option}):
+                    for param in self.theme._rcparams_mapping["lines"][parameter]:
+                        print(f'>> check if plt.rcParams["{param}"] == {option}')
+                        self.assertEqual(option, plt.rcParams[param])   
+
+        lines_test("style", self.theme._line_style_options)
+        lines_test("width", [0, 0.5, 1.5, 5])
+
+
+    def test_set_font(self):
+        def font_test(parameter, options):
+            print(f"\n***** fonts.{parameter} *****")
+            for option in options:
+                print(f"> set fonts.{parameter} to be {option}")
+                with self.theme.set_font(**{"sans_serif" if parameter == "sans-serif" else parameter : option}):
+                    for param in self.theme._rcparams_mapping["fonts"][parameter]:
+                        print(f'>> check if plt.rcParams["{param}"] == {option}')
+                        if parameter == "family":
+                            self.assertEqual([option], plt.rcParams[param])   
+                        else:
+                            self.assertEqual(option, plt.rcParams[param])   
+
+        font_test("family", self.theme._font_family_options)
+        font_test("cursive", [["Apple Chancery", "Textile"]])
+        font_test("fantasy", [["Chicago", "Charcoal"]])
+        font_test("monospace", [["DejaVu Sans Mono", "Bitstream Vera Sans Mono"]])
+        font_test("sans-serif", [["DejaVu Sans", "Bitstream Vera Sans"]])
+        font_test("serif", [["DejaVu Serif", "Bitstream Vera Serif"]])
+        font_test("size", [0, 5, 15, 50])
+        font_test("stretch", self.theme._font_stretch_options)
+        font_test("style", self.theme._font_style_options)
+        font_test("variant", self.theme._font_variant_options)
+        font_test("weight", self.theme._font_weight_options)
+
+
+    def test_set_legend(self):
+        def legend_test(parameter, options):
+            print(f"\n***** legend.{parameter} *****")
+            for option in options:
+                print(f"> set legend.{parameter} to be {option}")
+                with self.theme.set_legend(**{parameter: option}):
+                    for param in self.theme._rcparams_mapping["legend"][parameter]:
+                        print(f'>> check if plt.rcParams["{param}"] == {option}')
+                        self.assertEqual(option, plt.rcParams[param])   
+
+        legend_test("location", self.theme._legend_location_options)
+        legend_test("round", [True, False])
+        legend_test("shadow", [True, False])
+        legend_test("title_size", self.theme._font_size_options)
+        legend_test("text_size", self.theme._font_size_options)
+        legend_test("alpha", [0, 0.5, 1])
+        legend_test("marker_scale", [0, 0.5, 1.5, 5])
+        legend_test("padding", [0, 0.5, 1.5, 5])
+        legend_test("margin", [0, 0.5, 1.5, 5])
+        legend_test("spacing", [0, 0.5, 1.5, 5])
+
+
+if __name__ == "__main__":
+    unittest.main()
\ No newline at end of file