|
1 | 1 | """
|
2 | 2 | Exercise PolyPlug.
|
3 | 3 | """
|
| 4 | +import builtins |
4 | 5 | import copy
|
5 | 6 | import json
|
6 | 7 | import pytest
|
7 | 8 | import polyplug
|
8 | 9 | from unittest import mock
|
9 | 10 |
|
10 | 11 |
|
| 12 | +@pytest.fixture(autouse=True) |
| 13 | +def test_wrapper(): |
| 14 | + """ |
| 15 | + Ensures clean state. |
| 16 | + """ |
| 17 | + # Clear the listeners. |
| 18 | + polyplug.LISTENERS = {} |
| 19 | + # Run the test. |
| 20 | + yield |
| 21 | + # ??? |
| 22 | + # Profit! |
| 23 | + |
| 24 | + |
11 | 25 | DOM_FROM_JSON = {
|
12 | 26 | "nodeType": 1,
|
13 | 27 | "tagName": "div",
|
@@ -892,3 +906,203 @@ def test_htmltokenizer_tokenize_complex_tree():
|
892 | 906 | tok = polyplug.HTMLTokenizer(raw)
|
893 | 907 | tok.tokenize(parent)
|
894 | 908 | assert parent.as_dict == expected
|
| 909 | + |
| 910 | + |
| 911 | +def test_get_listener_id(): |
| 912 | + """ |
| 913 | + Return a string containing a hex representation of a sha256 hash of the |
| 914 | + passed in Query, event type and listener function. |
| 915 | + """ |
| 916 | + q = polyplug.Query(id="foo") |
| 917 | + event_type = "click" |
| 918 | + |
| 919 | + def test_fn(): |
| 920 | + pass |
| 921 | + |
| 922 | + id_1 = polyplug.get_listener_id(q, event_type, test_fn) |
| 923 | + id_2 = polyplug.get_listener_id(q, event_type, test_fn) |
| 924 | + assert id_1 == id_2 # These should be the same..! |
| 925 | + |
| 926 | + |
| 927 | +def test_print(): |
| 928 | + """ |
| 929 | + The polyplug print function emits the expected JSON message. |
| 930 | + """ |
| 931 | + with mock.patch("builtins.print") as mock_print: |
| 932 | + # Simple case with defaults. |
| 933 | + polyplug.print("Hello", "world") |
| 934 | + mock_print.assert_called_once_with( |
| 935 | + '{"type": "stdout", "content": "Hello world\\n"}' |
| 936 | + ) |
| 937 | + mock_print.reset_mock() |
| 938 | + # More complex with sep and end |
| 939 | + polyplug.print("Hello", "world", sep="-", end="") |
| 940 | + mock_print.assert_called_once_with( |
| 941 | + '{"type": "stdout", "content": "Hello-world"}' |
| 942 | + ) |
| 943 | + |
| 944 | + |
| 945 | +def test_update(): |
| 946 | + """ |
| 947 | + Given a query object and a representation of a target node, the expected |
| 948 | + updateDOM message is emitted, with the correct payload. |
| 949 | + """ |
| 950 | + query = polyplug.Query(id="foo") |
| 951 | + raw_dom = copy.deepcopy(DOM_FROM_JSON) |
| 952 | + target = polyplug.ElementNode(**raw_dom) |
| 953 | + with mock.patch("builtins.print") as mock_print: |
| 954 | + polyplug.update(query, target) |
| 955 | + assert mock_print.call_count == 1 |
| 956 | + msg = json.loads(mock_print.call_args.args[0]) |
| 957 | + assert msg["type"] == "updateDOM" |
| 958 | + assert msg["query"]["id"] == "foo" |
| 959 | + assert msg["target"] == DOM_FROM_JSON |
| 960 | + |
| 961 | + |
| 962 | +def test_remove(): |
| 963 | + """ """ |
| 964 | + with mock.patch("builtins.print") as mock_print: |
| 965 | + |
| 966 | + @polyplug.plug(polyplug.Query(id="foo"), "some-event") |
| 967 | + def test_fn(event): |
| 968 | + return "It works!" |
| 969 | + |
| 970 | + assert mock_print.call_count == 1 |
| 971 | + listener_id = polyplug.get_listener_id( |
| 972 | + polyplug.Query(id="foo"), "some-event", test_fn |
| 973 | + ) |
| 974 | + assert listener_id in polyplug.LISTENERS |
| 975 | + mock_print.reset_mock() |
| 976 | + polyplug.remove(polyplug.Query(id="foo"), "some-event", test_fn) |
| 977 | + assert mock_print.call_count == 1 |
| 978 | + msg = json.loads(mock_print.call_args.args[0]) |
| 979 | + assert msg["type"] == "removeEvent" |
| 980 | + assert msg["query"]["id"] == "foo" |
| 981 | + assert msg["eventType"] == "some-event" |
| 982 | + assert listener_id not in polyplug.LISTENERS |
| 983 | + |
| 984 | + |
| 985 | +def test_plug_decorator_register(): |
| 986 | + """ |
| 987 | + Ensure the expected register JSON message is emitted when the decorator is |
| 988 | + used on a user's function. |
| 989 | + """ |
| 990 | + with mock.patch("builtins.print") as mock_print: |
| 991 | + |
| 992 | + @polyplug.plug(polyplug.Query(id="foo"), "some-event") |
| 993 | + def test_fn(event): |
| 994 | + return "It works!" |
| 995 | + |
| 996 | + assert mock_print.call_count == 1 |
| 997 | + msg = json.loads(mock_print.call_args.args[0]) |
| 998 | + assert msg["type"] == "registerEvent" |
| 999 | + assert msg["listener"] == polyplug.get_listener_id( |
| 1000 | + polyplug.Query(id="foo"), "some-event", test_fn |
| 1001 | + ) |
| 1002 | + assert msg["query"]["id"] == "foo" |
| 1003 | + assert msg["eventType"] == "some-event" |
| 1004 | + result = polyplug.LISTENERS[msg["listener"]](None) |
| 1005 | + assert result == "It works!" |
| 1006 | + |
| 1007 | + |
| 1008 | +def test_receive_bad_json(): |
| 1009 | + """ |
| 1010 | + If the receive function get a non-JSON message, it complains with an error |
| 1011 | + message of its own. |
| 1012 | + """ |
| 1013 | + with mock.patch("builtins.print") as mock_print: |
| 1014 | + polyplug.receive("not VALID") |
| 1015 | + assert mock_print.call_count == 1 |
| 1016 | + msg = json.loads(mock_print.call_args.args[0]) |
| 1017 | + assert msg["type"] == "error" |
| 1018 | + assert msg["context"]["type"] == "JSONDecodeError" |
| 1019 | + assert ( |
| 1020 | + msg["context"]["msg"] |
| 1021 | + == "Expecting value: line 1 column 1 (char 0)" |
| 1022 | + ) |
| 1023 | + |
| 1024 | + |
| 1025 | +def test_receive_incomplete_message(): |
| 1026 | + """ |
| 1027 | + If the receive function gets valid JSON that is the wrong "shape", it |
| 1028 | + complains with a message of its own. |
| 1029 | + """ |
| 1030 | + with mock.patch("builtins.print") as mock_print: |
| 1031 | + polyplug.receive(json.dumps({"foo": "bar"})) |
| 1032 | + assert mock_print.call_count == 1 |
| 1033 | + msg = json.loads(mock_print.call_args.args[0]) |
| 1034 | + assert msg["type"] == "error" |
| 1035 | + assert msg["context"]["type"] == "ValueError" |
| 1036 | + assert ( |
| 1037 | + msg["context"]["msg"] |
| 1038 | + == 'Incomplete message received: {"foo": "bar"}' |
| 1039 | + ) |
| 1040 | + |
| 1041 | + |
| 1042 | +def test_receive_no_listener(): |
| 1043 | + """ |
| 1044 | + If the receive function gets a valid message but the referenced listener |
| 1045 | + function doesn't exist, it complains with a message of its own. |
| 1046 | + """ |
| 1047 | + with mock.patch("builtins.print") as mock_print: |
| 1048 | + polyplug.receive( |
| 1049 | + json.dumps( |
| 1050 | + { |
| 1051 | + "type": "some-event", |
| 1052 | + "target": DOM_FROM_JSON, |
| 1053 | + "listener": "does_not_exist", |
| 1054 | + } |
| 1055 | + ) |
| 1056 | + ) |
| 1057 | + assert mock_print.call_count == 1 |
| 1058 | + msg = json.loads(mock_print.call_args.args[0]) |
| 1059 | + assert msg["type"] == "error" |
| 1060 | + assert msg["context"]["type"] == "RuntimeError" |
| 1061 | + assert msg["context"]["msg"] == "No such listener: does_not_exist" |
| 1062 | + |
| 1063 | + |
| 1064 | +def test_receive_for_registered_listener(): |
| 1065 | + """ |
| 1066 | + If the receive function gets a valid message for an existing event, |
| 1067 | + the function is called with the expected DomEvent object. |
| 1068 | + """ |
| 1069 | + with mock.patch("builtins.print") as mock_print: |
| 1070 | + # To be called when there's user defined work to be done. |
| 1071 | + mock_work = mock.MagicMock() |
| 1072 | + |
| 1073 | + @polyplug.plug(polyplug.Query(id="foo"), "some-event") |
| 1074 | + def test_fn(event): |
| 1075 | + """ |
| 1076 | + Do some work as if an end user. |
| 1077 | + """ |
| 1078 | + # Expected eventType. |
| 1079 | + if event.event_type != "some-event": |
| 1080 | + raise ValueError("It broke! Wrong event.") |
| 1081 | + # The target represents the expected element. |
| 1082 | + if event.target.tagName != "div": |
| 1083 | + raise ValueError("It broke! Wrong target root.") |
| 1084 | + # It's possible to find one of the expected child nodes. |
| 1085 | + ul = event.target.find(".list") |
| 1086 | + if ul.tagName != "ul": |
| 1087 | + raise ValueError("It broke! Wrong child nodes.") |
| 1088 | + # Signal things worked out. ;-) |
| 1089 | + mock_work("It works!") |
| 1090 | + |
| 1091 | + assert mock_print.call_count == 1 |
| 1092 | + mock_print.reset_mock() |
| 1093 | + |
| 1094 | + listener_id = polyplug.get_listener_id( |
| 1095 | + polyplug.Query(id="foo"), "some-event", test_fn |
| 1096 | + ) |
| 1097 | + |
| 1098 | + polyplug.receive( |
| 1099 | + json.dumps( |
| 1100 | + { |
| 1101 | + "type": "some-event", |
| 1102 | + "target": DOM_FROM_JSON, |
| 1103 | + "listener": listener_id, |
| 1104 | + } |
| 1105 | + ) |
| 1106 | + ) |
| 1107 | + |
| 1108 | + mock_work.assert_called_once_with("It works!") |
0 commit comments