Merge branch 'main' into avm99963-monorail
GitOrigin-RevId: 2bbe35caf837ba29cc5c1a89d66a6881c1230034
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>
+ )
+}
+