146f9517509f3b1422aaf3c4f4150e8032e490cc
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKitCocoa / EditorStateTests.mm
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27
28 #if WK_API_ENABLED
29
30 #import "EditingTestHarness.h"
31 #import "PlatformUtilities.h"
32 #import "TestWKWebView.h"
33 #import <WebKit/WKWebViewPrivate.h>
34
35 #if PLATFORM(IOS_FAMILY)
36 #import "UIKitSPI.h"
37 #import <UIKit/UIKit.h>
38 #endif
39
40 namespace TestWebKitAPI {
41
42 static RetainPtr<EditingTestHarness> setUpEditorStateTestHarness()
43 {
44     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
45     auto testHarness = adoptNS([[EditingTestHarness alloc] initWithWebView:webView.get()]);
46     [webView synchronouslyLoadTestPageNamed:@"editor-state-test-harness"];
47     return testHarness;
48 }
49
50 TEST(EditorStateTests, TypingAttributesBold)
51 {
52     auto testHarness = setUpEditorStateTestHarness();
53
54     [testHarness insertHTML:@"<b>first</b>" andExpectEditorStateWith:@{ @"bold": @YES }];
55     [testHarness toggleBold];
56     [testHarness insertText:@" second" andExpectEditorStateWith:@{ @"bold": @NO }];
57     [testHarness insertHTML:@"<span style='font-weight: 700'> third</span>" andExpectEditorStateWith:@{ @"bold": @YES }];
58     [testHarness insertHTML:@"<span style='font-weight: 300'> fourth</span>" andExpectEditorStateWith:@{ @"bold": @NO }];
59     [testHarness insertHTML:@"<span style='font-weight: 800'> fifth</span>" andExpectEditorStateWith:@{ @"bold": @YES }];
60     [testHarness insertHTML:@"<span style='font-weight: 400'> sixth</span>" andExpectEditorStateWith:@{ @"bold": @NO }];
61     [testHarness insertHTML:@"<span style='font-weight: 900'> seventh</span>" andExpectEditorStateWith:@{ @"bold": @YES }];
62     [testHarness toggleBold];
63     [testHarness insertText:@" eighth" andExpectEditorStateWith:@{ @"bold": @NO }];
64     [testHarness insertHTML:@"<strong> ninth</strong>" andExpectEditorStateWith:@{ @"bold": @YES }];
65     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"bold": @YES }];
66     [testHarness deleteBackwardAndExpectEditorStateWith:@{ @"bold": @YES }];
67     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @YES }];
68     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @NO }];
69     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @YES }];
70     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @NO }];
71     [testHarness selectAllAndExpectEditorStateWith:@{ @"bold": @YES }];
72     EXPECT_WK_STREQ("first second third fourth fifth sixth seventh eighth ninth", [[testHarness webView] stringByEvaluatingJavaScript:@"getSelection().toString()"]);
73 }
74
75 TEST(EditorStateTests, TypingAttributesItalic)
76 {
77     auto testHarness = setUpEditorStateTestHarness();
78
79     [testHarness insertHTML:@"<i>first</i>" andExpectEditorStateWith:@{ @"italic": @YES }];
80     [testHarness toggleItalic];
81     [testHarness insertText:@" second" andExpectEditorStateWith:@{ @"italic": @NO }];
82     [testHarness insertHTML:@"<span style='font-style: italic'> third</span>" andExpectEditorStateWith:@{ @"italic": @YES }];
83     [testHarness toggleItalic];
84     [testHarness insertText:@" fourth" andExpectEditorStateWith:@{ @"italic": @NO }];
85     [testHarness toggleItalic];
86     [testHarness insertText:@" fifth" andExpectEditorStateWith:@{ @"italic": @YES }];
87     [testHarness insertHTML:@"<span style='font-style: normal'> sixth</span>" andExpectEditorStateWith:@{ @"italic": @NO }];
88     [testHarness insertHTML:@"<span style='font-style: oblique'> seventh</span>" andExpectEditorStateWith:@{ @"italic": @YES }];
89     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"italic": @YES }];
90     [testHarness deleteBackwardAndExpectEditorStateWith:@{ @"italic": @YES }];
91     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @YES }];
92     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @NO }];
93     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @YES }];
94     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @NO }];
95
96     [testHarness selectAllAndExpectEditorStateWith:@{ @"italic": @YES }];
97     EXPECT_WK_STREQ("first second third fourth fifth sixth seventh", [[testHarness webView] stringByEvaluatingJavaScript:@"getSelection().toString()"]);
98 }
99
100 TEST(EditorStateTests, TypingAttributesUnderline)
101 {
102     auto testHarness = setUpEditorStateTestHarness();
103
104     [testHarness insertHTML:@"<u>first</u>" andExpectEditorStateWith:@{ @"underline": @YES }];
105     [testHarness toggleUnderline];
106     [testHarness insertText:@" second" andExpectEditorStateWith:@{ @"underline": @NO }];
107     [testHarness insertHTML:@"<span style='text-decoration: underline'> third</span>" andExpectEditorStateWith:@{ @"underline": @YES }];
108     [testHarness insertHTML:@"<span style='text-decoration: line-through'> fourth</span>" andExpectEditorStateWith:@{ @"underline": @NO }];
109     [testHarness insertHTML:@"<span style='text-decoration: underline overline line-through'> fifth</span>" andExpectEditorStateWith:@{ @"underline": @YES }];
110     [testHarness insertHTML:@"<span style='text-decoration: none'> sixth</span>" andExpectEditorStateWith:@{ @"underline": @NO }];
111     [testHarness toggleUnderline];
112     [testHarness insertText:@" seventh" andExpectEditorStateWith:@{ @"underline": @YES }];
113     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"underline": @YES }];
114     [testHarness deleteBackwardAndExpectEditorStateWith:@{ @"underline": @YES }];
115     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @YES }];
116     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @NO }];
117     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @YES }];
118     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @NO }];
119
120     [testHarness selectAllAndExpectEditorStateWith:@{ @"underline": @YES }];
121     EXPECT_WK_STREQ("first second third fourth fifth sixth seventh", [[testHarness webView] stringByEvaluatingJavaScript:@"getSelection().toString()"]);
122 }
123
124 TEST(EditorStateTests, TypingAttributesTextAlignmentAbsoluteAlignmentOptions)
125 {
126     auto testHarness = setUpEditorStateTestHarness();
127     TestWKWebView *webView = [testHarness webView];
128
129     [webView stringByEvaluatingJavaScript:@"document.body.style.direction = 'ltr'"];
130
131     [testHarness insertHTML:@"<div style='text-align: right;'>right</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
132     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
133
134     [testHarness insertText:@"justified" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
135     [testHarness alignJustifiedAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentJustified) }];
136     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentJustified) }];
137
138     [testHarness alignCenterAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
139     [testHarness insertText:@"center" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
140     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
141
142     [testHarness insertHTML:@"<span id='left'>left</span>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
143     [webView stringByEvaluatingJavaScript:@"getSelection().setBaseAndExtent(left.childNodes[0], 0, left.childNodes[0], 6)"];
144     [testHarness alignLeftAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
145
146     [testHarness selectAllAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
147     EXPECT_WK_STREQ("right\njustified\ncenter\nleft", [webView stringByEvaluatingJavaScript:@"getSelection().toString()"]);
148 }
149
150 TEST(EditorStateTests, TypingAttributesTextAlignmentStartEnd)
151 {
152     auto testHarness = setUpEditorStateTestHarness();
153     TestWKWebView *webView = [testHarness webView];
154
155     [webView stringByEvaluatingJavaScript:@"document.styleSheets[0].insertRule('.start { text-align: start; }')"];
156     [webView stringByEvaluatingJavaScript:@"document.styleSheets[0].insertRule('.end { text-align: end; }')"];
157     [webView stringByEvaluatingJavaScript:@"document.body.style.direction = 'rtl'"];
158
159     [testHarness insertHTML:@"<div class='start'>rtl start</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
160     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
161
162     [testHarness insertHTML:@"<div class='end'>rtl end</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
163     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
164
165     [[testHarness webView] stringByEvaluatingJavaScript:@"document.body.style.direction = 'ltr'"];
166     [testHarness insertHTML:@"<div class='start'>ltr start</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
167     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
168
169     [testHarness insertHTML:@"<div class='end'>ltr end</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
170     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
171 }
172
173 TEST(EditorStateTests, TypingAttributesTextAlignmentDirectionalText)
174 {
175     auto testHarness = setUpEditorStateTestHarness();
176     [[testHarness webView] stringByEvaluatingJavaScript:@"document.body.setAttribute('dir', 'auto')"];
177
178     [testHarness insertHTML:@"<div>מקור השם עברית</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
179     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
180     [testHarness insertHTML:@"<div dir='ltr'>מקור השם עברית</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
181     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
182     [testHarness insertHTML:@"<div dir='rtl'>מקור השם עברית</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
183     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
184
185     [testHarness insertHTML:@"<div dir='auto'>This is English text</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
186     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
187     [testHarness insertHTML:@"<div dir='rtl'>This is English text</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
188     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
189     [testHarness insertHTML:@"<div dir='ltr'>This is English text</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
190     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
191 }
192
193 TEST(EditorStateTests, TypingAttributesTextColor)
194 {
195     auto testHarness = setUpEditorStateTestHarness();
196
197     [testHarness setForegroundColor:@"rgb(255, 0, 0)"];
198     [testHarness insertText:@"red" andExpectEditorStateWith:@{ @"text-color": @"rgb(255, 0, 0)" }];
199
200     [testHarness insertHTML:@"<span style='color: rgb(0, 255, 0)'>green</span>" andExpectEditorStateWith:@{ @"text-color": @"rgb(0, 255, 0)" }];
201     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-color": @"rgb(0, 255, 0)" }];
202
203     [testHarness setForegroundColor:@"rgb(0, 0, 255)"];
204     [testHarness insertText:@"blue" andExpectEditorStateWith:@{ @"text-color": @"rgb(0, 0, 255)" }];
205 }
206
207 TEST(EditorStateTests, TypingAttributesMixedStyles)
208 {
209     auto testHarness = setUpEditorStateTestHarness();
210
211     [testHarness alignCenterAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
212     [testHarness setForegroundColor:@"rgb(128, 128, 128)"];
213     [testHarness toggleBold];
214     [testHarness toggleItalic];
215     [testHarness toggleUnderline];
216     NSDictionary *expectedAttributes = @{
217         @"bold": @YES,
218         @"italic": @YES,
219         @"underline": @YES,
220         @"text-color": @"rgb(128, 128, 128)",
221         @"text-alignment": @(NSTextAlignmentCenter)
222     };
223     BOOL containsProperties = [testHarness latestEditorStateContains:expectedAttributes];
224     EXPECT_TRUE(containsProperties);
225     if (!containsProperties)
226         NSLog(@"Expected %@ to contain %@", [testHarness latestEditorState], expectedAttributes);
227 }
228
229 TEST(EditorStateTests, TypingAttributeLinkColor)
230 {
231     auto testHarness = setUpEditorStateTestHarness();
232     [testHarness insertHTML:@"<a href='https://www.apple.com/'>This is a link</a>" andExpectEditorStateWith:@{ @"text-color": @"rgb(0, 0, 238)" }];
233     [testHarness selectAllAndExpectEditorStateWith:@{ @"text-color": @"rgb(0, 0, 238)" }];
234     EXPECT_WK_STREQ("https://www.apple.com/", [[testHarness webView] stringByEvaluatingJavaScript:@"document.querySelector('a').href"]);
235 }
236
237 #if PLATFORM(IOS_FAMILY)
238
239 static void checkContentViewHasTextWithFailureDescription(TestWKWebView *webView, BOOL expectedToHaveText, NSString *description)
240 {
241     BOOL hasText = webView.textInputContentView.hasText;
242     if (expectedToHaveText)
243         EXPECT_TRUE(hasText);
244     else
245         EXPECT_FALSE(hasText);
246
247     if (expectedToHaveText != hasText)
248         NSLog(@"Expected -[%@ hasText] to be %@, but observed: %@ (%@)", [webView.textInputContentView class], expectedToHaveText ? @"YES" : @"NO", hasText ? @"YES" : @"NO", description);
249 }
250
251 TEST(EditorStateTests, ContentViewHasTextInContentEditableElement)
252 {
253     auto testHarness = setUpEditorStateTestHarness();
254     TestWKWebView *webView = [testHarness webView];
255
256     checkContentViewHasTextWithFailureDescription(webView, NO, @"before inserting any content");
257     [testHarness insertHTML:@"<img src='icon.png'></img>"];
258     checkContentViewHasTextWithFailureDescription(webView, NO, @"after inserting an image element");
259     [testHarness insertText:@"A"];
260     checkContentViewHasTextWithFailureDescription(webView, YES, @"after inserting text");
261     [testHarness selectAll];
262     checkContentViewHasTextWithFailureDescription(webView, YES, @"after selecting everything");
263     [testHarness deleteBackwards];
264     checkContentViewHasTextWithFailureDescription(webView, NO, @"after deleting everything");
265     [testHarness insertParagraph];
266     checkContentViewHasTextWithFailureDescription(webView, YES, @"after inserting a newline");
267     [testHarness deleteBackwards];
268     checkContentViewHasTextWithFailureDescription(webView, NO, @"after deleting the newline");
269     [testHarness insertText:@"B"];
270     checkContentViewHasTextWithFailureDescription(webView, YES, @"after inserting text again");
271     [webView stringByEvaluatingJavaScript:@"document.body.blur()"];
272     [webView waitForNextPresentationUpdate];
273     checkContentViewHasTextWithFailureDescription(webView, NO, @"after losing focus");
274 }
275
276 TEST(EditorStateTests, ContentViewHasTextInTextarea)
277 {
278     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
279     auto testHarness = adoptNS([[EditingTestHarness alloc] initWithWebView:webView.get()]);
280     [webView synchronouslyLoadHTMLString:@"<textarea id='textarea'></textarea>"];
281     [webView stringByEvaluatingJavaScript:@"textarea.focus()"];
282     [webView waitForNextPresentationUpdate];
283
284     checkContentViewHasTextWithFailureDescription(webView.get(), NO, @"before inserting any content");
285     [testHarness insertText:@"A"];
286     checkContentViewHasTextWithFailureDescription(webView.get(), YES, @"after inserting text");
287     [testHarness selectAll];
288     checkContentViewHasTextWithFailureDescription(webView.get(), YES, @"after selecting everything");
289     [testHarness deleteBackwards];
290     checkContentViewHasTextWithFailureDescription(webView.get(), NO, @"after deleting everything");
291     [testHarness insertParagraph];
292     checkContentViewHasTextWithFailureDescription(webView.get(), YES, @"after inserting a newline");
293     [testHarness deleteBackwards];
294     checkContentViewHasTextWithFailureDescription(webView.get(), NO, @"after deleting the newline");
295     [testHarness insertText:@"B"];
296     checkContentViewHasTextWithFailureDescription(webView.get(), YES, @"after inserting text again");
297     [webView stringByEvaluatingJavaScript:@"textarea.blur()"];
298     [webView waitForNextPresentationUpdate];
299     checkContentViewHasTextWithFailureDescription(webView.get(), NO, @"after losing focus");
300 }
301
302 TEST(EditorStateTests, CaretColorInContentEditable)
303 {
304     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
305     [webView synchronouslyLoadHTMLString:@"<body style=\"caret-color: red;\" contenteditable=\"true\"></body>"];
306     [webView stringByEvaluatingJavaScript:@"document.body.focus()"];
307     UIView<UITextInputTraits_Private> *textInput = (UIView<UITextInputTraits_Private> *) [webView textInputContentView];
308     UIColor *insertionPointColor = textInput.insertionPointColor;
309     UIColor *redColor = [UIColor redColor];
310     auto colorSpace = adoptCF(CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
311     auto cgInsertionPointColor = adoptCF(CGColorCreateCopyByMatchingToColorSpace(colorSpace.get(), kCGRenderingIntentDefault, insertionPointColor.CGColor, NULL));
312     auto cgRedColor = adoptCF(CGColorCreateCopyByMatchingToColorSpace(colorSpace.get(), kCGRenderingIntentDefault, redColor.CGColor, NULL));
313     EXPECT_TRUE(CGColorEqualToColor(cgInsertionPointColor.get(), cgRedColor.get()));
314 }
315 #endif
316
317 } // namespace TestWebKitAPI
318
319 #endif // WK_API_ENABLED