From 8a0d814423d315585d56d954df5c75ee8346c061 Mon Sep 17 00:00:00 2001 From: Aaron Golden Date: Mon, 21 Oct 2013 02:25:24 -0700 Subject: [PATCH] Allows various text views to handle "tap-hold" characters (fixes #91) Adds an internal variant of SLKeyboard's typeString method, which can fall back on the JavaScript setValue method if the normal type string mechanism throws an exception. Causes Subliminal UI element classes to use the new method when appropriate. --- Integration Tests/Tests/SLTextFieldTest.m | 7 +++++ .../Tests/SLTextFieldTestViewController.m | 2 ++ .../SLKeyboard+Internal.h | 31 +++++++++++++++++++ .../SLKeyboard+Internal.m | 26 ++++++++++++++++ .../User Interface Elements/SLKeyboard.h | 6 ++++ .../User Interface Elements/SLTextField.m | 6 ++-- .../User Interface Elements/SLTextView.m | 6 ++-- Subliminal.xcodeproj/project.pbxproj | 8 +++++ 8 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 Sources/Classes/UIAutomation/User Interface Elements/SLKeyboard+Internal.h create mode 100644 Sources/Classes/UIAutomation/User Interface Elements/SLKeyboard+Internal.m diff --git a/Integration Tests/Tests/SLTextFieldTest.m b/Integration Tests/Tests/SLTextFieldTest.m index 500b79b..980b6eb 100644 --- a/Integration Tests/Tests/SLTextFieldTest.m +++ b/Integration Tests/Tests/SLTextFieldTest.m @@ -40,6 +40,7 @@ - (void)setUpTestCaseWithSelector:(SEL)testSelector { [super setUpTestCaseWithSelector:testSelector]; if (testSelector == @selector(testSetText) || + testSelector == @selector(testSetTextCanHandleTapHoldCharacters) || testSelector == @selector(testSetTextClearsCurrentText) || testSelector == @selector(testSetTextWhenFieldClearsOnBeginEditing) || testSelector == @selector(testGetText) || @@ -67,6 +68,12 @@ - (void)testSetText { SLAssertTrue([SLAskApp(text) isEqualToString:expectedText], @"Text was not set to expected value."); } +- (void)testSetTextCanHandleTapHoldCharacters { + NSString *const expectedText = @"foo’s a difficult string to type!"; + SLAssertNoThrow([UIAElement(_textField) setText:expectedText], @"Should not have thrown."); + SLAssertTrue([SLAskApp(text) isEqualToString:expectedText], @"Text was not set to expected value."); +} + - (void)testSetTextClearsCurrentText { NSString *const expectedText1 = @"foo"; SLAssertNoThrow([UIAElement(_textField) setText:expectedText1], @"Should not have thrown."); diff --git a/Integration Tests/Tests/SLTextFieldTestViewController.m b/Integration Tests/Tests/SLTextFieldTestViewController.m index 196e471..0ecaca9 100644 --- a/Integration Tests/Tests/SLTextFieldTestViewController.m +++ b/Integration Tests/Tests/SLTextFieldTestViewController.m @@ -39,6 +39,7 @@ - (void)loadViewForTestCase:(SEL)testCase { UIView *view = [[UIView alloc] initWithFrame:CGRectZero]; const CGRect kTextFieldFrame = (CGRect){CGPointZero, CGSizeMake(100.0f, 30.0f)}; if (testCase == @selector(testSetText) || + testCase == @selector(testSetTextCanHandleTapHoldCharacters) || testCase == @selector(testSetTextClearsCurrentText) || testCase == @selector(testSetTextWhenFieldClearsOnBeginEditing) || testCase == @selector(testGetText) || @@ -141,6 +142,7 @@ - (void)viewWillLayoutSubviews { - (NSString *)text { NSString *text; if (self.testCase == @selector(testSetText) || + self.testCase == @selector(testSetTextCanHandleTapHoldCharacters) || self.testCase == @selector(testSetTextClearsCurrentText) || self.testCase == @selector(testSetTextWhenFieldClearsOnBeginEditing) || self.testCase == @selector(testGetText) || diff --git a/Sources/Classes/UIAutomation/User Interface Elements/SLKeyboard+Internal.h b/Sources/Classes/UIAutomation/User Interface Elements/SLKeyboard+Internal.h new file mode 100644 index 0000000..ecd5ee5 --- /dev/null +++ b/Sources/Classes/UIAutomation/User Interface Elements/SLKeyboard+Internal.h @@ -0,0 +1,31 @@ +// +// SLKeyboard+Internal.h +// Subliminal +// +// Created by Aaron Golden on 10/21/13. +// Copyright (c) 2013 Inkling. All rights reserved. +// + +#import "SLKeyboard.h" + +@class SLUIAElement; + +@interface SLKeyboard (Internal) + +/** + Uses -[SLKeyboard typeString:] to tap the keys of the input string on the + receiver. Unlike -[SLKeyboard typeString:], this method will not throw an + exception if the input string contains characters that can be accessed on + the receiver only through a tap-hold gesture. Instead, this method will + send the setValue JavaScript message to the input element as a "fallback" + procedure. + + @param string The string to be typed on the keyboard or set as the value for + element. + @param element The user interface element on which the setValue JavaScript + method will be called if the internal call to -[SLKeyboard typeString:] + throws an exception. + */ + - (void)typeString:(NSString *)string withSetValueFallbackUsingElement:(SLUIAElement *)element; + +@end diff --git a/Sources/Classes/UIAutomation/User Interface Elements/SLKeyboard+Internal.m b/Sources/Classes/UIAutomation/User Interface Elements/SLKeyboard+Internal.m new file mode 100644 index 0000000..203f2b0 --- /dev/null +++ b/Sources/Classes/UIAutomation/User Interface Elements/SLKeyboard+Internal.m @@ -0,0 +1,26 @@ +// +// SLKeyboard+Internal.m +// Subliminal +// +// Created by Aaron Golden on 10/21/13. +// Copyright (c) 2013 Inkling. All rights reserved. +// + +#import "SLKeyboard+Internal.h" + +#import "SLUIAElement+Subclassing.h" +#import "SLLogger.h" +#import "SLStringUtilities.h" + +@implementation SLKeyboard (Internal) + +- (void)typeString:(NSString *)string withSetValueFallbackUsingElement:(SLUIAElement *)element { + @try { + [self typeString:string]; + } @catch (id exception) { + [[SLLogger sharedLogger] logWarning:[NSString stringWithFormat:@"-[SLKeyboard typeString:] will fall back on UIAElement.setValue due to an exception in UIAKeyboard.typeString: %@", exception]]; + [element waitUntilTappable:YES thenSendMessage:@"setValue('%@')", [string slStringByEscapingForJavaScriptLiteral]]; + } +} + +@end diff --git a/Sources/Classes/UIAutomation/User Interface Elements/SLKeyboard.h b/Sources/Classes/UIAutomation/User Interface Elements/SLKeyboard.h index 4f5cb83..c8a1e87 100644 --- a/Sources/Classes/UIAutomation/User Interface Elements/SLKeyboard.h +++ b/Sources/Classes/UIAutomation/User Interface Elements/SLKeyboard.h @@ -47,6 +47,12 @@ as necessary to make the corresponding keys visible. @param string The string to be typed on the keyboard. + + @bug This method throws an exception if string contains any characters + that can be accessed only through a tap-hold gesture, for example + “smart-quotes.” Note that SLTextField, SLTextView, and related classes + work around this bug internally when their text contents are set with + the -setText: method. */ - (void)typeString:(NSString *)string; diff --git a/Sources/Classes/UIAutomation/User Interface Elements/SLTextField.m b/Sources/Classes/UIAutomation/User Interface Elements/SLTextField.m index 9d2173d..ede2b7a 100644 --- a/Sources/Classes/UIAutomation/User Interface Elements/SLTextField.m +++ b/Sources/Classes/UIAutomation/User Interface Elements/SLTextField.m @@ -22,7 +22,7 @@ #import "SLTextField.h" #import "SLUIAElement+Subclassing.h" -#import "SLKeyboard.h" +#import "SLKeyboard+Internal.h" @implementation SLTextField @@ -40,7 +40,7 @@ - (void)setText:(NSString *)text { // Clear any current text before typing the new text. [self waitUntilTappable:YES thenSendMessage:@"setValue('')"]; - [[SLKeyboard keyboard] typeString:text]; + [[SLKeyboard keyboard] typeString:text withSetValueFallbackUsingElement:self]; } - (BOOL)matchesObject:(NSObject *)object { @@ -109,7 +109,7 @@ - (void)setText:(NSString *)text { [[SLKeyboardKey elementWithAccessibilityLabel:@"Delete"] tap]; } - [[SLKeyboard keyboard] typeString:text]; + [[SLKeyboard keyboard] typeString:text withSetValueFallbackUsingElement:self]; } @end diff --git a/Sources/Classes/UIAutomation/User Interface Elements/SLTextView.m b/Sources/Classes/UIAutomation/User Interface Elements/SLTextView.m index c884043..5027060 100644 --- a/Sources/Classes/UIAutomation/User Interface Elements/SLTextView.m +++ b/Sources/Classes/UIAutomation/User Interface Elements/SLTextView.m @@ -8,7 +8,7 @@ #import "SLTextView.h" #import "SLUIAElement+Subclassing.h" -#import "SLKeyboard.h" +#import "SLKeyboard+Internal.h" @implementation SLTextView @@ -26,7 +26,7 @@ - (void)setText:(NSString *)text { // Clear any current text before typing the new text. [self waitUntilTappable:YES thenSendMessage:@"setValue('')"]; - [[SLKeyboard keyboard] typeString:text]; + [[SLKeyboard keyboard] typeString:text withSetValueFallbackUsingElement:self]; } - (BOOL)matchesObject:(NSObject *)object { @@ -70,7 +70,7 @@ - (void)setText:(NSString *)text { [[SLKeyboardKey elementWithAccessibilityLabel:@"Delete"] tap]; } - [[SLKeyboard keyboard] typeString:text]; + [[SLKeyboard keyboard] typeString:text withSetValueFallbackUsingElement:self]; } @end diff --git a/Subliminal.xcodeproj/project.pbxproj b/Subliminal.xcodeproj/project.pbxproj index a566766..c8ab2d6 100644 --- a/Subliminal.xcodeproj/project.pbxproj +++ b/Subliminal.xcodeproj/project.pbxproj @@ -25,6 +25,8 @@ 0640035B178FDE7800479173 /* SLElementTouchAndHoldTestViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 06400359178FDE7800479173 /* SLElementTouchAndHoldTestViewController.xib */; }; 064B6F55173B13E9004AB1BF /* SLElementVisibilityTestCoveredByClearRegion.xib in Resources */ = {isa = PBXBuildFile; fileRef = 064B6F54173B13E9004AB1BF /* SLElementVisibilityTestCoveredByClearRegion.xib */; }; 064B6FC7173DCE9A004AB1BF /* SLElementVisibilityWithSubviews.xib in Resources */ = {isa = PBXBuildFile; fileRef = 064B6FC6173DCE9A004AB1BF /* SLElementVisibilityWithSubviews.xib */; }; + 0657C0E2181528B8007E72DF /* SLKeyboard+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 0657C0E0181528B6007E72DF /* SLKeyboard+Internal.h */; }; + 0657C0E3181528B8007E72DF /* SLKeyboard+Internal.m in Sources */ = {isa = PBXBuildFile; fileRef = 0657C0E1181528B7007E72DF /* SLKeyboard+Internal.m */; }; 068D3E4D16DE9008004E7E28 /* SLTableViewChildElementMatchingTestViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 068D3E4C16DE9008004E7E28 /* SLTableViewChildElementMatchingTestViewController.xib */; }; 06953E3F178FDA7100B3D1B7 /* SLElementTouchAndHoldTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 06953E3E178FDA7100B3D1B7 /* SLElementTouchAndHoldTest.m */; }; 0696BA5816E013D600DD70CF /* SLElementDraggingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0696BA5516E013D600DD70CF /* SLElementDraggingTest.m */; }; @@ -245,6 +247,8 @@ 06400359178FDE7800479173 /* SLElementTouchAndHoldTestViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SLElementTouchAndHoldTestViewController.xib; sourceTree = ""; }; 064B6F54173B13E9004AB1BF /* SLElementVisibilityTestCoveredByClearRegion.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SLElementVisibilityTestCoveredByClearRegion.xib; sourceTree = ""; }; 064B6FC6173DCE9A004AB1BF /* SLElementVisibilityWithSubviews.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SLElementVisibilityWithSubviews.xib; sourceTree = ""; }; + 0657C0E0181528B6007E72DF /* SLKeyboard+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SLKeyboard+Internal.h"; sourceTree = ""; }; + 0657C0E1181528B7007E72DF /* SLKeyboard+Internal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SLKeyboard+Internal.m"; sourceTree = ""; }; 068D3E4C16DE9008004E7E28 /* SLTableViewChildElementMatchingTestViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SLTableViewChildElementMatchingTestViewController.xib; sourceTree = ""; }; 06953E3E178FDA7100B3D1B7 /* SLElementTouchAndHoldTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLElementTouchAndHoldTest.m; sourceTree = ""; }; 0696BA5516E013D600DD70CF /* SLElementDraggingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLElementDraggingTest.m; sourceTree = ""; }; @@ -716,6 +720,8 @@ 2CE9AA4B17E3A747007EF0B5 /* SLSwitch.m */, F0C07A511704011300C93F93 /* SLKeyboard.h */, F0C07A521704011300C93F93 /* SLKeyboard.m */, + 0657C0E0181528B6007E72DF /* SLKeyboard+Internal.h */, + 0657C0E1181528B7007E72DF /* SLKeyboard+Internal.m */, F00800CC174C1C64001927AC /* SLPopover.h */, F00800CD174C1C64001927AC /* SLPopover.m */, F0C07A491704002100C93F93 /* SLTextField.h */, @@ -951,6 +957,7 @@ F04346A7175AD10200D91F7F /* NSObject+SLVisibility.h in Headers */, F0A3F63417A715AE007529C3 /* SLTextView.h in Headers */, DB501DC917B9669A001658CB /* SLStatusBar.h in Headers */, + 0657C0E2181528B8007E72DF /* SLKeyboard+Internal.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1192,6 +1199,7 @@ F04346A8175AD10200D91F7F /* NSObject+SLVisibility.m in Sources */, F0A3F63517A715AE007529C3 /* SLTextView.m in Sources */, DB501DCA17B9669A001658CB /* SLStatusBar.m in Sources */, + 0657C0E3181528B8007E72DF /* SLKeyboard+Internal.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };