Skip to content

Commit

Permalink
feat: add ability to resume and change delay of a timer
Browse files Browse the repository at this point in the history
BREAKING CHANGE: tick, delay, stops are now properties of a timer prop
  • Loading branch information
troch committed Nov 30, 2015
1 parent 49f981e commit cfbdf7c
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 17 deletions.
37 changes: 33 additions & 4 deletions modules/timer.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,61 @@
import React from 'react';
import invariant from 'invariant';

function timer(delay) {
function checkDelay(delay) {
invariant(
typeof delay === 'number' && delay > 0,
'[react-timer-hoc] `delay` should be a number greater than 0.'
);
}

function timer(delay) {
checkDelay(delay);

return function TimerHoc(TimedComponent) {
class Timer extends React.Component {
constructor(props) {
super(props);
this.delay = delay;
this.state = { tick: 0 };

this.setTimeout = ::this.setTimeout;
this.stop = ::this.stop;
this.resume = ::this.resume;
this.setDelay = ::this.setDelay;
}

setTimeout() {
const duration = delay - (this.startTime - Date.now()) % delay;
const { delay, startTime } = this;
const duration = delay - (startTime - Date.now()) % delay;

this.timer = setTimeout(() => {
this.setState({ tick: this.state.tick + 1 });
if (!this.stopped) this.setTimeout();
}, delay);
}

resume() {
if (this.stopped) {
this.stopped = false;
this.startTime = Date.now();
this.setTimeout();
}
}

stop() {
this.stopped = true;
clearTimeout(this.timer);
}

setDelay(delay) {
checkDelay(delay);
this.delay = delay;
if (!this.stopped) {
this.stop();
this.resume();
}
}

componentDidMount() {
this.stopped = false;
this.startTime = Date.now();
Expand All @@ -40,10 +67,12 @@ function timer(delay) {
}

render() {
const { props, stop } = this;
const { props, stop, resume, setDelay } = this;
const { tick } = this.state;

return React.createElement(TimedComponent, { ...props, tick, delay, stop });
const timer = { delay, tick, stop, resume, setDelay };

return React.createElement(TimedComponent, { ...props, timer });
}
};

Expand Down
53 changes: 41 additions & 12 deletions modules/timer.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createRenderer, renderIntoDocument, findRenderedComponentWithType } from 'react-addons-test-utils';
import { expect } from 'chai';
import sinon from 'sinon';
import React, { Component } from 'react';
import h from 'react-hyperscript';
import timer from './timer';
Expand All @@ -11,27 +12,55 @@ const win = doc.defaultView;
global.document = doc;
global.window = win;

class Counter extends Component {
render() {
const { tick } = this.props;
return h('div', { tick });
}
}

describe('Timer', function() {
it('should run', function() {
class Counter extends Component {
render() {
const { tick } = this.props;
return h('div', { tick });
}
}
let clock, wrappedCounter, counter;

before(() => clock = sinon.useFakeTimers());
after(() => clock.restore());

it('should pass down a timer property alongside other props', function() {
const WrappedCounter = timer(1000)(Counter);
expect(WrappedCounter.displayName).to.equal('Timer@1000[Counter]');

const wrappedCounter = renderIntoDocument(h(WrappedCounter, { customProp: 1 }));
const counter = findRenderedComponentWithType(wrappedCounter, Counter);
expect(counter.props.tick).to.equal(0);
expect(counter.props.delay).to.equal(1000);
expect(counter.props.stop).to.be.a.function;

counter = findRenderedComponentWithType(wrappedCounter, Counter);

expect(counter.props.timer.tick).to.equal(0);
expect(counter.props.timer.delay).to.equal(1000);
expect(counter.props.timer.stop).to.be.a.function;
expect(counter.props.timer.setDelay).to.be.a.function;
expect(counter.props.customProp).to.equal(1);
});

it('should increment a tick property', function() {
clock.tick(1100);
expect(counter.props.timer.tick).to.equal(1);
clock.tick(1000);
expect(counter.props.timer.tick).to.equal(2);
});

it('should have the ability to be stopped and resumed', function() {
expect(wrappedCounter.stopped).to.be.false;
counter.props.stop();
counter.props.timer.stop();
expect(wrappedCounter.stopped).to.be.true;
counter.props.timer.resume();
expect(wrappedCounter.stopped).to.be.false;
});

it('shoud give the ability to change its delay', function() {
counter.props.timer.setDelay(60000);
expect(wrappedCounter.delay).to.be.equal(60000);

clock.tick(60100);
expect(counter.props.timer.tick).to.equal(3);
counter.props.timer.stop();
});
});
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
"jsdom": "^7.1.0",
"mocha": "^2.3.4",
"react-addons-test-utils": "^0.14.3",
"react-hyperscript": "^2.2.0"
"react-hyperscript": "^2.2.0",
"sinon": "^1.17.2",
"sinon-chai": "^2.8.0"
},
"dependencies": {
"invariant": "^2.2.0",
Expand Down

0 comments on commit cfbdf7c

Please sign in to comment.