Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 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 "ios/chrome/browser/passwords/password_generation_agent.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <memory> | |
| 9 | |
| 10 #include "base/logging.h" | |
| 11 #include "base/mac/foundation_util.h" | |
| 12 #include "base/mac/scoped_nsobject.h" | |
|
Eugene But (OOO till 7-30)
2016/07/13 22:30:31
s/include/import
vabr (Chromium)
2016/07/14 07:36:31
Done.
| |
| 13 #include "base/mac/scoped_objc_class_swizzler.h" | |
| 14 #include "base/macros.h" | |
| 15 #include "base/strings/string16.h" | |
| 16 #include "base/strings/sys_string_conversions.h" | |
| 17 #include "base/strings/utf_string_conversions.h" | |
| 18 #include "components/autofill/core/common/form_data.h" | |
| 19 #include "components/autofill/core/common/form_field_data.h" | |
| 20 #include "components/autofill/core/common/password_form.h" | |
| 21 #import "components/autofill/ios/browser/js_suggestion_manager.h" | |
| 22 #include "google_apis/gaia/gaia_urls.h" | |
| 23 #import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h" | |
| 24 #import "ios/chrome/browser/passwords/js_password_manager.h" | |
| 25 #import "ios/chrome/browser/passwords/password_generation_offer_view.h" | |
| 26 #import "ios/chrome/browser/passwords/passwords_ui_delegate.h" | |
| 27 #import "ios/chrome/browser/ui/commands/generic_chrome_command.h" | |
| 28 #include "ios/chrome/browser/ui/commands/ios_command_ids.h" | |
| 29 #import "ios/testing/ocmock_complex_type_helper.h" | |
| 30 #include "ios/web/public/test/test_web_state.h" | |
| 31 #include "ios/web/public/web_state/url_verification_constants.h" | |
| 32 #import "ios/web/public/test/web_test_with_web_state.h" | |
| 33 #include "testing/gtest_mac.h" | |
| 34 #include "third_party/ocmock/OCMock/OCMock.h" | |
| 35 #include "third_party/ocmock/gtest_support.h" | |
| 36 #include "url/gurl.h" | |
| 37 | |
| 38 namespace { | |
| 39 | |
| 40 NSString* const kAccountCreationFormName = @"create-foo-account"; | |
| 41 NSString* const kAccountCreationFieldName = @"password"; | |
| 42 NSString* const kAccountCreationOrigin = @"http://foo.com/login"; | |
| 43 NSString* const kEmailFieldName = @"email"; | |
| 44 | |
| 45 // Static storage to access arguments passed to swizzled method of UIWindow. | |
| 46 static id g_chrome_execute_command_sender = nil; | |
| 47 | |
| 48 } // namespace | |
| 49 | |
| 50 @interface MockPasswordsUiDelegate : NSObject<PasswordsUiDelegate> | |
| 51 | |
| 52 - (instancetype)init; | |
| 53 | |
| 54 @property(nonatomic, readonly) BOOL UIShown; | |
| 55 | |
| 56 @end | |
| 57 | |
| 58 @implementation MockPasswordsUiDelegate { | |
| 59 // YES if showGenerationAlertWithPassword was called more recently than | |
| 60 // hideGenerationAlert, NO otherwise. | |
| 61 BOOL _UIShown; | |
| 62 } | |
| 63 | |
| 64 - (instancetype)init { | |
| 65 self = [super init]; | |
| 66 if (self) { | |
| 67 _UIShown = NO; | |
| 68 } | |
| 69 return self; | |
| 70 } | |
| 71 | |
| 72 @synthesize UIShown = _UIShown; | |
| 73 | |
| 74 - (void)showGenerationAlertWithPassword:(NSString*)password | |
| 75 andPromptDelegate: | |
| 76 (id<PasswordGenerationPromptDelegate>)delegate { | |
| 77 _UIShown = YES; | |
| 78 } | |
| 79 | |
| 80 - (void)hideGenerationAlert { | |
| 81 _UIShown = NO; | |
| 82 } | |
| 83 | |
| 84 @end | |
| 85 | |
| 86 // A donor class that provides a chromeExecuteCommand method that can be | |
| 87 // swapped with UIWindow. | |
| 88 @interface DonorWindow : NSObject | |
| 89 | |
| 90 - (void)chromeExecuteCommand:(id)sender; | |
| 91 | |
| 92 @end | |
| 93 | |
| 94 @implementation DonorWindow | |
| 95 | |
| 96 - (void)chromeExecuteCommand:(id)sender { | |
| 97 g_chrome_execute_command_sender = [sender retain]; | |
| 98 } | |
| 99 | |
| 100 @end | |
| 101 | |
| 102 namespace { | |
| 103 | |
| 104 // A helper to swizzle chromeExecuteCommand method on UIWindow. | |
| 105 class ScopedWindowSwizzler { | |
| 106 public: | |
| 107 ScopedWindowSwizzler() | |
| 108 : class_swizzler_([UIWindow class], | |
| 109 [DonorWindow class], | |
| 110 @selector(chromeExecuteCommand:)) { | |
| 111 DCHECK(!g_chrome_execute_command_sender); | |
| 112 } | |
| 113 | |
| 114 ~ScopedWindowSwizzler() { | |
| 115 [g_chrome_execute_command_sender release]; | |
| 116 g_chrome_execute_command_sender = nil; | |
| 117 } | |
| 118 | |
| 119 private: | |
| 120 base::mac::ScopedObjCClassSwizzler class_swizzler_; | |
| 121 | |
| 122 DISALLOW_COPY_AND_ASSIGN(ScopedWindowSwizzler); | |
| 123 }; | |
| 124 | |
| 125 // Returns a form that should be marked as an account creation form by local | |
| 126 // heuristics. | |
| 127 autofill::PasswordForm GetAccountCreationForm() { | |
| 128 autofill::FormFieldData name_field; | |
| 129 name_field.name = base::ASCIIToUTF16("name"); | |
| 130 name_field.form_control_type = "text"; | |
| 131 | |
| 132 autofill::FormFieldData email_field; | |
| 133 email_field.name = base::ASCIIToUTF16("email"); | |
| 134 email_field.form_control_type = "email"; | |
| 135 | |
| 136 autofill::FormFieldData password_field; | |
| 137 password_field.name = base::SysNSStringToUTF16(kAccountCreationFieldName); | |
| 138 password_field.form_control_type = "password"; | |
| 139 | |
| 140 autofill::FormFieldData confirmPasswordField; | |
| 141 confirmPasswordField.name = base::ASCIIToUTF16("confirm"); | |
| 142 confirmPasswordField.form_control_type = "password"; | |
| 143 | |
| 144 autofill::FormData form; | |
| 145 form.name = base::SysNSStringToUTF16(kAccountCreationFormName); | |
| 146 form.origin = GURL(base::SysNSStringToUTF8(kAccountCreationOrigin)); | |
| 147 form.action = GURL(base::SysNSStringToUTF8(kAccountCreationOrigin)); | |
| 148 | |
| 149 form.fields.push_back(name_field); | |
| 150 form.fields.push_back(email_field); | |
| 151 form.fields.push_back(password_field); | |
| 152 form.fields.push_back(confirmPasswordField); | |
| 153 | |
| 154 autofill::PasswordForm password_form; | |
| 155 password_form.origin = form.origin; | |
| 156 password_form.username_element = email_field.name; | |
| 157 password_form.password_element = password_field.name; | |
| 158 | |
| 159 password_form.form_data = form; | |
| 160 | |
| 161 return password_form; | |
| 162 } | |
| 163 | |
| 164 // Executes each block in |blocks|, where each block must have the type | |
| 165 // void^(void). | |
| 166 void ExecuteBlocks(NSArray* blocks) { | |
| 167 for (void (^block)(void) in blocks) | |
| 168 block(); | |
| 169 } | |
| 170 | |
| 171 // Returns a form that has the same origin as GAIA. | |
| 172 autofill::PasswordForm GetGAIAForm() { | |
| 173 autofill::PasswordForm form(GetAccountCreationForm()); | |
| 174 form.origin = GaiaUrls::GetInstance()->gaia_login_form_realm(); | |
| 175 form.signon_realm = form.origin.GetOrigin().spec(); | |
| 176 return form; | |
| 177 } | |
| 178 | |
| 179 // Returns a form with no text fields. | |
| 180 autofill::PasswordForm GetFormWithNoTextFields() { | |
| 181 autofill::PasswordForm form(GetAccountCreationForm()); | |
| 182 form.form_data.fields.clear(); | |
| 183 return form; | |
| 184 } | |
| 185 | |
| 186 // Returns true if |field| has type "password" and false otherwise. | |
| 187 bool IsPasswordField(const autofill::FormFieldData& field) { | |
| 188 return field.form_control_type == "password"; | |
| 189 } | |
| 190 | |
| 191 // Returns all password fields in |form|. | |
| 192 std::vector<autofill::FormFieldData> GetPasswordFields( | |
| 193 const autofill::PasswordForm& form) { | |
| 194 std::vector<autofill::FormFieldData> fields; | |
| 195 fields.reserve(form.form_data.fields.size()); | |
| 196 for (const auto& field : form.form_data.fields) { | |
| 197 if (IsPasswordField(field)) | |
| 198 fields.push_back(field); | |
| 199 } | |
| 200 return fields; | |
| 201 } | |
| 202 | |
| 203 // Returns a form with no password fields. | |
| 204 autofill::PasswordForm GetFormWithNoPasswordFields() { | |
| 205 autofill::PasswordForm form(GetAccountCreationForm()); | |
| 206 form.form_data.fields.erase( | |
| 207 std::remove_if(form.form_data.fields.begin(), form.form_data.fields.end(), | |
| 208 &IsPasswordField), | |
| 209 form.form_data.fields.end()); | |
| 210 return form; | |
| 211 } | |
| 212 | |
| 213 // Test fixture for testing PasswordGenerationAgent. | |
| 214 class PasswordGenerationAgentTest : public web::WebTestWithWebState { | |
| 215 public: | |
| 216 void SetUp() override { | |
| 217 web::WebTestWithWebState::SetUp(); | |
| 218 mock_js_suggestion_manager_.reset( | |
| 219 [[OCMockObject niceMockForClass:[JsSuggestionManager class]] retain]); | |
| 220 mock_js_password_manager_.reset( | |
| 221 [[OCMockObject niceMockForClass:[JsPasswordManager class]] retain]); | |
| 222 mock_ui_delegate_.reset([[MockPasswordsUiDelegate alloc] init]); | |
| 223 test_web_state_.reset(new web::TestWebState); | |
| 224 agent_.reset([[PasswordGenerationAgent alloc] | |
| 225 initWithWebState:test_web_state_.get() | |
| 226 passwordManager:nullptr | |
| 227 passwordManagerDriver:nullptr | |
| 228 JSPasswordManager:mock_js_password_manager_ | |
| 229 JSSuggestionManager:mock_js_suggestion_manager_ | |
| 230 passwordsUiDelegate:mock_ui_delegate_]); | |
| 231 @autoreleasepool { | |
| 232 accessory_view_controller_.reset([[FormInputAccessoryViewController alloc] | |
| 233 initWithWebState:test_web_state_.get() | |
| 234 providers:@[ agent_ ]]); | |
| 235 } | |
| 236 } | |
| 237 | |
| 238 // Sends form data, autofill data, and password manager data to the | |
| 239 // generation agent so that it can find an account creation form and password | |
| 240 // field. | |
| 241 void LoadAccountCreationForm() { | |
| 242 autofill::PasswordForm password_form(GetAccountCreationForm()); | |
| 243 [agent() allowPasswordGenerationForForm:password_form]; | |
| 244 std::vector<autofill::PasswordForm> password_forms; | |
| 245 password_forms.push_back(password_form); | |
| 246 [agent() processParsedPasswordForms:password_forms]; | |
| 247 SetCurrentURLAndTrustLevel( | |
| 248 GURL(base::SysNSStringToUTF8(kAccountCreationOrigin)), | |
| 249 web::URLVerificationTrustLevel::kAbsolute); | |
| 250 SetContentIsHTML(YES); | |
| 251 } | |
| 252 | |
| 253 // Sets up the web controller mock to use the specified URL and trust level. | |
| 254 void SetCurrentURLAndTrustLevel( | |
| 255 GURL url, | |
| 256 web::URLVerificationTrustLevel url_trust_level) { | |
| 257 test_web_state_->SetCurrentURL(url); | |
| 258 test_web_state_->SetTrustLevel(url_trust_level); | |
| 259 } | |
| 260 | |
| 261 // Swizzles the current web controller to set whether the content is HTML. | |
| 262 void SetContentIsHTML(BOOL content_is_html) { | |
| 263 test_web_state_->SetContentIsHTML(content_is_html); | |
| 264 } | |
| 265 | |
| 266 // Simulates an event on the specified form/field. | |
| 267 void SimulateFormActivity(NSString* form_name, | |
| 268 NSString* field_name, | |
| 269 NSString* type) { | |
| 270 [accessory_view_controller_ webState:test_web_state_.get() | |
| 271 didRegisterFormActivityWithFormNamed:base::SysNSStringToUTF8(form_name) | |
| 272 fieldName:base::SysNSStringToUTF8(field_name) | |
| 273 type:base::SysNSStringToUTF8(type) | |
| 274 value:"" | |
| 275 keyCode:web::WebStateObserver:: | |
| 276 kInvalidFormKeyCode | |
| 277 inputMissing:false]; | |
| 278 } | |
| 279 | |
| 280 // Returns a mock of JsSuggestionManager. | |
| 281 id mock_js_suggestion_manager() { return mock_js_suggestion_manager_; } | |
| 282 | |
| 283 // Returns a mock of JsPasswordManager. | |
| 284 id mock_js_password_manager() { return mock_js_password_manager_; } | |
| 285 | |
| 286 MockPasswordsUiDelegate* mock_ui_delegate() { return mock_ui_delegate_; } | |
| 287 | |
| 288 protected: | |
| 289 // Returns the current generation agent. | |
| 290 PasswordGenerationAgent* agent() { return agent_.get(); } | |
| 291 | |
| 292 // Returns the current accessory view controller. | |
| 293 FormInputAccessoryViewController* accessory_controller() { | |
| 294 return accessory_view_controller_.get(); | |
| 295 } | |
| 296 | |
| 297 private: | |
| 298 // Test WebState. | |
| 299 std::unique_ptr<web::TestWebState> test_web_state_; | |
| 300 | |
| 301 // Mock for JsSuggestionManager; | |
| 302 base::scoped_nsobject<id> mock_js_suggestion_manager_; | |
| 303 | |
| 304 // Mock for JsPasswordManager. | |
| 305 base::scoped_nsobject<id> mock_js_password_manager_; | |
| 306 | |
| 307 // Mock for the UI delegate. | |
| 308 base::scoped_nsobject<MockPasswordsUiDelegate> mock_ui_delegate_; | |
| 309 | |
| 310 // Controller that shows custom input accessory views. | |
| 311 base::scoped_nsobject<FormInputAccessoryViewController> | |
| 312 accessory_view_controller_; | |
| 313 | |
| 314 // The current generation agent. | |
| 315 base::scoped_nsobject<PasswordGenerationAgent> agent_; | |
| 316 }; | |
| 317 | |
| 318 // Tests that local heuristics skip forms with GAIA realm. | |
| 319 TEST_F(PasswordGenerationAgentTest, | |
| 320 OnParsedForms_ShouldIgnoreFormsWithGaiaRealm) { | |
| 321 // Send only a form with GAIA origin to the agent. | |
| 322 std::vector<autofill::PasswordForm> forms; | |
| 323 forms.push_back(GetGAIAForm()); | |
| 324 [agent() processParsedPasswordForms:forms]; | |
| 325 | |
| 326 // No account creation form should have been found. | |
| 327 EXPECT_FALSE(agent().possibleAccountCreationForm); | |
| 328 EXPECT_TRUE(agent().passwordFields.empty()); | |
| 329 } | |
| 330 | |
| 331 // Tests that local heuristics skip forms with no text fields. | |
| 332 TEST_F(PasswordGenerationAgentTest, | |
| 333 OnParsedForms_ShouldIgnoreFormsWithNotEnoughTextFields) { | |
| 334 // Send only a form with GAIA origin to the agent. | |
| 335 std::vector<autofill::PasswordForm> forms; | |
| 336 forms.push_back(GetFormWithNoTextFields()); | |
| 337 [agent() processParsedPasswordForms:forms]; | |
| 338 | |
| 339 // No account creation form should have been found. | |
| 340 EXPECT_FALSE(agent().possibleAccountCreationForm); | |
| 341 EXPECT_TRUE(agent().passwordFields.empty()); | |
| 342 } | |
| 343 | |
| 344 // Tests that local heuristics skip forms with no password fields. | |
| 345 TEST_F(PasswordGenerationAgentTest, | |
| 346 OnParsedForms_ShouldIgnoreFormsWithNoPasswordFields) { | |
| 347 // Send only a form with GAIA origin to the agent. | |
| 348 std::vector<autofill::PasswordForm> forms; | |
| 349 forms.push_back(GetFormWithNoPasswordFields()); | |
| 350 [agent() processParsedPasswordForms:forms]; | |
| 351 | |
| 352 // No account creation form should have been found. | |
| 353 EXPECT_FALSE(agent().possibleAccountCreationForm); | |
| 354 EXPECT_TRUE(agent().passwordFields.empty()); | |
| 355 } | |
| 356 | |
| 357 // Tests that local heuristics extract an account creation form from the page | |
| 358 // when one exists, along with its password fields. | |
| 359 TEST_F(PasswordGenerationAgentTest, OnParsedForms) { | |
| 360 // Send several forms. One should be selected. | |
| 361 std::vector<autofill::PasswordForm> forms; | |
| 362 forms.push_back(GetGAIAForm()); | |
| 363 forms.push_back(GetFormWithNoTextFields()); | |
| 364 forms.push_back(GetFormWithNoPasswordFields()); | |
| 365 forms.push_back(GetAccountCreationForm()); | |
| 366 [agent() processParsedPasswordForms:forms]; | |
| 367 | |
| 368 // Should have found an account creation form and extracted its password | |
| 369 // fields. | |
| 370 EXPECT_EQ(forms[3], *agent().possibleAccountCreationForm); | |
| 371 std::vector<autofill::FormFieldData> expectedPasswordFields( | |
| 372 GetPasswordFields(forms[3])); | |
| 373 EXPECT_EQ(expectedPasswordFields.size(), agent().passwordFields.size()); | |
| 374 for (size_t i = 0; i < expectedPasswordFields.size(); ++i) { | |
| 375 EXPECT_FORM_FIELD_DATA_EQUALS(expectedPasswordFields[i], | |
| 376 agent().passwordFields[i]); | |
| 377 } | |
| 378 } | |
| 379 | |
| 380 // Tests that password generation field identification waits until it has | |
| 381 // approval from autofill and the password manager and an account creation | |
| 382 // form has been identified with local heuristics.. | |
| 383 TEST_F(PasswordGenerationAgentTest, DeterminePasswordGenerationField) { | |
| 384 std::vector<autofill::PasswordForm> forms; | |
| 385 forms.push_back(GetAccountCreationForm()); | |
| 386 | |
| 387 autofill::PasswordForm form(GetAccountCreationForm()); | |
| 388 std::vector<autofill::FormFieldData> passwordFields(GetPasswordFields(form)); | |
| 389 | |
| 390 // The signals can be received in any order, so test them accordingly by | |
| 391 // breaking the steps into blocks and executing them in different orders. | |
| 392 id sendForms = ^{ | |
| 393 std::vector<autofill::PasswordForm> forms; | |
| 394 forms.push_back(form); | |
| 395 [agent() processParsedPasswordForms:forms]; | |
| 396 }; | |
| 397 id sendPasswordManagerWhitelist = ^{ | |
| 398 [agent() allowPasswordGenerationForForm:form]; | |
| 399 }; | |
| 400 id expectFieldNotFound = ^{ | |
| 401 EXPECT_FALSE(agent().passwordGenerationField); | |
| 402 }; | |
| 403 id expectFieldFound = ^{ | |
| 404 // When there are multiple password fields in the account creation form, | |
| 405 // the first one is used as the generation field. | |
| 406 EXPECT_FORM_FIELD_DATA_EQUALS(passwordFields[0], | |
| 407 (*agent().passwordGenerationField)); | |
| 408 }; | |
| 409 | |
| 410 // For each permutation of steps, the field should only be set after the third | |
| 411 // signal is received. | |
| 412 @autoreleasepool { | |
| 413 ExecuteBlocks(@[ | |
| 414 sendForms, expectFieldNotFound, sendPasswordManagerWhitelist, | |
| 415 expectFieldFound | |
| 416 ]); | |
| 417 [agent() clearState]; | |
| 418 | |
| 419 ExecuteBlocks(@[ | |
| 420 sendPasswordManagerWhitelist, expectFieldNotFound, sendForms, | |
| 421 expectFieldFound | |
| 422 ]); | |
| 423 [agent() clearState]; | |
| 424 } | |
| 425 } | |
| 426 | |
| 427 // Tests that the password generation UI is shown when the user focuses the | |
| 428 // password field in the account creation form. | |
| 429 TEST_F(PasswordGenerationAgentTest, | |
| 430 ShouldStartGenerationWhenPasswordFieldFocused) { | |
| 431 LoadAccountCreationForm(); | |
| 432 id mock = [OCMockObject partialMockForObject:accessory_controller()]; | |
| 433 [[mock expect] showCustomInputAccessoryView:[OCMArg any]]; | |
| 434 SimulateFormActivity(kAccountCreationFormName, kAccountCreationFieldName, | |
| 435 @"focus"); | |
| 436 | |
| 437 EXPECT_OCMOCK_VERIFY(mock); | |
| 438 [mock stop]; | |
| 439 } | |
| 440 | |
| 441 // Tests that requesting password generation shows the alert UI. | |
| 442 TEST_F(PasswordGenerationAgentTest, ShouldShowAlertWhenGenerationRequested) { | |
| 443 LoadAccountCreationForm(); | |
| 444 id mock = [OCMockObject partialMockForObject:accessory_controller()]; | |
| 445 [[mock expect] showCustomInputAccessoryView:[OCMArg any]]; | |
| 446 SimulateFormActivity(kAccountCreationFormName, kAccountCreationFieldName, | |
| 447 @"focus"); | |
| 448 EXPECT_EQ(NO, mock_ui_delegate().UIShown); | |
| 449 | |
| 450 [agent() generatePassword]; | |
| 451 EXPECT_EQ(YES, mock_ui_delegate().UIShown); | |
| 452 | |
| 453 EXPECT_OCMOCK_VERIFY(mock); | |
| 454 [mock stop]; | |
| 455 } | |
| 456 | |
| 457 // Tests that the password generation UI is hidden when the user changes focus | |
| 458 // from the password field. | |
| 459 TEST_F(PasswordGenerationAgentTest, | |
| 460 ShouldStopGenerationWhenDifferentFieldFocused) { | |
| 461 LoadAccountCreationForm(); | |
| 462 id mock = [OCMockObject partialMockForObject:accessory_controller()]; | |
| 463 [[mock expect] showCustomInputAccessoryView:[OCMArg any]]; | |
| 464 SimulateFormActivity(kAccountCreationFormName, kAccountCreationFieldName, | |
| 465 @"focus"); | |
| 466 | |
| 467 [[mock expect] restoreDefaultInputAccessoryView]; | |
| 468 SimulateFormActivity(kAccountCreationFormName, kEmailFieldName, @"focus"); | |
| 469 | |
| 470 EXPECT_OCMOCK_VERIFY(mock); | |
| 471 [mock stop]; | |
| 472 } | |
| 473 | |
| 474 // Tests that the password field is filled when the user accepts a generated | |
| 475 // password. | |
| 476 TEST_F(PasswordGenerationAgentTest, | |
| 477 ShouldFillPasswordFieldAndDismissAlertWhenUserAcceptsGeneratedPassword) { | |
| 478 LoadAccountCreationForm(); | |
| 479 // Focus the password field to start generation. | |
| 480 SimulateFormActivity(kAccountCreationFormName, kAccountCreationFieldName, | |
| 481 @"focus"); | |
| 482 NSString* password = @"abc"; | |
| 483 | |
| 484 [[[mock_js_password_manager() stub] andDo:^(NSInvocation* invocation) { | |
| 485 void (^completion_handler)(BOOL); | |
| 486 [invocation getArgument:&completion_handler atIndex:4]; | |
| 487 completion_handler(YES); | |
| 488 }] fillPasswordForm:kAccountCreationFormName | |
| 489 withGeneratedPassword:password | |
| 490 completionHandler:[OCMArg any]]; | |
| 491 | |
| 492 [agent() generatePassword]; | |
| 493 EXPECT_EQ(YES, mock_ui_delegate().UIShown); | |
| 494 | |
| 495 [agent() acceptPasswordGeneration:nil]; | |
| 496 EXPECT_EQ(NO, mock_ui_delegate().UIShown); | |
| 497 EXPECT_OCMOCK_VERIFY(mock_js_password_manager()); | |
| 498 } | |
| 499 | |
| 500 // Tests that the Save Passwords setting screen is shown when the user taps | |
| 501 // "show saved passwords". | |
| 502 TEST_F(PasswordGenerationAgentTest, | |
| 503 ShouldShowPasswordsAndDismissAlertWhenUserTapsShow) { | |
| 504 ScopedWindowSwizzler swizzler; | |
| 505 LoadAccountCreationForm(); | |
| 506 // Focus the password field to start generation. | |
| 507 SimulateFormActivity(kAccountCreationFormName, kAccountCreationFieldName, | |
| 508 @"focus"); | |
| 509 [agent() generatePassword]; | |
| 510 EXPECT_EQ(YES, mock_ui_delegate().UIShown); | |
| 511 | |
| 512 [agent() showSavedPasswords:nil]; | |
| 513 EXPECT_EQ(NO, mock_ui_delegate().UIShown); | |
| 514 | |
| 515 GenericChromeCommand* command = base::mac::ObjCCast<GenericChromeCommand>( | |
| 516 g_chrome_execute_command_sender); | |
| 517 EXPECT_TRUE(command); | |
| 518 EXPECT_EQ(IDC_SHOW_SAVE_PASSWORDS_SETTINGS, command.tag); | |
| 519 } | |
| 520 | |
| 521 } // namespace | |
| OLD | NEW |