Commit b643d8d6 authored by Eleonora Rocchi's avatar Eleonora Rocchi Committed by Commit Bot

[PwdCheckAndroid] Add text to the status header in Check passwords.

This CL adds the logic to fill the text describing the status of the
last/current check. It also adds tests to verify the correct messages
are displayed.

IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_OFFLINE.png.sha1 image:
https://storage.cloud.google.com/chromium-translation-screenshots/fcb59be745a5ffbb21fcd8f78f1608f6817910d7

IDS_PASSWORD_CHECK_STATUS_MESSAGE_RUNNING.png.sha1 image:
https://storage.cloud.google.com/chromium-translation-screenshots/7ed062035e44451197ebccfc7a52e9297e669c40

IDS_PASSWORD_CHECK_STATUS_MESSAGE_SUCCESS_NO_LEAKS.png.sha1 image:
https://storage.cloud.google.com/chromium-translation-screenshots/693aefb5231f2fa632d467c4febe56e5f4cd28b4

IDS_PASSWORD_CHECK_STATUS_MESSAGE_SUCCESS_WITH_LEAKS.png.sha1 image:
https://storage.cloud.google.com/chromium-translation-screenshots/f49f6f620eb105d574ea8b989701a56f6ec02b8b

Bug: 1109691, 1092444
Change-Id: I41144279472e00062fd12b91e8c645c3a283bf61
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2322486
Commit-Queue: Eleonora Rocchi <erocchi@google.com>
Reviewed-by: default avatarFriedrich [CET] <fhorschig@chromium.org>
Cr-Commit-Position: refs/heads/master@{#795874}
parent e64d8c93
......@@ -47,6 +47,7 @@
<TextView
android:id="@+id/check_status_description"
android:layout_marginTop="@dimen/check_status_description_margin_top"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textAppearance="@style/TextAppearance.TextSmall.Secondary"
......
......@@ -4,11 +4,17 @@
found in the LICENSE file. -->
<resources>
<dimen name="check_status_description_margin_top">2dp</dimen>
<dimen name="check_status_icon_margin_horizontal">16dp</dimen>
<dimen name="check_status_icon_size">24dp</dimen>
<dimen name="check_status_restart_button_clickable_surface_size">48dp</dimen>
<dimen name="check_status_restart_button_margin_end">4dp</dimen>
<!-- Margin for the header message, it depends on the status -->
<dimen name="check_status_message_error_margin_vertical">24dp</dimen>
<dimen name="check_status_message_idle_margin_vertical">27dp</dimen>
<dimen name="check_status_message_running_margin_vertical">36dp</dimen>
<dimen name="compromised_credential_row_button_margin_top">16dp</dimen>
<dimen name="compromised_credential_row_more_padding_end">8dp</dimen>
<dimen name="compromised_credential_row_more_padding_start">16dp</dimen>
......
......@@ -10,16 +10,22 @@ import static org.chromium.chrome.browser.password_check.PasswordCheckProperties
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.CHECK_STATUS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.CHECK_TIMESTAMP;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.COMPROMISED_CREDENTIALS_COUNT;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.UNKNOWN_PROGRESS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.ITEMS;
import static org.chromium.components.embedder_support.util.UrlUtilities.stripScheme;
import android.content.Context;
import android.content.res.Resources;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
import org.chromium.chrome.browser.password_check.PasswordCheckProperties.ItemType;
import org.chromium.chrome.browser.password_check.internal.R;
import org.chromium.components.browser_ui.widget.listmenu.BasicListMenu;
......@@ -156,21 +162,25 @@ class PasswordCheckViewBinder {
* @param key The {@link PropertyKey} which changed.
*/
private static void bindHeaderView(PropertyModel model, View view, PropertyKey key) {
Pair<Integer, Integer> progress = model.get(CHECK_PROGRESS);
@PasswordCheckUIStatus
int status = model.get(CHECK_STATUS);
Long checkTimestamp = model.get(CHECK_TIMESTAMP);
Integer compromisedCredentialsCount = model.get(COMPROMISED_CREDENTIALS_COUNT);
if (key == CHECK_PROGRESS) {
// TODO(crbug.com/1109691): Set text based on progress.
updateStatusText(view, status, compromisedCredentialsCount, checkTimestamp, progress);
} else if (key == CHECK_STATUS) {
// TODO(crbug.com/1109691): Set text and illustration based on status.
// TODO(crbug.com/1109691): Set illustration based on status.
updateActionButton(view, status);
updateStatusIcon(view, status, compromisedCredentialsCount);
updateStatusText(view, status, compromisedCredentialsCount, checkTimestamp, progress);
} else if (key == CHECK_TIMESTAMP) {
updateStatusText(view, status, compromisedCredentialsCount, checkTimestamp, progress);
} else if (key == COMPROMISED_CREDENTIALS_COUNT) {
// TODO(crbug.com/1109691): Set text and illustration based on compromised credential
// count.
// TODO(crbug.com/1109691): Set illustration based on compromised credentials count.
updateStatusIcon(view, status, compromisedCredentialsCount);
} else if (key == CHECK_TIMESTAMP) {
// TODO(crbug.com/1109691): Set text description based on timestamp.
updateStatusText(view, status, compromisedCredentialsCount, checkTimestamp, progress);
} else {
assert false : "Unhandled update to property:" + key;
}
......@@ -195,6 +205,7 @@ class PasswordCheckViewBinder {
private static void updateStatusIcon(
View view, @PasswordCheckUIStatus int status, Integer compromisedCredentialsCount) {
// TODO(crbug.com/1114051): Set default values for header properties.
if (status == PasswordCheckUIStatus.IDLE && compromisedCredentialsCount == null) return;
ImageView statusIcon = view.findViewById(R.id.check_status_icon);
statusIcon.setImageResource(getIconResource(status, compromisedCredentialsCount));
......@@ -234,6 +245,121 @@ class PasswordCheckViewBinder {
return status == PasswordCheckUIStatus.RUNNING ? View.VISIBLE : View.GONE;
}
private static void updateStatusText(View view, @PasswordCheckUIStatus int status,
Integer compromisedCredentialsCount, Long checkTimestamp,
Pair<Integer, Integer> progress) {
// TODO(crbug.com/1114051): Set default values for header properties.
if (status == PasswordCheckUIStatus.IDLE
&& (compromisedCredentialsCount == null || checkTimestamp == null)) {
return;
}
if (status == PasswordCheckUIStatus.RUNNING && progress == null) return;
TextView statusMessage = view.findViewById(R.id.check_status_message);
statusMessage.setText(
getStatusMessage(view, status, compromisedCredentialsCount, progress));
LinearLayout textLayout = view.findViewById(R.id.check_status_text_layout);
int verticalMargin = getDimensionPixelOffset(view, getStatusTextMargin(status));
textLayout.setPadding(0, verticalMargin, 0, verticalMargin);
TextView statusDescription = view.findViewById(R.id.check_status_description);
statusDescription.setText(getStatusDescription(view, checkTimestamp));
statusDescription.setVisibility(getStatusDescriptionVisibility(status));
}
private static String getStatusMessage(View view, @PasswordCheckUIStatus int status,
Integer compromisedCredentialsCount, Pair<Integer, Integer> progress) {
switch (status) {
case PasswordCheckUIStatus.IDLE:
assert compromisedCredentialsCount != null;
return compromisedCredentialsCount == 0
? getString(view, R.string.password_check_status_message_idle_no_leaks)
: view.getContext().getResources().getQuantityString(
R.plurals.password_check_status_message_idle_with_leaks,
compromisedCredentialsCount, compromisedCredentialsCount);
case PasswordCheckUIStatus.RUNNING:
assert progress != null;
if (progress.equals(UNKNOWN_PROGRESS)) {
return getString(view, R.string.password_check_status_message_initial_running);
} else {
return String.format(
getString(view, R.string.password_check_status_message_running),
progress.first, progress.second);
}
case PasswordCheckUIStatus.ERROR_OFFLINE:
return getString(view, R.string.password_check_status_message_error_offline);
case PasswordCheckUIStatus.ERROR_NO_PASSWORDS:
return getString(view, R.string.password_check_status_message_error_no_passwords);
case PasswordCheckUIStatus.ERROR_SIGNED_OUT:
return getString(view, R.string.password_check_status_message_error_signed_out);
case PasswordCheckUIStatus.ERROR_QUOTA_LIMIT:
return getString(view, R.string.password_check_status_message_error_quota_limit);
case PasswordCheckUIStatus.ERROR_QUOTA_LIMIT_ACCOUNT_CHECK:
return getString(view,
R.string.password_check_status_message_error_quota_limit_account_check);
case PasswordCheckUIStatus.ERROR_UNKNOWN:
return getString(view, R.string.password_check_status_message_error_unknown);
default:
assert false : "Unhandled check status " + status + "on message update";
}
return null;
}
private static int getStatusTextMargin(@PasswordCheckUIStatus int status) {
switch (status) {
case PasswordCheckUIStatus.IDLE:
return R.dimen.check_status_message_idle_margin_vertical;
case PasswordCheckUIStatus.RUNNING:
return R.dimen.check_status_message_running_margin_vertical;
case PasswordCheckUIStatus.ERROR_OFFLINE:
case PasswordCheckUIStatus.ERROR_NO_PASSWORDS:
case PasswordCheckUIStatus.ERROR_SIGNED_OUT:
case PasswordCheckUIStatus.ERROR_QUOTA_LIMIT:
case PasswordCheckUIStatus.ERROR_QUOTA_LIMIT_ACCOUNT_CHECK:
case PasswordCheckUIStatus.ERROR_UNKNOWN:
return R.dimen.check_status_message_error_margin_vertical;
default:
assert false : "Unhandled check status " + status + "on text margin update";
}
return 0;
}
private static String getStatusDescription(View view, Long checkTimestamp) {
if (checkTimestamp == null) return null;
Resources res = view.getContext().getResources();
return res.getString(R.string.password_check_status_description_idle,
getTimestamp(res, System.currentTimeMillis() - checkTimestamp));
}
@VisibleForTesting
protected static String getTimestamp(Resources res, long timeDeltaMs) {
if (timeDeltaMs < 0) timeDeltaMs = 0;
int daysElapsed = (int) (timeDeltaMs / (24L * 60L * 60L * 1000L));
int hoursElapsed = (int) (timeDeltaMs / (60L * 60L * 1000L));
int minutesElapsed = (int) (timeDeltaMs / (60L * 1000L));
String relativeTime;
if (daysElapsed > 0L) {
relativeTime = res.getQuantityString(
org.chromium.chrome.R.plurals.n_days_ago, daysElapsed, daysElapsed);
} else if (hoursElapsed > 0L) {
relativeTime = res.getQuantityString(
org.chromium.chrome.R.plurals.n_hours_ago, hoursElapsed, hoursElapsed);
} else if (minutesElapsed > 0L) {
relativeTime = res.getQuantityString(
org.chromium.chrome.R.plurals.n_minutes_ago, minutesElapsed, minutesElapsed);
} else {
relativeTime = res.getString(R.string.password_check_just_now);
}
return relativeTime;
}
private static int getStatusDescriptionVisibility(@PasswordCheckUIStatus int status) {
return status == PasswordCheckUIStatus.IDLE ? View.VISIBLE : View.GONE;
}
private static ListMenu createCredentialMenu(Context context, CompromisedCredential credential,
PasswordCheckCoordinator.CredentialEventHandler credentialHandler) {
MVCListAdapter.ModelList menuItems = new MVCListAdapter.ModelList();
......@@ -249,4 +375,12 @@ class PasswordCheckViewBinder {
};
return new BasicListMenu(context, menuItems, delegate);
}
private static String getString(View view, int resourceId) {
return view.getContext().getResources().getString(resourceId);
}
private static int getDimensionPixelOffset(View view, int resourceId) {
return view.getContext().getResources().getDimensionPixelOffset(resourceId);
}
}
......@@ -187,10 +187,49 @@
<message name="IDS_PASSWORD_CHECK_CREDENTIAL_ROW_SCRIPT_BUTTON_EXPLANATION" desc="Small description explaining that an automated password change can be done with a script.">
Let Google Assistant help you change your password
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_DESCRIPTION_IDLE" desc="Small description explaining that the password check has been completed and showing how long ago the last check has been successfully completed.">
Checked passwords · <ph name="TIME_SINCE_LAST_CHECK">%1$s<ex>Just now</ex></ph>
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_NO_PASSWORDS" desc="Error message explaining that the password check failed because the user has no passwords on this device.">
No saved passwords. Chrome can check your passwords when you save them.
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_OFFLINE" desc="Error message explaining that the password check failed because the user is offline.">
Chrome can't check your passwords. Try checking your internet connection.
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_QUOTA_LIMIT" desc="Error message explaining that the password check failed because the user has exceeded their quota.">
Chrome couldn't check all passwords. Try again tomorrow.
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_QUOTA_LIMIT_ACCOUNT_CHECK" desc="Error message explaining that the password check failed because the user has exceeded their quota and redirecting to check in the Google Account.">
Chrome couldn't check all passwords. Try again tomorrow or check passwords in your Google Account.
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_SIGNED_OUT" desc="Error message explaining that the password check failed because the user is signed-out.">
Chrome can check your passwords when you sign in with your Google Account.
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_UNKNOWN" desc="Error message explaining that the password check failed for unknown reasons.">
Chrome can't check your passwords. Try again.
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_IDLE_NO_LEAKS" desc="Message explaining that the password check has been completed and no leaked passwords have been found.">
No compromised passwords
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_IDLE_WITH_LEAKS" desc="Message explaining that the password check has been completed and some leaked passwords have been found.">
{COMPROMISED_PASSWORDS, plural, =1 {# compromised password} other {# compromised passwords}}
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_INITIAL_RUNNING" desc="Status message shown when the check for leaked passwords is running and we have not yet information regarding the progress.">
Checking passwords…
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_RUNNING" desc="Status message shown when the check for leaked passwords is running.">
Checking passwords (<ph name="ANALYSED_PASSWORDS">%1$s<ex>1</ex></ph> of <ph name="TOTAL_PASSWORDS">%2$s<ex>42</ex></ph>)…
</message>
<!-- Accessibility strings -->
<message name="IDS_ACCESSIBILITY_PASSWORD_CHECK_RESTART_BUTTON" desc="Content description for the button to restart the password check.">
Restart password check
</message>
<!-- Utility string to show the timestamp -->
<message name="IDS_PASSWORD_CHECK_JUST_NOW" desc="A time label for a check that happened just now. For example 'Checked passwords · Just now'">
Just now
</message>
</messages>
</release>
</grit>
......@@ -21,19 +21,29 @@ import static org.mockito.Mockito.verify;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.CompromisedCredentialProperties.COMPROMISED_CREDENTIAL;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.CompromisedCredentialProperties.CREDENTIAL_HANDLER;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.CHECK_PROGRESS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.CHECK_STATUS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.CHECK_TIMESTAMP;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.COMPROMISED_CREDENTIALS_COUNT;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.UNKNOWN_PROGRESS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.ITEMS;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_NO_PASSWORDS;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_OFFLINE;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_QUOTA_LIMIT;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_QUOTA_LIMIT_ACCOUNT_CHECK;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_SIGNED_OUT;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_UNKNOWN;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.IDLE;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.RUNNING;
import static org.chromium.content_public.browser.test.util.CriteriaHelper.pollUiThread;
import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Pair;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
......@@ -44,6 +54,7 @@ import androidx.annotation.IdRes;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Rule;
......@@ -82,6 +93,13 @@ public class PasswordCheckViewTest {
private static final CompromisedCredential SCRIPTED =
new CompromisedCredential("script.com", "Charlie", "secret", false, true);
private static final int LEAKS_COUNT = 2;
private static final long S_TO_MS = 1000;
private static final long MIN_TO_MS = 60 * S_TO_MS;
private static final long H_TO_MS = 60 * MIN_TO_MS;
private static final long DAY_TO_MS = 24 * H_TO_MS;
private PropertyModel mModel = PasswordCheckProperties.createDefaultModel();
private PasswordCheckFragmentView mPasswordCheckView;
......@@ -132,7 +150,9 @@ public class PasswordCheckViewTest {
@Test
@MediumTest
public void testStatusDisplaysIconOnIdleNoLeaks() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(IDLE, 0)); });
Long checkTimestamp = System.currentTimeMillis();
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(IDLE, 0, checkTimestamp)); });
waitForListViewToHaveLength(1);
assertDisplaysIcon(R.drawable.ic_check_circle_filled_green_24dp);
}
......@@ -140,7 +160,9 @@ public class PasswordCheckViewTest {
@Test
@MediumTest
public void testStatusDisplaysIconOnIdleWithLeaks() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(IDLE, 2)); });
Long checkTimestamp = System.currentTimeMillis();
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(IDLE, LEAKS_COUNT, checkTimestamp)); });
waitForListViewToHaveLength(1);
assertDisplaysIcon(org.chromium.chrome.R.drawable.ic_warning_red_24dp);
}
......@@ -165,7 +187,9 @@ public class PasswordCheckViewTest {
@Test
@MediumTest
public void testStatusDisplaysRestartAction() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(IDLE, 0)); });
Long checkTimestamp = System.currentTimeMillis();
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(IDLE, 0, checkTimestamp)); });
waitForListViewToHaveLength(1);
assertThat(getActionButton().getVisibility(), is(View.VISIBLE));
assertTrue(getActionButton().isClickable());
......@@ -180,6 +204,114 @@ public class PasswordCheckViewTest {
assertFalse(getActionButton().isClickable());
}
@Test
@MediumTest
public void testStatusRunnningText() {
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(RUNNING, UNKNOWN_PROGRESS)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_initial_running)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test
@MediumTest
public void testStatusIdleNoLeaksText() {
Long checkTimestamp = System.currentTimeMillis();
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(IDLE, 0, checkTimestamp)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_idle_no_leaks)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.VISIBLE));
}
@Test
@MediumTest
public void testStatusIdleWithLeaksText() {
Long checkTimestamp = System.currentTimeMillis();
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(IDLE, LEAKS_COUNT, checkTimestamp)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(mPasswordCheckView.getContext().getResources().getQuantityString(
R.plurals.password_check_status_message_idle_with_leaks, LEAKS_COUNT,
LEAKS_COUNT)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.VISIBLE));
}
@Test
@MediumTest
public void testStatusErrorOfflineText() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(ERROR_OFFLINE)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_error_offline)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test
@MediumTest
public void testStatusErrorNoPasswordsText() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(ERROR_NO_PASSWORDS)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_error_no_passwords)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test
@MediumTest
public void testStatusErrorQuotaLimitText() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(ERROR_QUOTA_LIMIT)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_error_quota_limit)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test
@MediumTest
public void testStatusErrorQuotaLimitAccountCheckText() {
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(ERROR_QUOTA_LIMIT_ACCOUNT_CHECK)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(
R.string.password_check_status_message_error_quota_limit_account_check)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test
@MediumTest
public void testStatusErrorSignedOutText() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(ERROR_SIGNED_OUT)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_error_signed_out)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test
@MediumTest
public void testStatusErrorUnknownText() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(ERROR_UNKNOWN)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_error_unknown)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test
@MediumTest
public void testCrendentialDisplaysNameOriginAndReason() {
......@@ -237,7 +369,7 @@ public class PasswordCheckViewTest {
@MediumTest
public void testClickingChangePasswordTriggersHandler() {
runOnUiThreadBlocking(() -> mModel.get(ITEMS).add(buildCredentialItem(ANA)));
pollUiThread(() -> Criteria.checkThat(getPasswordCheckViewList().getChildCount(), is(1)));
waitForListViewToHaveLength(1);
TouchCommon.singleClickView(getCredentialChangeButtonAt(0));
......@@ -248,7 +380,7 @@ public class PasswordCheckViewTest {
@MediumTest
public void testClickingChangePasswordWithScriptTriggersHandler() {
runOnUiThreadBlocking(() -> mModel.get(ITEMS).add(buildCredentialItem(SCRIPTED)));
pollUiThread(() -> Criteria.checkThat(getPasswordCheckViewList().getChildCount(), is(1)));
waitForListViewToHaveLength(1);
TouchCommon.singleClickView(getCredentialChangeButtonWithScriptAt(0));
......@@ -271,15 +403,42 @@ public class PasswordCheckViewTest {
verify(mMockHandler).onRemove(eq(ANA));
}
private MVCListAdapter.ListItem buildHeader(@PasswordCheckUIStatus int status) {
return buildHeader(status, null);
@Test
@SmallTest
public void testGetTimestampStrings() {
Resources res = mPasswordCheckView.getContext().getResources();
assertThat(PasswordCheckViewBinder.getTimestamp(res, 10 * S_TO_MS), is("Just now"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, MIN_TO_MS), is("1 minute ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 17 * MIN_TO_MS), is("17 minutes ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, H_TO_MS), is("1 hour ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 13 * H_TO_MS), is("13 hours ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, DAY_TO_MS), is("1 day ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 2 * DAY_TO_MS), is("2 days ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 315 * DAY_TO_MS), is("315 days ago"));
}
private MVCListAdapter.ListItem buildHeader(@PasswordCheckUIStatus int status,
Integer compromisedCredentialsCount, Long checkTimestamp) {
return buildHeader(status, compromisedCredentialsCount, checkTimestamp, null);
}
private MVCListAdapter.ListItem buildHeader(
@PasswordCheckUIStatus int status, Integer compromisedCredentialsCount) {
@PasswordCheckUIStatus int status, Pair<Integer, Integer> progress) {
return buildHeader(status, null, null, progress);
}
private MVCListAdapter.ListItem buildHeader(@PasswordCheckUIStatus int status) {
return buildHeader(status, null, null, null);
}
private MVCListAdapter.ListItem buildHeader(@PasswordCheckUIStatus int status,
Integer compromisedCredentialsCount, Long checkTimestamp,
Pair<Integer, Integer> progress) {
return new MVCListAdapter.ListItem(PasswordCheckProperties.ItemType.HEADER,
new PropertyModel.Builder(HeaderProperties.ALL_KEYS)
.with(CHECK_PROGRESS, progress)
.with(CHECK_STATUS, status)
.with(CHECK_TIMESTAMP, checkTimestamp)
.with(COMPROMISED_CREDENTIALS_COUNT, compromisedCredentialsCount)
.build());
}
......@@ -331,6 +490,14 @@ public class PasswordCheckViewTest {
return getStatus().findViewById(R.id.check_status_progress);
}
private TextView getHeaderDescription() {
return getStatus().findViewById(R.id.check_status_description);
}
private TextView getHeaderMessage() {
return getStatus().findViewById(R.id.check_status_message);
}
private ImageButton getActionButton() {
return getStatus().findViewById(R.id.check_status_restart_button);
}
......
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