Skip to content

Commit

Permalink
Merge pull request #3 from BillRobitskeJr/value-scale
Browse files Browse the repository at this point in the history
feat(scale): add scale property
  • Loading branch information
BillRobitskeJr committed Jun 28, 2019
2 parents c35a644 + f519534 commit bd1acae
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 7 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"name": "react-time-duration-input",
"version": "0.0.0-development",
"version": "1.0.0",
"description": "React component for input time durations",
"main": "dist/index.js",
"scripts": {
"test": "jest",
"test-watch": "jest --watch",
"coverage": "jest --coverage",
"lint": "eslint ./src/**/*.js",
"prepublishOnly": "babel ./src --ignore src/**/*.test.js --out-dir ./dist -s inline",
Expand Down
35 changes: 29 additions & 6 deletions src/components/TimeDurationInput/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import React, { useState, useCallback, useEffect } from 'react'
import PropTypes from 'prop-types'

export function TimeDurationInput ({ value, onChange, className }) {
const [ duration, setDuration ] = useState(convertValueToDuration(value))
export function TimeDurationInput ({ value, scale, onChange, className }) {
const [ duration, setDuration ] = useState(convertFromValue(value, scale))

useEffect(() => {
const newDuration = convertValueToDuration(value)
const newDuration = convertFromValue(value, scale)
if (newDuration !== duration) setDuration(newDuration)
}, [ value ])
}, [ value, scale ])

const onInputChange = useCallback(({ target }) => {
setDuration(target.value)
const newValue = convertDurationToValue(target.value)
const newValue = convertToValue(target.value, scale)
if (!isNaN(newValue)) onChange(newValue)
}, [ onChange ])
}, [ onChange, scale ])

return (
<input type="text" className={className} value={duration} onChange={onInputChange} data-testid="duration-input" />
Expand All @@ -21,14 +22,32 @@ export function TimeDurationInput ({ value, onChange, className }) {

TimeDurationInput.propTypes = {
value: PropTypes.number,
scale: PropTypes.oneOf([ 'd', 'h', 'm', 's', 'ms' ]),
onChange: PropTypes.func,
className: PropTypes.string
}

TimeDurationInput.defaultProps = {
scale: 'ms',
onChange: () => {}
}

export const SCALE_CONVERSIONS = {
ms: 1,
s: 1000,
m: 60000,
h: 3600000,
d: 86400000
}

export function convertValueFromScale (value, scale) {
return value * (SCALE_CONVERSIONS[scale] || 1)
}

export function convertValueToScale (value, scale) {
return value / (SCALE_CONVERSIONS[scale] || 1)
}

export function convertValueToDuration (value) {
const milliseconds = Math.round(value % 1000)
const seconds = Math.floor(value / 1000 % 60)
Expand All @@ -51,4 +70,8 @@ export function convertDurationToValue (duration) {
return (((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000 + milliseconds
}

export const convertFromValue = (value, scale) => convertValueToDuration(convertValueFromScale(value, scale))

export const convertToValue = (duration, scale) => convertValueToScale(convertDurationToValue(duration), scale)

export default TimeDurationInput
102 changes: 102 additions & 0 deletions src/components/TimeDurationInput/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,106 @@ describe('TimeDurationInput', () => {
})
})
})

describe('scaled values', () => {
describe('when value = 250 and scale = "ms"', () => {
it('displays "250ms" in the input element', () => {
const { getByTestId } = render(<TimeDurationInput value={250} scale="ms" />)

expect(getByTestId('duration-input')).toHaveValue('250ms')
})
})

describe('when value = 250 and scale = "s"', () => {
it('displays "4m 10s" in the input element', () => {
const { getByTestId } = render(<TimeDurationInput value={250} scale="s" />)

expect(getByTestId('duration-input')).toHaveValue('4m 10s')
})
})

describe('when value = 250 and scale = "m"', () => {
it('displays "4h 10m" in the input element', () => {
const { getByTestId } = render(<TimeDurationInput value={250} scale="m" />)

expect(getByTestId('duration-input')).toHaveValue('4h 10m')
})
})

describe('when value = 250 and scale = "h"', () => {
it('displays "10d 10h" in the input element', () => {
const { getByTestId } = render(<TimeDurationInput value={250} scale="h" />)

expect(getByTestId('duration-input')).toHaveValue('10d 10h')
})
})

describe('when value = 250 and scale = "d"', () => {
it('displays "250d" in the input element', () => {
const { getByTestId } = render(<TimeDurationInput value={250} scale="d" />)

expect(getByTestId('duration-input')).toHaveValue('250d')
})
})

describe('when scale = "ms" and input is changed to "1h 45m"', () => {
it('calls onChange with value 6300000', () => {
const onChange = jest.fn()
const { getByTestId } = render(<TimeDurationInput scale="ms" onChange={onChange} />)

fireEvent.change(getByTestId('duration-input'), { target: { value: '1h 45m' } })

expect(onChange.mock.calls.length).toBe(1)
expect(onChange.mock.calls[0][0]).toBe(6300000)
})
})

describe('when scale = "s" and input is changed to "1h 45m"', () => {
it('calls onChange with value 6300', () => {
const onChange = jest.fn()
const { getByTestId } = render(<TimeDurationInput scale="s" onChange={onChange} />)

fireEvent.change(getByTestId('duration-input'), { target: { value: '1h 45m' } })

expect(onChange.mock.calls.length).toBe(1)
expect(onChange.mock.calls[0][0]).toBe(6300)
})
})

describe('when scale = "m" and input is changed to "1h 45m"', () => {
it('calls onChange with value 105', () => {
const onChange = jest.fn()
const { getByTestId } = render(<TimeDurationInput scale="m" onChange={onChange} />)

fireEvent.change(getByTestId('duration-input'), { target: { value: '1h 45m' } })

expect(onChange.mock.calls.length).toBe(1)
expect(onChange.mock.calls[0][0]).toBe(105)
})
})

describe('when scale = "h" and input is changed to "1h 45m"', () => {
it('calls onChange with value 1.75', () => {
const onChange = jest.fn()
const { getByTestId } = render(<TimeDurationInput scale="h" onChange={onChange} />)

fireEvent.change(getByTestId('duration-input'), { target: { value: '1h 45m' } })

expect(onChange.mock.calls.length).toBe(1)
expect(onChange.mock.calls[0][0]).toBeCloseTo(1.75)
})
})

describe('when scale = "d" and input is changed to "1h 45m"', () => {
it('calls onChange with value 0.0729167 (approx.)', () => {
const onChange = jest.fn()
const { getByTestId } = render(<TimeDurationInput scale="d" onChange={onChange} />)

fireEvent.change(getByTestId('duration-input'), { target: { value: '1h 45m' } })

expect(onChange.mock.calls.length).toBe(1)
expect(onChange.mock.calls[0][0]).toBeCloseTo(0.0729167)
})
})
})
})
43 changes: 43 additions & 0 deletions src/components/TimeDurationInput/scale.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { convertValueFromScale, convertValueToScale } from './index'

const SCENARIOS = [
{ value: 0, scales: { d: 0, h: 0, m: 0, s: 0, ms: 0 } }
]

describe('convertValueFromScale(value, scale)', () => {
describe('when scale is invalid', () => {
it('returns the passed value', () => {
const value = Math.random()
expect(convertValueFromScale(value, 'invalid')).toBeCloseTo(value)
})
})

SCENARIOS.forEach(({ value, scales }) => {
Object.keys(scales).forEach(scale => {
describe(`when value = ${scales[scale]} and scale = "${scale}"`, () => {
it(`returns ${value}`, () => {
expect(convertValueFromScale(scales[scale], scale)).toBeCloseTo(value)
})
})
})
})
})

describe('convertValueToScale(value, scale)', () => {
describe('when scale is invalid', () => {
it('returns the passed value', () => {
const value = Math.random()
expect(convertValueToScale(value, 'invalid')).toBeCloseTo(value)
})
})

SCENARIOS.forEach(({ value, scales }) => {
Object.keys(scales).forEach(scale => {
describe(`when value = ${value} and scale = "${scale}"`, () => {
it(`returns ${scales[scale]}`, () => {
expect(convertValueToScale(value, scale)).toBeCloseTo(scales[scale])
})
})
})
})
})

0 comments on commit bd1acae

Please sign in to comment.