Repository: mui/material-ui (TypeScript)
Date: 2020-06-08 20:00:33+00:00
Task type: Bug Fix

Issue:
[StepConnector] Step connector does not get completed or active styles when alternative
If you use the `alternativeLabel` and `nonLinear` prop on the `Stepper` component, the props passed to the `StepConnector` are not the same, you can see it in this demo:

https://stackblitz.com/edit/lswyj7

<img width="606" alt="Capture d’écran 2020-06-06 à 13 33 58" src="https://user-images.githubusercontent.com/3165635/83943202-68cab880-a7fa-11ea-9c2a-f00f673bdc03.png">

In the alternative versions, the connector gets the `active` and `completed` props, whereas in the other version it doesn't.

I believe the connector shouldn't get any `completed` or `active` props in `nonLinear` mode?


Issue discussion:
Thanks for the bug report. I could reproduction. I definitely agree that both should behave identically. Now, given that people have been asking for being able to customize the active, completed states in the past, I think that could do something like this:

```diff
diff --git a/packages/material-ui/src/Step/Step.js b/packages/material-ui/src/Step/Step.js
index 75a327567..9fd174d30 100644
--- a/packages/material-ui/src/Step/Step.js
+++ b/packages/material-ui/src/Step/Step.js
@@ -33,7 +33,7 @@ const Step = React.forwardRef(function Step(props, ref) {
     className,
     completed = false,
     // eslint-disable-next-line react/prop-types
-    connector,
+    connector: connectorProp,
     disabled = false,
     expanded = false,
     // eslint-disable-next-line react/prop-types
@@ -45,7 +45,18 @@ const Step = React.forwardRef(function Step(props, ref) {
     ...other
   } = props;

-  return (
+  const connector = connectorProp
+    ? React.cloneElement(connectorProp, {
+        orientation,
+        alternativeLabel,
+        index,
+        active,
+        completed,
+        disabled,
+      })
+    : null;
+
+  const newChildren = (
     <div
       className={clsx(
         classes.root,
@@ -59,17 +70,7 @@ const Step = React.forwardRef(function Step(props, ref) {
       ref={ref}
       {...other}
     >
-      {connector &&
-        alternativeLabel &&
-        index !== 0 &&
-        React.cloneElement(connector, {
-          orientation,
-          alternativeLabel,
-          index,
-          active,
-          completed,
-          disabled,
-        })}
+      {connector && alternativeLabel && index !== 0 ? connector : null}

       {React.Children.map(children, (child) => {
         if (!React.isValidElement(child)) {
@@ -101,6 +102,17 @@ const Step = React.forwardRef(function Step(props, ref) {
       })}
     </div>
   );
+
+  if (connector && !alternativeLabel && index !== 0) {
+    return (
+      <React.Fragment>
+        {connector}
+        {newChildren}
+      </React.Fragment>
+    );
+  }
+
+  return newChildren;
 });

 Step.propTypes = {
diff --git a/packages/material-ui/src/Stepper/Stepper.js b/packages/material-ui/src/Stepper/Stepper.js
index e16aa1e33..ea56f2948 100644
--- a/packages/material-ui/src/Stepper/Stepper.js
+++ b/packages/material-ui/src/Stepper/Stepper.js
@@ -46,13 +46,6 @@ const Stepper = React.forwardRef(function Stepper(props, ref) {
     : null;
   const childrenArray = React.Children.toArray(children);
   const steps = childrenArray.map((step, index) => {
-    const controlProps = {
-      alternativeLabel,
-      connector: connectorProp,
-      last: index + 1 === childrenArray.length,
-      orientation,
-    };
-
     const state = {
       index,
       active: false,
@@ -68,16 +61,14 @@ const Stepper = React.forwardRef(function Stepper(props, ref) {
       state.disabled = true;
     }

-    return [
-      !alternativeLabel &&
-        connector &&
-        index !== 0 &&
-        React.cloneElement(connector, {
-          key: index,
-          ...state,
-        }),
-      React.cloneElement(step, { ...controlProps, ...state, ...step.props }),
-    ];
+    return React.cloneElement(step, {
+      alternativeLabel,
+      connector: connectorProp,
+      last: index + 1 === childrenArray.length,
+      orientation,
+      ...state,
+      ...step.props,
+    });
   });

   return (
```

<img width="684" alt="Capture d’écran 2020-06-06 à 01 00 15" src="https://user-images.githubusercontent.com/3165635/83928754-1dc88b00-a791-11ea-83aa-b26d7469302f.png">

@oliviertassinari Hello! I would like to take this bug as my first contribution to the project
@baterson It's all yours 🙏.

Solution patch:
diff --git a/packages/material-ui/src/Step/Step.js b/packages/material-ui/src/Step/Step.js
--- a/packages/material-ui/src/Step/Step.js
+++ b/packages/material-ui/src/Step/Step.js
@@ -33,7 +33,7 @@ const Step = React.forwardRef(function Step(props, ref) {
     className,
     completed = false,
     // eslint-disable-next-line react/prop-types
-    connector,
+    connector: connectorProp,
     disabled = false,
     expanded = false,
     // eslint-disable-next-line react/prop-types
@@ -45,7 +45,18 @@ const Step = React.forwardRef(function Step(props, ref) {
     ...other
   } = props;
 
-  return (
+  const connector = connectorProp
+    ? React.cloneElement(connectorProp, {
+        orientation,
+        alternativeLabel,
+        index,
+        active,
+        completed,
+        disabled,
+      })
+    : null;
+
+  const newChildren = (
     <div
       className={clsx(
         classes.root,
@@ -59,17 +70,7 @@ const Step = React.forwardRef(function Step(props, ref) {
       ref={ref}
       {...other}
     >
-      {connector &&
-        alternativeLabel &&
-        index !== 0 &&
-        React.cloneElement(connector, {
-          orientation,
-          alternativeLabel,
-          index,
-          active,
-          completed,
-          disabled,
-        })}
+      {connector && alternativeLabel && index !== 0 ? connector : null}
 
       {React.Children.map(children, (child) => {
         if (!React.isValidElement(child)) {
@@ -101,6 +102,16 @@ const Step = React.forwardRef(function Step(props, ref) {
       })}
     </div>
   );
+
+  if (connector && !alternativeLabel && index !== 0) {
+    return (
+      <React.Fragment>
+        {connector}
+        {newChildren}
+      </React.Fragment>
+    );
+  }
+  return newChildren;
 });
 
 Step.propTypes = {
diff --git a/packages/material-ui/src/Stepper/Stepper.js b/packages/material-ui/src/Stepper/Stepper.js
--- a/packages/material-ui/src/Stepper/Stepper.js
+++ b/packages/material-ui/src/Stepper/Stepper.js
@@ -46,13 +46,6 @@ const Stepper = React.forwardRef(function Stepper(props, ref) {
     : null;
   const childrenArray = React.Children.toArray(children);
   const steps = childrenArray.map((step, index) => {
-    const controlProps = {
-      alternativeLabel,
-      connector: connectorProp,
-      last: index + 1 === childrenArray.length,
-      orientation,
-    };
-
     const state = {
       index,
       active: false,
@@ -68,16 +61,14 @@ const Stepper = React.forwardRef(function Stepper(props, ref) {
       state.disabled = true;
     }
 
-    return [
-      !alternativeLabel &&
-        connector &&
-        index !== 0 &&
-        React.cloneElement(connector, {
-          key: index,
-          ...state,
-        }),
-      React.cloneElement(step, { ...controlProps, ...state, ...step.props }),
-    ];
+    return React.cloneElement(step, {
+      alternativeLabel,
+      connector,
+      last: index + 1 === childrenArray.length,
+      orientation,
+      ...state,
+      ...step.props,
+    });
   });
 
   return (


Test patch:
diff --git a/packages/material-ui/src/Stepper/Stepper.test.js b/packages/material-ui/src/Stepper/Stepper.test.js
--- a/packages/material-ui/src/Stepper/Stepper.test.js
+++ b/packages/material-ui/src/Stepper/Stepper.test.js
@@ -1,9 +1,9 @@
 import * as React from 'react';
 import { expect } from 'chai';
-import CheckCircle from '../internal/svg-icons/CheckCircle';
 import { createShallow, getClasses } from '@material-ui/core/test-utils';
 import createMount from 'test/utils/createMount';
 import describeConformance from '../test-utils/describeConformance';
+import { createClientRender } from 'test/utils/createClientRender';
 import Paper from '../Paper';
 import Step from '../Step';
 import StepLabel from '../StepLabel';
@@ -16,6 +16,7 @@ describe('<Stepper />', () => {
   let shallow;
   // StrictModeViolation: test uses StepContent
   const mount = createMount({ strict: false });
+  const render = createClientRender({ strict: false });
 
   before(() => {
     classes = getClasses(<Stepper />);
@@ -45,20 +46,17 @@ describe('<Stepper />', () => {
   });
 
   describe('rendering children', () => {
-    it('renders 3 children with connectors as separators', () => {
-      const wrapper = shallow(
+    it('renders 3 Step and 2 StepConnector components', () => {
+      const wrapper = mount(
         <Stepper>
-          <div />
-          <div />
-          <div />
+          <Step />
+          <Step />
+          <Step />
         </Stepper>,
       );
 
-      const children = wrapper.children();
-
-      expect(children.length).to.equal(5);
-      expect(wrapper.childAt(1).find(StepConnector).length).to.equal(1);
-      expect(wrapper.childAt(3).find(StepConnector).length).to.equal(1);
+      expect(wrapper.find(StepConnector).length).to.equal(2);
+      expect(wrapper.find(Step).length).to.equal(3);
     });
   });
 
@@ -122,7 +120,7 @@ describe('<Stepper />', () => {
 
   describe('step connector', () => {
     it('should have a default step connector', () => {
-      const wrapper = shallow(
+      const wrapper = mount(
         <Stepper>
           <Step />
           <Step />
@@ -133,14 +131,15 @@ describe('<Stepper />', () => {
     });
 
     it('should allow the developer to specify a custom step connector', () => {
-      const wrapper = shallow(
-        <Stepper connector={<CheckCircle />}>
+      const CustomConnector = () => null;
+      const wrapper = mount(
+        <Stepper connector={<CustomConnector />}>
           <Step />
           <Step />
         </Stepper>,
       );
 
-      expect(wrapper.find(CheckCircle).length).to.equal(1);
+      expect(wrapper.find(CustomConnector).length).to.equal(1);
       expect(wrapper.find(StepConnector).length).to.equal(0);
     });
 
@@ -156,7 +155,7 @@ describe('<Stepper />', () => {
     });
 
     it('should pass active prop to connector when second step is active', () => {
-      const wrapper = shallow(
+      const wrapper = mount(
         <Stepper activeStep={1}>
           <Step />
           <Step />
@@ -167,7 +166,7 @@ describe('<Stepper />', () => {
     });
 
     it('should pass completed prop to connector when second step is completed', () => {
-      const wrapper = shallow(
+      const wrapper = mount(
         <Stepper activeStep={2}>
           <Step />
           <Step />
@@ -176,6 +175,29 @@ describe('<Stepper />', () => {
       const connectors = wrapper.find(StepConnector);
       expect(connectors.first().props().completed).to.equal(true);
     });
+
+    it('should pass correct active and completed props to the StepConnector with nonLinear prop', () => {
+      const steps = ['Step1', 'Step2', 'Step3'];
+
+      const { container } = render(
+        <Stepper orientation="horizontal" nonLinear connector={<StepConnector />}>
+          {steps.map((label, index) => (
+            <Step key={label} active completed={index === 2}>
+              <StepLabel>{label}</StepLabel>
+            </Step>
+          ))}
+        </Stepper>,
+      );
+
+      const connectors = container.querySelectorAll('.MuiStepConnector-root');
+
+      expect(connectors).to.have.length(2);
+      expect(connectors[0]).to.have.class('MuiStepConnector-active');
+      expect(connectors[0]).to.not.have.class('MuiStepConnector-completed');
+
+      expect(connectors[1]).to.have.class('MuiStepConnector-active');
+      expect(connectors[1]).to.have.class('MuiStepConnector-completed');
+    });
   });
 
   it('renders with a null child', () => {