blob: 63a070538cba1ad5195054c151ea5b932892e286 [file] [log] [blame]
/*
* Copyright (C) 2017-2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "EditingTestHarness.h"
#import "PlatformUtilities.h"
#import "TestCocoa.h"
#import "TestInputDelegate.h"
#import "TestWKWebView.h"
#import "UserInterfaceSwizzler.h"
#import <WebKit/WKWebViewPrivate.h>
#import <wtf/Vector.h>
#if PLATFORM(IOS_FAMILY)
#import "UIKitSPI.h"
#endif
static void* const SelectionAttributesObservationContext = (void*)&SelectionAttributesObservationContext;
@interface SelectionChangeObserver : NSObject
- (instancetype)initWithWebView:(TestWKWebView *)webView;
@property (nonatomic, readonly) TestWKWebView *webView;
@property (nonatomic, readonly) _WKSelectionAttributes currentSelectionAttributes;
@end
@implementation SelectionChangeObserver {
RetainPtr<TestWKWebView> _webView;
Vector<_WKSelectionAttributes> _observedSelectionAttributes;
}
- (instancetype)initWithWebView:(TestWKWebView *)webView
{
if (!(self = [super init]))
return nil;
_webView = webView;
[_webView addObserver:self forKeyPath:@"_selectionAttributes" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:SelectionAttributesObservationContext];
return self;
}
- (TestWKWebView *)webView
{
return _webView.get();
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
{
if (context == SelectionAttributesObservationContext) {
if (!_observedSelectionAttributes.isEmpty())
EXPECT_EQ(_observedSelectionAttributes.last(), [change[NSKeyValueChangeOldKey] unsignedIntValue]);
_observedSelectionAttributes.append([change[NSKeyValueChangeNewKey] unsignedIntValue]);
return;
}
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
- (_WKSelectionAttributes)currentSelectionAttributes
{
return _observedSelectionAttributes.isEmpty() ? _WKSelectionAttributeNoSelection : _observedSelectionAttributes.last();
}
@end
namespace TestWebKitAPI {
static RetainPtr<EditingTestHarness> setUpEditorStateTestHarness()
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
auto testHarness = adoptNS([[EditingTestHarness alloc] initWithWebView:webView.get()]);
[webView synchronouslyLoadTestPageNamed:@"editor-state-test-harness"];
return testHarness;
}
TEST(EditorStateTests, TypingAttributesBold)
{
auto testHarness = setUpEditorStateTestHarness();
[testHarness insertHTML:@"<b>first</b>" andExpectEditorStateWith:@{ @"bold": @YES }];
[testHarness toggleBold];
[testHarness insertText:@" second" andExpectEditorStateWith:@{ @"bold": @NO }];
[testHarness insertHTML:@"<span style='font-weight: 700'> third</span>" andExpectEditorStateWith:@{ @"bold": @YES }];
[testHarness insertHTML:@"<span style='font-weight: 300'> fourth</span>" andExpectEditorStateWith:@{ @"bold": @NO }];
[testHarness insertHTML:@"<span style='font-weight: 800'> fifth</span>" andExpectEditorStateWith:@{ @"bold": @YES }];
[testHarness insertHTML:@"<span style='font-weight: 400'> sixth</span>" andExpectEditorStateWith:@{ @"bold": @NO }];
[testHarness insertHTML:@"<span style='font-weight: 900'> seventh</span>" andExpectEditorStateWith:@{ @"bold": @YES }];
[testHarness toggleBold];
[testHarness insertText:@" eighth" andExpectEditorStateWith:@{ @"bold": @NO }];
[testHarness insertHTML:@"<strong> ninth</strong>" andExpectEditorStateWith:@{ @"bold": @YES }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"bold": @YES }];
[testHarness deleteBackwardAndExpectEditorStateWith:@{ @"bold": @YES }];
[testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @YES }];
[testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @NO }];
[testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @YES }];
[testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @NO }];
[testHarness selectAllAndExpectEditorStateWith:@{ @"bold": @YES }];
EXPECT_WK_STREQ("first second third fourth fifth sixth seventh eighth ninth", [[testHarness webView] stringByEvaluatingJavaScript:@"getSelection().toString()"]);
}
TEST(EditorStateTests, TypingAttributesItalic)
{
auto testHarness = setUpEditorStateTestHarness();
[testHarness insertHTML:@"<i>first</i>" andExpectEditorStateWith:@{ @"italic": @YES }];
[testHarness toggleItalic];
[testHarness insertText:@" second" andExpectEditorStateWith:@{ @"italic": @NO }];
[testHarness insertHTML:@"<span style='font-style: italic'> third</span>" andExpectEditorStateWith:@{ @"italic": @YES }];
[testHarness toggleItalic];
[testHarness insertText:@" fourth" andExpectEditorStateWith:@{ @"italic": @NO }];
[testHarness toggleItalic];
[testHarness insertText:@" fifth" andExpectEditorStateWith:@{ @"italic": @YES }];
[testHarness insertHTML:@"<span style='font-style: normal'> sixth</span>" andExpectEditorStateWith:@{ @"italic": @NO }];
[testHarness insertHTML:@"<span style='font-style: oblique'> seventh</span>" andExpectEditorStateWith:@{ @"italic": @YES }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"italic": @YES }];
[testHarness deleteBackwardAndExpectEditorStateWith:@{ @"italic": @YES }];
[testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @YES }];
[testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @NO }];
[testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @YES }];
[testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @NO }];
[testHarness selectAllAndExpectEditorStateWith:@{ @"italic": @YES }];
EXPECT_WK_STREQ("first second third fourth fifth sixth seventh", [[testHarness webView] stringByEvaluatingJavaScript:@"getSelection().toString()"]);
}
TEST(EditorStateTests, TypingAttributesUnderline)
{
auto testHarness = setUpEditorStateTestHarness();
[testHarness insertHTML:@"<u>first</u>" andExpectEditorStateWith:@{ @"underline": @YES }];
[testHarness toggleUnderline];
[testHarness insertText:@" second" andExpectEditorStateWith:@{ @"underline": @NO }];
[testHarness insertHTML:@"<span style='text-decoration: underline'> third</span>" andExpectEditorStateWith:@{ @"underline": @YES }];
[testHarness insertHTML:@"<span style='text-decoration: line-through'> fourth</span>" andExpectEditorStateWith:@{ @"underline": @NO }];
[testHarness insertHTML:@"<span style='text-decoration: underline overline line-through'> fifth</span>" andExpectEditorStateWith:@{ @"underline": @YES }];
[testHarness insertHTML:@"<span style='text-decoration: none'> sixth</span>" andExpectEditorStateWith:@{ @"underline": @NO }];
[testHarness toggleUnderline];
[testHarness insertText:@" seventh" andExpectEditorStateWith:@{ @"underline": @YES }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"underline": @YES }];
[testHarness deleteBackwardAndExpectEditorStateWith:@{ @"underline": @YES }];
[testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @YES }];
[testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @NO }];
[testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @YES }];
[testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @NO }];
[testHarness selectAllAndExpectEditorStateWith:@{ @"underline": @YES }];
EXPECT_WK_STREQ("first second third fourth fifth sixth seventh", [[testHarness webView] stringByEvaluatingJavaScript:@"getSelection().toString()"]);
}
TEST(EditorStateTests, TypingAttributesTextAlignmentAbsoluteAlignmentOptions)
{
auto testHarness = setUpEditorStateTestHarness();
TestWKWebView *webView = [testHarness webView];
[webView stringByEvaluatingJavaScript:@"document.body.style.direction = 'ltr'"];
[testHarness insertHTML:@"<div style='text-align: right;'>right</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
[testHarness insertText:@"justified" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
[testHarness alignJustifiedAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentJustified) }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentJustified) }];
[testHarness alignCenterAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
[testHarness insertText:@"center" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
[testHarness insertHTML:@"<span id='left'>left</span>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
[webView stringByEvaluatingJavaScript:@"getSelection().setBaseAndExtent(left.childNodes[0], 0, left.childNodes[0], 6)"];
[testHarness alignLeftAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
[testHarness selectAllAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
EXPECT_WK_STREQ("right\njustified\ncenter\nleft", [webView stringByEvaluatingJavaScript:@"getSelection().toString()"]);
}
TEST(EditorStateTests, TypingAttributesTextAlignmentStartEnd)
{
auto testHarness = setUpEditorStateTestHarness();
TestWKWebView *webView = [testHarness webView];
[webView stringByEvaluatingJavaScript:@"document.styleSheets[0].insertRule('.start { text-align: start; }')"];
[webView stringByEvaluatingJavaScript:@"document.styleSheets[0].insertRule('.end { text-align: end; }')"];
[webView stringByEvaluatingJavaScript:@"document.body.style.direction = 'rtl'"];
[testHarness insertHTML:@"<div class='start'>rtl start</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
[testHarness insertHTML:@"<div class='end'>rtl end</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
[[testHarness webView] stringByEvaluatingJavaScript:@"document.body.style.direction = 'ltr'"];
[testHarness insertHTML:@"<div class='start'>ltr start</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
[testHarness insertHTML:@"<div class='end'>ltr end</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
}
TEST(EditorStateTests, TypingAttributesTextAlignmentDirectionalText)
{
auto testHarness = setUpEditorStateTestHarness();
[[testHarness webView] stringByEvaluatingJavaScript:@"document.body.setAttribute('dir', 'auto')"];
[testHarness insertHTML:@"<div>מקור השם עברית</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
[testHarness insertHTML:@"<div dir='ltr'>מקור השם עברית</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
[testHarness insertHTML:@"<div dir='rtl'>מקור השם עברית</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
[testHarness insertHTML:@"<div dir='auto'>This is English text</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
[testHarness insertHTML:@"<div dir='rtl'>This is English text</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
[testHarness insertHTML:@"<div dir='ltr'>This is English text</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
}
TEST(EditorStateTests, TypingAttributesTextColor)
{
auto testHarness = setUpEditorStateTestHarness();
[testHarness setForegroundColor:@"rgb(255, 0, 0)"];
[testHarness insertText:@"red" andExpectEditorStateWith:@{ @"text-color": @"rgb(255, 0, 0)" }];
[testHarness insertHTML:@"<span style='color: rgb(0, 255, 0)'>green</span>" andExpectEditorStateWith:@{ @"text-color": @"rgb(0, 255, 0)" }];
[testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-color": @"rgb(0, 255, 0)" }];
[testHarness setForegroundColor:@"rgb(0, 0, 255)"];
[testHarness insertText:@"blue" andExpectEditorStateWith:@{ @"text-color": @"rgb(0, 0, 255)" }];
}
TEST(EditorStateTests, TypingAttributesMixedStyles)
{
auto testHarness = setUpEditorStateTestHarness();
[testHarness alignCenterAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
[testHarness setForegroundColor:@"rgb(128, 128, 128)"];
[testHarness toggleBold];
[testHarness toggleItalic];
[testHarness toggleUnderline];
NSDictionary *expectedAttributes = @{
@"bold": @YES,
@"italic": @YES,
@"underline": @YES,
@"text-color": @"rgb(128, 128, 128)",
@"text-alignment": @(NSTextAlignmentCenter)
};
BOOL containsProperties = [testHarness latestEditorStateContains:expectedAttributes];
EXPECT_TRUE(containsProperties);
if (!containsProperties)
NSLog(@"Expected %@ to contain %@", [testHarness latestEditorState], expectedAttributes);
}
TEST(EditorStateTests, TypingAttributeLinkColor)
{
auto testHarness = setUpEditorStateTestHarness();
[testHarness insertHTML:@"<a href='https://www.apple.com/'>This is a link</a>" andExpectEditorStateWith:@{ @"text-color": @"rgb(0, 0, 238)" }];
[testHarness selectAllAndExpectEditorStateWith:@{ @"text-color": @"rgb(0, 0, 238)" }];
EXPECT_WK_STREQ("https://www.apple.com/", [[testHarness webView] stringByEvaluatingJavaScript:@"document.querySelector('a').href"]);
}
#if PLATFORM(IOS_FAMILY)
static void checkContentViewHasTextWithFailureDescription(TestWKWebView *webView, BOOL expectedToHaveText, NSString *description)
{
BOOL hasText = webView.textInputContentView.hasText;
if (expectedToHaveText)
EXPECT_TRUE(hasText);
else
EXPECT_FALSE(hasText);
if (expectedToHaveText != hasText)
NSLog(@"Expected -[%@ hasText] to be %@, but observed: %@ (%@)", [webView.textInputContentView class], expectedToHaveText ? @"YES" : @"NO", hasText ? @"YES" : @"NO", description);
}
TEST(EditorStateTests, ContentViewHasTextInContentEditableElement)
{
auto testHarness = setUpEditorStateTestHarness();
TestWKWebView *webView = [testHarness webView];
checkContentViewHasTextWithFailureDescription(webView, NO, @"before inserting any content");
[testHarness insertHTML:@"<img src='icon.png'></img>"];
checkContentViewHasTextWithFailureDescription(webView, NO, @"after inserting an image element");
[testHarness insertText:@"A"];
checkContentViewHasTextWithFailureDescription(webView, YES, @"after inserting text");
[testHarness selectAll];
checkContentViewHasTextWithFailureDescription(webView, YES, @"after selecting everything");
[testHarness deleteBackwards];
checkContentViewHasTextWithFailureDescription(webView, NO, @"after deleting everything");
[testHarness insertParagraph];
checkContentViewHasTextWithFailureDescription(webView, YES, @"after inserting a newline");
[testHarness deleteBackwards];
checkContentViewHasTextWithFailureDescription(webView, NO, @"after deleting the newline");
[testHarness insertText:@"B"];
checkContentViewHasTextWithFailureDescription(webView, YES, @"after inserting text again");
[webView stringByEvaluatingJavaScript:@"document.body.blur()"];
[webView waitForNextPresentationUpdate];
checkContentViewHasTextWithFailureDescription(webView, NO, @"after losing focus");
}
TEST(EditorStateTests, ContentViewHasTextInTextarea)
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
auto testHarness = adoptNS([[EditingTestHarness alloc] initWithWebView:webView.get()]);
[webView synchronouslyLoadHTMLString:@"<textarea id='textarea'></textarea>"];
[webView stringByEvaluatingJavaScript:@"textarea.focus()"];
[webView waitForNextPresentationUpdate];
checkContentViewHasTextWithFailureDescription(webView.get(), NO, @"before inserting any content");
[testHarness insertText:@"A"];
checkContentViewHasTextWithFailureDescription(webView.get(), YES, @"after inserting text");
[testHarness selectAll];
checkContentViewHasTextWithFailureDescription(webView.get(), YES, @"after selecting everything");
[testHarness deleteBackwards];
checkContentViewHasTextWithFailureDescription(webView.get(), NO, @"after deleting everything");
[testHarness insertParagraph];
checkContentViewHasTextWithFailureDescription(webView.get(), YES, @"after inserting a newline");
[testHarness deleteBackwards];
checkContentViewHasTextWithFailureDescription(webView.get(), NO, @"after deleting the newline");
[testHarness insertText:@"B"];
checkContentViewHasTextWithFailureDescription(webView.get(), YES, @"after inserting text again");
[webView stringByEvaluatingJavaScript:@"textarea.blur()"];
[webView waitForNextPresentationUpdate];
checkContentViewHasTextWithFailureDescription(webView.get(), NO, @"after losing focus");
}
TEST(EditorStateTests, CaretColorInContentEditable)
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
[webView synchronouslyLoadHTMLString:@"<body style=\"caret-color: red;\" contenteditable=\"true\"></body>"];
[webView stringByEvaluatingJavaScript:@"document.body.focus()"];
[webView waitForNextPresentationUpdate];
UIView<UITextInputTraits_Private> *textInput = (UIView<UITextInputTraits_Private> *) [webView textInputContentView];
UIColor *insertionPointColor = textInput.insertionPointColor;
UIColor *redColor = [UIColor redColor];
auto colorSpace = adoptCF(CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
auto cgInsertionPointColor = adoptCF(CGColorCreateCopyByMatchingToColorSpace(colorSpace.get(), kCGRenderingIntentDefault, insertionPointColor.CGColor, NULL));
auto cgRedColor = adoptCF(CGColorCreateCopyByMatchingToColorSpace(colorSpace.get(), kCGRenderingIntentDefault, redColor.CGColor, NULL));
EXPECT_TRUE(CGColorEqualToColor(cgInsertionPointColor.get(), cgRedColor.get()));
}
TEST(EditorStateTests, ObserveSelectionAttributeChanges)
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
auto editor = adoptNS([[EditingTestHarness alloc] initWithWebView:webView.get()]);
[webView _setEditable:YES];
[webView synchronouslyLoadHTMLString:@"<body></body>"];
auto observer = adoptNS([[SelectionChangeObserver alloc] initWithWebView:webView.get()]);
[webView evaluateJavaScript:@"document.body.focus()" completionHandler:nil];
[webView waitForNextPresentationUpdate];
EXPECT_EQ(_WKSelectionAttributeIsCaret | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
[editor insertText:@"Hello"];
EXPECT_EQ(_WKSelectionAttributeIsCaret, [observer currentSelectionAttributes]);
[editor insertText:@"."];
EXPECT_EQ(_WKSelectionAttributeIsCaret | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
[editor moveBackward];
EXPECT_EQ(_WKSelectionAttributeIsCaret, [observer currentSelectionAttributes]);
[editor moveForward];
EXPECT_EQ(_WKSelectionAttributeIsCaret | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
[editor deleteBackwards];
EXPECT_EQ(_WKSelectionAttributeIsCaret, [observer currentSelectionAttributes]);
[editor insertParagraph];
EXPECT_EQ(_WKSelectionAttributeIsCaret | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
[editor selectAll];
EXPECT_EQ(_WKSelectionAttributeIsRange | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
[webView evaluateJavaScript:@"getSelection().removeAllRanges()" completionHandler:nil];
[webView waitForNextPresentationUpdate];
EXPECT_EQ(_WKSelectionAttributeNoSelection, [observer currentSelectionAttributes]);
}
TEST(EditorStateTests, ParagraphBoundary)
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
[webView synchronouslyLoadHTMLString:@"<body contenteditable><p>Hello world.</p></body>"];
[webView stringByEvaluatingJavaScript:@"document.body.focus()"];
[webView waitForNextPresentationUpdate];
auto textInput = [webView textInputContentView];
auto editor = adoptNS([[EditingTestHarness alloc] initWithWebView:webView.get()]);
[editor selectAll];
EXPECT_TRUE([textInput isPosition:textInput.selectedTextRange.start atBoundary:UITextGranularityParagraph inDirection:UITextStorageDirectionBackward]);
EXPECT_TRUE([textInput isPosition:textInput.selectedTextRange.end atBoundary:UITextGranularityParagraph inDirection:UITextStorageDirectionForward]);
[editor moveForward];
[editor moveBackward];
[editor moveBackward];
EXPECT_FALSE([textInput isPosition:textInput.selectedTextRange.start atBoundary:UITextGranularityParagraph inDirection:UITextStorageDirectionBackward]);
EXPECT_FALSE([textInput isPosition:textInput.selectedTextRange.end atBoundary:UITextGranularityParagraph inDirection:UITextStorageDirectionForward]);
}
TEST(EditorStateTests, SelectedText)
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
[webView synchronouslyLoadTestPageNamed:@"lots-of-text"];
[webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
[webView waitForNextPresentationUpdate];
EXPECT_GT([[webView textInputContentView] selectedText].length, 0U);
}
constexpr unsigned glyphWidth { 25 }; // pixels
static NSString *applyAhemStyle(NSString *htmlString)
{
return [NSString stringWithFormat:@"<style>@font-face { font-family: Ahem; src: url(Ahem.ttf); } body { margin: 0; } * { font: %upx/1 Ahem; -webkit-text-size-adjust: none; }</style><meta name='viewport' content='width=980, initial-scale=1.0'>%@", glyphWidth, htmlString];
}
TEST(EditorStateTests, MarkedTextRange_HorizontalCaretSelection)
{
IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[webView synchronouslyLoadHTMLStringAndWaitUntilAllImmediateChildFramesPaint:applyAhemStyle(@"<body contenteditable='true'>.</body>")]; // . is dummy to force Ahem to load
[webView stringByEvaluatingJavaScript:@"document.body.focus()"];
auto *contentView = [webView textInputContentView];
[contentView setMarkedText:@"hello" selectedRange:NSMakeRange(0, 0)];
[webView waitForNextPresentationUpdate];
UITextRange *markedTextRange = [contentView markedTextRange];
NSArray<UITextSelectionRect *> *rects = [contentView selectionRectsForRange:markedTextRange];
EXPECT_EQ(1U, rects.count);
EXPECT_EQ(CGRectMake(0, 0, 5 * glyphWidth, glyphWidth), rects[0].rect);
EXPECT_EQ(CGRectMake(0, 0, 2, glyphWidth), [contentView caretRectForPosition:markedTextRange.start]);
EXPECT_EQ(CGRectMake(124, 0, 2, glyphWidth), [contentView caretRectForPosition:markedTextRange.end]);
EXPECT_FALSE(rects[0].isVertical);
}
TEST(EditorStateTests, MarkedTextRange_HorizontalRangeSelection)
{
IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[webView synchronouslyLoadHTMLStringAndWaitUntilAllImmediateChildFramesPaint:applyAhemStyle(@"<body contenteditable='true'>.</body>")]; // . is dummy to force Ahem to load
[webView stringByEvaluatingJavaScript:@"document.body.focus()"];
[webView _synchronouslyExecuteEditCommand:@"InsertText" argument:@"Hello world"];
auto *contentView = [webView textInputContentView];
[contentView selectWordBackward];
[contentView setMarkedText:@"world" selectedRange:NSMakeRange(0, 5)];
[webView waitForNextPresentationUpdate];
UITextRange *markedTextRange = [contentView markedTextRange];
NSArray<UITextSelectionRect *> *rects = [contentView selectionRectsForRange:markedTextRange];
EXPECT_EQ(1U, rects.count);
EXPECT_EQ(CGRectMake(150, 0, 5 * glyphWidth, glyphWidth), rects[0].rect);
EXPECT_EQ(CGRectMake(149, 0, 2, glyphWidth), [contentView caretRectForPosition:markedTextRange.start]);
EXPECT_EQ(CGRectMake(274, 0, 2, glyphWidth), [contentView caretRectForPosition:markedTextRange.end]);
EXPECT_FALSE(rects[0].isVertical);
}
TEST(EditorStateTests, MarkedTextRange_VerticalCaretSelection)
{
IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[webView synchronouslyLoadHTMLStringAndWaitUntilAllImmediateChildFramesPaint:applyAhemStyle(@"<body style='writing-mode: vertical-lr' contenteditable='true'>.</body>")]; // . is dummy to force Ahem to load
[webView stringByEvaluatingJavaScript:@"document.body.focus()"];
auto *contentView = [webView textInputContentView];
[contentView setMarkedText:@"hello" selectedRange:NSMakeRange(0, 0)];
[webView waitForNextPresentationUpdate];
UITextRange *markedTextRange = [contentView markedTextRange];
NSArray<UITextSelectionRect *> *rects = [contentView selectionRectsForRange:markedTextRange];
EXPECT_EQ(1U, rects.count);
EXPECT_EQ(CGRectMake(0, 0, glyphWidth, 5 * glyphWidth), rects[0].rect);
EXPECT_EQ(CGRectMake(0, 0, glyphWidth, 2), [contentView caretRectForPosition:markedTextRange.start]);
EXPECT_EQ(CGRectMake(0, 124, glyphWidth, 2), [contentView caretRectForPosition:markedTextRange.end]);
EXPECT_TRUE(rects[0].isVertical);
}
TEST(EditorStateTests, MarkedTextRange_VerticalRangeSelection)
{
IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[webView synchronouslyLoadHTMLString:applyAhemStyle(@"<body style='writing-mode: vertical-lr' contenteditable='true'>.</body>")]; // . is dummy to force Ahem to load
[webView stringByEvaluatingJavaScript:@"document.body.focus()"];
[webView _synchronouslyExecuteEditCommand:@"InsertText" argument:@"Hello world"];
auto *contentView = [webView textInputContentView];
[contentView selectWordBackward];
[contentView setMarkedText:@"world" selectedRange:NSMakeRange(0, 5)];
[webView waitForNextPresentationUpdate];
UITextRange *markedTextRange = [contentView markedTextRange];
NSArray<UITextSelectionRect *> *rects = [contentView selectionRectsForRange:markedTextRange];
EXPECT_EQ(1U, rects.count);
EXPECT_EQ(CGRectMake(0, 150, glyphWidth, 5 * glyphWidth), rects[0].rect);
EXPECT_EQ(CGRectMake(0, 149, glyphWidth, 2), [contentView caretRectForPosition:markedTextRange.start]);
EXPECT_EQ(CGRectMake(0, 274, glyphWidth, 2), [contentView caretRectForPosition:markedTextRange.end]);
EXPECT_TRUE(rects[0].isVertical);
}
#endif // PLATFORM(IOS_FAMILY)
} // namespace TestWebKitAPI