# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """Test trait types of the widget packages.""" import array import datetime as dt import pytest from unittest import TestCase from traitlets import HasTraits, Int, TraitError from traitlets.tests.test_traitlets import TraitTestBase from ipywidgets import Color, NumberFormat from ipywidgets.widgets.widget import _remove_buffers, _put_buffers from ipywidgets.widgets.trait_types import date_serialization, TypedTuple class NumberFormatTrait(HasTraits): value = NumberFormat(".3f") class TestNumberFormat(TraitTestBase): obj = NumberFormatTrait() _good_values = [ '.2f', '.0%', '($.2f', '+20', '.^20', '.2s', '#x', ',.2r', ' .2f', '.2', '' ] _bad_values = [52, False, 'broken', '..2f', '.2a'] class ColorTrait(HasTraits): value = Color("black") class TestColor(TraitTestBase): obj = ColorTrait() _good_values = [ "blue", # valid color name "#AA0", # single digit hex "#FFFFFF", # double digit hex "transparent", # special color name '#aaaa', # single digit hex with alpha '#ffffffff', # double digit hex with alpha 'rgb(0, 0, 0)', # rgb 'rgb( 20,70,50 )', # rgb with spaces 'rgba(10,10,10, 0.5)', # rgba with float alpha 'rgba(255, 255, 255, 255)', # out of bounds alpha (spec says clamp to 1) 'hsl(0.0, .0, 0)', # hsl 'hsl( 0.5,0.3,0 )', # hsl with spaces 'hsla(10,10,10, 0.5)', # rgba with float alpha ] _bad_values = [ "vanilla", "blues", # Invald color names 1.2, 0.0, # Should fail with float input 0, 1, 2, # Should fail with int input 'rgb(0.4, 512, -40)', 'hsl(0.4, 512, -40)', 'rgba(0, 0, 0)', 'hsla(0, 0, 0)', None, ] class ColorTraitWithNone(HasTraits): value = Color("black", allow_none=True) class TestColorWithNone(TraitTestBase): obj = ColorTraitWithNone() _good_values = TestColor._good_values + [None] _bad_values = list(filter(lambda v: v is not None, TestColor._bad_values)) class TestDateSerialization(TestCase): def setUp(self): self.to_json = date_serialization['to_json'] self.dummy_manager = None def test_serialize_none(self): self.assertIs(self.to_json(None, self.dummy_manager), None) def test_serialize_date(self): date = dt.date(1900, 2, 18) expected = { 'year': 1900, 'month': 1, 'date': 18 } self.assertEqual(self.to_json(date, self.dummy_manager), expected) class TestDateDeserialization(TestCase): def setUp(self): self.from_json = date_serialization['from_json'] self.dummy_manager = None def test_deserialize_none(self): self.assertIs(self.from_json(None, self.dummy_manager), None) def test_deserialize_date(self): serialized_date = { 'year': 1900, 'month': 1, 'date': 18 } expected = dt.date(1900, 2, 18) self.assertEqual( self.from_json(serialized_date, self.dummy_manager), expected ) class TestBuffers(TestCase): def test_remove_and_put_buffers(self): mv1 = memoryview(b'test1') mv2 = memoryview(b'test2') state = {'plain': [0, 'text'], # should not get removed 'x': {'ar': mv1}, # should result in an empty dict 'y': {'shape': (10, 10), 'data': mv1}, 'z': (mv1, mv2), # tests tuple assigment 'top': mv1, # test a top level removal 'deep': {'a': 1, 'b':[0,{'deeper':mv2}]}} # deeply nested plain = state['plain'] x = state['x'] y = state['y'] y_shape = y['shape'] state_before = state state, buffer_paths, buffers = _remove_buffers(state) # check if buffers are removed self.assertIn('plain', state) self.assertIn('shape', state['y']) self.assertNotIn('ar', state['x']) self.assertEqual(state['x'], {}) self.assertNotIn('data', state['y']) self.assertNotIn(mv1, state['z']) self.assertNotIn(mv1, state['z']) self.assertNotIn('top', state) self.assertIn('deep', state) self.assertIn('b', state['deep']) self.assertNotIn('deeper', state['deep']['b'][1]) # check that items that didn't need change aren't touched self.assertIsNot(state, state_before) self.assertIs(state['plain'], plain) self.assertIsNot(state['x'], x) self.assertIsNot(state['y'], y) self.assertIs(state['y']['shape'], y_shape) # check that the buffer paths really point to the right buffer for path, buffer in [(['x', 'ar'], mv1), (['y', 'data'], mv1), (['z', 0], mv1), (['z', 1], mv2),\ (['top'], mv1), (['deep', 'b', 1, 'deeper'], mv2)]: self.assertIn(path, buffer_paths, "%r not in path" % path) index = buffer_paths.index(path) self.assertEqual(buffer, buffers[index]) # and check that we can put it back together again _put_buffers(state, buffer_paths, buffers) # we know that tuples get converted to list, so help the comparison by changing the tuple to a list state_before['z'] = list(state_before['z']) self.assertEqual(state_before, state) def test_typed_tuple_uninitialized_ints(): class TestCase(HasTraits): value = TypedTuple(trait=Int()) obj = TestCase() assert obj.value == () def test_typed_tuple_init_ints(): class TestCase(HasTraits): value = TypedTuple(trait=Int()) obj = TestCase(value=(1, 2, 3)) assert obj.value == (1, 2, 3) def test_typed_tuple_set_ints(): class TestCase(HasTraits): value = TypedTuple(trait=Int()) obj = TestCase() obj.value = (1, 2, 3) assert obj.value == (1, 2, 3) def test_typed_tuple_default(): class TestCase(HasTraits): value = TypedTuple(default_value=(1, 2, 3)) obj = TestCase() assert obj.value == (1, 2, 3) def test_typed_tuple_mixed_default(): class TestCase(HasTraits): value = TypedTuple(default_value=(1, 2, 'foobar')) obj = TestCase() assert obj.value == (1, 2, 'foobar') def test_typed_tuple_bad_default(): class TestCase(HasTraits): value = TypedTuple(trait=Int(), default_value=(1, 2, 'foobar')) with pytest.raises(TraitError): obj = TestCase() a = obj.value # a read might be needed to trigger default validation def test_typed_tuple_bad_set(): class TestCase(HasTraits): value = TypedTuple(trait=Int()) obj = TestCase() with pytest.raises(TraitError): obj.value = (1, 2, 'foobar') def test_typed_tuple_positional_trait(): class TestCase(HasTraits): value = TypedTuple(Int()) obj = TestCase(value=(1, 2, 3)) assert obj.value == (1, 2, 3) def test_typed_tuple_positional_default(): class TestCase(HasTraits): value = TypedTuple((1, 2, 3)) obj = TestCase() assert obj.value == (1, 2, 3)