blob: b508f1ea7432ef88ae6aa316d1104f046a1f3e5b [file] [log] [blame]
// Copyright 2019 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.
#include "chrome/browser/ui/views/qrcode_generator/qrcode_generator_bubble.h"
#include "base/scoped_observation.h"
#include "chrome/services/qrcode_generator/public/cpp/qrcode_generator_service.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/view_observer.h"
#include "url/gurl.h"
namespace qrcode_generator {
namespace {
using QRCodeGeneratorBubbleTest = testing::Test;
TEST_F(QRCodeGeneratorBubbleTest, SuggestedDownloadURLNoIP) {
EXPECT_EQ(QRCodeGeneratorBubble::GetQRCodeFilenameForURL(GURL("10.1.2.3")),
u"qrcode_chrome.png");
EXPECT_EQ(QRCodeGeneratorBubble::GetQRCodeFilenameForURL(
GURL("https://chromium.org")),
u"qrcode_chromium.org.png");
EXPECT_EQ(
QRCodeGeneratorBubble::GetQRCodeFilenameForURL(GURL("text, not url")),
u"qrcode_chrome.png");
}
TEST_F(QRCodeGeneratorBubbleTest, GeneratedCodeHasQuietZone) {
const int kBaseSizeDip = 16;
const int kQuietZoneTiles = 4;
const int kTileToDip = 2;
const int kQuietZoneDip = kQuietZoneTiles * kTileToDip;
SkBitmap base_bitmap;
base_bitmap.allocN32Pixels(kBaseSizeDip, kBaseSizeDip);
base_bitmap.eraseColor(SK_ColorRED);
auto base_image = gfx::ImageSkia::CreateFrom1xBitmap(base_bitmap);
auto image = QRCodeGeneratorBubble::AddQRCodeQuietZone(
base_image,
gfx::Size(kBaseSizeDip / kTileToDip, kBaseSizeDip / kTileToDip));
EXPECT_EQ(base_image.width(), kBaseSizeDip);
EXPECT_EQ(base_image.height(), kBaseSizeDip);
EXPECT_EQ(image.width(), kBaseSizeDip + kQuietZoneDip * 2);
EXPECT_EQ(image.height(), kBaseSizeDip + kQuietZoneDip * 2);
EXPECT_EQ(SK_ColorRED, base_image.bitmap()->getColor(0, 0));
EXPECT_EQ(SK_ColorWHITE, image.bitmap()->getColor(0, 0));
EXPECT_EQ(SK_ColorWHITE,
image.bitmap()->getColor(kQuietZoneDip, kQuietZoneDip - 1));
EXPECT_EQ(SK_ColorWHITE,
image.bitmap()->getColor(kQuietZoneDip - 1, kQuietZoneDip));
EXPECT_EQ(SK_ColorRED,
image.bitmap()->getColor(kQuietZoneDip, kQuietZoneDip));
}
// Test-fake implementation of QRCodeGeneratorService; the real implementation
// can't be used in these tests because it requires spawning a service process.
class FakeQRCodeGeneratorService : public mojom::QRCodeGeneratorService {
public:
FakeQRCodeGeneratorService() = default;
void GenerateQRCode(mojom::GenerateQRCodeRequestPtr request,
GenerateQRCodeCallback callback) override {
pending_callback_ = std::move(callback);
if (run_loop_)
run_loop_->Quit();
}
void WaitForRequest() {
if (HasPendingRequest())
return;
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
}
bool HasPendingRequest() const { return bool(pending_callback_); }
void DeliverResponse(mojom::GenerateQRCodeResponsePtr response) {
CHECK(pending_callback_);
std::move(pending_callback_).Run(std::move(response));
}
private:
GenerateQRCodeCallback pending_callback_;
std::unique_ptr<base::RunLoop> run_loop_;
};
class ViewVisibilityWaiter : public views::ViewObserver {
public:
explicit ViewVisibilityWaiter(views::View* view) {
observation_.Observe(view);
}
void Wait() { run_loop_.Run(); }
void OnViewVisibilityChanged(views::View* view,
views::View* starting_view) override {
run_loop_.Quit();
}
private:
base::ScopedObservation<views::View, views::ViewObserver> observation_{this};
base::RunLoop run_loop_;
};
class QRCodeGeneratorBubbleUITest : public ChromeViewsTestBase {
public:
void SetUp() override {
ChromeViewsTestBase::SetUp();
anchor_widget_.reset(CreateTestWidget().release());
anchor_view_ =
anchor_widget_->SetContentsView(std::make_unique<views::View>());
CHECK(anchor_view_);
auto bubble = std::make_unique<QRCodeGeneratorBubble>(
anchor_view_, nullptr, base::DoNothing(),
GURL("https://www.chromium.org/a"));
bubble->SetQRCodeServiceForTesting(
mojo::Remote<mojom::QRCodeGeneratorService>(
receiver_.BindNewPipeAndPassRemote()));
bubble_ = bubble.get();
bubble_widget_.reset(
views::BubbleDialogDelegateView::CreateBubble(std::move(bubble)));
}
void TearDown() override {
bubble_widget_.reset();
anchor_widget_.reset();
ChromeViewsTestBase::TearDown();
}
QRCodeGeneratorBubble* bubble() { return bubble_; }
views::ImageView* image() { return bubble_->image_for_testing(); }
views::Textfield* textfield() { return bubble_->textfield_for_testing(); }
FakeQRCodeGeneratorService* service() { return &fake_service_; }
private:
WidgetAutoclosePtr anchor_widget_;
views::View* anchor_view_;
QRCodeGeneratorBubble* bubble_;
WidgetAutoclosePtr bubble_widget_;
FakeQRCodeGeneratorService fake_service_;
mojo::Receiver<mojom::QRCodeGeneratorService> receiver_{&fake_service_};
};
// This test is a bit fiddly because mojo imposes asynchronicity on both sender
// and receiver sides of a service. That means that:
// 1. We need to wait for QR code generation requests to arrive at the service
// 2. We need to wait for observable changes to the ImageView to know when
// responses have been delivered
TEST_F(QRCodeGeneratorBubbleUITest, ImageShowsAfterErrorState) {
bubble()->Show();
auto image_showing = [&]() {
return image()->GetVisible() && image()->GetPreferredSize().height() > 0 &&
image()->GetPreferredSize().width() > 0;
};
EXPECT_TRUE(image_showing());
service()->WaitForRequest();
ASSERT_TRUE(service()->HasPendingRequest());
auto error_response = mojom::GenerateQRCodeResponse::New();
error_response->error_code = mojom::QRCodeGeneratorError::UNKNOWN_ERROR;
EXPECT_TRUE(image_showing());
{
ViewVisibilityWaiter waiter(image());
service()->DeliverResponse(std::move(error_response));
waiter.Wait();
EXPECT_FALSE(image_showing());
}
// The UI regenerates the QR code when the user types new text, so synthesize
// that.
textfield()->InsertOrReplaceText(u"https://www.chromium.org/b");
service()->WaitForRequest();
auto ok_response = mojom::GenerateQRCodeResponse::New();
ok_response->error_code = mojom::QRCodeGeneratorError::NONE;
ok_response->bitmap.allocN32Pixels(16, 16);
ok_response->data.resize(16 * 16);
ok_response->data_size = gfx::Size(16, 16);
{
ViewVisibilityWaiter waiter(image());
service()->DeliverResponse(std::move(ok_response));
waiter.Wait();
EXPECT_TRUE(image_showing());
}
}
} // namespace
} // namespace qrcode_generator