| // Copyright 2018 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 "ui/base/ime/win/tsf_text_store.h" |
| |
| #include <initguid.h> // for GUID_NULL and GUID_PROP_INPUTSCOPE |
| |
| #include <InputScope.h> |
| #include <OleCtl.h> |
| #include <wrl/client.h> |
| |
| #include "base/memory/ref_counted.h" |
| #include "base/stl_util.h" |
| #include "base/win/scoped_com_initializer.h" |
| #include "base/win/scoped_variant.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/ime/text_input_client.h" |
| #include "ui/events/event.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| using testing::_; |
| using testing::Invoke; |
| using testing::Return; |
| |
| namespace ui { |
| namespace { |
| |
| class MockTextInputClient : public TextInputClient { |
| public: |
| ~MockTextInputClient() {} |
| MOCK_METHOD1(SetCompositionText, void(const ui::CompositionText&)); |
| MOCK_METHOD0(ConfirmCompositionText, void()); |
| MOCK_METHOD0(ClearCompositionText, void()); |
| MOCK_METHOD1(InsertText, void(const base::string16&)); |
| MOCK_METHOD1(InsertChar, void(const ui::KeyEvent&)); |
| MOCK_CONST_METHOD0(GetTextInputType, ui::TextInputType()); |
| MOCK_CONST_METHOD0(GetTextInputMode, ui::TextInputMode()); |
| MOCK_CONST_METHOD0(GetTextDirection, base::i18n::TextDirection()); |
| MOCK_CONST_METHOD0(GetTextInputFlags, int()); |
| MOCK_CONST_METHOD0(CanComposeInline, bool()); |
| MOCK_CONST_METHOD0(GetCaretBounds, gfx::Rect()); |
| MOCK_CONST_METHOD2(GetCompositionCharacterBounds, bool(uint32_t, gfx::Rect*)); |
| MOCK_CONST_METHOD0(HasCompositionText, bool()); |
| MOCK_CONST_METHOD0(GetFocusReason, ui::TextInputClient::FocusReason()); |
| MOCK_METHOD0(ShouldDoLearning, bool()); |
| MOCK_CONST_METHOD1(GetTextRange, bool(gfx::Range*)); |
| MOCK_CONST_METHOD1(GetCompositionTextRange, bool(gfx::Range*)); |
| MOCK_CONST_METHOD1(GetEditableSelectionRange, bool(gfx::Range*)); |
| MOCK_METHOD1(SetEditableSelectionRange, bool(const gfx::Range&)); |
| MOCK_METHOD1(DeleteRange, bool(const gfx::Range&)); |
| MOCK_CONST_METHOD2(GetTextFromRange, |
| bool(const gfx::Range&, base::string16*)); |
| MOCK_METHOD0(OnInputMethodChanged, void()); |
| MOCK_METHOD1(ChangeTextDirectionAndLayoutAlignment, |
| bool(base::i18n::TextDirection)); |
| MOCK_METHOD2(ExtendSelectionAndDelete, void(size_t, size_t)); |
| MOCK_METHOD1(EnsureCaretNotInRect, void(const gfx::Rect&)); |
| MOCK_CONST_METHOD1(IsTextEditCommandEnabled, bool(TextEditCommand)); |
| MOCK_METHOD1(SetTextEditCommandForNextKeyEvent, void(TextEditCommand)); |
| MOCK_CONST_METHOD0(GetClientSourceForMetrics, ukm::SourceId()); |
| }; |
| |
| class MockStoreACPSink : public ITextStoreACPSink { |
| public: |
| MockStoreACPSink() : ref_count_(0) {} |
| |
| // IUnknown |
| ULONG STDMETHODCALLTYPE AddRef() override { |
| return InterlockedIncrement(&ref_count_); |
| } |
| ULONG STDMETHODCALLTYPE Release() override { |
| const LONG count = InterlockedDecrement(&ref_count_); |
| if (!count) { |
| delete this; |
| return 0; |
| } |
| return static_cast<ULONG>(count); |
| } |
| HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** report) override { |
| if (iid == IID_IUnknown || iid == IID_ITextStoreACPSink) { |
| *report = static_cast<ITextStoreACPSink*>(this); |
| } else { |
| *report = nullptr; |
| return E_NOINTERFACE; |
| } |
| AddRef(); |
| return S_OK; |
| } |
| |
| // ITextStoreACPSink |
| MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE, |
| OnTextChange, |
| HRESULT(DWORD, const TS_TEXTCHANGE*)); |
| MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, OnSelectionChange, HRESULT()); |
| MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE, |
| OnLayoutChange, |
| HRESULT(TsLayoutCode, TsViewCookie)); |
| MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, OnStatusChange, HRESULT(DWORD)); |
| MOCK_METHOD4_WITH_CALLTYPE(STDMETHODCALLTYPE, |
| OnAttrsChange, |
| HRESULT(LONG, LONG, ULONG, const TS_ATTRID*)); |
| MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, OnLockGranted, HRESULT(DWORD)); |
| MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, |
| OnStartEditTransaction, |
| HRESULT()); |
| MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, |
| OnEndEditTransaction, |
| HRESULT()); |
| |
| private: |
| virtual ~MockStoreACPSink() {} |
| |
| volatile LONG ref_count_; |
| }; |
| |
| const HWND kWindowHandle = reinterpret_cast<HWND>(1); |
| |
| } // namespace |
| |
| class TSFTextStoreTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| text_store_ = new TSFTextStore(); |
| sink_ = new MockStoreACPSink(); |
| EXPECT_EQ(S_OK, text_store_->AdviseSink(IID_ITextStoreACPSink, sink_.get(), |
| TS_AS_ALL_SINKS)); |
| text_store_->SetFocusedTextInputClient(kWindowHandle, &text_input_client_); |
| } |
| |
| void TearDown() override { |
| EXPECT_EQ(S_OK, text_store_->UnadviseSink(sink_.get())); |
| sink_ = nullptr; |
| text_store_ = nullptr; |
| } |
| |
| // Accessors to the internal state of TSFTextStore. |
| base::string16* string_buffer() { return &text_store_->string_buffer_; } |
| size_t* committed_size() { return &text_store_->committed_size_; } |
| |
| base::win::ScopedCOMInitializer com_initializer_; |
| MockTextInputClient text_input_client_; |
| scoped_refptr<TSFTextStore> text_store_; |
| scoped_refptr<MockStoreACPSink> sink_; |
| }; |
| |
| class TSFTextStoreTestCallback { |
| public: |
| explicit TSFTextStoreTestCallback(TSFTextStore* text_store) |
| : text_store_(text_store) { |
| CHECK(text_store_); |
| } |
| virtual ~TSFTextStoreTestCallback() {} |
| |
| protected: |
| // Accessors to the internal state of TSFTextStore. |
| bool* edit_flag() { return &text_store_->edit_flag_; } |
| base::string16* string_buffer() { return &text_store_->string_buffer_; } |
| size_t* committed_size() { return &text_store_->committed_size_; } |
| gfx::Range* selection() { return &text_store_->selection_; } |
| ImeTextSpans* text_spans() { return &text_store_->text_spans_; } |
| |
| void SetInternalState(const base::string16& new_string_buffer, |
| LONG new_committed_size, |
| LONG new_selection_start, |
| LONG new_selection_end) { |
| ASSERT_LE(0, new_committed_size); |
| ASSERT_LE(new_committed_size, new_selection_start); |
| ASSERT_LE(new_selection_start, new_selection_end); |
| ASSERT_LE(new_selection_end, static_cast<LONG>(new_string_buffer.size())); |
| *string_buffer() = new_string_buffer; |
| *committed_size() = new_committed_size; |
| selection()->set_start(new_selection_start); |
| selection()->set_end(new_selection_end); |
| } |
| |
| bool HasReadLock() const { return text_store_->HasReadLock(); } |
| bool HasReadWriteLock() const { return text_store_->HasReadWriteLock(); } |
| |
| void GetSelectionTest(LONG expected_acp_start, LONG expected_acp_end) { |
| TS_SELECTION_ACP selection = {}; |
| ULONG fetched = 0; |
| EXPECT_EQ(S_OK, text_store_->GetSelection(0, 1, &selection, &fetched)); |
| EXPECT_EQ(1u, fetched); |
| EXPECT_EQ(expected_acp_start, selection.acpStart); |
| EXPECT_EQ(expected_acp_end, selection.acpEnd); |
| } |
| |
| void SetSelectionTest(LONG acp_start, LONG acp_end, HRESULT expected_result) { |
| TS_SELECTION_ACP selection = {}; |
| selection.acpStart = acp_start; |
| selection.acpEnd = acp_end; |
| selection.style.ase = TS_AE_NONE; |
| selection.style.fInterimChar = 0; |
| EXPECT_EQ(expected_result, text_store_->SetSelection(1, &selection)); |
| if (expected_result == S_OK) { |
| GetSelectionTest(acp_start, acp_end); |
| } |
| } |
| |
| void SetTextTest(LONG acp_start, |
| LONG acp_end, |
| const base::string16& text, |
| HRESULT error_code) { |
| TS_TEXTCHANGE change = {}; |
| ASSERT_EQ(error_code, |
| text_store_->SetText(0, acp_start, acp_end, text.c_str(), |
| text.size(), &change)); |
| if (error_code == S_OK) { |
| EXPECT_EQ(acp_start, change.acpStart); |
| EXPECT_EQ(acp_end, change.acpOldEnd); |
| EXPECT_EQ(acp_start + text.size(), (size_t)change.acpNewEnd); |
| } |
| } |
| |
| void GetTextTest(LONG acp_start, |
| LONG acp_end, |
| const base::string16& expected_string, |
| LONG expected_next_acp) { |
| wchar_t buffer[1024] = {}; |
| ULONG text_buffer_copied = 0; |
| TS_RUNINFO run_info = {}; |
| ULONG run_info_buffer_copied = 0; |
| LONG next_acp = 0; |
| ASSERT_EQ(S_OK, text_store_->GetText(acp_start, acp_end, buffer, 1024, |
| &text_buffer_copied, &run_info, 1, |
| &run_info_buffer_copied, &next_acp)); |
| ASSERT_EQ(expected_string.size(), text_buffer_copied); |
| EXPECT_EQ(expected_string, |
| base::string16(buffer, buffer + text_buffer_copied)); |
| EXPECT_EQ(1u, run_info_buffer_copied); |
| EXPECT_EQ(expected_string.size(), run_info.uCount); |
| EXPECT_EQ(TS_RT_PLAIN, run_info.type); |
| EXPECT_EQ(expected_next_acp, next_acp); |
| } |
| |
| void GetTextErrorTest(LONG acp_start, LONG acp_end, HRESULT error_code) { |
| wchar_t buffer[1024] = {}; |
| ULONG text_buffer_copied = 0; |
| TS_RUNINFO run_info = {}; |
| ULONG run_info_buffer_copied = 0; |
| LONG next_acp = 0; |
| EXPECT_EQ(error_code, |
| text_store_->GetText(acp_start, acp_end, buffer, 1024, |
| &text_buffer_copied, &run_info, 1, |
| &run_info_buffer_copied, &next_acp)); |
| } |
| |
| void InsertTextAtSelectionTest(const wchar_t* buffer, |
| ULONG buffer_size, |
| LONG expected_start, |
| LONG expected_end, |
| LONG expected_change_start, |
| LONG expected_change_old_end, |
| LONG expected_change_new_end) { |
| LONG start = 0; |
| LONG end = 0; |
| TS_TEXTCHANGE change = {}; |
| EXPECT_EQ(S_OK, text_store_->InsertTextAtSelection(0, buffer, buffer_size, |
| &start, &end, &change)); |
| EXPECT_EQ(expected_start, start); |
| EXPECT_EQ(expected_end, end); |
| EXPECT_EQ(expected_change_start, change.acpStart); |
| EXPECT_EQ(expected_change_old_end, change.acpOldEnd); |
| EXPECT_EQ(expected_change_new_end, change.acpNewEnd); |
| } |
| |
| void InsertTextAtSelectionQueryOnlyTest(const wchar_t* buffer, |
| ULONG buffer_size, |
| LONG expected_start, |
| LONG expected_end) { |
| LONG start = 0; |
| LONG end = 0; |
| EXPECT_EQ(S_OK, text_store_->InsertTextAtSelection(TS_IAS_QUERYONLY, buffer, |
| buffer_size, &start, |
| &end, nullptr)); |
| EXPECT_EQ(expected_start, start); |
| EXPECT_EQ(expected_end, end); |
| } |
| |
| void GetTextExtTest(TsViewCookie view_cookie, |
| LONG acp_start, |
| LONG acp_end, |
| LONG expected_left, |
| LONG expected_top, |
| LONG expected_right, |
| LONG expected_bottom) { |
| RECT rect = {}; |
| BOOL clipped = FALSE; |
| EXPECT_EQ(S_OK, text_store_->GetTextExt(view_cookie, acp_start, acp_end, |
| &rect, &clipped)); |
| EXPECT_EQ(expected_left, rect.left); |
| EXPECT_EQ(expected_top, rect.top); |
| EXPECT_EQ(expected_right, rect.right); |
| EXPECT_EQ(expected_bottom, rect.bottom); |
| EXPECT_EQ(FALSE, clipped); |
| } |
| |
| void GetTextExtNoLayoutTest(TsViewCookie view_cookie, |
| LONG acp_start, |
| LONG acp_end) { |
| RECT rect = {}; |
| BOOL clipped = FALSE; |
| EXPECT_EQ(TS_E_NOLAYOUT, text_store_->GetTextExt(view_cookie, acp_start, |
| acp_end, &rect, &clipped)); |
| } |
| |
| scoped_refptr<TSFTextStore> text_store_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TSFTextStoreTestCallback); |
| }; |
| |
| namespace { |
| |
| const HRESULT kInvalidResult = 0x12345678; |
| |
| TEST_F(TSFTextStoreTest, GetStatusTest) { |
| TS_STATUS status = {}; |
| EXPECT_EQ(S_OK, text_store_->GetStatus(&status)); |
| EXPECT_EQ(0u, status.dwDynamicFlags); |
| EXPECT_EQ((ULONG)(TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT), |
| status.dwStaticFlags); |
| } |
| |
| TEST_F(TSFTextStoreTest, QueryInsertTest) { |
| LONG result_start = 0; |
| LONG result_end = 0; |
| *string_buffer() = L""; |
| *committed_size() = 0; |
| EXPECT_EQ(E_INVALIDARG, |
| text_store_->QueryInsert(0, 0, 0, nullptr, &result_end)); |
| EXPECT_EQ(E_INVALIDARG, |
| text_store_->QueryInsert(0, 0, 0, &result_start, nullptr)); |
| EXPECT_EQ(S_OK, |
| text_store_->QueryInsert(0, 0, 0, &result_start, &result_end)); |
| EXPECT_EQ(0, result_start); |
| EXPECT_EQ(0, result_end); |
| *string_buffer() = L"1234"; |
| *committed_size() = 1; |
| EXPECT_EQ(S_OK, |
| text_store_->QueryInsert(0, 1, 0, &result_start, &result_end)); |
| EXPECT_EQ(1, result_start); |
| EXPECT_EQ(1, result_end); |
| EXPECT_EQ(E_INVALIDARG, |
| text_store_->QueryInsert(1, 0, 0, &result_start, &result_end)); |
| EXPECT_EQ(S_OK, |
| text_store_->QueryInsert(2, 2, 0, &result_start, &result_end)); |
| EXPECT_EQ(2, result_start); |
| EXPECT_EQ(2, result_end); |
| EXPECT_EQ(S_OK, |
| text_store_->QueryInsert(2, 3, 0, &result_start, &result_end)); |
| EXPECT_EQ(2, result_start); |
| EXPECT_EQ(3, result_end); |
| EXPECT_EQ(E_INVALIDARG, |
| text_store_->QueryInsert(3, 2, 0, &result_start, &result_end)); |
| EXPECT_EQ(S_OK, |
| text_store_->QueryInsert(3, 4, 0, &result_start, &result_end)); |
| EXPECT_EQ(3, result_start); |
| EXPECT_EQ(4, result_end); |
| EXPECT_EQ(S_OK, |
| text_store_->QueryInsert(3, 5, 0, &result_start, &result_end)); |
| EXPECT_EQ(3, result_start); |
| EXPECT_EQ(4, result_end); |
| } |
| |
| class SyncRequestLockTestCallback : public TSFTextStoreTestCallback { |
| public: |
| explicit SyncRequestLockTestCallback(TSFTextStore* text_store) |
| : TSFTextStoreTestCallback(text_store) {} |
| |
| HRESULT LockGranted1(DWORD flags) { |
| EXPECT_TRUE(HasReadLock()); |
| EXPECT_FALSE(HasReadWriteLock()); |
| return S_OK; |
| } |
| |
| HRESULT LockGranted2(DWORD flags) { |
| EXPECT_TRUE(HasReadLock()); |
| EXPECT_TRUE(HasReadWriteLock()); |
| return S_OK; |
| } |
| |
| HRESULT LockGranted3(DWORD flags) { |
| EXPECT_TRUE(HasReadLock()); |
| EXPECT_FALSE(HasReadWriteLock()); |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result)); |
| EXPECT_EQ(TS_E_SYNCHRONOUS, result); |
| return S_OK; |
| } |
| |
| HRESULT LockGranted4(DWORD flags) { |
| EXPECT_TRUE(HasReadLock()); |
| EXPECT_FALSE(HasReadWriteLock()); |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, |
| text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result)); |
| EXPECT_EQ(TS_E_SYNCHRONOUS, result); |
| return S_OK; |
| } |
| |
| HRESULT LockGranted5(DWORD flags) { |
| EXPECT_TRUE(HasReadLock()); |
| EXPECT_TRUE(HasReadWriteLock()); |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result)); |
| EXPECT_EQ(TS_E_SYNCHRONOUS, result); |
| return S_OK; |
| } |
| |
| HRESULT LockGranted6(DWORD flags) { |
| EXPECT_TRUE(HasReadLock()); |
| EXPECT_TRUE(HasReadWriteLock()); |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, |
| text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result)); |
| EXPECT_EQ(TS_E_SYNCHRONOUS, result); |
| return S_OK; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SyncRequestLockTestCallback); |
| }; |
| |
| TEST_F(TSFTextStoreTest, SynchronousRequestLockTest) { |
| SyncRequestLockTestCallback callback(text_store_.get()); |
| EXPECT_CALL(*sink_, OnLockGranted(_)) |
| .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted1)) |
| .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted2)) |
| .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted3)) |
| .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted4)) |
| .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted5)) |
| .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted6)); |
| |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result)); |
| EXPECT_EQ(S_OK, result); |
| result = kInvalidResult; |
| EXPECT_EQ(S_OK, |
| text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result)); |
| EXPECT_EQ(S_OK, result); |
| |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result)); |
| EXPECT_EQ(S_OK, result); |
| result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result)); |
| EXPECT_EQ(S_OK, result); |
| |
| result = kInvalidResult; |
| EXPECT_EQ(S_OK, |
| text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result)); |
| EXPECT_EQ(S_OK, result); |
| result = kInvalidResult; |
| EXPECT_EQ(S_OK, |
| text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result)); |
| EXPECT_EQ(S_OK, result); |
| } |
| |
| class AsyncRequestLockTestCallback : public TSFTextStoreTestCallback { |
| public: |
| explicit AsyncRequestLockTestCallback(TSFTextStore* text_store) |
| : TSFTextStoreTestCallback(text_store), state_(0) {} |
| |
| HRESULT LockGranted1(DWORD flags) { |
| EXPECT_EQ(0, state_); |
| state_ = 1; |
| EXPECT_TRUE(HasReadLock()); |
| EXPECT_FALSE(HasReadWriteLock()); |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); |
| EXPECT_EQ(TS_S_ASYNC, result); |
| EXPECT_EQ(1, state_); |
| state_ = 2; |
| return S_OK; |
| } |
| |
| HRESULT LockGranted2(DWORD flags) { |
| EXPECT_EQ(2, state_); |
| EXPECT_TRUE(HasReadLock()); |
| EXPECT_FALSE(HasReadWriteLock()); |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); |
| EXPECT_EQ(TS_S_ASYNC, result); |
| EXPECT_EQ(2, state_); |
| state_ = 3; |
| return S_OK; |
| } |
| |
| HRESULT LockGranted3(DWORD flags) { |
| EXPECT_EQ(3, state_); |
| EXPECT_TRUE(HasReadLock()); |
| EXPECT_TRUE(HasReadWriteLock()); |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); |
| EXPECT_EQ(TS_S_ASYNC, result); |
| EXPECT_EQ(3, state_); |
| state_ = 4; |
| return S_OK; |
| } |
| |
| HRESULT LockGranted4(DWORD flags) { |
| EXPECT_EQ(4, state_); |
| EXPECT_TRUE(HasReadLock()); |
| EXPECT_TRUE(HasReadWriteLock()); |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); |
| EXPECT_EQ(TS_S_ASYNC, result); |
| EXPECT_EQ(4, state_); |
| state_ = 5; |
| return S_OK; |
| } |
| |
| HRESULT LockGranted5(DWORD flags) { |
| EXPECT_EQ(5, state_); |
| EXPECT_TRUE(HasReadLock()); |
| EXPECT_FALSE(HasReadWriteLock()); |
| state_ = 6; |
| return S_OK; |
| } |
| |
| private: |
| int state_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AsyncRequestLockTestCallback); |
| }; |
| |
| TEST_F(TSFTextStoreTest, AsynchronousRequestLockTest) { |
| AsyncRequestLockTestCallback callback(text_store_.get()); |
| EXPECT_CALL(*sink_, OnLockGranted(_)) |
| .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted1)) |
| .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted2)) |
| .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted3)) |
| .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted4)) |
| .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted5)); |
| |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); |
| EXPECT_EQ(S_OK, result); |
| } |
| |
| class RequestLockTextChangeTestCallback : public TSFTextStoreTestCallback { |
| public: |
| explicit RequestLockTextChangeTestCallback(TSFTextStore* text_store) |
| : TSFTextStoreTestCallback(text_store), state_(0) {} |
| |
| HRESULT LockGranted1(DWORD flags) { |
| EXPECT_EQ(0, state_); |
| state_ = 1; |
| EXPECT_TRUE(HasReadLock()); |
| EXPECT_TRUE(HasReadWriteLock()); |
| |
| *edit_flag() = true; |
| SetInternalState(L"012345", 6, 6, 6); |
| text_spans()->clear(); |
| |
| state_ = 2; |
| return S_OK; |
| } |
| |
| void InsertText(const base::string16& text) { |
| EXPECT_EQ(2, state_); |
| EXPECT_EQ(L"012345", text); |
| state_ = 3; |
| } |
| |
| void SetCompositionText(const ui::CompositionText& composition) { |
| EXPECT_EQ(3, state_); |
| EXPECT_EQ(L"", composition.text); |
| EXPECT_EQ(0u, composition.selection.start()); |
| EXPECT_EQ(0u, composition.selection.end()); |
| EXPECT_EQ(0u, composition.ime_text_spans.size()); |
| state_ = 4; |
| } |
| |
| HRESULT OnTextChange(DWORD flags, const TS_TEXTCHANGE* change) { |
| EXPECT_EQ(4, state_); |
| HRESULT result = kInvalidResult; |
| state_ = 5; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); |
| EXPECT_EQ(S_OK, result); |
| EXPECT_EQ(6, state_); |
| state_ = 7; |
| return S_OK; |
| } |
| |
| HRESULT LockGranted2(DWORD flags) { |
| EXPECT_EQ(5, state_); |
| EXPECT_TRUE(HasReadLock()); |
| EXPECT_TRUE(HasReadWriteLock()); |
| state_ = 6; |
| return S_OK; |
| } |
| |
| private: |
| int state_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RequestLockTextChangeTestCallback); |
| }; |
| |
| TEST_F(TSFTextStoreTest, RequestLockOnTextChangeTest) { |
| RequestLockTextChangeTestCallback callback(text_store_.get()); |
| EXPECT_CALL(*sink_, OnLockGranted(_)) |
| .WillOnce( |
| Invoke(&callback, &RequestLockTextChangeTestCallback::LockGranted1)) |
| .WillOnce( |
| Invoke(&callback, &RequestLockTextChangeTestCallback::LockGranted2)); |
| |
| EXPECT_CALL(*sink_, OnSelectionChange()).WillOnce(Return(S_OK)); |
| EXPECT_CALL(*sink_, OnLayoutChange(_, _)).WillOnce(Return(S_OK)); |
| EXPECT_CALL(*sink_, OnTextChange(_, _)) |
| .WillOnce( |
| Invoke(&callback, &RequestLockTextChangeTestCallback::OnTextChange)); |
| EXPECT_CALL(text_input_client_, InsertText(_)) |
| .WillOnce( |
| Invoke(&callback, &RequestLockTextChangeTestCallback::InsertText)); |
| EXPECT_CALL(text_input_client_, SetCompositionText(_)) |
| .WillOnce(Invoke(&callback, |
| &RequestLockTextChangeTestCallback::SetCompositionText)); |
| |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); |
| EXPECT_EQ(S_OK, result); |
| } |
| |
| class SelectionTestCallback : public TSFTextStoreTestCallback { |
| public: |
| explicit SelectionTestCallback(TSFTextStore* text_store) |
| : TSFTextStoreTestCallback(text_store) {} |
| |
| HRESULT ReadLockGranted(DWORD flags) { |
| SetInternalState(L"", 0, 0, 0); |
| |
| GetSelectionTest(0, 0); |
| SetSelectionTest(0, 0, TF_E_NOLOCK); |
| |
| SetInternalState(L"012345", 0, 0, 3); |
| |
| GetSelectionTest(0, 3); |
| SetSelectionTest(0, 0, TF_E_NOLOCK); |
| |
| return S_OK; |
| } |
| |
| HRESULT ReadWriteLockGranted(DWORD flags) { |
| SetInternalState(L"", 0, 0, 0); |
| |
| SetSelectionTest(0, 0, S_OK); |
| GetSelectionTest(0, 0); |
| SetSelectionTest(0, 1, TF_E_INVALIDPOS); |
| SetSelectionTest(1, 0, TF_E_INVALIDPOS); |
| SetSelectionTest(1, 1, TF_E_INVALIDPOS); |
| |
| SetInternalState(L"0123456", 3, 3, 3); |
| |
| SetSelectionTest(0, 0, TF_E_INVALIDPOS); |
| SetSelectionTest(0, 1, TF_E_INVALIDPOS); |
| SetSelectionTest(0, 3, TF_E_INVALIDPOS); |
| SetSelectionTest(0, 6, TF_E_INVALIDPOS); |
| SetSelectionTest(0, 7, TF_E_INVALIDPOS); |
| SetSelectionTest(0, 8, TF_E_INVALIDPOS); |
| |
| SetSelectionTest(1, 0, TF_E_INVALIDPOS); |
| SetSelectionTest(1, 1, TF_E_INVALIDPOS); |
| SetSelectionTest(1, 3, TF_E_INVALIDPOS); |
| SetSelectionTest(1, 6, TF_E_INVALIDPOS); |
| SetSelectionTest(1, 7, TF_E_INVALIDPOS); |
| SetSelectionTest(1, 8, TF_E_INVALIDPOS); |
| |
| SetSelectionTest(3, 0, TF_E_INVALIDPOS); |
| SetSelectionTest(3, 1, TF_E_INVALIDPOS); |
| SetSelectionTest(3, 3, S_OK); |
| SetSelectionTest(3, 6, S_OK); |
| SetSelectionTest(3, 7, S_OK); |
| SetSelectionTest(3, 8, TF_E_INVALIDPOS); |
| |
| SetSelectionTest(6, 0, TF_E_INVALIDPOS); |
| SetSelectionTest(6, 1, TF_E_INVALIDPOS); |
| SetSelectionTest(6, 3, TF_E_INVALIDPOS); |
| SetSelectionTest(6, 6, S_OK); |
| SetSelectionTest(6, 7, S_OK); |
| SetSelectionTest(6, 8, TF_E_INVALIDPOS); |
| |
| SetSelectionTest(7, 0, TF_E_INVALIDPOS); |
| SetSelectionTest(7, 1, TF_E_INVALIDPOS); |
| SetSelectionTest(7, 3, TF_E_INVALIDPOS); |
| SetSelectionTest(7, 6, TF_E_INVALIDPOS); |
| SetSelectionTest(7, 7, S_OK); |
| SetSelectionTest(7, 8, TF_E_INVALIDPOS); |
| |
| SetSelectionTest(8, 0, TF_E_INVALIDPOS); |
| SetSelectionTest(8, 1, TF_E_INVALIDPOS); |
| SetSelectionTest(8, 3, TF_E_INVALIDPOS); |
| SetSelectionTest(8, 6, TF_E_INVALIDPOS); |
| SetSelectionTest(8, 7, TF_E_INVALIDPOS); |
| SetSelectionTest(8, 8, TF_E_INVALIDPOS); |
| |
| return S_OK; |
| } |
| }; |
| |
| TEST_F(TSFTextStoreTest, SetGetSelectionTest) { |
| SelectionTestCallback callback(text_store_.get()); |
| EXPECT_CALL(*sink_, OnLockGranted(_)) |
| .WillOnce(Invoke(&callback, &SelectionTestCallback::ReadLockGranted)) |
| .WillOnce( |
| Invoke(&callback, &SelectionTestCallback::ReadWriteLockGranted)); |
| |
| TS_SELECTION_ACP selection_buffer = {}; |
| ULONG fetched_count = 0; |
| EXPECT_EQ(TS_E_NOLOCK, |
| text_store_->GetSelection(0, 1, &selection_buffer, &fetched_count)); |
| |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); |
| } |
| |
| class SetGetTextTestCallback : public TSFTextStoreTestCallback { |
| public: |
| explicit SetGetTextTestCallback(TSFTextStore* text_store) |
| : TSFTextStoreTestCallback(text_store) {} |
| |
| HRESULT ReadLockGranted(DWORD flags) { |
| SetTextTest(0, 0, L"", TF_E_NOLOCK); |
| |
| GetTextTest(0, -1, L"", 0); |
| GetTextTest(0, 0, L"", 0); |
| GetTextErrorTest(0, 1, TF_E_INVALIDPOS); |
| |
| SetInternalState(L"0123456", 3, 3, 3); |
| |
| GetTextErrorTest(-1, -1, TF_E_INVALIDPOS); |
| GetTextErrorTest(-1, 0, TF_E_INVALIDPOS); |
| GetTextErrorTest(-1, 1, TF_E_INVALIDPOS); |
| GetTextErrorTest(-1, 3, TF_E_INVALIDPOS); |
| GetTextErrorTest(-1, 6, TF_E_INVALIDPOS); |
| GetTextErrorTest(-1, 7, TF_E_INVALIDPOS); |
| GetTextErrorTest(-1, 8, TF_E_INVALIDPOS); |
| |
| GetTextTest(0, -1, L"0123456", 7); |
| GetTextTest(0, 0, L"", 0); |
| GetTextTest(0, 1, L"0", 1); |
| GetTextTest(0, 3, L"012", 3); |
| GetTextTest(0, 6, L"012345", 6); |
| GetTextTest(0, 7, L"0123456", 7); |
| GetTextErrorTest(0, 8, TF_E_INVALIDPOS); |
| |
| GetTextTest(1, -1, L"123456", 7); |
| GetTextErrorTest(1, 0, TF_E_INVALIDPOS); |
| GetTextTest(1, 1, L"", 1); |
| GetTextTest(1, 3, L"12", 3); |
| GetTextTest(1, 6, L"12345", 6); |
| GetTextTest(1, 7, L"123456", 7); |
| GetTextErrorTest(1, 8, TF_E_INVALIDPOS); |
| |
| GetTextTest(3, -1, L"3456", 7); |
| GetTextErrorTest(3, 0, TF_E_INVALIDPOS); |
| GetTextErrorTest(3, 1, TF_E_INVALIDPOS); |
| GetTextTest(3, 3, L"", 3); |
| GetTextTest(3, 6, L"345", 6); |
| GetTextTest(3, 7, L"3456", 7); |
| GetTextErrorTest(3, 8, TF_E_INVALIDPOS); |
| |
| GetTextTest(6, -1, L"6", 7); |
| GetTextErrorTest(6, 0, TF_E_INVALIDPOS); |
| GetTextErrorTest(6, 1, TF_E_INVALIDPOS); |
| GetTextErrorTest(6, 3, TF_E_INVALIDPOS); |
| GetTextTest(6, 6, L"", 6); |
| GetTextTest(6, 7, L"6", 7); |
| GetTextErrorTest(6, 8, TF_E_INVALIDPOS); |
| |
| GetTextTest(7, -1, L"", 7); |
| GetTextErrorTest(7, 0, TF_E_INVALIDPOS); |
| GetTextErrorTest(7, 1, TF_E_INVALIDPOS); |
| GetTextErrorTest(7, 3, TF_E_INVALIDPOS); |
| GetTextErrorTest(7, 6, TF_E_INVALIDPOS); |
| GetTextTest(7, 7, L"", 7); |
| GetTextErrorTest(7, 8, TF_E_INVALIDPOS); |
| |
| GetTextErrorTest(8, -1, TF_E_INVALIDPOS); |
| GetTextErrorTest(8, 0, TF_E_INVALIDPOS); |
| GetTextErrorTest(8, 1, TF_E_INVALIDPOS); |
| GetTextErrorTest(8, 3, TF_E_INVALIDPOS); |
| GetTextErrorTest(8, 6, TF_E_INVALIDPOS); |
| GetTextErrorTest(8, 7, TF_E_INVALIDPOS); |
| GetTextErrorTest(8, 8, TF_E_INVALIDPOS); |
| |
| return S_OK; |
| } |
| |
| HRESULT ReadWriteLockGranted(DWORD flags) { |
| SetInternalState(L"", 0, 0, 0); |
| SetTextTest(0, 0, L"", S_OK); |
| |
| SetInternalState(L"", 0, 0, 0); |
| SetTextTest(0, 1, L"", TS_E_INVALIDPOS); |
| |
| SetInternalState(L"0123456", 3, 3, 3); |
| |
| SetTextTest(0, 0, L"", TS_E_INVALIDPOS); |
| SetTextTest(0, 1, L"", TS_E_INVALIDPOS); |
| SetTextTest(0, 3, L"", TS_E_INVALIDPOS); |
| SetTextTest(0, 6, L"", TS_E_INVALIDPOS); |
| SetTextTest(0, 7, L"", TS_E_INVALIDPOS); |
| SetTextTest(0, 8, L"", TS_E_INVALIDPOS); |
| |
| SetTextTest(1, 0, L"", TS_E_INVALIDPOS); |
| SetTextTest(1, 1, L"", TS_E_INVALIDPOS); |
| SetTextTest(1, 3, L"", TS_E_INVALIDPOS); |
| SetTextTest(1, 6, L"", TS_E_INVALIDPOS); |
| SetTextTest(1, 7, L"", TS_E_INVALIDPOS); |
| SetTextTest(1, 8, L"", TS_E_INVALIDPOS); |
| |
| SetTextTest(3, 0, L"", TS_E_INVALIDPOS); |
| SetTextTest(3, 1, L"", TS_E_INVALIDPOS); |
| |
| SetTextTest(3, 3, L"", S_OK); |
| GetTextTest(0, -1, L"0123456", 7); |
| GetSelectionTest(3, 3); |
| SetInternalState(L"0123456", 3, 3, 3); |
| |
| SetTextTest(3, 6, L"", S_OK); |
| GetTextTest(0, -1, L"0126", 4); |
| GetSelectionTest(3, 3); |
| SetInternalState(L"0123456", 3, 3, 3); |
| |
| SetTextTest(3, 7, L"", S_OK); |
| GetTextTest(0, -1, L"012", 3); |
| GetSelectionTest(3, 3); |
| SetInternalState(L"0123456", 3, 3, 3); |
| |
| SetTextTest(3, 8, L"", TS_E_INVALIDPOS); |
| |
| SetTextTest(6, 0, L"", TS_E_INVALIDPOS); |
| SetTextTest(6, 1, L"", TS_E_INVALIDPOS); |
| SetTextTest(6, 3, L"", TS_E_INVALIDPOS); |
| |
| SetTextTest(6, 6, L"", S_OK); |
| GetTextTest(0, -1, L"0123456", 7); |
| GetSelectionTest(6, 6); |
| SetInternalState(L"0123456", 3, 3, 3); |
| |
| SetTextTest(6, 7, L"", S_OK); |
| GetTextTest(0, -1, L"012345", 6); |
| GetSelectionTest(6, 6); |
| SetInternalState(L"0123456", 3, 3, 3); |
| |
| SetTextTest(6, 8, L"", TS_E_INVALIDPOS); |
| |
| SetTextTest(7, 0, L"", TS_E_INVALIDPOS); |
| SetTextTest(7, 1, L"", TS_E_INVALIDPOS); |
| SetTextTest(7, 3, L"", TS_E_INVALIDPOS); |
| SetTextTest(7, 6, L"", TS_E_INVALIDPOS); |
| |
| SetTextTest(7, 7, L"", S_OK); |
| GetTextTest(0, -1, L"0123456", 7); |
| GetSelectionTest(7, 7); |
| SetInternalState(L"0123456", 3, 3, 3); |
| |
| SetTextTest(7, 8, L"", TS_E_INVALIDPOS); |
| |
| SetInternalState(L"0123456", 3, 3, 3); |
| SetTextTest(3, 3, L"abc", S_OK); |
| GetTextTest(0, -1, L"012abc3456", 10); |
| GetSelectionTest(3, 6); |
| |
| SetInternalState(L"0123456", 3, 3, 3); |
| SetTextTest(3, 6, L"abc", S_OK); |
| GetTextTest(0, -1, L"012abc6", 7); |
| GetSelectionTest(3, 6); |
| |
| SetInternalState(L"0123456", 3, 3, 3); |
| SetTextTest(3, 7, L"abc", S_OK); |
| GetTextTest(0, -1, L"012abc", 6); |
| GetSelectionTest(3, 6); |
| |
| SetInternalState(L"0123456", 3, 3, 3); |
| SetTextTest(6, 6, L"abc", S_OK); |
| GetTextTest(0, -1, L"012345abc6", 10); |
| GetSelectionTest(6, 9); |
| |
| SetInternalState(L"0123456", 3, 3, 3); |
| SetTextTest(6, 7, L"abc", S_OK); |
| GetTextTest(0, -1, L"012345abc", 9); |
| GetSelectionTest(6, 9); |
| |
| SetInternalState(L"0123456", 3, 3, 3); |
| SetTextTest(7, 7, L"abc", S_OK); |
| GetTextTest(0, -1, L"0123456abc", 10); |
| GetSelectionTest(7, 10); |
| |
| return S_OK; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SetGetTextTestCallback); |
| }; |
| |
| TEST_F(TSFTextStoreTest, SetGetTextTest) { |
| SetGetTextTestCallback callback(text_store_.get()); |
| EXPECT_CALL(*sink_, OnLockGranted(_)) |
| .WillOnce(Invoke(&callback, &SetGetTextTestCallback::ReadLockGranted)) |
| .WillOnce( |
| Invoke(&callback, &SetGetTextTestCallback::ReadWriteLockGranted)); |
| |
| wchar_t buffer[1024] = {}; |
| ULONG text_buffer_copied = 0; |
| TS_RUNINFO run_info = {}; |
| ULONG run_info_buffer_copied = 0; |
| LONG next_acp = 0; |
| EXPECT_EQ(TF_E_NOLOCK, text_store_->GetText( |
| 0, -1, buffer, 1024, &text_buffer_copied, |
| &run_info, 1, &run_info_buffer_copied, &next_acp)); |
| TS_TEXTCHANGE change = {}; |
| EXPECT_EQ(TF_E_NOLOCK, text_store_->SetText(0, 0, 0, L"abc", 3, &change)); |
| |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); |
| } |
| |
| class InsertTextAtSelectionTestCallback : public TSFTextStoreTestCallback { |
| public: |
| explicit InsertTextAtSelectionTestCallback(TSFTextStore* text_store) |
| : TSFTextStoreTestCallback(text_store) {} |
| |
| HRESULT ReadLockGranted(DWORD flags) { |
| const wchar_t kBuffer[] = L"0123456789"; |
| |
| SetInternalState(L"abcedfg", 0, 0, 0); |
| InsertTextAtSelectionQueryOnlyTest(kBuffer, 10, 0, 0); |
| GetSelectionTest(0, 0); |
| InsertTextAtSelectionQueryOnlyTest(kBuffer, 0, 0, 0); |
| |
| SetInternalState(L"abcedfg", 0, 2, 5); |
| InsertTextAtSelectionQueryOnlyTest(kBuffer, 10, 2, 5); |
| GetSelectionTest(2, 5); |
| InsertTextAtSelectionQueryOnlyTest(kBuffer, 0, 2, 5); |
| |
| LONG start = 0; |
| LONG end = 0; |
| TS_TEXTCHANGE change = {}; |
| EXPECT_EQ(TS_E_NOLOCK, text_store_->InsertTextAtSelection( |
| 0, kBuffer, 10, &start, &end, &change)); |
| return S_OK; |
| } |
| |
| HRESULT ReadWriteLockGranted(DWORD flags) { |
| SetInternalState(L"abcedfg", 0, 0, 0); |
| |
| const wchar_t kBuffer[] = L"0123456789"; |
| InsertTextAtSelectionQueryOnlyTest(kBuffer, 10, 0, 0); |
| GetSelectionTest(0, 0); |
| InsertTextAtSelectionQueryOnlyTest(kBuffer, 0, 0, 0); |
| |
| SetInternalState(L"", 0, 0, 0); |
| InsertTextAtSelectionTest(kBuffer, 10, 0, 10, 0, 0, 10); |
| GetSelectionTest(0, 10); |
| GetTextTest(0, -1, L"0123456789", 10); |
| |
| SetInternalState(L"abcedfg", 0, 0, 0); |
| InsertTextAtSelectionTest(kBuffer, 10, 0, 10, 0, 0, 10); |
| GetSelectionTest(0, 10); |
| GetTextTest(0, -1, L"0123456789abcedfg", 17); |
| |
| SetInternalState(L"abcedfg", 0, 0, 3); |
| InsertTextAtSelectionTest(kBuffer, 0, 0, 0, 0, 3, 0); |
| GetSelectionTest(0, 0); |
| GetTextTest(0, -1, L"edfg", 4); |
| |
| SetInternalState(L"abcedfg", 0, 3, 7); |
| InsertTextAtSelectionTest(kBuffer, 10, 3, 13, 3, 7, 13); |
| GetSelectionTest(3, 13); |
| GetTextTest(0, -1, L"abc0123456789", 13); |
| |
| SetInternalState(L"abcedfg", 0, 7, 7); |
| InsertTextAtSelectionTest(kBuffer, 10, 7, 17, 7, 7, 17); |
| GetSelectionTest(7, 17); |
| GetTextTest(0, -1, L"abcedfg0123456789", 17); |
| |
| return S_OK; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(InsertTextAtSelectionTestCallback); |
| }; |
| |
| TEST_F(TSFTextStoreTest, InsertTextAtSelectionTest) { |
| InsertTextAtSelectionTestCallback callback(text_store_.get()); |
| EXPECT_CALL(*sink_, OnLockGranted(_)) |
| .WillOnce(Invoke(&callback, |
| &InsertTextAtSelectionTestCallback::ReadLockGranted)) |
| .WillOnce(Invoke( |
| &callback, &InsertTextAtSelectionTestCallback::ReadWriteLockGranted)); |
| |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); |
| EXPECT_EQ(S_OK, result); |
| result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); |
| EXPECT_EQ(S_OK, result); |
| } |
| |
| class ScenarioTestCallback : public TSFTextStoreTestCallback { |
| public: |
| explicit ScenarioTestCallback(TSFTextStore* text_store) |
| : TSFTextStoreTestCallback(text_store) {} |
| |
| HRESULT LockGranted1(DWORD flags) { |
| SetSelectionTest(0, 0, S_OK); |
| |
| SetTextTest(0, 0, L"abc", S_OK); |
| SetTextTest(1, 2, L"xyz", S_OK); |
| |
| GetTextTest(0, -1, L"axyzc", 5); |
| |
| text_spans()->clear(); |
| ImeTextSpan text_span; |
| text_span.start_offset = 0; |
| text_span.end_offset = 5; |
| text_span.underline_color = SK_ColorBLACK; |
| text_span.thickness = ImeTextSpan::Thickness::kThin; |
| text_span.background_color = SK_ColorTRANSPARENT; |
| text_spans()->push_back(text_span); |
| *edit_flag() = true; |
| *committed_size() = 0; |
| return S_OK; |
| } |
| |
| void SetCompositionText1(const ui::CompositionText& composition) { |
| EXPECT_EQ(L"axyzc", composition.text); |
| EXPECT_EQ(1u, composition.selection.start()); |
| EXPECT_EQ(4u, composition.selection.end()); |
| ASSERT_EQ(1u, composition.ime_text_spans.size()); |
| EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color); |
| EXPECT_EQ(SK_ColorTRANSPARENT, |
| composition.ime_text_spans[0].background_color); |
| EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset); |
| EXPECT_EQ(5u, composition.ime_text_spans[0].end_offset); |
| EXPECT_EQ(ImeTextSpan::Thickness::kThin, |
| composition.ime_text_spans[0].thickness); |
| } |
| |
| HRESULT LockGranted2(DWORD flags) { |
| SetTextTest(3, 4, L"ZCP", S_OK); |
| GetTextTest(0, -1, L"axyZCPc", 7); |
| |
| text_spans()->clear(); |
| ImeTextSpan text_span; |
| text_span.start_offset = 3; |
| text_span.end_offset = 5; |
| text_span.underline_color = SK_ColorBLACK; |
| text_span.thickness = ImeTextSpan::Thickness::kThick; |
| text_spans()->push_back(text_span); |
| text_span.start_offset = 5; |
| text_span.end_offset = 7; |
| text_span.underline_color = SK_ColorBLACK; |
| text_span.thickness = ImeTextSpan::Thickness::kThin; |
| text_spans()->push_back(text_span); |
| |
| *edit_flag() = true; |
| *committed_size() = 3; |
| |
| return S_OK; |
| } |
| |
| void InsertText2(const base::string16& text) { EXPECT_EQ(L"axy", text); } |
| |
| void SetCompositionText2(const ui::CompositionText& composition) { |
| EXPECT_EQ(L"ZCPc", composition.text); |
| EXPECT_EQ(0u, composition.selection.start()); |
| EXPECT_EQ(3u, composition.selection.end()); |
| ASSERT_EQ(2u, composition.ime_text_spans.size()); |
| EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color); |
| EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset); |
| EXPECT_EQ(2u, composition.ime_text_spans[0].end_offset); |
| EXPECT_EQ(ImeTextSpan::Thickness::kThick, |
| composition.ime_text_spans[0].thickness); |
| EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[1].underline_color); |
| EXPECT_EQ(2u, composition.ime_text_spans[1].start_offset); |
| EXPECT_EQ(4u, composition.ime_text_spans[1].end_offset); |
| EXPECT_EQ(ImeTextSpan::Thickness::kThin, |
| composition.ime_text_spans[1].thickness); |
| } |
| |
| HRESULT LockGranted3(DWORD flags) { |
| GetTextTest(0, -1, L"axyZCPc", 7); |
| |
| text_spans()->clear(); |
| *edit_flag() = true; |
| *committed_size() = 7; |
| |
| return S_OK; |
| } |
| |
| void InsertText3(const base::string16& text) { EXPECT_EQ(L"ZCPc", text); } |
| |
| void SetCompositionText3(const ui::CompositionText& composition) { |
| EXPECT_EQ(L"", composition.text); |
| EXPECT_EQ(0u, composition.selection.start()); |
| EXPECT_EQ(0u, composition.selection.end()); |
| EXPECT_EQ(0u, composition.ime_text_spans.size()); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ScenarioTestCallback); |
| }; |
| |
| TEST_F(TSFTextStoreTest, ScenarioTest) { |
| ScenarioTestCallback callback(text_store_.get()); |
| EXPECT_CALL(text_input_client_, SetCompositionText(_)) |
| .WillOnce(Invoke(&callback, &ScenarioTestCallback::SetCompositionText1)) |
| .WillOnce(Invoke(&callback, &ScenarioTestCallback::SetCompositionText2)) |
| .WillOnce(Invoke(&callback, &ScenarioTestCallback::SetCompositionText3)); |
| |
| EXPECT_CALL(text_input_client_, InsertText(_)) |
| .WillOnce(Invoke(&callback, &ScenarioTestCallback::InsertText2)) |
| .WillOnce(Invoke(&callback, &ScenarioTestCallback::InsertText3)); |
| |
| EXPECT_CALL(*sink_, OnLockGranted(_)) |
| .WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted1)) |
| .WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted2)) |
| .WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted3)); |
| |
| // OnSelectionChange will be called once after LockGranted3(). |
| EXPECT_CALL(*sink_, OnSelectionChange()).WillOnce(Return(S_OK)); |
| |
| // OnLayoutChange will be called once after LockGranted3(). |
| EXPECT_CALL(*sink_, OnLayoutChange(_, _)).WillOnce(Return(S_OK)); |
| |
| // OnTextChange will be called once after LockGranted3(). |
| EXPECT_CALL(*sink_, OnTextChange(_, _)).WillOnce(Return(S_OK)); |
| |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); |
| EXPECT_EQ(S_OK, result); |
| result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); |
| EXPECT_EQ(S_OK, result); |
| result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); |
| EXPECT_EQ(S_OK, result); |
| } |
| |
| class GetTextExtTestCallback : public TSFTextStoreTestCallback { |
| public: |
| explicit GetTextExtTestCallback(TSFTextStore* text_store) |
| : TSFTextStoreTestCallback(text_store), |
| layout_prepared_character_num_(0) {} |
| |
| HRESULT LockGranted(DWORD flags) { |
| SetInternalState(L"0123456789012", 0, 0, 0); |
| layout_prepared_character_num_ = 13; |
| |
| TsViewCookie view_cookie = 0; |
| EXPECT_EQ(S_OK, text_store_->GetActiveView(&view_cookie)); |
| GetTextExtTest(view_cookie, 0, 0, 11, 12, 11, 20); |
| GetTextExtTest(view_cookie, 0, 1, 11, 12, 20, 20); |
| GetTextExtTest(view_cookie, 0, 2, 11, 12, 30, 20); |
| GetTextExtTest(view_cookie, 9, 9, 100, 12, 100, 20); |
| GetTextExtTest(view_cookie, 9, 10, 101, 12, 110, 20); |
| GetTextExtTest(view_cookie, 10, 10, 110, 12, 110, 20); |
| GetTextExtTest(view_cookie, 11, 11, 20, 112, 20, 120); |
| GetTextExtTest(view_cookie, 11, 12, 21, 112, 30, 120); |
| GetTextExtTest(view_cookie, 9, 12, 101, 12, 30, 120); |
| GetTextExtTest(view_cookie, 9, 13, 101, 12, 40, 120); |
| GetTextExtTest(view_cookie, 0, 13, 11, 12, 40, 120); |
| GetTextExtTest(view_cookie, 13, 13, 40, 112, 40, 120); |
| |
| layout_prepared_character_num_ = 12; |
| GetTextExtNoLayoutTest(view_cookie, 13, 13); |
| |
| layout_prepared_character_num_ = 0; |
| GetTextExtNoLayoutTest(view_cookie, 0, 0); |
| |
| SetInternalState(L"", 0, 0, 0); |
| GetTextExtTest(view_cookie, 0, 0, 1, 2, 4, 6); |
| |
| // Last character is not availabe due to timing issue of async API. |
| // In this case, we will get first character bounds instead of whole text |
| // bounds. |
| SetInternalState(L"abc", 0, 0, 3); |
| layout_prepared_character_num_ = 2; |
| GetTextExtTest(view_cookie, 0, 0, 11, 12, 11, 20); |
| |
| // TODO(nona, kinaba): Remove following test case after PPAPI supporting |
| // GetCompositionCharacterBounds. |
| SetInternalState(L"a", 0, 0, 1); |
| layout_prepared_character_num_ = 0; |
| GetTextExtTest(view_cookie, 0, 1, 1, 2, 4, 6); |
| return S_OK; |
| } |
| |
| bool GetCompositionCharacterBounds(uint32_t index, gfx::Rect* rect) { |
| if (index >= layout_prepared_character_num_) |
| return false; |
| rect->set_x((index % 10) * 10 + 11); |
| rect->set_y((index / 10) * 100 + 12); |
| rect->set_width(9); |
| rect->set_height(8); |
| return true; |
| } |
| |
| gfx::Rect GetCaretBounds() { return gfx::Rect(1, 2, 3, 4); } |
| |
| private: |
| uint32_t layout_prepared_character_num_; |
| |
| DISALLOW_COPY_AND_ASSIGN(GetTextExtTestCallback); |
| }; |
| |
| TEST_F(TSFTextStoreTest, GetTextExtTest) { |
| GetTextExtTestCallback callback(text_store_.get()); |
| EXPECT_CALL(text_input_client_, GetCaretBounds()) |
| .WillRepeatedly( |
| Invoke(&callback, &GetTextExtTestCallback::GetCaretBounds)); |
| |
| EXPECT_CALL(text_input_client_, GetCompositionCharacterBounds(_, _)) |
| .WillRepeatedly(Invoke( |
| &callback, &GetTextExtTestCallback::GetCompositionCharacterBounds)); |
| |
| EXPECT_CALL(*sink_, OnLockGranted(_)) |
| .WillOnce(Invoke(&callback, &GetTextExtTestCallback::LockGranted)); |
| |
| HRESULT result = kInvalidResult; |
| EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); |
| EXPECT_EQ(S_OK, result); |
| } |
| |
| TEST_F(TSFTextStoreTest, RequestSupportedAttrs) { |
| EXPECT_CALL(text_input_client_, GetTextInputType()) |
| .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT)); |
| EXPECT_CALL(text_input_client_, GetTextInputMode()) |
| .WillRepeatedly(Return(TEXT_INPUT_MODE_DEFAULT)); |
| |
| EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs(0, 1, nullptr)); |
| |
| const TS_ATTRID kUnknownAttributes[] = {GUID_NULL}; |
| EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs( |
| 0, base::size(kUnknownAttributes), kUnknownAttributes)) |
| << "Must fail for unknown attributes"; |
| |
| const TS_ATTRID kAttributes[] = {GUID_NULL, GUID_PROP_INPUTSCOPE, GUID_NULL}; |
| EXPECT_EQ(S_OK, text_store_->RequestSupportedAttrs(0, base::size(kAttributes), |
| kAttributes)) |
| << "InputScope must be supported"; |
| |
| { |
| SCOPED_TRACE("Check if RequestSupportedAttrs fails while focus is lost"); |
| // Emulate focus lost |
| text_store_->SetFocusedTextInputClient(nullptr, nullptr); |
| EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs(0, 0, nullptr)); |
| EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs( |
| 0, base::size(kAttributes), kAttributes)); |
| } |
| } |
| |
| TEST_F(TSFTextStoreTest, RetrieveRequestedAttrs) { |
| EXPECT_CALL(text_input_client_, GetTextInputType()) |
| .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT)); |
| EXPECT_CALL(text_input_client_, GetTextInputMode()) |
| .WillRepeatedly(Return(TEXT_INPUT_MODE_DEFAULT)); |
| |
| ULONG num_copied = 0xfffffff; |
| EXPECT_HRESULT_FAILED( |
| text_store_->RetrieveRequestedAttrs(1, nullptr, &num_copied)); |
| |
| { |
| SCOPED_TRACE("Make sure if InputScope is supported"); |
| TS_ATTRVAL buffer[2] = {}; |
| num_copied = 0xfffffff; |
| ASSERT_EQ(S_OK, text_store_->RetrieveRequestedAttrs(base::size(buffer), |
| buffer, &num_copied)); |
| bool input_scope_found = false; |
| for (size_t i = 0; i < num_copied; ++i) { |
| base::win::ScopedVariant variant; |
| // Move ownership from |buffer[i].varValue| to |variant|. |
| std::swap(*variant.Receive(), buffer[i].varValue); |
| if (IsEqualGUID(buffer[i].idAttr, GUID_PROP_INPUTSCOPE)) { |
| EXPECT_EQ(VT_UNKNOWN, variant.type()); |
| Microsoft::WRL::ComPtr<ITfInputScope> input_scope; |
| EXPECT_HRESULT_SUCCEEDED(variant.AsInput()->punkVal->QueryInterface( |
| IID_PPV_ARGS(&input_scope))); |
| input_scope_found = true; |
| // we do not break here to clean up all the retrieved VARIANTs. |
| } |
| } |
| EXPECT_TRUE(input_scope_found); |
| } |
| { |
| SCOPED_TRACE("Check if RetrieveRequestedAttrs fails while focus is lost"); |
| // Emulate focus lost |
| text_store_->SetFocusedTextInputClient(nullptr, nullptr); |
| num_copied = 0xfffffff; |
| TS_ATTRVAL buffer[2] = {}; |
| EXPECT_HRESULT_FAILED(text_store_->RetrieveRequestedAttrs( |
| base::size(buffer), buffer, &num_copied)); |
| } |
| } |
| |
| } // namespace |
| } // namespace ui |