8efbb90e82a2565877497700767af4daa6667292
[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)
36 #import <UIKit/UIKit.h>
37 #endif
38
39 namespace TestWebKitAPI {
40
41 static RetainPtr<EditingTestHarness> setUpEditorStateTestHarness()
42 {
43     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
44     auto testHarness = adoptNS([[EditingTestHarness alloc] initWithWebView:webView.get()]);
45     [webView synchronouslyLoadTestPageNamed:@"editor-state-test-harness"];
46     return testHarness;
47 }
48
49 TEST(EditorStateTests, TypingAttributesBold)
50 {
51     auto testHarness = setUpEditorStateTestHarness();
52
53     [testHarness insertHTML:@"<b>first</b>" andExpectEditorStateWith:@{ @"bold": @YES }];
54     [testHarness toggleBold];
55     [testHarness insertText:@" second" andExpectEditorStateWith:@{ @"bold": @NO }];
56     [testHarness insertHTML:@"<span style='font-weight: 700'> third</span>" andExpectEditorStateWith:@{ @"bold": @YES }];
57     [testHarness insertHTML:@"<span style='font-weight: 300'> fourth</span>" andExpectEditorStateWith:@{ @"bold": @NO }];
58     [testHarness insertHTML:@"<span style='font-weight: 800'> fifth</span>" andExpectEditorStateWith:@{ @"bold": @YES }];
59     [testHarness insertHTML:@"<span style='font-weight: 400'> sixth</span>" andExpectEditorStateWith:@{ @"bold": @NO }];
60     [testHarness insertHTML:@"<span style='font-weight: 900'> seventh</span>" andExpectEditorStateWith:@{ @"bold": @YES }];
61     [testHarness toggleBold];
62     [testHarness insertText:@" eighth" andExpectEditorStateWith:@{ @"bold": @NO }];
63     [testHarness insertHTML:@"<strong> ninth</strong>" andExpectEditorStateWith:@{ @"bold": @YES }];
64     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"bold": @YES }];
65     [testHarness deleteBackwardAndExpectEditorStateWith:@{ @"bold": @YES }];
66     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @YES }];
67     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @NO }];
68     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @YES }];
69     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @NO }];
70     [testHarness selectAllAndExpectEditorStateWith:@{ @"bold": @YES }];
71     EXPECT_WK_STREQ("first second third fourth fifth sixth seventh eighth ninth", [[testHarness webView] stringByEvaluatingJavaScript:@"getSelection().toString()"]);
72 }
73
74 TEST(EditorStateTests, TypingAttributesItalic)
75 {
76     auto testHarness = setUpEditorStateTestHarness();
77
78     [testHarness insertHTML:@"<i>first</i>" andExpectEditorStateWith:@{ @"italic": @YES }];
79     [testHarness toggleItalic];
80     [testHarness insertText:@" second" andExpectEditorStateWith:@{ @"italic": @NO }];
81     [testHarness insertHTML:@"<span style='font-style: italic'> third</span>" andExpectEditorStateWith:@{ @"italic": @YES }];
82     [testHarness toggleItalic];
83     [testHarness insertText:@" fourth" andExpectEditorStateWith:@{ @"italic": @NO }];
84     [testHarness toggleItalic];
85     [testHarness insertText:@" fifth" andExpectEditorStateWith:@{ @"italic": @YES }];
86     [testHarness insertHTML:@"<span style='font-style: normal'> sixth</span>" andExpectEditorStateWith:@{ @"italic": @NO }];
87     [testHarness insertHTML:@"<span style='font-style: oblique'> seventh</span>" andExpectEditorStateWith:@{ @"italic": @YES }];
88     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"italic": @YES }];
89     [testHarness deleteBackwardAndExpectEditorStateWith:@{ @"italic": @YES }];
90     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @YES }];
91     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @NO }];
92     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @YES }];
93     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @NO }];
94
95     [testHarness selectAllAndExpectEditorStateWith:@{ @"italic": @YES }];
96     EXPECT_WK_STREQ("first second third fourth fifth sixth seventh", [[testHarness webView] stringByEvaluatingJavaScript:@"getSelection().toString()"]);
97 }
98
99 TEST(EditorStateTests, TypingAttributesUnderline)
100 {
101     auto testHarness = setUpEditorStateTestHarness();
102
103     [testHarness insertHTML:@"<u>first</u>" andExpectEditorStateWith:@{ @"underline": @YES }];
104     [testHarness toggleUnderline];
105     [testHarness insertText:@" second" andExpectEditorStateWith:@{ @"underline": @NO }];
106     [testHarness insertHTML:@"<span style='text-decoration: underline'> third</span>" andExpectEditorStateWith:@{ @"underline": @YES }];
107     [testHarness insertHTML:@"<span style='text-decoration: line-through'> fourth</span>" andExpectEditorStateWith:@{ @"underline": @NO }];
108     [testHarness insertHTML:@"<span style='text-decoration: underline overline line-through'> fifth</span>" andExpectEditorStateWith:@{ @"underline": @YES }];
109     [testHarness insertHTML:@"<span style='text-decoration: none'> sixth</span>" andExpectEditorStateWith:@{ @"underline": @NO }];
110     [testHarness toggleUnderline];
111     [testHarness insertText:@" seventh" andExpectEditorStateWith:@{ @"underline": @YES }];
112     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"underline": @YES }];
113     [testHarness deleteBackwardAndExpectEditorStateWith:@{ @"underline": @YES }];
114     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @YES }];
115     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @NO }];
116     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @YES }];
117     [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @NO }];
118
119     [testHarness selectAllAndExpectEditorStateWith:@{ @"underline": @YES }];
120     EXPECT_WK_STREQ("first second third fourth fifth sixth seventh", [[testHarness webView] stringByEvaluatingJavaScript:@"getSelection().toString()"]);
121 }
122
123 TEST(EditorStateTests, TypingAttributesTextAlignmentAbsoluteAlignmentOptions)
124 {
125     auto testHarness = setUpEditorStateTestHarness();
126     TestWKWebView *webView = [testHarness webView];
127
128     [webView stringByEvaluatingJavaScript:@"document.body.style.direction = 'ltr'"];
129
130     [testHarness insertHTML:@"<div style='text-align: right;'>right</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
131     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
132
133     [testHarness insertText:@"justified" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
134     [testHarness alignJustifiedAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentJustified) }];
135     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentJustified) }];
136
137     [testHarness alignCenterAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
138     [testHarness insertText:@"center" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
139     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
140
141     [testHarness insertHTML:@"<span id='left'>left</span>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
142     [webView stringByEvaluatingJavaScript:@"getSelection().setBaseAndExtent(left.childNodes[0], 0, left.childNodes[0], 6)"];
143     [testHarness alignLeftAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
144
145     [testHarness selectAllAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
146     EXPECT_WK_STREQ("right\njustified\ncenter\nleft", [webView stringByEvaluatingJavaScript:@"getSelection().toString()"]);
147 }
148
149 TEST(EditorStateTests, TypingAttributesTextAlignmentStartEnd)
150 {
151     auto testHarness = setUpEditorStateTestHarness();
152     TestWKWebView *webView = [testHarness webView];
153
154     [webView stringByEvaluatingJavaScript:@"document.styleSheets[0].insertRule('.start { text-align: start; }')"];
155     [webView stringByEvaluatingJavaScript:@"document.styleSheets[0].insertRule('.end { text-align: end; }')"];
156     [webView stringByEvaluatingJavaScript:@"document.body.style.direction = 'rtl'"];
157
158     [testHarness insertHTML:@"<div class='start'>rtl start</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
159     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
160
161     [testHarness insertHTML:@"<div class='end'>rtl end</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
162     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
163
164     [[testHarness webView] stringByEvaluatingJavaScript:@"document.body.style.direction = 'ltr'"];
165     [testHarness insertHTML:@"<div class='start'>ltr start</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
166     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
167
168     [testHarness insertHTML:@"<div class='end'>ltr end</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
169     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
170 }
171
172 TEST(EditorStateTests, TypingAttributesTextAlignmentDirectionalText)
173 {
174     auto testHarness = setUpEditorStateTestHarness();
175     [[testHarness webView] stringByEvaluatingJavaScript:@"document.body.setAttribute('dir', 'auto')"];
176
177     [testHarness insertHTML:@"<div>מקור השם עברית</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
178     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
179     [testHarness insertHTML:@"<div dir='ltr'>מקור השם עברית</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
180     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
181     [testHarness insertHTML:@"<div dir='rtl'>מקור השם עברית</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
182     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
183
184     [testHarness insertHTML:@"<div dir='auto'>This is English text</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
185     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
186     [testHarness insertHTML:@"<div dir='rtl'>This is English text</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
187     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
188     [testHarness insertHTML:@"<div dir='ltr'>This is English text</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
189     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
190 }
191
192 TEST(EditorStateTests, TypingAttributesTextColor)
193 {
194     auto testHarness = setUpEditorStateTestHarness();
195
196     [testHarness setForegroundColor:@"rgb(255, 0, 0)"];
197     [testHarness insertText:@"red" andExpectEditorStateWith:@{ @"text-color": @"rgb(255, 0, 0)" }];
198
199     [testHarness insertHTML:@"<span style='color: rgb(0, 255, 0)'>green</span>" andExpectEditorStateWith:@{ @"text-color": @"rgb(0, 255, 0)" }];
200     [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-color": @"rgb(0, 255, 0)" }];
201
202     [testHarness setForegroundColor:@"rgb(0, 0, 255)"];
203     [testHarness insertText:@"blue" andExpectEditorStateWith:@{ @"text-color": @"rgb(0, 0, 255)" }];
204 }
205
206 TEST(EditorStateTests, TypingAttributesMixedStyles)
207 {
208     auto testHarness = setUpEditorStateTestHarness();
209
210     [testHarness alignCenterAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
211     [testHarness setForegroundColor:@"rgb(128, 128, 128)"];
212     [testHarness toggleBold];
213     [testHarness toggleItalic];
214     [testHarness toggleUnderline];
215     NSDictionary *expectedAttributes = @{
216         @"bold": @YES,
217         @"italic": @YES,
218         @"underline": @YES,
219         @"text-color": @"rgb(128, 128, 128)",
220         @"text-alignment": @(NSTextAlignmentCenter)
221     };
222     BOOL containsProperties = [testHarness latestEditorStateContains:expectedAttributes];
223     EXPECT_TRUE(containsProperties);
224     if (!containsProperties)
225         NSLog(@"Expected %@ to contain %@", [testHarness latestEditorState], expectedAttributes);
226 }
227
228 TEST(EditorStateTests, TypingAttributeLinkColor)
229 {
230     auto testHarness = setUpEditorStateTestHarness();
231     [testHarness insertHTML:@"<a href='https://www.apple.com/'>This is a link</a>" andExpectEditorStateWith:@{ @"text-color": @"rgb(0, 0, 238)" }];
232     [testHarness selectAllAndExpectEditorStateWith:@{ @"text-color": @"rgb(0, 0, 238)" }];
233     EXPECT_WK_STREQ("https://www.apple.com/", [[testHarness webView] stringByEvaluatingJavaScript:@"document.querySelector('a').href"]);
234 }
235
236 #if PLATFORM(IOS)
237
238 static void checkContentViewHasTextWithFailureDescription(TestWKWebView *webView, BOOL expectedToHaveText, NSString *description)
239 {
240     BOOL hasText = webView.textInputContentView.hasText;
241     if (expectedToHaveText)
242         EXPECT_TRUE(hasText);
243     else
244         EXPECT_FALSE(hasText);
245
246     if (expectedToHaveText != hasText)
247         NSLog(@"Expected -[%@ hasText] to be %@, but observed: %@ (%@)", [webView.textInputContentView class], expectedToHaveText ? @"YES" : @"NO", hasText ? @"YES" : @"NO", description);
248 }
249
250 TEST(EditorStateTests, ContentViewHasTextInContentEditableElement)
251 {
252     auto testHarness = setUpEditorStateTestHarness();
253     TestWKWebView *webView = [testHarness webView];
254
255     checkContentViewHasTextWithFailureDescription(webView, NO, @"before inserting any content");
256     [testHarness insertHTML:@"<img src='icon.png'></img>"];
257     checkContentViewHasTextWithFailureDescription(webView, NO, @"after inserting an image element");
258     [testHarness insertText:@"A"];
259     checkContentViewHasTextWithFailureDescription(webView, YES, @"after inserting text");
260     [testHarness selectAll];
261     checkContentViewHasTextWithFailureDescription(webView, YES, @"after selecting everything");
262     [testHarness deleteBackwards];
263     checkContentViewHasTextWithFailureDescription(webView, NO, @"after deleting everything");
264     [testHarness insertParagraph];
265     checkContentViewHasTextWithFailureDescription(webView, YES, @"after inserting a newline");
266     [testHarness deleteBackwards];
267     checkContentViewHasTextWithFailureDescription(webView, NO, @"after deleting the newline");
268     [testHarness insertText:@"B"];
269     checkContentViewHasTextWithFailureDescription(webView, YES, @"after inserting text again");
270     [webView stringByEvaluatingJavaScript:@"document.body.blur()"];
271     [webView waitForNextPresentationUpdate];
272     checkContentViewHasTextWithFailureDescription(webView, NO, @"after losing focus");
273 }
274
275 TEST(EditorStateTests, ContentViewHasTextInTextarea)
276 {
277     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
278     auto testHarness = adoptNS([[EditingTestHarness alloc] initWithWebView:webView.get()]);
279     [webView synchronouslyLoadHTMLString:@"<textarea id='textarea'></textarea>"];
280     [webView stringByEvaluatingJavaScript:@"textarea.focus()"];
281     [webView waitForNextPresentationUpdate];
282
283     checkContentViewHasTextWithFailureDescription(webView.get(), NO, @"before inserting any content");
284     [testHarness insertText:@"A"];
285     checkContentViewHasTextWithFailureDescription(webView.get(), YES, @"after inserting text");
286     [testHarness selectAll];
287     checkContentViewHasTextWithFailureDescription(webView.get(), YES, @"after selecting everything");
288     [testHarness deleteBackwards];
289     checkContentViewHasTextWithFailureDescription(webView.get(), NO, @"after deleting everything");
290     [testHarness insertParagraph];
291     checkContentViewHasTextWithFailureDescription(webView.get(), YES, @"after inserting a newline");
292     [testHarness deleteBackwards];
293     checkContentViewHasTextWithFailureDescription(webView.get(), NO, @"after deleting the newline");
294     [testHarness insertText:@"B"];
295     checkContentViewHasTextWithFailureDescription(webView.get(), YES, @"after inserting text again");
296     [webView stringByEvaluatingJavaScript:@"textarea.blur()"];
297     [webView waitForNextPresentationUpdate];
298     checkContentViewHasTextWithFailureDescription(webView.get(), NO, @"after losing focus");
299 }
300
301 #endif
302
303 } // namespace TestWebKitAPI
304
305 #endif // WK_API_ENABLED