"""Reusable trait-testing helpers shipped with the package.

Do not remove: downstream projects (e.g. ipywidgets) import TraitTestBase
from here to test their own trait types.
"""

from __future__ import annotations

from typing import Any
from unittest import TestCase

from traitlets import TraitError


class TraitTestBase(TestCase):
    """A best testing class for basic trait types."""

    def assign(self, value: Any) -> None:
        self.obj.value = value  # type:ignore[attr-defined]

    def coerce(self, value: Any) -> Any:
        return value

    def test_good_values(self) -> None:
        if hasattr(self, "_good_values"):
            for value in self._good_values:
                self.assign(value)
                self.assertEqual(self.obj.value, self.coerce(value))  # type:ignore[attr-defined]

    def test_bad_values(self) -> None:
        if hasattr(self, "_bad_values"):
            for value in self._bad_values:
                try:
                    self.assertRaises(TraitError, self.assign, value)
                except AssertionError:
                    raise AssertionError(value) from None

    def test_default_value(self) -> None:
        if hasattr(self, "_default_value"):
            self.assertEqual(self._default_value, self.obj.value)  # type:ignore[attr-defined]

    def test_allow_none(self) -> None:
        if (
            hasattr(self, "_bad_values")
            and hasattr(self, "_good_values")
            and None in self._bad_values
        ):
            trait = self.obj.traits()["value"]  # type:ignore[attr-defined]
            try:
                trait.allow_none = True
                self._bad_values.remove(None)
                # skip coerce. Allow None casts None to None.
                self.assign(None)
                self.assertEqual(self.obj.value, None)  # type:ignore[attr-defined]
                self.test_good_values()
                self.test_bad_values()
            finally:
                # tear down
                trait.allow_none = False
                self._bad_values.append(None)

    def tearDown(self) -> None:
        # restore default value after tests, if set
        if hasattr(self, "_default_value"):
            self.obj.value = self._default_value  # type:ignore[attr-defined]
