#------------------------------------------------------------------------------
# Copyright (c) 2005, Enthought, Inc.
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in enthought/LICENSE.txt and may be redistributed only
# under the conditions described in the aforementioned license.  The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
# Thanks for using Enthought open source!
#
# Author: David C. Morrill Date: 03/20/2003 Description: Unit Test Case for the
# Traits Package
#------------------------------------------------------------------------------

#  Imports


import sys

from traits.testing.unittest_tools import unittest

from ..api import (Any, CFloat, CInt, CLong, Delegate, Float, HasTraits,
                   Instance, Int, List, Long, Str, Trait, TraitError,
                   TraitList, TraitPrefixList, TraitPrefixMap, TraitRange,
                   Tuple, pop_exception_handler, push_exception_handler)

#  Base unit test classes:


class BaseTest(object):

    def assign(self, value):
        self.obj.value = value

    def coerce(self, value):
        return value

    def test_assignment(self):
        obj = self.obj

        # Validate default value
        value = self._default_value
        self.assertEqual(obj.value, value)

        # Validate all legal values
        for i, value in enumerate(self._good_values):
            obj.value = value
            self.assertEqual(obj.value, self.coerce(value))

            # If there's a defined
            if i < len(self._mapped_values):
                self.assertEqual(obj.value_, self._mapped_values[i])

        # Validate correct behavior for illegal values
        for value in self._bad_values:
            self.assertRaises(TraitError, self.assign, value)


class test_base2(unittest.TestCase):

    def indexed_assign(self, list, index, value):
        list[index] = value

    def indexed_range_assign(self, list, index1, index2, value):
        list[index1: index2] = value

    def extended_slice_assign(self, list, index1, index2, step, value):
        list[index1:index2:step] = value

    # This avoids using a method name that contains 'test' so that this is not
    # called by the tester directly, as nose looks for all tests, regardless of
    # the handler at the bottom of this file.
    def check_values(self, name, default_value, good_values, bad_values,
                     actual_values=None, mapped_values=None):
        obj = self.obj
        try:
            # Make sure the default value is correct:
            msg = 'default value'
            value = default_value
            self.assertEqual(getattr(obj, name), value)

            # Iterate over all legal values being tested:
            if actual_values is None:
                actual_values = good_values
            msg = 'legal values'
            i = 0
            for value in good_values:
                setattr(obj, name, value)
                self.assertEqual(getattr(obj, name), actual_values[i])
                if mapped_values is not None:
                    self.assertEqual(getattr(obj, name + '_'),
                                     mapped_values[i])
                i += 1

            # Iterate over all illegal values being tested:
            msg = 'illegal values'
            for value in bad_values:
                self.assertRaises(TraitError, setattr, obj, name, value)
        except:
            print('Failed while testing %s for value: %s(%s) in %s' % (
                msg, value, value.__class__.__name__, self.__class__.__name__))
            raise


class AnyTrait(HasTraits):
    value = Any


class AnyTraitTest(BaseTest, unittest.TestCase):

    obj = AnyTrait()

    _default_value = None
    _good_values = [10.0, 'ten', 'ten', [10], {'ten': 10}, (10,), None, 1j]
    _mapped_values = []
    _bad_values = []


class CoercibleIntTrait(HasTraits):
    value = CInt(99)


class IntTrait(HasTraits):
    value = Int(99)


class CoercibleIntTest(AnyTraitTest):

    obj = CoercibleIntTrait()

    _default_value = 99
    _good_values = [10, -10, 10, -10, 10.1, -10.1, '10', '-10', '10',
                    '-10']
    _bad_values = ['10L', '-10L', '10.1', '-10.1', '10L', '-10L', '10.1',
                   '-10.1', 'ten', 'ten', [10], {'ten': 10}, (10, ), None,
                   1j]

    def coerce(self, value):
        try:
            return int(value)
        except:
            try:
                return int(float(value))
            except:
                return int(int(value))


class IntTest(AnyTraitTest):

    obj = IntTrait()

    _default_value = 99
    _good_values = [10, -10, 10, -10]
    _bad_values = ['ten', 'ten', [10], {'ten': 10}, (10,), None, 1j,
                   10.1, -10.1, '10L', '-10L', '10.1', '-10.1', '10L',
                   '-10L', '10.1', '-10.1',  '10', '-10', '10', '-10']

    try:
        import numpy as np
    except ImportError:
        pass
    else:
        _good_values.extend([
            np.int64(10), np.int64(-10),
            np.int32(10), np.int32(-10),
            np.int_(10), np.int_(-10)
        ])

    def coerce(self, value):
        try:
            return int(value)
        except:
            try:
                return int(float(value))
            except:
                return int(int(value))


class CoercibleLongTrait(HasTraits):
    value = CLong(99)


class LongTrait(HasTraits):
    value = Long(99)


class CoercibleLongTest(AnyTraitTest):

    obj = CoercibleLongTrait()

    _default_value = 99
    _good_values = [
        10, -10, 10, -10, 10.1, -10.1, '10', '-10', '10', '-10']
    if sys.version_info[0] < 3:
        _good_values.extend(['10L', '-10L', '10L', '-10L'])
    _bad_values = ['10.1', '-10.1', '10.1', '-10.1', 'ten', 'ten', [10],
                   [10], {'ten': 10}, (10,), (10,), None, 1j]

    def coerce(self, value):
        try:
            return int(value)
        except:
            return int(float(value))


class LongTest(AnyTraitTest):

    obj = LongTrait()

    _default_value = 99
    _good_values = [10, -10, 10, -10]
    _bad_values = ['ten', 'ten', [10], [10], {'ten': 10}, (10, ), (10,),
                   None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
                   '-10.1', '10', '-10', '10L', '-10L', '10.1',
                   '-10.1']

    def coerce(self, value):
        try:
            return int(value)
        except:
            return int(float(value))


class CoercibleFloatTrait(HasTraits):
    value = CFloat(99.0)


class FloatTrait(HasTraits):
    value = Float(99.0)


class CoercibleFloatTest(AnyTraitTest):
    obj = CoercibleFloatTrait()

    _default_value = 99.0
    _good_values = [10, -10, 10, -10, 10.1, -10.1, '10', '-10', '10.1',
                    '-10.1', '10', '-10', '10.1', '-10.1']
    _bad_values = ['10L', '-10L', '10L', '-10L', 'ten', 'ten', [10],
                   {'ten': 10}, (10, ), None, 1j]

    def coerce(self, value):
        try:
            return float(value)
        except:
            return float(int(value))


class FloatTest(AnyTraitTest):
    obj = FloatTrait()

    _default_value = 99.0
    _good_values = [10, -10, 10.1, -10.1]
    _bad_values = ['ten', 'ten', [10], {'ten': 10}, (10,), None,
                   1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', '10',
                   '-10', '10L', '-10L', '10.1', '-10.1']

    if sys.version_info[0] < 3:
        # 2to3 will remove the L suffix and therfore make them actually good
        # ones!
        _bad_values.extend([-10, 10])

    def coerce(self, value):
        try:
            return float(value)
        except:
            return float(int(value))

#  Trait that can only have 'complex'(i.e. imaginary) values:


class ImaginaryValueTrait(HasTraits):
    value = Trait(99.0 - 99.0j)


class ImaginaryValueTest(AnyTraitTest):

    obj = ImaginaryValueTrait()

    _default_value = 99.0 - 99.0j
    _good_values = [10, -10, 10, -10, 10.1, -10.1, '10', '-10', '10.1',
                    '-10.1', 10j, 10 + 10j, 10 - 10j, 10.1j, 10.1 + 10.1j,
                    10.1 - 10.1j, '10j', '10+10j', '10-10j']
    _bad_values = ['10L', '-10L', 'ten', [10], {'ten': 10}, (10,), None]

    def coerce(self, value):
        try:
            return complex(value)
        except:
            return complex(int(value))


class StringTrait(HasTraits):
    value = Trait('string')


class StringTest(AnyTraitTest):

    obj = StringTrait()

    _default_value = 'string'
    _good_values = [10, -10, 10, -10, 10.1, -10.1, '10', '-10', '10L',
                    '-10L', '10.1', '-10.1', 'string', 'string', 1j, [10],
                    ['ten'], {'ten': 10}, (10,), None]
    _bad_values = []

    def coerce(self, value):
        return str(value)


class UnicodeTrait(HasTraits):
    value = Trait('unicode')


class UnicodeTest(StringTest):

    obj = UnicodeTrait()

    _default_value = 'unicode'
    _good_values = [10, -10, 10, -10, 10.1, -10.1, '10', '-10', '10L',
                    '-10L', '10.1', '-10.1', '', '', 'string', 'string', 1j,
                    [10], ['ten'], ['ten'], {'ten': 10}, (10,), None]
    _bad_values = []

    def coerce(self, value):
        return str(value)


class EnumTrait(HasTraits):
    value = Trait([1, 'one', 2, 'two', 3, 'three', 4.4, 'four.four'])


class EnumTest(AnyTraitTest):

    obj = EnumTrait()

    _default_value = 1
    _good_values = [1, 'one', 2, 'two', 3, 'three', 4.4, 'four.four']
    _bad_values = [0, 'zero', 4, None]


class MappedTrait(HasTraits):
    value = Trait('one', {'one': 1, 'two': 2, 'three': 3})


class MappedTest(AnyTraitTest):
    obj = MappedTrait()

    _default_value = 'one'
    _good_values = ['one', 'two', 'three']
    _mapped_values = [1, 2, 3]
    _bad_values = ['four', 1, 2, 3, [1], (1,), {1: 1}, None]


class PrefixListTrait(HasTraits):
    value = Trait('one', TraitPrefixList('one', 'two', 'three'))


class PrefixListTest(AnyTraitTest):
    obj = PrefixListTrait()

    _default_value = 'one'
    _good_values = ['o', 'on', 'one', 'tw', 'two', 'th', 'thr', 'thre',
                    'three']
    _bad_values = ['t', 'one ', ' two', 1, None]

    def coerce(self, value):
        return {'o': 'one', 'on': 'one', 'tw': 'two', 'th': 'three'}[value[:2]]


class PrefixMapTrait(HasTraits):
    value = Trait('one', TraitPrefixMap({'one': 1, 'two': 2, 'three': 3}))


class PrefixMapTest(AnyTraitTest):
    obj = PrefixMapTrait()

    _default_value = 'one'
    _good_values = ['o', 'on', 'one', 'tw', 'two', 'th', 'thr', 'thre',
                    'three']
    _mapped_values = [1, 1, 1, 2, 2, 3, 3, 3]
    _bad_values = ['t', 'one ', ' two', 1, None]

    def coerce(self, value):
        return {'o': 'one', 'on': 'one', 'tw': 'two', 'th': 'three'}[value[:2]]


class IntRangeTrait(HasTraits):
    value = Trait(3, TraitRange(2, 5))


class IntRangeTest(AnyTraitTest):

    obj = IntRangeTrait()

    _default_value = 3
    _good_values = [2, 3, 4, 5]
    _bad_values = [0, 1, 6, 0.999, 6.01, 'two', '0.999', '6.01', None]

    def coerce(self, value):
        try:
            return int(value)
        except:
            try:
                return int(float(value))
            except:
                return int(int(value))


class FloatRangeTrait(HasTraits):
    value = Trait(3.0, TraitRange(2.0, 5.0))


class FloatRangeTest(AnyTraitTest):

    obj = FloatRangeTrait()

    _default_value = 3.0
    _good_values = [2.0, 3.0, 4.0, 5.0, 2.001, 4.999]
    _bad_values = [0, 1, 6, 0, 1, 6, 1.999, 6.01, 'two', '0.999', '6.01',
                   None]

    def coerce(self, value):
        try:
            return float(value)
        except:
            return float(int(value))


# Old style class version:


class OTraitTest1:
    pass


class OTraitTest2(OTraitTest1):
    pass


class OTraitTest3(OTraitTest2):
    pass


class OBadTraitTest:
    pass

otrait_test1 = OTraitTest1()


class OldInstanceTrait(HasTraits):
    value = Trait(otrait_test1)


class OldInstanceTest(AnyTraitTest):
    obj = OldInstanceTrait()

    _default_value = otrait_test1
    _good_values = [otrait_test1, OTraitTest1(), OTraitTest2(),
                    OTraitTest3(), None]
    _bad_values = [0, 0, 0.0, 0j, OTraitTest1, OTraitTest2, OBadTraitTest(),
                   'string', 'string', [otrait_test1], (otrait_test1,),
                   {'data': otrait_test1}]


# New style class version:
class NTraitTest1(object):
    pass


class NTraitTest2(NTraitTest1):
    pass


class NTraitTest3(NTraitTest2):
    pass


class NBadTraitTest:
    pass


ntrait_test1 = NTraitTest1()


class NewInstanceTrait(HasTraits):
    value = Trait(ntrait_test1)


class NewInstanceTest(AnyTraitTest):
    obj = NewInstanceTrait()

    _default_value = ntrait_test1
    _good_values = [ntrait_test1, NTraitTest1(), NTraitTest2(), NTraitTest3(),
                    None]
    _bad_values = [0, 0, 0.0, 0j, NTraitTest1, NTraitTest2, NBadTraitTest(),
                   'string', 'string', [ntrait_test1], (ntrait_test1,),
                   {'data': ntrait_test1}]


class FactoryClass(HasTraits):
    pass


class ConsumerClass(HasTraits):
    x = Instance(FactoryClass, ())


class ConsumerSubclass(ConsumerClass):
    x = FactoryClass()

embedded_instance_trait = Trait('', Str,
                                Instance('traits.has_traits.HasTraits'))


class Dummy(HasTraits):
    x = embedded_instance_trait
    xl = List(embedded_instance_trait)


class RegressionTest(unittest.TestCase):
    """ Check that fixed bugs stay fixed.
    """

    def test_factory_subclass_no_segfault(self):
        """ Test that we can provide an instance as a default in the definition
        of a subclass.
        """
        # There used to be a bug where this would segfault.
        obj = ConsumerSubclass()
        obj.x

    def test_trait_compound_instance(self):
        """ Test that a deferred Instance() embedded in a TraitCompound handler
        and then a list will not replace the validate method for the outermost
        trait.
        """
        # Pass through an instance in order to make the instance trait resolve
        # the class.
        d = Dummy()
        d.xl = [HasTraits()]
        d.x = 'OK'

#  Trait(using a function) that must be an odd integer:


def odd_integer(object, name, value):
    try:
        float(value)
        if(value % 2) == 1:
            return int(value)
    except:
        pass
    raise TraitError


class OddIntegerTrait(HasTraits):
    value = Trait(99, odd_integer)


class OddIntegerTest(AnyTraitTest):
    obj = OddIntegerTrait()

    _default_value = 99
    _good_values = [1, 3, 5, 7, 9, 999999999,
                    1, 3, 5, 7, 9, 999999999,
                    1.0, 3.0, 5.0, 7.0, 9.0, 999999999.0,
                    -1, -3, -5, -7, -9, -999999999,
                    -1, -3, -5, -7, -9, -999999999,
                    -1.0, -3.0, -5.0, -7.0, -9.0, -999999999.0]
    _bad_values = [0, 2, -2, 1j, None, '1', [1], (1,), {1: 1}]


class NotifierTraits(HasTraits):
    value1 = Int
    value2 = Int
    value1_count = Int
    value2_count = Int

    def _anytrait_changed(self, trait_name, old, new):
        if trait_name == 'value1':
            self.value1_count += 1
        elif trait_name == 'value2':
            self.value2_count += 1

    def _value1_changed(self, old, new):
        self.value1_count += 1

    def _value2_changed(self, old, new):
        self.value2_count += 1


class NotifierTests(unittest.TestCase):
    obj = NotifierTraits()

    def __init__(self, value):
        unittest.TestCase.__init__(self, value)

    def setUp(self):
        obj = self.obj
        obj.value1 = 0
        obj.value2 = 0
        obj.value1_count = 0
        obj.value2_count = 0

    def tearDown(self):
        obj = self.obj
        obj.on_trait_change(self.on_value1_changed, 'value1', remove=True)
        obj.on_trait_change(self.on_value2_changed, 'value2', remove=True)
        obj.on_trait_change(self.on_anytrait_changed, remove=True)

    def on_anytrait_changed(self, object, trait_name, old, new):
        if trait_name == 'value1':
            self.obj.value1_count += 1
        elif trait_name == 'value2':
            self.obj.value2_count += 1

    def on_value1_changed(self):
        self.obj.value1_count += 1

    def on_value2_changed(self):
        self.obj.value2_count += 1

    def test_simple(self):
        obj = self.obj

        obj.value1 = 1
        self.assertEqual(obj.value1_count, 2)
        self.assertEqual(obj.value2_count, 0)

        obj.value2 = 1
        self.assertEqual(obj.value1_count, 2)
        self.assertEqual(obj.value2_count, 2)

    def test_complex(self):
        obj = self.obj

        obj.on_trait_change(self.on_value1_changed, 'value1')
        obj.value1 = 1
        self.assertEqual(obj.value1_count, 3)
        self.assertEqual(obj.value2_count, 0)

        obj.on_trait_change(self.on_value2_changed, 'value2')
        obj.value2 = 1
        self.assertEqual(obj.value1_count, 3)
        self.assertEqual(obj.value2_count, 3)

        obj.on_trait_change(self.on_anytrait_changed)

        obj.value1 = 2
        self.assertEqual(obj.value1_count, 7)
        self.assertEqual(obj.value2_count, 3)

        obj.value1 = 2
        self.assertEqual(obj.value1_count, 7)
        self.assertEqual(obj.value2_count, 3)

        obj.value2 = 2
        self.assertEqual(obj.value1_count, 7)
        self.assertEqual(obj.value2_count, 7)

        obj.on_trait_change(self.on_value1_changed, 'value1', remove=True)
        obj.value1 = 3
        self.assertEqual(obj.value1_count, 10)
        self.assertEqual(obj.value2_count, 7)

        obj.on_trait_change(self.on_value2_changed, 'value2', remove=True)
        obj.value2 = 3
        self.assertEqual(obj.value1_count, 10)
        self.assertEqual(obj.value2_count, 10)

        obj.on_trait_change(self.on_anytrait_changed, remove=True)

        obj.value1 = 4
        self.assertEqual(obj.value1_count, 12)
        self.assertEqual(obj.value2_count, 10)

        obj.value2 = 4
        self.assertEqual(obj.value1_count, 12)
        self.assertEqual(obj.value2_count, 12)


class RaisesArgumentlessRuntimeError(HasTraits):
    x = Int(0)

    def _x_changed(self):
        raise RuntimeError


class TestRuntimeError(unittest.TestCase):

    def setUp(self):
        push_exception_handler(lambda *args: None, reraise_exceptions=True)

    def tearDown(self):
        pop_exception_handler()

    def test_runtime_error(self):
        f = RaisesArgumentlessRuntimeError()
        self.assertRaises(RuntimeError, setattr, f, 'x', 5)


class DelegatedFloatTrait(HasTraits):
    value = Trait(99.0)


class DelegateTrait(HasTraits):
    value = Delegate('delegate')
    delegate = Trait(DelegatedFloatTrait())


class DelegateTrait2(DelegateTrait):
    delegate = Trait(DelegateTrait())


class DelegateTrait3(DelegateTrait):
    delegate = Trait(DelegateTrait2())


class DelegateTests(unittest.TestCase):

    def test_delegation(self):
        obj = DelegateTrait3()

        self.assertEqual(obj.value, 99.0)
        parent1 = obj.delegate
        parent2 = parent1.delegate
        parent3 = parent2.delegate
        parent3.value = 3.0
        self.assertEqual(obj.value, 3.0)
        parent2.value = 2.0
        self.assertEqual(obj.value, 2.0)
        self.assertEqual(parent3.value, 3.0)
        parent1.value = 1.0
        self.assertEqual(obj.value, 1.0)
        self.assertEqual(parent2.value, 2.0)
        self.assertEqual(parent3.value, 3.0)
        obj.value = 0.0
        self.assertEqual(obj.value, 0.0)
        self.assertEqual(parent1.value, 1.0)
        self.assertEqual(parent2.value, 2.0)
        self.assertEqual(parent3.value, 3.0)
        del obj.value
        self.assertEqual(obj.value, 1.0)
        del parent1.value
        self.assertEqual(obj.value, 2.0)
        self.assertEqual(parent1.value, 2.0)
        del parent2.value
        self.assertEqual(obj.value, 3.0)
        self.assertEqual(parent1.value, 3.0)
        self.assertEqual(parent2.value, 3.0)
        del parent3.value
        # Uncommenting the following line allows
        # the last assertions to pass. However, this
        # may not be intended behavior, so keeping
        # the line commented.
        #del parent2.value
        self.assertEqual(obj.value, 99.0)
        self.assertEqual(parent1.value, 99.0)
        self.assertEqual(parent2.value, 99.0)
        self.assertEqual(parent3.value, 99.0)

#  Complex(i.e. 'composite') Traits tests:

# Make a TraitCompound handler that does not have a fast_validate so we can
# check for a particular regression.
slow = Trait(1, TraitRange(1, 3), TraitRange(-3, -1))
try:
    del slow.handler.fast_validate
except AttributeError:
    pass


class complex_value(HasTraits):
    num1 = Trait(1, TraitRange(1, 5), TraitRange(-5, -1))
    num2 = Trait(1, TraitRange(1, 5),
                 TraitPrefixList('one', 'two', 'three', 'four', 'five'))
    num3 = Trait(1, TraitRange(1, 5),
                 TraitPrefixMap({'one': 1, 'two': 2, 'three': 3,
                                 'four': 4, 'five': 5}))
    num4 = Trait(1, Trait(1, Tuple, slow), 10)
    num5 = Trait(1, 10, Trait(1, Tuple, slow))


class test_complex_value(test_base2):
    obj = complex_value()

    def test_num1(self):
        self.check_values('num1', 1, [1, 2, 3, 4, 5, -1, -2, -3, -4, -5],
                          [0, 6, -6, '0', '6', '-6', 0.0, 6.0, -6.0, [1], (1,),
                           {1: 1}, None],
                          [1, 2, 3, 4, 5, -1, -2, -3, -4, -5])

    def test_enum_exceptions(self):
        """ Check that enumerated values can be combined with nested
        TraitCompound handlers.
        """
        self.check_values('num4', 1, [1, 2, 3, -3, -2, -1, 10, ()],
                          [0, 4, 5, -5, -4, 11],
                          )
        self.check_values('num5', 1, [1, 2, 3, -3, -2, -1, 10, ()],
                          [0, 4, 5, -5, -4, 11],
                          )


class list_value(HasTraits):
    # Trait definitions:
    list1 = Trait([2], TraitList(Trait([1, 2, 3, 4]),
                                 maxlen=4))
    list2 = Trait([2], TraitList(Trait([1, 2, 3, 4]),
                                 minlen=1, maxlen=4))
    alist = List()


class test_list_value(test_base2):

    obj = list_value()

    def setUp(self):
        test_base2.setUp(self)
        self.last_event = None

    def tearDown(self):
        del self.last_event

    def del_range(self, list, index1, index2):
        del list[index1: index2]

    def del_extended_slice(self, list, index1, index2, step):
        del list[index1:index2:step]

    def check_list(self, list):
        self.assertEqual(list, [2])
        self.assertEqual(len(list), 1)
        list.append(3)
        self.assertEqual(len(list), 2)
        list[1] = 2
        self.assertEqual(list[1], 2)
        self.assertEqual(len(list), 2)
        list[0] = 1
        self.assertEqual(list[0], 1)
        self.assertEqual(len(list), 2)
        self.assertRaises(TraitError, self.indexed_assign, list, 0, 5)
        self.assertRaises(TraitError, list.append, 5)
        self.assertRaises(TraitError, list.extend, [1, 2, 3])
        list.extend([3, 4])
        self.assertEqual(list, [1, 2, 3, 4])
        self.assertRaises(TraitError, list.append, 1)
        self.assertRaises(ValueError,
                          self.extended_slice_assign,
                          list, 0, 4, 2, [4, 5, 6])
        del list[1]
        self.assertEqual(list, [1, 3, 4])
        del list[0]
        self.assertEqual(list, [3, 4])
        list[:0] = [1, 2]
        self.assertEqual(list, [1, 2, 3, 4])
        self.assertRaises(TraitError,
                          self.indexed_range_assign, list, 0, 0, [1])
        del list[0:3]
        self.assertEqual(list, [4])
        self.assertRaises(TraitError,
                          self.indexed_range_assign, list, 0, 0, [4, 5])

    def test_list1(self):
        self.check_list(self.obj.list1)

    def test_list2(self):
        self.check_list(self.obj.list2)
        self.assertRaises(TraitError, self.del_range, self.obj.list2, 0, 1)
        self.assertRaises(TraitError,
                          self.del_extended_slice,
                          self.obj.list2, 4, -5, -1)

    def assertLastTraitListEventEqual(self, index, removed, added):
        self.assertEqual(self.last_event.index, index)
        self.assertEqual(self.last_event.removed, removed)
        self.assertEqual(self.last_event.added, added)

    def test_trait_list_event(self):
        """ Record TraitListEvent behavior.
        """
        # FIXME: The behavior of TraitListEvent is suboptimal with
        # respect to extended slice changes. Previously, TraitListObject
        # used to have a __setitem__() and a separate __setslice__() to
        # handle non-extended slices. Extended slices were added to the
        # underlying list object later. The __setitem__() code handled
        # the new extended slices, but created the TraitListEvent in the
        # same way it did for an integer index; namely it wrapped the
        # value with a list. For simple slices, the `index` attribute of
        # the TraitListEvent is an integer, and the `added` list is just
        # the list of values added. For an extended slice, the `index`
        # attribute is the slice object and the `added` list is the list
        # of values wrapped in another list.
        self.obj.alist = [1, 2, 3, 4]
        self.obj.on_trait_change(self._record_trait_list_event, 'alist_items')
        del self.obj.alist[0]
        self.assertLastTraitListEventEqual(0, [1], [])
        self.obj.alist.append(5)
        self.assertLastTraitListEventEqual(3, [], [5])
        self.obj.alist[0:2] = [6, 7]
        self.assertLastTraitListEventEqual(0, [2, 3], [6, 7])
        self.obj.alist[0:2:1] = [8, 9]
        self.assertLastTraitListEventEqual(0, [6, 7], [8, 9])
        old_event = self.last_event
        self.obj.alist[0:2:1] = [8, 9]
        # If no values changed, no new TraitListEvent will be generated.
        self.assertIs(self.last_event, old_event)
        self.obj.alist[0:4:2] = [10, 11]
        self.assertLastTraitListEventEqual(
            slice(0, 4, 2), [[8, 4]], [[10, 11]])
        del self.obj.alist[1:4:2]
        self.assertLastTraitListEventEqual(slice(1, 4, 2), [[9, 5]], [])
        self.obj.alist = [1, 2, 3, 4]
        del self.obj.alist[2:4]
        self.assertLastTraitListEventEqual(2, [3, 4], [])

    def _record_trait_list_event(self, object, name, old, new):
        self.last_event = new
