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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/autofill/js_suggestion_manager_unittest.mm
diff --git a/ios/chrome/browser/autofill/js_suggestion_manager_unittest.mm b/ios/chrome/browser/autofill/js_suggestion_manager_unittest.mm
new file mode 100644
index 0000000000000000000000000000000000000000..7f9c20ce3322763faa8f8d83a9c9638a43760a19
--- /dev/null
+++ b/ios/chrome/browser/autofill/js_suggestion_manager_unittest.mm
@@ -0,0 +1,373 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "components/autofill/ios/browser/js_suggestion_manager.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/strings/sys_string_conversions.h"
+#import "base/test/ios/wait_util.h"
+#include "ios/chrome/browser/web/chrome_web_test.h"
+#import "ios/web/public/test/js_test_util.h"
+#import "ios/web/public/test/web_js_test.h"
+#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
+#import "ios/web/public/web_state/web_state.h"
+#import "testing/gtest_mac.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+// Test fixture to test suggestions.
+class JsSuggestionManagerTest : public web::WebJsTest<ChromeWebTest> {
+ protected:
+ JsSuggestionManagerTest()
+ : web::WebJsTest<ChromeWebTest>(@[ @"suggestion_controller" ]) {}
+ // Loads the given HTML and initializes the Autofill JS scripts.
+ void LoadHtml(NSString* html);
+ // Helper method that initializes a form with three fields. Can be used to
+ // test whether adding an attribute on the second field causes it to be
+ // skipped (or not, as is appropriate) by selectNextElement.
+ void SequentialNavigationSkipCheck(NSString* attribute, BOOL shouldSkip);
+ // Returns the active element name from the JS side.
+ NSString* GetActiveElementName() {
+ return ExecuteJavaScript(@"document.activeElement.name");
+ }
+ JsSuggestionManager* manager_;
+};
+
+void JsSuggestionManagerTest::LoadHtml(NSString* html) {
+ WebJsTest<ChromeWebTest>::LoadHtml(html);
+ manager_ =
+ static_cast<JsSuggestionManager*>([web_state()->GetJSInjectionReceiver()
+ instanceOfClass:[JsSuggestionManager class]]);
+ [manager_ inject];
+}
+
+TEST_F(JsSuggestionManagerTest, InitAndInject) {
+ LoadHtml(@"<html></html>");
+ EXPECT_TRUE([manager_ hasBeenInjected]);
+}
+
+TEST_F(JsSuggestionManagerTest, SelectElementInTabOrder) {
+ NSString* htmlFragment =
+ @"<html> <body>"
+ "<input id='1 (0)' tabIndex=1 href='http://www.w3schools.com'>1 (0)</a>"
+ "<input id='0 (0)' tabIndex=0 href='http://www.w3schools.com'>0 (0)</a>"
+ "<input id='2' tabIndex=2 href='http://www.w3schools.com'>2</a>"
+ "<input id='0 (1)' tabIndex=0 href='http://www.w3schools.com'>0 (1)</a>"
+ "<input id='-2' tabIndex=-2 href='http://www.w3schools.com'>-2</a>"
+ "<a href='http://www.w3schools.com'></a>"
+ "<input id='-1 (0)' tabIndex=-1 href='http://www.w3schools.com'>-1</a>"
+ "<input id='-2 (2)' tabIndex=-2 href='http://www.w3schools.com'>-2</a>"
+ "<input id='0 (2)' tabIndex=0 href='http://www.w3schools.com'>0 - 2</a>"
+ "<input id='3' tabIndex=3 href='http://www.w3schools.com'>3</a>"
+ "<input id='1 (1)' tabIndex=1 href='http://www.w3schools.com'>1 (1)</a>"
+ "<input id='-1 (1)' tabIndex=-1 href='http://www.w3schools.com'>-1 </a>"
+ "<input id='0 (3)' tabIndex=0 href='http://www.w3schools.com'>0 (3)</a>"
+ "</body></html>";
+ LoadHtmlAndInject(htmlFragment);
+
+ // clang-format off
+ NSDictionary* next_expected_ids = @ {
+ @"1 (0)" : @"1 (1)",
+ @"0 (0)" : @"0 (1)",
+ @"2" : @"3",
+ @"0 (1)" : @"0 (2)",
+ @"-2" : @"0 (2)",
+ @"-1 (0)" : @"0 (2)",
+ @"-2 (2)" : @"0 (2)",
+ @"0 (2)" : @"0 (3)",
+ @"3" : @"0 (0)",
+ @"1 (1)" : @"2",
+ @"-1 (1)" : @"0 (3)",
+ @"0 (3)" : @"null"
+ };
+ // clang-format on
+
+ for (NSString* element_id : next_expected_ids) {
+ NSString* expected_id = [next_expected_ids objectForKey:element_id];
+ EXPECT_NSEQ(expected_id,
+ ExecuteJavaScriptWithFormat(
+ @"var elements=document.getElementsByTagName('input');"
+ "var element=document.getElementById('%@');"
+ "var next = __gCrWeb.suggestion.getNextElementInTabOrder("
+ " element, elements);"
+ "next ? next.id : 'null';",
+ element_id))
+ << "Wrong when selecting next element of element with element id "
+ << base::SysNSStringToUTF8(element_id);
+ }
+ EXPECT_NSEQ(@YES,
+ ExecuteJavaScriptWithFormat(
+ @"var elements=document.getElementsByTagName('input');"
+ "var element=document.getElementsByTagName('a')[0];"
+ "var next = __gCrWeb.suggestion.getNextElementInTabOrder("
+ " element, elements); next===null"))
+ << "Wrong when selecting the next element of an element not in the "
+ << "element list.";
+
+ for (NSString* element_id : next_expected_ids) {
+ NSString* expected_id = [next_expected_ids objectForKey:element_id];
+ if ([expected_id isEqualToString:@"null"]) {
+ // If the expected next element is null, the focus is not moved.
+ expected_id = element_id;
+ }
+ EXPECT_NSEQ(expected_id, ExecuteJavaScriptWithFormat(
+ @"document.getElementById('%@').focus();"
+ "__gCrWeb.suggestion.selectNextElement();"
+ "document.activeElement.id",
+ element_id))
+ << "Wrong when selecting next element with active element "
+ << base::SysNSStringToUTF8(element_id);
+ }
+
+ for (NSString* element_id : next_expected_ids) {
+ // If the expected next element is null, there is no next element.
+ BOOL expected = ![next_expected_ids[element_id] isEqualToString:@"null"];
+ EXPECT_NSEQ(@(expected), ExecuteJavaScriptWithFormat(
+ @"document.getElementById('%@').focus();"
+ "__gCrWeb.suggestion.hasNextElement()",
+ element_id))
+ << "Wrong when checking hasNextElement() for "
+ << base::SysNSStringToUTF8(element_id);
+ }
+
+ // clang-format off
+ NSDictionary* prev_expected_ids = @{
+ @"1 (0)" : @"null",
+ @"0 (0)" : @"3",
+ @"2" : @"1 (1)",
+ @"0 (1)" : @"0 (0)",
+ @"-2" : @"0 (1)",
+ @"-1 (0)": @"0 (1)",
+ @"-2 (2)": @"0 (1)",
+ @"0 (2)" : @"0 (1)",
+ @"3" : @"2",
+ @"1 (1)" : @"1 (0)",
+ @"-1 (1)": @"1 (1)",
+ @"0 (3)" : @"0 (2)",
+ };
+ // clang-format on
+
+ for (NSString* element_id : prev_expected_ids) {
+ NSString* expected_id = [prev_expected_ids objectForKey:element_id];
+ EXPECT_NSEQ(
+ expected_id,
+ ExecuteJavaScriptWithFormat(
+ @"var elements=document.getElementsByTagName('input');"
+ "var element=document.getElementById('%@');"
+ "var prev = __gCrWeb.suggestion.getPreviousElementInTabOrder("
+ " element, elements);"
+ "prev ? prev.id : 'null';",
+ element_id))
+ << "Wrong when selecting prev element of element with element id "
+ << base::SysNSStringToUTF8(element_id);
+ }
+ EXPECT_NSEQ(
+ @YES, ExecuteJavaScriptWithFormat(
+ @"var elements=document.getElementsByTagName('input');"
+ "var element=document.getElementsByTagName('a')[0];"
+ "var prev = __gCrWeb.suggestion.getPreviousElementInTabOrder("
+ " element, elements); prev===null"))
+ << "Wrong when selecting the previous element of an element not in the "
+ << "element list";
+
+ for (NSString* element_id : prev_expected_ids) {
+ NSString* expected_id = [prev_expected_ids objectForKey:element_id];
+ if ([expected_id isEqualToString:@"null"]) {
+ // If the expected previous element is null, the focus is not moved.
+ expected_id = element_id;
+ }
+ EXPECT_NSEQ(expected_id, ExecuteJavaScriptWithFormat(
+ @"document.getElementById('%@').focus();"
+ "__gCrWeb.suggestion.selectPreviousElement();"
+ "document.activeElement.id",
+ element_id))
+ << "Wrong when selecting previous element with active element "
+ << base::SysNSStringToUTF8(element_id);
+ }
+
+ for (NSString* element_id : prev_expected_ids) {
+ // If the expected next element is null, there is no next element.
+ BOOL expected = ![prev_expected_ids[element_id] isEqualToString:@"null"];
+ EXPECT_NSEQ(@(expected), ExecuteJavaScriptWithFormat(
+ @"document.getElementById('%@').focus();"
+ "__gCrWeb.suggestion.hasPreviousElement()",
+ element_id))
+ << "Wrong when checking hasPreviousElement() for "
+ << base::SysNSStringToUTF8(element_id);
+ }
+}
+
+TEST_F(JsSuggestionManagerTest, SequentialNavigation) {
+ LoadHtml(@"<html><body><form name='testform' method='post'>"
+ "<input type='text' name='firstname'/>"
+ "<input type='text' name='lastname'/>"
+ "<input type='email' name='email'/>"
+ "</form></body></html>");
+
+ [manager_
+ executeJavaScript:@"document.getElementsByName('firstname')[0].focus()"
+ completionHandler:nil];
+ [manager_ selectNextElement];
+ EXPECT_NSEQ(@"lastname", GetActiveElementName());
+ __block BOOL block_was_called = NO;
+ [manager_ fetchPreviousAndNextElementsPresenceWithCompletionHandler:^void(
+ BOOL has_previous_element, BOOL has_next_element) {
+ block_was_called = YES;
+ EXPECT_TRUE(has_previous_element);
+ EXPECT_TRUE(has_next_element);
+ }];
+ base::test::ios::WaitUntilCondition(^bool() {
+ return block_was_called;
+ });
+ [manager_ selectNextElement];
+ EXPECT_NSEQ(@"email", GetActiveElementName());
+ [manager_ selectPreviousElement];
+ EXPECT_NSEQ(@"lastname", GetActiveElementName());
+}
+
+void JsSuggestionManagerTest::SequentialNavigationSkipCheck(NSString* attribute,
+ BOOL shouldSkip) {
+ LoadHtml([NSString stringWithFormat:@"<html><body>"
+ "<form name='testform' method='post'>"
+ "<input type='text' name='firstname'/>"
+ "<%@ name='middlename'/>"
+ "<input type='text' name='lastname'/>"
+ "</form></body></html>",
+ attribute]);
+ [manager_
+ executeJavaScript:@"document.getElementsByName('firstname')[0].focus()"
+ completionHandler:nil];
+ NSString* const kActiveElementNameJS = @"document.activeElement.name";
+ EXPECT_NSEQ(@"firstname",
+ web::ExecuteJavaScript(manager_, kActiveElementNameJS));
+ [manager_ selectNextElement];
+ NSString* activeElementNameJS = GetActiveElementName();
+ if (shouldSkip)
+ EXPECT_NSEQ(@"lastname", activeElementNameJS);
+ else
+ EXPECT_NSEQ(@"middlename", activeElementNameJS);
+}
+
+TEST_F(JsSuggestionManagerTest, SequentialNavigationNoSkipText) {
+ SequentialNavigationSkipCheck(@"input type='text'", NO);
+}
+
+TEST_F(JsSuggestionManagerTest, SequentialNavigationNoSkipTextArea) {
+ SequentialNavigationSkipCheck(@"input type='textarea'", NO);
+}
+
+TEST_F(JsSuggestionManagerTest, SequentialNavigationOverInvisibleElement) {
+ SequentialNavigationSkipCheck(@"input type='text' style='display:none'", YES);
+}
+
+TEST_F(JsSuggestionManagerTest, SequentialNavigationOverHiddenElement) {
+ SequentialNavigationSkipCheck(@"input type='text' style='visibility:hidden'",
+ YES);
+}
+
+TEST_F(JsSuggestionManagerTest, SequentialNavigationOverDisabledElement) {
+ SequentialNavigationSkipCheck(@"type='text' disabled", YES);
+}
+
+TEST_F(JsSuggestionManagerTest, SequentialNavigationNoSkipPassword) {
+ SequentialNavigationSkipCheck(@"input type='password'", NO);
+}
+
+TEST_F(JsSuggestionManagerTest, SequentialNavigationSkipSubmit) {
+ SequentialNavigationSkipCheck(@"input type='submit'", YES);
+}
+
+TEST_F(JsSuggestionManagerTest, SequentialNavigationSkipImage) {
+ SequentialNavigationSkipCheck(@"input type='image'", YES);
+}
+
+TEST_F(JsSuggestionManagerTest, SequentialNavigationSkipButton) {
+ SequentialNavigationSkipCheck(@"input type='button'", YES);
+}
+
+TEST_F(JsSuggestionManagerTest, SequentialNavigationSkipRange) {
+ SequentialNavigationSkipCheck(@"input type='range'", YES);
+}
+
+TEST_F(JsSuggestionManagerTest, SequentialNavigationSkipRadio) {
+ SequentialNavigationSkipCheck(@"type='radio'", YES);
+}
+
+TEST_F(JsSuggestionManagerTest, SequentialNavigationSkipCheckbox) {
+ SequentialNavigationSkipCheck(@"type='checkbox'", YES);
+}
+
+// Special test for a condition where the closeKeyboard script would cause an
+// illegal JS recursion if a blur event results in an event that triggers a
+// crwebinvoke:// back, such as a page change.
+TEST_F(JsSuggestionManagerTest, CloseKeyboardSafetyTest) {
+ LoadHtml(@"<select id='select'>Select</select>");
+ ExecuteJavaScript(
+ @"select.onblur = function(){window.location.href = '#test'}");
+ ExecuteJavaScript(@"select.focus()");
+ // In the failure condition the app will crash during the next line.
+ [manager_ closeKeyboard];
+ // TODO(crbug.com/661624): add a check for the keyboard actually being
+ // dismissed; unfortunately it is not known how to adapt
+ // WaitForBackgroundTasks to yield for events wrapped with window.setTimeout()
+ // or other deferred events.
+}
+
+// Test fixture to test
+// |fetchPreviousAndNextElementsPresenceWithCompletionHandler|.
+class FetchPreviousAndNextExceptionTest : public JsSuggestionManagerTest {
+ public:
+ void SetUp() override {
+ JsSuggestionManagerTest::SetUp();
+ LoadHtml(@"<html></html>");
+ }
+
+ protected:
+ // Evaluates JS and tests that the completion handler passed to
+ // |fetchPreviousAndNextElementsPresenceWithCompletionHandler| is called with
+ // (NO, NO) indicating no previous and next element.
+ void EvaluateJavaScriptAndExpectNoPreviousAndNextElement(NSString* js) {
+ ExecuteJavaScript(js);
+ __block BOOL block_was_called = NO;
+ id completionHandler = ^(BOOL hasPreviousElement, BOOL hasNextElement) {
+ EXPECT_FALSE(hasPreviousElement);
+ EXPECT_FALSE(hasNextElement);
+ block_was_called = YES;
+ };
+ [manager_ fetchPreviousAndNextElementsPresenceWithCompletionHandler:
+ completionHandler];
+ base::test::ios::WaitUntilCondition(^bool() {
+ return block_was_called;
+ });
+ }
+};
+
+// Tests that |fetchPreviousAndNextElementsPresenceWithCompletionHandler| works
+// when |__gCrWeb.suggestion.hasPreviousElement| throws an exception.
+TEST_F(FetchPreviousAndNextExceptionTest, HasPreviousElementException) {
+ EvaluateJavaScriptAndExpectNoPreviousAndNextElement(
+ @"__gCrWeb.suggestion.hasPreviousElement = function() { bar.foo1; }");
+}
+
+// Tests that |fetchPreviousAndNextElementsPresenceWithCompletionHandler| works
+// when |__gCrWeb.suggestion.hasNextElement| throws an exception.
+TEST_F(FetchPreviousAndNextExceptionTest, HasNextElementException) {
+ EvaluateJavaScriptAndExpectNoPreviousAndNextElement(
+ @"__gCrWeb.suggestion.hasNextElement = function() { bar.foo1; }");
+}
+
+// Tests that |fetchPreviousAndNextElementsPresenceWithCompletionHandler| works
+// when |Array.toString| has been overridden to return a malformed string
+// without a ",".
+TEST_F(FetchPreviousAndNextExceptionTest, HasPreviousElementNull) {
+ EvaluateJavaScriptAndExpectNoPreviousAndNextElement(
+ @"Array.prototype.toString = function() { return 'Hello'; }");
+}
+
+} // namespace
« 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