910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/auth/views/auth_input_row_view.h"

#include <memory>
#include <string>

#include "ash/auth/views/auth_textfield.h"
#include "ash/auth/views/test_support/mock_auth_input_row_view_observer.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/check.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/widget/widget.h"

namespace ash {

namespace {

constexpr std::u16string_view kPassword = u"password";
constexpr std::u16string_view kPin = u"123456";

}  // namespace

class InputRowWithPasswordUnitTest : public AshTestBase {
 public:
  InputRowWithPasswordUnitTest() = default;
  InputRowWithPasswordUnitTest(const InputRowWithPasswordUnitTest&) = delete;
  InputRowWithPasswordUnitTest& operator=(const InputRowWithPasswordUnitTest&) =
      delete;
  ~InputRowWithPasswordUnitTest() override = default;

  void SetInputRowToFocus() { auth_input_->RequestFocus(); }

 protected:
  // AshTestBase:
  void SetUp() override {
    AshTestBase::SetUp();

    widget_ = CreateFramelessTestWidget();
    widget_->SetFullscreen(true);
    widget_->Show();

    auth_input_ = widget_->SetContentsView(std::make_unique<AuthInputRowView>(
        AuthInputRowView::AuthType::kPassword));
    test_api_ = std::make_unique<AuthInputRowView::TestApi>(auth_input_);
    // Initialize the textfield with some text.
    auth_input_->RequestFocus();
    for (const char16_t c : kPassword) {
      PressAndReleaseKey(ui::DomCodeToUsLayoutNonLocatedKeyboardCode(
          ui::UsLayoutDomKeyToDomCode(ui::DomKey::FromCharacter(c))));
    }
    CHECK(test_api_->GetTextfield()->HasFocus());
    CHECK(test_api_->GetSubmitButton()->GetEnabled());
    CHECK(test_api_->GetDisplayTextButton()->GetEnabled());
    CHECK_EQ(test_api_->GetDisplayTextButton()->GetToggled(), false);

    // Add observer.
    mock_observer_ = std::make_unique<MockAuthInputRowViewObserver>();
    auth_input_->AddObserver(mock_observer_.get());
  }

  void TearDown() override {
    test_api_.reset();
    auth_input_->RemoveObserver(mock_observer_.get());
    mock_observer_.reset();
    auth_input_ = nullptr;
    widget_.reset();
    AshTestBase::TearDown();
  }

  std::unique_ptr<views::Widget> widget_;
  std::unique_ptr<MockAuthInputRowViewObserver> mock_observer_;
  std::unique_ptr<AuthInputRowView::TestApi> test_api_;
  raw_ptr<AuthInputRowView> auth_input_ = nullptr;
};

// Testing tab on the textfield the textfield lost the focus and the display
// text button gets it.
TEST_F(InputRowWithPasswordUnitTest, OnBlurObserverTest) {
  EXPECT_CALL(*mock_observer_, OnTextfieldBlur()).Times(1);
  PressAndReleaseKey(ui::VKEY_TAB);
  CHECK(test_api_->GetDisplayTextButton()->HasFocus());
}

// Testing textfield OnFocus observer.
TEST_F(InputRowWithPasswordUnitTest, OnFocusObserverWithClickTest) {
  PressAndReleaseKey(ui::VKEY_TAB);
  EXPECT_CALL(*mock_observer_, OnTextfieldFocus()).Times(1);
  LeftClickOn(test_api_->GetTextfield());
}

// Testing textfield OnContentsChanged observer.
TEST_F(InputRowWithPasswordUnitTest, OnContentsChangedTest) {
  const std::u16string modified_string = base::StrCat({kPassword, u"s"});
  EXPECT_CALL(*mock_observer_,
              OnContentsChanged(std::u16string_view(modified_string)))
      .Times(1);
  PressAndReleaseKey(ui::VKEY_S);
}

// Testing OnTextVisibleChanged Observer with keyboard interactions.
TEST_F(InputRowWithPasswordUnitTest, OnTextVisibleChangedTestWithKeyPresses) {
  PressAndReleaseKey(ui::VKEY_TAB);
  CHECK(test_api_->GetDisplayTextButton()->HasFocus());
  EXPECT_CALL(*mock_observer_, OnTextVisibleChanged(true)).Times(1);
  PressAndReleaseKey(ui::VKEY_RETURN);
}

// Testing OnTextVisibleChanged observer with mouse click.
TEST_F(InputRowWithPasswordUnitTest, OnTextVisibleChangedTestWithMouseClick) {
  EXPECT_CALL(*mock_observer_, OnTextVisibleChanged(true)).Times(1);
  LeftClickOn(test_api_->GetDisplayTextButton());
}

// Testing press enter in the textfield calls the submit with the password.
TEST_F(InputRowWithPasswordUnitTest, OnSubmitTestWithEnter) {
  EXPECT_CALL(*mock_observer_, OnSubmit(kPassword)).Times(1);
  PressAndReleaseKey(ui::VKEY_RETURN);
}

// Testing press enter with disabled state.
TEST_F(InputRowWithPasswordUnitTest, TestDisabledEnter) {
  auth_input_->SetInputEnabled(false);
  EXPECT_CALL(*mock_observer_, OnSubmit(kPassword)).Times(0);
  PressAndReleaseKey(ui::VKEY_RETURN);
}

// Testing clicking on the submit button calls the submit with the password.
TEST_F(InputRowWithPasswordUnitTest, OnSubmitTestWithClick) {
  EXPECT_CALL(*mock_observer_, OnSubmit(kPassword)).Times(1);
  LeftClickOn(test_api_->GetSubmitButton());
}

// Testing clicking on the submit button with disabled state.
TEST_F(InputRowWithPasswordUnitTest, DisabledSubmitClickTest) {
  auth_input_->SetInputEnabled(false);
  EXPECT_CALL(*mock_observer_, OnSubmit(kPassword)).Times(0);
  LeftClickOn(test_api_->GetSubmitButton());
}

// Testing ESC press in the textfield calls OnEscape observer.
TEST_F(InputRowWithPasswordUnitTest, TextfieldOnEscapeTest) {
  EXPECT_CALL(*mock_observer_, OnEscape()).Times(1);
  PressAndReleaseKey(ui::VKEY_ESCAPE);
}

// Testing ESC press on the display text calls OnEscape observer.
TEST_F(InputRowWithPasswordUnitTest, DisplayButtonOnEscapeTest) {
  PressAndReleaseKey(ui::VKEY_TAB);
  CHECK(test_api_->GetDisplayTextButton()->HasFocus());
  EXPECT_CALL(*mock_observer_, OnEscape()).Times(1);
  PressAndReleaseKey(ui::VKEY_ESCAPE);
}

// Testing ESC press on the Submit button calls OnEscape observer.
TEST_F(InputRowWithPasswordUnitTest, SubmitButtonOnEscapeTest) {
  auth_input_->GetFocusManager()->SetFocusedView(test_api_->GetSubmitButton());
  CHECK(test_api_->GetSubmitButton()->HasFocus());
  EXPECT_CALL(*mock_observer_, OnEscape()).Times(1);
  PressAndReleaseKey(ui::VKEY_ESCAPE);
}

// Testing When password is visible and select all on the password and remove
// the text should call: 1: OnContentsChanged 2: OnTextVisibleChanged(/*
// visible=*/false)
TEST_F(InputRowWithPasswordUnitTest, RemoveTextTest) {
  LeftClickOn(test_api_->GetDisplayTextButton());
  // Select all and delete.
  EXPECT_CALL(*mock_observer_, OnContentsChanged(std::u16string_view()))
      .Times(1);
  EXPECT_CALL(*mock_observer_, OnTextVisibleChanged(false)).Times(1);
  PressAndReleaseKey(ui::VKEY_A, ui::EF_CONTROL_DOWN);
  PressAndReleaseKey(ui::VKEY_BACK);
  // Submit button should be disabled with empty text.
  CHECK_EQ(test_api_->GetSubmitButton()->GetEnabled(), false);
  // Display text button should be disabled with empty text.
  CHECK_EQ(test_api_->GetDisplayTextButton()->GetEnabled(), false);
}

// Testing ResetState functionality.
TEST_F(InputRowWithPasswordUnitTest, ResetStateTest) {
  EXPECT_CALL(*mock_observer_, OnContentsChanged(std::u16string_view()))
      .Times(1);
  auth_input_->ResetState();

  // The textfield should be empty.
  EXPECT_EQ(test_api_->GetTextfield()->GetText(), std::u16string());
  // Submit button should be disabled with empty text.
  CHECK_EQ(test_api_->GetSubmitButton()->GetEnabled(), false);
  // Display text button should be disabled with empty text.
  CHECK_EQ(test_api_->GetDisplayTextButton()->GetEnabled(), false);

  // Double check that SetInputEnabled will not enable the buttons when
  // there is no input.
  auth_input_->SetInputEnabled(true);
  CHECK_EQ(test_api_->GetSubmitButton()->GetEnabled(), false);
  CHECK_EQ(test_api_->GetDisplayTextButton()->GetEnabled(), false);
}

class InputRowWithPinUnitTest : public AshTestBase {
 public:
  InputRowWithPinUnitTest() = default;
  InputRowWithPinUnitTest(const InputRowWithPinUnitTest&) = delete;
  InputRowWithPinUnitTest& operator=(const InputRowWithPinUnitTest&) = delete;
  ~InputRowWithPinUnitTest() override = default;

  void SetInputRowToFocus() { auth_input_->RequestFocus(); }

 protected:
  // AshTestBase:
  void SetUp() override {
    AshTestBase::SetUp();

    widget_ = CreateFramelessTestWidget();
    widget_->SetFullscreen(true);
    widget_->Show();

    auth_input_ = widget_->SetContentsView(
        std::make_unique<AuthInputRowView>(AuthInputRowView::AuthType::kPin));
    test_api_ = std::make_unique<AuthInputRowView::TestApi>(auth_input_);
    // Initialize the textfield with some text.
    auth_input_->RequestFocus();

    for (const char16_t c : kPin) {
      PressAndReleaseKey(ui::DomCodeToUsLayoutNonLocatedKeyboardCode(
          ui::UsLayoutDomKeyToDomCode(ui::DomKey::FromCharacter(c))));
    }
    CHECK(test_api_->GetTextfield()->HasFocus());
    CHECK_EQ(test_api_->GetTextfield()->GetText(), kPin);
    CHECK(test_api_->GetSubmitButton()->GetEnabled());
    CHECK(test_api_->GetDisplayTextButton()->GetEnabled());
    CHECK_EQ(test_api_->GetDisplayTextButton()->GetToggled(), false);

    // Add observer.
    mock_observer_ = std::make_unique<MockAuthInputRowViewObserver>();
    auth_input_->AddObserver(mock_observer_.get());
  }

  void TearDown() override {
    test_api_.reset();
    auth_input_->RemoveObserver(mock_observer_.get());
    mock_observer_.reset();
    auth_input_ = nullptr;
    widget_.reset();
    AshTestBase::TearDown();
  }

  std::unique_ptr<views::Widget> widget_;
  std::unique_ptr<MockAuthInputRowViewObserver> mock_observer_;
  std::unique_ptr<AuthInputRowView::TestApi> test_api_;
  raw_ptr<AuthInputRowView> auth_input_ = nullptr;
};

// Testing PIN OnContentsChanged observer.
TEST_F(InputRowWithPinUnitTest, OnContentsChangedTest) {
  const std::u16string modified_pin = base::StrCat({kPin, u"5"});
  EXPECT_CALL(*mock_observer_,
              OnContentsChanged(std::u16string_view(modified_pin)))
      .Times(1);
  PressAndReleaseKey(ui::VKEY_5);
}

// Testing PIN OnContentsChanged observer with disabled input area.
TEST_F(InputRowWithPinUnitTest, DisabledDigitPressTest) {
  auth_input_->SetInputEnabled(false);
  const std::u16string modified_pin = base::StrCat({kPin, u"5"});
  EXPECT_CALL(*mock_observer_,
              OnContentsChanged(std::u16string_view(modified_pin)))
      .Times(0);
  PressAndReleaseKey(ui::VKEY_5);
}

// Testing PIN OnContentsChanged observer with letter.
TEST_F(InputRowWithPinUnitTest, OnContentsChangedWithLetterTest) {
  EXPECT_CALL(*mock_observer_, OnContentsChanged(kPin)).Times(0);
  PressAndReleaseKey(ui::VKEY_E);
  CHECK_EQ(test_api_->GetTextfield()->GetText(), kPin);
}

// Testing PIN backspace press.
TEST_F(InputRowWithPinUnitTest, BackspacePressTest) {
  static constexpr std::u16string_view kModifiedPin =
      kPin.substr(0, kPin.size() - 1);
  EXPECT_CALL(*mock_observer_, OnContentsChanged(kModifiedPin)).Times(1);
  PressAndReleaseKey(ui::VKEY_BACK);
}

// Testing PIN backspace press with disabled input area.
TEST_F(InputRowWithPinUnitTest, DisabledBackspacePressTest) {
  auth_input_->SetInputEnabled(false);
  static constexpr std::u16string_view kModifiedPin =
      kPin.substr(0, kPin.size() - 1);
  EXPECT_CALL(*mock_observer_, OnContentsChanged(kModifiedPin)).Times(0);
  PressAndReleaseKey(ui::VKEY_BACK);
}

// Testing PIN backspace press with disabled and after that re-enabled input
// area.
TEST_F(InputRowWithPinUnitTest, ReenabledBackspacePressTest) {
  auth_input_->SetInputEnabled(false);
  auth_input_->SetInputEnabled(true);
  auth_input_->RequestFocus();

  static constexpr std::u16string_view kModifiedPin =
      kPin.substr(0, kPin.size() - 1);
  EXPECT_CALL(*mock_observer_, OnContentsChanged(kModifiedPin)).Times(1);
  PressAndReleaseKey(ui::VKEY_BACK);
}

// Testing that clearing the input would disable submit and display-text button.
TEST_F(InputRowWithPinUnitTest, ClearingInputDisabledButtonsTest) {
  auth_input_->SetInputEnabled(true);
  auth_input_->RequestFocus();
  std::u16string modified_pin(kPin);
  while (!modified_pin.empty()) {
    modified_pin.pop_back();
    EXPECT_CALL(*mock_observer_,
                OnContentsChanged(std::u16string_view(modified_pin)))
        .Times(1);
    PressAndReleaseKey(ui::VKEY_BACK);
  }
  CHECK_EQ(test_api_->GetSubmitButton()->GetEnabled(), false);
  CHECK_EQ(test_api_->GetDisplayTextButton()->GetEnabled(), false);

  // Double check that SetInputEnabled will not enable the buttons when
  // there is no input.
  auth_input_->SetInputEnabled(true);
  CHECK_EQ(test_api_->GetSubmitButton()->GetEnabled(), false);
  CHECK_EQ(test_api_->GetDisplayTextButton()->GetEnabled(), false);
}

// Testing PIN OnSubmit observer.
TEST_F(InputRowWithPinUnitTest, OnSubmitTest) {
  EXPECT_CALL(*mock_observer_, OnSubmit(kPin)).Times(1);
  PressAndReleaseKey(ui::VKEY_RETURN);
}

// Testing PIN OnEscape observer.
TEST_F(InputRowWithPinUnitTest, OnEscapeTest) {
  EXPECT_CALL(*mock_observer_, OnEscape()).Times(1);
  PressAndReleaseKey(ui::VKEY_ESCAPE);
}

}  // namespace ash