-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from copios-jp/master
Zone Timer
- Loading branch information
Showing
9 changed files
with
319 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import React from 'react' | ||
import { Component } from 'react' | ||
import Timer from '../../../services/timer/' | ||
import { withStyles } from '@material-ui/core/styles' | ||
import styles from '../../styles/' | ||
|
||
import { getZone } from '../../../services/analytics/' | ||
|
||
const pad = (integer) => { | ||
return integer < 10 ? `0${integer}` : integer.toString() | ||
} | ||
|
||
class StopWatch extends Component { | ||
componentDidMount() { | ||
this.timer.on('tick', this.update) | ||
this.timer.start() | ||
} | ||
|
||
componentDidUpdate = (prevProps) => { | ||
if (getZone(prevProps.sensor) != getZone(this.props.sensor)) { | ||
this.timer.reset() | ||
} | ||
} | ||
|
||
componentWillUnmount() { | ||
this.timer.stop() | ||
} | ||
|
||
getFormattedTime() { | ||
const { time } = this.state | ||
const minutes = Math.round(time / 60) | ||
const seconds = Math.round(time % 60) | ||
return `${pad(minutes)}:${pad(seconds)}` | ||
} | ||
|
||
render = () => { | ||
const { classes } = this.props | ||
const time = this.getFormattedTime() | ||
return <div className={classes.timer}>{time}</div> | ||
} | ||
|
||
state = { | ||
time: this.props.time || 0, | ||
} | ||
|
||
timer = new Timer() | ||
|
||
update = (time) => { | ||
this.setState({ ...this.state, time }) | ||
} | ||
} | ||
|
||
export default withStyles(styles)(StopWatch) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import React from 'react' | ||
import { shallow } from 'enzyme' | ||
import { MANUAL } from '../../../services/analytics/MaxHeartRateCalculators/' | ||
import { DEFAULT_ZONE_COEFFICIENTS } from '../sensor/' | ||
|
||
jest.mock('../../../services/timer/', () => { | ||
return function() { | ||
this.on = jest.fn() | ||
this.start = jest.fn() | ||
this.stop = jest.fn() | ||
this.reset = jest.fn() | ||
this.value = 61 | ||
} | ||
}) | ||
|
||
import StopWatch from './' | ||
|
||
describe('Timer', () => { | ||
let subject | ||
let instance | ||
|
||
const defaultProps = { | ||
time: 61, | ||
sensor: { | ||
method: MANUAL, | ||
rate: 50, | ||
max: 100, | ||
coefficients: [].concat(DEFAULT_ZONE_COEFFICIENTS), | ||
}, | ||
} | ||
|
||
const setup = () => { | ||
subject = shallow(<StopWatch {...defaultProps} />) | ||
instance = subject.dive().instance() | ||
} | ||
|
||
beforeAll(() => { | ||
setup() | ||
}) | ||
|
||
describe('component did mount', () => { | ||
it('registers timer.on tick', () => { | ||
const instance = subject.dive().instance() | ||
expect(instance.timer.on).toBeCalledWith('tick', instance.update) | ||
}) | ||
it('starts the timer', () => { | ||
expect(instance.timer.start).toBeCalled() | ||
}) | ||
}) | ||
|
||
describe('component will unmount', () => { | ||
beforeEach(() => { | ||
instance.componentWillUnmount() | ||
}) | ||
|
||
it('stops the timer', () => { | ||
expect(instance.timer.stop).toBeCalled() | ||
}) | ||
}) | ||
|
||
describe('component did update', () => { | ||
describe('same zone', () => { | ||
beforeEach(() => { | ||
instance.componentDidUpdate(defaultProps) | ||
}) | ||
it('does not reset the timer', () => { | ||
expect(instance.timer.reset).not.toBeCalled() | ||
}) | ||
}) | ||
|
||
describe('different zone', () => { | ||
beforeEach(() => { | ||
const nextProps = { | ||
sensor: { ...defaultProps.sensor, rate: 70 }, | ||
} | ||
instance.props = nextProps | ||
instance.componentDidUpdate(defaultProps) | ||
}) | ||
it('resets the timer', () => { | ||
expect(instance.timer.reset).toBeCalled() | ||
}) | ||
}) | ||
}) | ||
|
||
describe('render', () => { | ||
it('renders a div with formatted time', () => { | ||
expect(subject.dive().text()).toMatch(/01:01/) | ||
}) | ||
}) | ||
|
||
describe('update', () => { | ||
beforeEach(() => { | ||
instance.update(71) | ||
}) | ||
it('updates state.time', () => { | ||
expect(instance.state.time).toEqual(71) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import events from 'events' | ||
import NanoTimer from 'nanotimer' | ||
|
||
const HOUR_MINUTES = 60 | ||
const MINUTE_SECONDS = 60 | ||
export const MAX_DURATION = HOUR_MINUTES * MINUTE_SECONDS | ||
|
||
export default class Timer extends events.EventEmitter { | ||
timer = new NanoTimer() | ||
|
||
value = 0 | ||
|
||
reset = () => { | ||
this.value = 0 | ||
this.emit('tick', this.value) | ||
} | ||
|
||
tick = () => { | ||
this.value++ | ||
|
||
if (this.value > MAX_DURATION) { | ||
this.stop() | ||
} else { | ||
this.emit('tick', this.value) | ||
} | ||
} | ||
|
||
start = () => { | ||
this.timer.setInterval(this.tick, '', '1s') | ||
} | ||
|
||
stop = () => { | ||
this.timer.clearInterval() | ||
this.value = 0 | ||
this.emit('tick', this.value) | ||
this.removeAllListeners() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import Timer from './' | ||
import lolex from 'lolex' | ||
import { MAX_DURATION } from './' | ||
|
||
describe('timer', () => { | ||
let timer | ||
let clock | ||
const setup = () => { | ||
clock = lolex.install() | ||
timer = new Timer() | ||
|
||
jest.spyOn(timer, 'removeAllListeners') | ||
jest.spyOn(timer, 'emit') | ||
jest.spyOn(timer.timer, 'setInterval') | ||
jest.spyOn(timer.timer, 'clearInterval').mockImplementation(jest.fn()) | ||
timer.start() | ||
clock.tick(1000) | ||
} | ||
|
||
const teardown = () => { | ||
clock = clock.uninstall() | ||
timer.removeAllListeners.mockRestore() | ||
timer.emit.mockRestore() | ||
timer.stop() | ||
} | ||
|
||
describe('start', () => { | ||
beforeEach(() => { | ||
setup() | ||
}) | ||
afterEach(teardown) | ||
it('has an interval', () => { | ||
expect(timer.timer.setInterval).toBeCalledWith(timer.tick, '', '1s') | ||
}) | ||
}) | ||
|
||
describe('reset', () => { | ||
beforeEach(setup) | ||
afterEach(teardown) | ||
it('value is 0', () => { | ||
expect(timer.value).toEqual(1) | ||
timer.reset() | ||
expect(timer.value).toEqual(0) | ||
}) | ||
}) | ||
|
||
describe('stop', () => { | ||
beforeEach(() => { | ||
setup() | ||
jest.spyOn(timer.timer, 'clearInterval') | ||
timer.stop() | ||
}) | ||
afterEach(teardown) | ||
|
||
it('clears the interval', () => { | ||
expect(timer.timer.clearInterval).toBeCalled() | ||
}) | ||
|
||
it('value is 0', () => { | ||
expect(timer.value).toEqual(0) | ||
}) | ||
|
||
it('remmoves listeners', () => { | ||
expect(timer.removeAllListeners).toBeCalled() | ||
}) | ||
|
||
it('emits value of 0', () => { | ||
expect(timer.emit).toBeCalledWith('tick', 0) | ||
}) | ||
}) | ||
|
||
describe('tick', () => { | ||
describe('valid', () => { | ||
beforeEach(setup) | ||
afterEach(teardown) | ||
it('emits tick', () => { | ||
expect(timer.emit).toBeCalledWith('tick', 1) | ||
}) | ||
}) | ||
|
||
describe('running for more than an hour', () => { | ||
beforeAll(() => { | ||
setup() | ||
jest.spyOn(timer, 'stop') | ||
clock.tick(MAX_DURATION * 1000) | ||
}) | ||
|
||
it('calls stop when the timer has run longer than allowed', () => { | ||
expect(timer.stop).toBeCalled() | ||
}) | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.