[orca] Add support for aria-invalid



commit 1b2e86f0e364118ddd0ceed5f354209333996744
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Mon Oct 24 15:22:37 2016 -0400

    Add support for aria-invalid

 src/orca/formatting.py                      |   42 ++++---
 src/orca/generator.py                       |   19 +++
 src/orca/object_properties.py               |   42 +++++++-
 src/orca/script_utilities.py                |    3 +
 src/orca/scripts/web/script_utilities.py    |   28 +++++
 src/orca/speech_generator.py                |   10 ++
 test/html/aria-invalid.html                 |   23 ++++
 test/keystrokes/firefox/aria_invalid.params |    1 +
 test/keystrokes/firefox/aria_invalid.py     |  158 +++++++++++++++++++++++++++
 9 files changed, 306 insertions(+), 20 deletions(-)
---
diff --git a/src/orca/formatting.py b/src/orca/formatting.py
index 30e6770..a61fa2a 100644
--- a/src/orca/formatting.py
+++ b/src/orca/formatting.py
@@ -38,6 +38,7 @@ TUTORIAL = '(tutorial and (pause + tutorial) or [])'
 MNEMONIC = '(mnemonic and (pause + mnemonic + lineBreak) or [])'
 
 BRAILLE_TEXT = '[Text(obj, asString(labelOrName or placeholderText), asString(eol), startOffset, endOffset)]\
+                + (invalid and [Region(" " + asString(invalid))])\
                 + (required and [Region(" " + asString(required))])\
                 + (readOnly and [Region(" " + asString(readOnly))])'
 
@@ -52,6 +53,7 @@ formatting = {
 
     'strings' : {
         'speech': {
+            'invalid': object_properties.INVALID_INDICATORS_SPEECH,
             'required': object_properties.STATE_REQUIRED_SPEECH,
             'readonly': object_properties.STATE_READ_ONLY_SPEECH,
             'insensitive': object_properties.STATE_INSENSITIVE_SPEECH,
@@ -72,6 +74,7 @@ formatting = {
             'required': object_properties.STATE_REQUIRED_BRAILLE,
             'readonly': object_properties.STATE_READ_ONLY_BRAILLE,
             'insensitive': object_properties.STATE_INSENSITIVE_BRAILLE,
+            'invalid': object_properties.INVALID_INDICATORS_BRAILLE,
             'checkbox': object_properties.CHECK_BOX_INDICATORS_BRAILLE,
             'radiobutton': object_properties.RADIO_BUTTON_INDICATORS_BRAILLE,
             'togglebutton': object_properties.TOGGLE_BUTTON_INDICATORS_BRAILLE,
@@ -80,6 +83,7 @@ formatting = {
             'nestinglevel': object_properties.NESTING_LEVEL_BRAILLE,
         },
         'sound': {
+            'invalid': object_properties.INVALID_INDICATORS_SOUND,
             'required': object_properties.STATE_REQUIRED_SOUND,
             'readonly': object_properties.STATE_READ_ONLY_SOUND,
             'insensitive': object_properties.STATE_INSENSITIVE_SOUND,
@@ -136,8 +140,8 @@ formatting = {
             },
         pyatspi.ROLE_CHECK_BOX: {
             'focused': 'checkedState',
-            'unfocused': 'labelOrName + roleName + checkedState + required + availability + ' + MNEMONIC + ' 
+ accelerator',
-            'basicWhereAmI': 'namedContainingPanel + labelOrName + roleName + checkedState + ' + MNEMONIC + 
' + accelerator + required'
+            'unfocused': 'labelOrName + roleName + checkedState + required + pause + invalid + availability 
+ ' + MNEMONIC + ' + accelerator',
+            'basicWhereAmI': 'namedContainingPanel + labelOrName + roleName + checkedState + ' + MNEMONIC + 
' + accelerator + required + pause + invalid'
             },
         pyatspi.ROLE_CHECK_MENU_ITEM: {
             'focused': 'checkedState',
@@ -177,9 +181,9 @@ formatting = {
             },
         pyatspi.ROLE_ENTRY: {
             'focused': 'labelOrName + readOnly + textRole + (currentLineText or placeholderText) + 
allTextSelection',
-            'unfocused': 'labelOrName + readOnly + textRole + (currentLineText or placeholderText) + 
allTextSelection + ' + MNEMONIC,
-            'basicWhereAmI': 'labelOrName + readOnly + textRole + (textContent or placeholderText) + 
anyTextSelection + ' + MNEMONIC,
-            'detailedWhereAmI': 'labelOrName + readOnly + textRole + (textContentWithAttributes or 
placeholderText) + anyTextSelection + ' + MNEMONIC,
+            'unfocused': 'labelOrName + readOnly + textRole + (currentLineText or placeholderText) + 
allTextSelection + required + pause + invalid + ' + MNEMONIC,
+            'basicWhereAmI': 'labelOrName + readOnly + textRole + (textContent or placeholderText) + 
anyTextSelection + required + pause + invalid + ' + MNEMONIC,
+            'detailedWhereAmI': 'labelOrName + readOnly + textRole + (textContentWithAttributes or 
placeholderText) + anyTextSelection + required + pause + invalid + ' + MNEMONIC,
             },
         pyatspi.ROLE_FOOTER: {
             'unfocused': '(displayedText or name) + roleName',
@@ -361,13 +365,13 @@ formatting = {
             },
         pyatspi.ROLE_SLIDER: {
             'focused': 'value',
-            'unfocused': 'labelOrName + roleName + value + required + availability + ' + MNEMONIC,
-            'basicWhereAmI': 'labelOrName + roleName + value + percentage + ' + MNEMONIC + ' + accelerator + 
required'
+            'unfocused': 'labelOrName + roleName + value + required + pause + invalid + availability + ' + 
MNEMONIC,
+            'basicWhereAmI': 'labelOrName + roleName + value + percentage + ' + MNEMONIC + ' + accelerator + 
required + pause + invalid'
             },
         pyatspi.ROLE_SPIN_BUTTON: {
             'focused': 'name',
-            'unfocused': 'labelAndName + allTextSelection + roleName + availability + ' + MNEMONIC + ' + 
required',
-            'basicWhereAmI': 'label + roleName + name + allTextSelection + ' + MNEMONIC + ' + accelerator + 
required'
+            'unfocused': 'labelAndName + allTextSelection + roleName + required + pause + invalid + 
availability + ' + MNEMONIC,
+            'basicWhereAmI': 'label + roleName + name + allTextSelection + ' + MNEMONIC + ' + accelerator + 
required + pause + invalid'
             },
         pyatspi.ROLE_SPLIT_PANE: {
             'focused': 'value',
@@ -404,7 +408,7 @@ formatting = {
                               + cellCheckedState\
                               + (realActiveDescendantDisplayedText or imageDescription + image)\
                               + (expandableState and (expandableState + numberOfChildren))\
-                              + required)'
+                              + required + pause + invalid)'
             },
         pyatspi.ROLE_TABLE_ROW: {
             'focused': 'expandableState',
@@ -484,9 +488,9 @@ formatting = {
             },
         'default': {
             'focused':   '[Component(obj,\
-                                     asString(label + displayedText + value + roleName + required))]',
+                                     asString(label + displayedText + value + roleName + required + 
invalid))]',
             'unfocused': '[Component(obj,\
-                                     asString(label + displayedText + value + roleName + required))]',
+                                     asString(label + displayedText + value + roleName + required + 
invalid))]',
             },
         pyatspi.ROLE_ALERT: {
             'unfocused': '((substring and ' + BRAILLE_TEXT + ')\
@@ -668,7 +672,7 @@ formatting = {
         #'REAL_ROLE_SCROLL_PANE': 'default'
         pyatspi.ROLE_SLIDER: {
             'unfocused': '[Component(obj,\
-                                     asString(labelOrName + value + roleName + required))]'
+                                     asString(labelOrName + value + roleName + required + invalid))]'
             },
         pyatspi.ROLE_SPIN_BUTTON: {
             'unfocused': '[Text(obj, asString(label), asString(eol))]\
@@ -752,7 +756,7 @@ formatting = {
         },
         pyatspi.ROLE_CHECK_BOX: {
             'focused': 'checkedState',
-            'unfocused': 'roleName + checkedState + required + availability',
+            'unfocused': 'roleName + checkedState + required + invalid + availability',
         },
         pyatspi.ROLE_CHECK_MENU_ITEM: {
             'focused': 'checkedState',
@@ -764,10 +768,10 @@ formatting = {
         },
         pyatspi.ROLE_DIAL: {
             'focused': 'percentage',
-            'unfocused': 'roleName + percentage + required + availability',
+            'unfocused': 'roleName + percentage + required + invalid + availability',
         },
         pyatspi.ROLE_ENTRY: {
-            'unfocused': 'roleName + readOnly + required + availability',
+            'unfocused': 'roleName + readOnly + required + invalid + availability',
         },
         pyatspi.ROLE_HEADING: {
             'focused': 'expandableState',
@@ -819,11 +823,11 @@ formatting = {
         },
         pyatspi.ROLE_SLIDER: {
             'focused': 'percentage',
-            'unfocused': 'roleName + percentage + required + availability',
+            'unfocused': 'roleName + percentage + required + invalid + availability',
         },
         pyatspi.ROLE_SPIN_BUTTON: {
             'focused': 'percentage',
-            'unfocused': 'roleName + availability + percentage + required',
+            'unfocused': 'roleName + availability + percentage + required + invalid',
         },
         pyatspi.ROLE_SPLIT_PANE: {
             'focused': 'percentage',
@@ -837,7 +841,7 @@ formatting = {
             'focused': 'expandableState',
         },
         pyatspi.ROLE_TEXT: {
-            'unfocused': 'roleName + readOnly + required + availability',
+            'unfocused': 'roleName + readOnly + required + invalid + availability',
         },
         pyatspi.ROLE_TOGGLE_BUTTON: {
             'focused': 'expandableState or toggleState',
diff --git a/src/orca/generator.py b/src/orca/generator.py
index 6fb2f7a..2797436 100644
--- a/src/orca/generator.py
+++ b/src/orca/generator.py
@@ -462,6 +462,25 @@ class Generator:
             result.append(self._script.formatting.getString(**args))
         return result
 
+    def _generateInvalid(self, obj, **args):
+        error = self._script.utilities.getError(obj)
+        if not error:
+            return []
+
+        result = []
+        if not args.get('mode', None):
+            args['mode'] = self._mode
+        args['stringType'] = 'invalid'
+        indicators = self._script.formatting.getString(**args)
+
+        if error == 'spelling':
+            result.append(indicators[1])
+        elif error == 'grammar':
+            result.append(indicators[2])
+        else:
+            result.append(indicators[0])
+        return result
+
     def _generateRequired(self, obj, **args):
         """Returns an array of strings for use by speech and braille that
         represent the required state of the object, but only if it is
diff --git a/src/orca/object_properties.py b/src/orca/object_properties.py
index 3087d93..a9a11ff 100644
--- a/src/orca/object_properties.py
+++ b/src/orca/object_properties.py
@@ -298,7 +298,39 @@ STATE_REQUIRED_BRAILLE = _("required")
 # one item can be selected at a time.
 STATE_MULTISELECT_SPEECH = _("multi-select")
 
-
+# Translators: STATE_INVALID_ENTRY indicates that the associated object, such
+# as a form field, has an error. The following string is spoken when all we
+# know is that an error has occurred, but not the type of error.
+STATE_INVALID_SPEECH = C_("error", "invalid entry")
+
+# Translators: STATE_INVALID_ENTRY indicates that the associated object, such
+# as a form field, has an error. The following string is displayed in braille
+# when all we know is that an error has occurred, but not the type of error.
+# We prefer a smaller string than in speech because braille displays have a
+# limited size.
+STATE_INVALID_BRAILLE = C_("error", "invalid")
+
+# Translators: STATE_INVALID_ENTRY indicates that the associated object, such
+# as a form field, has an error. The following string is spoken when the error
+# is related to spelling.
+STATE_INVALID_SPELLING_SPEECH = C_("error", "invalid spelling")
+
+# Translators: STATE_INVALID_ENTRY indicates that the associated object, such
+# as a form field, has an error. The following string is displayed in braille
+# when the error is related to spelling. We prefer a smaller string than in
+# speech because braille displays have a limited size.
+STATE_INVALID_SPELLING_BRAILLE = C_("error", "spelling")
+
+# Translators: STATE_INVALID_ENTRY indicates that the associated object, such
+# as a form field, has an error. The following string is spoken when the error
+# is related to grammar.
+STATE_INVALID_GRAMMAR_SPEECH = C_("error", "invalid grammar")
+
+# Translators: STATE_INVALID_ENTRY indicates that the associated object, such
+# as a form field, has an error. The following string is displayed in braille
+# when the error is related to grammar. We prefer a smaller string than in
+# speech because braille displays have a limited size.
+STATE_INVALID_GRAMMAR_BRAILLE = C_("error", "grammar")
 
 # TODO: Look at why we're doing this as lists.
 
@@ -306,6 +338,10 @@ CHECK_BOX_INDICATORS_SPEECH = \
     [STATE_NOT_CHECKED, STATE_CHECKED, STATE_PARTIALLY_CHECKED]
 EXPANSION_INDICATORS_SPEECH = \
     [STATE_COLLAPSED, STATE_EXPANDED]
+INVALID_INDICATORS_SPEECH = \
+    [STATE_INVALID_SPEECH,
+     STATE_INVALID_SPELLING_SPEECH,
+     STATE_INVALID_GRAMMAR_SPEECH]
 RADIO_BUTTON_INDICATORS_SPEECH = \
     [STATE_UNSELECTED_RADIO_BUTTON, STATE_SELECTED_RADIO_BUTTON]
 TOGGLE_BUTTON_INDICATORS_SPEECH = \
@@ -313,6 +349,9 @@ TOGGLE_BUTTON_INDICATORS_SPEECH = \
 
 CHECK_BOX_INDICATORS_BRAILLE     = ["< >", "<x>", "<->"]
 EXPANSION_INDICATORS_BRAILLE     = [STATE_COLLAPSED, STATE_EXPANDED]
+INVALID_INDICATORS_BRAILLE       = [STATE_INVALID_BRAILLE,
+                                    STATE_INVALID_SPELLING_BRAILLE,
+                                    STATE_INVALID_GRAMMAR_BRAILLE]
 RADIO_BUTTON_INDICATORS_BRAILLE  = ["& y", "&=y"]
 TOGGLE_BUTTON_INDICATORS_BRAILLE = ["& y", "&=y"]
 
@@ -321,6 +360,7 @@ EOL_INDICATOR_BRAILLE = " $l"
 
 CHECK_BOX_INDICATORS_SOUND = ["not_checked", "checked", "partially_checked"]
 EXPANSION_INDICATORS_SOUND = ["collapsed", "expanded"]
+INVALID_INDICATORS_SOUND = ["invalid", "invalid_spelling", "invalid_grammar"]
 RADIO_BUTTON_INDICATORS_SOUND = ["unselected", "selected"]
 TOGGLE_BUTTON_INDICATORS_SOUND = ["not_pressed", "pressed"]
 STATE_CLICKABLE_SOUND = "clickable"
diff --git a/src/orca/script_utilities.py b/src/orca/script_utilities.py
index 529df0b..14b4b07 100644
--- a/src/orca/script_utilities.py
+++ b/src/orca/script_utilities.py
@@ -2369,6 +2369,9 @@ class Utilities:
 
         return False
 
+    def getError(self, obj):
+        return obj.getState().contains(pyatspi.STATE_INVALID_ENTRY)
+
     def getCharacterAtOffset(self, obj, offset=None):
         text = self.queryNonEmptyText(obj)
         if text:
diff --git a/src/orca/scripts/web/script_utilities.py b/src/orca/scripts/web/script_utilities.py
index b7f9836..bbbe82c 100644
--- a/src/orca/scripts/web/script_utilities.py
+++ b/src/orca/scripts/web/script_utilities.py
@@ -2835,6 +2835,34 @@ class Utilities(script_utilities.Utilities):
 
         return child
 
+    def getError(self, obj):
+        if not (obj and self.inDocumentContent(obj)):
+            return super().getError(obj)
+
+        try:
+            state = obj.getState()
+        except:
+            msg = "ERROR: Exception getting state for %s" % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+            return False
+
+        if not state.contains(pyatspi.STATE_INVALID_ENTRY):
+            return False
+
+        try:
+            self._currentAttrs.pop(hash(obj))
+        except:
+            pass
+
+        attrs, start, end = self.textAttributes(obj, 0, True)
+        error = attrs.get("invalid")
+        if error == "false":
+            return False
+        if error not in ["spelling", "grammar"]:
+            return True
+
+        return error
+
     def hasNoSize(self, obj):
         if not (obj and self.inDocumentContent(obj)):
             return super().hasNoSize(obj)
diff --git a/src/orca/speech_generator.py b/src/orca/speech_generator.py
index a019261..39e76f4 100644
--- a/src/orca/speech_generator.py
+++ b/src/orca/speech_generator.py
@@ -262,6 +262,16 @@ class SpeechGenerator(generator.Generator):
             result.extend(acss)
         return result
 
+    def _generateInvalid(self, obj, **args):
+        if _settingsManager.getSetting('onlySpeakDisplayedText'):
+            return []
+
+        acss = self.voice(SYSTEM)
+        result = generator.Generator._generateInvalid(self, obj, **args)
+        if result:
+            result.extend(acss)
+        return result
+
     def _generateRequired(self, obj, **args):
         if _settingsManager.getSetting('onlySpeakDisplayedText'):
             return []
diff --git a/test/html/aria-invalid.html b/test/html/aria-invalid.html
new file mode 100644
index 0000000..3f263d9
--- /dev/null
+++ b/test/html/aria-invalid.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<title>Invalid</title>
+</head>
+<body>
+<p>Examples:</p>
+<div>
+  <input aria-label="text 1" type="text" value="Hello wurld" aria-invalid="spelling">
+  <input aria-label="text 2" type="text" value="World hello" aria-invalid="grammar">
+  <input aria-label="text 3" type="text" value="1234" aria-invalid="true">
+  <input aria-label="text 4" type="text" value="Good" aria-invalid="false">
+  <input aria-label="accept terms of service" type="checkbox" value="tos-accepted" aria-invalid="true">
+</div>
+<div>
+  <input aria-label="time 1" id="time1" type="text" aria-errormessage="msgID" value="" aria-invalid="false">
+  <span id="msgID" aria-live="off" style="visibility:hidden"> Invalid time:  the time must be between 9:00 
AM and 5:00 PM" </span>
+</div>
+<div>
+  <input aria-label="time 2" id="time2" type="text" aria-errormessage="msgID" aria-invalid="true" 
value="11:30 PM" />
+  <span id="msgID" style="visibility:visible"> Invalid time: The time must be between 9:00 AM and 5:00 
PM"</span>
+</div>
+</body>
+</html>
diff --git a/test/keystrokes/firefox/aria_invalid.params b/test/keystrokes/firefox/aria_invalid.params
new file mode 100644
index 0000000..ea699f2
--- /dev/null
+++ b/test/keystrokes/firefox/aria_invalid.params
@@ -0,0 +1 @@
+PARAMS=$TEST_DIR/../../html/aria-invalid.html
diff --git a/test/keystrokes/firefox/aria_invalid.py b/test/keystrokes/firefox/aria_invalid.py
new file mode 100644
index 0000000..f94b68c
--- /dev/null
+++ b/test/keystrokes/firefox/aria_invalid.py
@@ -0,0 +1,158 @@
+#!/usr/bin/python
+
+from macaroon.playback import *
+import utils
+
+sequence = MacroSequence()
+
+#sequence.append(WaitForDocLoad())
+sequence.append(PauseAction(5000))
+sequence.append(KeyComboAction("<Control>Home"))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "1. Tab",
+    ["BRAILLE LINE:  'text 1 Hello wurld $l spelling'",
+     "     VISIBLE:  'text 1 Hello wurld $l spelling', cursor=19",
+     "BRAILLE LINE:  'Focus mode'",
+     "     VISIBLE:  'Focus mode', cursor=0",
+     "BRAILLE LINE:  'text 1 Hello wurld $l spelling'",
+     "     VISIBLE:  'text 1 Hello wurld $l spelling', cursor=19",
+     "SPEECH OUTPUT: 'text 1 entry Hello wurld selected.'",
+     "SPEECH OUTPUT: 'invalid spelling'",
+     "SPEECH OUTPUT: 'Focus mode' voice=system"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("KP_Enter"))
+sequence.append(utils.AssertPresentationAction(
+    "2. WhereAmI",
+    ["BRAILLE LINE:  'text 1 Hello wurld $l spelling'",
+     "     VISIBLE:  'text 1 Hello wurld $l spelling', cursor=19",
+     "SPEECH OUTPUT: 'text 1 entry Hello wurld selected.'",
+     "SPEECH OUTPUT: 'invalid spelling'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "3. Tab",
+    ["BRAILLE LINE:  'text 2 World hello $l grammar'",
+     "     VISIBLE:  'text 2 World hello $l grammar', cursor=19",
+     "BRAILLE LINE:  'text 2 World hello $l grammar'",
+     "     VISIBLE:  'text 2 World hello $l grammar', cursor=19",
+     "SPEECH OUTPUT: 'text 2 entry World hello selected.'",
+     "SPEECH OUTPUT: 'invalid grammar'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("KP_Enter"))
+sequence.append(utils.AssertPresentationAction(
+    "4. WhereAmI",
+    ["BRAILLE LINE:  'text 2 World hello $l grammar'",
+     "     VISIBLE:  'text 2 World hello $l grammar', cursor=19",
+     "SPEECH OUTPUT: 'text 2 entry World hello selected.'",
+     "SPEECH OUTPUT: 'invalid grammar'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "5. Tab",
+    ["BRAILLE LINE:  'text 3 1234 $l invalid'",
+     "     VISIBLE:  'text 3 1234 $l invalid', cursor=12",
+     "BRAILLE LINE:  'text 3 1234 $l invalid'",
+     "     VISIBLE:  'text 3 1234 $l invalid', cursor=12",
+     "SPEECH OUTPUT: 'text 3 entry 1234 selected.'",
+     "SPEECH OUTPUT: 'invalid entry'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("KP_Enter"))
+sequence.append(utils.AssertPresentationAction(
+    "6. WhereAmI",
+    ["BRAILLE LINE:  'text 3 1234 $l invalid'",
+     "     VISIBLE:  'text 3 1234 $l invalid', cursor=12",
+     "SPEECH OUTPUT: 'text 3 entry 1234 selected.'",
+     "SPEECH OUTPUT: 'invalid entry'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "7. Tab",
+    ["BRAILLE LINE:  'text 4 Good $l'",
+     "     VISIBLE:  'text 4 Good $l', cursor=12",
+     "BRAILLE LINE:  'text 4 Good $l'",
+     "     VISIBLE:  'text 4 Good $l', cursor=12",
+     "SPEECH OUTPUT: 'text 4 entry Good selected.'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("KP_Enter"))
+sequence.append(utils.AssertPresentationAction(
+    "8. WhereAmI",
+    ["BRAILLE LINE:  'text 4 Good $l'",
+     "     VISIBLE:  'text 4 Good $l', cursor=12",
+     "SPEECH OUTPUT: 'text 4 entry Good selected.'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "9. Tab",
+    ["BRAILLE LINE:  '< > accept terms of service check box'",
+     "     VISIBLE:  '< > accept terms of service chec', cursor=1",
+     "BRAILLE LINE:  'Browse mode'",
+     "     VISIBLE:  'Browse mode', cursor=0",
+     "SPEECH OUTPUT: 'accept terms of service check box not checked.'",
+     "SPEECH OUTPUT: 'invalid entry'",
+     "SPEECH OUTPUT: 'Browse mode' voice=system"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("KP_Enter"))
+sequence.append(utils.AssertPresentationAction(
+    "10. WhereAmI",
+    ["BRAILLE LINE:  '< > accept terms of service check box'",
+     "     VISIBLE:  '< > accept terms of service chec', cursor=1",
+     "BRAILLE LINE:  '< > accept terms of service check box'",
+     "     VISIBLE:  '< > accept terms of service chec', cursor=1",
+     "SPEECH OUTPUT: 'accept terms of service check box not checked.'",
+     "SPEECH OUTPUT: 'invalid entry'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "11. Tab",
+    ["BRAILLE LINE:  'time 1  $l'",
+     "     VISIBLE:  'time 1  $l', cursor=8",
+     "BRAILLE LINE:  'Focus mode'",
+     "     VISIBLE:  'Focus mode', cursor=0",
+     "SPEECH OUTPUT: 'time 1 entry.'",
+     "SPEECH OUTPUT: 'Focus mode' voice=system"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("KP_Enter"))
+sequence.append(utils.AssertPresentationAction(
+    "12. WhereAmI",
+    ["BRAILLE LINE:  'time 1  $l'",
+     "     VISIBLE:  'time 1  $l', cursor=8",
+     "BRAILLE LINE:  'time 1  $l'",
+     "     VISIBLE:  'time 1  $l', cursor=8",
+     "SPEECH OUTPUT: 'time 1 entry.'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "13. Tab",
+    ["BRAILLE LINE:  'time 2 11:30 PM $l invalid'",
+     "     VISIBLE:  'time 2 11:30 PM $l invalid', cursor=16",
+     "BRAILLE LINE:  'time 2 11:30 PM $l invalid'",
+     "     VISIBLE:  'time 2 11:30 PM $l invalid', cursor=16",
+     "SPEECH OUTPUT: 'time 2 entry 11:30 PM selected.'",
+     "SPEECH OUTPUT: 'invalid entry'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("KP_Enter"))
+sequence.append(utils.AssertPresentationAction(
+    "14. WhereAmI",
+    ["BRAILLE LINE:  'time 2 11:30 PM $l invalid'",
+     "     VISIBLE:  'time 2 11:30 PM $l invalid', cursor=16",
+     "SPEECH OUTPUT: 'time 2 entry 11:30 PM selected.'",
+     "SPEECH OUTPUT: 'invalid entry'"]))
+
+sequence.append(utils.AssertionSummaryAction())
+sequence.start()


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]