From cfbdf7c671a986867eefb770c6a22b5083df3ae2 Mon Sep 17 00:00:00 2001 From: Thomas Roch Date: Mon, 30 Nov 2015 19:56:57 +0000 Subject: [PATCH] feat: add ability to resume and change delay of a timer BREAKING CHANGE: tick, delay, stops are now properties of a timer prop --- modules/timer.js | 37 ++++++++++++++++++++++++++---- modules/timer.test.js | 53 +++++++++++++++++++++++++++++++++---------- package.json | 4 +++- 3 files changed, 77 insertions(+), 17 deletions(-) diff --git a/modules/timer.js b/modules/timer.js index 0677270..4b3186d 100644 --- a/modules/timer.js +++ b/modules/timer.js @@ -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(); @@ -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 }); } }; diff --git a/modules/timer.test.js b/modules/timer.test.js index 9c1d955..468acbd 100644 --- a/modules/timer.test.js +++ b/modules/timer.test.js @@ -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'; @@ -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(); }); }); diff --git a/package.json b/package.json index 3c94ade..796acdc 100644 --- a/package.json +++ b/package.json @@ -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",