Repository: MyreMylar/pygame_gui
Pull request #690 (2025-05-28 10:45:53)

General code improvements
Adds some more validation warnings to theme files and a performance monitor function to the utility functions. 


Patch:
diff --git a/pygame_gui/__init__.py b/pygame_gui/__init__.py
index 5d2069d..beeb1ef 100644
--- a/pygame_gui/__init__.py
+++ b/pygame_gui/__init__.py
@@ -50,6 +50,7 @@ from pygame_gui._constants import TEXT_EFFECT_SHAKE
 from pygame_gui._constants import TEXT_EFFECT_EXPAND_CONTRACT
 from pygame_gui._constants import UITextEffectType
 from pygame_gui.core.package_resource import PackageResource
+from pygame_gui.core.utility import performance_monitor
 
 __all__ = [
     "UIManager",
@@ -92,4 +93,5 @@ __all__ = [
     "TEXT_EFFECT_TILT",
     "TEXT_EFFECT_EXPAND_CONTRACT",
     "TEXT_EFFECT_SHAKE",
+    "performance_monitor",
 ]
diff --git a/pygame_gui/_constants.py b/pygame_gui/_constants.py
index 02c820d..d6e1422 100644
--- a/pygame_gui/_constants.py
+++ b/pygame_gui/_constants.py
@@ -1,6 +1,5 @@
 """
 Some  constants used across the GUI
-TODO: Using strings for now for compatibility but we could deprecate these to be integers later.
 """
 
 from warnings import warn
diff --git a/pygame_gui/core/text/text_line_chunk.py b/pygame_gui/core/text/text_line_chunk.py
index 5f82280..b8cb007 100644
--- a/pygame_gui/core/text/text_line_chunk.py
+++ b/pygame_gui/core/text/text_line_chunk.py
@@ -943,7 +943,5 @@ class TextLineChunkFTFont(TextLayoutRect):
         to.
 
         """
-        # TODO: Could be a memory hog on large text surfaces as we are currently holding the whole
-        #       text surface again. Perhaps only hold the chunk's part?
         if self.target_surface is not None:
             self.pre_effect_target_surface = self.target_surface.copy()
diff --git a/pygame_gui/core/ui_appearance_theme.py b/pygame_gui/core/ui_appearance_theme.py
index 388f4a3..b9aff8a 100644
--- a/pygame_gui/core/ui_appearance_theme.py
+++ b/pygame_gui/core/ui_appearance_theme.py
@@ -35,6 +35,595 @@ from pygame_gui.core.colour_parser import (
 )
 
 
+class UIThemeValidationError(Exception):
+    """Custom exception for UI theme validation errors."""
+
+    def __init__(self, message: str, element_id: Optional[str] = None):
+        super().__init__(message)
+        self.element_id = element_id
+
+
+class UIThemeValidator:
+    """
+    Enhanced theme validator that provides better error messages
+    and catches more potential issues.
+    """
+
+    @staticmethod
+    def validate_theme_file(theme_data: Dict[str, Any]) -> List[str]:
+        """Comprehensive theme validation with detailed error messages."""
+        errors = []
+
+        for element_type, element_data in theme_data.items():
+            if not isinstance(element_data, dict):
+                errors.append(
+                    f"Element '{element_type}' must be a dictionary, got {type(element_data).__name__}"
+                )
+                continue
+
+            # Validate colors section
+            if "colours" in element_data:
+                color_errors = UIThemeValidator._validate_colors(
+                    element_data["colours"], element_type
+                )
+                errors.extend(color_errors)
+
+            # Also check for 'colors' (American spelling)
+            if "colors" in element_data:
+                color_errors = UIThemeValidator._validate_colors(
+                    element_data["colors"], element_type
+                )
+                errors.extend(color_errors)
+
+            # Validate misc section
+            if "misc" in element_data:
+                misc_errors = UIThemeValidator._validate_misc(
+                    element_data["misc"], element_type
+                )
+                errors.extend(misc_errors)
+
+            # Validate images section
+            if "images" in element_data:
+                image_errors = UIThemeValidator._validate_images(
+                    element_data["images"], element_type
+                )
+                errors.extend(image_errors)
+
+            # Validate fonts section
+            if "font" in element_data:
+                font_errors = UIThemeValidator._validate_font(
+                    element_data["font"], element_type
+                )
+                errors.extend(font_errors)
+
+        return errors
+
+    @staticmethod
+    def _validate_colors(colors: Dict[str, Any], element_type: str) -> List[str]:
+        """Validate color definitions."""
+        errors = []
+
+        for color_name, color_value in colors.items():
+            if isinstance(color_value, str):
+                try:
+                    # Test if it's a valid color string
+                    test_color = parse_colour_or_gradient_string(color_value)
+                    if test_color is None:
+                        errors.append(
+                            f"{element_type}.colours.{color_name}: "
+                            f"Invalid color string '{color_value}'"
+                        )
+                except (ValueError, TypeError, AttributeError) as e:
+                    errors.append(
+                        f"{element_type}.colours.{color_name}: "
+                        f"Error parsing color '{color_value}': {str(e)}"
+                    )
+            elif isinstance(color_value, (list, tuple)):
+                if len(color_value) not in (3, 4):
+                    errors.append(
+                        f"{element_type}.colours.{color_name}: "
+                        f"Color tuple must have 3 or 4 values, got {len(color_value)}"
+                    )
+                elif not all(
+                    isinstance(c, (int, float)) and 0 <= c <= 255 for c in color_value
+                ):
+                    errors.append(
+                        f"{element_type}.colours.{color_name}: "
+                        f"Color values must be numbers between 0-255"
+                    )
+            else:
+                errors.append(
+                    f"{element_type}.colours.{color_name}: "
+                    f"Color must be string or tuple, got {type(color_value).__name__}"
+                )
+
+        return errors
+
+    @staticmethod
+    def _validate_misc(misc: Dict[str, Any], element_type: str) -> List[str]:
+        """Validate miscellaneous settings."""
+        errors = []
+
+        # Validate numeric values
+        numeric_fields = {
+            "border_width": (0, 100),
+            "shadow_width": (0, 50),
+            "tool_tip_delay": (0.0, 10.0),
+            "text_shadow_size": (0, 10),
+            "border_radius": (0, 100),
+        }
+
+        for field, (min_val, max_val) in numeric_fields.items():
+            if field in misc:
+                try:
+                    if isinstance(misc[field], str):
+                        value = float(misc[field])
+                    else:
+                        value = float(misc[field])
+
+                    if not min_val <= value <= max_val:
+                        errors.append(
+                            f"{element_type}.misc.{field}: "
+                            f"Value {value} must be between {min_val} and {max_val}"
+                        )
+                except (ValueError, TypeError):
+                    errors.append(
+                        f"{element_type}.misc.{field}: "
+                        f"Must be a number, got {type(misc[field]).__name__}"
+                    )
+
+        # Validate tuple fields (x, y coordinates)
+        tuple_fields = {
+            "text_shadow_offset": ((-50, 50), (-50, 50)),  # (x_range, y_range)
+        }
+
+        # Validate corner radius fields (single value or 4 values for corners)
+        corner_radius_fields = {
+            "shape_corner_radius": (0, 100),  # (min_val, max_val)
+        }
+
+        for field, ranges in tuple_fields.items():
+            if field in misc:
+                value = misc[field]
+                x_range, y_range = ranges
+
+                # Handle string format "x,y"
+                if isinstance(value, str):
+                    try:
+                        parts = value.strip().split(",")
+                        if len(parts) != 2:
+                            errors.append(
+                                f"{element_type}.misc.{field}: "
+                                f"Must have 2 comma-separated values (x,y), got {len(parts)}"
+                            )
+                            continue
+                        x_val = float(parts[0].strip())
+                        y_val = float(parts[1].strip())
+                    except (ValueError, TypeError):
+                        errors.append(
+                            f"{element_type}.misc.{field}: "
+                            f"Invalid format '{value}', expected 'x,y' with numeric values"
+                        )
+                        continue
+
+                # Handle tuple/list format
+                elif isinstance(value, (list, tuple)):
+                    if len(value) != 2:
+                        errors.append(
+                            f"{element_type}.misc.{field}: "
+                            f"Must have 2 values (x,y), got {len(value)}"
+                        )
+                        continue
+                    try:
+                        x_val = float(value[0])
+                        y_val = float(value[1])
+                    except (ValueError, TypeError):
+                        errors.append(
+                            f"{element_type}.misc.{field}: "
+                            f"Values must be numeric, got {[type(v).__name__ for v in value]}"
+                        )
+                        continue
+
+                else:
+                    errors.append(
+                        f"{element_type}.misc.{field}: "
+                        f"Must be string 'x,y' or tuple/list [x,y], got {type(value).__name__}"
+                    )
+                    continue
+
+                # Validate ranges
+                x_min, x_max = x_range
+                y_min, y_max = y_range
+
+                if not x_min <= x_val <= x_max:
+                    errors.append(
+                        f"{element_type}.misc.{field}: "
+                        f"X value {x_val} must be between {x_min} and {x_max}"
+                    )
+
+                if not y_min <= y_val <= y_max:
+                    errors.append(
+                        f"{element_type}.misc.{field}: "
+                        f"Y value {y_val} must be between {y_min} and {y_max}"
+                    )
+
+        # Validate corner radius fields (single value or 4 corner values)
+        for field, (min_val, max_val) in corner_radius_fields.items():
+            if field in misc:
+                value = misc[field]
+
+                # Handle string format - can be single value or "tl,tr,br,bl"
+                if isinstance(value, str):
+                    try:
+                        parts = value.strip().split(",")
+                        if len(parts) == 1:
+                            # Single value for all corners
+                            radius_val = float(parts[0].strip())
+                            if not min_val <= radius_val <= max_val:
+                                errors.append(
+                                    f"{element_type}.misc.{field}: "
+                                    f"Value {radius_val} must be between {min_val} and {max_val}"
+                                )
+                        elif len(parts) == 4:
+                            # Four values for individual corners
+                            for i, part in enumerate(parts):
+                                corner_names = [
+                                    "top-left",
+                                    "top-right",
+                                    "bottom-right",
+                                    "bottom-left",
+                                ]
+                                try:
+                                    radius_val = float(part.strip())
+                                    if not min_val <= radius_val <= max_val:
+                                        errors.append(
+                                            f"{element_type}.misc.{field}: "
+                                            f"{corner_names[i]} corner value "
+                                            f"{radius_val} must be between {min_val} and {max_val}"
+                                        )
+                                except (ValueError, TypeError):
+                                    errors.append(
+                                        f"{element_type}.misc.{field}: "
+                                        f"{corner_names[i]} corner value must be numeric, got '{part.strip()}'"
+                                    )
+                        else:
+                            errors.append(
+                                f"{element_type}.misc.{field}: "
+                                f"Must have 1 value (all corners) or 4 comma-separated values "
+                                f"(individual corners), got {len(parts)}"
+                            )
+                    except (ValueError, TypeError):
+                        errors.append(
+                            f"{element_type}.misc.{field}: "
+                            f"Invalid format '{value}', expected single number or 'tl,tr,br,bl' with numeric values"
+                        )
+
+                # Handle numeric value (single value for all corners)
+                elif isinstance(value, (int, float)):
+                    if not min_val <= value <= max_val:
+                        errors.append(
+                            f"{element_type}.misc.{field}: "
+                            f"Value {value} must be between {min_val} and {max_val}"
+                        )
+
+                # Handle tuple/list format (4 corner values)
+                elif isinstance(value, (list, tuple)):
+                    if len(value) == 1:
+                        # Single value in a list/tuple
+                        try:
+                            radius_val = float(value[0])
+                            if not min_val <= radius_val <= max_val:
+                                errors.append(
+                                    f"{element_type}.misc.{field}: "
+                                    f"Value {radius_val} must be between {min_val} and {max_val}"
+                                )
+                        except (ValueError, TypeError):
+                            errors.append(
+                                f"{element_type}.misc.{field}: "
+                                f"Value must be numeric, got {type(value[0]).__name__}"
+                            )
+                    elif len(value) == 4:
+                        # Four corner values
+                        corner_names = [
+                            "top-left",
+                            "top-right",
+                            "bottom-right",
+                            "bottom-left",
+                        ]
+                        for i, corner_val in enumerate(value):
+                            try:
+                                radius_val = float(corner_val)
+                                if not min_val <= radius_val <= max_val:
+                                    errors.append(
+                                        f"{element_type}.misc.{field}: "
+                                        f"{corner_names[i]} corner value {radius_val} must be "
+                                        f"between {min_val} and {max_val}"
+                                    )
+                            except (ValueError, TypeError):
+                                errors.append(
+                                    f"{element_type}.misc.{field}: "
+                                    f"{corner_names[i]} corner value must be numeric, got {type(corner_val).__name__}"
+                                )
+                    else:
+                        errors.append(
+                            f"{element_type}.misc.{field}: "
+                            f"Must have 1 value (all corners) or 4 values "
+                            f"(individual corners), got {len(value)}"
+                        )
+
+                else:
+                    errors.append(
+                        f"{element_type}.misc.{field}: "
+                        f"Must be number, string, or list/tuple, got {type(value).__name__}"
+                    )
+
+        # Validate string choices
+        string_choices = {
+            "shape": ["rectangle", "rounded_rectangle", "ellipse"],
+            "text_horiz_alignment": ["left", "center", "centre", "right"],
+            "text_vert_alignment": ["top", "center", "centre", "bottom"],
+            "text_horiz_alignment_method": [
+                "default",
+                "rect",
+                "right_triangle",
+                "left_triangle",
+            ],
+            "text_vert_alignment_method": ["default", "rect"],
+        }
+
+        for field, choices in string_choices.items():
+            if field in misc:
+                if misc[field] not in choices:
+                    errors.append(
+                        f"{element_type}.misc.{field}: "
+                        f"Must be one of {choices}, got '{misc[field]}'"
+                    )
+
+        # Validate boolean fields
+        boolean_fields = [
+            "enable_arrow_buttons",
+            "expand_to_fit_text",
+            "word_wrap",
+            "allow_double_clicks",
+            "selectable",
+            "link_hover_underline",
+        ]
+
+        for field in boolean_fields:
+            if field in misc:
+                if isinstance(misc[field], str):
+                    if misc[field] not in ["0", "1", "true", "false", "True", "False"]:
+                        errors.append(
+                            f"{element_type}.misc.{field}: "
+                            f"Boolean field must be '0', '1', 'true', or 'false', got '{misc[field]}'"
+                        )
+                elif not isinstance(misc[field], (bool, int)):
+                    errors.append(
+                        f"{element_type}.misc.{field}: "
+                        f"Boolean field must be boolean or string, got {type(misc[field]).__name__}"
+                    )
+
+        return errors
+
+    @staticmethod
+    def _validate_images(images: Dict[str, Any], element_type: str) -> List[str]:
+        """Validate image definitions."""
+        errors = []
+
+        for image_name, image_data in images.items():
+            if not isinstance(image_data, dict):
+                errors.append(
+                    f"{element_type}.images.{image_name}: "
+                    f"Must be a dictionary, got {type(image_data).__name__}"
+                )
+                continue
+
+            # Check for required path or package/resource
+            has_path = "path" in image_data
+            has_package_resource = "package" in image_data and "resource" in image_data
+
+            if not (has_path or has_package_resource):
+                errors.append(
+                    f"{element_type}.images.{image_name}: "
+                    f"Must have either 'path' or both 'package' and 'resource'"
+                )
+
+            # Validate path if present
+            if has_path and not isinstance(image_data["path"], str):
+                errors.append(
+                    f"{element_type}.images.{image_name}.path: "
+                    f"Must be a string, got {type(image_data['path']).__name__}"
+                )
+
+            # Validate package/resource if present
+            if "package" in image_data and not isinstance(image_data["package"], str):
+                errors.append(
+                    f"{element_type}.images.{image_name}.package: "
+                    f"Must be a string, got {type(image_data['package']).__name__}"
+                )
+
+            if "resource" in image_data and not isinstance(image_data["resource"], str):
+                errors.append(
+                    f"{element_type}.images.{image_name}.resource: "
+                    f"Must be a string, got {type(image_data['resource']).__name__}"
+                )
+
+            # Validate sub_surface_rect if present
+            if "sub_surface_rect" in image_data:
+                rect_data = image_data["sub_surface_rect"]
+                if isinstance(rect_data, str):
+                    try:
+                        rect_parts = rect_data.strip().split(",")
+                        if len(rect_parts) != 4:
+                            errors.append(
+                                f"{element_type}.images.{image_name}.sub_surface_rect: "
+                                f"Must have 4 comma-separated values, got {len(rect_parts)}"
+                            )
+                        else:
+                            for part in rect_parts:
+                                int(part.strip())  # Test if it's a valid integer
+                    except ValueError:
+                        errors.append(
+                            f"{element_type}.images.{image_name}.sub_surface_rect: "
+                            f"All values must be integers: '{rect_data}'"
+                        )
+                elif isinstance(rect_data, (list, tuple)):
+                    if len(rect_data) != 4:
+                        errors.append(
+                            f"{element_type}.images.{image_name}.sub_surface_rect: "
+                            f"Must have 4 values, got {len(rect_data)}"
+                        )
+                    elif not all(isinstance(x, (int, float)) for x in rect_data):
+                        errors.append(
+                            f"{element_type}.images.{image_name}.sub_surface_rect: "
+                            f"All values must be numbers"
+                        )
+                else:
+                    errors.append(
+                        f"{element_type}.images.{image_name}.sub_surface_rect: "
+                        f"Must be string or list/tuple, got {type(rect_data).__name__}"
+                    )
+
+        return errors
+
+    @staticmethod
+    def _validate_font(
+        font_data: Union[Dict[str, Any], List[Dict[str, Any]]], element_type: str
+    ) -> List[str]:
+        """Validate font definitions."""
+        errors = []
+
+        # Handle both single font dict and list of font dicts
+        font_list = font_data if isinstance(font_data, list) else [font_data]
+
+        for i, font in enumerate(font_list):
+            prefix = (
+                f"{element_type}.font[{i}]"
+                if isinstance(font_data, list)
+                else f"{element_type}.font"
+            )
+
+            if not isinstance(font, dict):
+                errors.append(
+                    f"{prefix}: Must be a dictionary, got {type(font).__name__}"
+                )
+                continue
+
+            # Check for required fields
+            required_fields = ["name", "size"]
+            for field in required_fields:
+                if field not in font:
+                    errors.append(f"{prefix}: Missing required field '{field}'")
+
+            # Validate size
+            if "size" in font:
+                try:
+                    if isinstance(font["size"], str):
+                        size = int(font["size"])
+                    else:
+                        size = int(font["size"])
+
+                    if size <= 0:
+                        errors.append(f"{prefix}.size: Must be positive, got {size}")
+                    elif size > 200:  # Reasonable upper limit
+                        errors.append(
+                            f"{prefix}.size: Size {size} seems unusually large (max recommended: 200)"
+                        )
+                except (ValueError, TypeError):
+                    errors.append(
+                        f"{prefix}.size: Must be an integer, got {type(font['size']).__name__}"
+                    )
+
+            # Validate name
+            if "name" in font and not isinstance(font["name"], str):
+                errors.append(
+                    f"{prefix}.name: Must be a string, got {type(font['name']).__name__}"
+                )
+
+            # Validate boolean fields
+            boolean_fields = ["bold", "italic", "antialiased"]
+            for field in boolean_fields:
+                if field in font:
+                    if isinstance(font[field], str):
+                        if font[field] not in [
+                            "0",
+                            "1",
+                            "true",
+                            "false",
+                            "True",
+                            "False",
+                        ]:
+                            errors.append(
+                                f"{prefix}.{field}: "
+                                f"Boolean field must be '0', '1', 'true', or 'false', got '{font[field]}'"
+                            )
+                    elif not isinstance(font[field], (bool, int)):
+                        errors.append(
+                            f"{prefix}.{field}: "
+                            f"Must be boolean or string, got {type(font[field]).__name__}"
+                        )
+
+            # Validate locale
+            if "locale" in font and not isinstance(font["locale"], str):
+                errors.append(
+                    f"{prefix}.locale: Must be a string, got {type(font['locale']).__name__}"
+                )
+
+            # Validate direction
+            if "direction" in font:
+                valid_directions = ["ltr", "rtl", "LTR", "RTL"]
+                if font["direction"] not in valid_directions:
+                    errors.append(
+                        f"{prefix}.direction: "
+                        f"Must be one of {valid_directions}, got '{font['direction']}'"
+                    )
+
+            # Validate path fields
+            path_fields = [
+                "regular_path",
+                "bold_path",
+                "italic_path",
+                "bold_italic_path",
+            ]
+            for field in path_fields:
+                if field in font and not isinstance(font[field], str):
+                    errors.append(
+                        f"{prefix}.{field}: Must be a string, got {type(font[field]).__name__}"
+                    )
+
+            # Validate resource fields
+            resource_fields = [
+                "regular_resource",
+                "bold_resource",
+                "italic_resource",
+                "bold_italic_resource",
+            ]
+            for field in resource_fields:
+                if field in font:
+                    if not isinstance(font[field], dict):
+                        errors.append(
+                            f"{prefix}.{field}: Must be a dictionary, got {type(font[field]).__name__}"
+                        )
+                    else:
+                        resource = font[field]
+                        if "package" not in resource or "resource" not in resource:
+                            errors.append(
+                                f"{prefix}.{field}: Must have both 'package' and 'resource' fields"
+                            )
+                        if "package" in resource and not isinstance(
+                            resource["package"], str
+                        ):
+                            errors.append(f"{prefix}.{field}.package: Must be a string")
+                        if "resource" in resource and not isinstance(
+                            resource["resource"], str
+                        ):
+                            errors.append(
+                                f"{prefix}.{field}.resource: Must be a string"
+                            )
+
+        return errors
+
+
 class UIAppearanceTheme(IUIAppearanceThemeInterface):
     """
     The Appearance Theme class handles all the data that styles and generally dictates the
@@ -946,6 +1535,23 @@ class UIAppearanceTheme(IUIAppearanceThemeInterface):
         return None
 
     def _parse_theme_data_from_json_dict(self, theme_dict):
+        # Validate theme data before processing
+        validation_errors = UIThemeValidator.validate_theme_file(theme_dict)
+
+        if validation_errors:
+            # Log all validation errors as warnings
+            warnings.warn(
+                f"Theme validation found {len(validation_errors)} error(s):\n"
+                + "\n".join(f"  - {error}" for error in validation_errors),
+                UserWarning,
+            )
+
+            # Optionally, you could raise an exception instead:
+            # raise UIThemeValidationError(
+            #     f"Theme validation failed with {len(validation_errors)} error(s):\n" +
+            #     "\n".join(f"  - {error}" for error in validation_errors)
+            # )
+
         for element_name in theme_dict.keys():
             if element_name == "defaults":
                 self._load_colour_defaults_from_theme(theme_dict)
@@ -1154,11 +1760,28 @@ class UIAppearanceTheme(IUIAppearanceThemeInterface):
         if element_name not in self.ui_element_misc_data:
             self.ui_element_misc_data[element_name] = {}
         misc_dict = element_theming[data_type]
+
+        # Validate misc data for this specific element
+        # pylint: disable=protected-access
+        misc_errors = UIThemeValidator._validate_misc(misc_dict, element_name)
+        if misc_errors:
+            warnings.warn(
+                f"Misc data validation errors for {element_name}:\n"
+                + "\n".join(f"  - {error}" for error in misc_errors),
+                UserWarning,
+            )
+
         for misc_data_key in misc_dict:
-            if isinstance(misc_dict[misc_data_key], (dict, str)):
+            if isinstance(misc_dict[misc_data_key], (dict, str, int, float, bool)):
                 self.ui_element_misc_data[element_name][misc_data_key] = misc_dict[
                     misc_data_key
                 ]
+            else:
+                warnings.warn(
+                    f"Unsupported misc data type for {element_name}.{misc_data_key}: "
+                    f"{type(misc_dict[misc_data_key]).__name__}. Skipping.",
+                    UserWarning,
+                )
 
     def _load_element_image_data_from_theme(
         self, data_type: str, element_name: str, element_theming: Dict[str, Any]
@@ -1176,6 +1799,16 @@ class UIAppearanceTheme(IUIAppearanceThemeInterface):
         if element_name not in self.ui_element_image_locs:
             self.ui_element_image_locs[element_name] = {}
         loaded_img_dict = element_theming[data_type]
+
+        # Validate image data for this specific element
+        # pylint: disable=protected-access
+        image_errors = UIThemeValidator._validate_images(loaded_img_dict, element_name)
+        if image_errors:
+            warnings.warn(
+                f"Image data validation errors for {element_name}:\n"
+                + "\n".join(f"  - {error}" for error in image_errors),
+                UserWarning,
+            )
         for image_key in loaded_img_dict:
             if image_key not in self.ui_element_image_locs[element_name]:
                 self.ui_element_image_locs[element_name][image_key] = {"changed": True}
@@ -1262,6 +1895,16 @@ class UIAppearanceTheme(IUIAppearanceThemeInterface):
         :param file_dict: The file dictionary from the theming file to load data from.
         """
 
+        # Validate font data for this specific element
+        # pylint: disable=protected-access
+        font_errors = UIThemeValidator._validate_font(file_dict, element_name)
+        if font_errors:
+            warnings.warn(
+                f"Font data validation errors for {element_name}:\n"
+                + "\n".join(f"  - {error}" for error in font_errors),
+                UserWarning,
+            )
+
         if element_name not in self.ui_element_fonts_info:
             self.ui_element_fonts_info[element_name] = {}
 
@@ -1456,3 +2099,29 @@ class UIAppearanceTheme(IUIAppearanceThemeInterface):
 
     def get_shadow_generator(self) -> ShadowGenerator:
         return self.shadow_generator
+
+    def validate_theme_data(self, theme_data: Dict[str, Any]) -> List[str]:
+        """
+        Validate theme data without loading it.
+
+        :param theme_data: The theme data dictionary to validate.
+        :return: List of validation error messages. Empty list means no errors.
+        """
+        return UIThemeValidator.validate_theme_file(theme_data)
+
+    def validate_theme_file_path(
+        self, file_path: Union[str, os.PathLike, PackageResource]
+    ) -> List[str]:
+        """
+        Validate a theme file without loading it into the theme.
+
+        :param file_path: Path to the theme file to validate.
+        :return: List of validation error messages. Empty list means no errors.
+        """
+        try:
+            theme_dict = self._load_theme_by_path(file_path)
+            if theme_dict is None:
+                return ["Failed to load theme file"]
+            return self.validate_theme_data(theme_dict)
+        except (OSError, IOError, ValueError, TypeError) as e:
+            return [f"Error loading theme file: {str(e)}"]
diff --git a/pygame_gui/core/utility.py b/pygame_gui/core/utility.py
index 398e632..66cd488 100644
--- a/pygame_gui/core/utility.py
+++ b/pygame_gui/core/utility.py
@@ -11,6 +11,7 @@ import os
 import sys
 import io
 import base64
+import functools
 
 from typing import Union, Dict, Tuple, Optional, Callable, List, Any
 from importlib import resources
@@ -365,6 +366,47 @@ def apply_colour_to_surface(
         shape_surface.blit(colour_surface, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
 
 
+def performance_monitor(operation_name: str):
+    """
+    Decorator to monitor performance of operations and identify bottlenecks.
+
+    This decorator measures the execution time of functions and logs a warning
+    if the operation takes longer than 16.67 milliseconds (60 FPS frame time).
+    This helps identify performance issues that could impact UI responsiveness
+    in real-time applications.
+
+    :param operation_name: A descriptive name for the operation being monitored.
+
+    Example usage:
+        @performance_monitor("Theme Loading")
+        def load_theme_data(self):
+            # ... complex theme loading logic ...
+            pass
+
+    The decorator will automatically log slow operations like:
+        "SLOW OPERATION: Theme Loading took 25.43ms"
+    """
+
+    def decorator(func):
+        @functools.wraps(func)
+        def wrapper(*args, **kwargs):
+            start_time = time.perf_counter()
+            try:
+                result = func(*args, **kwargs)
+                return result
+            finally:
+                end_time = time.perf_counter()
+                duration = (end_time - start_time) * 1000  # Convert to milliseconds
+
+                # Log slow operations (slower than 60 FPS frame time)
+                if duration > 16.67:
+                    print(f"SLOW OPERATION: {operation_name} took {duration:.2f}ms")
+
+        return wrapper
+
+    return decorator
+
+
 class FontResource:
     """
     A resource class to handle all the data we need to load a python font object from a


Test patch:
diff --git a/tests/test_core/test_ui_appearance_theme.py b/tests/test_core/test_ui_appearance_theme.py
index 3920cbe..97fe6d0 100644
--- a/tests/test_core/test_ui_appearance_theme.py
+++ b/tests/test_core/test_ui_appearance_theme.py
@@ -1,9 +1,10 @@
 import os
 import pytest
 import pygame
+import warnings
 
 from pygame_gui.core.interfaces.gui_font_interface import IGUIFontInterface
-from pygame_gui.core.ui_appearance_theme import UIAppearanceTheme
+from pygame_gui.core.ui_appearance_theme import UIAppearanceTheme, UIThemeValidator, UIThemeValidationError
 from pygame_gui.core.resource_loaders import BlockingThreadedResourceLoader
 from pygame_gui import PackageResource
 
@@ -51,29 +52,37 @@ class TestUIAppearanceTheme:
 
     def test_load_theme_invalid_colour_gradients(self, _init_pygame, _display_surface_return_none):
         theme = UIAppearanceTheme(BlockingThreadedResourceLoader(), locale='en')
-        with pytest.warns(UserWarning, match="Invalid gradient"):
-            theme.load_theme(os.path.join("tests", "data", "themes", "appearance_theme_test.json"))
+        with pytest.warns(UserWarning, match="Theme validation found 2 error\\(s\\)"):
+            with pytest.warns(UserWarning, match="Invalid gradient"):
+                theme.load_theme(os.path.join("tests", "data", "themes", "appearance_theme_test.json"))
 
     def test_get_colour_from_gradient_objects(self, _init_pygame, _display_surface_return_none):
         theme = UIAppearanceTheme(BlockingThreadedResourceLoader(), locale='en')
-        with pytest.warns(UserWarning, match="Invalid gradient"):
-            theme.load_theme(os.path.join("tests", "data", "themes", "appearance_theme_test.json"))
+        with pytest.warns(UserWarning, match="Theme validation found 2 error\\(s\\)"):
+            with pytest.warns(UserWarning, match="Invalid gradient"):
+                theme.load_theme(os.path.join("tests", "data", "themes", "appearance_theme_test.json"))
         colour = theme.get_colour(colour_id='dark_bg', combined_element_ids=['#test_parent'])
         assert colour == pygame.Color('#25f92e')
 
     def test_load_theme_bad_font_data(self, _init_pygame, _display_surface_return_none):
         theme = UIAppearanceTheme(BlockingThreadedResourceLoader(), locale='en')
-        with pytest.warns(UserWarning, match="Unable to create subsurface rectangle from string"):
-            theme.load_theme(os.path.join("tests", "data", "themes",
-                                          "appearance_theme_bad_font_data_test.json"))
+        with pytest.warns(UserWarning, match="Theme validation found 4 error\\(s\\)"):
+            with pytest.warns(UserWarning, match="Font data validation errors"):
+                with pytest.warns(UserWarning, match="Image data validation errors"):
+                    with pytest.warns(UserWarning, match="Unable to create subsurface rectangle"):
+                        theme.load_theme(os.path.join("tests", "data", "themes",
+                                                      "appearance_theme_bad_font_data_test.json"))
 
     def test_load_theme_twice(self, _init_pygame, _display_surface_return_none):
         theme = UIAppearanceTheme(BlockingThreadedResourceLoader(), locale='en')
-        with pytest.warns(UserWarning, match="Unable to create subsurface rectangle from string"):
-            theme.load_theme(os.path.join("tests", "data", "themes",
-                                          "appearance_theme_bad_font_data_test.json"))
-            theme.load_theme(os.path.join("tests", "data", "themes",
-                                          "ui_button_non_default.json"))
+        with pytest.warns(UserWarning, match="Theme validation found 4 error\\(s\\)"):
+            with pytest.warns(UserWarning, match="Font data validation errors"):
+                with pytest.warns(UserWarning, match="Image data validation errors"):
+                    with pytest.warns(UserWarning, match="Unable to create subsurface rectangle"):
+                        theme.load_theme(os.path.join("tests", "data", "themes",
+                                                      "appearance_theme_bad_font_data_test.json"))
+                        theme.load_theme(os.path.join("tests", "data", "themes",
+                                                      "ui_button_non_default.json"))
 
     def test_load_theme_with_non_preloaded_font(self, _init_pygame,
                                                 _display_surface_return_none: None):
@@ -200,6 +209,365 @@ class TestUIAppearanceTheme:
         assert theme.get_font_info(['button']) == theme.ui_element_fonts_info['button']['en']
         assert theme.get_font(['button']) == theme.ele_font_res['button']['en'].loaded_font
 
+    # Theme Validation Tests
+    def test_theme_validator_valid_theme(self, _init_pygame, _display_surface_return_none):
+        """Test that valid theme data passes validation."""
+        valid_theme = {
+            "button": {
+                "colours": {
+                    "normal_bg": "#45494e",
+                    "hovered_bg": "#35393e",
+                    "normal_text": "#c5cbd8"
+                },
+                "misc": {
+                    "border_width": "2",
+                    "shadow_width": "1",
+                    "shape": "rounded_rectangle"
+                },
+                "font": {
+                    "name": "arial",
+                    "size": "14",
+                    "bold": "0"
+                }
+            }
+        }
+        
+        errors = UIThemeValidator.validate_theme_file(valid_theme)
+        assert len(errors) == 0
+
+    def test_theme_validator_invalid_colors(self, _init_pygame, _display_surface_return_none):
+        """Test validation of invalid color data."""
+        invalid_theme = {
+            "button": {
+                "colours": {
+                    "normal_bg": "not_a_color",  # Invalid color
+                    "hovered_bg": [300, 400, 500],  # Invalid RGB values
+                    "normal_text": 123  # Wrong type
+                }
+            }
+        }
+        
+        errors = UIThemeValidator.validate_theme_file(invalid_theme)
+        assert len(errors) == 3
+        assert any("Invalid color string 'not_a_color'" in error for error in errors)
+        assert any("Color values must be numbers between 0-255" in error for error in errors)
+        assert any("Color must be string or tuple, got int" in error for error in errors)
+
+    def test_theme_validator_invalid_misc(self, _init_pygame, _display_surface_return_none):
+        """Test validation of invalid misc data."""
+        invalid_theme = {
+            "button": {
+                "misc": {
+                    "border_width": "not_a_number",  # Invalid number
+                    "shadow_width": "-5",  # Negative value
+                    "shape": "invalid_shape",  # Invalid choice
+                    "enable_arrow_buttons": "maybe"  # Invalid boolean
+                }
+            }
+        }
+        
+        errors = UIThemeValidator.validate_theme_file(invalid_theme)
+        assert len(errors) == 4
+        assert any("Must be a number" in error for error in errors)
+        assert any("Value -5.0 must be between 0 and 50" in error for error in errors)
+        assert any("Must be one of ['rectangle', 'rounded_rectangle', 'ellipse']" in error for error in errors)
+        assert any("Boolean field must be '0', '1', 'true', or 'false'" in error for error in errors)
+
+    def test_theme_validator_invalid_font(self, _init_pygame, _display_surface_return_none):
+        """Test validation of invalid font data."""
+        invalid_theme = {
+            "button": {
+                "font": {
+                    "name": 123,  # Wrong type
+                    "size": "-10",  # Invalid size
+                    "bold": "maybe"  # Invalid boolean
+                }
+            }
+        }
+        
+        errors = UIThemeValidator.validate_theme_file(invalid_theme)
+        assert len(errors) == 3
+        assert any("Must be a string, got int" in error for error in errors)
+        assert any("Must be positive, got -10" in error for error in errors)
+        assert any("Boolean field must be '0', '1', 'true', or 'false'" in error for error in errors)
+
+    def test_theme_validator_invalid_images(self, _init_pygame, _display_surface_return_none):
+        """Test validation of invalid image data."""
+        invalid_theme = {
+            "button": {
+                "images": {
+                    "background": {
+                        # Missing path or package/resource
+                        "sub_surface_rect": "1,2,3"  # Wrong number of values
+                    }
+                }
+            }
+        }
+        
+        errors = UIThemeValidator.validate_theme_file(invalid_theme)
+        assert len(errors) == 2
+        assert any("Must have either 'path' or both 'package' and 'resource'" in error for error in errors)
+        assert any("Must have 4 comma-separated values, got 3" in error for error in errors)
+
+    def test_theme_validator_invalid_element_type(self, _init_pygame, _display_surface_return_none):
+        """Test validation of invalid element type."""
+        invalid_theme = {
+            "invalid_element": "not_a_dict"  # Wrong type for element
+        }
+        
+        errors = UIThemeValidator.validate_theme_file(invalid_theme)
+        assert len(errors) == 1
+        assert "Element 'invalid_element' must be a dictionary, got str" in errors[0]
+
+    def test_theme_validator_color_validation_methods(self, _init_pygame, _display_surface_return_none):
+        """Test specific color validation methods."""
+        # Test valid colors
+        valid_colors = {
+            "hex_color": "#FF0000",
+            "tuple_color": [255, 0, 0],
+            "named_color": "red"
+        }
+        errors = UIThemeValidator._validate_colors(valid_colors, "test_element")
+        assert len(errors) == 0
+        
+        # Test invalid colors
+        invalid_colors = {
+            "bad_color": "not_a_color",
+            "bad_tuple": [300, -50, 500],
+            "wrong_type": 123
+        }
+        errors = UIThemeValidator._validate_colors(invalid_colors, "test_element")
+        assert len(errors) == 3
+
+    def test_theme_validator_misc_validation_methods(self, _init_pygame, _display_surface_return_none):
+        """Test specific misc validation methods."""
+        # Test valid misc data
+        valid_misc = {
+            "border_width": "5",
+            "shape": "rectangle",
+            "enable_arrow_buttons": "1"
+        }
+        errors = UIThemeValidator._validate_misc(valid_misc, "test_element")
+        assert len(errors) == 0
+        
+        # Test invalid misc data
+        invalid_misc = {
+            "border_width": "not_a_number",
+            "shape": "triangle",
+            "enable_arrow_buttons": "maybe"
+        }
+        errors = UIThemeValidator._validate_misc(invalid_misc, "test_element")
+        assert len(errors) == 3
+
+    def test_theme_validator_font_validation_methods(self, _init_pygame, _display_surface_return_none):
+        """Test specific font validation methods."""
+        # Test valid font data
+        valid_font = {
+            "name": "arial",
+            "size": "14",
+            "bold": "0",
+            "italic": "0"
+        }
+        errors = UIThemeValidator._validate_font(valid_font, "test_element")
+        assert len(errors) == 0
+        
+        # Test invalid font data
+        invalid_font = {
+            "name": 123,
+            "size": "-10",
+            "bold": "maybe"
+        }
+        errors = UIThemeValidator._validate_font(invalid_font, "test_element")
+        assert len(errors) == 3
+
+    def test_theme_validator_image_validation_methods(self, _init_pygame, _display_surface_return_none):
+        """Test specific image validation methods."""
+        # Test valid image data
+        valid_images = {
+            "background": {
+                "path": "test.png"
+            },
+            "icon": {
+                "package": "test.package",
+                "resource": "test.png"
+            }
+        }
+        errors = UIThemeValidator._validate_images(valid_images, "test_element")
+        assert len(errors) == 0
+        
+        # Test invalid image data
+        invalid_images = {
+            "background": {
+                # Missing path or package/resource
+                "sub_surface_rect": "1,2,3"  # Wrong number of values
+            }
+        }
+        errors = UIThemeValidator._validate_images(invalid_images, "test_element")
+        assert len(errors) == 2
+
+    def test_appearance_theme_validate_theme_data(self, _init_pygame, _display_surface_return_none):
+        """Test UIAppearanceTheme.validate_theme_data method."""
+        theme = UIAppearanceTheme(BlockingThreadedResourceLoader(), locale='en')
+        
+        # Test valid theme
+        valid_theme = {
+            "button": {
+                "colours": {"normal_bg": "#FF0000"},
+                "misc": {"border_width": "2"}
+            }
+        }
+        errors = theme.validate_theme_data(valid_theme)
+        assert len(errors) == 0
+        
+        # Test invalid theme
+        invalid_theme = {
+            "button": {
+                "colours": {"normal_bg": "invalid_color"}
+            }
+        }
+        errors = theme.validate_theme_data(invalid_theme)
+        assert len(errors) == 1
+
+    def test_appearance_theme_validation_integration(self, _init_pygame, _display_surface_return_none):
+        """Test that validation is integrated into theme loading."""
+        theme = UIAppearanceTheme(BlockingThreadedResourceLoader(), locale='en')
+        
+        # Test loading invalid theme generates warnings
+        invalid_theme = {
+            "button": {
+                "colours": {"normal_bg": "invalid_color"},
+                "misc": {"border_width": "not_a_number"}
+            }
+        }
+        
+        with pytest.warns(UserWarning, match="Theme validation found 2 error\\(s\\)"):
+            with pytest.warns(UserWarning, match="Invalid Theme Colour"):
+                with pytest.warns(UserWarning, match="Misc data validation errors"):
+                    theme.update_theming(invalid_theme)
+
+    def test_appearance_theme_validation_warnings_for_misc_data(self, _init_pygame, _display_surface_return_none):
+        """Test that misc data validation generates warnings during loading."""
+        theme = UIAppearanceTheme(BlockingThreadedResourceLoader(), locale='en')
+        
+        invalid_theme = {
+            "button": {
+                "misc": {
+                    "border_width": "not_a_number",
+                    "shape": "invalid_shape"
+                }
+            }
+        }
+        with pytest.warns(UserWarning, match="Theme validation found 2 error\\(s\\)"):
+            with pytest.warns(UserWarning, match="Misc data validation errors"):
+                theme.update_theming(invalid_theme)
+
+    def test_appearance_theme_validation_warnings_for_font_data(self, _init_pygame, _display_surface_return_none):
+        """Test that font data validation generates warnings during loading."""
+        theme = UIAppearanceTheme(BlockingThreadedResourceLoader(), locale='en')
+        
+        invalid_theme = {
+            "button": {
+                "font": {
+                    "name": 123,
+                    "size": "-10"
+                }
+            }
+        }
+        with pytest.warns(UserWarning, match="Theme validation found 2 error\\(s\\)"):
+            with pytest.warns(UserWarning, match="Font data validation errors"):
+                with pytest.warns(UserWarning, match="Font size less than or equal to 0"):
+                    with pytest.warns(UserWarning, match="Trying to pre-load font id:_regular_aa_14 with no paths set & its not a system font"):
+                        theme.update_theming(invalid_theme)
+
+    def test_appearance_theme_validation_warnings_for_image_data(self, _init_pygame, _display_surface_return_none):
+        """Test that image data validation generates warnings during loading."""
+        theme = UIAppearanceTheme(BlockingThreadedResourceLoader(), locale='en')
+        
+        invalid_theme = {
+            "button": {
+                "images": {
+                    "background": {
+                        "sub_surface_rect": "1,2,3"  # Wrong number of values
+                    }
+                }
+            }
+        }
+        with pytest.warns(UserWarning, match="Theme validation found 2 error\\(s\\)"):
+            with pytest.warns(UserWarning, match="Image data validation errors"):
+                with pytest.warns(UserWarning, match="Unable to find image with id: background"):
+                    theme.update_theming(invalid_theme)
+
+    def test_theme_validator_text_shadow_offset_validation(self, _init_pygame, _display_surface_return_none):
+        """Test specific validation for text_shadow_offset tuple values."""
+        
+        # Test valid formats
+        valid_themes = [
+            {"button": {"misc": {"text_shadow_offset": "2,3"}}},  # String format
+            {"button": {"misc": {"text_shadow_offset": " -1 , 5 "}}},  # String with spaces
+            {"button": {"misc": {"text_shadow_offset": (1, 2)}}},  # Tuple format
+            {"button": {"misc": {"text_shadow_offset": [0, -10]}}},  # List format
+            {"button": {"misc": {"text_shadow_offset": (-50, 50)}}},  # Boundary values
+        ]
+        
+        for theme_data in valid_themes:
+            errors = UIThemeValidator.validate_theme_file(theme_data)
+            assert len(errors) == 0, f"Valid theme should have no errors: {theme_data}"
+        
+        # Test invalid formats
+        invalid_themes = [
+            ({"button": {"misc": {"text_shadow_offset": "5"}}}, "Must have 2 comma-separated values"),
+            ({"button": {"misc": {"text_shadow_offset": "1,2,3"}}}, "Must have 2 comma-separated values"),
+            ({"button": {"misc": {"text_shadow_offset": "abc,def"}}}, "expected 'x,y' with numeric values"),
+            ({"button": {"misc": {"text_shadow_offset": (1, 2, 3)}}}, "Must have 2 values"),
+            ({"button": {"misc": {"text_shadow_offset": ('a', 'b')}}}, "Values must be numeric"),
+            ({"button": {"misc": {"text_shadow_offset": 5}}}, "Must be string 'x,y' or tuple/list"),
+            ({"button": {"misc": {"text_shadow_offset": (100, 0)}}}, "X value 100.0 must be between -50 and 50"),
+            ({"button": {"misc": {"text_shadow_offset": (0, -100)}}}, "Y value -100.0 must be between -50 and 50"),
+        ]
+        
+        for theme_data, expected_message in invalid_themes:
+            errors = UIThemeValidator.validate_theme_file(theme_data)
+            assert len(errors) >= 1, f"Invalid theme should have errors: {theme_data}"
+            assert expected_message in str(errors), f"Expected message '{expected_message}' not found in errors: {errors}"
+
+    def test_theme_validator_shape_corner_radius_validation(self, _init_pygame, _display_surface_return_none):
+        """Test specific validation for shape_corner_radius single and 4-corner values."""
+        
+        # Test valid formats
+        valid_themes = [
+            {"button": {"misc": {"shape_corner_radius": "5"}}},  # Single value string
+            {"button": {"misc": {"shape_corner_radius": 10}}},  # Single value integer
+            {"button": {"misc": {"shape_corner_radius": 7.5}}},  # Single value float
+            {"button": {"misc": {"shape_corner_radius": "2,4,6,8"}}},  # 4-corner string
+            {"button": {"misc": {"shape_corner_radius": " 1 , 2 , 3 , 4 "}}},  # 4-corner string with spaces
+            {"button": {"misc": {"shape_corner_radius": (5, 10, 15, 20)}}},  # 4-corner tuple
+            {"button": {"misc": {"shape_corner_radius": [0, 5, 10, 15]}}},  # 4-corner list
+            {"button": {"misc": {"shape_corner_radius": [25]}}},  # Single value in list
+            {"button": {"misc": {"shape_corner_radius": (0, 100, 0, 100)}}},  # Boundary values
+        ]
+        
+        for theme_data in valid_themes:
+            errors = UIThemeValidator.validate_theme_file(theme_data)
+            assert len(errors) == 0, f"Valid theme should have no errors: {theme_data}"
+        
+        # Test invalid formats
+        invalid_themes = [
+            ({"button": {"misc": {"shape_corner_radius": "1,2"}}}, "Must have 1 value (all corners) or 4 comma-separated values"),
+            ({"button": {"misc": {"shape_corner_radius": "1,2,3"}}}, "Must have 1 value (all corners) or 4 comma-separated values"),
+            ({"button": {"misc": {"shape_corner_radius": "abc,def,ghi,jkl"}}}, "corner value must be numeric"),
+            ({"button": {"misc": {"shape_corner_radius": (1, 2, 3)}}}, "Must have 1 value (all corners) or 4 values"),
+            ({"button": {"misc": {"shape_corner_radius": ('a', 'b', 'c', 'd')}}}, "corner value must be numeric"),
+            ({"button": {"misc": {"shape_corner_radius": {"radius": 5}}}}, "Must be number, string, or list/tuple"),
+            ({"button": {"misc": {"shape_corner_radius": 150}}}, "Value 150 must be between 0 and 100"),
+            ({"button": {"misc": {"shape_corner_radius": -5}}}, "Value -5 must be between 0 and 100"),
+            ({"button": {"misc": {"shape_corner_radius": (5, 150, 10, 20)}}}, "top-right corner value 150.0 must be between 0 and 100"),
+        ]
+        
+        for theme_data, expected_message in invalid_themes:
+            errors = UIThemeValidator.validate_theme_file(theme_data)
+            assert len(errors) >= 1, f"Invalid theme should have errors: {theme_data}"
+            assert expected_message in str(errors), f"Expected message '{expected_message}' not found in errors: {errors}"
+
 
 if __name__ == '__main__':
     pytest.console_main()
diff --git a/tests/test_elements/test_ui_2d_slider.py b/tests/test_elements/test_ui_2d_slider.py
index 3640d7c..c67fa57 100644
--- a/tests/test_elements/test_ui_2d_slider.py
+++ b/tests/test_elements/test_ui_2d_slider.py
@@ -185,6 +185,10 @@ class TestUI2DSlider:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests",
diff --git a/tests/test_elements/test_ui_button.py b/tests/test_elements/test_ui_button.py
index 20f2ec5..236354b 100644
--- a/tests/test_elements/test_ui_button.py
+++ b/tests/test_elements/test_ui_button.py
@@ -479,6 +479,10 @@ class TestUIButton:
         button.update(0.01)
         assert button_clicked
 
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_command_bad_value(self, _init_pygame: None, default_ui_manager: UIManager,
                                _display_surface_return_none):
         with pytest.raises(TypeError, match="Command function must be callable"):
@@ -796,6 +800,10 @@ class TestUIButton:
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid gradient")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_changed_theme_data_bad_values(self, _init_pygame,
                                                         _display_surface_return_none):
         manager = UIManager((800, 600),
@@ -809,9 +817,11 @@ class TestUIButton:
 
     def test_rebuild_from_changed_theme_data_bad_values_2(self, _init_pygame,
                                                           _display_surface_return_none):
-        with pytest.warns(UserWarning, match="Unable to find image with id"):
-            manager = UIManager((800, 600),
-                                os.path.join("tests", "data", "themes", "ui_button_bad_values_2.json"))
+        with pytest.warns(UserWarning, match="Theme validation found 4 error\\(s\\)"):
+            with pytest.warns(UserWarning, match="Image data validation errors"):
+                with pytest.warns(UserWarning, match="Unable to find image with id"):
+                    manager = UIManager((800, 600),
+                                        os.path.join("tests", "data", "themes", "ui_button_bad_values_2.json"))
 
         button = UIButton(relative_rect=pygame.Rect(10, 10, 150, 30),
                           text="Test Button",
diff --git a/tests/test_elements/test_ui_drop_down_menu.py b/tests/test_elements/test_ui_drop_down_menu.py
index 3c7aa4d..ad38361 100644
--- a/tests/test_elements/test_ui_drop_down_menu.py
+++ b/tests/test_elements/test_ui_drop_down_menu.py
@@ -249,6 +249,10 @@ class TestUIDropDownMenu:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
@@ -263,6 +267,10 @@ class TestUIDropDownMenu:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values_live(self, _init_pygame,
                                                      _display_surface_return_none):
         manager = UIManager((800, 600))
diff --git a/tests/test_elements/test_ui_form.py b/tests/test_elements/test_ui_form.py
index 7e260e4..3b94954 100644
--- a/tests/test_elements/test_ui_form.py
+++ b/tests/test_elements/test_ui_form.py
@@ -466,6 +466,10 @@ class TestUIForm:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests",
@@ -542,6 +546,10 @@ class TestUIForm:
         assert UIForm.type_checker("boolean(True, True)") == ("boolean", {"default": True, "required": True})
         assert UIForm.type_checker("boolean(False, False)") == ("boolean", {"default": False, "required": False})
 
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_type_checker_bad_values(self, _init_pygame, default_ui_manager,
                                      _display_surface_return_none):
         # General
@@ -651,6 +659,10 @@ class TestUIForm:
 
         assert not container.alive()
 
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_validate_questionnaire_bad_values(self, _init_pygame, default_ui_manager,
                                                _display_surface_return_none):
         container = UIForm(pygame.Rect(100, 100, 200, 200), questionnaire=get_questionnaire(),
diff --git a/tests/test_elements/test_ui_horizontal_scroll_bar.py b/tests/test_elements/test_ui_horizontal_scroll_bar.py
index 1899dc1..f60af09 100644
--- a/tests/test_elements/test_ui_horizontal_scroll_bar.py
+++ b/tests/test_elements/test_ui_horizontal_scroll_bar.py
@@ -172,6 +172,10 @@ class TestUIHorizontalScrollBar:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
diff --git a/tests/test_elements/test_ui_horizontal_slider.py b/tests/test_elements/test_ui_horizontal_slider.py
index 7a9a29f..0b7248e 100644
--- a/tests/test_elements/test_ui_horizontal_slider.py
+++ b/tests/test_elements/test_ui_horizontal_slider.py
@@ -173,15 +173,16 @@ class TestUIHorizontalSlider:
         assert slider.right_button is None
         assert slider.image is not None
 
-    @pytest.mark.filterwarnings("ignore:Invalid value")
-    @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Invalid value")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
-        manager = UIManager((800, 600), os.path.join("tests",
-                                                     "data",
-                                                     "themes",
-                                                     "ui_horizontal_slider_bad_values.json"))
+        with pytest.warns(UserWarning, match="Theme validation found 4 error\\(s\\)"):
+            manager = UIManager((800, 600), os.path.join("tests",
+                                                         "data",
+                                                         "themes",
+                                                         "ui_horizontal_slider_bad_values.json"))
 
         slider = UIHorizontalSlider(relative_rect=pygame.Rect(100, 100, 200, 30),
                                     start_value=51,
diff --git a/tests/test_elements/test_ui_label.py b/tests/test_elements/test_ui_label.py
index 0f464cb..f2db5b0 100644
--- a/tests/test_elements/test_ui_label.py
+++ b/tests/test_elements/test_ui_label.py
@@ -111,6 +111,10 @@ class TestUILabel:
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Label Rect is too small for text")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame, _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data", "themes",
                                                      "ui_label_bad_values.json"))
diff --git a/tests/test_elements/test_ui_panel.py b/tests/test_elements/test_ui_panel.py
index 7b91617..6961982 100644
--- a/tests/test_elements/test_ui_panel.py
+++ b/tests/test_elements/test_ui_panel.py
@@ -504,6 +504,10 @@ class TestUIPanel:
     @pytest.mark.filterwarnings("ignore:Invalid gradient")
     @pytest.mark.filterwarnings("ignore:Unable to load")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_changed_theme_data_bad_values(self, _init_pygame,
                                                         _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data", "themes",
diff --git a/tests/test_elements/test_ui_screen_space_health_bar.py b/tests/test_elements/test_ui_screen_space_health_bar.py
index dab4d5a..8096828 100644
--- a/tests/test_elements/test_ui_screen_space_health_bar.py
+++ b/tests/test_elements/test_ui_screen_space_health_bar.py
@@ -112,6 +112,10 @@ class TestUIScreenSpaceHealthBar:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame, _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data", "themes", "ui_screen_health_bar_bad_values.json"))
         healthy_sprite = HealthySprite()
diff --git a/tests/test_elements/test_ui_selection_list.py b/tests/test_elements/test_ui_selection_list.py
index 8cdcf1e..cb189df 100644
--- a/tests/test_elements/test_ui_selection_list.py
+++ b/tests/test_elements/test_ui_selection_list.py
@@ -657,6 +657,10 @@ class TestUISelectionList:
     @pytest.mark.filterwarnings("ignore:Invalid gradient")
     @pytest.mark.filterwarnings("ignore:Unable to load")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_changed_theme_data_bad_values(self, _init_pygame,
                                                         _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data", "themes",
diff --git a/tests/test_elements/test_ui_status_bar.py b/tests/test_elements/test_ui_status_bar.py
index f499c19..44222e2 100644
--- a/tests/test_elements/test_ui_status_bar.py
+++ b/tests/test_elements/test_ui_status_bar.py
@@ -52,6 +52,10 @@ class TestUIStatusBar:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame, _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data", "themes", "ui_status_bar_bad_values.json"))
         healthy_sprite = HealthySprite()
diff --git a/tests/test_elements/test_ui_text_box.py b/tests/test_elements/test_ui_text_box.py
index 3f1e20a..8dc7819 100644
--- a/tests/test_elements/test_ui_text_box.py
+++ b/tests/test_elements/test_ui_text_box.py
@@ -625,6 +625,10 @@ class TestUITextBox:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
diff --git a/tests/test_elements/test_ui_text_entry_box.py b/tests/test_elements/test_ui_text_entry_box.py
index 8807829..7b2e052 100644
--- a/tests/test_elements/test_ui_text_entry_box.py
+++ b/tests/test_elements/test_ui_text_entry_box.py
@@ -579,6 +579,10 @@ class TestUITextEntryBox:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
@@ -820,6 +824,18 @@ class TestUITextEntryBox:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+
+
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+
+
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+
+
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+
+
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_set_text_rebuild_select_area_4(self, _init_pygame,
                                             _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
@@ -1637,6 +1653,18 @@ class TestUITextEntryBox:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+
+
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+
+
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+
+
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+
+
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_redraw_selected_text(self, _init_pygame, _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
                                                      "themes",
@@ -1749,6 +1777,10 @@ class TestUITextEntryBox:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame, _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
                                                      "themes",
diff --git a/tests/test_elements/test_ui_text_entry_line.py b/tests/test_elements/test_ui_text_entry_line.py
index b075f76..1b518ed 100644
--- a/tests/test_elements/test_ui_text_entry_line.py
+++ b/tests/test_elements/test_ui_text_entry_line.py
@@ -214,7 +214,11 @@ class TestUITextEntryLine:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
-    def test_set_text_rebuild_select_area_3(self, _init_pygame, _display_surface_return_none):
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
+    def test_set_text_rebuild_select_area_4(self, _init_pygame, _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
                                                      "themes",
                                                      "ui_text_entry_line_bad_values.json"))
@@ -917,6 +921,18 @@ class TestUITextEntryLine:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+
+
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+
+
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+
+
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+
+
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_redraw_selected_text(self, _init_pygame, _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
                                                      "themes",
@@ -1041,6 +1057,10 @@ class TestUITextEntryLine:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame, _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
                                                      "themes",
diff --git a/tests/test_elements/test_ui_tool_tip.py b/tests/test_elements/test_ui_tool_tip.py
index f426827..4f56181 100644
--- a/tests/test_elements/test_ui_tool_tip.py
+++ b/tests/test_elements/test_ui_tool_tip.py
@@ -122,6 +122,10 @@ class TestUIToolTip:
         assert tool_tip.text_block.image is not None
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_bad_values_theme_build(self, _init_pygame,
                                     _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data", "themes", "ui_tool_tip_bad_values.json"))
diff --git a/tests/test_elements/test_ui_vertical_scroll_bar.py b/tests/test_elements/test_ui_vertical_scroll_bar.py
index 160f738..6d5ed37 100644
--- a/tests/test_elements/test_ui_vertical_scroll_bar.py
+++ b/tests/test_elements/test_ui_vertical_scroll_bar.py
@@ -171,6 +171,10 @@ class TestUIVerticalScrollBar:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
diff --git a/tests/test_elements/test_ui_window.py b/tests/test_elements/test_ui_window.py
index b46d6c6..2c8ce57 100644
--- a/tests/test_elements/test_ui_window.py
+++ b/tests/test_elements/test_ui_window.py
@@ -499,6 +499,10 @@ class TestUIWindow:
     @pytest.mark.filterwarnings("ignore:Invalid gradient")
     @pytest.mark.filterwarnings("ignore:Unable to load")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_changed_theme_data_bad_values(self, _init_pygame,
                                                         _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
diff --git a/tests/test_elements/test_ui_world_space_health_bar.py b/tests/test_elements/test_ui_world_space_health_bar.py
index 34df862..5d3e494 100644
--- a/tests/test_elements/test_ui_world_space_health_bar.py
+++ b/tests/test_elements/test_ui_world_space_health_bar.py
@@ -71,6 +71,10 @@ class TestUIWorldSpaceHealthBar:
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame, _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data", "themes", "ui_world_health_bar_bad_values.json"))
         healthy_sprite = UIWorldSpaceHealthBar.ExampleHealthSprite()
diff --git a/tests/test_windows/test_ui_confirmation_dialog.py b/tests/test_windows/test_ui_confirmation_dialog.py
index 6313fd3..b39c7ec 100644
--- a/tests/test_windows/test_ui_confirmation_dialog.py
+++ b/tests/test_windows/test_ui_confirmation_dialog.py
@@ -193,6 +193,10 @@ class TestUIConfirmationDialog:
         assert confirm_dialog.image is not None
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_bad_values_theme_build(self, _init_pygame,
                                     _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
diff --git a/tests/test_windows/test_ui_message_window.py b/tests/test_windows/test_ui_message_window.py
index 35ca1d4..131a899 100644
--- a/tests/test_windows/test_ui_message_window.py
+++ b/tests/test_windows/test_ui_message_window.py
@@ -155,6 +155,10 @@ class TestUIMessageWindow:
         assert message_window.image is not None
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
+    @pytest.mark.filterwarnings("ignore:Theme validation found")
+    @pytest.mark.filterwarnings("ignore:Misc data validation")
+    @pytest.mark.filterwarnings("ignore:Font data validation")
+    @pytest.mark.filterwarnings("ignore:Image data validation")
     def test_bad_values_theme_build(self, _init_pygame,
                                     _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",