diff --git a/lib/DatePicker.js b/lib/DatePicker.js
index 1246cc0..1690ede 100644
--- a/lib/DatePicker.js
+++ b/lib/DatePicker.js
@@ -1,7 +1,7 @@
/**
* @module DatePicker组件
*/
-import './index.css';
+
import React, { Component, PropTypes } from 'react';
import { nextTime, getTimeName } from './time.js';
import {
@@ -26,9 +26,9 @@ class DatePicker extends Component {
};
});
- this.animating = false;
- this.touchY = 0;
- this.angle = 0;
+ this.animating = false; // 判断是否在transition过渡动画之中
+ this.touchY = 0; // 保存touchstart的pageY
+ this.angle = 0; // 容器转过的角度
this.state = {
angle: 0,
dates,
@@ -41,16 +41,31 @@ class DatePicker extends Component {
this._moveToNext = this._moveToNext.bind(this);
}
+ /**
+ * 根据角度返回透明度(0-1之间)
+ * @param {number} angle 角度
+ * @return
+ */
_setOpacity(angle) {
return angle > 0
? ((40 - angle) / 40 * 100 | 0) / 100
: ((40 + angle) / 40 * 100 | 0) / 100;
}
+ /**
+ * 清除对象的transition样式
+ * @param {Dom} obj 指定的对象
+ * @return {undefined}
+ */
_clearTransition(obj) {
obj.style[TRANSITION] = ''; // eslint-disable-line
}
+ /**
+ * 滑动到下一日期
+ * @param {number} direction 滑动方向
+ * @return {undefined}
+ */
_moveToNext(direction) {
const scroll = this.refs.scroll;
const angle = this.angle;
@@ -70,6 +85,12 @@ class DatePicker extends Component {
}
}
+ /**
+ * 添加滑动动画
+ * @param {DOM} obj DOM对象
+ * @param {number} angle 角度
+ * @return {undefined}
+ */
_moveTo(obj, angle) {
this.animating = true;
obj.style[TRANSITION] = `${TRANSFORM_CSS} .2s ease-out`; // eslint-disable-line
@@ -78,11 +99,20 @@ class DatePicker extends Component {
});
}
+ /**
+ * 点击完成按钮事件
+ * @return {undefined}
+ */
handleFinishBtnClick() {
const date = this.state.dates.find(value => value.angle + this.state.angle === 0);
this.props.onSelect(date.value);
}
+ /**
+ * 滑动日期选择器事件
+ * @param {Object} event 事件对象
+ * @return {undefined}
+ */
handleContentTouch(event) {
event.preventDefault();
if (this.animating) return;
@@ -107,6 +137,10 @@ class DatePicker extends Component {
}
}
+ /**
+ * transition过渡完成事件
+ * @return {undefined}
+ */
handleContentTransitionEnd() {
const { dates, angle } = this.state;
const date = dates.find(value => value.angle + angle === 0);
@@ -126,6 +160,11 @@ class DatePicker extends Component {
});
}
+ /**
+ * 渲染一个日期DOM对象
+ * @param {Object} date date数据
+ * @return {Object} JSX对象
+ */
renderDatepickerItem(date) {
const itemStyle = {
[TRANSFORM]: `rotateX(${date.angle}deg) translate3d(0,0,100px)`,
@@ -141,6 +180,10 @@ class DatePicker extends Component {
);
}
+ /**
+ * render函数
+ * @return {Object} JSX对象
+ */
render() {
const { layerBackground, btnColor } = this.props;
const scrollStyle = {
diff --git a/lib/index.js b/lib/index.js
index 627f870..4271eb3 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -1,2 +1,3 @@
+import './index.css';
import DatePicker from './DatePicker.js';
export default DatePicker;
diff --git a/lib/time.js b/lib/time.js
index 6fb2349..9758686 100644
--- a/lib/time.js
+++ b/lib/time.js
@@ -1,3 +1,7 @@
+/**
+ * @module time工具
+ */
+
function convertDate(timestamp, formate) {
const date = new Date(timestamp);
const year = date.getFullYear();
@@ -16,7 +20,11 @@ function convertDate(timestamp, formate) {
.replace(/s+/, second);
}
-
+/**
+ * 获取相对日期的偏移日期
+ * @param {Date} 日期
+ * @return {number} 相对的天数
+ */
export function nextTime(now = new Date(), index = 1) {
if (Object.prototype.toString.call(now, null) !== '[object Date]') {
throw new Error('参数类型不对');
@@ -27,6 +35,11 @@ export function nextTime(now = new Date(), index = 1) {
return date;
}
+/**
+ * 获取指定日期的格式化日期名称
+ * @param {Date} 日期
+ * @return {String} 格式化日期名称
+ */
export function getTimeName(now) {
if (Object.prototype.toString.call(now, null) !== '[object Date]') {
throw new Error('参数类型不对');
diff --git a/lib/transition.js b/lib/transition.js
index b99da5f..8fb876f 100644
--- a/lib/transition.js
+++ b/lib/transition.js
@@ -6,7 +6,7 @@ if (typeof document.body.style.transition === 'string') {
TRANSITIONEND = 'transitionend';
TRANSITION_CSS = 'transition';
} else if (typeof document.body.style.webkitTransition === 'string') {
- TRANSITION = 'webkitTransition';
+ TRANSITION = 'WebkitTransition';
TRANSITION_CSS = '-webkit-transition';
TRANSITIONEND = 'webkitTransitionEnd';
}
@@ -17,7 +17,7 @@ if (typeof document.body.style.transform === 'string') {
TRANSFORM = 'transform';
TRANSFORM_CSS = 'transform';
} else if (typeof document.body.style.webkitTransform === 'string') {
- TRANSFORM = 'webkitTransform';
+ TRANSFORM = 'WebkitTransform';
TRANSFORM_CSS = '-webkit-transform';
}
diff --git a/package.json b/package.json
index fc60eff..2d037b0 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
"scripts": {
"build": "webpack",
"start": "webpack-dev-server",
- "release": "scripts/release",
+ "release": "npm install build;scripts/release",
"preview-release": "scripts/preview-release",
"mocha": "./node_modules/.bin/mocha --compilers js:babel-core/register --require ./test/helper.js --recursive",
"test": "./node_modules/.bin/nyc npm run mocha",
@@ -20,6 +20,7 @@
"babel-core": "^6.9.0",
"babel-eslint": "^6.0.4",
"babel-loader": "^6.2.4",
+ "babel-polyfill": "^6.9.1",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-0": "^6.5.0",
@@ -27,6 +28,7 @@
"coveralls": "^2.11.9",
"css-loader": "^0.23.1",
"cssnext": "^1.8.4",
+ "enzyme": "^2.3.0",
"eslint": "^2.10.2",
"eslint-config-airbnb": "9.0.1",
"eslint-plugin-import": "^1.8.0",
@@ -41,7 +43,9 @@
"postcss-cssnext": "^2.5.2",
"postcss-loader": "^0.9.1",
"postcss-nested": "^1.0.0",
+ "react-addons-test-utils": "^15.1.0",
"rf-release": "^0.4.0",
+ "sinon": "^1.17.4",
"style-loader": "^0.13.1",
"webpack": "^1.13.1",
"webpack-dev-server": "^1.14.1",
@@ -52,4 +56,4 @@
"react-dom": "^15.1.0"
},
"license": "ISC"
-}
\ No newline at end of file
+}
diff --git a/test/functional/DatePicker_spec.js b/test/functional/DatePicker_spec.js
new file mode 100644
index 0000000..9a5e885
--- /dev/null
+++ b/test/functional/DatePicker_spec.js
@@ -0,0 +1,177 @@
+
+import React from 'react';
+import { assert, expect } from 'chai';
+import sinon from 'sinon';
+import { mount, shallow } from 'enzyme';
+import DatePicker from '../../lib/DatePicker';
+import {nextTime} from '../../lib/time';
+
+describe('时间选择器组件DatePicker', () => {
+ describe('测试初始化的过程:', () => {
+ it('测试传入_setOpacity的参数', () => {
+ const spyFunction = sinon.spy(DatePicker.prototype, '_setOpacity');
+ const datePicker = mount(
+
+ );
+
+ const list = [
+ spyFunction.getCall(0).args[0],
+ spyFunction.getCall(1).args[0],
+ spyFunction.getCall(2).args[0],
+ spyFunction.getCall(3).args[0],
+ spyFunction.getCall(4).args[0],
+ ];
+
+ const expectList = [45, 22.5, 0, -22.5, -45];
+ expect(list).to.deep.equals(expectList);
+ spyFunction.restore();
+ })
+ })
+
+
+ describe('测试方法:', () => {
+ it('_setOpacity应该返回相应的值,当传入不同的临界值', () => {
+ const datePicker = mount(
+
+ );
+ const inst = datePicker.instance();
+
+ const test1 = [0, 1];
+ const test2 = [40, 0];
+ const test3 = [-40, 0];
+
+ expect(inst._setOpacity(test1[0])).to.equals(test1[1]);
+ expect(inst._setOpacity(test2[0])).to.equals(test2[1]);
+ expect(inst._setOpacity(test3[0])).to.equals(test3[1]);
+ })
+
+ it('应该滑动到上一个日期,当调用_moveToNext方法', () => {
+ const spyFunction = sinon.spy();
+ const datePicker = mount(
+
+ );
+ datePicker.instance()._moveToNext(-1);
+ datePicker.find('.datepicker-finish-btn').simulate('click');
+ sinon.assert.calledWith(spyFunction, nextTime(new Date(), -1));
+ })
+
+
+ it('应该向_moveToNext传入-1, 当滑动到上一个日期', () => {
+ const spyFunction = sinon.spy(DatePicker.prototype, '_moveToNext');
+ const datePicker = mount(
+
+ );
+ const touchstartEvent = {
+ type: 'touchstart',
+ targetTouches: [{ pageY: 0 }],
+ };
+ const touchendEvent = {
+ type: 'touchend',
+ changedTouches: [{ pageY: 50 }],
+ };
+
+
+ datePicker.find('.datepicker-content').simulate('touchStart', touchstartEvent);
+ datePicker.find('.datepicker-content').simulate('touchEnd', touchendEvent);
+ sinon.assert.calledWith(spyFunction, -1);
+ spyFunction.restore();
+ })
+
+ })
+
+ describe('测试交互事件:', () => {
+ it('应该触发handleContent3次, 在触摸屏幕之后', () => {
+ const spyFunction = sinon.spy(DatePicker.prototype, 'handleContentTouch');
+ const datePicker = mount(
+
+ );
+
+ datePicker.find('.datepicker-content').simulate('touchStart');
+ datePicker.find('.datepicker-content').simulate('touchMove');
+ datePicker.find('.datepicker-content').simulate('touchEnd');
+ sinon.assert.callCount(spyFunction, 3);
+ spyFunction.restore();
+ })
+
+ it('应该触发onSelect, 在点击完成按钮之后', () => {
+ const spyFunction = sinon.spy();
+ const datePicker = mount(
+
+ );
+ datePicker.find('.datepicker-finish-btn').simulate('click');
+ sinon.assert.calledOnce(spyFunction);
+ })
+
+
+ it('应该向onSelect传入上一个日期, 当向下滑动超过touchLen', () => {
+ const spyFunction = sinon.spy();
+ const datePicker = mount(
+
+ );
+ const touchstartEvent = {
+ type: 'touchstart',
+ targetTouches: [{ pageY: 0 }],
+ };
+ const touchendEvent = {
+ type: 'touchend',
+ changedTouches: [{ pageY: 50 }],
+ };
+
+ datePicker.find('.datepicker-content').simulate('touchStart', touchstartEvent);
+ datePicker.find('.datepicker-content').simulate('touchEnd', touchendEvent);
+ datePicker.find('.datepicker-content').simulate('transitionEnd');
+ datePicker.find('.datepicker-finish-btn').simulate('click');
+ sinon.assert.calledWith(spyFunction, nextTime(new Date(), -1));
+ })
+
+ it('应该向onSelect传入下一个日期, 当向上滑动超过touchLen', () => {
+ const spyFunction = sinon.spy();
+ const datePicker = mount(
+
+ );
+ const touchstartEvent = {
+ type: 'touchstart',
+ targetTouches: [{ pageY: 0 }],
+ };
+ const touchendEvent = {
+ type: 'touchend',
+ changedTouches: [{ pageY: -50 }],
+ };
+
+ datePicker.find('.datepicker-content').simulate('touchStart', touchstartEvent);
+ datePicker.find('.datepicker-content').simulate('touchEnd', touchendEvent);
+ datePicker.find('.datepicker-content').simulate('transitionEnd');
+ datePicker.find('.datepicker-finish-btn').simulate('click');
+ sinon.assert.calledWith(spyFunction, nextTime(new Date(), 1));
+ })
+
+ it('应该恢复当前日期, 但滑动超过touchLen但上一个日期小于minDate', () => {
+ const spyFunction = sinon.spy();
+ const datePicker = mount(
+
+ );
+ const touchstartEvent = {
+ type: 'touchstart',
+ targetTouches: [{ pageY: 0 }],
+ };
+ const touchendEvent = {
+ type: 'touchend',
+ changedTouches: [{ pageY: 50 }],
+ };
+
+ datePicker.find('.datepicker-content').simulate('touchStart', touchstartEvent);
+ datePicker.find('.datepicker-content').simulate('touchEnd', touchendEvent);
+ datePicker.find('.datepicker-content').simulate('transitionEnd');
+ datePicker.find('.datepicker-finish-btn').simulate('click');
+ sinon.assert.calledWith(spyFunction, nextTime(new Date(), 0));
+ })
+ })
+
+});
diff --git a/test/functional/time_spec.js b/test/functional/time_spec.js
index 29f98af..ab333f7 100644
--- a/test/functional/time_spec.js
+++ b/test/functional/time_spec.js
@@ -1,47 +1,54 @@
-var chai = require('chai');
-var expect = chai.expect;
-import { nextTime, getTimeName } from '../../lib/time.js';
-
-
-describe('nextTime函数', () => {
- it('参数类型不对', function () {
- const now = 1466654887871;
- expect(function(){ nextTime(1466654887871) }).to.throw('参数类型不对');
- });
- it("参数2015.5.31应该返回2016.6.1", function() {
- const now = new Date(2016, 4, 31);
- expect(nextTime(now).getTime()).to.equal(new Date(2016, 5, 1).getTime());
- });
- it("参数2015.5.31应该返回2016.6.1", function() {
- const now = new Date(2016, 4, 31);
- expect(nextTime(now, 1).getTime()).to.equal(new Date(2016, 5, 1).getTime());
- });
+import { expect } from 'chai';
+import { nextTime, getTimeName } from '../../lib/time.js';
- it("参数2015.6.1应该返回2016.5.31", function() {
- const now = new Date(2016, 5, 1);
- expect(nextTime(now, -1).getTime()).to.equal(new Date(2016, 4, 31).getTime());
+describe('time工具', () => {
+
+ describe('nextTime函数', () => {
+ it('参数类型不对', function () {
+ const now = 1466654887871;
+ expect(function(){ nextTime(1466654887871) }).to.throw('参数类型不对');
+ });
+
+ it('默认参数的情况下', () => {
+ expect(nextTime().getTime()).to.equal(nextTime(new Date(), 1).getTime());
+ });
+
+ it("参数2015.5.31应该返回2016.6.1", function() {
+ const now = new Date(2016, 4, 31);
+ expect(nextTime(now).getTime()).to.equal(new Date(2016, 5, 1).getTime());
+ });
+ it("参数2015.5.31应该返回2016.6.1", function() {
+ const now = new Date(2016, 4, 31);
+ expect(nextTime(now, 1).getTime()).to.equal(new Date(2016, 5, 1).getTime());
+ });
+
+ it("参数2015.6.1应该返回2016.5.31", function() {
+ const now = new Date(2016, 5, 1);
+ expect(nextTime(now, -1).getTime()).to.equal(new Date(2016, 4, 31).getTime());
+ });
});
-});
-describe('getTimeName函数', () => {
- it('参数类型不对', function () {
- const now = 1466654887871;
- expect(function(){ getTimeName(1466654887871) }).to.throw('参数类型不对');
+ describe('getTimeName函数', () => {
+ it('参数类型不对', function () {
+ const now = 1466654887871;
+ expect(function(){ getTimeName(1466654887871) }).to.throw('参数类型不对');
+ });
+
+ it('应该返回昨天', () => {
+ const data = nextTime(new Date(), -1);
+ expect(getTimeName(data)).to.equal('昨天');
+ })
+
+ it('应该返回今天', () => {
+ const data = new Date();
+ expect(getTimeName(data)).to.equal('今天');
+ })
+
+ it('应该返回2016-5-10', () => {
+ const data = new Date(2016, 4, 10);
+ expect(getTimeName(data)).to.equal('2016-5-10');
+ })
});
- it('应该返回昨天', () => {
- const data = nextTime(new Date(), -1);
- expect(getTimeName(data)).to.equal('昨天');
- })
-
- it('应该返回今天', () => {
- const data = new Date();
- expect(getTimeName(data)).to.equal('今天');
- })
-
- it('应该返回2016-5-10', () => {
- const data = new Date(2016, 4, 10);
- expect(getTimeName(data)).to.equal('2016-5-10');
- })
-});
+})
diff --git a/test/helper.js b/test/helper.js
index 8b13789..93eecba 100644
--- a/test/helper.js
+++ b/test/helper.js
@@ -1 +1,16 @@
+require('babel-polyfill');
+var jsdom = require('jsdom').jsdom;
+var exposedProperties = ['window', 'navigator', 'document'];
+global.document = jsdom('');
+global.window = document.defaultView;
+Object.keys(document.defaultView).forEach((property) => {
+ if (typeof global[property] === 'undefined') {
+ exposedProperties.push(property);
+ global[property] = document.defaultView[property];
+ }
+});
+
+global.navigator = {
+ userAgent: 'node.js'
+};