Skip to content

Commit

Permalink
refactor(react): update ProgressIndicator to FC (#10364)
Browse files Browse the repository at this point in the history
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
joshblack and kodiakhq[bot] authored Jan 13, 2022
1 parent ee07256 commit ee7084f
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 2 deletions.
15 changes: 13 additions & 2 deletions packages/react/src/components/ProgressIndicator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,16 @@
* LICENSE file in the root directory of this source tree.
*/

export { default as ProgressIndicatorSkeleton } from './ProgressIndicator.Skeleton';
export * from './ProgressIndicator';
import * as FeatureFlags from '@carbon/feature-flags';
import { default as ProgressIndicatorSkeleton } from './ProgressIndicator.Skeleton';
import {
ProgressIndicator as ProgressIndicatorClassic,
ProgressStep,
} from './ProgressIndicator';
import { ProgressIndicator as ProgressIndicatorNext } from './next/ProgressIndicator';

const ProgressIndicator = FeatureFlags.enabled('enable-v11-release')
? ProgressIndicatorNext
: ProgressIndicatorClassic;

export { ProgressIndicator, ProgressIndicatorSkeleton, ProgressStep };
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import cx from 'classnames';
import PropTypes from 'prop-types';
import React, { useState } from 'react';
import { usePrefix } from '../../../internal/usePrefix';

function ProgressIndicator({
children,
className: customClassName,
currentIndex: controlledIndex = 0,
onChange,
spaceEqually,
vertical,
...rest
}) {
const prefix = usePrefix();
const [currentIndex, setCurrentIndex] = useState(controlledIndex);
const [prevControlledIndex, setPrevControlledIndex] = useState(
controlledIndex
);
const className = cx({
[`${prefix}--progress`]: true,
[`${prefix}--progress--vertical`]: vertical,
[`${prefix}--progress--space-equal`]: spaceEqually && !vertical,
[customClassName]: customClassName,
});

if (controlledIndex !== prevControlledIndex) {
setCurrentIndex(controlledIndex);
setPrevControlledIndex(controlledIndex);
}

return (
<ul className={className} {...rest}>
{React.Children.map(children, (child, index) => {
// only setup click handlers if onChange event is passed
const onClick = onChange ? () => onChange(index) : undefined;
if (index === currentIndex) {
return React.cloneElement(child, {
current: true,
index,
onClick,
});
}
if (index < currentIndex) {
return React.cloneElement(child, {
complete: true,
index,
onClick,
});
}
if (index > currentIndex) {
return React.cloneElement(child, {
complete: child.props.complete || false,
index,
onClick,
});
}
return null;
})}
</ul>
);
}

ProgressIndicator.propTypes = {
/**
* Provide <ProgressStep> components to be rendered in the
* <ProgressIndicator>
*/
children: PropTypes.node,

/**
* Provide an optional className to be applied to the containing node
*/
className: PropTypes.string,

/**
* Optionally specify the current step array index
*/
currentIndex: PropTypes.number,

/**
* Optional callback called if a ProgressStep is clicked on. Returns the index of the step.
*/
onChange: PropTypes.func,

/**
* Specify whether the progress steps should be split equally in size in the div
*/
spaceEqually: PropTypes.bool,
/**
* Determines whether or not the ProgressIndicator should be rendered vertically.
*/
vertical: PropTypes.bool,
};

export { ProgressIndicator };
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import { mount } from 'enzyme';
import React from 'react';
import { ProgressIndicator } from '../ProgressIndicator';
import { ProgressStep } from '../../';

const prefix = 'bx';

function getActiveIndex(wrapper) {
return wrapper
.find(`.${prefix}--progress-step--current`)
.parent()
.prop('index');
}

describe('ProgressIndicator', () => {
describe('Renders as expected', () => {
let list;

beforeEach(() => {
list = mount(
<ProgressIndicator className="some-class" currentIndex={3}>
<ProgressStep
label="label"
description="Step 1: Getting Started with Node.js"
/>
<ProgressStep
label="label"
description="Step 2: Getting Started with Node.js"
/>
<ProgressStep
label="label"
description="Step 3: Getting Started with Node.js"
/>
<ProgressStep
label="label"
description="Step 4: Getting Started with Node.js"
/>
<ProgressStep
label="label"
description="Step 5: Getting Started with Node.js"
/>
<ProgressStep
label="label"
description="Step 6: Getting Started with Node.js"
/>
</ProgressIndicator>
);
});

it('should be a ul element', () => {
expect(list.find('ul').length).toEqual(1);
});

it('should render children as expected', () => {
expect(list.find(ProgressStep).length).toEqual(6);
});

it('should have the initial currentIndex from props', () => {
expect(getActiveIndex(list)).toEqual(3);
});

it('should update state when currentIndex is changed', () => {
list.setProps({ currentIndex: 1 });
expect(getActiveIndex(list)).toEqual(1);
list.setProps({ currentIndex: 0 });
expect(getActiveIndex(list)).toEqual(0);
});

it('should trigger onChange if clicked', () => {
const mockOnChange = jest.fn();

list.setProps({ onChange: mockOnChange });
list.find(ProgressStep).at(0).find('button').simulate('click');
expect(mockOnChange).toHaveBeenCalledWith(0);
});

describe('ProgressStep', () => {
it('should render with correct base className', () => {
expect(
list
.find(ProgressStep)
.at(0)
.children()
.hasClass(`${prefix}--progress-step`)
).toEqual(true);
});

it('should render with a label', () => {
expect(list.find(ProgressStep).at(0).prop('label')).toEqual('label');
});

it('should render with a description', () => {
expect(list.find(ProgressStep).at(0).prop('description')).toEqual(
'Step 1: Getting Started with Node.js'
);
});

it('should render description in <title> node', () => {
expect(list.find('ProgressStep title').at(0).text()).toEqual(
'Step 1: Getting Started with Node.js'
);
});

describe('current', () => {
it('should render a current ProgressStep with correct className', () => {
expect(
list
.find(ProgressStep)
.at(3)
.children()
.hasClass(`${prefix}--progress-step--current`)
).toEqual(true);
});

it('should render a current ProgressStep with correct props', () => {
expect(list.find(ProgressStep).at(3).prop('current')).toBe(true);
});
});

describe('complete', () => {
it('should render any completed ProgressSteps with correct className', () => {
expect(
list
.find(ProgressStep)
.at(0)
.children()
.hasClass(`${prefix}--progress-step--complete`)
).toEqual(true);
});
it('should render any completed ProgressSteps with correct props', () => {
expect(list.find(ProgressStep).at(0).prop('complete')).toBe(true);
});
});

describe('incomplete', () => {
it('should render any incomplete ProgressSteps with correct className', () => {
expect(
list
.find(ProgressStep)
.at(5)
.children()
.hasClass(`${prefix}--progress-step--incomplete`)
).toEqual(true);
});
it('should render any incomplete ProgressSteps with correct props', () => {
expect(list.find(ProgressStep).at(5).prop('complete')).toBe(false);
});

it('should render any clickable ProgressSteps with correct classname', () => {
list.setProps({ onChange: jest.fn() });
expect(list.find(`.${prefix}--progress-step-button`)).toHaveLength(6); // one button for each div
expect(
list.find(`.${prefix}--progress-step-button--unclickable`)
).toHaveLength(1); // only the current step should be unclickable
});
});
});
});
});

0 comments on commit ee7084f

Please sign in to comment.