Commit 9f7b813b authored by Mei Liang's avatar Mei Liang Committed by Commit Bot

[Test][TabSelectionEditor] Add end to end tests

This CL adds the following:
  * End to end tests for TabSelectionEditor
  * Some customized RecyclerViewMatchers
  * A TabSelectionEditorTestingRobot that allows others to perform and
    verify action within the TabSelectionEditor.

Change-Id: Ic7283772374a0c0ed2a3aca84d4e4d245118fd04
Bug: 983170
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1894413
Commit-Queue: Mei Liang <meiliang@chromium.org>
Reviewed-by: default avatarWei-Yin Chen (陳威尹) <wychen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#712906}
parent 276ce52d
...@@ -100,6 +100,7 @@ class TabGridViewBinder { ...@@ -100,6 +100,7 @@ class TabGridViewBinder {
} else if (TabProperties.IS_SELECTED == propertyKey) { } else if (TabProperties.IS_SELECTED == propertyKey) {
int selectedTabBackground = int selectedTabBackground =
model.get(TabProperties.SELECTED_TAB_BACKGROUND_DRAWABLE_ID); model.get(TabProperties.SELECTED_TAB_BACKGROUND_DRAWABLE_ID);
view.setSelected(model.get(TabProperties.IS_SELECTED));
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
if (model.get(TabProperties.IS_SELECTED)) { if (model.get(TabProperties.IS_SELECTED)) {
view.fastFindViewById(R.id.selected_view_below_lollipop) view.fastFindViewById(R.id.selected_view_below_lollipop)
......
...@@ -110,8 +110,11 @@ public class TabListCoordinator implements Destroyable { ...@@ -110,8 +110,11 @@ public class TabListCoordinator implements Destroyable {
RecyclerView.RecyclerListener recyclerListener = null; RecyclerView.RecyclerListener recyclerListener = null;
if (mMode == TabListMode.GRID || mMode == TabListMode.CAROUSEL) { if (mMode == TabListMode.GRID || mMode == TabListMode.CAROUSEL) {
mAdapter.registerType(UiType.SELECTABLE, () -> { mAdapter.registerType(UiType.SELECTABLE, () -> {
return (ViewGroup) LayoutInflater.from(context).inflate( ViewGroup group = (ViewGroup) LayoutInflater.from(context).inflate(
R.layout.selectable_tab_grid_card_item, parentView, false); R.layout.selectable_tab_grid_card_item, parentView, false);
group.setClickable(true);
return group;
}, TabGridViewBinder::bindSelectableTab); }, TabGridViewBinder::bindSelectableTab);
mAdapter.registerType(UiType.CLOSABLE, () -> { mAdapter.registerType(UiType.CLOSABLE, () -> {
...@@ -121,6 +124,7 @@ public class TabListCoordinator implements Destroyable { ...@@ -121,6 +124,7 @@ public class TabListCoordinator implements Destroyable {
group.getLayoutParams().width = context.getResources().getDimensionPixelSize( group.getLayoutParams().width = context.getResources().getDimensionPixelSize(
R.dimen.tab_carousel_card_width); R.dimen.tab_carousel_card_width);
} }
group.setClickable(true);
return group; return group;
}, TabGridViewBinder::bindClosableTab); }, TabGridViewBinder::bindClosableTab);
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.tasks.tab_management;
import android.support.test.espresso.matcher.BoundedMatcher;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
/**
* Contains useful RecyclerViewMatcher.
*/
public class RecyclerViewMatcherUtils {
/**
* This view matcher matches a RecyclerView that has a given number of items in its adapter.
*
* @param itemCount The matches item count.
* @return A matcher that matches RecyclerView with its adapter item count.
*/
public static Matcher<View> adapterHasItemCount(int itemCount) {
return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
@Override
protected boolean matchesSafely(RecyclerView recyclerView) {
return recyclerView.getAdapter().getItemCount() == itemCount;
}
@Override
public void describeTo(Description description) {
description.appendText("RecyclerView.Adapter has " + itemCount + " items");
}
};
}
/**
* This view matcher matches a RecyclerView that has a view that matches the given view matcher
* at the given adapter position.
*
* First this matcher scrolls the RecyclerView to the given position and then matches with the
* given view matcher.
*
* @param position The matches adapter position.
* @param itemMatcher A view matcher to match.
* @return A matcher that matches RecyclerView with its adapter item position and the given view
* matcher.
*/
public static Matcher<View> atPosition(int position, Matcher<View> itemMatcher) {
return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
@Override
protected boolean matchesSafely(RecyclerView recyclerView) {
recyclerView.scrollToPosition(position);
RecyclerView.ViewHolder viewHolder =
recyclerView.findViewHolderForAdapterPosition(position);
if (viewHolder == null) return false;
return itemMatcher.matches(viewHolder.itemView);
}
@Override
public void describeTo(Description description) {
description.appendText("has view " + itemMatcher + " at position " + position);
}
};
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.tasks.tab_management;
import android.support.test.filters.MediumTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.flags.FeatureUtilities;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.tab_ui.R;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import java.util.ArrayList;
import java.util.List;
/**
* End-to-end test for TabSelectionEditor.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class TabSelectionEditorTest {
@Rule
public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
@Rule
public TestRule mProcessor = new Features.InstrumentationProcessor();
private TabSelectionEditorTestingRobot mRobot = new TabSelectionEditorTestingRobot();
private TabModelSelector mTabModelSelector;
private TabSelectionEditorCoordinator
.TabSelectionEditorController mTabSelectionEditorController;
@Before
public void setUp() throws Exception {
FeatureUtilities.setTabGroupsAndroidEnabledForTesting(true);
mActivityTestRule.startMainActivityFromLauncher();
TabUiTestHelper.createTabs(mActivityTestRule.getActivity(), false, 2);
mTabModelSelector = mActivityTestRule.getActivity().getTabModelSelector();
TestThreadUtils.runOnUiThreadBlocking(() -> {
TabSelectionEditorCoordinator tabSelectionEditorCoordinator =
new TabSelectionEditorCoordinator(mActivityTestRule.getActivity(),
mActivityTestRule.getActivity().getCurrentFocus(), mTabModelSelector,
mActivityTestRule.getActivity().getTabContentManager(), null);
mTabSelectionEditorController = tabSelectionEditorCoordinator.getController();
});
}
@Test
@MediumTest
public void testShowTabs() {
List<Tab> tabs = getTabsInCurrentTabModel();
TestThreadUtils.runOnUiThreadBlocking(() -> { mTabSelectionEditorController.show(tabs); });
mRobot.resultRobot.verifyTabSelectionEditorIsVisible()
.verifyToolbarActionButtonDisabled()
.verifyToolbarActionButtonWithResourceId(R.string.tab_selection_editor_group)
.verifyToolbarSelectionTextWithResourceId(
R.string.tab_selection_editor_toolbar_select_tabs)
.verifyAdapterHasItemCount(tabs.size())
.verifyHasAtLeastNItemVisible(1);
}
@Test
@MediumTest
public void testToggleItem() {
List<Tab> tabs = getTabsInCurrentTabModel();
TestThreadUtils.runOnUiThreadBlocking(() -> { mTabSelectionEditorController.show(tabs); });
mRobot.resultRobot.verifyItemNotSelectedAtAdapterPosition(0);
mRobot.actionRobot.clickItemAtAdapterPosition(0);
mRobot.resultRobot.verifyItemSelectedAtAdapterPosition(0).verifyToolbarSelectionText(
"1 selected");
mRobot.actionRobot.clickItemAtAdapterPosition(0);
mRobot.resultRobot.verifyItemNotSelectedAtAdapterPosition(0)
.verifyToolbarSelectionTextWithResourceId(
R.string.tab_selection_editor_toolbar_select_tabs);
}
@Test
@MediumTest
public void testToolbarNavigationButtonHideTabSelectionEditor() {
List<Tab> tabs = getTabsInCurrentTabModel();
TestThreadUtils.runOnUiThreadBlocking(() -> { mTabSelectionEditorController.show(tabs); });
mRobot.resultRobot.verifyTabSelectionEditorIsVisible();
mRobot.actionRobot.clickToolbarNavigationButton();
mRobot.resultRobot.verifyTabSelectionEditorIsHidden();
}
@Test
@MediumTest
public void testToolbarGroupButtonEnabledState() {
List<Tab> tabs = getTabsInCurrentTabModel();
TestThreadUtils.runOnUiThreadBlocking(() -> { mTabSelectionEditorController.show(tabs); });
mRobot.resultRobot.verifyToolbarActionButtonDisabled()
.verifyToolbarActionButtonWithResourceId(R.string.tab_selection_editor_group);
mRobot.actionRobot.clickItemAtAdapterPosition(0);
mRobot.resultRobot.verifyToolbarActionButtonDisabled();
mRobot.actionRobot.clickItemAtAdapterPosition(1);
mRobot.resultRobot.verifyToolbarActionButtonEnabled();
mRobot.actionRobot.clickItemAtAdapterPosition(1);
mRobot.resultRobot.verifyToolbarActionButtonDisabled();
}
@Test
@MediumTest
public void testToolbarGroupButton() {
List<Tab> tabs = getTabsInCurrentTabModel();
TestThreadUtils.runOnUiThreadBlocking(() -> { mTabSelectionEditorController.show(tabs); });
mRobot.resultRobot.verifyToolbarActionButtonWithResourceId(
R.string.tab_selection_editor_group);
mRobot.actionRobot.clickItemAtAdapterPosition(0)
.clickItemAtAdapterPosition(1)
.clickToolbarActionButton();
mRobot.resultRobot.verifyTabSelectionEditorIsHidden();
// TODO(1021803): verify the undo snack after the bug is resolved.
// verifyUndoSnackbarWithTextIsShown(mActivityTestRule.getActivity().getString(
// R.string.undo_bar_group_tabs_message, 2));
}
@Test
@MediumTest
public void testConfigureToolbar_ActionButtonEnableThreshold() {
List<Tab> tabs = getTabsInCurrentTabModel();
int enableThreshold = 1;
TestThreadUtils.runOnUiThreadBlocking(() -> {
mTabSelectionEditorController.configureToolbar("Test", null, enableThreshold, null);
mTabSelectionEditorController.show(tabs);
});
mRobot.resultRobot.verifyToolbarActionButtonDisabled().verifyToolbarActionButtonWithText(
"Test");
for (int i = 0; i < enableThreshold; i++) {
mRobot.actionRobot.clickItemAtAdapterPosition(i);
}
mRobot.resultRobot.verifyToolbarActionButtonEnabled();
mRobot.actionRobot.clickItemAtAdapterPosition(enableThreshold - 1);
mRobot.resultRobot.verifyToolbarActionButtonDisabled();
}
private List<Tab> getTabsInCurrentTabModel() {
List<Tab> tabs = new ArrayList<>();
TabModel currentTabModel = mTabModelSelector.getCurrentModel();
for (int i = 0; i < currentTabModel.getCount(); i++) {
tabs.add(currentTabModel.getTabAt(i));
}
return tabs;
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.tasks.tab_management;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import android.os.Build;
import android.support.test.espresso.NoMatchingRootException;
import android.support.test.espresso.Root;
import android.support.test.espresso.matcher.BoundedMatcher;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Assert;
/**
* This is the testing util class for TabSelectionEditor. It's used to perform action and verify
* result within the TabSelectionEditor.
*/
public class TabSelectionEditorTestingRobot {
/**
* @return A root matcher that matches the TabSelectionEditor popup decor view.
*/
private static Matcher<Root> isTabSelectionEditorPopup() {
return new TypeSafeMatcher<Root>() {
@Override
public void describeTo(Description description) {
description.appendText("is TabSelectionEditor Popup");
}
@Override
public boolean matchesSafely(Root root) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
return withDecorView(
withClassName(is(TabSelectionEditorLayout.class.getName())))
.matches(root);
} else {
return withDecorView(
withClassName(is("android.widget.PopupWindow$PopupDecorView")))
.matches(root);
}
}
};
}
/**
* @return A view matcher that matches the item is selected.
*/
private static Matcher<View> itemIsSelected() {
return new BoundedMatcher<View, SelectableTabGridView>(SelectableTabGridView.class) {
private SelectableTabGridView mSelectableTabGridView;
@Override
protected boolean matchesSafely(SelectableTabGridView selectableTabGridView) {
mSelectableTabGridView = selectableTabGridView;
return mSelectableTabGridView.isSelected() && actionButtonSelected()
&& highlightIndicatorIsVisible();
}
@Override
public void describeTo(Description description) {
description.appendText("Item is selected");
}
private boolean actionButtonSelected() {
return mSelectableTabGridView.getResources().getInteger(
org.chromium.chrome.tab_ui.R.integer.list_item_level_selected)
== mSelectableTabGridView
.findViewById(org.chromium.chrome.tab_ui.R.id.action_button)
.getBackground()
.getLevel();
}
private boolean highlightIndicatorIsVisible() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
return mSelectableTabGridView
.findViewById(org.chromium.chrome.tab_ui.R.id
.selected_view_below_lollipop)
.getVisibility()
== View.VISIBLE;
} else {
return mSelectableTabGridView.getForeground() != null;
}
}
};
}
public final TabSelectionEditorTestingRobot.Result resultRobot;
public final TabSelectionEditorTestingRobot.Action actionRobot;
TabSelectionEditorTestingRobot() {
resultRobot = new TabSelectionEditorTestingRobot.Result();
actionRobot = new TabSelectionEditorTestingRobot.Action();
}
/**
* This Robot is used to perform action within the TabSelectionEditor.
*/
class Action {
TabSelectionEditorTestingRobot.Action clickItemAtAdapterPosition(int position) {
onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
.inRoot(isTabSelectionEditorPopup())
.perform(actionOnItemAtPosition(position, click()));
return this;
}
TabSelectionEditorTestingRobot.Action clickToolbarActionButton() {
onView(allOf(withId(org.chromium.chrome.tab_ui.R.id.action_button),
withParent(withId(org.chromium.chrome.tab_ui.R.id.action_bar))))
.inRoot(isTabSelectionEditorPopup())
.perform(click());
return this;
}
TabSelectionEditorTestingRobot.Action clickToolbarNavigationButton() {
onView(allOf(withContentDescription(org.chromium.chrome.tab_ui.R.string.close),
withParent(withId(org.chromium.chrome.tab_ui.R.id.action_bar))))
.inRoot(isTabSelectionEditorPopup())
.perform(click());
return this;
}
}
/**
* This Robot is used to verify result within the TabSelectionEditor.
*/
class Result {
TabSelectionEditorTestingRobot.Result verifyTabSelectionEditorIsVisible() {
onView(withId(org.chromium.chrome.tab_ui.R.id.selectable_list))
.inRoot(isTabSelectionEditorPopup())
.check(matches(isDisplayed()));
return this;
}
TabSelectionEditorTestingRobot.Result verifyTabSelectionEditorIsHidden() {
try {
onView(withId(org.chromium.chrome.tab_ui.R.id.selectable_list))
.inRoot(isTabSelectionEditorPopup())
.check(matches(isDisplayed()));
} catch (NoMatchingRootException e) {
return this;
}
assert false : "TabSelectionEditor should be hidden, but it's not.";
return this;
}
TabSelectionEditorTestingRobot.Result verifyToolbarSelectionTextWithResourceId(
int resourceId) {
onView(withText(resourceId))
.inRoot(isTabSelectionEditorPopup())
.check(matches(isDisplayed()));
return this;
}
TabSelectionEditorTestingRobot.Result verifyToolbarSelectionText(String text) {
onView(withText(text))
.inRoot(isTabSelectionEditorPopup())
.check(matches(isDisplayed()));
return this;
}
TabSelectionEditorTestingRobot.Result verifyToolbarActionButtonWithResourceId(
int resourceId) {
onView(allOf(withId(org.chromium.chrome.tab_ui.R.id.action_button),
withParent(withId(org.chromium.chrome.tab_ui.R.id.action_bar))))
.inRoot(isTabSelectionEditorPopup())
.check(matches(withText(resourceId)));
return this;
}
TabSelectionEditorTestingRobot.Result verifyToolbarActionButtonWithText(String text) {
onView(allOf(withId(org.chromium.chrome.tab_ui.R.id.action_button),
withParent(withId(org.chromium.chrome.tab_ui.R.id.action_bar))))
.inRoot(isTabSelectionEditorPopup())
.check(matches(withText(text)));
return this;
}
TabSelectionEditorTestingRobot.Result verifyToolbarActionButtonDisabled() {
onView(allOf(withId(org.chromium.chrome.tab_ui.R.id.action_button),
withParent(withId(org.chromium.chrome.tab_ui.R.id.action_bar))))
.inRoot(isTabSelectionEditorPopup())
.check(matches(not(isEnabled())));
return this;
}
TabSelectionEditorTestingRobot.Result verifyToolbarActionButtonEnabled() {
onView(allOf(withId(org.chromium.chrome.tab_ui.R.id.action_button),
withParent(withId(org.chromium.chrome.tab_ui.R.id.action_bar))))
.inRoot(isTabSelectionEditorPopup())
.check(matches(isEnabled()));
return this;
}
TabSelectionEditorTestingRobot.Result verifyHasAtLeastNItemVisible(int count) {
onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
.inRoot(isTabSelectionEditorPopup())
.check((v, noMatchException) -> {
if (noMatchException != null) throw noMatchException;
Assert.assertTrue(v instanceof RecyclerView);
Assert.assertTrue(((RecyclerView) v).getChildCount() >= count);
});
return this;
}
TabSelectionEditorTestingRobot.Result verifyAdapterHasItemCount(int count) {
onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
.inRoot(isTabSelectionEditorPopup())
.check(matches(RecyclerViewMatcherUtils.adapterHasItemCount(count)));
return this;
}
TabSelectionEditorTestingRobot.Result verifyItemNotSelectedAtAdapterPosition(int position) {
onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
.inRoot(isTabSelectionEditorPopup())
.check(matches(
not(RecyclerViewMatcherUtils.atPosition(position, itemIsSelected()))));
return this;
}
TabSelectionEditorTestingRobot.Result verifyItemSelectedAtAdapterPosition(int position) {
onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
.inRoot(isTabSelectionEditorPopup())
.check(matches(
RecyclerViewMatcherUtils.atPosition(position, itemIsSelected())));
return this;
}
TabSelectionEditorTestingRobot.Result verifyUndoSnackbarWithTextIsShown(String text) {
onView(withText(text)).check(matches(isDisplayed()));
return this;
}
}
}
...@@ -35,9 +35,12 @@ tab_management_test_java_sources = [ ...@@ -35,9 +35,12 @@ tab_management_test_java_sources = [
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderTest.java", "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderTest.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java", "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorLayoutBinderTest.java", "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorLayoutBinderTest.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTestingRobot.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMultiWindowTest.java", "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMultiWindowTest.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java", "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TestRecyclerViewSimpleViewBinder.java", "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TestRecyclerViewSimpleViewBinder.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/RecyclerViewMatcherUtils.java",
] ]
tab_management_junit_java_sources = [ tab_management_junit_java_sources = [
......
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