chromium/ash/auth/views/auth_input_row_view_unittest.cc

// 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 "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 kPassword = u"password";
constexpr std::u16string 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 modifiedString = kPassword + u"s";
  EXPECT_CALL(*mock_observer_, OnContentsChanged(modifiedString)).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())).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())).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);
}

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 modifiedPIN = kPIN + u"5";
  EXPECT_CALL(*mock_observer_, OnContentsChanged(modifiedPIN)).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 modifiedPIN = kPIN + u"5";
  EXPECT_CALL(*mock_observer_, OnContentsChanged(modifiedPIN)).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) {
  std::u16string modifiedPIN = kPIN;
  modifiedPIN.pop_back();
  EXPECT_CALL(*mock_observer_, OnContentsChanged(modifiedPIN)).Times(1);
  PressAndReleaseKey(ui::VKEY_BACK);
}

// Testing PIN backspace press with disabled input area.
TEST_F(InputRowWithPinUnitTest, DisabledBackspacePressTest) {
  auth_input_->SetInputEnabled(false);
  std::u16string modifiedPIN = kPIN;
  modifiedPIN.pop_back();
  EXPECT_CALL(*mock_observer_, OnContentsChanged(modifiedPIN)).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();

  std::u16string modifiedPIN = kPIN;
  modifiedPIN.pop_back();
  EXPECT_CALL(*mock_observer_, OnContentsChanged(modifiedPIN)).Times(1);
  PressAndReleaseKey(ui::VKEY_BACK);
}

// 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