def test_delays_unsubscribe_until_the_end_of_current_dispatch(self): store = create_store(reducers['todos']) unsubscribe_handles = [] def do_unsubscribe_all(): for unsubscribe in unsubscribe_handles: unsubscribe() listener_1 = mock.MagicMock() listener_2 = mock.MagicMock() listener_3 = mock.MagicMock() unsubscribe_handles.append(store['subscribe'](lambda: listener_1())) unsubscribe_handles.append(store['subscribe']( lambda: [listener_2(), do_unsubscribe_all()])) unsubscribe_handles.append(store['subscribe'](lambda: listener_3())) store['dispatch'](unknown_action()) self.assertEqual(len(listener_1.call_args_list), 1) self.assertEqual(len(listener_2.call_args_list), 1) self.assertEqual(len(listener_3.call_args_list), 1) store['dispatch'](unknown_action()) self.assertEqual(len(listener_1.call_args_list), 1) self.assertEqual(len(listener_2.call_args_list), 1) self.assertEqual(len(listener_3.call_args_list), 1)
def test_applied_reducer_to_initial_state(self): store = create_store(reducers['todos'], [ { 'id': 1, 'text': 'Hello' } ]) self.assertEqual(store['get_state'](), [ { 'id': 1, 'text': 'Hello' } ]) store['dispatch'](unknown_action()) self.assertEqual(store['get_state'](), [ { 'id': 1, 'text': 'Hello' } ]) store['dispatch'](add_todo('World')) self.assertEqual(store['get_state'](), [ { 'id': 1, 'text': 'Hello' }, { 'id': 2, 'text': 'World' } ])
def test_delays_subscribe_until_the_end_of_current_dispatch(self): store = create_store(reducers['todos']) listener_1 = mock.MagicMock() listener_2 = mock.MagicMock() listener_3 = mock.MagicMock() listener_3_added = False def maybe_add_third_listener(): nonlocal listener_3_added if not listener_3_added: listener_3_added = True store['subscribe'](lambda: listener_3()) store['subscribe'](lambda: listener_1()) store['subscribe'](lambda: [listener_2(), maybe_add_third_listener()]) store['dispatch'](unknown_action()) self.assertEqual(len(listener_1.call_args_list), 1) self.assertEqual(len(listener_2.call_args_list), 1) self.assertEqual(len(listener_3.call_args_list), 0) store['dispatch'](unknown_action()) self.assertEqual(len(listener_1.call_args_list), 2) self.assertEqual(len(listener_2.call_args_list), 2) self.assertEqual(len(listener_3.call_args_list), 1)
def test_delays_unsubscribe_until_the_end_of_current_dispatch(self): store = create_store(reducers['todos']) unsubscribe_handles = [] def do_unsubscribe_all(): for unsubscribe in unsubscribe_handles: unsubscribe() listener_1 = mock.MagicMock() listener_2 = mock.MagicMock() listener_3 = mock.MagicMock() unsubscribe_handles.append(store['subscribe'](lambda: listener_1())) unsubscribe_handles.append(store['subscribe'](lambda: [listener_2(), do_unsubscribe_all()])) unsubscribe_handles.append(store['subscribe'](lambda: listener_3())) store['dispatch'](unknown_action()) self.assertEqual(len(listener_1.call_args_list), 1) self.assertEqual(len(listener_2.call_args_list), 1) self.assertEqual(len(listener_3.call_args_list), 1) store['dispatch'](unknown_action()) self.assertEqual(len(listener_1.call_args_list), 1) self.assertEqual(len(listener_2.call_args_list), 1) self.assertEqual(len(listener_3.call_args_list), 1)
def test_handles_nested_dispatches_gracefully(self): def foo(state, action={}): if state is None: state = 0 if action.get('type') == 'foo': return 1 return state def bar(state, action={}): if state is None: state = 0 if action.get('type') == 'bar': return 2 else: return state store = create_store(combine_reducers({'foo': foo, 'bar': bar})) def kinda_component_did_update(): state = store['get_state']() if state.get('bar') == 0: store['dispatch']({'type': 'bar'}) store['subscribe'](kinda_component_did_update) store['dispatch']({'type': 'foo'}) self.assertEqual(store['get_state'](), {'foo': 1, 'bar': 2})
def test_throws_if_action_type_is_none(self): store = create_store(reducers['todos']) with self.assertRaises(Exception) as e: store['dispatch']({'type': None}) self.assertTrue( 'Actions may not have an undefined "type"' in str(e.exception))
def test_handles_nested_dispatches_gracefully(self): def foo(state, action={}): if state is None: state = 0 if action.get('type') == 'foo': return 1 return state def bar(state, action={}): if state is None: state = 0 if action.get('type') == 'bar': return 2 else: return state store = create_store(combine_reducers({ 'foo': foo, 'bar': bar })) def kinda_component_did_update(): state = store['get_state']() if state.get('bar') == 0: store['dispatch']({ 'type': 'bar' }) store['subscribe'](kinda_component_did_update) store['dispatch']({ 'type': 'foo' }) self.assertEqual(store['get_state'](), { 'foo': 1, 'bar': 2 })
def test_preserves_state_when_replacing_reducer(self): store = create_store(reducers['todos']) store['dispatch'](add_todo('Hello')) store['dispatch'](add_todo('World')) self.assertEqual(store['get_state'](), [{ 'id': 1, 'text': 'Hello' }, { 'id': 2, 'text': 'World' }]) store['replace_reducer'](reducers['todos_reverse']) self.assertEqual(store['get_state'](), [{ 'id': 1, 'text': 'Hello' }, { 'id': 2, 'text': 'World' }]) store['dispatch'](add_todo('Perhaps')) self.assertEqual(store['get_state'](), [{ 'id': 3, 'text': 'Perhaps' }, { 'id': 1, 'text': 'Hello' }, { 'id': 2, 'text': 'World' }]) store['replace_reducer'](reducers['todos']) self.assertEqual(store['get_state'](), [{ 'id': 3, 'text': 'Perhaps' }, { 'id': 1, 'text': 'Hello' }, { 'id': 2, 'text': 'World' }]) store['dispatch'](add_todo('Surely')) self.assertEqual(store['get_state'](), [{ 'id': 3, 'text': 'Perhaps' }, { 'id': 1, 'text': 'Hello' }, { 'id': 2, 'text': 'World' }, { 'id': 4, 'text': 'Surely' }])
def test_does_not_throw_if_action_type_is_falsy(self): store = create_store(reducers['todos']) try: store['dispatch']({ 'type': False }) store['dispatch']({ 'type': 0 }) store['dispatch']({ 'type': '' }) except Exception: self.fail('These should not have raised an exception')
def test_passes_initial_action_and_initial_state(self): store = create_store(reducers['todos'], [ { 'id': 1, 'text': 'Hello' } ]) self.assertEqual(store['get_state'](), [{ 'id': 1, 'text': 'Hello' }])
def test_provides_up_to_date_state_when_subscriber_is_notified(self): store = create_store(reducers['todos']) def callback(): state = store['get_state']() self.assertEqual(state, [{'id': 1, 'text': 'Hello'}]) store['dispatch'](add_todo('Hello'))
def test_does_not_throw_if_action_type_is_falsy(self): store = create_store(reducers['todos']) try: store['dispatch']({'type': False}) store['dispatch']({'type': 0}) store['dispatch']({'type': ''}) except Exception: self.fail('These should not have raised an exception')
def test_exposes_public_API(self): store = create_store(combine_reducers(reducers)) methods = store.keys() self.assertEqual(len(methods), 4) self.assertTrue('subscribe' in methods) self.assertTrue('dispatch' in methods) self.assertTrue('get_state' in methods) self.assertTrue('replace_reducer' in methods)
def test_throws_if_listener_is_not_a_function(self): store = create_store(reducers['todos']) with self.assertRaises(Exception): store['subscribe']() with self.assertRaises(Exception): store['subscribe']('') with self.assertRaises(Exception): store['subscribe'](None)
def test_throws_if_reducer_is_not_a_function(self): with self.assertRaises(Exception): create_store(combine_reducers) with self.assertRaises(Exception): create_store('test') with self.assertRaises(Exception): create_store({}) try: create_store(lambda *x: {}) except Exception as e: self.fail('create_store(lambda: {}) should not have failed')
def test_throws_if_next_reducer_is_not_a_function(self): store = create_store(reducers['todos']) with self.assertRaises(Exception) as e: store['replace_reducer']() self.assertTrue('Expected next_reducer to be a function' in str(e.exception)) try: store['replace_reducer'](lambda *x: x) except Exception: self.fail('Should not have raised an exception')
def test_provides_up_to_date_state_when_subscriber_is_notified(self): store = create_store(reducers['todos']) def callback(): state = store['get_state']() self.assertEqual(state, [ { 'id': 1, 'text': 'Hello' } ]) store['dispatch'](add_todo('Hello'))
def test_throws_if_next_reducer_is_not_a_function(self): store = create_store(reducers['todos']) with self.assertRaises(Exception) as e: store['replace_reducer']() self.assertTrue( 'Expected next_reducer to be a function' in str(e.exception)) try: store['replace_reducer'](lambda *x: x) except Exception: self.fail('Should not have raised an exception')
def test_only_removes_relevant_listener_when_unsubscribe_is_called(self): store = create_store(reducers['todos']) listener = mock.MagicMock() store['subscribe'](listener) unsubscribe_second = store['subscribe'](listener) unsubscribe_second() unsubscribe_second() store['dispatch'](unknown_action()) self.assertEqual(len(listener.call_args_list), 1)
def test_only_removes_listener_once_when_unsubscribe_is_called(self): store = create_store(reducers['todos']) listener_a = mock.MagicMock() listener_b = mock.MagicMock() unsubscribe_a = store['subscribe'](listener_a) store['subscribe'](listener_b) unsubscribe_a() unsubscribe_a() store['dispatch'](unknown_action()) self.assertEqual(len(listener_a.call_args_list), 0) self.assertEqual(len(listener_b.call_args_list), 1)
def test_only_accepts_plain_objects(self): store = create_store(reducers['todos']) try: store['dispatch'](unknown_action()) except Exception: self.fail('Should not have thrown exception') class AwesomeMap: def __init__(self): self.x = 1 for non_object in [None, 42, 'hey', AwesomeMap()]: with self.assertRaises(Exception): store['dispatch'](non_object)
def test_applied_reducer_to_initial_state(self): store = create_store(reducers['todos'], [{'id': 1, 'text': 'Hello'}]) self.assertEqual(store['get_state'](), [{'id': 1, 'text': 'Hello'}]) store['dispatch'](unknown_action()) self.assertEqual(store['get_state'](), [{'id': 1, 'text': 'Hello'}]) store['dispatch'](add_todo('World')) self.assertEqual(store['get_state'](), [{ 'id': 1, 'text': 'Hello' }, { 'id': 2, 'text': 'World' }])
def test_supports_removing_a_subscription_within_a_subscription(self): store = create_store(reducers['todos']) listener_a = mock.MagicMock() listener_b = mock.MagicMock() listener_c = mock.MagicMock() store['subscribe'](listener_a) unsub_b = store['subscribe'](lambda: [listener_b(), unsub_b()]) store['subscribe'](listener_c) store['dispatch'](unknown_action()) store['dispatch'](unknown_action()) self.assertEqual(len(listener_a.call_args_list), 2) self.assertEqual(len(listener_b.call_args_list), 1) self.assertEqual(len(listener_c.call_args_list), 2)
def test_supports_multiple_subscriptions(self): store = create_store(reducers['todos']) listener_a = mock.MagicMock() listener_b = mock.MagicMock() unsubscribe_a = store['subscribe'](listener_a) store['dispatch'](unknown_action()) self.assertEqual(len(listener_a.call_args_list), 1) self.assertEqual(len(listener_b.call_args_list), 0) store['dispatch'](unknown_action()) self.assertEqual(len(listener_a.call_args_list), 2) self.assertEqual(len(listener_b.call_args_list), 0) unsubscribe_b = store['subscribe'](listener_b) self.assertEqual(len(listener_a.call_args_list), 2) self.assertEqual(len(listener_b.call_args_list), 0) store['dispatch'](unknown_action()) self.assertEqual(len(listener_a.call_args_list), 3) self.assertEqual(len(listener_b.call_args_list), 1) unsubscribe_a() self.assertEqual(len(listener_a.call_args_list), 3) self.assertEqual(len(listener_b.call_args_list), 1) store['dispatch'](unknown_action()) self.assertEqual(len(listener_a.call_args_list), 3) self.assertEqual(len(listener_b.call_args_list), 2) unsubscribe_b() self.assertEqual(len(listener_a.call_args_list), 3) self.assertEqual(len(listener_b.call_args_list), 2) store['dispatch'](unknown_action()) self.assertEqual(len(listener_a.call_args_list), 3) self.assertEqual(len(listener_b.call_args_list), 2) unsubscribe_a = store['subscribe'](listener_a) self.assertEqual(len(listener_a.call_args_list), 3) self.assertEqual(len(listener_b.call_args_list), 2) store['dispatch'](unknown_action()) self.assertEqual(len(listener_a.call_args_list), 4) self.assertEqual(len(listener_b.call_args_list), 2)
def test_accepts_enhancer_as_second_argument_if_no_initial_state(self): def spy_enhancer(vanilla_create_store): def enhancer(*args): self.assertEqual(args[0], reducers['todos']) self.assertEqual(args[1], None) self.assertEqual(len(args), 2) vanilla_store = vanilla_create_store(*args) vanilla_store['dispatch'] = mock.MagicMock( side_effect=vanilla_store['dispatch']) return vanilla_store return enhancer store = create_store(reducers['todos'], spy_enhancer) action = add_todo('Hello') store['dispatch'](action) self.assertEqual(store['dispatch'].call_args_list, [mock.call(action)]) self.assertEqual(store['get_state'](), [{'id': 1, 'text': 'Hello'}])
def test_uses_last_snapshot_of_subscribers_during_nested_dispatch(self): store = create_store(reducers['todos']) listener_1 = mock.MagicMock() listener_2 = mock.MagicMock() listener_3 = mock.MagicMock() listener_4 = mock.MagicMock() unsubscribe_4 = None unsubscribe_1 = None def callback_for_listener_1(): nonlocal unsubscribe_1, unsubscribe_4 listener_1() self.assertEqual(len(listener_1.call_args_list), 1) self.assertEqual(len(listener_2.call_args_list), 0) self.assertEqual(len(listener_3.call_args_list), 0) self.assertEqual(len(listener_4.call_args_list), 0) unsubscribe_1() unsubscribe_4 = store['subscribe'](listener_4) store['dispatch'](unknown_action()) self.assertEqual(len(listener_1.call_args_list), 1) self.assertEqual(len(listener_2.call_args_list), 1) self.assertEqual(len(listener_3.call_args_list), 1) self.assertEqual(len(listener_4.call_args_list), 1) unsubscribe_1 = store['subscribe'](callback_for_listener_1) store['subscribe'](listener_2) store['subscribe'](listener_3) store['dispatch'](unknown_action()) self.assertEqual(len(listener_1.call_args_list), 1) self.assertEqual(len(listener_2.call_args_list), 2) self.assertEqual(len(listener_3.call_args_list), 2) self.assertEqual(len(listener_4.call_args_list), 1) unsubscribe_4() store['dispatch'](unknown_action()) self.assertEqual(len(listener_1.call_args_list), 1) self.assertEqual(len(listener_2.call_args_list), 3) self.assertEqual(len(listener_3.call_args_list), 3) self.assertEqual(len(listener_4.call_args_list), 1)
def test_accepts_enhancer_as_second_argument_if_no_initial_state(self): def spy_enhancer(vanilla_create_store): def enhancer(*args): self.assertEqual(args[0], reducers['todos']) self.assertEqual(args[1], None) self.assertEqual(len(args), 2) vanilla_store = vanilla_create_store(*args) vanilla_store['dispatch'] = mock.MagicMock(side_effect=vanilla_store['dispatch']) return vanilla_store return enhancer store = create_store(reducers['todos'], spy_enhancer) action = add_todo('Hello') store['dispatch'](action) self.assertEqual(store['dispatch'].call_args_list, [mock.call(action)]) self.assertEqual(store['get_state'](), [{ 'id': 1, 'text': 'Hello' }])
def test_warns_if_no_reducers_passed_to_combine_reducers(self, logging): def foo(state=None, action=None): if state is None: state = { 'bar': 1} return state def baz(state=None, action=None): if state is None: state = { 'qux': 3 } return state reducer = combine_reducers({ 'foo': foo, 'baz': baz }) reducer() self.assertEqual(len(logging.call_args_list), 0) reducer({ 'foo': { 'bar': 2 }}) self.assertEqual(len(logging.call_args_list), 0) reducer({ 'foo': { 'bar': 2 }, 'baz': { 'qux': 4 } }) self.assertEqual(len(logging.call_args_list), 0) create_store(reducer, { 'bar': 2 }) m = str(logging.call_args_list[0]) self.assertTrue(re.search(r'Unexpected key "bar".*create_store.*instead: ("foo"|"baz"), ("foo"|"baz")', m)) create_store(reducer, { 'bar': 2, 'qux': 4, 'thud': 5 }) m = str(logging.call_args_list[1]) self.assertTrue(re.search(r'Unexpected keys ("qux"|"thud"), ("qux"|"thud").*create_store.*instead: ("foo"|"baz"), ("foo"|"baz")', m)) create_store(reducer, 1) m = str(logging.call_args_list[2]) self.assertTrue(re.search(r'create_store has an unexpected type of <class "int">.*keys: ("foo"|"baz"), ("foo"|"baz")', m)) reducer({ 'corge': 2 }) m = str(logging.call_args_list[3]) self.assertTrue(re.search(r'Unexpected key "corge".*reducer.*instead: ("foo"|"baz"), ("foo"|"baz")', m)) reducer({ 'rick': 2, 'morty': 4 }) m = str(logging.call_args_list[4]) self.assertTrue(re.search(r'Unexpected keys ("rick"|"morty"), ("rick"|"morty").*reducer.*instead: ("foo"|"baz"), ("foo"|"baz")', m)) reducer(1) m = str(logging.call_args_list[5]) self.assertTrue(re.search(r'reducer has an unexpected type of <class "int">', m))
def test_throws_if_enhancer_is_neither_undefined_or_a_function(self): with self.assertRaises(Exception): create_store(reducers['todos'], None, {}) with self.assertRaises(Exception): create_store(reducers['todos'], None, []) with self.assertRaises(Exception): create_store(reducers['todos'], None, False) try: create_store(reducers['todos'], None, None) create_store(reducers['todos'], None, lambda x: x) create_store(reducers['todos'], lambda x: x) create_store(reducers['todos'], []) create_store(reducers['todos'], {}) except Exception: self.fail('Should not have thrown an exception')
def test_does_not_allow_dispatch_from_within_reducer(self): store = create_store(reducers['dispatch_in_middle_of_reducer']) with self.assertRaises(Exception) as e: store['dispatch'](dispatch_in_middle(lambda: store['dispatch'] (unknown_action()))) self.assertTrue('may not dispatch' in str(e.exception))
def setUp(self): self.store = create_store(todos) self.action_creator_functions = dict(action_creators) for key in self.action_creator_functions: if not hasattr(self.action_creator_functions[key], '__call__'): del self.action_creator_functions[key]
def test_preserves_state_when_replacing_reducer(self): store = create_store(reducers['todos']) store['dispatch'](add_todo('Hello')) store['dispatch'](add_todo('World')) self.assertEqual(store['get_state'](), [ { 'id': 1, 'text': 'Hello' }, { 'id': 2, 'text': 'World' } ]) store['replace_reducer'](reducers['todos_reverse']) self.assertEqual(store['get_state'](), [ { 'id': 1, 'text': 'Hello' }, { 'id': 2, 'text': 'World' } ]) store['dispatch'](add_todo('Perhaps')) self.assertEqual(store['get_state'](), [ { 'id': 3, 'text': 'Perhaps' }, { 'id': 1, 'text': 'Hello' }, { 'id': 2, 'text': 'World' } ]) store['replace_reducer'](reducers['todos']) self.assertEqual(store['get_state'](), [ { 'id': 3, 'text': 'Perhaps' }, { 'id': 1, 'text': 'Hello' }, { 'id': 2, 'text': 'World' } ]) store['dispatch'](add_todo('Surely')) self.assertEqual(store['get_state'](), [ { 'id': 3, 'text': 'Perhaps' }, { 'id': 1, 'text': 'Hello' }, { 'id': 2, 'text': 'World' }, { 'id': 4, 'text': 'Surely' } ])
def test_passes_initial_action_and_initial_state(self): store = create_store(reducers['todos'], [{'id': 1, 'text': 'Hello'}]) self.assertEqual(store['get_state'](), [{'id': 1, 'text': 'Hello'}])
def test_throws_if_action_type_is_none(self): store = create_store(reducers['todos']) with self.assertRaises(Exception) as e: store['dispatch']({ 'type': None }) self.assertTrue('Actions may not have an undefined "type"' in str(e.exception))
def test_does_not_allow_dispatch_from_within_reducer(self): store = create_store(reducers['dispatch_in_middle_of_reducer']) with self.assertRaises(Exception) as e: store['dispatch'](dispatch_in_middle(lambda: store['dispatch'](unknown_action()))) self.assertTrue('may not dispatch' in str(e.exception))