Merge branch 'main' into avm99963-monorail

GitOrigin-RevId: 2bbe35caf837ba29cc5c1a89d66a6881c1230034
diff --git a/static_src/react/issue-wizard/LandingStep.tsx b/static_src/react/issue-wizard/LandingStep.tsx
index efe6491..2925e87 100644
--- a/static_src/react/issue-wizard/LandingStep.tsx
+++ b/static_src/react/issue-wizard/LandingStep.tsx
@@ -1,10 +1,14 @@
+// Copyright 2021 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.
+
 import React from 'react';
-import {makeStyles, withStyles} from '@material-ui/styles';
-import {blue, yellow, red, grey} from '@material-ui/core/colors';
+import { makeStyles, withStyles } from '@material-ui/styles';
+import { blue, yellow, red, grey } from '@material-ui/core/colors';
 import FormControlLabel from '@material-ui/core/FormControlLabel';
-import Checkbox, {CheckboxProps} from '@material-ui/core/Checkbox';
+import Checkbox, { CheckboxProps } from '@material-ui/core/Checkbox';
 import SelectMenu from './SelectMenu.tsx';
-import RadioDescription from './RadioDescription.tsx';
+import { RadioDescription } from './RadioDescription/RadioDescription.tsx';
 
 const CustomCheckbox = withStyles({
   root: {
@@ -42,7 +46,7 @@
     fontSize: '16px',
     fontWeight: '500',
   },
-  star:{
+  star: {
     color: red[700],
     marginRight: '8px',
     fontSize: '16px',
@@ -63,8 +67,8 @@
   },
 });
 
-export default function LandingStep({checkExisting, setCheckExisting, userType, setUserType, category, setCategory}:
-  {checkExisting: boolean, setCheckExisting: Function, userType: string, setUserType: Function, category: string, setCategory: Function}) {
+export default function LandingStep({ checkExisting, setCheckExisting, userType, setUserType, category, setCategory }:
+  { checkExisting: boolean, setCheckExisting: Function, userType: string, setUserType: Function, category: string, setCategory: Function }) {
   const classes = useStyles();
 
   const handleCheckChange = (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -82,11 +86,11 @@
       <p className={classes.subheader}>
         Please select your following role: <span className={classes.red}>*</span>
       </p>
-      <RadioDescription value={userType} setValue={setUserType}/>
+      <RadioDescription value={userType} setValue={setUserType} />
       <div className={classes.subheader}>
         Which of the following best describes the issue that you are reporting? <span className={classes.red}>*</span>
       </div>
-      <SelectMenu option={category} setOption={setCategory}/>
+      <SelectMenu option={category} setOption={setCategory} />
       <div className={classes.warningBox}>
         <p className={classes.warningHeader}> Avoid duplicate issue reports:</p>
         <div>
diff --git a/static_src/react/issue-wizard/RadioDescription.test.tsx b/static_src/react/issue-wizard/RadioDescription.test.tsx
deleted file mode 100644
index ff65eae..0000000
--- a/static_src/react/issue-wizard/RadioDescription.test.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2021 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.
-
-import React from 'react';
-import {render, screen, cleanup} from '@testing-library/react';
-import userEvent from '@testing-library/user-event'
-import {assert} from 'chai';
-import sinon from 'sinon';
-
-import RadioDescription from './RadioDescription.tsx';
-
-describe('RadioDescription', () => {
-  afterEach(cleanup);
-
-  it('renders', () => {
-    render(<RadioDescription />);
-    // look for blue radios
-      const radioOne = screen.getByRole('radio', {name: /Web Developer/i});
-      assert.isNotNull(radioOne)
-
-      const radioTwo = screen.getByRole('radio', {name: /End User/i});
-      assert.isNotNull(radioTwo)
-
-      const radioThree = screen.getByRole('radio', {name: /Chromium Contributor/i});
-      assert.isNotNull(radioThree)
-  });
-
-  it('checks selected radio value', () => {
-    // We're passing in the "Web Developer" value here manually
-    // to tell our code that that radio button is selected.
-    render(<RadioDescription value={'Web Developer'} />);
-
-    const checkedRadio = screen.getByRole('radio', {name: /Web Developer/i});
-    assert.isTrue(checkedRadio.checked);
-
-    // Extra check to make sure we haven't checked every single radio button.
-    const uncheckedRadio = screen.getByRole('radio', {name: /End User/i});
-    assert.isFalse(uncheckedRadio.checked);
-  });
-
-  it('sets radio value when clicked', () => {
-    // Using the sinon.js testing library to create a function for testing.
-    const setValue = sinon.stub();
-
-    render(<RadioDescription setValue={setValue} />);
-
-    const radio = screen.getByRole('radio', {name: /Web Developer/i});
-    userEvent.click(radio);
-
-    // Asserts that "Web Developer" was passed into our "setValue" function.
-    sinon.assert.calledWith(setValue, 'Web Developer');
-  });
-});
\ No newline at end of file
diff --git a/static_src/react/issue-wizard/RadioDescription.tsx b/static_src/react/issue-wizard/RadioDescription.tsx
deleted file mode 100644
index ad78c78..0000000
--- a/static_src/react/issue-wizard/RadioDescription.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2021 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.
-
-import React from 'react';
-import {makeStyles, withStyles} from '@material-ui/styles';
-import {blue, grey} from '@material-ui/core/colors';
-import Radio, {RadioProps} from '@material-ui/core/Radio';
-
-const userGroups = Object.freeze({
-  END_USER: 'End User',
-  WEB_DEVELOPER: 'Web Developer',
-  CONTRIBUTOR: 'Chromium Contributor',
-});
-
-const BlueRadio = withStyles({
-  root: {
-    color: blue[400],
-    '&$checked': {
-      color: blue[600],
-    },
-  },
-  checked: {},
-})((props: RadioProps) => <Radio color="default" {...props} />);
-
-const useStyles = makeStyles({
-  flex: {
-    display: 'flex',
-    justifyContent: 'space-between',
-  },
-  container: {
-    width: '320px',
-    height: '150px',
-    position: 'relative',
-    display: 'inline-block',
-  },
-  text: {
-    position: 'absolute',
-    display: 'inline-block',
-    left: '55px',
-  },
-  title: {
-    marginTop: '7px',
-    fontSize: '20px',
-    color: grey[900],
-  },
-  subheader: {
-    fontSize: '16px',
-    color: grey[800],
-  },
-  line: {
-    position: 'absolute',
-    bottom: 0,
-    width: '300px',
-    left: '20px',
-  }
-});
-
-/**
- * `<RadioDescription />`
- *
- * React component for radio buttons and their descriptions
- * on the landing step of the Issue Wizard.
- *
- *  @return ReactElement.
- */
-export default function RadioDescription({value, setValue} : {value: string, setValue: Function}): React.ReactElement {
-  const classes = useStyles();
-
-  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
-    setValue(event.target.value);
-  };
-
-  return (
-    <div className={classes.flex}>
-      <div className={classes.container}>
-        <BlueRadio
-          checked={value === userGroups.END_USER}
-          onChange={handleChange}
-          value={userGroups.END_USER}
-          inputProps={{ 'aria-label': userGroups.END_USER}}
-        />
-        <div className={classes.text}>
-          <p className={classes.title}>{userGroups.END_USER}</p>
-          <p className={classes.subheader}>I am a user trying to do something on a website.</p>
-        </div>
-        <hr color={grey[200]} className={classes.line}/>
-      </div>
-      <div className={classes.container}>
-        <BlueRadio
-          checked={value === userGroups.WEB_DEVELOPER}
-          onChange={handleChange}
-          value={userGroups.WEB_DEVELOPER}
-          inputProps={{ 'aria-label': userGroups.WEB_DEVELOPER }}
-        />
-        <div className={classes.text}>
-          <p className={classes.title}>{userGroups.WEB_DEVELOPER}</p>
-          <p className={classes.subheader}>I am a web developer trying to build something.</p>
-        </div>
-        <hr color={grey[200]} className={classes.line}/>
-      </div>
-      <div className={classes.container}>
-        <BlueRadio
-          checked={value === userGroups.CONTRIBUTOR}
-          onChange={handleChange}
-          value={userGroups.CONTRIBUTOR}
-          inputProps={{ 'aria-label': userGroups.CONTRIBUTOR }}
-        />
-        <div className={classes.text}>
-          <p className={classes.title}>{userGroups.CONTRIBUTOR}</p>
-          <p className={classes.subheader}>I know about a problem in specific tests or code.</p>
-        </div>
-        <hr color={grey[200]} className={classes.line}/>
-      </div>
-    </div>
-    );
-  }
\ No newline at end of file
diff --git a/static_src/react/issue-wizard/RadioDescription/RadioDescription.test.tsx b/static_src/react/issue-wizard/RadioDescription/RadioDescription.test.tsx
new file mode 100644
index 0000000..296e449
--- /dev/null
+++ b/static_src/react/issue-wizard/RadioDescription/RadioDescription.test.tsx
@@ -0,0 +1,67 @@
+// Copyright 2021 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.
+
+import React from 'react';
+import { render, screen, cleanup } from '@testing-library/react';
+import userEvent from '@testing-library/user-event'
+import { assert } from 'chai';
+import sinon from 'sinon';
+
+import { RadioDescription } from './RadioDescription.tsx';
+
+describe('RadioDescription', () => {
+  afterEach(cleanup);
+
+  it('renders', () => {
+    render(<RadioDescription />);
+    // look for blue radios
+    const radioOne = screen.getByRole('radio', { name: /Web Developer/i });
+    assert.isNotNull(radioOne)
+
+    const radioTwo = screen.getByRole('radio', { name: /End User/i });
+    assert.isNotNull(radioTwo)
+
+    const radioThree = screen.getByRole('radio', { name: /Chromium Contributor/i });
+    assert.isNotNull(radioThree)
+  });
+
+  it('checks selected radio value', () => {
+    // We're passing in the "Web Developer" value here manually
+    // to tell our code that that radio button is selected.
+    render(<RadioDescription value={'Web Developer'} />);
+
+    const checkedRadio = screen.getByRole('radio', { name: /Web Developer/i });
+    assert.isTrue(checkedRadio.checked);
+
+    // Extra check to make sure we haven't checked every single radio button.
+    const uncheckedRadio = screen.getByRole('radio', { name: /End User/i });
+    assert.isFalse(uncheckedRadio.checked);
+  });
+
+  it('sets radio value when radio button is clicked', () => {
+    // Using the sinon.js testing library to create a function for testing.
+    const setValue = sinon.stub();
+
+    render(<RadioDescription setValue={setValue} />);
+
+    const radio = screen.getByRole('radio', { name: /Web Developer/i });
+    userEvent.click(radio);
+
+    // Asserts that "Web Developer" was passed into our "setValue" function.
+    sinon.assert.calledWith(setValue, 'Web Developer');
+  });
+
+  it('sets radio value when any part of the parent RoleSelection is clicked', () => {
+    const setValue = sinon.stub();
+
+    render(<RadioDescription setValue={setValue} />);
+
+    // Click text in the RoleSelection component
+    const p = screen.getByText('End User');
+    userEvent.click(p);
+
+    // Asserts that "End User" was passed into our "setValue" function.
+    sinon.assert.calledWith(setValue, 'End User');
+  });
+});
\ No newline at end of file
diff --git a/static_src/react/issue-wizard/RadioDescription/RadioDescription.tsx b/static_src/react/issue-wizard/RadioDescription/RadioDescription.tsx
new file mode 100644
index 0000000..9a5a7d2
--- /dev/null
+++ b/static_src/react/issue-wizard/RadioDescription/RadioDescription.tsx
@@ -0,0 +1,59 @@
+// Copyright 2021 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.
+
+import React from 'react';
+import { makeStyles } from '@material-ui/styles';
+import { RoleSelection } from './RoleSelection/RoleSelection.tsx';
+
+const userGroups = Object.freeze({
+  END_USER: 'End User',
+  WEB_DEVELOPER: 'Web Developer',
+  CONTRIBUTOR: 'Chromium Contributor',
+});
+
+const useStyles = makeStyles({
+  flex: {
+    display: 'flex',
+    justifyContent: 'space-between',
+  }
+});
+
+/**
+ * RadioDescription contains a set of radio buttons and descriptions (RoleSelection)
+ * to be chosen from in the landing step of the Issue Wizard.
+ *
+ * @returns React.ReactElement
+ */
+export const RadioDescription = ({ value, setValue }: { value: string, setValue: Function }): React.ReactElement => {
+  const classes = useStyles();
+
+  const handleRoleSelectionClick = (userGroup: string) =>
+    (event: React.MouseEvent<HTMLElement>) => setValue(userGroup)
+
+  return (
+    <div className={classes.flex}>
+      <RoleSelection
+        checked={value === userGroups.END_USER}
+        handleOnClick={handleRoleSelectionClick(userGroups.END_USER)}
+        value={userGroups.END_USER}
+        description="I am a user trying to do something on a website."
+        inputProps={{ 'aria-label': userGroups.END_USER }}
+      />
+      <RoleSelection
+        checked={value === userGroups.WEB_DEVELOPER}
+        handleOnClick={handleRoleSelectionClick(userGroups.WEB_DEVELOPER)}
+        value={userGroups.WEB_DEVELOPER}
+        description="I am a web developer trying to build something."
+        inputProps={{ 'aria-label': userGroups.WEB_DEVELOPER }}
+      />
+      <RoleSelection
+        checked={value === userGroups.CONTRIBUTOR}
+        handleOnClick={handleRoleSelectionClick(userGroups.CONTRIBUTOR)}
+        value={userGroups.CONTRIBUTOR}
+        description="I know about a problem in specific tests or code."
+        inputProps={{ 'aria-label': userGroups.CONTRIBUTOR }}
+      />
+    </div>
+  );
+}
\ No newline at end of file
diff --git a/static_src/react/issue-wizard/RadioDescription/RoleSelection/RoleSelection.tsx b/static_src/react/issue-wizard/RadioDescription/RoleSelection/RoleSelection.tsx
new file mode 100644
index 0000000..803a7b7
--- /dev/null
+++ b/static_src/react/issue-wizard/RadioDescription/RoleSelection/RoleSelection.tsx
@@ -0,0 +1,95 @@
+// Copyright 2021 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.
+
+import React from 'react';
+import { makeStyles, withStyles } from '@material-ui/styles';
+import { blue, grey } from '@material-ui/core/colors';
+import Radio, { RadioProps } from '@material-ui/core/Radio';
+
+const useStyles = makeStyles({
+  container: {
+    width: '320px',
+    height: '150px',
+    position: 'relative',
+    display: 'inline-block',
+    cursor: 'pointer',
+  },
+  text: {
+    position: 'absolute',
+    display: 'inline-block',
+    left: '55px',
+  },
+  title: {
+    marginTop: '7px',
+    fontSize: '20px',
+    color: grey[900],
+  },
+  subheader: {
+    fontSize: '16px',
+    color: grey[800],
+  },
+  line: {
+    position: 'absolute',
+    bottom: 0,
+    width: '300px',
+    left: '20px',
+  }
+});
+
+const BlueRadio = withStyles({
+  root: {
+    color: blue[400],
+    '&$checked': {
+      color: blue[600],
+    },
+  },
+  checked: {},
+})((props: RadioProps) => <Radio color="default" {...props} />);
+
+interface RoleSelectionProps {
+  /* Whether or not the radio button should be checked */
+  checked: boolean
+  /* onClick callback defined in parent component */
+  handleOnClick: (event: React.MouseEvent<HTMLElement>) => void
+  /*
+      A string representing the type of user; this is the value of the input
+      see `userGroups`, which is defined in RadioDescription
+  */
+  value: string
+  /* Descriptive text to be displayed along with the radio button */
+  description: string
+  /* Additional props for the radio button component */
+  inputProps: { [key: string]: string }
+}
+
+/**
+ * RoleSelection encapsulates the radio button and details
+ * for selecting a role as an issue reporter in the issue wizard
+ * @see RadioDescription
+ */
+
+export const RoleSelection = ({
+  checked,
+  handleOnClick,
+  value,
+  description,
+  inputProps
+}: RoleSelectionProps): React.ReactElement => {
+  const classes = useStyles();
+  return (
+    <div className={classes.container} onClick={handleOnClick}>
+      <BlueRadio
+        checked={checked}
+        value={value}
+        inputProps={inputProps}
+      />
+      <div className={classes.text}>
+        <p className={classes.title}>{value}</p>
+        <p className={classes.subheader}>{description}</p>
+      </div>
+      <hr color={grey[200]} className={classes.line} />
+    </div>
+  )
+}
+
diff --git a/static_src/react/mr-react-autocomplete.tsx b/static_src/react/mr-react-autocomplete.tsx
index 8cc5f84..65a045f 100644
--- a/static_src/react/mr-react-autocomplete.tsx
+++ b/static_src/react/mr-react-autocomplete.tsx
@@ -77,11 +77,16 @@
   updated(changedProperties: Map<string | number | symbol, unknown>): void {
     super.updated(changedProperties);
 
+    const maxChipLabelWidth = '290px';
     const theme = createTheme({
       components: {
         MuiChip: {
           styleOverrides: {
-            root: {fontSize: 13},
+            root: { fontSize: 13 },
+            label: {
+              textOverflow: 'ellipsis',
+              maxWidth: maxChipLabelWidth
+            }
           },
         },
       },