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