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
+ }
},
},
},