Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions Integration Tests/Tests/SLEditingMenuTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//
// SLEditingMenuTest.m
// Subliminal
//
// For details and documentation:
// http://github.com/inkling/Subliminal
//
// Copyright 2013-2014 Inkling Systems, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#import "SLIntegrationTest.h"

@interface SLEditingMenuTest : SLIntegrationTest

@end

@implementation SLEditingMenuTest {
SLEditingMenu *_menu;
SLElement *_testText, *_otherText;
NSString *_menuItemTitle;
}

+ (NSString *)testCaseViewControllerClassName {
return @"SLEditingMenuTestViewController";
}

//+ (BOOL)supportsCurrentPlatform {
// // Don't run these tests on Travis, because the editing menu
// // intermittently fails to appear on Travis, while it consistently
// // appears in other environments.
// return [super supportsCurrentPlatform] && !getenv("TRAVIS");
//}

- (void)setUpTest {
[super setUpTest];

_menu = [SLEditingMenu menu];

_testText = [SLElement elementMatching:^BOOL(NSObject *obj) {
return [obj.accessibilityLabel hasPrefix:@"A corner of the pub"];
} withDescription:@"text element"];
_otherText = [SLElement elementWithAccessibilityLabel:@"Inklings"];

_menuItemTitle = @"Copy";
}

- (void)setUpTestCaseWithSelector:(SEL)testCaseSelector {
[super setUpTestCaseWithSelector:testCaseSelector];

SLAssertTrueWithTimeout(SLAskAppYesNo(webViewDidFinishLoad), 5.0, @"Webview did not load test HTML.");

if (testCaseSelector == @selector(testTapCustomMenuItem)) {
// It's important that this item have the same title as a standard item
// (see the implementation of `-[SLEditingMenuItem itemWithAccessibilityLabel:]`)
SLAskApp1(installCustomMenuItemWithTitle:, _menuItemTitle);
}
}

- (void)tearDownTestCaseWithSelector:(SEL)testCaseSelector {
// we never need to hide the editing menu because view controllers are not reused between tests
// the view controller will automatically restore the standard menu items, too

[super tearDownTestCaseWithSelector:testCaseSelector];
}

- (void)showEditingMenu {
[UIAElement(_testText) touchAndHoldWithDuration:1.0];

// wait for editing menu to appear before proceeding
static const NSTimeInterval kMenuAnimationDuration = 0.5;
(void)SLWaitUntilTrue([UIAElement(_menu) isValidAndVisible], kMenuAnimationDuration);
}

- (void)testCanMatchEditingMenu {
SLAssertFalse([UIAElement(_menu) isValidAndVisible], @"The editing menu should not be visible.");

[self showEditingMenu];

SLAssertTrue([UIAElement(_menu) isVisible], @"The editing menu should be visible.");
}

- (void)testTapMenuItem {
[self showEditingMenu];

SLEditingMenuItem *menuItem = [SLEditingMenuItem itemWithAccessibilityLabel:_menuItemTitle];
SLAssertTrue([UIAElement(menuItem) isVisible], nil);
SLAssertNoThrow([UIAElement(menuItem) tap], nil);

// give a little bit of time for the responder to perform the menu item action to be received
SLAssertTrueWithTimeout(SLAskAppYesNo(menuItemWasTapped), 0.2, @"The menu item should have been tapped.");
}

// Something of an internal test--see the implementation of `-[SLEditingMenuItem itemWithAccessibilityLabel:]`.
// It's important that the custom item have the same label as a standard item.
- (void)testTapCustomMenuItem {
SLAssertNoThrow([self testTapMenuItem], @"Tapping the custom menu item did not work as expected.");
}

@end
129 changes: 129 additions & 0 deletions Integration Tests/Tests/SLEditingMenuTestViewController.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//
// SLEditingMenuTestViewController.m
// Subliminal
//
// Created by Jeffrey Wear on 10/11/13.
// Copyright (c) 2013 Inkling. All rights reserved.
//

#import "SLTestCaseViewController.h"

#import <Subliminal/SLTestController+AppHooks.h>


@interface SLEditingMenuTestWebView : UIWebView
@property (nonatomic, readonly) BOOL copyItemWasTapped;
@property (nonatomic) BOOL standardMenuItemsDisabled;
@end

@implementation SLEditingMenuTestWebView

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (self.standardMenuItemsDisabled) return NO;

return [super canPerformAction:action withSender:sender];
}

- (void)copy:(id)sender {
_copyItemWasTapped = YES;
[super copy:sender];
}

@end


@interface SLEditingMenuTestViewController : SLTestCaseViewController <UIWebViewDelegate>
@property (nonatomic) BOOL standardMenuItemsDisabled;
@end

@implementation SLEditingMenuTestViewController {
SLEditingMenuTestWebView *_webView;
BOOL _webViewDidFinishLoad;

NSArray *_preexistingCustomMenuItems;
UIMenuItem *_customMenuItem;
BOOL _customMenuItemWasTapped;
}

- (void)loadViewForTestCase:(SEL)testCase {
_webView = [[SLEditingMenuTestWebView alloc] initWithFrame:CGRectZero];
_webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.view = _webView;
}

- (void)viewDidLoad {
[super viewDidLoad];

NSURL *webArchiveURL = [[NSBundle mainBundle] URLForResource:@"Inklings~iPhone" withExtension:@"webarchive"];
NSURLRequest *webArchiveRequest = [NSURLRequest requestWithURL:webArchiveURL];
_webView.delegate = self;
[_webView loadRequest:webArchiveRequest];

_webView.accessibilityLabel = @"foo";
}

- (instancetype)initWithTestCaseWithSelector:(SEL)testCase {
self = [super initWithTestCaseWithSelector:testCase];
if (self) {
_preexistingCustomMenuItems = [[[UIMenuController sharedMenuController] menuItems] copy];

SLTestController *testController = [SLTestController sharedTestController];
[testController registerTarget:self forAction:@selector(webViewDidFinishLoad)];
[testController registerTarget:self forAction:@selector(menuItemWasTapped)];
[testController registerTarget:self forAction:@selector(installCustomMenuItemWithTitle:)];
}
return self;
}

- (void)dealloc {
[[SLTestController sharedTestController] deregisterTarget:self];

[[UIMenuController sharedMenuController] setMenuItems:_preexistingCustomMenuItems];
}

#pragma mark - Editing menu

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (self.standardMenuItemsDisabled) {
return action == _customMenuItem.action;
} else {
return [super canPerformAction:action withSender:sender];
}
}

- (void)slCopy:(id)sender {
[_webView copy:sender];
}

- (void)setStandardMenuItemsDisabled:(BOOL)standardMenuItemsDisabled {
_standardMenuItemsDisabled = standardMenuItemsDisabled;
_webView.standardMenuItemsDisabled = standardMenuItemsDisabled;
}

- (void)installCustomMenuItemWithTitle:(NSString *)title {
self.standardMenuItemsDisabled = YES;

UIMenuController *menuController = [UIMenuController sharedMenuController];
// the title is for the test, to recognize and tap the item;
// the action is for the view controller, to track whether the item was tapped
_customMenuItem = [[UIMenuItem alloc] initWithTitle:title action:@selector(slCopy:)];
menuController.menuItems = @[ _customMenuItem ];
}

#pragma mark - UIWebView delegate

- (void)webViewDidFinishLoad:(UIWebView *)webView {
_webViewDidFinishLoad = YES;
}

#pragma mark - App hooks

- (NSNumber *)webViewDidFinishLoad {
return @(_webViewDidFinishLoad);
}

- (NSNumber *)menuItemWasTapped {
return @(_webView.copyItemWasTapped);
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// SLEditingMenu.h
// Subliminal
//
// For details and documentation:
// http://github.com/inkling/Subliminal
//
// Copyright 2013-2014 Inkling Systems, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#import "SLStaticElement.h"

/**
The singleton `SLEditingMenu` instance allows you to manipulate your application's editing menu
--the menu that shows commands like Cut, Copy, and Paste when the user selects text.
*/
@interface SLEditingMenu : SLStaticElement

/**
Returns an element representing the application's editing menu.

@return An element representing the application's editing menu.
*/
+ (instancetype)menu;

@end


/**
Instances of `SLEditingMenuItem` refer to items shown by the application's editing menu.
*/
@interface SLEditingMenuItem : SLStaticElement

/**
Creates and returns an element which represents the menu item with the specified label.

This is the designated initializer for a menu item.

@param label The item's accessibility label.
@return A newly created element representing the menu item with the specified label.
*/
+ (instancetype)itemWithAccessibilityLabel:(NSString *)label;

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// SLEditingMenu.m
// Subliminal
//
// For details and documentation:
// http://github.com/inkling/Subliminal
//
// Copyright 2013-2014 Inkling Systems, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#import "SLEditingMenu.h"
#import "SLUIAElement+Subclassing.h"

@implementation SLEditingMenu

+ (instancetype)menu {
return [[self alloc] initWithUIARepresentation:@"UIATarget.localTarget().frontMostApp().editingMenu()"];
}

@end


@implementation SLEditingMenuItem {
NSString *_label;
}

+ (instancetype)itemWithAccessibilityLabel:(NSString *)label {
return [[self alloc] initWithAccessibilityLabel:label];
}

- (instancetype)initWithAccessibilityLabel:(NSString *)label {
NSParameterAssert([label length]);

NSString *escapedLabel = [label slStringByEscapingForJavaScriptLiteral];
NSString *UIARepresentation = [NSString stringWithFormat:@"((function(){\
var items = UIATarget.localTarget().frontMostApp().editingMenu().elements();"
// the editing menu may contain multiple items with the same name (one system, one custom),
// only one of which is actually visible in the menu (has a non-zero size),
// so we must search through the array rather than retrieving the item by name
@"var item = null; \
if (items.toArray().some(function(elem) {\
item = elem;\
return ((elem.name() === '%@') && (elem.rect().size.width > 0));\
})) {\
return item;\
} else {"
// return whatever element the menu would have returned
@"return items['%@'];\
}\
})())", escapedLabel, escapedLabel];
self = [super initWithUIARepresentation:UIARepresentation];
if (self) {
_label = [label copy];
}
return self;
}

- (NSString *)description {
return [NSString stringWithFormat:@"<%@ label:\"%@\">", NSStringFromClass([self class]), _label];
}

@end
1 change: 1 addition & 0 deletions Sources/Subliminal.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#import "SLStaticElement.h"
#import "SLAlert.h"
#import "SLButton.h"
#import "SLEditingMenu.h"
#import "SLKeyboard.h"
#import "SLPopover.h"
#import "SLStatusBar.h"
Expand Down
Loading