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

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

Issue 2580363002: Upstream Chrome on iOS source code [1/11]. (Closed)
Patch Set: Created 3 years, 12 months 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 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/autofill/form_suggestion_controller.h"
6
7 #include <utility>
8 #include <vector>
9
10 #include "base/ios/ios_util.h"
11 #include "base/mac/foundation_util.h"
12 #import "components/autofill/ios/browser/form_suggestion.h"
13 #import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
14 #import "ios/chrome/browser/autofill/form_suggestion_provider.h"
15 #import "ios/chrome/browser/autofill/form_suggestion_view.h"
16 #include "ios/chrome/browser/ui/ui_util.h"
17 #import "ios/chrome/browser/web/chrome_web_test.h"
18 #include "ios/chrome/test/base/scoped_block_swizzler.h"
19 #import "ios/web/public/web_state/crw_web_view_proxy.h"
20 #import "ios/web/web_state/ui/crw_web_controller.h"
21 #import "testing/gtest_mac.h"
22 #import "third_party/ocmock/OCMock/OCMock.h"
23 #include "third_party/ocmock/gtest_support.h"
24
25 #if !defined(__has_feature) || !__has_feature(objc_arc)
26 #error "This file requires ARC support."
27 #endif
28
29 @interface FormInputAccessoryViewController (Testing)
30 - (instancetype)initWithWebState:(web::WebState*)webState
31 JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager
32 providers:(NSArray*)providers;
33 @end
34
35 // Test provider that records invocations of its interface methods.
36 @interface TestSuggestionProvider : NSObject<FormSuggestionProvider>
37
38 @property(weak, nonatomic, readonly) FormSuggestion* suggestion;
39 @property(weak, nonatomic, readonly) NSString* formName;
40 @property(weak, nonatomic, readonly) NSString* fieldName;
41 @property(nonatomic, assign) BOOL selected;
42 @property(nonatomic, assign) BOOL askedIfSuggestionsAvailable;
43 @property(nonatomic, assign) BOOL askedForSuggestions;
44
45 - (instancetype)initWithSuggestions:(NSArray*)suggestions;
46
47 @end
48
49 @implementation TestSuggestionProvider {
50 NSArray* _suggestions;
51 NSString* _formName;
52 NSString* _fieldName;
53 FormSuggestion* _suggestion;
54 }
55
56 @synthesize selected = _selected;
57 @synthesize askedIfSuggestionsAvailable = _askedIfSuggestionsAvailable;
58 @synthesize askedForSuggestions = _askedForSuggestions;
59
60 - (instancetype)initWithSuggestions:(NSArray*)suggestions {
61 self = [super init];
62 if (self)
63 _suggestions = [suggestions copy];
64 return self;
65 }
66
67 - (NSString*)formName {
68 return _formName;
69 }
70
71 - (NSString*)fieldName {
72 return _fieldName;
73 }
74
75 - (FormSuggestion*)suggestion {
76 return _suggestion;
77 }
78
79 - (void)checkIfSuggestionsAvailableForForm:(NSString*)formName
80 field:(NSString*)fieldName
81 type:(NSString*)type
82 typedValue:(NSString*)typedValue
83 webState:(web::WebState*)webState
84 completionHandler:
85 (SuggestionsAvailableCompletion)completion {
86 self.askedIfSuggestionsAvailable = YES;
87 completion([_suggestions count] > 0);
88 }
89
90 - (void)retrieveSuggestionsForForm:(NSString*)formName
91 field:(NSString*)fieldName
92 type:(NSString*)type
93 typedValue:(NSString*)typedValue
94 webState:(web::WebState*)webState
95 completionHandler:(SuggestionsReadyCompletion)completion {
96 self.askedForSuggestions = YES;
97 completion(_suggestions, self);
98 }
99
100 - (void)didSelectSuggestion:(FormSuggestion*)suggestion
101 forField:(NSString*)fieldName
102 form:(NSString*)formName
103 completionHandler:(SuggestionHandledCompletion)completion {
104 self.selected = YES;
105 _suggestion = suggestion;
106 _formName = [formName copy];
107 _fieldName = [fieldName copy];
108 completion();
109 }
110
111 @end
112
113 namespace {
114
115 // Finds the FormSuggestionView in |parent|'s view hierarchy, if it exists.
116 FormSuggestionView* GetSuggestionView(UIView* parent) {
117 if ([parent isKindOfClass:[FormSuggestionView class]])
118 return base::mac::ObjCCastStrict<FormSuggestionView>(parent);
119 for (UIView* child in parent.subviews) {
120 UIView* suggestion_view = GetSuggestionView(child);
121 if (suggestion_view)
122 return base::mac::ObjCCastStrict<FormSuggestionView>(suggestion_view);
123 }
124 return nil;
125 }
126
127 // Test fixture for FormSuggestionController testing.
128 class FormSuggestionControllerTest : public ChromeWebTest {
129 public:
130 FormSuggestionControllerTest() {}
131
132 void SetUp() override {
133 ChromeWebTest::SetUp();
134
135 // Mock out the JsSuggestionManager.
136 mock_js_suggestion_manager_ =
137 [OCMockObject niceMockForClass:[JsSuggestionManager class]];
138
139 // Set up a fake keyboard accessory view. It is expected to have two
140 // subviews.
141 input_accessory_view_ = [[UIView alloc] init];
142 UIView* fake_view_1 = [[UIView alloc] init];
143 [input_accessory_view_ addSubview:fake_view_1];
144 UIView* fake_view_2 = [[UIView alloc] init];
145 [input_accessory_view_ addSubview:fake_view_2];
146
147 // Return the fake keyboard accessory view from the mock CRWWebViewProxy.
148 mock_web_view_proxy_ =
149 [OCMockObject niceMockForProtocol:@protocol(CRWWebViewProxy)];
150 [[[mock_web_view_proxy_ stub] andReturn:input_accessory_view_]
151 keyboardAccessory];
152 }
153
154 void TearDown() override {
155 [suggestion_controller_ detachFromWebState];
156 ChromeWebTest::TearDown();
157 }
158
159 // Sets |url| to be current for WebState.
160 void SetCurrentUrl(const std::string& url) { LoadHtml(@"", GURL(url)); }
161
162 // Swizzles the current web controller to set whether the content is HTML.
163 void SetContentIsHtml(BOOL content_is_html) {
164 id content_is_html_block = ^BOOL(CRWWebController* webController) {
165 return content_is_html;
166 };
167 content_is_html_swizzler_.reset(new ScopedBlockSwizzler(
168 [CRWWebController class], @selector(contentIsHTML),
169 content_is_html_block));
170 }
171
172 protected:
173 // Sets up |suggestion_controller_| with the specified array of
174 // FormSuggestionProviders.
175 void SetUpController(NSArray* providers) {
176 suggestion_controller_ = [[FormSuggestionController alloc]
177 initWithWebState:web_state()
178 providers:providers
179 JsSuggestionManager:mock_js_suggestion_manager_];
180 [suggestion_controller_ setWebViewProxy:mock_web_view_proxy_];
181 @autoreleasepool {
182 accessory_controller_ = [[FormInputAccessoryViewController alloc]
183 initWithWebState:web_state()
184 JSSuggestionManager:mock_js_suggestion_manager_
185 providers:@[
186 [suggestion_controller_ accessoryViewProvider]
187 ]];
188 }
189 // Mock out the FormInputAccessoryViewController so it can use the fake
190 // CRWWebViewProxy
191 id mock_accessory_controller =
192 [OCMockObject partialMockForObject:accessory_controller_];
193 [[[mock_accessory_controller stub] andReturn:mock_web_view_proxy_]
194 webViewProxy];
195
196 // On iPad devices, the suggestion view is added directly to the
197 // keyboard view instead of to the input accessory view which is no longer
198 // available on iPad devices. The following code mocks out the methods on
199 // FormInputAccessoryViewController that add and remove the suggestion view.
200 // The mocks now just add and remove it directly to and from
201 // input_accessory_view_ so that the tests can locate it with
202 // GetSuggestionView (defined above).
203 // TODO(crbug.com/661622): Revisit this to see if there's a better way to
204 // test the iPad case. At a minimum, the name 'input_accessory_view_' should
205 // be made more generic.
206 if (IsIPadIdiom()) {
207 void (^mockShow)(NSInvocation*) = ^(NSInvocation* invocation) {
208 __unsafe_unretained UIView* view;
209 [invocation getArgument:&view atIndex:2];
210 for (UIView* view in [input_accessory_view_ subviews]) {
211 [view removeFromSuperview];
212 }
213 [input_accessory_view_ addSubview:view];
214 };
215 [[[mock_accessory_controller stub] andDo:mockShow]
216 showCustomInputAccessoryView:[OCMArg any]];
217
218 void (^mockRestore)(NSInvocation*) = ^(NSInvocation* invocation) {
219 for (UIView* view in [input_accessory_view_ subviews]) {
220 [view removeFromSuperview];
221 }
222 };
223 [[[mock_accessory_controller stub] andDo:mockRestore]
224 restoreDefaultInputAccessoryView];
225 }
226 }
227
228 // Swizzler for [CRWWebController contentIsHTML].
229 std::unique_ptr<ScopedBlockSwizzler> content_is_html_swizzler_;
230
231 // The FormSuggestionController under test.
232 FormSuggestionController* suggestion_controller_;
233
234 // A fake keyboard accessory view.
235 UIView* input_accessory_view_;
236
237 // Mock JsSuggestionManager for verifying interactions.
238 id mock_js_suggestion_manager_;
239
240 // Mock CRWWebViewProxy for verifying interactions.
241 id mock_web_view_proxy_;
242
243 // Accessory view controller.
244 FormInputAccessoryViewController* accessory_controller_;
245
246 DISALLOW_COPY_AND_ASSIGN(FormSuggestionControllerTest);
247 };
248
249 // Tests that pages whose URLs don't have a web scheme aren't processed.
250 TEST_F(FormSuggestionControllerTest, PageLoadShouldBeIgnoredWhenNotWebScheme) {
251 SetUpController(@[]);
252 SetCurrentUrl("data:text/html;charset=utf8;base64,");
253 [suggestion_controller_ webStateDidLoadPage:web_state()];
254
255 EXPECT_FALSE(GetSuggestionView(input_accessory_view_));
256 EXPECT_OCMOCK_VERIFY(mock_js_suggestion_manager_);
257 }
258
259 // Tests that pages whose content isn't HTML aren't processed.
260 TEST_F(FormSuggestionControllerTest, PageLoadShouldBeIgnoredWhenNotHtml) {
261 SetUpController(@[]);
262 SetCurrentUrl("http://foo.com");
263 SetContentIsHtml(NO);
264 [suggestion_controller_ webStateDidLoadPage:web_state()];
265
266 EXPECT_FALSE(GetSuggestionView(input_accessory_view_));
267 EXPECT_OCMOCK_VERIFY(mock_js_suggestion_manager_);
268 }
269
270 // Tests that the keyboard accessory view is reset and JavaScript is injected
271 // when a page is loaded.
272 TEST_F(FormSuggestionControllerTest,
273 PageLoadShouldRestoreKeyboardAccessoryViewAndInjectJavaScript) {
274 SetUpController(@[]);
275 SetCurrentUrl("http://foo.com");
276
277 // Load the page. The JS should be injected.
278 [[mock_js_suggestion_manager_ expect] inject];
279 [suggestion_controller_ webStateDidLoadPage:web_state()];
280 EXPECT_OCMOCK_VERIFY(mock_js_suggestion_manager_);
281
282 // Trigger form activity, which should set up the suggestions view.
283 [accessory_controller_ webState:web_state()
284 didRegisterFormActivityWithFormNamed:"form"
285 fieldName:"field"
286 type:"type"
287 value:"value"
288 keyCode:web::WebStateObserver::
289 kInvalidFormKeyCode
290 inputMissing:false];
291 EXPECT_TRUE(GetSuggestionView(input_accessory_view_));
292
293 // Trigger another page load. The suggestions accessory view should
294 // not be present.
295 [accessory_controller_ webStateDidLoadPage:web_state()];
296 EXPECT_FALSE(GetSuggestionView(input_accessory_view_));
297 }
298
299 // Tests that "blur" events are ignored.
300 TEST_F(FormSuggestionControllerTest, FormActivityBlurShouldBeIgnored) {
301 [accessory_controller_ webState:web_state()
302 didRegisterFormActivityWithFormNamed:"form"
303 fieldName:"field"
304 type:"blur" // blur!
305 value:"value"
306 keyCode:web::WebStateObserver::
307 kInvalidFormKeyCode
308 inputMissing:false];
309 EXPECT_FALSE(GetSuggestionView(input_accessory_view_));
310 }
311
312 // Tests that no suggestions are displayed when no providers are registered.
313 TEST_F(FormSuggestionControllerTest,
314 FormActivityShouldRetrieveSuggestions_NoProvidersAvailable) {
315 // Set up the controller without any providers.
316 SetUpController(@[]);
317 SetCurrentUrl("http://foo.com");
318 [accessory_controller_ webState:web_state()
319 didRegisterFormActivityWithFormNamed:"form"
320 fieldName:"field"
321 type:"type"
322 value:"value"
323 keyCode:web::WebStateObserver::
324 kInvalidFormKeyCode
325 inputMissing:false];
326
327 // The suggestions accessory view should be empty.
328 FormSuggestionView* suggestionView = GetSuggestionView(input_accessory_view_);
329 EXPECT_TRUE(suggestionView);
330 EXPECT_EQ(0U, [suggestionView.suggestions count]);
331 }
332
333 // Tests that, when no providers have suggestions to offer for a form/field,
334 // they aren't asked and no suggestions are displayed.
335 TEST_F(FormSuggestionControllerTest,
336 FormActivityShouldRetrieveSuggestions_NoSuggestionsAvailable) {
337 // Set up the controller with some providers, but none of them will
338 // have suggestions available.
339 TestSuggestionProvider* provider1 =
340 [[TestSuggestionProvider alloc] initWithSuggestions:@[]];
341 TestSuggestionProvider* provider2 =
342 [[TestSuggestionProvider alloc] initWithSuggestions:@[]];
343 SetUpController(@[ provider1, provider2 ]);
344 SetCurrentUrl("http://foo.com");
345
346 [accessory_controller_ webState:web_state()
347 didRegisterFormActivityWithFormNamed:"form"
348 fieldName:"field"
349 type:"type"
350 value:"value"
351 keyCode:web::WebStateObserver::
352 kInvalidFormKeyCode
353 inputMissing:false];
354
355 // The providers should each be asked if they have suggestions for the
356 // form in question.
357 EXPECT_TRUE([provider1 askedIfSuggestionsAvailable]);
358 EXPECT_TRUE([provider2 askedIfSuggestionsAvailable]);
359
360 // Since none of the providers had suggestions available, none of them
361 // should have been asked for suggestions.
362 EXPECT_FALSE([provider1 askedForSuggestions]);
363 EXPECT_FALSE([provider2 askedForSuggestions]);
364
365 // The accessory view should be empty.
366 FormSuggestionView* suggestionView = GetSuggestionView(input_accessory_view_);
367 EXPECT_TRUE(suggestionView);
368 EXPECT_EQ(0U, [suggestionView.suggestions count]);
369 }
370
371 // Tests that, once a provider is asked if it has suggestions for a form/field,
372 // it and only it is asked to provide them, and that they are then displayed
373 // in the keyboard accessory view.
374 TEST_F(FormSuggestionControllerTest,
375 FormActivityShouldRetrieveSuggestions_SuggestionsAddedToAccessoryView) {
376 // Set up the controller with some providers, one of which can provide
377 // suggestions.
378 NSArray* suggestions = @[
379 [FormSuggestion suggestionWithValue:@"foo"
380 displayDescription:nil
381 icon:@""
382 identifier:0],
383 [FormSuggestion suggestionWithValue:@"bar"
384 displayDescription:nil
385 icon:@""
386 identifier:1]
387 ];
388 TestSuggestionProvider* provider1 =
389 [[TestSuggestionProvider alloc] initWithSuggestions:suggestions];
390 TestSuggestionProvider* provider2 =
391 [[TestSuggestionProvider alloc] initWithSuggestions:@[]];
392 SetUpController(@[ provider1, provider2 ]);
393 SetCurrentUrl("http://foo.com");
394
395 [accessory_controller_ webState:web_state()
396 didRegisterFormActivityWithFormNamed:"form"
397 fieldName:"field"
398 type:"type"
399 value:"value"
400 keyCode:web::WebStateObserver::
401 kInvalidFormKeyCode
402 inputMissing:false];
403
404 // Since the first provider has suggestions available, it and only it
405 // should have been asked.
406 EXPECT_TRUE([provider1 askedIfSuggestionsAvailable]);
407 EXPECT_FALSE([provider2 askedIfSuggestionsAvailable]);
408
409 // Since the first provider said it had suggestions, it and only it
410 // should have been asked to provide them.
411 EXPECT_TRUE([provider1 askedForSuggestions]);
412 EXPECT_FALSE([provider2 askedForSuggestions]);
413
414 // The accessory view should show the suggestions.
415 FormSuggestionView* suggestionView = GetSuggestionView(input_accessory_view_);
416 EXPECT_TRUE(suggestionView);
417 EXPECT_NSEQ(suggestions, suggestionView.suggestions);
418 }
419
420 // Tests that selecting a suggestion from the accessory view informs the
421 // specified delegate for that suggestion.
422 TEST_F(FormSuggestionControllerTest, SelectingSuggestionShouldNotifyDelegate) {
423 // Send some suggestions to the controller and then tap one.
424 NSArray* suggestions = @[
425 [FormSuggestion suggestionWithValue:@"foo"
426 displayDescription:nil
427 icon:@""
428 identifier:0],
429 ];
430 TestSuggestionProvider* provider =
431 [[TestSuggestionProvider alloc] initWithSuggestions:suggestions];
432 SetUpController(@[ provider ]);
433 SetCurrentUrl("http://foo.com");
434 [accessory_controller_ webState:web_state()
435 didRegisterFormActivityWithFormNamed:"form"
436 fieldName:"field"
437 type:"type"
438 value:"value"
439 keyCode:web::WebStateObserver::
440 kInvalidFormKeyCode
441 inputMissing:false];
442
443 // Selecting a suggestion should notify the delegate.
444 [suggestion_controller_ didSelectSuggestion:suggestions[0]];
445 EXPECT_TRUE([provider selected]);
446 EXPECT_NSEQ(@"form", [provider formName]);
447 EXPECT_NSEQ(@"field", [provider fieldName]);
448 EXPECT_NSEQ(suggestions[0], [provider suggestion]);
449 }
450
451 } // namespace
OLDNEW
« no previous file with comments | « ios/chrome/browser/autofill/form_structure_browsertest.mm ('k') | ios/chrome/browser/autofill/js_autofill_manager_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698