From e84c1dcac2b07310c3fdbd431eec3c5551b9a278 Mon Sep 17 00:00:00 2001 From: Bhushan Shah Date: Thu, 8 Oct 2020 14:07:36 +0530 Subject: [PATCH] input-method-v1: improve the test coverage of the class --- src/wayland/autotests/server/CMakeLists.txt | 4 + .../server/test_inputmethod_interface.cpp | 400 +++++++++++++++++- 2 files changed, 403 insertions(+), 1 deletion(-) diff --git a/src/wayland/autotests/server/CMakeLists.txt b/src/wayland/autotests/server/CMakeLists.txt index 98a6601f55..01160e44cc 100644 --- a/src/wayland/autotests/server/CMakeLists.txt +++ b/src/wayland/autotests/server/CMakeLists.txt @@ -95,6 +95,10 @@ ecm_add_qtwayland_client_protocol(INPUTMETHOD_SRCS PROTOCOL ${WaylandProtocols_DATADIR}/unstable/input-method/input-method-unstable-v1.xml BASENAME input-method-unstable-v1 ) +ecm_add_qtwayland_client_protocol(VIEWPORTER_SRCS + PROTOCOL ${WaylandProtocols_DATADIR}/unstable/text-input/text-input-unstable-v1.xml + BASENAME text-input-unstable-v1 + ) add_executable(testInputMethodInterface test_inputmethod_interface.cpp ${INPUTMETHOD_SRCS}) target_link_libraries(testInputMethodInterface Qt5::Test Plasma::KWaylandServer KF5::WaylandClient Wayland::Client) add_test(NAME kwayland-testInputMethodInterface COMMAND testInputMethodInterface) diff --git a/src/wayland/autotests/server/test_inputmethod_interface.cpp b/src/wayland/autotests/server/test_inputmethod_interface.cpp index f22b1f398d..af19606b55 100644 --- a/src/wayland/autotests/server/test_inputmethod_interface.cpp +++ b/src/wayland/autotests/server/test_inputmethod_interface.cpp @@ -23,10 +23,10 @@ #include "KWayland/Client/output.h" #include "qwayland-input-method-unstable-v1.h" +#include "qwayland-server-text-input-unstable-v1.h" using namespace KWaylandServer; - class InputPanelSurface : public QObject, public QtWayland::zwp_input_panel_surface_v1 { Q_OBJECT @@ -52,6 +52,82 @@ public: } }; +class InputMethodV1Context : public QObject, public QtWayland::zwp_input_method_context_v1 +{ + Q_OBJECT +public: + quint32 contentPurpose() { return imPurpose; } + quint32 contentHints() { return imHint; } +Q_SIGNALS: + void content_type_changed(); + void invoke_action(quint32 button, quint32 index); + void preferred_language(QString lang); + void surrounding_text(QString lang, quint32 cursor, quint32 anchor); + void reset(); + +protected: + void zwp_input_method_context_v1_content_type(uint32_t hint, uint32_t purpose) override + { + imHint = hint; + imPurpose = purpose; + Q_EMIT content_type_changed(); + } + void zwp_input_method_context_v1_invoke_action(uint32_t button, uint32_t index) override + { + Q_EMIT invoke_action(button, index); + } + void zwp_input_method_context_v1_preferred_language(const QString &language) override + { + Q_EMIT preferred_language(language); + } + void zwp_input_method_context_v1_surrounding_text(const QString &text, uint32_t cursor, uint32_t anchor) override + { + Q_EMIT surrounding_text(text, cursor, anchor); + } + void zwp_input_method_context_v1_reset() override + { + Q_EMIT reset(); + } +private: + quint32 imHint = 0; + quint32 imPurpose = 0; +}; + +class InputMethodV1 : public QObject, public QtWayland::zwp_input_method_v1 +{ + Q_OBJECT +public: + InputMethodV1(struct ::wl_registry *registry, int id, int version) + : QtWayland::zwp_input_method_v1(registry, id, version) + { + } + InputMethodV1Context* context() { + return m_context; + } + +Q_SIGNALS: + void activated(); + void deactivated(); + +protected: + void zwp_input_method_v1_activate(struct ::zwp_input_method_context_v1 *context) override + { + m_context = new InputMethodV1Context(); + m_context->init(context); + Q_EMIT activated(); + }; + void zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context) override + { + Q_UNUSED(context) + delete m_context; + m_context = nullptr; + Q_EMIT deactivated(); + + }; +private: + InputMethodV1Context *m_context; +}; + class TestInputMethodInterface : public QObject { Q_OBJECT @@ -64,6 +140,13 @@ public: private Q_SLOTS: void initTestCase(); void testAdd(); + void testActivate(); + void testContext(); + void testGrabkeyboard(); + void testContentHints_data(); + void testContentHints(); + void testContentPurpose_data(); + void testContentPurpose(); private: KWayland::Client::ConnectionThread *m_connection; @@ -72,6 +155,7 @@ private: KWayland::Client::Seat *m_clientSeat = nullptr; KWayland::Client::Output *m_output= nullptr; + InputMethodV1 *m_inputMethod; InputPanel* m_inputPanel; QThread *m_thread; Display m_display; @@ -131,6 +215,8 @@ void TestInputMethodInterface::initTestCase() connect(registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, registry](const QByteArray &interface, quint32 name, quint32 version) { if (interface == "zwp_input_panel_v1") { m_inputPanel = new InputPanel(registry->registry(), name, version); + } else if (interface == "zwp_input_method_v1") { + m_inputMethod = new InputMethodV1(registry->registry(), name, version); } }); connect(registry, &KWayland::Client::Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) { @@ -172,6 +258,7 @@ TestInputMethodInterface::~TestInputMethodInterface() m_thread = nullptr; } delete m_inputPanel; + delete m_inputMethod; delete m_inputMethodIface; delete m_inputPanelIface; m_connection->deleteLater(); @@ -198,6 +285,317 @@ void TestInputMethodInterface::testAdd() QVERIFY(panelTopLevelSpy.wait()); } +void TestInputMethodInterface::testActivate() +{ + QVERIFY(m_inputMethodIface); + QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated); + QVERIFY(inputMethodActivateSpy.isValid()); + QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated); + + // before sending activate the context should be null + QVERIFY(!m_inputMethodIface->context()); + + // send activate now + m_inputMethodIface->sendActivate(); + QVERIFY(inputMethodActivateSpy.wait()); + QCOMPARE(inputMethodActivateSpy.count(), 1); + QVERIFY(m_inputMethodIface->context()); + + // send deactivate and verify server interface resets context + m_inputMethodIface->sendDeactivate(); + QVERIFY(inputMethodDeactivateSpy.wait()); + QCOMPARE(inputMethodActivateSpy.count(), 1); + QVERIFY(!m_inputMethodIface->context()); + +} + +void TestInputMethodInterface::testContext() +{ + QVERIFY(m_inputMethodIface); + QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated); + QVERIFY(inputMethodActivateSpy.isValid()); + QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated); + + // before sending activate the context should be null + QVERIFY(!m_inputMethodIface->context()); + + // send activate now + m_inputMethodIface->sendActivate(); + QVERIFY(inputMethodActivateSpy.wait()); + QCOMPARE(inputMethodActivateSpy.count(), 1); + + KWaylandServer::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context(); + QVERIFY(serverContext); + + InputMethodV1Context *imContext = m_inputMethod->context(); + QVERIFY(imContext); + + quint32 serial = 1; + + // commit some text + QSignalSpy commitStringSpy(serverContext, &KWaylandServer::InputMethodContextV1Interface::commitString); + QVERIFY(commitStringSpy.isValid()); + imContext->commit_string(serial, "hello"); + QVERIFY(commitStringSpy.wait()); + QCOMPARE(commitStringSpy.count(), serial); + QCOMPARE(commitStringSpy.last().at(0).value(), serial); + QCOMPARE(commitStringSpy.last().at(1).value(), "hello"); + serial++; + + // preedit styling event + QSignalSpy preeditStylingSpy(serverContext, &KWaylandServer::InputMethodContextV1Interface::preeditStyling); + QVERIFY(preeditStylingSpy.isValid()); + // protocol does not document 3rd argument mean in much details (styling) + imContext->preedit_styling(0, 5, 1); + QVERIFY(preeditStylingSpy.wait()); + QCOMPARE(preeditStylingSpy.count(), 1); + QCOMPARE(preeditStylingSpy.last().at(0).value(), 0); + QCOMPARE(preeditStylingSpy.last().at(1).value(), 5); + QCOMPARE(preeditStylingSpy.last().at(2).value(), 1); + + // preedit cursor event + QSignalSpy preeditCursorSpy(serverContext, &KWaylandServer::InputMethodContextV1Interface::preeditCursor); + QVERIFY(preeditCursorSpy.isValid()); + imContext->preedit_cursor(3); + QVERIFY(preeditCursorSpy.wait()); + QCOMPARE(preeditCursorSpy.count(), 1); + QCOMPARE(preeditCursorSpy.last().at(0).value(), 3); + + // commit preedit_string + QSignalSpy preeditStringSpy(serverContext, &KWaylandServer::InputMethodContextV1Interface::preeditString); + QVERIFY(preeditStringSpy.isValid()); + imContext->preedit_string(serial, "hello", "kde"); + QVERIFY(preeditStringSpy.wait()); + QCOMPARE(preeditStringSpy.count(), 1); + QCOMPARE(preeditStringSpy.last().at(0).value(), serial); + QCOMPARE(preeditStringSpy.last().at(1).value(), "hello"); + QCOMPARE(preeditStringSpy.last().at(2).value(), "kde"); + serial++; + + // delete surrounding text + QSignalSpy deleteSurroundingSpy(serverContext, &KWaylandServer::InputMethodContextV1Interface::deleteSurroundingText); + QVERIFY(deleteSurroundingSpy.isValid()); + imContext->delete_surrounding_text(0, 5); + QVERIFY(deleteSurroundingSpy.wait()); + QCOMPARE(deleteSurroundingSpy.count(), 1); + QCOMPARE(deleteSurroundingSpy.last().at(0).value(), 0); + QCOMPARE(deleteSurroundingSpy.last().at(1).value(), 5); + + // set cursor position + QSignalSpy cursorPositionSpy(serverContext, &KWaylandServer::InputMethodContextV1Interface::cursorPosition); + QVERIFY(cursorPositionSpy.isValid()); + imContext->cursor_position(2, 4); + QVERIFY(cursorPositionSpy.wait()); + QCOMPARE(cursorPositionSpy.count(), 1); + QCOMPARE(cursorPositionSpy.last().at(0).value(), 2); + QCOMPARE(cursorPositionSpy.last().at(1).value(), 4); + + // invoke action + QSignalSpy invokeActionSpy(imContext, &InputMethodV1Context::invoke_action); + QVERIFY(invokeActionSpy.isValid()); + serverContext->sendInvokeAction(3, 5); + QVERIFY(invokeActionSpy.wait()); + QCOMPARE(invokeActionSpy.count(), 1); + QCOMPARE(invokeActionSpy.last().at(0).value(), 3); + QCOMPARE(invokeActionSpy.last().at(1).value(), 5); + + // preferred language + QSignalSpy preferredLanguageSpy(imContext, &InputMethodV1Context::preferred_language); + QVERIFY(preferredLanguageSpy.isValid()); + serverContext->sendPreferredLanguage("gu_IN"); + QVERIFY(preferredLanguageSpy.wait()); + QCOMPARE(preferredLanguageSpy.count(), 1); + QCOMPARE(preferredLanguageSpy.last().at(0).value(), "gu_IN"); + + // surrounding text + QSignalSpy surroundingTextSpy(imContext, &InputMethodV1Context::surrounding_text); + QVERIFY(surroundingTextSpy.isValid()); + serverContext->sendSurroundingText("Hello Plasma!", 2, 4); + QVERIFY(surroundingTextSpy.wait()); + QCOMPARE(surroundingTextSpy.count(), 1); + QCOMPARE(surroundingTextSpy.last().at(0).value(), "Hello Plasma!"); + QCOMPARE(surroundingTextSpy.last().at(1).value(), 2); + QCOMPARE(surroundingTextSpy.last().at(2).value(), 4); + + // reset + QSignalSpy resetSpy(imContext, &InputMethodV1Context::reset); + QVERIFY(resetSpy.isValid()); + serverContext->sendReset(); + QVERIFY(resetSpy.wait()); + QCOMPARE(resetSpy.count(), 1); + + // send deactivate and verify server interface resets context + m_inputMethodIface->sendDeactivate(); + QVERIFY(inputMethodDeactivateSpy.wait()); + QCOMPARE(inputMethodActivateSpy.count(), 1); + QVERIFY(!m_inputMethodIface->context()); + QVERIFY(!m_inputMethod->context()); +} + +void TestInputMethodInterface::testGrabkeyboard() +{ + QVERIFY(m_inputMethodIface); + QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated); + QVERIFY(inputMethodActivateSpy.isValid()); + QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated); + + // before sending activate the context should be null + QVERIFY(!m_inputMethodIface->context()); + + // send activate now + m_inputMethodIface->sendActivate(); + QVERIFY(inputMethodActivateSpy.wait()); + QCOMPARE(inputMethodActivateSpy.count(), 1); + + KWaylandServer::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context(); + QVERIFY(serverContext); + + InputMethodV1Context *imContext = m_inputMethod->context(); + QVERIFY(imContext); + + QSignalSpy keyEventSpy(serverContext, &KWaylandServer::InputMethodContextV1Interface::key); + QVERIFY(keyEventSpy.isValid()); + imContext->key(0, 123, 56, 1); + QEXPECT_FAIL("", "We should be not get key event if keyboard is not grabbed", Continue); + QVERIFY(!keyEventSpy.wait(200)); + + QSignalSpy modifierEventSpy(serverContext, &KWaylandServer::InputMethodContextV1Interface::modifiers); + QVERIFY(modifierEventSpy.isValid()); + imContext->modifiers(1234, 0, 0, 0, 0); + QEXPECT_FAIL("", "We should be not get modifiers event if keyboard is not grabbed", Continue); + QVERIFY(!modifierEventSpy.wait(200)); + + // grab the keyboard + wl_keyboard* keyboard = imContext->grab_keyboard(); + QVERIFY(keyboard); + + //TODO: add more tests about keyboard grab here + + // send deactivate and verify server interface resets context + m_inputMethodIface->sendDeactivate(); + QVERIFY(inputMethodDeactivateSpy.wait()); + QCOMPARE(inputMethodActivateSpy.count(), 1); + QVERIFY(!m_inputMethodIface->context()); + QVERIFY(!m_inputMethod->context()); +} + +void TestInputMethodInterface::testContentHints_data() +{ + QTest::addColumn("serverHints"); + QTest::addColumn("imHint"); + QTest::addRow("Spellcheck") << TextInputContentHints(TextInputContentHint::AutoCorrection) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_auto_correction); + QTest::addRow("AutoCapital") << TextInputContentHints(TextInputContentHint::AutoCapitalization) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_auto_capitalization); + QTest::addRow("Lowercase") << TextInputContentHints(TextInputContentHint::LowerCase) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_lowercase); + QTest::addRow("Uppercase") << TextInputContentHints(TextInputContentHint::UpperCase) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_uppercase); + QTest::addRow("Titlecase") << TextInputContentHints(TextInputContentHint::TitleCase) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_titlecase); + QTest::addRow("HiddenText") << TextInputContentHints(TextInputContentHint::HiddenText) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_hidden_text); + QTest::addRow("SensitiveData") << TextInputContentHints(TextInputContentHint::SensitiveData) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_sensitive_data); + QTest::addRow("Latin") << TextInputContentHints(TextInputContentHint::Latin) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_latin); + QTest::addRow("Multiline") << TextInputContentHints(TextInputContentHint::MultiLine) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_multiline); + QTest::addRow("Auto") << TextInputContentHints(TextInputContentHint::AutoCorrection | TextInputContentHint::AutoCapitalization) + << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_auto_correction | QtWaylandServer::zwp_text_input_v1::content_hint_auto_capitalization); +} + +void TestInputMethodInterface::testContentHints() +{ + QVERIFY(m_inputMethodIface); + QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated); + QVERIFY(inputMethodActivateSpy.isValid()); + QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated); + + // before sending activate the context should be null + QVERIFY(!m_inputMethodIface->context()); + + // send activate now + m_inputMethodIface->sendActivate(); + QVERIFY(inputMethodActivateSpy.wait()); + QCOMPARE(inputMethodActivateSpy.count(), 1); + + KWaylandServer::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context(); + QVERIFY(serverContext); + + InputMethodV1Context *imContext = m_inputMethod->context(); + QVERIFY(imContext); + + QSignalSpy contentTypeChangedSpy(imContext, &InputMethodV1Context::content_type_changed); + QVERIFY(contentTypeChangedSpy.isValid()); + + QFETCH(KWaylandServer::TextInputContentHints, serverHints); + serverContext->sendContentType(serverHints, KWaylandServer::TextInputContentPurpose::Normal); + QVERIFY(contentTypeChangedSpy.wait()); + QCOMPARE(contentTypeChangedSpy.count(), 1); + QEXPECT_FAIL("SensitiveData", "SensitiveData content hint need fixing", Continue); + QTEST(imContext->contentHints(), "imHint"); + + // send deactivate and verify server interface resets context + m_inputMethodIface->sendDeactivate(); + QVERIFY(inputMethodDeactivateSpy.wait()); + QCOMPARE(inputMethodActivateSpy.count(), 1); + QVERIFY(!m_inputMethodIface->context()); + QVERIFY(!m_inputMethod->context()); +} + +void TestInputMethodInterface::testContentPurpose_data() +{ + QTest::addColumn("serverPurpose"); + QTest::addColumn("imPurpose"); + + QTest::newRow("Alpha") << TextInputContentPurpose::Alpha << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_alpha); + QTest::newRow("Digits") << TextInputContentPurpose::Digits << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_digits); + QTest::newRow("Number") << TextInputContentPurpose::Number << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_number); + QTest::newRow("Phone") << TextInputContentPurpose::Phone << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_phone); + QTest::newRow("Url") << TextInputContentPurpose::Url << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_url); + QTest::newRow("Email") << TextInputContentPurpose::Email << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_email); + QTest::newRow("Name") << TextInputContentPurpose::Name << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_name); + QTest::newRow("Password") << TextInputContentPurpose::Password << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_password); + QTest::newRow("Date") << TextInputContentPurpose::Date << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_date); + QTest::newRow("Time") << TextInputContentPurpose::Time << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_time); + QTest::newRow("DateTime") << TextInputContentPurpose::DateTime << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_datetime); + QTest::newRow("Terminal") << TextInputContentPurpose::Terminal << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_terminal); + QTest::newRow("Normal") << TextInputContentPurpose::Normal << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_normal); + QTest::newRow("Pin") << TextInputContentPurpose::Pin << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_password); +} + +void TestInputMethodInterface::testContentPurpose() +{ + QVERIFY(m_inputMethodIface); + QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated); + QVERIFY(inputMethodActivateSpy.isValid()); + QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated); + + // before sending activate the context should be null + QVERIFY(!m_inputMethodIface->context()); + + // send activate now + m_inputMethodIface->sendActivate(); + QVERIFY(inputMethodActivateSpy.wait()); + QCOMPARE(inputMethodActivateSpy.count(), 1); + + KWaylandServer::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context(); + QVERIFY(serverContext); + + InputMethodV1Context *imContext = m_inputMethod->context(); + QVERIFY(imContext); + + QSignalSpy contentTypeChangedSpy(imContext, &InputMethodV1Context::content_type_changed); + QVERIFY(contentTypeChangedSpy.isValid()); + + QFETCH(KWaylandServer::TextInputContentPurpose, serverPurpose); + serverContext->sendContentType(KWaylandServer::TextInputContentHints(KWaylandServer::TextInputContentHint::None), serverPurpose); + QVERIFY(contentTypeChangedSpy.wait()); + QCOMPARE(contentTypeChangedSpy.count(), 1); + QEXPECT_FAIL("Normal", "Normal should not return content_purpose_alpha", Continue); + QEXPECT_FAIL("Pin", "Pin should return content_purpose_password", Continue); + QTEST(imContext->contentPurpose(), "imPurpose"); + + // send deactivate and verify server interface resets context + m_inputMethodIface->sendDeactivate(); + QVERIFY(inputMethodDeactivateSpy.wait()); + QCOMPARE(inputMethodActivateSpy.count(), 1); + QVERIFY(!m_inputMethodIface->context()); + QVERIFY(!m_inputMethod->context()); +} + QTEST_GUILESS_MAIN(TestInputMethodInterface) #include "test_inputmethod_interface.moc"