chromium/ui/ozone/platform/wayland/host/wayland_input_method_context_unittest.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/ozone/platform/wayland/host/wayland_input_method_context.h"

#include <text-input-unstable-v1-server-protocol.h>
#include <wayland-server.h>

#include <memory>
#include <optional>
#include <string_view>

#include "base/environment.h"
#include "base/i18n/break_iterator.h"
#include "base/memory/raw_ptr.h"
#include "base/nix/xdg_util.h"
#include "base/strings/utf_string_conversions.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ime/ime_text_span.h"
#include "ui/base/ime/linux/linux_input_method_context.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/ime/text_input_flags.h"
#include "ui/base/ime/text_input_type.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/ozone/events_ozone.h"
#include "ui/gfx/range/range.h"
#include "ui/ozone/platform/wayland/host/wayland_event_source.h"
#include "ui/ozone/platform/wayland/host/wayland_seat.h"
#include "ui/ozone/platform/wayland/host/wayland_window.h"
#include "ui/ozone/platform/wayland/test/mock_surface.h"
#include "ui/ozone/platform/wayland/test/mock_zcr_extended_text_input.h"
#include "ui/ozone/platform/wayland/test/mock_zwp_text_input.h"
#include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h"
#include "ui/ozone/platform/wayland/test/test_zcr_text_input_extension.h"
#include "ui/ozone/platform/wayland/test/wayland_test.h"

_;
DoAll;
InSequence;
Mock;
Optional;
SaveArg;
Values;

namespace ui {

// Returns the number of grapheme clusters in the text.
std::optional<size_t> CountGraphemeCluster(std::u16string_view text) {}

// TODO(crbug.com/40240866): Subclass FakeTextInputClient after pruning deps.
class MockTextInputClient : public TextInputClient {};

class MockZWPTextInputWrapper : public ZWPTextInputWrapper {};

class TestInputMethodContextDelegate : public LinuxInputMethodContextDelegate {};

class TestKeyboardDelegate : public WaylandKeyboard::Delegate {};

class WaylandInputMethodContextTestBase : public WaylandTest {};

WaylandInputMethodContextTest;
WaylandInputMethodContextOldServerTest;

INSTANTIATE_TEST_SUITE_P();

INSTANTIATE_TEST_SUITE_P();

TEST_P(WaylandInputMethodContextOldServerTest, SetInputType) {}

TEST_P(WaylandInputMethodContextTest, ActivateDeactivate) {}

TEST_P(WaylandInputMethodContextTest, Reset) {}

TEST_P(WaylandInputMethodContextTest, SetCursorLocation) {}

TEST_P(WaylandInputMethodContextTest, SetSurroundingTextForShortText) {}

TEST_P(WaylandInputMethodContextTest, SetSurroundingTextForLongText) {}

TEST_P(WaylandInputMethodContextTest, SetSurroundingTextForLongTextInLeftEdge) {}

TEST_P(WaylandInputMethodContextTest,
       SetSurroundingTextForLongTextInRightEdge) {}

// TODO(crbug.com/354862211): This test is Lacros-specific and should be
// removed. It fails without Lacros-specific patches to libwayland, which
// shouldn't be applied when updating to a new Wayland of version as Lacros is
// being sunset.
TEST_P(WaylandInputMethodContextTest, DISABLED_SetSurroundingTextForLongRange) {}

TEST_P(WaylandInputMethodContextTest,
       SetSurroundingTextForShortTextWithGrammmarFragment) {}

TEST_P(WaylandInputMethodContextTest,
       SetSurroundingTextForLongTextWithGrammmarFragment) {}

TEST_P(WaylandInputMethodContextTest,
       SetSurroundingTextForShortTextWithAutocorrect) {}

TEST_P(WaylandInputMethodContextTest, DeleteSurroundingTextWithExtendedRange) {}

TEST_P(WaylandInputMethodContextTest, DeleteSurroundingTextInIncorrectOrder) {}

TEST_P(WaylandInputMethodContextTest,
       DeleteSurroundingTextAndCommitInIncorrectOrder) {}

TEST_P(WaylandInputMethodContextTest, SetInputType) {}

TEST_P(WaylandInputMethodContextTest, SetInputTypeWithoutLearning) {}

TEST_P(WaylandInputMethodContextTest,
       SetInputTypeWithoutInlineCompositionSupport) {}

TEST_P(WaylandInputMethodContextTest, SetInputTypeAfterFocus) {}

TEST_P(WaylandInputMethodContextTest, OnPreeditChanged) {}

TEST_P(WaylandInputMethodContextTest, OnCommit) {}

// Regression test for crbug.com/40263583
TEST_P(WaylandInputMethodContextTest,
       OnCommitAfterEmptyPreeditStringWithoutCursor) {}

TEST_P(WaylandInputMethodContextTest, OnCommitAfterPreeditStringWithoutCursor) {}

#if BUILDFLAG(IS_CHROMEOS_LACROS)
TEST_P(WaylandInputMethodContextTest, OnConfirmCompositionText) {
  constexpr char16_t text[] = u"ab😀cあdef";
  constexpr gfx::Range range(5, 6);  // あ is selected.

  // SetSurroundingText should be called in UTF-8.
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
                SetSurroundingText("ab😀cあdef", gfx::Range(7, 10)));
  });
  input_method_context_->SetSurroundingText(text, gfx::Range(0, 9),
                                            gfx::Range::InvalidRange(), range,
                                            std::nullopt, std::nullopt);
  EXPECT_EQ(
      input_method_context_->predicted_state_for_testing().surrounding_text,
      text);
  EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
            range);
  connection_->Flush();

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    auto* text_input = server->text_input_manager_v1()->text_input();
    Mock::VerifyAndClearExpectations(text_input);

    const gfx::Range sent_range(10, 7);
    zwp_text_input_v1_send_cursor_position(
        text_input->resource(), sent_range.start(), sent_range.end());
    zwp_text_input_v1_send_commit_string(text_input->resource(), 0,
                                         "ab😀cあdef");
  });

  EXPECT_THAT(input_method_context_delegate_->last_on_confirm_composition_arg(),
              Optional(true));
  EXPECT_EQ(
      input_method_context_->predicted_state_for_testing().surrounding_text,
      text);
  // Cursor position is set to `range` position explicitly by OnCursorPosition.
  EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
            range);
  EXPECT_EQ(input_method_context_->predicted_state_for_testing().composition,
            gfx::Range(0));
}

TEST_P(WaylandInputMethodContextTest,
       OnConfirmCompositionTextExtendedKeepSelectionNoComposition) {
  input_method_context_->SetSurroundingText(
      u"abcd", gfx::Range(0, 4), gfx::Range::InvalidRange(), gfx::Range(0, 4),
      std::nullopt, std::nullopt);
  connection_->Flush();

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    zcr_extended_text_input_v1_send_confirm_preedit(
        server->text_input_extension_v1()->extended_text_input()->resource(),
        /*selection_behavior=*/
        ZCR_EXTENDED_TEXT_INPUT_V1_CONFIRM_PREEDIT_SELECTION_BEHAVIOR_UNCHANGED);
  });

  EXPECT_THAT(input_method_context_delegate_->last_on_confirm_composition_arg(),
              Optional(true));
  // Selection range should not be changed.
  EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
            gfx::Range(0, 4));
}

TEST_P(WaylandInputMethodContextTest,
       OnConfirmCompositionTextExtendedKeepSelectionComposition) {
  input_method_context_->SetSurroundingText(
      u"abcd", gfx::Range(0, 4), gfx::Range::InvalidRange(), gfx::Range(2),
      std::nullopt, std::nullopt);
  input_method_context_->OnPreeditString("xyz", {}, gfx::Range(1));
  connection_->Flush();

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    zcr_extended_text_input_v1_send_confirm_preedit(
        server->text_input_extension_v1()->extended_text_input()->resource(),
        /*selection_behavior=*/
        ZCR_EXTENDED_TEXT_INPUT_V1_CONFIRM_PREEDIT_SELECTION_BEHAVIOR_UNCHANGED);
  });

  EXPECT_THAT(input_method_context_delegate_->last_on_confirm_composition_arg(),
              Optional(true));
  // Selection range should not be changed.
  EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
            gfx::Range(3));
}

TEST_P(WaylandInputMethodContextTest,
       OnConfirmCompositionTextExtendedDontKeepSelectionNoComposition) {
  input_method_context_->SetSurroundingText(
      u"abcd", gfx::Range(0, 4), gfx::Range::InvalidRange(), gfx::Range(0, 4),
      std::nullopt, std::nullopt);
  connection_->Flush();

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    zcr_extended_text_input_v1_send_confirm_preedit(
        server->text_input_extension_v1()->extended_text_input()->resource(),
        /*selection_behavior=*/
        ZCR_EXTENDED_TEXT_INPUT_V1_CONFIRM_PREEDIT_SELECTION_BEHAVIOR_AFTER_PREEDIT);
  });

  EXPECT_THAT(input_method_context_delegate_->last_on_confirm_composition_arg(),
              Optional(false));
  // Selection range should not be changed.
  EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
            gfx::Range(0, 4));
  EXPECT_EQ(input_method_context_->predicted_state_for_testing().composition,
            gfx::Range(0));
}

TEST_P(WaylandInputMethodContextTest,
       OnConfirmCompositionTextExtendedDontKeepSelectionComposition) {
  input_method_context_->SetSurroundingText(
      u"abcd", gfx::Range(0, 4), gfx::Range::InvalidRange(), gfx::Range(2),
      std::nullopt, std::nullopt);
  input_method_context_->OnPreeditString("xyz", {}, gfx::Range(1));
  connection_->Flush();

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    zcr_extended_text_input_v1_send_confirm_preedit(
        server->text_input_extension_v1()->extended_text_input()->resource(),
        /*selection_behavior=*/
        ZCR_EXTENDED_TEXT_INPUT_V1_CONFIRM_PREEDIT_SELECTION_BEHAVIOR_AFTER_PREEDIT);
  });

  EXPECT_THAT(input_method_context_delegate_->last_on_confirm_composition_arg(),
              Optional(false));
  // Selection range should move to the end of commit.
  EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
            gfx::Range(5));
  EXPECT_EQ(input_method_context_->predicted_state_for_testing().composition,
            gfx::Range(0));
}

TEST_P(WaylandInputMethodContextTest, OnConfirmCompositionTextForLongRange) {
  const std::u16string text(5000, u'あ');
  constexpr gfx::Range range(4000, 4500);

  std::string expected_sent_text;
  gfx::Range expected_sent_range;
  if (GetApiVersion() == wl::TestZcrTextInputExtensionV1::Version::kV8) {
    // In old protocol, even if the range covers, the surrounding text
    // longer than 4000 bytes is trimmed to meet the limitation.
    // Selection range is also adjusted by the trimmed text before sendin to
    // Exo.
    expected_sent_text = base::UTF16ToUTF8(std::u16string(1332, u'あ'));
    expected_sent_range = gfx::Range(1248, 2748);
  } else {
    // In new protocol, the surrounding text is trimmed around selection with
    // at most 500 bytes buffer.
    expected_sent_text = base::UTF16ToUTF8(std::u16string(832, u'あ'));
    expected_sent_range = gfx::Range(498, 1998);
  }

  // SetSurroundingText should be called in UTF-8.
  PostToServerAndWait([expected_sent_text, expected_sent_range](
                          wl::TestWaylandServerThread* server) {
    EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
                SetSurroundingText(expected_sent_text, expected_sent_range));
  });
  input_method_context_->SetSurroundingText(text, gfx::Range(0, 5000),
                                            gfx::Range::InvalidRange(), range,
                                            std::nullopt, std::nullopt);
  EXPECT_EQ(
      input_method_context_->predicted_state_for_testing().surrounding_text,
      text);
  EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
            range);
  connection_->Flush();

  PostToServerAndWait([expected_sent_text, expected_sent_range](
                          wl::TestWaylandServerThread* server) {
    auto* text_input = server->text_input_manager_v1()->text_input();
    Mock::VerifyAndClearExpectations(text_input);

    gfx::Range range =
        gfx::Range(expected_sent_range.end(), expected_sent_range.start());

    zwp_text_input_v1_send_cursor_position(text_input->resource(),
                                           range.start(), range.end());
    zwp_text_input_v1_send_commit_string(text_input->resource(), 0,
                                         expected_sent_text.c_str());
  });

  EXPECT_THAT(input_method_context_delegate_->last_on_confirm_composition_arg(),
              Optional(true));
  EXPECT_EQ(
      input_method_context_->predicted_state_for_testing().surrounding_text,
      text);
  // Cursor position is set to `range` position explicitly by OnCursorPosition.
  EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
            range);
  EXPECT_EQ(input_method_context_->predicted_state_for_testing().composition,
            gfx::Range(0));
}
#endif

TEST_P(WaylandInputMethodContextTest, OnSetPreeditRegion_Success) {}

TEST_P(WaylandInputMethodContextTest, OnSetPreeditRegion_NoSurroundingText) {}

// The range is represented in UTF-16 code points, so it is independent from
// grapheme clusters.
TEST_P(WaylandInputMethodContextTest,
       OnSetPreeditRegion_GraphemeClusterIndependeceSimple) {}

TEST_P(WaylandInputMethodContextTest,
       OnSetPreeditRegion_GraphemeClusterIndependeceCombined) {}

TEST_P(WaylandInputMethodContextTest, OnClearGrammarFragments) {}

TEST_P(WaylandInputMethodContextTest, OnAddGrammarFragments) {}

TEST_P(WaylandInputMethodContextTest, OnInsertImage) {}

TEST_P(WaylandInputMethodContextTest, OnSetAutocorrectRange) {}

TEST_P(WaylandInputMethodContextTest, OnSetVirtualKeyboardOccludedBounds) {}

TEST_P(WaylandInputMethodContextTest,
       OnSetVirtualKeyboardOccludedBoundsUpdatesPastTextInputClients) {}

TEST_P(WaylandInputMethodContextTest,
       OnSetVirtualKeyboardOccludedBoundsWithDeletedPastTextInputClient) {}

TEST_P(WaylandInputMethodContextTest, DisplayVirtualKeyboard) {}

TEST_P(WaylandInputMethodContextTest, DismissVirtualKeyboard) {}

TEST_P(WaylandInputMethodContextTest, UpdateVirtualKeyboardState) {}

TEST_P(WaylandInputMethodContextTest, OnKeySym) {}

namespace {

std::unique_ptr<KeyEvent> CreateKeyEventForCharacterComposer(
    KeyboardCode keyboard_code,
    DomCode dom_code,
    DomKey dom_key) {}

}  // namespace

TEST_P(WaylandInputMethodContextTest, CharacterComposerPreeditStringDeadKey) {}

TEST_P(WaylandInputMethodContextTest,
       CharacterComposerPreeditStringComposeKey) {}

class WaylandInputMethodContextNoKeyboardTest
    : public WaylandInputMethodContextTest {};

INSTANTIATE_TEST_SUITE_P();

TEST_P(WaylandInputMethodContextNoKeyboardTest, ActivateDeactivate) {}

TEST_P(WaylandInputMethodContextNoKeyboardTest, UpdateFocusBetweenTextFields) {}

// For use in tests that simply test the WaylandInputMethodContext in isolation
// without using a real v1/v3 wrapper.
class WaylandInputMethodContextWithMockWrapperTest : public WaylandTestSimple {};

TEST_F(WaylandInputMethodContextWithMockWrapperTest,
       SetSurroundingShortTextWithCompositionRange) {}

TEST_F(WaylandInputMethodContextWithMockWrapperTest,
       SetSurroundingLongTextWithCompositionRange) {}

TEST_F(WaylandInputMethodContextWithMockWrapperTest,
       SetSurroundingLongTextWithCompositionRangeOutsideSurroundingTextRange) {}

TEST_F(WaylandInputMethodContextWithMockWrapperTest,
       SetSurroundingWithCompositionRangeOutideText) {}

TEST_F(WaylandInputMethodContextWithMockWrapperTest,
       SetSurroundingWithCompositionRangeInvalid) {}

TEST_F(WaylandInputMethodContextWithMockWrapperTest, OnPreeditChanged) {}

TEST_F(WaylandInputMethodContextWithMockWrapperTest,
       OnPreeditChangedInvalidCursorEnd) {}

TEST_F(WaylandInputMethodContextWithMockWrapperTest,
       OnPreeditChangedGnomeWorkaround) {}

}  // namespace ui