Bug 1328726 - Add wdspec test for emoji in key actions. r=ato, r=jgraham, a=test-only
authorMaja Frydrychowicz <mjzffr@gmail.com>
Tue, 24 Jan 2017 16:18:04 -0500
changeset 376530 d3deaae90a747264db55e7dc024b59dec3858110
parent 376529 48a5f47f26c51dd7d462b63ff27f6f7a107e68bd
child 376531 94bbc18b7fc1b125123b434f056fc91b775a34b5
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersato, jgraham, test-only
bugs1328726
milestone53.0a2
Bug 1328726 - Add wdspec test for emoji in key actions. r=ato, r=jgraham, a=test-only MozReview-Commit-ID: F4xyHqjCc87
testing/web-platform/tests/test_keys_wdspec.html
testing/web-platform/tests/webdriver/actions.py
--- a/testing/web-platform/tests/test_keys_wdspec.html
+++ b/testing/web-platform/tests/test_keys_wdspec.html
@@ -1,55 +1,70 @@
 <!doctype html>
 <meta charset=utf-8>
-
 <head>
     <title>Test Keys</title>
     <script>
         var allEvents = {events: []};
         function displayMessage(message) {
             document.getElementById("events").innerHTML = "<p>" + message + "</p>";
         }
 
         function appendMessage(message) {
             document.getElementById("events").innerHTML += "<p>" + message + "</p>";
         }
 
+        /**
+         * Escape |key| if it's in a surrogate-half character range.
+         *
+         * Example: given "\ud83d" return "U+d83d".
+         *
+         * Otherwise JSON.stringify will convert it to U+FFFD (REPLACEMENT CHARACTER)
+         * when returning a value from executeScript, for example.
+         */
+        function escapeSurrogateHalf(key) {
+          if (typeof key !== "undefined" && key.length === 1) {
+            var charCode = key.charCodeAt(0);
+            var highSurrogate = charCode >= 0xD800 && charCode <= 0xDBFF;
+            var surrogate = highSurrogate || (charCode >= 0xDC00 && charCode <= 0xDFFF);
+            if (surrogate) {
+              key = "U+" + charCode.toString(16);
+            }
+          }
+          return key;
+        }
 
         function recordEvent(event) {
+          var key = escapeSurrogateHalf(event.key);
           allEvents.events.push({
             "code": event.code,
-            "key": event.key,
+            "key": key,
             "which": event.which,
             "location": event.location,
             "ctrl": event.ctrlKey,
             "meta": event.metaKey,
             "shift": event.shiftKey,
             "repeat": event.repeat,
             "type": event.type
           });
-          appendMessage(`${event.type}(code:${event.code}, key:${event.key}, which:${event.which})`);
+          appendMessage(`${event.type}(code:${event.code}, key:${key}, which:${event.which})`);
         }
 
         function resetEvents() {
             allEvents.events.length = 0;
-            displayMessage('');
+            displayMessage("");
         }
-
     </script>
 </head>
 <body>
   <div>
     <h2>KeyReporter</h2>
     <input type="text" id="keys" size="80"
            onkeyup="recordEvent(event)"
            onkeypress="recordEvent(event)"
            onkeydown="recordEvent(event)">
   </div>
   <div id="resultContainer" style="width:300;height:60">
     <h2>Events</h2>
     <div id="events"></div>
   </div>
-
 </body>
 </html>
-
-
--- a/testing/web-platform/tests/webdriver/actions.py
+++ b/testing/web-platform/tests/webdriver/actions.py
@@ -1,62 +1,73 @@
 import pytest
 
 from support.keys import Keys
 
 
 def get_events(session):
     """Return list of key events recorded in the test_keys_page fixture."""
-    return session.execute_script("return allEvents.events;") or []
+    events = session.execute_script("return allEvents.events;") or []
+    # `key` values in `allEvents` may be escaped (see `escapeSurrogateHalf` in
+    # test_keys_wdspec.html), so this converts them back into unicode literals.
+    for e in events:
+        # example: turn "U+d83d" (6 chars) into u"\ud83d" (1 char)
+        if e["key"].startswith(u"U+"):
+            key = e["key"]
+            hex_suffix = key[key.index("+") + 1:]
+            e["key"] = unichr(int(hex_suffix, 16))
+    return events
 
 
-def get_keys(input):
-    """Get printable characters entered into |input|.
+def get_keys(input_el):
+    """Get printable characters entered into `input_el`.
 
-    :param input: HTML input element.
+    :param input_el: HTML input element.
     """
-    rv = input.property("value")
+    rv = input_el.property("value")
     if rv is None:
         return ""
     else:
         return rv
 
 
 def filter_dict(source, d):
-    """Filter |source| dict to only contain same keys as |d| dict.
+    """Filter `source` dict to only contain same keys as `d` dict.
 
     :param source: dictionary to filter.
     :param d: dictionary whose keys determine the filtering.
     """
     return {k: source[k] for k in d.keys()}
 
 
 @pytest.fixture
 def key_reporter(session, test_keys_page, request):
-    """ Represents focused input element on |test_keys_page| """
-    input = session.find.css("#keys", all=False)
-    input.click()
-    return input
+    """Represents focused input element from `test_keys_page` fixture."""
+    input_el = session.find.css("#keys", all=False)
+    input_el.click()
+    return input_el
 
 
 @pytest.fixture
 def test_keys_page(session, server):
     session.url = server.where_is("test_keys_wdspec.html")
 
 
 @pytest.fixture
 def key_chain(session):
     return session.actions.sequence("key", "keyboard_id")
 
+
 @pytest.fixture(autouse=True)
 def release_actions(session, request):
     # release all actions after each test
     # equivalent to a teardown_function, but with access to session fixture
     request.addfinalizer(session.actions.release)
 
+
 def test_no_actions_send_no_events(session, key_reporter, key_chain):
     key_chain.perform()
     assert len(get_keys(key_reporter)) == 0
     assert len(get_events(session)) == 0
 
 
 def test_lone_keyup_sends_no_events(session, key_reporter, key_chain):
     key_chain.key_up("a").perform()
@@ -95,16 +106,17 @@ def test_lone_keyup_sends_no_events(sess
 @pytest.mark.parametrize("value,code", [
     (u"a", "KeyA",),
     ("a", "KeyA",),
     (u"\"", "Quote"),
     (u",", "Comma"),
     (u"\u00E0", ""),
     (u"\u0416", ""),
     (u"@", "Digit2"),
+    (u"\u2603", ""),
     (u"\uF6C2", ""),  # PUA
 ])
 def test_single_printable_key_sends_correct_events(session,
                                                    key_reporter,
                                                    key_chain,
                                                    value,
                                                    code):
     key_chain \
@@ -116,16 +128,32 @@ def test_single_printable_key_sends_corr
         {"code": code, "key": value, "type": "keypress"},
         {"code": code, "key": value, "type": "keyup"},
     ]
     events = [filter_dict(e, expected[0]) for e in get_events(session)]
     assert events == expected
     assert get_keys(key_reporter) == value
 
 
+@pytest.mark.parametrize("value", [
+    (u"\U0001F604"),
+    (u"\U0001F60D"),
+])
+def test_single_emoji_records_correct_key(session, key_reporter, key_chain, value):
+    # Not using key_chain.send_keys() because we always want to treat value as
+    # one character here. `len(value)` varies by platform for non-BMP characters,
+    # so we don't want to iterate over value.
+    key_chain \
+        .key_down(value) \
+        .key_up(value) \
+        .perform()
+    # events sent by major browsers are inconsistent so only check key value
+    assert get_keys(key_reporter) == value
+
+
 @pytest.mark.parametrize("value,code,key", [
     (u"\uE050", "ShiftRight", "Shift"),
     (u"\uE053", "OSRight", "Meta"),
     (Keys.CONTROL, "ControlLeft", "Control"),
 ])
 def test_single_modifier_key_sends_correct_events(session,
                                                   key_reporter,
                                                   key_chain,