Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1021)

Side by Side Diff: ios/chrome/browser/autofill/js_suggestion_manager_unittest.mm

Issue 2580363002: Upstream Chrome on iOS source code [1/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "components/autofill/ios/browser/js_suggestion_manager.h"
6
7 #import <Foundation/Foundation.h>
8
9 #include "base/strings/sys_string_conversions.h"
10 #import "base/test/ios/wait_util.h"
11 #include "ios/chrome/browser/web/chrome_web_test.h"
12 #import "ios/web/public/test/js_test_util.h"
13 #import "ios/web/public/test/web_js_test.h"
14 #import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
15 #import "ios/web/public/web_state/web_state.h"
16 #import "testing/gtest_mac.h"
17
18 #if !defined(__has_feature) || !__has_feature(objc_arc)
19 #error "This file requires ARC support."
20 #endif
21
22 namespace {
23
24 // Test fixture to test suggestions.
25 class JsSuggestionManagerTest : public web::WebJsTest<ChromeWebTest> {
26 protected:
27 JsSuggestionManagerTest()
28 : web::WebJsTest<ChromeWebTest>(@[ @"suggestion_controller" ]) {}
29 // Loads the given HTML and initializes the Autofill JS scripts.
30 void LoadHtml(NSString* html);
31 // Helper method that initializes a form with three fields. Can be used to
32 // test whether adding an attribute on the second field causes it to be
33 // skipped (or not, as is appropriate) by selectNextElement.
34 void SequentialNavigationSkipCheck(NSString* attribute, BOOL shouldSkip);
35 // Returns the active element name from the JS side.
36 NSString* GetActiveElementName() {
37 return ExecuteJavaScript(@"document.activeElement.name");
38 }
39 JsSuggestionManager* manager_;
40 };
41
42 void JsSuggestionManagerTest::LoadHtml(NSString* html) {
43 WebJsTest<ChromeWebTest>::LoadHtml(html);
44 manager_ =
45 static_cast<JsSuggestionManager*>([web_state()->GetJSInjectionReceiver()
46 instanceOfClass:[JsSuggestionManager class]]);
47 [manager_ inject];
48 }
49
50 TEST_F(JsSuggestionManagerTest, InitAndInject) {
51 LoadHtml(@"<html></html>");
52 EXPECT_TRUE([manager_ hasBeenInjected]);
53 }
54
55 TEST_F(JsSuggestionManagerTest, SelectElementInTabOrder) {
56 NSString* htmlFragment =
57 @"<html> <body>"
58 "<input id='1 (0)' tabIndex=1 href='http://www.w3schools.com'>1 (0)</a>"
59 "<input id='0 (0)' tabIndex=0 href='http://www.w3schools.com'>0 (0)</a>"
60 "<input id='2' tabIndex=2 href='http://www.w3schools.com'>2</a>"
61 "<input id='0 (1)' tabIndex=0 href='http://www.w3schools.com'>0 (1)</a>"
62 "<input id='-2' tabIndex=-2 href='http://www.w3schools.com'>-2</a>"
63 "<a href='http://www.w3schools.com'></a>"
64 "<input id='-1 (0)' tabIndex=-1 href='http://www.w3schools.com'>-1</a>"
65 "<input id='-2 (2)' tabIndex=-2 href='http://www.w3schools.com'>-2</a>"
66 "<input id='0 (2)' tabIndex=0 href='http://www.w3schools.com'>0 - 2</a>"
67 "<input id='3' tabIndex=3 href='http://www.w3schools.com'>3</a>"
68 "<input id='1 (1)' tabIndex=1 href='http://www.w3schools.com'>1 (1)</a>"
69 "<input id='-1 (1)' tabIndex=-1 href='http://www.w3schools.com'>-1 </a>"
70 "<input id='0 (3)' tabIndex=0 href='http://www.w3schools.com'>0 (3)</a>"
71 "</body></html>";
72 LoadHtmlAndInject(htmlFragment);
73
74 // clang-format off
75 NSDictionary* next_expected_ids = @ {
76 @"1 (0)" : @"1 (1)",
77 @"0 (0)" : @"0 (1)",
78 @"2" : @"3",
79 @"0 (1)" : @"0 (2)",
80 @"-2" : @"0 (2)",
81 @"-1 (0)" : @"0 (2)",
82 @"-2 (2)" : @"0 (2)",
83 @"0 (2)" : @"0 (3)",
84 @"3" : @"0 (0)",
85 @"1 (1)" : @"2",
86 @"-1 (1)" : @"0 (3)",
87 @"0 (3)" : @"null"
88 };
89 // clang-format on
90
91 for (NSString* element_id : next_expected_ids) {
92 NSString* expected_id = [next_expected_ids objectForKey:element_id];
93 EXPECT_NSEQ(expected_id,
94 ExecuteJavaScriptWithFormat(
95 @"var elements=document.getElementsByTagName('input');"
96 "var element=document.getElementById('%@');"
97 "var next = __gCrWeb.suggestion.getNextElementInTabOrder("
98 " element, elements);"
99 "next ? next.id : 'null';",
100 element_id))
101 << "Wrong when selecting next element of element with element id "
102 << base::SysNSStringToUTF8(element_id);
103 }
104 EXPECT_NSEQ(@YES,
105 ExecuteJavaScriptWithFormat(
106 @"var elements=document.getElementsByTagName('input');"
107 "var element=document.getElementsByTagName('a')[0];"
108 "var next = __gCrWeb.suggestion.getNextElementInTabOrder("
109 " element, elements); next===null"))
110 << "Wrong when selecting the next element of an element not in the "
111 << "element list.";
112
113 for (NSString* element_id : next_expected_ids) {
114 NSString* expected_id = [next_expected_ids objectForKey:element_id];
115 if ([expected_id isEqualToString:@"null"]) {
116 // If the expected next element is null, the focus is not moved.
117 expected_id = element_id;
118 }
119 EXPECT_NSEQ(expected_id, ExecuteJavaScriptWithFormat(
120 @"document.getElementById('%@').focus();"
121 "__gCrWeb.suggestion.selectNextElement();"
122 "document.activeElement.id",
123 element_id))
124 << "Wrong when selecting next element with active element "
125 << base::SysNSStringToUTF8(element_id);
126 }
127
128 for (NSString* element_id : next_expected_ids) {
129 // If the expected next element is null, there is no next element.
130 BOOL expected = ![next_expected_ids[element_id] isEqualToString:@"null"];
131 EXPECT_NSEQ(@(expected), ExecuteJavaScriptWithFormat(
132 @"document.getElementById('%@').focus();"
133 "__gCrWeb.suggestion.hasNextElement()",
134 element_id))
135 << "Wrong when checking hasNextElement() for "
136 << base::SysNSStringToUTF8(element_id);
137 }
138
139 // clang-format off
140 NSDictionary* prev_expected_ids = @{
141 @"1 (0)" : @"null",
142 @"0 (0)" : @"3",
143 @"2" : @"1 (1)",
144 @"0 (1)" : @"0 (0)",
145 @"-2" : @"0 (1)",
146 @"-1 (0)": @"0 (1)",
147 @"-2 (2)": @"0 (1)",
148 @"0 (2)" : @"0 (1)",
149 @"3" : @"2",
150 @"1 (1)" : @"1 (0)",
151 @"-1 (1)": @"1 (1)",
152 @"0 (3)" : @"0 (2)",
153 };
154 // clang-format on
155
156 for (NSString* element_id : prev_expected_ids) {
157 NSString* expected_id = [prev_expected_ids objectForKey:element_id];
158 EXPECT_NSEQ(
159 expected_id,
160 ExecuteJavaScriptWithFormat(
161 @"var elements=document.getElementsByTagName('input');"
162 "var element=document.getElementById('%@');"
163 "var prev = __gCrWeb.suggestion.getPreviousElementInTabOrder("
164 " element, elements);"
165 "prev ? prev.id : 'null';",
166 element_id))
167 << "Wrong when selecting prev element of element with element id "
168 << base::SysNSStringToUTF8(element_id);
169 }
170 EXPECT_NSEQ(
171 @YES, ExecuteJavaScriptWithFormat(
172 @"var elements=document.getElementsByTagName('input');"
173 "var element=document.getElementsByTagName('a')[0];"
174 "var prev = __gCrWeb.suggestion.getPreviousElementInTabOrder("
175 " element, elements); prev===null"))
176 << "Wrong when selecting the previous element of an element not in the "
177 << "element list";
178
179 for (NSString* element_id : prev_expected_ids) {
180 NSString* expected_id = [prev_expected_ids objectForKey:element_id];
181 if ([expected_id isEqualToString:@"null"]) {
182 // If the expected previous element is null, the focus is not moved.
183 expected_id = element_id;
184 }
185 EXPECT_NSEQ(expected_id, ExecuteJavaScriptWithFormat(
186 @"document.getElementById('%@').focus();"
187 "__gCrWeb.suggestion.selectPreviousElement();"
188 "document.activeElement.id",
189 element_id))
190 << "Wrong when selecting previous element with active element "
191 << base::SysNSStringToUTF8(element_id);
192 }
193
194 for (NSString* element_id : prev_expected_ids) {
195 // If the expected next element is null, there is no next element.
196 BOOL expected = ![prev_expected_ids[element_id] isEqualToString:@"null"];
197 EXPECT_NSEQ(@(expected), ExecuteJavaScriptWithFormat(
198 @"document.getElementById('%@').focus();"
199 "__gCrWeb.suggestion.hasPreviousElement()",
200 element_id))
201 << "Wrong when checking hasPreviousElement() for "
202 << base::SysNSStringToUTF8(element_id);
203 }
204 }
205
206 TEST_F(JsSuggestionManagerTest, SequentialNavigation) {
207 LoadHtml(@"<html><body><form name='testform' method='post'>"
208 "<input type='text' name='firstname'/>"
209 "<input type='text' name='lastname'/>"
210 "<input type='email' name='email'/>"
211 "</form></body></html>");
212
213 [manager_
214 executeJavaScript:@"document.getElementsByName('firstname')[0].focus()"
215 completionHandler:nil];
216 [manager_ selectNextElement];
217 EXPECT_NSEQ(@"lastname", GetActiveElementName());
218 __block BOOL block_was_called = NO;
219 [manager_ fetchPreviousAndNextElementsPresenceWithCompletionHandler:^void(
220 BOOL has_previous_element, BOOL has_next_element) {
221 block_was_called = YES;
222 EXPECT_TRUE(has_previous_element);
223 EXPECT_TRUE(has_next_element);
224 }];
225 base::test::ios::WaitUntilCondition(^bool() {
226 return block_was_called;
227 });
228 [manager_ selectNextElement];
229 EXPECT_NSEQ(@"email", GetActiveElementName());
230 [manager_ selectPreviousElement];
231 EXPECT_NSEQ(@"lastname", GetActiveElementName());
232 }
233
234 void JsSuggestionManagerTest::SequentialNavigationSkipCheck(NSString* attribute,
235 BOOL shouldSkip) {
236 LoadHtml([NSString stringWithFormat:@"<html><body>"
237 "<form name='testform' method='post'>"
238 "<input type='text' name='firstname'/>"
239 "<%@ name='middlename'/>"
240 "<input type='text' name='lastname'/>"
241 "</form></body></html>",
242 attribute]);
243 [manager_
244 executeJavaScript:@"document.getElementsByName('firstname')[0].focus()"
245 completionHandler:nil];
246 NSString* const kActiveElementNameJS = @"document.activeElement.name";
247 EXPECT_NSEQ(@"firstname",
248 web::ExecuteJavaScript(manager_, kActiveElementNameJS));
249 [manager_ selectNextElement];
250 NSString* activeElementNameJS = GetActiveElementName();
251 if (shouldSkip)
252 EXPECT_NSEQ(@"lastname", activeElementNameJS);
253 else
254 EXPECT_NSEQ(@"middlename", activeElementNameJS);
255 }
256
257 TEST_F(JsSuggestionManagerTest, SequentialNavigationNoSkipText) {
258 SequentialNavigationSkipCheck(@"input type='text'", NO);
259 }
260
261 TEST_F(JsSuggestionManagerTest, SequentialNavigationNoSkipTextArea) {
262 SequentialNavigationSkipCheck(@"input type='textarea'", NO);
263 }
264
265 TEST_F(JsSuggestionManagerTest, SequentialNavigationOverInvisibleElement) {
266 SequentialNavigationSkipCheck(@"input type='text' style='display:none'", YES);
267 }
268
269 TEST_F(JsSuggestionManagerTest, SequentialNavigationOverHiddenElement) {
270 SequentialNavigationSkipCheck(@"input type='text' style='visibility:hidden'",
271 YES);
272 }
273
274 TEST_F(JsSuggestionManagerTest, SequentialNavigationOverDisabledElement) {
275 SequentialNavigationSkipCheck(@"type='text' disabled", YES);
276 }
277
278 TEST_F(JsSuggestionManagerTest, SequentialNavigationNoSkipPassword) {
279 SequentialNavigationSkipCheck(@"input type='password'", NO);
280 }
281
282 TEST_F(JsSuggestionManagerTest, SequentialNavigationSkipSubmit) {
283 SequentialNavigationSkipCheck(@"input type='submit'", YES);
284 }
285
286 TEST_F(JsSuggestionManagerTest, SequentialNavigationSkipImage) {
287 SequentialNavigationSkipCheck(@"input type='image'", YES);
288 }
289
290 TEST_F(JsSuggestionManagerTest, SequentialNavigationSkipButton) {
291 SequentialNavigationSkipCheck(@"input type='button'", YES);
292 }
293
294 TEST_F(JsSuggestionManagerTest, SequentialNavigationSkipRange) {
295 SequentialNavigationSkipCheck(@"input type='range'", YES);
296 }
297
298 TEST_F(JsSuggestionManagerTest, SequentialNavigationSkipRadio) {
299 SequentialNavigationSkipCheck(@"type='radio'", YES);
300 }
301
302 TEST_F(JsSuggestionManagerTest, SequentialNavigationSkipCheckbox) {
303 SequentialNavigationSkipCheck(@"type='checkbox'", YES);
304 }
305
306 // Special test for a condition where the closeKeyboard script would cause an
307 // illegal JS recursion if a blur event results in an event that triggers a
308 // crwebinvoke:// back, such as a page change.
309 TEST_F(JsSuggestionManagerTest, CloseKeyboardSafetyTest) {
310 LoadHtml(@"<select id='select'>Select</select>");
311 ExecuteJavaScript(
312 @"select.onblur = function(){window.location.href = '#test'}");
313 ExecuteJavaScript(@"select.focus()");
314 // In the failure condition the app will crash during the next line.
315 [manager_ closeKeyboard];
316 // TODO(crbug.com/661624): add a check for the keyboard actually being
317 // dismissed; unfortunately it is not known how to adapt
318 // WaitForBackgroundTasks to yield for events wrapped with window.setTimeout()
319 // or other deferred events.
320 }
321
322 // Test fixture to test
323 // |fetchPreviousAndNextElementsPresenceWithCompletionHandler|.
324 class FetchPreviousAndNextExceptionTest : public JsSuggestionManagerTest {
325 public:
326 void SetUp() override {
327 JsSuggestionManagerTest::SetUp();
328 LoadHtml(@"<html></html>");
329 }
330
331 protected:
332 // Evaluates JS and tests that the completion handler passed to
333 // |fetchPreviousAndNextElementsPresenceWithCompletionHandler| is called with
334 // (NO, NO) indicating no previous and next element.
335 void EvaluateJavaScriptAndExpectNoPreviousAndNextElement(NSString* js) {
336 ExecuteJavaScript(js);
337 __block BOOL block_was_called = NO;
338 id completionHandler = ^(BOOL hasPreviousElement, BOOL hasNextElement) {
339 EXPECT_FALSE(hasPreviousElement);
340 EXPECT_FALSE(hasNextElement);
341 block_was_called = YES;
342 };
343 [manager_ fetchPreviousAndNextElementsPresenceWithCompletionHandler:
344 completionHandler];
345 base::test::ios::WaitUntilCondition(^bool() {
346 return block_was_called;
347 });
348 }
349 };
350
351 // Tests that |fetchPreviousAndNextElementsPresenceWithCompletionHandler| works
352 // when |__gCrWeb.suggestion.hasPreviousElement| throws an exception.
353 TEST_F(FetchPreviousAndNextExceptionTest, HasPreviousElementException) {
354 EvaluateJavaScriptAndExpectNoPreviousAndNextElement(
355 @"__gCrWeb.suggestion.hasPreviousElement = function() { bar.foo1; }");
356 }
357
358 // Tests that |fetchPreviousAndNextElementsPresenceWithCompletionHandler| works
359 // when |__gCrWeb.suggestion.hasNextElement| throws an exception.
360 TEST_F(FetchPreviousAndNextExceptionTest, HasNextElementException) {
361 EvaluateJavaScriptAndExpectNoPreviousAndNextElement(
362 @"__gCrWeb.suggestion.hasNextElement = function() { bar.foo1; }");
363 }
364
365 // Tests that |fetchPreviousAndNextElementsPresenceWithCompletionHandler| works
366 // when |Array.toString| has been overridden to return a malformed string
367 // without a ",".
368 TEST_F(FetchPreviousAndNextExceptionTest, HasPreviousElementNull) {
369 EvaluateJavaScriptAndExpectNoPreviousAndNextElement(
370 @"Array.prototype.toString = function() { return 'Hello'; }");
371 }
372
373 } // namespace
OLDNEW
« no previous file with comments | « ios/chrome/browser/autofill/js_autofill_manager_unittest.mm ('k') | ios/chrome/browser/browser_coordinator.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698