1// Copyright (c) 2012 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#include "base/bind.h"
6#include "base/run_loop.h"
7#include "base/strings/utf_string_conversions.h"
8#include "chrome/browser/speech/speech_recognition_bubble_controller.h"
9#include "chrome/browser/ui/browser.h"
10#include "chrome/browser/ui/browser_tabstrip.h"
11#include "chrome/browser/ui/tabs/tab_strip_model.h"
12#include "chrome/test/base/browser_with_test_window_test.h"
13#include "chrome/test/base/testing_profile.h"
14#include "content/public/test/test_browser_thread.h"
15#include "testing/gtest/include/gtest/gtest.h"
16#include "ui/gfx/rect.h"
17
18using content::BrowserThread;
19using content::WebContents;
20
21namespace speech {
22
23// A mock bubble class which fakes a focus change or recognition cancel by the
24// user and closing of the info bubble.
25class MockSpeechRecognitionBubble : public SpeechRecognitionBubbleBase {
26 public:
27  enum BubbleType {
28    BUBBLE_TEST_FOCUS_CHANGED,
29    BUBBLE_TEST_CLICK_CANCEL,
30    BUBBLE_TEST_CLICK_TRY_AGAIN,
31  };
32
33  MockSpeechRecognitionBubble(WebContents* web_contents,
34                        Delegate* delegate,
35                        const gfx::Rect&)
36      : SpeechRecognitionBubbleBase(web_contents) {
37    VLOG(1) << "MockSpeechRecognitionBubble created";
38    base::MessageLoop::current()->PostTask(
39        FROM_HERE, base::Bind(&InvokeDelegate, delegate));
40  }
41
42  static void InvokeDelegate(Delegate* delegate) {
43    VLOG(1) << "MockSpeechRecognitionBubble invoking delegate for type "
44            << type_;
45    switch (type_) {
46      case BUBBLE_TEST_FOCUS_CHANGED:
47        delegate->InfoBubbleFocusChanged();
48        break;
49      case BUBBLE_TEST_CLICK_CANCEL:
50        delegate->InfoBubbleButtonClicked(
51            SpeechRecognitionBubble::BUTTON_CANCEL);
52        break;
53      case BUBBLE_TEST_CLICK_TRY_AGAIN:
54        delegate->InfoBubbleButtonClicked(
55            SpeechRecognitionBubble::BUTTON_TRY_AGAIN);
56        break;
57    }
58  }
59
60  static void set_type(BubbleType type) {
61    type_ = type;
62  }
63  static BubbleType type() {
64    return type_;
65  }
66
67  virtual void Show() OVERRIDE {}
68  virtual void Hide() OVERRIDE {}
69  virtual void UpdateLayout() OVERRIDE {}
70  virtual void UpdateImage() OVERRIDE {}
71
72 private:
73  static BubbleType type_;
74};
75
76// The test fixture.
77class SpeechRecognitionBubbleControllerTest
78    : public SpeechRecognitionBubbleControllerDelegate,
79      public BrowserWithTestWindowTest {
80 public:
81  SpeechRecognitionBubbleControllerTest()
82      : BrowserWithTestWindowTest(),
83        cancel_clicked_(false),
84        try_again_clicked_(false),
85        focus_changed_(false),
86        controller_(new SpeechRecognitionBubbleController(this)) {
87    EXPECT_EQ(NULL, test_fixture_);
88    test_fixture_ = this;
89  }
90
91  virtual ~SpeechRecognitionBubbleControllerTest() {
92    test_fixture_ = NULL;
93  }
94
95  // SpeechRecognitionBubbleControllerDelegate methods.
96  virtual void InfoBubbleButtonClicked(
97      int session_id,
98      SpeechRecognitionBubble::Button button) OVERRIDE {
99    VLOG(1) << "Received InfoBubbleButtonClicked for button " << button;
100    EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
101    if (button == SpeechRecognitionBubble::BUTTON_CANCEL) {
102      cancel_clicked_ = true;
103    } else if (button == SpeechRecognitionBubble::BUTTON_TRY_AGAIN) {
104      try_again_clicked_ = true;
105    }
106  }
107
108  virtual void InfoBubbleFocusChanged(int session_id) OVERRIDE {
109    VLOG(1) << "Received InfoBubbleFocusChanged";
110    EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
111    focus_changed_ = true;
112  }
113
114  // testing::Test methods.
115  virtual void SetUp() {
116    BrowserWithTestWindowTest::SetUp();
117    SpeechRecognitionBubble::set_factory(
118        &SpeechRecognitionBubbleControllerTest::CreateBubble);
119  }
120
121  virtual void TearDown() {
122    SpeechRecognitionBubble::set_factory(NULL);
123    BrowserWithTestWindowTest::TearDown();
124  }
125
126  static void ActivateBubble() {
127    if (MockSpeechRecognitionBubble::type() !=
128        MockSpeechRecognitionBubble::BUBBLE_TEST_FOCUS_CHANGED) {
129      test_fixture_->controller_->SetBubbleMessage(ASCIIToUTF16("Test"));
130    }
131  }
132
133  static SpeechRecognitionBubble* CreateBubble(
134      WebContents* web_contents,
135      SpeechRecognitionBubble::Delegate* delegate,
136      const gfx::Rect& element_rect) {
137    EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI));
138    // Set up to activate the bubble soon after it gets created, since we test
139    // events sent by the bubble and those are handled only when the bubble is
140    // active.
141    base::MessageLoop::current()->PostTask(FROM_HERE,
142                                           base::Bind(&ActivateBubble));
143
144    // The |web_contents| parameter would be NULL since the dummy session id
145    // passed to CreateBubble would not have matched any active tab. So get a
146    // real WebContents pointer from the test fixture and pass that, because
147    // the bubble controller registers for tab close notifications which need
148    // a valid WebContents.
149    web_contents =
150        test_fixture_->browser()->tab_strip_model()->GetActiveWebContents();
151    return new MockSpeechRecognitionBubble(web_contents, delegate,
152                                           element_rect);
153  }
154
155 protected:
156  bool cancel_clicked_;
157  bool try_again_clicked_;
158  bool focus_changed_;
159  scoped_refptr<SpeechRecognitionBubbleController> controller_;
160
161  static const int kBubbleSessionId;
162  static SpeechRecognitionBubbleControllerTest* test_fixture_;
163};
164
165SpeechRecognitionBubbleControllerTest*
166SpeechRecognitionBubbleControllerTest::test_fixture_ = NULL;
167
168const int SpeechRecognitionBubbleControllerTest::kBubbleSessionId = 1;
169
170MockSpeechRecognitionBubble::BubbleType MockSpeechRecognitionBubble::type_ =
171    MockSpeechRecognitionBubble::BUBBLE_TEST_FOCUS_CHANGED;
172
173// Test that the speech bubble UI gets created in the UI thread and that the
174// focus changed callback comes back in the IO thread.
175TEST_F(SpeechRecognitionBubbleControllerTest, TestFocusChanged) {
176  MockSpeechRecognitionBubble::set_type(
177      MockSpeechRecognitionBubble::BUBBLE_TEST_FOCUS_CHANGED);
178
179  controller_->CreateBubble(kBubbleSessionId, 1, 1, gfx::Rect(1, 1));
180  base::RunLoop().RunUntilIdle();
181  EXPECT_TRUE(focus_changed_);
182  EXPECT_FALSE(cancel_clicked_);
183  EXPECT_FALSE(try_again_clicked_);
184  controller_->CloseBubble();
185}
186
187// Test that the speech bubble UI gets created in the UI thread and that the
188// recognition cancelled callback comes back in the IO thread.
189TEST_F(SpeechRecognitionBubbleControllerTest, TestRecognitionCancelled) {
190  MockSpeechRecognitionBubble::set_type(
191      MockSpeechRecognitionBubble::BUBBLE_TEST_CLICK_CANCEL);
192
193  controller_->CreateBubble(kBubbleSessionId, 1, 1, gfx::Rect(1, 1));
194  base::RunLoop().RunUntilIdle();
195  EXPECT_TRUE(cancel_clicked_);
196  EXPECT_FALSE(try_again_clicked_);
197  EXPECT_FALSE(focus_changed_);
198  controller_->CloseBubble();
199}
200
201// Test that the speech bubble UI gets created in the UI thread and that the
202// try-again button click event comes back in the IO thread.
203TEST_F(SpeechRecognitionBubbleControllerTest, TestTryAgainClicked) {
204  MockSpeechRecognitionBubble::set_type(
205      MockSpeechRecognitionBubble::BUBBLE_TEST_CLICK_TRY_AGAIN);
206
207  controller_->CreateBubble(kBubbleSessionId, 1, 1, gfx::Rect(1, 1));
208  base::RunLoop().RunUntilIdle();
209  EXPECT_FALSE(cancel_clicked_);
210  EXPECT_TRUE(try_again_clicked_);
211  EXPECT_FALSE(focus_changed_);
212  controller_->CloseBubble();
213}
214
215}  // namespace speech
216