Merge pull request #80 from kevinsqi/add-dashboard-style

Add props.circleRatio to enable partial diameter "car speedometer" style
This commit is contained in:
Kevin Qi 2019-04-28 20:58:38 -07:00 committed by GitHub
commit d71ad84bdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 497 additions and 91 deletions

View File

@ -63,6 +63,7 @@ const percentage = 66;
| `backgroundPadding` | Padding between background and edge of svg as a percentage relative to total width of component. Default: `null`. |
| `initialAnimation` | Toggle whether to animate progress starting from 0% on initial mount. Default: `false`. |
| `counterClockwise` | Toggle whether to rotate progressbar in counterclockwise direction. Default: `false`. |
| `circleRatio` | Number from 0-1 representing ratio of the full circle diameter the progressbar should use. Default: `1`. |
| `classes` | Object allowing overrides of classNames of each svg subcomponent (root, trail, path, text, background). Enables styling with react-jss. See [this PR](https://github.com/kevinsqi/react-circular-progressbar/pull/25) for more detail. |
| `styles` | Object allowing customization of styles of each svg subcomponent (root, trail, path, text, background). |

View File

@ -4,14 +4,19 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@types/classnames": "^2.2.7",
"@types/d3-ease": "^1.0.8",
"@types/jest": "24.0.11",
"@types/node": "11.13.6",
"@types/react": "16.8.14",
"@types/react-dom": "16.8.4",
"classnames": "^2.2.6",
"d3-ease": "^1.0.5",
"gh-pages": "^2.0.1",
"react": "^16.8.6",
"react-circular-progressbar": "file:./../",
"react-dom": "^16.8.6",
"react-move": "^5.2.1",
"react-scripts": "2.1.8",
"typescript": "3.4.4"
},

View File

@ -0,0 +1,59 @@
import React from 'react';
import { Animate } from 'react-move';
import CircularProgressbar from 'react-circular-progressbar';
type Props = {
duration: number;
easingFunction: Function;
percentage: number;
};
type State = {
isAnimated: boolean;
};
class AnimatedProgressbar extends React.Component<Props, State> {
state = {
isAnimated: false,
};
componentDidMount() {
this.setState({
isAnimated: true,
});
}
render() {
return (
<Animate
start={() => ({
percentage: 0,
})}
update={() => ({
percentage: [this.state.isAnimated ? this.props.percentage : 0],
timing: {
duration: this.props.duration * 1000,
ease: this.props.easingFunction,
},
})}
>
{({ percentage }) => {
const roundedPercentage = Math.round(percentage);
return (
<CircularProgressbar
percentage={roundedPercentage}
text={`${roundedPercentage}%`}
styles={{
path: {
transition: 'none',
},
}}
/>
);
}}
</Animate>
);
}
}
export default AnimatedProgressbar;

View File

@ -1,19 +1,33 @@
import React from 'react';
import CircularProgressbar from 'react-circular-progressbar';
import classNames from 'classnames';
import { easeSinOut, easeQuadIn, easeQuadInOut, easeLinear, easeCubicInOut } from 'd3-ease';
// Custom progressbar wrappers
import AnimatedProgressbar from './AnimatedProgressbar';
import ChangingProgressbar from './ChangingProgressbar';
const githubURL = 'https://github.com/kevinsqi/react-circular-progressbar';
const Example: React.FunctionComponent<{ description: string }> = ({ description, children }) => (
<div className="col-12 col-sm-6 col-md-3">
<div className="row mb-1">
<div className="col-6 offset-3">{children}</div>
const Code: React.FunctionComponent<React.HTMLProps<HTMLSpanElement>> = (props) => (
<code className={classNames('p-1 bg-yellow text-dark', props.className)} {...props} />
);
const Example: React.FunctionComponent<{ description: React.ReactNode }> = ({
description,
children,
}) => (
<div className="col-12 col-sm-6 col-md-4 mt-5">
<div className="row">
<div className="col-6 col-md-4 offset-3 offset-md-4">{children}</div>
</div>
<p className="text-center">{description}</p>
<p className="text-center mt-3">{description}</p>
</div>
);
function Demo() {
const [showAllExamples, setShowAllExamples] = React.useState(false);
return (
<div className="container">
<div className="row mt-5">
@ -43,27 +57,47 @@ function Demo() {
<hr />
<div className="row mt-5">
<div className="col-12 mb-5">
<div className="col-12">
<h2 className="text-center">Built with SVG and styled with plain CSS.</h2>
</div>
<Example description="Customize text and styling dynamically based on percentage.">
<Example
description={
<span>
Customize <Code>props.text</Code> and <Code>props.className</Code> based on
percentage.
</span>
}
>
<ChangingProgressbar
percentages={[75, 100]}
classForPercentage={(percentage: number) => {
return percentage === 100 ? 'complete' : 'incomplete';
}}
textForPercentage={(percentage: number) => {
return percentage === 100 ? `${percentage}!!` : `${percentage}`;
return percentage === 100 ? `${percentage}!!` : `${percentage}...`;
}}
/>
</Example>
<Example description="Customize stroke width and make it go counterclockwise.">
<ChangingProgressbar percentages={[0, 20, 80]} strokeWidth={5} counterClockwise />
<Example
description={
<span>
Customize <Code>props.strokeWidth</Code> and make it go counterclockwise with{' '}
<Code>props.counterClockwise</Code>.
</span>
}
>
<ChangingProgressbar percentages={[0, 80]} strokeWidth={5} counterClockwise />
</Example>
<Example description="Add a background color for that inverted look.">
<Example
description={
<span>
Use <Code>props.background</Code> to give it an inverted style.
</span>
}
>
<CircularProgressbar
className="CircularProgressbar-inverted"
background
@ -86,16 +120,90 @@ function Demo() {
/>
</Example>
<Example description="With SVG and CSS you can do whatever you want.">
<Example
description={
<span>
Use a library like react-move to ease <Code>props.percentage</Code> if you want to
animate text.
</span>
}
>
<AnimatedProgressbar percentage={66} duration={1.4} easingFunction={easeQuadInOut} />
</Example>
<Example
description={
<span>
"Give me that car speedometer look?" Use <Code>props.circleRatio</Code> and CSS
rotation.
</span>
}
>
<CircularProgressbar
percentage={66}
text={`${66}%`}
circleRatio={0.75}
styles={{
trail: {
strokeLinecap: 'butt',
transform: 'rotate(-135deg)',
transformOrigin: 'center center',
},
path: {
strokeLinecap: 'butt',
transform: 'rotate(-135deg)',
transformOrigin: 'center center',
},
}}
/>
</Example>
<Example description="Need multiple lines of text or custom content? With a bit of CSS you can do whatever you want.">
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
<div style={{ position: 'absolute', width: '100%' }}>
<CircularProgressbar percentage={50} />
<CircularProgressbar percentage={66} />
</div>
<div style={{ width: '100%', padding: '10%' }}>
<div style={{ width: '100%', padding: '20%' }}>
<img style={{ width: '100%' }} src="https://i.imgur.com/b9NyUGm.png" alt="doge" />
</div>
</div>
</Example>
{showAllExamples ? (
<React.Fragment>
<Example
description={
<span>
<Code>circleRatio</Code>&nbsp;
<Code>counterClockwise</Code>&nbsp;
<Code>background</Code>
</span>
}
>
<CircularProgressbar
percentage={50}
circleRatio={0.7}
counterClockwise
background
backgroundPadding={4}
styles={{
trail: {
stroke: '#fff',
},
}}
/>
</Example>
</React.Fragment>
) : (
<div className="col-12 text-center">
<button
className="btn btn-link text-secondary"
onClick={() => setShowAllExamples(true)}
>
<small>Show more examples</small>
</button>
</div>
)}
</div>
<hr />
@ -104,7 +212,7 @@ function Demo() {
<div className="text-center mt-5">
<p>Install with yarn or npm:</p>
<p className="mb-5">
<code className="p-2 text-dark bg-yellow">yarn add react-circular-progressbar</code>
<Code>yarn add react-circular-progressbar</Code>
</p>
<a className="btn btn-info btn-lg" href={githubURL}>
View docs on Github

View File

@ -1039,6 +1039,13 @@
dependencies:
regenerator-runtime "^0.12.0"
"@babel/runtime@^7.3.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.4.tgz#dc2e34982eb236803aa27a07fea6857af1b9171d"
integrity sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg==
dependencies:
regenerator-runtime "^0.13.2"
"@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2":
version "7.2.2"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907"
@ -1228,6 +1235,16 @@
"@svgr/plugin-svgo" "^4.0.3"
loader-utils "^1.1.0"
"@types/classnames@^2.2.7":
version "2.2.7"
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.7.tgz#fb68cc9be8487e6ea5b13700e759bfbab7e0fefd"
integrity sha512-rzOhiQ55WzAiFgXRtitP/ZUT8iVNyllEpylJ5zHzR4vArUvMB39GTk+Zon/uAM0JxEFAWnwsxC2gH8s+tZ3Myg==
"@types/d3-ease@^1.0.8":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-1.0.8.tgz#b440761fb85985d76259ec9c5bf01c4c56778ac2"
integrity sha512-VRf8czVWHSJPoUWxMunzpePK02//wHDAswknU8QWzcyrQn6pqe46bHRYi2smSpw5VjsT2CG8k/QeWIdWPS3Bmg==
"@types/jest-diff@*":
version "20.0.1"
resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89"
@ -2602,6 +2619,11 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
classnames@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
clean-css@4.2.x:
version "4.2.1"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17"
@ -3232,6 +3254,16 @@ cyclist@~0.2.2:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=
d3-ease@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.5.tgz#8ce59276d81241b1b72042d6af2d40e76d936ffb"
integrity sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ==
d3-timer@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.9.tgz#f7bb8c0d597d792ff7131e1c24a36dd471a471ba"
integrity sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg==
damerau-levenshtein@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514"
@ -6147,6 +6179,13 @@ jsx-ast-utils@^2.0.1:
dependencies:
array-includes "^3.0.3"
kapellmeister@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/kapellmeister/-/kapellmeister-3.0.1.tgz#419b715cd221acda3db79892caedf63e1c9f7d25"
integrity sha512-S7+gYcziMREv8RxG46138mb1O4Xf9II/bCxEJPYkhlZ7PgGWTlicgsyNad/DGc5oEAlWGLXE5ExLbTDVvJmgDA==
dependencies:
d3-timer "^1.0.9"
killable@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@ -8168,15 +8207,6 @@ prompts@^0.1.9:
kleur "^2.0.1"
sisteransi "^0.1.1"
prop-types@^15.5.10:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.8.1"
prop-types@^15.6.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
@ -8185,6 +8215,15 @@ prop-types@^15.6.2:
loose-envify "^1.3.1"
object-assign "^4.1.1"
prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.8.1"
property-information@^5.0.0, property-information@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.0.1.tgz#c3b09f4f5750b1634c0b24205adbf78f18bdf94f"
@ -8367,10 +8406,8 @@ react-app-polyfill@^0.2.2:
raf "3.4.1"
whatwg-fetch "3.0.0"
"react-circular-progressbar@file:./../":
version "1.0.0"
dependencies:
prop-types "^15.5.10"
"react-circular-progressbar@file:./..":
version "1.1.0"
react-dev-utils@^8.0.0:
version "8.0.0"
@ -8423,6 +8460,21 @@ react-is@^16.8.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-move@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/react-move/-/react-move-5.2.1.tgz#2098cbb071538a487a815f54b73842c30aaa7628"
integrity sha512-2If8uw9jJUQC8KxT1V1bJJ9amDvbHM4zCT42UekmP9KKrkf6elbc1PsXmPprfMjd20PkejtKnvs9T/TTXmTymA==
dependencies:
"@babel/runtime" "^7.3.4"
kapellmeister "^3.0.1"
prop-types "^15.7.2"
react-lifecycles-compat "^3.0.4"
react-scripts@2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-2.1.8.tgz#21195bb928b2c0462aa98b2d32edf7d034cff2a9"
@ -8605,6 +8657,11 @@ regenerator-runtime@^0.12.0:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
regenerator-runtime@^0.13.2:
version "0.13.2"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447"
integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==
regenerator-transform@^0.13.3:
version "0.13.3"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb"

View File

@ -16,6 +16,7 @@ type CircularProgressbarDefaultProps = {
backgroundPadding: number;
initialAnimation: boolean;
counterClockwise: boolean;
circleRatio: number;
classes: {
root: string;
trail: string;
@ -55,6 +56,7 @@ class CircularProgressbar extends React.Component<
backgroundPadding: 0,
initialAnimation: false,
counterClockwise: false,
circleRatio: 1,
classes: {
root: 'CircularProgressbar',
trail: 'CircularProgressbar-trail',
@ -117,36 +119,6 @@ class CircularProgressbar extends React.Component<
return 0;
}
getPathDescription() {
const radius = this.getPathRadius();
const rotation = this.props.counterClockwise ? 1 : 0;
// Move to center of canvas
// Relative move to top canvas
// Relative arc to bottom of canvas
// Relative arc to top of canvas
return `
M ${CENTER_X},${CENTER_Y}
m 0,-${radius}
a ${radius},${radius} ${rotation} 1 1 0,${2 * radius}
a ${radius},${radius} ${rotation} 1 1 0,-${2 * radius}
`;
}
getPathStyles() {
const diameter = Math.PI * 2 * this.getPathRadius();
const truncatedPercentage = Math.min(
Math.max(this.state.percentage, MIN_PERCENTAGE),
MAX_PERCENTAGE,
);
const dashoffset = ((100 - truncatedPercentage) / 100) * diameter;
return {
strokeDasharray: `${diameter}px ${diameter}px`,
strokeDashoffset: `${this.props.counterClockwise ? -dashoffset : dashoffset}px`,
};
}
getPathRadius() {
// the radius of the path is defined to be in the middle, so in order for the path to
// fit perfectly inside the 100x100 viewBox, need to subtract half the strokeWidth
@ -154,8 +126,18 @@ class CircularProgressbar extends React.Component<
}
render() {
const { percentage, className, classes, styles, strokeWidth, text } = this.props;
const pathDescription = this.getPathDescription();
const {
className,
classes,
counterClockwise,
percentage,
styles,
strokeWidth,
text,
circleRatio,
} = this.props;
const pathRadius = this.getPathRadius();
return (
<svg
@ -173,20 +155,22 @@ class CircularProgressbar extends React.Component<
/>
) : null}
<path
<Path
className={classes.trail}
style={styles.trail}
d={pathDescription}
counterClockwise={counterClockwise}
pathRadius={pathRadius}
percentage={100 * circleRatio}
strokeWidth={strokeWidth}
fillOpacity={0}
style={styles.trail}
/>
<path
<Path
className={classes.path}
d={pathDescription}
counterClockwise={counterClockwise}
pathRadius={pathRadius}
percentage={percentage * circleRatio}
strokeWidth={strokeWidth}
fillOpacity={0}
style={Object.assign({}, styles.path, this.getPathStyles())}
style={styles.path}
/>
{text ? (
@ -199,4 +183,74 @@ class CircularProgressbar extends React.Component<
}
}
function Path({
className,
counterClockwise,
pathRadius,
percentage,
strokeWidth,
style,
}: {
className?: string;
counterClockwise: boolean;
pathRadius: number;
percentage: number;
strokeWidth: number;
style?: object;
}) {
return (
<path
className={className}
style={Object.assign({}, style, getDashStyle({ pathRadius, percentage, counterClockwise }))}
d={getPathDescription({
pathRadius,
counterClockwise,
})}
strokeWidth={strokeWidth}
fillOpacity={0}
/>
);
}
function getPathDescription({
pathRadius,
counterClockwise,
}: {
pathRadius: number;
counterClockwise: boolean;
}) {
const radius = pathRadius;
const rotation = counterClockwise ? 1 : 0;
// Move to center of canvas
// Relative move to top canvas
// Relative arc to bottom of canvas
// Relative arc to top of canvas
return `
M ${CENTER_X},${CENTER_Y}
m 0,-${radius}
a ${radius},${radius} ${rotation} 1 1 0,${2 * radius}
a ${radius},${radius} ${rotation} 1 1 0,-${2 * radius}
`;
}
function getDashStyle({
pathRadius,
percentage,
counterClockwise,
}: {
pathRadius: number;
percentage: number;
counterClockwise: boolean;
}) {
const diameter = Math.PI * 2 * pathRadius;
const truncatedPercentage = Math.min(Math.max(percentage, MIN_PERCENTAGE), MAX_PERCENTAGE);
const gapLength = (1 - truncatedPercentage / 100) * diameter;
return {
strokeDasharray: `${diameter}px ${diameter}px`,
strokeDashoffset: `${counterClockwise ? -gapLength : gapLength}px`,
};
}
export default CircularProgressbar;

View File

@ -22,6 +22,8 @@
.CircularProgressbar .CircularProgressbar-trail {
stroke: #d6d6d6;
/* Used when trail is not full diameter, i.e. when props.percentageOfCircleToShow is set */
stroke-linecap: round;
}
.CircularProgressbar .CircularProgressbar-text {

View File

@ -1,8 +1,21 @@
import React from 'react';
import { shallow } from 'enzyme';
import { mount, shallow, ReactWrapper } from 'enzyme';
import CircularProgressbar from '../src/index';
function getExpectedStrokeDashoffset({
percentage,
strokeWidth,
}: {
percentage: number;
strokeWidth: number;
}) {
const radius = 50 - strokeWidth / 2;
const diameter = 2 * radius * Math.PI;
const expectedGapLength = (1 - percentage / 100) * diameter;
return `${expectedGapLength}px`;
}
describe('<CircularProgressbar />', () => {
test('SVG rendered to DOM', () => {
const wrapper = shallow(<CircularProgressbar percentage={50} />);
@ -36,29 +49,91 @@ describe('<CircularProgressbar />', () => {
describe('props.percentage', () => {
test('Renders correct path', () => {
const percentage = 30;
const wrapper = shallow(
const wrapper = mount(
<CircularProgressbar percentage={percentage} strokeWidth={0} className="my-custom-class" />,
);
const dashoffset = wrapper.find('.CircularProgressbar-path').prop('style')!.strokeDashoffset;
const expectedRadius = 50;
const expectedDiameter = 2 * expectedRadius * Math.PI;
const expectedOffset = ((100 - percentage) / 100) * expectedDiameter;
expect(dashoffset).toEqual(`${expectedOffset}px`);
expect(
wrapper
.find('.CircularProgressbar-path')
.hostNodes()
.prop('style')!.strokeDashoffset,
).toEqual(getExpectedStrokeDashoffset({ percentage, strokeWidth: 0 }));
const expectedRadius = 50;
const expectedArcto = `a ${expectedRadius},${expectedRadius}`;
expect(wrapper.find('.CircularProgressbar-path').prop('d')).toContain(expectedArcto);
expect(
wrapper
.find('.CircularProgressbar-path')
.hostNodes()
.prop('d'),
).toContain(expectedArcto);
});
});
describe('props.counterClockwise', () => {
test('Reverses dashoffset', () => {
const clockwise = shallow(<CircularProgressbar percentage={50} />);
const counterClockwise = shallow(<CircularProgressbar percentage={50} counterClockwise />);
const clockwise = mount(<CircularProgressbar percentage={50} />);
const counterClockwise = mount(<CircularProgressbar percentage={50} counterClockwise />);
// Counterclockwise should have the negative dashoffset of clockwise
expect(
`-${clockwise.find('.CircularProgressbar-path').prop('style')!.strokeDashoffset}`,
).toEqual(counterClockwise.find('.CircularProgressbar-path').prop('style')!.strokeDashoffset);
`-${
clockwise
.find('.CircularProgressbar-path')
.hostNodes()
.prop('style')!.strokeDashoffset
}`,
).toEqual(
counterClockwise
.find('.CircularProgressbar-path')
.hostNodes()
.prop('style')!.strokeDashoffset,
);
});
});
describe('props.circleRatio', () => {
test('Default full diameter', () => {
const percentage = 25;
const strokeWidth = 5;
const wrapper = mount(
<CircularProgressbar percentage={percentage} strokeWidth={strokeWidth} circleRatio={1} />,
);
expect(
wrapper
.find('.CircularProgressbar-path')
.hostNodes()
.prop('style')!.strokeDashoffset,
).toEqual(getExpectedStrokeDashoffset({ percentage, strokeWidth }));
});
test('Correct path and trail lengths', () => {
const percentage = 25;
const strokeWidth = 5;
const circleRatio = 0.8;
const wrapper = mount(
<CircularProgressbar
percentage={percentage}
strokeWidth={strokeWidth}
circleRatio={circleRatio}
/>,
);
// Path offset should be scaled
expect(
wrapper
.find('.CircularProgressbar-path')
.hostNodes()
.prop('style')!.strokeDashoffset,
).toEqual(getExpectedStrokeDashoffset({ percentage: percentage * circleRatio, strokeWidth }));
// Trail offset should be scaled
expect(
wrapper
.find('.CircularProgressbar-trail')
.hostNodes()
.prop('style')!.strokeDashoffset,
).toEqual(getExpectedStrokeDashoffset({ percentage: 100 * circleRatio, strokeWidth }));
});
});
describe('props.styles', () => {
@ -101,14 +176,34 @@ describe('<CircularProgressbar />', () => {
});
describe('props.classes', () => {
test('Has default values', () => {
const wrapper = shallow(<CircularProgressbar percentage={50} text="50" />);
expect(wrapper.find('.CircularProgressbar').type()).toEqual('svg');
expect(wrapper.find('.CircularProgressbar-path').type()).toEqual('path');
expect(wrapper.find('.CircularProgressbar-trail').type()).toEqual('path');
expect(wrapper.find('.CircularProgressbar-text').type()).toEqual('text');
const wrapper = mount(<CircularProgressbar percentage={50} text="50" />);
expect(
wrapper
.find('.CircularProgressbar')
.hostNodes()
.type(),
).toEqual('svg');
expect(
wrapper
.find('.CircularProgressbar-path')
.hostNodes()
.type(),
).toEqual('path');
expect(
wrapper
.find('.CircularProgressbar-trail')
.hostNodes()
.type(),
).toEqual('path');
expect(
wrapper
.find('.CircularProgressbar-text')
.hostNodes()
.type(),
).toEqual('text');
});
test('Prop overrides work', () => {
const wrapper = shallow(
const wrapper = mount(
<CircularProgressbar
percentage={50}
text="50"
@ -131,11 +226,36 @@ describe('<CircularProgressbar />', () => {
expect(wrapper.find('.CircularProgressbar-background').exists()).toBe(false);
// Assert override classes do exist
expect(wrapper.find('.someRootClass').type()).toEqual('svg');
expect(wrapper.find('.somePathClass').type()).toEqual('path');
expect(wrapper.find('.someTrailClass').type()).toEqual('path');
expect(wrapper.find('.someTextClass').type()).toEqual('text');
expect(wrapper.find('.someBackgroundClass').type()).toEqual('circle');
expect(
wrapper
.find('.someRootClass')
.hostNodes()
.type(),
).toEqual('svg');
expect(
wrapper
.find('.somePathClass')
.hostNodes()
.type(),
).toEqual('path');
expect(
wrapper
.find('.someTrailClass')
.hostNodes()
.type(),
).toEqual('path');
expect(
wrapper
.find('.someTextClass')
.hostNodes()
.type(),
).toEqual('text');
expect(
wrapper
.find('.someBackgroundClass')
.hostNodes()
.type(),
).toEqual('circle');
});
});
});