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

Side by Side Diff: ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm

Issue 2589583003: Upstream Chrome on iOS source code [7/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 2016 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 <AVFoundation/AVFoundation.h>
6 #import <EarlGrey/EarlGrey.h>
7 #import <UIKit/UIKit.h>
8
9 #import "base/mac/scoped_nsobject.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "base/test/scoped_command_line.h"
12 #include "components/strings/grit/components_strings.h"
13 #include "components/version_info/version_info.h"
14 #import "ios/chrome/app/main_controller.h"
15 #include "ios/chrome/browser/chrome_switches.h"
16 #import "ios/chrome/browser/ui/browser_view_controller.h"
17 #import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
18 #include "ios/chrome/browser/ui/commands/ios_command_ids.h"
19 #include "ios/chrome/browser/ui/icons/chrome_icon.h"
20 #include "ios/chrome/browser/ui/qr_scanner/camera_controller.h"
21 #include "ios/chrome/browser/ui/qr_scanner/qr_scanner_view.h"
22 #include "ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.h"
23 #include "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
24 #include "ios/chrome/grit/ios_chromium_strings.h"
25 #include "ios/chrome/grit/ios_strings.h"
26 #import "ios/chrome/test/app/chrome_test_util.h"
27 #import "ios/chrome/test/base/scoped_block_swizzler.h"
28 #import "ios/chrome/test/earl_grey/chrome_matchers.h"
29 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
30 #import "ios/testing/earl_grey/disabled_test_macros.h"
31 #include "ios/web/public/test/http_server.h"
32 #include "ios/web/public/test/http_server_util.h"
33 #import "third_party/ocmock/OCMock/OCMock.h"
34 #import "ui/base/l10n/l10n_util.h"
35 #import "ui/base/l10n/l10n_util_mac.h"
36
37 using namespace chrome_test_util;
38 using namespace qr_scanner;
39
40 namespace {
41
42 char kTestURL[] = "http://testurl";
43 char kTestURLResponse[] = "Test URL page";
44 char kTestQuery[] = "testquery";
45 char kTestQueryURL[] = "http://searchurl/testquery";
46 char kTestQueryResponse[] = "Test query page";
47
48 char kTestURLEdited[] = "http://testuredited";
49 char kTestURLEditedResponse[] = "Test URL edited page";
50 char kTestQueryEditedURL[] = "http://searchurl/testqueredited";
51 char kTestQueryEditedResponse[] = "Test query edited page";
52
53 // The GREYCondition timeout used for calls to waitWithTimeout:pollInterval:.
54 CFTimeInterval kGREYConditionTimeout = 5;
55 // The GREYCondition poll interval used for calls to
56 // waitWithTimeout:pollInterval:.
57 CFTimeInterval kGREYConditionPollInterval = 0.1;
58
59 // Returns the GREYMatcher for an element which is visible, interactable, and
60 // enabled.
61 id<GREYMatcher> visibleInteractableEnabled() {
62 return grey_allOf(grey_sufficientlyVisible(), grey_interactable(),
63 grey_enabled(), nil);
64 }
65
66 // Returns the GREYMatcher for the button that closes the QR Scanner.
67 id<GREYMatcher> qrScannerCloseButton() {
68 return buttonWithAccessibilityLabel(
69 [[ChromeIcon closeIcon] accessibilityLabel]);
70 }
71
72 // Returns the GREYMatcher for the button which indicates that torch is off and
73 // which turns on the torch.
74 id<GREYMatcher> qrScannerTorchOffButton() {
75 return grey_allOf(grey_accessibilityLabel(l10n_util::GetNSString(
76 IDS_IOS_QR_SCANNER_TORCH_BUTTON_ACCESSIBILITY_LABEL)),
77 grey_accessibilityValue(l10n_util::GetNSString(
78 IDS_IOS_QR_SCANNER_TORCH_OFF_ACCESSIBILITY_VALUE)),
79 grey_accessibilityTrait(UIAccessibilityTraitButton), nil);
80 }
81
82 // Returns the GREYMatcher for the button which indicates that torch is on and
83 // which turns off the torch.
84 id<GREYMatcher> qrScannerTorchOnButton() {
85 return grey_allOf(grey_accessibilityLabel(l10n_util::GetNSString(
86 IDS_IOS_QR_SCANNER_TORCH_BUTTON_ACCESSIBILITY_LABEL)),
87 grey_accessibilityValue(l10n_util::GetNSString(
88 IDS_IOS_QR_SCANNER_TORCH_ON_ACCESSIBILITY_VALUE)),
89 grey_accessibilityTrait(UIAccessibilityTraitButton), nil);
90 }
91
92 // Returns the GREYMatcher for the QR Scanner viewport caption.
93 id<GREYMatcher> qrScannerViewportCaption() {
94 return staticTextWithAccessibilityLabelId(
95 IDS_IOS_QR_SCANNER_VIEWPORT_CAPTION);
96 }
97
98 // Returns the GREYMatcher for the back button in the web toolbar.
99 id<GREYMatcher> webToolbarBackButton() {
100 return buttonWithAccessibilityLabelId(IDS_ACCNAME_BACK);
101 }
102
103 // Returns the GREYMatcher for the Cancel button to dismiss a UIAlertController.
104 id<GREYMatcher> dialogCancelButton() {
105 return grey_allOf(
106 grey_text(l10n_util::GetNSString(IDS_IOS_QR_SCANNER_ALERT_CANCEL)),
107 grey_accessibilityTrait(UIAccessibilityTraitStaticText), nil);
108 }
109
110 // Opens the QR Scanner view using a command.
111 // TODO(crbug.com/629776): Replace the command call with a UI action.
112 void showQRScannerWithCommand() {
113 base::scoped_nsobject<GenericChromeCommand> command(
114 [[GenericChromeCommand alloc] initWithTag:IDC_SHOW_QR_SCANNER]);
115 chrome_test_util::RunCommandWithActiveViewController(command);
116 }
117
118 // Taps the |button|.
119 void tapButton(id<GREYMatcher> button) {
120 [[EarlGrey selectElementWithMatcher:button] performAction:grey_tap()];
121 }
122
123 // Appends the given |editText| to the |text| already in the omnibox and presses
124 // the keyboard return key.
125 void editOmniboxTextAndTapKeyboardReturn(std::string text, NSString* editText) {
126 [[EarlGrey selectElementWithMatcher:omniboxText(text)]
127 performAction:grey_typeText([editText stringByAppendingString:@"\n"])];
128 }
129
130 // Presses the keyboard return key.
131 void tapKeyboardReturnKeyInOmniboxWithText(std::string text) {
132 [[EarlGrey selectElementWithMatcher:omniboxText(text)]
133 performAction:grey_typeText(@"\n")];
134 }
135
136 } // namespace
137
138 @interface QRScannerViewControllerTestCase : ChromeTestCase {
139 GURL _testURL;
140 GURL _testURLEdited;
141 GURL _testQuery;
142 GURL _testQueryEdited;
143 }
144
145 @end
146
147 @implementation QRScannerViewControllerTestCase {
148 // A scoped command line to enable the QR Scanner experiment.
149 std::unique_ptr<base::test::ScopedCommandLine> scoped_command_line_;
150 // A swizzler for the CameraController method cameraControllerWithDelegate:.
151 std::unique_ptr<ScopedBlockSwizzler> camera_controller_swizzler_;
152 // A swizzler for the WebToolbarController method
153 // loadGURLFromLocationBar:transition:.
154 std::unique_ptr<ScopedBlockSwizzler> load_GURL_from_location_bar_swizzler_;
155 }
156
157 + (void)setUp {
158 [super setUp];
159 std::map<GURL, std::string> responses;
160 responses[web::test::HttpServer::MakeUrl(kTestURL)] = kTestURLResponse;
161 responses[web::test::HttpServer::MakeUrl(kTestQueryURL)] = kTestQueryResponse;
162 responses[web::test::HttpServer::MakeUrl(kTestURLEdited)] =
163 kTestURLEditedResponse;
164 responses[web::test::HttpServer::MakeUrl(kTestQueryEditedURL)] =
165 kTestQueryEditedResponse;
166 web::test::SetUpSimpleHttpServer(responses);
167 }
168
169 - (void)setUp {
170 [super setUp];
171 _testURL = web::test::HttpServer::MakeUrl(kTestURL);
172 _testURLEdited = web::test::HttpServer::MakeUrl(kTestURLEdited);
173 _testQuery = web::test::HttpServer::MakeUrl(kTestQueryURL);
174 _testQueryEdited = web::test::HttpServer::MakeUrl(kTestQueryEditedURL);
175
176 // Enable the QR Scanner experiment.
177 scoped_command_line_.reset(new base::test::ScopedCommandLine);
178 scoped_command_line_->GetProcessCommandLine()->AppendSwitch(
179 switches::kEnableQRScanner);
180 }
181
182 - (void)tearDown {
183 [super tearDown];
184 load_GURL_from_location_bar_swizzler_.reset();
185 camera_controller_swizzler_.reset();
186 }
187
188 // Checks that the close button is visible, interactable, and enabled.
189 - (void)assertCloseButtonIsVisible {
190 [[EarlGrey selectElementWithMatcher:qrScannerCloseButton()]
191 assertWithMatcher:visibleInteractableEnabled()];
192 }
193
194 // Checks that the close button is not visible.
195 - (void)assertCloseButtonIsNotVisible {
196 [[EarlGrey selectElementWithMatcher:qrScannerCloseButton()]
197 assertWithMatcher:grey_notVisible()];
198 }
199
200 // Checks that the torch off button is visible, interactable, and enabled, and
201 // that the torch on button is not.
202 - (void)assertTorchOffButtonIsVisible {
203 [[EarlGrey selectElementWithMatcher:qrScannerTorchOffButton()]
204 assertWithMatcher:visibleInteractableEnabled()];
205 [[EarlGrey selectElementWithMatcher:qrScannerTorchOnButton()]
206 assertWithMatcher:grey_notVisible()];
207 }
208
209 // Checks that the torch on button is visible, interactable, and enabled, and
210 // that the torch off button is not.
211 - (void)assertTorchOnButtonIsVisible {
212 [[EarlGrey selectElementWithMatcher:qrScannerTorchOnButton()]
213 assertWithMatcher:visibleInteractableEnabled()];
214 [[EarlGrey selectElementWithMatcher:qrScannerTorchOffButton()]
215 assertWithMatcher:grey_notVisible()];
216 }
217
218 // Checks that the torch off button is visible and disabled.
219 - (void)assertTorchButtonIsDisabled {
220 [[EarlGrey selectElementWithMatcher:qrScannerTorchOffButton()]
221 assertWithMatcher:grey_allOf(grey_sufficientlyVisible(),
222 grey_not(grey_enabled()), nil)];
223 }
224
225 // Checks that the camera viewport caption is visible.
226 - (void)assertCameraViewportCaptionIsVisible {
227 [[EarlGrey selectElementWithMatcher:qrScannerViewportCaption()]
228 assertWithMatcher:grey_sufficientlyVisible()];
229 }
230
231 // Checks that the close button, the camera preview, and the camera viewport
232 // caption are visible. If |torch| is YES, checks that the torch off button is
233 // visible, otherwise checks that the torch button is disabled. If |preview| is
234 // YES, checks that the preview is visible and of the same size as the QR
235 // Scanner view, otherwise checks that the preview is in the view hierarchy but
236 // is hidden.
237 - (void)assertQRScannerUIIsVisibleWithTorch:(BOOL)torch {
238 [self assertCloseButtonIsVisible];
239 [self assertCameraViewportCaptionIsVisible];
240 if (torch) {
241 [self assertTorchOffButtonIsVisible];
242 } else {
243 [self assertTorchButtonIsDisabled];
244 }
245 }
246
247 // Presents the QR Scanner with a command, waits for it to be displayed, and
248 // checks if all its views and buttons are visible. Checks that no alerts are
249 // presented.
250 - (void)showQRScannerAndCheckLayoutWithCameraMock:(id)mock {
251 UIViewController* bvc = [self currentBVC];
252 [self assertModalOfClass:[QRScannerViewController class]
253 isNotPresentedBy:bvc];
254 [self assertModalOfClass:[UIAlertController class] isNotPresentedBy:bvc];
255
256 [self addCameraControllerInitializationExpectations:mock];
257 showQRScannerWithCommand();
258 [self waitForModalOfClass:[QRScannerViewController class] toAppearAbove:bvc];
259 [self assertQRScannerUIIsVisibleWithTorch:NO];
260 [self assertModalOfClass:[UIAlertController class]
261 isNotPresentedBy:[bvc presentedViewController]];
262 [self assertModalOfClass:[UIAlertController class] isNotPresentedBy:bvc];
263 }
264
265 // Closes the QR scanner by tapping the close button and waits for it to
266 // disappear.
267 - (void)closeQRScannerWithCameraMock:(id)mock {
268 [self addCameraControllerDismissalExpectations:mock];
269 tapButton(qrScannerCloseButton());
270 [self waitForModalOfClass:[QRScannerViewController class]
271 toDisappearFromAbove:[self currentBVC]];
272 }
273
274 // Returns the current BrowserViewController.
275 - (UIViewController*)currentBVC {
276 // TODO(crbug.com/629516): Evaluate moving this into a common utility.
277 MainController* mainController = chrome_test_util::GetMainController();
278 return [[mainController browserViewInformation] currentBVC];
279 }
280
281 // Checks that the omnibox is visible and contains |text|.
282 - (void)assertOmniboxIsVisibleWithText:(std::string)text {
283 [[EarlGrey selectElementWithMatcher:omniboxText(text)]
284 assertWithMatcher:grey_notNil()];
285 }
286
287 // Checks that the page that is currently loaded contains the |response|.
288 - (void)assertTestURLIsLoaded:(std::string)response {
289 id<GREYMatcher> testURLResponseMatcher =
290 chrome_test_util::webViewContainingText(response);
291 [[EarlGrey selectElementWithMatcher:testURLResponseMatcher]
292 assertWithMatcher:grey_notNil()];
293 }
294
295 #pragma mark helpers for dialogs
296
297 // Checks that the modal presented by |viewController| is of class |klass|.
298 - (void)assertModalOfClass:(Class)klass
299 isPresentedBy:(UIViewController*)viewController {
300 UIViewController* modal = [viewController presentedViewController];
301 NSString* errorString = [NSString
302 stringWithFormat:@"A modal of class %@ should be presented by %@.", klass,
303 [viewController class]];
304 GREYAssertTrue(modal && [modal isKindOfClass:klass], errorString);
305 }
306
307 // Checks that the |viewController| is not presenting a modal, or that the modal
308 // presented by |viewController| is not of class |klass|.
309 - (void)assertModalOfClass:(Class)klass
310 isNotPresentedBy:(UIViewController*)viewController {
311 UIViewController* modal = [viewController presentedViewController];
312 NSString* errorString = [NSString
313 stringWithFormat:@"A modal of class %@ should not be presented by %@.",
314 klass, [viewController class]];
315 GREYAssertTrue(!modal || ![modal isKindOfClass:klass], errorString);
316 }
317
318 // Checks that the modal presented by |viewController| is of class |klass| and
319 // waits for the modal's view to load.
320 - (void)waitForModalOfClass:(Class)klass
321 toAppearAbove:(UIViewController*)viewController {
322 [self assertModalOfClass:klass isPresentedBy:viewController];
323 UIViewController* modal = [viewController presentedViewController];
324 GREYCondition* modalViewLoadedCondition =
325 [GREYCondition conditionWithName:@"modalViewLoadedCondition"
326 block:^BOOL {
327 return [modal isViewLoaded];
328 }];
329 BOOL modalViewLoaded =
330 [modalViewLoadedCondition waitWithTimeout:kGREYConditionTimeout
331 pollInterval:kGREYConditionPollInterval];
332 NSString* errorString = [NSString
333 stringWithFormat:@"The view of a modal of class %@ should be loaded.",
334 klass];
335 GREYAssertTrue(modalViewLoaded, errorString);
336 }
337
338 // Checks that the |viewController| is not presenting a modal, or that the modal
339 // presented by |viewController| is not of class |klass|. If a modal was
340 // previously presented, waits until it is dismissed.
341 - (void)waitForModalOfClass:(Class)klass
342 toDisappearFromAbove:(UIViewController*)viewController {
343 GREYCondition* modalViewDismissedCondition = [GREYCondition
344 conditionWithName:@"modalViewDismissedCondition"
345 block:^BOOL {
346 UIViewController* modal =
347 [viewController presentedViewController];
348 return !modal || ![modal isKindOfClass:klass];
349 }];
350
351 BOOL modalViewDismissed =
352 [modalViewDismissedCondition waitWithTimeout:kGREYConditionTimeout
353 pollInterval:kGREYConditionPollInterval];
354 NSString* errorString = [NSString
355 stringWithFormat:@"The modal of class %@ should be loaded.", klass];
356 GREYAssertTrue(modalViewDismissed, errorString);
357 }
358
359 // Checks that the QRScannerViewController is presenting a UIAlertController and
360 // that the title of this alert corresponds to |state|.
361 - (void)assertQRScannerIsPresentingADialogForState:(CameraState)state {
362 [self assertModalOfClass:[UIAlertController class]
363 isPresentedBy:[[self currentBVC] presentedViewController]];
364 [[EarlGrey
365 selectElementWithMatcher:grey_text([self dialogTitleForState:state])]
366 assertWithMatcher:grey_notNil()];
367 }
368
369 // Checks that there is no visible alert with title corresponding to |state|.
370 - (void)assertQRScannerIsNotPresentingADialogForState:(CameraState)state {
371 [[EarlGrey
372 selectElementWithMatcher:grey_text([self dialogTitleForState:state])]
373 assertWithMatcher:grey_nil()];
374 }
375
376 // Returns the expected title for the dialog which is presented for |state|.
377 - (NSString*)dialogTitleForState:(CameraState)state {
378 base::string16 appName = base::UTF8ToUTF16(version_info::GetProductName());
379 switch (state) {
380 case CAMERA_AVAILABLE:
381 case CAMERA_NOT_LOADED:
382 return nil;
383 case CAMERA_IN_USE_BY_ANOTHER_APPLICATION:
384 return l10n_util::GetNSString(
385 IDS_IOS_QR_SCANNER_CAMERA_IN_USE_ALERT_TITLE);
386 case CAMERA_PERMISSION_DENIED:
387 return l10n_util::GetNSString(
388 IDS_IOS_QR_SCANNER_CAMERA_PERMISSIONS_HELP_TITLE_GO_TO_SETTINGS);
389 case CAMERA_UNAVAILABLE:
390 return l10n_util::GetNSString(
391 IDS_IOS_QR_SCANNER_CAMERA_UNAVAILABLE_ALERT_TITLE);
392 case MULTIPLE_FOREGROUND_APPS:
393 return l10n_util::GetNSString(
394 IDS_IOS_QR_SCANNER_MULTIPLE_FOREGROUND_APPS_ALERT_TITLE);
395 }
396 }
397
398 #pragma mark -
399 #pragma mark Helpers for mocks
400
401 // Swizzles the CameraController method cameraControllerWithDelegate: to return
402 // |cameraControllerMock| instead of a new instance of CameraController.
403 - (void)swizzleCameraController:(id)cameraControllerMock {
404 CameraController* (^swizzleCameraControllerBlock)(
405 id<CameraControllerDelegate>) = ^(id<CameraControllerDelegate> delegate) {
406 // |initWithDelegate:| must return an object with a return count of 1
407 // because it is preceded by a call to |alloc|.
408 return [cameraControllerMock retain];
409 };
410
411 camera_controller_swizzler_.reset(new ScopedBlockSwizzler(
412 [CameraController class], @selector(initWithDelegate:),
413 swizzleCameraControllerBlock));
414 }
415
416 // Swizzles the WebToolbarController loadGURLFromLocationBarBlock:transition:
417 // method to load |searchURL| instead of the generated search URL.
418 - (void)swizzleWebToolbarControllerLoadGURLFromLocationBar:
419 (const GURL&)searchURL {
420 void (^loadGURLFromLocationBarBlock)(WebToolbarController*, const GURL&,
421 ui::PageTransition) =
422 ^void(WebToolbarController* self, const GURL& url,
423 ui::PageTransition transition) {
424 [self.urlLoader loadURL:searchURL
425 referrer:web::Referrer()
426 transition:transition
427 rendererInitiated:NO];
428 [self cancelOmniboxEdit];
429 };
430
431 load_GURL_from_location_bar_swizzler_.reset(
432 new ScopedBlockSwizzler([WebToolbarController class],
433 @selector(loadGURLFromLocationBar:transition:),
434 loadGURLFromLocationBarBlock));
435 }
436
437 // Creates a new CameraController mock with camera permission granted if
438 // |granted| is set to YES.
439 - (id)getCameraControllerMockWithAuthorizationStatus:
440 (AVAuthorizationStatus)authorizationStatus {
441 id mock = [OCMockObject mockForClass:[CameraController class]];
442 [[[mock stub] andReturnValue:OCMOCK_VALUE(authorizationStatus)]
443 getAuthorizationStatus];
444 return mock;
445 }
446
447 #pragma mark delegate calls
448
449 // Calls |cameraStateChanged:| on the presented QRScannerViewController.
450 - (void)callCameraStateChanged:(CameraState)state {
451 QRScannerViewController* vc =
452 (QRScannerViewController*)[[self currentBVC] presentedViewController];
453 [vc cameraStateChanged:state];
454 }
455
456 // Calls |torchStateChanged:| on the presented QRScannerViewController.
457 - (void)callTorchStateChanged:(BOOL)torchIsOn {
458 QRScannerViewController* vc =
459 (QRScannerViewController*)[[self currentBVC] presentedViewController];
460 [vc torchStateChanged:torchIsOn];
461 }
462
463 // Calls |torchAvailabilityChanged:| on the presented QRScannerViewController.
464 - (void)callTorchAvailabilityChanged:(BOOL)torchIsAvailable {
465 QRScannerViewController* vc =
466 (QRScannerViewController*)[[self currentBVC] presentedViewController];
467 [vc torchAvailabilityChanged:torchIsAvailable];
468 }
469
470 // Calls |receiveQRScannerResult:| on the presented QRScannerViewController.
471 - (void)callReceiveQRScannerResult:(NSString*)result {
472 QRScannerViewController* vc =
473 (QRScannerViewController*)[[self currentBVC] presentedViewController];
474 [vc receiveQRScannerResult:result loadImmediately:NO];
475 }
476
477 #pragma mark expectations
478
479 // Adds functions which are expected to be called when the
480 // QRScannerViewController is presented to |cameraControllerMock|.
481 - (void)addCameraControllerInitializationExpectations:(id)cameraControllerMock {
482 [[cameraControllerMock expect] setTorchMode:AVCaptureTorchModeOff];
483 [[cameraControllerMock expect] loadCaptureSession:[OCMArg any]];
484 [[cameraControllerMock expect] startRecording];
485 }
486
487 // Adds functions which are expected to be called when the
488 // QRScannerViewController is dismissed to |cameraControllerMock|.
489 - (void)addCameraControllerDismissalExpectations:(id)cameraControllerMock {
490 [[cameraControllerMock expect] setTorchMode:AVCaptureTorchModeOff];
491 [[cameraControllerMock expect] stopRecording];
492 }
493
494 // Adds functions which are expected to be called when the torch is switched on
495 // to |cameraControllerMock|.
496 - (void)addCameraControllerTorchOnExpectations:(id)cameraControllerMock {
497 [[[cameraControllerMock expect] andReturnValue:@NO] isTorchActive];
498 [[cameraControllerMock expect] setTorchMode:AVCaptureTorchModeOn];
499 }
500
501 // Adds functions which are expected to be called when the torch is switched off
502 // to |cameraControllerMock|.
503 - (void)addCameraControllerTorchOffExpectations:(id)cameraControllerMock {
504 [[[cameraControllerMock expect] andReturnValue:@YES] isTorchActive];
505 [[cameraControllerMock expect] setTorchMode:AVCaptureTorchModeOff];
506 }
507
508 #pragma mark -
509 #pragma mark Tests
510
511 // Tests that the close button, camera preview, viewport caption, and the torch
512 // button are visible if the camera is available. The preview is delayed.
513 - (void)testQRScannerUIIsShown {
514 id cameraControllerMock =
515 [self getCameraControllerMockWithAuthorizationStatus:
516 AVAuthorizationStatusAuthorized];
517 [self swizzleCameraController:cameraControllerMock];
518
519 // Open the QR scanner.
520 [self showQRScannerAndCheckLayoutWithCameraMock:cameraControllerMock];
521
522 // Preview is loaded and camera is ready to be displayed.
523 [self assertQRScannerUIIsVisibleWithTorch:NO];
524
525 // Close the QR scanner.
526 [self closeQRScannerWithCameraMock:cameraControllerMock];
527 [cameraControllerMock verify];
528 }
529
530 // Tests that the torch is switched on and off when pressing the torch button,
531 // and that the button icon changes accordingly.
532 - (void)testTurningTorchOnAndOff {
533 id cameraControllerMock =
534 [self getCameraControllerMockWithAuthorizationStatus:
535 AVAuthorizationStatusAuthorized];
536 [self swizzleCameraController:cameraControllerMock];
537
538 // Open the QR scanner.
539 [self showQRScannerAndCheckLayoutWithCameraMock:cameraControllerMock];
540
541 // Torch becomes available.
542 [self callTorchAvailabilityChanged:YES];
543 [self assertQRScannerUIIsVisibleWithTorch:YES];
544
545 // Turn torch on.
546 [self addCameraControllerTorchOnExpectations:cameraControllerMock];
547 [self assertTorchOffButtonIsVisible];
548 tapButton(qrScannerTorchOffButton());
549 [self assertTorchOffButtonIsVisible];
550
551 // Torch becomes active.
552 [self callTorchStateChanged:YES];
553 [self assertTorchOnButtonIsVisible];
554
555 // Turn torch off.
556 [self addCameraControllerTorchOffExpectations:cameraControllerMock];
557 tapButton(qrScannerTorchOnButton());
558 [self assertTorchOnButtonIsVisible];
559
560 // Torch becomes inactive.
561 [self callTorchStateChanged:NO];
562 [self assertTorchOffButtonIsVisible];
563
564 // Close the QR scanner.
565 [self closeQRScannerWithCameraMock:cameraControllerMock];
566 [cameraControllerMock verify];
567 }
568
569 // Tests that if the QR scanner is closed while the torch is on, the torch is
570 // switched off and the correct button indicating that the torch is off is shown
571 // when the scanner is opened again.
572 - (void)testTorchButtonIsResetWhenQRScannerIsReopened {
573 id cameraControllerMock =
574 [self getCameraControllerMockWithAuthorizationStatus:
575 AVAuthorizationStatusAuthorized];
576 [self swizzleCameraController:cameraControllerMock];
577
578 // Open the QR scanner.
579 [self showQRScannerAndCheckLayoutWithCameraMock:cameraControllerMock];
580 [self assertQRScannerUIIsVisibleWithTorch:NO];
581 [self callTorchAvailabilityChanged:YES];
582 [self assertQRScannerUIIsVisibleWithTorch:YES];
583
584 // Turn torch on.
585 [self addCameraControllerTorchOnExpectations:cameraControllerMock];
586 tapButton(qrScannerTorchOffButton());
587 [self callTorchStateChanged:YES];
588 [self assertTorchOnButtonIsVisible];
589
590 // Close the QR scanner.
591 [self closeQRScannerWithCameraMock:cameraControllerMock];
592
593 // Reopen the QR scanner.
594 [self showQRScannerAndCheckLayoutWithCameraMock:cameraControllerMock];
595 [self callTorchAvailabilityChanged:YES];
596 [self assertTorchOffButtonIsVisible];
597
598 // Close the QR scanner again.
599 [self closeQRScannerWithCameraMock:cameraControllerMock];
600 [cameraControllerMock verify];
601 }
602
603 // Tests that the torch button is disabled when the camera reports that torch
604 // became unavailable.
605 - (void)testTorchButtonIsDisabledWhenTorchBecomesUnavailable {
606 id cameraControllerMock =
607 [self getCameraControllerMockWithAuthorizationStatus:
608 AVAuthorizationStatusAuthorized];
609 [self swizzleCameraController:cameraControllerMock];
610
611 // Open the QR scanner.
612 [self showQRScannerAndCheckLayoutWithCameraMock:cameraControllerMock];
613
614 // Torch becomes available.
615 [self callTorchAvailabilityChanged:YES];
616 [self assertQRScannerUIIsVisibleWithTorch:YES];
617
618 // Torch becomes unavailable.
619 [self callTorchAvailabilityChanged:NO];
620 [self assertQRScannerUIIsVisibleWithTorch:NO];
621
622 // Close the QR scanner.
623 [self closeQRScannerWithCameraMock:cameraControllerMock];
624 [cameraControllerMock verify];
625 }
626
627 #pragma mark dialogs
628
629 // Tests that a UIAlertController is presented instead of the
630 // QRScannerViewController if the camera is unavailable.
631 - (void)testCameraUnavailableDialog {
632 // TODO(crbug.com/663026): Reenable the test for devices.
633 #if !TARGET_IPHONE_SIMULATOR
634 EARL_GREY_TEST_DISABLED(@"Disabled for devices because existing system "
635 @"alerts would prevent app alerts to present "
636 @"correctly.");
637 #endif
638
639 UIViewController* bvc = [self currentBVC];
640 [self assertModalOfClass:[QRScannerViewController class]
641 isNotPresentedBy:bvc];
642 [self assertModalOfClass:[UIAlertController class] isNotPresentedBy:bvc];
643 id cameraControllerMock =
644 [self getCameraControllerMockWithAuthorizationStatus:
645 AVAuthorizationStatusDenied];
646 [self swizzleCameraController:cameraControllerMock];
647
648 showQRScannerWithCommand();
649 [self assertModalOfClass:[QRScannerViewController class]
650 isNotPresentedBy:bvc];
651 [self waitForModalOfClass:[UIAlertController class] toAppearAbove:bvc];
652
653 tapButton(dialogCancelButton());
654 [self waitForModalOfClass:[UIAlertController class] toDisappearFromAbove:bvc];
655 }
656
657 // Tests that a UIAlertController is presented by the QRScannerViewController if
658 // the camera state changes after the QRScannerViewController is presented.
659 - (void)testDialogIsDisplayedIfCameraStateChanges {
660 // TODO(crbug.com/663026): Reenable the test for devices.
661 #if !TARGET_IPHONE_SIMULATOR
662 EARL_GREY_TEST_DISABLED(@"Disabled for devices because existing system "
663 @"alerts would prevent app alerts to present "
664 @"correctly.");
665 #endif
666
667 id cameraControllerMock =
668 [self getCameraControllerMockWithAuthorizationStatus:
669 AVAuthorizationStatusAuthorized];
670 [self swizzleCameraController:cameraControllerMock];
671
672 std::vector<CameraState> tests{MULTIPLE_FOREGROUND_APPS, CAMERA_UNAVAILABLE,
673 CAMERA_PERMISSION_DENIED,
674 CAMERA_IN_USE_BY_ANOTHER_APPLICATION};
675
676 for (const CameraState& state : tests) {
677 [self showQRScannerAndCheckLayoutWithCameraMock:cameraControllerMock];
678 [self callCameraStateChanged:state];
679 [self assertQRScannerIsPresentingADialogForState:state];
680
681 // Close the dialog.
682 [self addCameraControllerDismissalExpectations:cameraControllerMock];
683 tapButton(dialogCancelButton());
684 UIViewController* bvc = [self currentBVC];
685 [self waitForModalOfClass:[QRScannerViewController class]
686 toDisappearFromAbove:bvc];
687 [self assertModalOfClass:[UIAlertController class] isNotPresentedBy:bvc];
688 }
689
690 [cameraControllerMock verify];
691 }
692
693 // Tests that a new dialog replaces an old dialog if the camera state changes.
694 - (void)testDialogIsReplacedIfCameraStateChanges {
695 // TODO(crbug.com/663026): Reenable the test for devices.
696 #if !TARGET_IPHONE_SIMULATOR
697 EARL_GREY_TEST_DISABLED(@"Disabled for devices because existing system "
698 @"alerts would prevent app alerts to present "
699 @"correctly.");
700 #endif
701
702 id cameraControllerMock =
703 [self getCameraControllerMockWithAuthorizationStatus:
704 AVAuthorizationStatusAuthorized];
705 [self swizzleCameraController:cameraControllerMock];
706
707 // Change state to CAMERA_UNAVAILABLE.
708 CameraState currentState = CAMERA_UNAVAILABLE;
709 [self showQRScannerAndCheckLayoutWithCameraMock:cameraControllerMock];
710 [self callCameraStateChanged:currentState];
711 [self assertQRScannerIsPresentingADialogForState:currentState];
712
713 std::vector<CameraState> tests{
714 CAMERA_PERMISSION_DENIED, MULTIPLE_FOREGROUND_APPS,
715 CAMERA_IN_USE_BY_ANOTHER_APPLICATION, CAMERA_UNAVAILABLE};
716
717 for (const CameraState& state : tests) {
718 [self callCameraStateChanged:state];
719 [self assertQRScannerIsPresentingADialogForState:state];
720 [self assertQRScannerIsNotPresentingADialogForState:currentState];
721 currentState = state;
722 }
723
724 // Cancel the dialog.
725 [self addCameraControllerDismissalExpectations:cameraControllerMock];
726 tapButton(dialogCancelButton());
727 [self waitForModalOfClass:[QRScannerViewController class]
728 toDisappearFromAbove:[self currentBVC]];
729 [self assertModalOfClass:[UIAlertController class]
730 isNotPresentedBy:[self currentBVC]];
731
732 [cameraControllerMock verify];
733 }
734
735 // Tests that an error dialog is dismissed if the camera becomes available.
736 - (void)testDialogDismissedIfCameraBecomesAvailable {
737 id cameraControllerMock =
738 [self getCameraControllerMockWithAuthorizationStatus:
739 AVAuthorizationStatusAuthorized];
740 [self swizzleCameraController:cameraControllerMock];
741
742 std::vector<CameraState> tests{CAMERA_IN_USE_BY_ANOTHER_APPLICATION,
743 CAMERA_UNAVAILABLE, MULTIPLE_FOREGROUND_APPS,
744 CAMERA_PERMISSION_DENIED};
745
746 for (const CameraState& state : tests) {
747 [self showQRScannerAndCheckLayoutWithCameraMock:cameraControllerMock];
748 [self callCameraStateChanged:state];
749 [self assertQRScannerIsPresentingADialogForState:state];
750
751 // Change state to CAMERA_AVAILABLE.
752 [self callCameraStateChanged:CAMERA_AVAILABLE];
753 [self assertQRScannerIsNotPresentingADialogForState:state];
754 [self closeQRScannerWithCameraMock:cameraControllerMock];
755 }
756
757 [cameraControllerMock verify];
758 }
759
760 #pragma mark scanned result
761
762 // A helper function for testing that the view controller correctly passes the
763 // received results to its delegate and that pages can be loaded. The result
764 // received from the camera controller is in |result|, |response| is the
765 // expected response on the loaded page, and |editString| is a nullable string
766 // which can be appended to the response in the omnibox before the page is
767 // loaded.
768 - (void)doTestReceivingResult:(std::string)result
769 response:(std::string)response
770 edit:(NSString*)editString {
771 id cameraControllerMock =
772 [self getCameraControllerMockWithAuthorizationStatus:
773 AVAuthorizationStatusAuthorized];
774 [self swizzleCameraController:cameraControllerMock];
775
776 // Open the QR scanner.
777 [self showQRScannerAndCheckLayoutWithCameraMock:cameraControllerMock];
778 [self callTorchAvailabilityChanged:YES];
779 [self assertQRScannerUIIsVisibleWithTorch:YES];
780
781 // Receive a scanned result from the camera.
782 [self addCameraControllerDismissalExpectations:cameraControllerMock];
783 [self callReceiveQRScannerResult:base::SysUTF8ToNSString(result)];
784
785 [self waitForModalOfClass:[QRScannerViewController class]
786 toDisappearFromAbove:[self currentBVC]];
787 [cameraControllerMock verify];
788
789 // Optionally edit the text in the omnibox before pressing return.
790 [self assertOmniboxIsVisibleWithText:result];
791 if (editString != nil) {
792 editOmniboxTextAndTapKeyboardReturn(result, editString);
793 } else {
794 tapKeyboardReturnKeyInOmniboxWithText(result);
795 }
796 [self assertTestURLIsLoaded:response];
797
798 // Press the back button to get back to the NTP.
799 tapButton(webToolbarBackButton());
800 [self assertModalOfClass:[QRScannerViewController class]
801 isNotPresentedBy:[self currentBVC]];
802 }
803
804 // Test that the correct page is loaded if the scanner result is a URL.
805 - (void)testReceivingQRScannerURLResult {
806 [self doTestReceivingResult:_testURL.GetContent()
807 response:kTestURLResponse
808 edit:nil];
809 }
810
811 // Test that the correct page is loaded if the scanner result is a URL which is
812 // then manually edited.
813 - (void)testReceivingQRScannerURLResultAndEditingTheURL {
814 [self doTestReceivingResult:_testURL.GetContent()
815 response:kTestURLEditedResponse
816 edit:@"\b\bedited/"];
817 }
818
819 // Test that the correct page is loaded if the scanner result is a search query.
820 - (void)testReceivingQRScannerSearchQueryResult {
821 [self swizzleWebToolbarControllerLoadGURLFromLocationBar:_testQuery];
822 [self doTestReceivingResult:kTestQuery response:kTestQueryResponse edit:nil];
823 }
824
825 // Test that the correct page is loaded if the scanner result is a search query
826 // which is then manually edited.
827 - (void)testReceivingQRScannerSearchQueryResultAndEditingTheQuery {
828 [self swizzleWebToolbarControllerLoadGURLFromLocationBar:_testQueryEdited];
829 [self doTestReceivingResult:kTestQuery
830 response:kTestQueryEditedResponse
831 edit:@"\bedited"];
832 }
833
834 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698