1 // Copyright 2013 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 "ui/base/ime/input_method_base.h"
6 
7 #include <memory>
8 
9 #include "base/gtest_prod_util.h"
10 #include "base/macros.h"
11 #include "base/run_loop.h"
12 #include "base/scoped_observer.h"
13 #include "base/test/task_environment.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "ui/base/ime/dummy_text_input_client.h"
16 #include "ui/base/ime/input_method_observer.h"
17 #include "ui/events/event.h"
18 
19 namespace ui {
20 namespace {
21 
22 class ClientChangeVerifier {
23  public:
24   ClientChangeVerifier() = default;
25 
26   // Expects that focused text input client will not be changed.
ExpectClientDoesNotChange()27   void ExpectClientDoesNotChange() {
28     previous_client_ = nullptr;
29     next_client_ = nullptr;
30     call_expected_ = false;
31     on_will_change_focused_client_called_ = false;
32     on_did_change_focused_client_called_ = false;
33     on_text_input_state_changed_ = false;
34   }
35 
36   // Expects that focused text input client will be changed from
37   // |previous_client| to |next_client|.
ExpectClientChange(TextInputClient * previous_client,TextInputClient * next_client)38   void ExpectClientChange(TextInputClient* previous_client,
39                           TextInputClient* next_client) {
40     previous_client_ = previous_client;
41     next_client_ = next_client;
42     call_expected_ = true;
43     on_will_change_focused_client_called_ = false;
44     on_did_change_focused_client_called_ = false;
45     on_text_input_state_changed_ = false;
46   }
47 
48   // Verifies the result satisfies the expectation or not.
Verify()49   void Verify() {
50     EXPECT_EQ(call_expected_, on_will_change_focused_client_called_);
51     EXPECT_EQ(call_expected_, on_did_change_focused_client_called_);
52     EXPECT_EQ(call_expected_, on_text_input_state_changed_);
53   }
54 
OnWillChangeFocusedClient(TextInputClient * focused_before,TextInputClient * focused)55   void OnWillChangeFocusedClient(TextInputClient* focused_before,
56                                  TextInputClient* focused) {
57     EXPECT_TRUE(call_expected_);
58 
59     // Check arguments
60     EXPECT_EQ(previous_client_, focused_before);
61     EXPECT_EQ(next_client_, focused);
62 
63     // Check call order
64     EXPECT_FALSE(on_will_change_focused_client_called_);
65     EXPECT_FALSE(on_did_change_focused_client_called_);
66     EXPECT_FALSE(on_text_input_state_changed_);
67 
68     on_will_change_focused_client_called_ = true;
69   }
70 
OnDidChangeFocusedClient(TextInputClient * focused_before,TextInputClient * focused)71   void OnDidChangeFocusedClient(TextInputClient* focused_before,
72                                 TextInputClient* focused) {
73     EXPECT_TRUE(call_expected_);
74 
75     // Check arguments
76     EXPECT_EQ(previous_client_, focused_before);
77     EXPECT_EQ(next_client_, focused);
78 
79     // Check call order
80     EXPECT_TRUE(on_will_change_focused_client_called_);
81     EXPECT_FALSE(on_did_change_focused_client_called_);
82     EXPECT_FALSE(on_text_input_state_changed_);
83 
84     on_did_change_focused_client_called_ = true;
85   }
86 
OnTextInputStateChanged(const TextInputClient * client)87   void OnTextInputStateChanged(const TextInputClient* client) {
88     EXPECT_TRUE(call_expected_);
89 
90     // Check arguments
91     EXPECT_EQ(next_client_, client);
92 
93     // Check call order
94     EXPECT_TRUE(on_will_change_focused_client_called_);
95     EXPECT_TRUE(on_did_change_focused_client_called_);
96     EXPECT_FALSE(on_text_input_state_changed_);
97 
98     on_text_input_state_changed_ = true;
99   }
100 
101  private:
102   TextInputClient* previous_client_ = nullptr;
103   TextInputClient* next_client_ = nullptr;
104   bool call_expected_ = false;
105   bool on_will_change_focused_client_called_ = false;
106   bool on_did_change_focused_client_called_ = false;
107   bool on_text_input_state_changed_ = false;
108 
109   DISALLOW_COPY_AND_ASSIGN(ClientChangeVerifier);
110 };
111 
112 class InputMethodBaseTest : public testing::Test {
113  private:
114   base::test::SingleThreadTaskEnvironment task_environment_{
115       base::test::SingleThreadTaskEnvironment::MainThreadType::UI};
116 };
117 
118 class MockInputMethodBase : public InputMethodBase {
119  public:
MockInputMethodBase(ClientChangeVerifier * verifier)120   explicit MockInputMethodBase(ClientChangeVerifier* verifier)
121       : InputMethodBase(nullptr), verifier_(verifier) {}
122   ~MockInputMethodBase() override = default;
123 
124  private:
125   // InputMethod:
DispatchKeyEvent(ui::KeyEvent *)126   ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent*) override {
127     return ui::EventDispatchDetails();
128   }
OnCaretBoundsChanged(const TextInputClient * client)129   void OnCaretBoundsChanged(const TextInputClient* client) override {}
CancelComposition(const TextInputClient * client)130   void CancelComposition(const TextInputClient* client) override {}
OnInputLocaleChanged()131   void OnInputLocaleChanged() override {}
IsInputLocaleCJK() const132   bool IsInputLocaleCJK() const override { return false; }
IsCandidatePopupOpen() const133   bool IsCandidatePopupOpen() const override { return false; }
134 
135   // InputMethodBase:
OnWillChangeFocusedClient(TextInputClient * focused_before,TextInputClient * focused)136   void OnWillChangeFocusedClient(TextInputClient* focused_before,
137                                  TextInputClient* focused) override {
138     verifier_->OnWillChangeFocusedClient(focused_before, focused);
139   }
140 
OnDidChangeFocusedClient(TextInputClient * focused_before,TextInputClient * focused)141   void OnDidChangeFocusedClient(TextInputClient* focused_before,
142                                 TextInputClient* focused) override {
143     verifier_->OnDidChangeFocusedClient(focused_before, focused);
144   }
145 
146   // Not owned.
147   ClientChangeVerifier* const verifier_;
148 
149   FRIEND_TEST_ALL_PREFIXES(InputMethodBaseTest, CandidateWindowEvents);
150   DISALLOW_COPY_AND_ASSIGN(MockInputMethodBase);
151 };
152 
153 class MockInputMethodObserver : public InputMethodObserver {
154  public:
MockInputMethodObserver(ClientChangeVerifier * verifier)155   explicit MockInputMethodObserver(ClientChangeVerifier* verifier)
156       : verifier_(verifier) {
157   }
158   ~MockInputMethodObserver() override = default;
159 
160  private:
OnFocus()161   void OnFocus() override {}
OnBlur()162   void OnBlur() override {}
OnCaretBoundsChanged(const TextInputClient * client)163   void OnCaretBoundsChanged(const TextInputClient* client) override {}
OnTextInputStateChanged(const TextInputClient * client)164   void OnTextInputStateChanged(const TextInputClient* client) override {
165     verifier_->OnTextInputStateChanged(client);
166   }
OnShowVirtualKeyboardIfEnabled()167   void OnShowVirtualKeyboardIfEnabled() override {}
OnInputMethodDestroyed(const InputMethod * client)168   void OnInputMethodDestroyed(const InputMethod* client) override {}
169 
170   // Not owned.
171   ClientChangeVerifier* const verifier_;
172   DISALLOW_COPY_AND_ASSIGN(MockInputMethodObserver);
173 };
174 
175 typedef ScopedObserver<InputMethod, InputMethodObserver>
176     InputMethodScopedObserver;
177 
SetFocusedTextInputClient(InputMethod * input_method,TextInputClient * text_input_client)178 void SetFocusedTextInputClient(InputMethod* input_method,
179                                TextInputClient* text_input_client) {
180   input_method->SetFocusedTextInputClient(text_input_client);
181 }
182 
TEST_F(InputMethodBaseTest,SetFocusedTextInputClient)183 TEST_F(InputMethodBaseTest, SetFocusedTextInputClient) {
184   DummyTextInputClient text_input_client_1st;
185   DummyTextInputClient text_input_client_2nd;
186 
187   ClientChangeVerifier verifier;
188   MockInputMethodBase input_method(&verifier);
189   MockInputMethodObserver input_method_observer(&verifier);
190   InputMethodScopedObserver scoped_observer(&input_method_observer);
191   scoped_observer.Add(&input_method);
192 
193   // Assume that the top-level-widget gains focus.
194   input_method.OnFocus();
195 
196   {
197     SCOPED_TRACE("Focus from nullptr to 1st TextInputClient");
198 
199     ASSERT_EQ(nullptr, input_method.GetTextInputClient());
200     verifier.ExpectClientChange(nullptr, &text_input_client_1st);
201     SetFocusedTextInputClient(&input_method, &text_input_client_1st);
202     EXPECT_EQ(&text_input_client_1st, input_method.GetTextInputClient());
203     verifier.Verify();
204   }
205 
206   {
207     SCOPED_TRACE("Redundant focus events must be ignored");
208     verifier.ExpectClientDoesNotChange();
209     SetFocusedTextInputClient(&input_method, &text_input_client_1st);
210     verifier.Verify();
211   }
212 
213   {
214     SCOPED_TRACE("Focus from 1st to 2nd TextInputClient");
215 
216     ASSERT_EQ(&text_input_client_1st, input_method.GetTextInputClient());
217     verifier.ExpectClientChange(&text_input_client_1st,
218                                 &text_input_client_2nd);
219     SetFocusedTextInputClient(&input_method, &text_input_client_2nd);
220     EXPECT_EQ(&text_input_client_2nd, input_method.GetTextInputClient());
221     verifier.Verify();
222   }
223 
224   {
225     SCOPED_TRACE("Focus from 2nd TextInputClient to nullptr");
226 
227     ASSERT_EQ(&text_input_client_2nd, input_method.GetTextInputClient());
228     verifier.ExpectClientChange(&text_input_client_2nd, nullptr);
229     SetFocusedTextInputClient(&input_method, nullptr);
230     EXPECT_EQ(nullptr, input_method.GetTextInputClient());
231     verifier.Verify();
232   }
233 
234   {
235     SCOPED_TRACE("Redundant focus events must be ignored");
236     verifier.ExpectClientDoesNotChange();
237     SetFocusedTextInputClient(&input_method, nullptr);
238     verifier.Verify();
239   }
240 }
241 
TEST_F(InputMethodBaseTest,DetachTextInputClient)242 TEST_F(InputMethodBaseTest, DetachTextInputClient) {
243   DummyTextInputClient text_input_client;
244   DummyTextInputClient text_input_client_the_other;
245 
246   ClientChangeVerifier verifier;
247   MockInputMethodBase input_method(&verifier);
248   MockInputMethodObserver input_method_observer(&verifier);
249   InputMethodScopedObserver scoped_observer(&input_method_observer);
250   scoped_observer.Add(&input_method);
251 
252   // Assume that the top-level-widget gains focus.
253   input_method.OnFocus();
254 
255   // Initialize for the next test.
256   {
257     verifier.ExpectClientChange(nullptr, &text_input_client);
258     input_method.SetFocusedTextInputClient(&text_input_client);
259     verifier.Verify();
260   }
261 
262   {
263     SCOPED_TRACE("DetachTextInputClient must be ignored for other clients");
264     ASSERT_EQ(&text_input_client, input_method.GetTextInputClient());
265     verifier.ExpectClientDoesNotChange();
266     input_method.DetachTextInputClient(&text_input_client_the_other);
267     EXPECT_EQ(&text_input_client, input_method.GetTextInputClient());
268     verifier.Verify();
269   }
270 
271   {
272     SCOPED_TRACE("DetachTextInputClient must succeed even after the "
273                  "top-level loses the focus");
274 
275     ASSERT_EQ(&text_input_client, input_method.GetTextInputClient());
276     input_method.OnBlur();
277     input_method.OnFocus();
278     verifier.ExpectClientChange(&text_input_client, nullptr);
279     input_method.DetachTextInputClient(&text_input_client);
280     EXPECT_EQ(nullptr, input_method.GetTextInputClient());
281     verifier.Verify();
282   }
283 }
284 
285 }  // namespace
286 }  // namespace ui
287