Commit 503b46a1 authored by Sujie Zhu's avatar Sujie Zhu Committed by Commit Bot

[Nickname Management][Settings page] Add nickname input validation.

We limit the nickname input maxlength to 25 and valid the nickname
input to ensure no digit is used as nickname.
Display the error message, and disable save button when the nickname
input is invalid.

Local build test video (googlers only):
https://drive.google.com/file/d/1l2Dck63oktdCus6GS8ywNAXFU7iJ0lxV/view?usp=sharing

The follow up CL is to fine-tune the CSS layout (width & margins) once
we finalize the UI.

Bug: 1082013
Change-Id: I2996c8a388e4dc4d282e3d9f4e769e4e00f60581
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2217091Reviewed-by: default avatardpapad <dpapad@chromium.org>
Reviewed-by: default avatarDominic Battré <battre@chromium.org>
Reviewed-by: default avatarJared Saul <jsaul@google.com>
Commit-Queue: Sujie Zhu <sujiezhu@google.com>
Cr-Commit-Position: refs/heads/master@{#773296}
parent 15342b36
......@@ -283,6 +283,9 @@
<message name="IDS_SETTINGS_CREDIT_CARD_NICKNAME" desc="The title for the input that lets users modify the nickname of the credit card.">
Card nickname
</message>
<message name="IDS_SETTINGS_CREDIT_CARD_NICKNAME_INVALID" desc="The error message that is shown when user uses digit numbers in credit card nickname">
Nickname can’t include numbers
</message>
<message name="IDS_SETTINGS_UPI_ID_LABEL" desc="A label which appears next to UPI IDs, when they are listed as stored payment info, but it is visually separate from the UPI ID. It lets the user know this is a UPI ID (e.g. as opposed to a credit card number). UPI is a system for payments in India. A UPI ID is an email-like string.">
UPI ID
</message>
......
743c539a4df50bd8f31180d34c4cc5f5994374c6
\ No newline at end of file
57675a7bb1603c4d225f68ee9a6c1bc44c2ee051
\ No newline at end of file
......@@ -43,6 +43,11 @@
#year {
width: 100px;
}
/* For nickname input, we might dispay error message.*/
#nicknameInput {
--cr-input-error-display: block;
}
</style>
<cr-dialog id="dialog" close-text="$i18n{close}">
<div slot="title">[[title_]]</div>
......@@ -51,12 +56,10 @@
nickname management is not enabled. -->
<cr-input id="legacyNameInput" label="$i18n{creditCardName}"
value="{{creditCard.name}}" spellcheck="false"
hidden$="[[nicknameManagementEnabled_]]" autofocus
on-input="onCreditCardNameOrNumberChanged_">
hidden$="[[nicknameManagementEnabled_]]" autofocus>
</cr-input>
<cr-input id="numberInput" label="$i18n{creditCardNumber}"
value="{{creditCard.cardNumber}}" autofocus
on-input="onCreditCardNameOrNumberChanged_">
value="{{creditCard.cardNumber}}" autofocus>
</cr-input>
<!-- aria-hidden for creditCardExpiration label since
creditCardExpirationMonth and creditCardExpirationYear provide
......@@ -88,13 +91,13 @@
<!-- TODO(crbug.com/1063426): Update CSS layout.-->
<template is="dom-if" if="[[nicknameManagementEnabled_]]">
<cr-input id="nameInput" label="$i18n{creditCardName}"
value="{{creditCard.name}}" spellcheck="false"
on-input="onCreditCardNameOrNumberChanged_">
value="{{creditCard.name}}" spellcheck="false">
</cr-input>
<!-- TODO(crbug.com/1063426): Limit nickname length and add validation
check.-->
<cr-input id="nicknameInput" label="$i18n{creditCardNickname}"
value="{{creditCard.nickname}}" spellcheck="false">
value="{{creditCard.nickname}}" spellcheck="false" maxlength="25"
on-input="validateNickname_"
invalid="[[nicknameInvalid_]]"
error-message="$i18n{creditCardNicknameInvalid}">
</cr-input>
</template>
<div id="saved-to-this-device-only-label">
......@@ -105,6 +108,10 @@
<cr-button id="cancelButton" class="cancel-button"
on-click="onCancelButtonTap_">$i18n{cancel}</cr-button>
<cr-button id="saveButton" class="action-button"
on-click="onSaveButtonTap_" disabled>$i18n{save}</cr-button>
on-click="onSaveButtonTap_"
disabled="[[!saveEnabled_(nicknameInvalid_, creditCard.*,
expirationMonth_, expirationYear_)]]">
$i18n{save}
</cr-button>
</div>
</cr-dialog>
......@@ -21,6 +21,13 @@ import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bun
import {loadTimeData} from '../i18n_setup.js';
/**
* Regular expression for invalid nickname. Nickname containing any digits will
* be treated as invalid.
* @type {!RegExp}
*/
const NICKNAME_INVALID_REGEX = new RegExp('.*\\d+.*');
Polymer({
is: 'settings-credit-card-edit-dialog',
......@@ -73,6 +80,15 @@ Polymer({
return loadTimeData.getBoolean('nicknameManagementEnabled');
}
},
/**
* Whether the current nickname input is invalid.
* @private
*/
nicknameInvalid_: {
type: Boolean,
value: false,
},
},
behaviors: [
......@@ -96,9 +112,6 @@ Polymer({
this.title_ = this.i18n(
this.creditCard.guid ? 'editCreditCardTitle' : 'addCreditCardTitle');
// Needed to initialize the disabled state of the Save button.
this.onCreditCardNameOrNumberChanged_();
// Add a leading '0' if a month is 1 char.
if (this.creditCard.expirationMonth.length == 1) {
this.creditCard.expirationMonth = '0' + this.creditCard.expirationMonth;
......@@ -162,28 +175,34 @@ Polymer({
/** @private */
onMonthChange_() {
this.expirationMonth_ = this.monthList_[this.$.month.selectedIndex];
this.$.saveButton.disabled = !this.saveEnabled_();
},
/** @private */
onYearChange_() {
this.expirationYear_ = this.yearList_[this.$.year.selectedIndex];
this.$.saveButton.disabled = !this.saveEnabled_();
},
/** @private */
onCreditCardNameOrNumberChanged_() {
this.$.saveButton.disabled = !this.saveEnabled_();
},
/** @private */
saveEnabled_() {
// The save button is enabled if:
// There is and name or number for the card
// and the expiration date is valid.
// and the expiration date is valid
// and the nickname is valid if present.
return ((this.creditCard.name && this.creditCard.name.trim()) ||
(this.creditCard.cardNumber &&
this.creditCard.cardNumber.trim())) &&
!this.checkIfCardExpired_(this.expirationMonth_, this.expirationYear_);
!this.checkIfCardExpired_(
this.expirationMonth_, this.expirationYear_) &&
!this.nicknameInvalid_;
},
/**
* Validate no digits are used in nickname. Display error message and disable
* the save button when invalid.
* @private
*/
validateNickname_() {
this.nicknameInvalid_ =
NICKNAME_INVALID_REGEX.test(this.creditCard.nickname);
},
});
......@@ -849,6 +849,7 @@ void AddAutofillStrings(content::WebUIDataSource* html_source,
{"creditCardExpiration", IDS_SETTINGS_CREDIT_CARD_EXPIRATION_DATE},
{"creditCardName", IDS_SETTINGS_NAME_ON_CREDIT_CARD},
{"creditCardNickname", IDS_SETTINGS_CREDIT_CARD_NICKNAME},
{"creditCardNicknameInvalid", IDS_SETTINGS_CREDIT_CARD_NICKNAME_INVALID},
{"creditCardNumber", IDS_SETTINGS_CREDIT_CARD_NUMBER},
{"creditCardExpirationMonth", IDS_SETTINGS_CREDIT_CARD_EXPIRATION_MONTH},
{"creditCardExpirationYear", IDS_SETTINGS_CREDIT_CARD_EXPIRATION_YEAR},
......
......@@ -13,6 +13,16 @@ import {createCreditCardEntry, createEmptyCreditCardEntry, TestPaymentsManager}
import {eventToPromise, whenAttributeIs} from 'chrome://test/test_util.m.js';
// clang-format on
/**
* Helper function to simulate typing in nickname in the nickname field.
* @param {!Element} nicknameInput
* @param {string} nickname
*/
function typeInNickname(nicknameInput, nickname) {
nicknameInput.value = nickname;
nicknameInput.fire('input');
}
suite('PaymentsSectionCreditCardEditDialogTest', function() {
setup(function() {
PolymerTest.clearBody();
......@@ -127,11 +137,9 @@ suite('PaymentsSectionCreditCardEditDialogTest', function() {
// Wait for the dialog to open.
await whenAttributeIs(creditCardDialog.$$('#dialog'), 'open', '');
// Fill in name to the legacy name input field and card number, and trigger
// the on-input handler.
// Fill in name to the legacy name input field and card number.
creditCardDialog.$$('#legacyNameInput').value = 'Jane Doe';
creditCardDialog.$$('#numberInput').value = '4111111111111111';
creditCardDialog.onCreditCardNameOrNumberChanged_();
flush();
assertTrue(creditCardDialog.$.expired.hidden);
......@@ -159,8 +167,7 @@ suite('PaymentsSectionCreditCardEditDialogTest', function() {
// handler.
creditCardDialog.$$('#nameInput').value = 'Jane Doe';
creditCardDialog.$$('#numberInput').value = '4111111111111111';
creditCardDialog.$$('#nicknameInput').value = 'Grocery Card';
creditCardDialog.onCreditCardNameOrNumberChanged_();
typeInNickname(creditCardDialog.$$('#nicknameInput'), 'Grocery Card');
flush();
assertTrue(creditCardDialog.$.expired.hidden);
......@@ -204,8 +211,7 @@ suite('PaymentsSectionCreditCardEditDialogTest', function() {
// on-input handler.
creditCardDialog.$$('#nameInput').value = 'Jane Doe';
creditCardDialog.$$('#numberInput').value = '4111111111111111';
creditCardDialog.$$('#nicknameInput').value = 'Grocery Card';
creditCardDialog.onCreditCardNameOrNumberChanged_();
typeInNickname(creditCardDialog.$$('#nicknameInput'), 'Grocery Card');
flush();
const savedPromise = eventToPromise('save-credit-card', creditCardDialog);
......@@ -218,4 +224,74 @@ suite('PaymentsSectionCreditCardEditDialogTest', function() {
assertEquals(saveEvent.detail.cardNumber, '4111111111111111');
assertEquals(saveEvent.detail.nickname, 'Grocery Card');
});
test('show error message when input nickname is invalid', async function() {
loadTimeData.overrideValues({nicknameManagementEnabled: true});
const creditCardDialog = createAddCreditCardDialog();
// Wait for the dialog to open.
await whenAttributeIs(creditCardDialog.$$('#dialog'), 'open', '');
// User clicks on nickname input.
const nicknameInput = creditCardDialog.$$('#nicknameInput');
assertTrue(!!nicknameInput);
nicknameInput.focus();
const validInputs = [
'', ' ', '~@#$%^&**(){}|<>', 'Grocery Card', 'Two percent Cashback',
/* UTF-16 hex encoded credit card emoji */ 'Chase Freedom \uD83D\uDCB3'
];
for (const nickname of validInputs) {
typeInNickname(nicknameInput, nickname);
assertFalse(nicknameInput.invalid);
// Error message is hidden for valid nickname input.
assertEquals(
'hidden', getComputedStyle(nicknameInput.$.error).visibility);
}
// Verify invalid nickname inputs.
const invalidInputs = [
'12345', '2abc', 'abc3', 'abc4de', 'a 1 b',
/* UTF-16 hex encoded digt 7 emoji */ 'Digit emoji: \u0037\uFE0F\u20E3'
];
for (const nickname of invalidInputs) {
typeInNickname(nicknameInput, nickname);
assertTrue(nicknameInput.invalid);
assertNotEquals('', nicknameInput.errorMessage);
// Error message is shown for invalid nickname input.
assertEquals(
'visible', getComputedStyle(nicknameInput.$.error).visibility);
}
// The error message is still shown even when user does not focus on the
// nickname field.
nicknameInput.blur();
assertTrue(nicknameInput.invalid);
assertEquals('visible', getComputedStyle(nicknameInput.$.error).visibility);
});
test('disable save button when input nickname is invalid', async function() {
loadTimeData.overrideValues({nicknameManagementEnabled: true});
const creditCard = createCreditCardEntry();
creditCard.name = 'Wrong name';
const now = new Date();
// Set the expiration year to next year to avoid expired card.
creditCard.expirationYear = now.getFullYear() + 1;
creditCard.cardNumber = '4444333322221111';
// Edit dialog for an existing card with no nickname.
const creditCardDialog = createEditCreditCardDialog([creditCard]);
// Wait for the dialog to open.
await whenAttributeIs(creditCardDialog.$$('#dialog'), 'open', '');
// Save button is enabled for existing card with no nickname.
assertFalse(creditCardDialog.$.saveButton.disabled);
const nicknameInput = creditCardDialog.$$('#nicknameInput');
typeInNickname(nicknameInput, 'invalid: 123');
// Save button is disabled since the nickname is invalid.
assertTrue(creditCardDialog.$.saveButton.disabled);
typeInNickname(nicknameInput, 'valid nickname');
// Save button is back to enabled since user updates with a valid nickname.
assertFalse(creditCardDialog.$.saveButton.disabled);
});
});
......@@ -328,9 +328,8 @@ suite('PaymentsSection', function() {
assertTrue(creditCardDialog.$.expired.hidden);
assertTrue(creditCardDialog.$.saveButton.disabled);
// Add a name and trigger the on-input handler.
// Add a name.
creditCardDialog.set('creditCard.name', 'Jane Doe');
creditCardDialog.onCreditCardNameOrNumberChanged_();
flush();
assertTrue(creditCardDialog.$.expired.hidden);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment