blob: 641c9154a2b121e6bc0f583f3c5a80a60bb179b8 [file]
// Copyright 2023 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/login/ui/access_code_input.h"
#include <memory>
#include <optional>
#include <string>
#include "ash/strings/grit/ash_strings.h"
#include "ash/test/ash_test_base.h"
#include "base/strings/string_number_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/textfield/textfield.h"
namespace ash {
namespace {
const int fixed_pin_length = 6;
}
class FixedLengthCodeInputTest : public AshTestBase {
public:
FixedLengthCodeInputTest() = default;
FixedLengthCodeInputTest(const FixedLengthCodeInputTest&) = delete;
FixedLengthCodeInputTest& operator=(const FixedLengthCodeInputTest&) = delete;
~FixedLengthCodeInputTest() override = default;
protected:
// AshTestBase:
void SetUp() override {
AshTestBase::SetUp();
input_view_ = std::make_unique<FixedLengthCodeInput>(
fixed_pin_length,
base::BindRepeating(&FixedLengthCodeInputTest::OnInputChange,
base::Unretained(this)),
base::BindRepeating(&FixedLengthCodeInputTest::OnEnter,
base::Unretained(this)),
base::BindRepeating(&FixedLengthCodeInputTest::OnEscape,
base::Unretained(this)),
/*obscure_pin=*/false);
obscure_input_view_ = std::make_unique<FixedLengthCodeInput>(
fixed_pin_length,
base::BindRepeating(&FixedLengthCodeInputTest::OnInputChange,
base::Unretained(this)),
base::BindRepeating(&FixedLengthCodeInputTest::OnEnter,
base::Unretained(this)),
base::BindRepeating(&FixedLengthCodeInputTest::OnEscape,
base::Unretained(this)),
/*obscure_pin=*/true);
}
void TearDown() override { AshTestBase::TearDown(); }
void OnInputChange(bool last_field_active, bool complete) {
++on_input_change_count;
if (complete) {
++on_input_change_complete_count;
}
}
void OnEnter() { ++on_enter_count; }
void OnEscape() { ++on_escape_count; }
std::unique_ptr<FixedLengthCodeInput> input_view_;
std::unique_ptr<FixedLengthCodeInput> obscure_input_view_;
int on_input_change_count = 0;
int on_input_change_complete_count = 0;
int on_enter_count = 0;
int on_escape_count = 0;
};
// Validates that the FixedLengthCodeInput::ContentsChanged() method handles
// correctly when the Textfield::InsertText() method is called with a digit.
TEST_F(FixedLengthCodeInputTest, ContentsChangedWithDigits) {
FixedLengthCodeInput::TestApi test_api(input_view_.get());
for (int index = 1; index <= fixed_pin_length; ++index) {
int active_index = test_api.GetActiveIndex();
EXPECT_EQ(active_index + 1, index);
views::Textfield* textfield = test_api.GetInputTextField(active_index);
textfield->InsertText(
base::NumberToString16(index),
ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
EXPECT_EQ(on_input_change_count, index);
EXPECT_EQ(on_input_change_complete_count,
(index == fixed_pin_length ? 1 : 0));
}
std::optional<std::string> code = test_api.GetCode();
EXPECT_TRUE(code.has_value());
EXPECT_EQ(code.value(), "123456");
EXPECT_EQ(on_enter_count, 0);
EXPECT_EQ(on_escape_count, 0);
}
TEST_F(FixedLengthCodeInputTest, AccessibleProperties) {
ui::AXNodeData data;
input_view_->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_FALSE(data.HasState(ax::mojom::State::kProtected));
EXPECT_EQ(data.role, ax::mojom::Role::kTextField);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringUTF16(
IDS_ASH_LOGIN_PARENT_ACCESS_GENERIC_DESCRIPTION));
data = ui::AXNodeData();
obscure_input_view_->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_TRUE(data.HasState(ax::mojom::State::kProtected));
std::unique_ptr<AccessibleInputField> field =
std::make_unique<AccessibleInputField>();
data = ui::AXNodeData();
field->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName), u"");
}
TEST_F(FixedLengthCodeInputTest, AccessibilityTextSelectionBound) {
ui::AXNodeData data;
input_view_->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart), 0);
EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd), 0);
input_view_->InsertDigit(4);
input_view_->InsertDigit(4);
input_view_->InsertDigit(4);
data = ui::AXNodeData();
input_view_->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart), 3);
EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd), 3);
input_view_->Backspace();
data = ui::AXNodeData();
input_view_->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart), 2);
EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd), 2);
input_view_->ClearInput();
data = ui::AXNodeData();
input_view_->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart), 0);
EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd), 1);
input_view_->InsertDigit(4);
data = ui::AXNodeData();
input_view_->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart), 1);
EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd), 1);
}
// Validates that the FixedLengthCodeInput::ContentsChanged() method handles
// and ignores correctly when the Textfield::InsertText() method is called
// with multipledigit.
TEST_F(FixedLengthCodeInputTest, ContentsChangedWithMultipleDigits) {
FixedLengthCodeInput::TestApi test_api(input_view_.get());
int active_index = test_api.GetActiveIndex();
EXPECT_EQ(active_index, 0);
EXPECT_EQ(on_input_change_count, 0);
auto CheckInsertIgnored = [&]() {
std::optional<std::string> code = test_api.GetCode();
EXPECT_FALSE(code.has_value());
EXPECT_EQ(test_api.GetActiveIndex(), 0);
EXPECT_EQ(on_input_change_count, 0);
EXPECT_EQ(on_enter_count, 0);
EXPECT_EQ(on_escape_count, 0);
};
views::Textfield* textfield = test_api.GetInputTextField(active_index);
textfield->InsertText(
u"01",
ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
CheckInsertIgnored();
textfield->InsertText(
u"12",
ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
CheckInsertIgnored();
textfield->InsertText(
u"004",
ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
CheckInsertIgnored();
textfield->InsertText(
u"987",
ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
CheckInsertIgnored();
}
// Validates that the FixedLengthCodeInput::ContentsChanged() method handles
// and ignores correctly when the Textfield::InsertText() method is called
// with non numerical strings.
TEST_F(FixedLengthCodeInputTest, ContentsChangedWithNonNumericalStrings) {
FixedLengthCodeInput::TestApi test_api(input_view_.get());
int active_index = test_api.GetActiveIndex();
EXPECT_EQ(active_index, 0);
EXPECT_EQ(on_input_change_count, 0);
auto CheckInsertIgnored = [&]() {
std::optional<std::string> code = test_api.GetCode();
EXPECT_FALSE(code.has_value());
EXPECT_EQ(test_api.GetActiveIndex(), 0);
EXPECT_EQ(on_input_change_count, 0);
EXPECT_EQ(on_enter_count, 0);
EXPECT_EQ(on_escape_count, 0);
};
views::Textfield* textfield = test_api.GetInputTextField(active_index);
textfield->InsertText(
u"a",
ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
CheckInsertIgnored();
textfield->InsertText(
u"xz",
ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
CheckInsertIgnored();
textfield->InsertText(
u"/",
ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
CheckInsertIgnored();
textfield->InsertText(
u"", ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
CheckInsertIgnored();
}
} // namespace ash