1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
import gc
from unittest.mock import Mock
import pytest
from app_model.expressions import Context, create_context, get_context
from app_model.expressions._context import _OBJ_TO_CONTEXT
def test_create_context() -> None:
"""You can create a context for any object"""
class T: ...
t = T()
tid = id(t)
ctx = create_context(t)
assert get_context(t) == ctx
assert hash(ctx) # hashable
assert tid in _OBJ_TO_CONTEXT
_OBJ_TO_CONTEXT.pop(tid)
del t
gc.collect()
assert tid not in _OBJ_TO_CONTEXT
# you can provide your own root, but it must be a context
create_context(T(), root=Context())
with pytest.raises(AssertionError):
create_context(T(), root={}) # type: ignore
def test_create_and_get_scoped_contexts() -> None:
"""Test that objects created in the stack of another contexted object.
likely the most common way that this API will be used:
"""
before = len(_OBJ_TO_CONTEXT)
class A:
def __init__(self) -> None:
create_context(self)
self.b = B()
class B:
def __init__(self) -> None:
create_context(self)
obja = A()
assert len(_OBJ_TO_CONTEXT) == before + 2
ctxa = get_context(obja)
ctxb = get_context(obja.b)
assert ctxa is not None
assert ctxb is not None
ctxa["hi"] = "hi"
assert ctxb["hi"] == "hi"
# keys get deleted on object deletion
del obja
gc.collect()
assert len(_OBJ_TO_CONTEXT) == before
def test_context_events() -> None:
"""Changing context keys emits an event"""
mock = Mock()
root = Context()
scoped = root.new_child()
scoped.changed.connect(mock) # connect the mock to the child
root["a"] = 1
# child re-emits parent events
assert mock.call_args[0][0] == {"a"}
mock.reset_mock()
scoped["b"] = 1
# also emits own events
assert mock.call_args[0][0] == {"b"}
mock.reset_mock()
del scoped["b"]
assert mock.call_args[0][0] == {"b"}
# but parent does not emit child events
mock.reset_mock()
mock2 = Mock()
root.changed.connect(mock2)
scoped["c"] = "c"
mock.assert_called_once()
mock2.assert_not_called()
mock.reset_mock()
with scoped.buffered_changes():
scoped["d"] = "d"
scoped["e"] = "f"
scoped["f"] = "f"
mock.assert_called_once_with({"d", "e", "f"})
# check events properly propagated when adding already existing context as child
mock3 = Mock()
root2a = Context()
root2b = Context()
scoped2 = root2a.new_child(root2b)
scoped2.changed.connect(mock3) # connect the mock to the child
root2a["a"] = 1
# child re-emits parent events
assert mock3.call_args[0][0] == {"a"}
mock3.reset_mock()
root2b["b"] = 1
# child re-emits added events
assert mock3.call_args[0][0] == {"b"}
mock3.reset_mock()
scoped2["c"] = 1
# also emits own events
assert mock3.call_args[0][0] == {"c"}
# check events properly propagated when making a context of contexts
mock4 = Mock()
root3a = Context()
root3b = Context()
root3c = Context()
root3d = Context()
root3e = Context()
combined = Context(root3a, root3b, root3c, root3d, root3e)
combined.changed.connect(mock4)
root3a["a"] = 1
assert mock4.call_args[0][0] == {"a"}
mock4.reset_mock()
root3b["b"] = 1
assert mock4.call_args[0][0] == {"b"}
mock4.reset_mock()
root3c["c"] = 1
assert mock4.call_args[0][0] == {"c"}
mock4.reset_mock()
root3d["d"] = 1
assert mock4.call_args[0][0] == {"d"}
mock4.reset_mock()
root3e["e"] = 1
assert mock4.call_args[0][0] == {"e"}
|