目标
如何测试倒计时
实现
倒计时组件
- 剩余时间展示格式为:x天HH:mm:ss
- 超过时间,将文案变成已结束
- 在时间范围之前,将文案变成未开始
- 支持data为空值
一个坑
我之前的想法是,直接拿时间的差值用moment这种算时间的把时间算出来,因为时间戳0是1970.01.01 00:00:00 。
然后结果是不对的,原因如下:
因为时间戳0是对于本初子午线,也就是0时区那里来说的,对于位处于东八区的我们来说,时间戳0是-8hour。
然后用
new Date(0)
得出来的结果就是,它去掉符号了,算出来是早上八点。这样我的倒计时结果就不对了。当然改进的方法就是将起始时间戳改成今天的0点,然后加上这个时间戳差值。但是我还是决定自己算一遍,为了代码的可读性。
const delta = endTime - now;
const nowDay = Math.floor(delta / (1000 * 60 * 60 * 24));
let remainTime: any = delta - nowDay * (1000 * 60 * 60 * 24);
const hour = Math.floor(remainTime / (1000 * 60 * 60));
remainTime %= 1000 * 60 * 60;
const minute = Math.floor(remainTime / (1000 * 60));
remainTime %= 1000 * 60;
const second = Math.floor(remainTime / 1000);
const nowTime = `${`0${hour}`.slice(-2)}:${`0${minute}`.slice(-2)}:${`0${second}`.slice(
-2,
)}`;
const time = (
<span>
{nowDay > 0 && `${nowDay}天`}
{`${nowTime}`}
</span>
);
代码
import React, { ReactNode, useEffect, useMemo, useState } from 'react';
import { NewShopTaskNoviceGiftGroupVO } from 'src/service/NewShopGiftPack/types';
import ContentWrap from '../ContentWrap';
import $style from './style.less';
interface PropsType {
children?: ReactNode;
data: NewShopTaskNoviceGiftGroupVO;
}
export default function (props: PropsType) {
const { children, data } = props;
const { statusDesc, title, status , startTime, endTime} = data || {};
const [now, setNow] = useState(new Date().getTime());
const extra = useMemo(() => {
let res: ReactNode = statusDesc;
if (status === 'RUNNING') {
if (startTime > now) {
res = '未开始';
} else if (endTime > now && startTime < now) {
const delta = endTime - now;
const nowDay = Math.floor(delta / (1000 * 60 * 60 * 24));
let remainTime: any = delta - nowDay * (1000 * 60 * 60 * 24);
const hour = Math.floor(remainTime / (1000 * 60 * 60));
remainTime %= 1000 * 60 * 60;
const minute = Math.floor(remainTime / (1000 * 60));
remainTime %= 1000 * 60;
const second = Math.floor(remainTime / 1000);
const nowTime = `${`0${hour}`.slice(-2)}:${`0${minute}`.slice(-2)}:${`0${second}`.slice(
-2,
)}`;
const time = (
<span>
{nowDay > 0 && `${nowDay}天`}
{`${nowTime}`}
</span>
);
res = (
<div className={$style.countDown}>
{`${statusDesc},距结束`}
{time}
</div>
);
} else if (endTime < now) {
res = '已结束';
}
}
return res;
}, [status, statusDesc, endTime, startTime, now]);
useEffect(() => {
const clock = setTimeout(() => {
if (endTime > now && startTime < now) {
setNow(new Date().getTime());
}
}, 100);
return () => clearTimeout(clock);
}, [endTime, startTime, now]);
return (
<div className={$style.giftItem}>
<ContentWrap title={title} extra={extra}>
{children}
</ContentWrap>
</div>
);
}
单测
- 使用MockDate设置时间,这个时间是不会变动的,然后计算应该有的结果和组件render后的结果对比就行;
- 测试在时间内,在时间前,在时间后,和边界情况00:00:00
单测如下:
import React from 'react';
import MockDate from 'mockdate';
import { render, mount } from 'enzyme';
import GiftItem from '../../../components/GiftItem/index.tsx';
describe('NewShopGiftPack/components/GiftItem', () => {
it('should be able to set undefined or null', () => {
expect(() => {
const wrapper = mount(<GiftItem />);
wrapper.setProps({ data: undefined });
}).not.toThrow();
expect(() => {
const wrapper = mount(<GiftItem />);
wrapper.setProps({ data: undefined });
}).not.toThrow();
expect(() => {
mount(<GiftItem />);
}).not.toThrow();
});
const mockData = {
statusDesc: '进行中',
title: '',
status: 'RUNNING',
endTime: 1657883990000,
startTime: 1657728000000,
};
it('count-down case: status RUNNING and during time range ', () => {
const fakeDay = new Date(
mockData.endTime - 1000 * 60 * 60 * 24 - 1000 - 60 * 1000 - 60 * 60 * 1000,
).getTime();
MockDate.set(fakeDay);
const wrapper = render(<GiftItem data={mockData} />);
expect(wrapper.find('.countDown').length).toBe(1); // show count-down
const currentRemainTime = wrapper.find('.countDown span').first().text(); // x天HH:mm:ss
expect(currentRemainTime).toBe('1天01:01:01');
MockDate.reset();
});
it('count-down case: status RUNNING and remain 1 ms ending', () => {
MockDate.set(mockData.endTime - 1);
const wrapper = render(<GiftItem data={mockData} />);
expect(wrapper.find('.countDown').length).toBe(1); // show count-down
const currentRemainTime = wrapper.find('.countDown span').first().text(); // x天HH:mm:ss
expect(currentRemainTime).toBe('00:00:00');
MockDate.reset();
});
it('count-down case: status RUNNING and before the time range', () => {
MockDate.set(mockData.startTime - 1);
const wrapper = render(<GiftItem data={mockData} />);
expect(wrapper.find('.countDown').length).toBe(0); // countDown should disappear
expect(wrapper.find('.extra').first().text()).toBe('未开始'); // when current date is out of the time range, text there should be rewrite with ignoring the RUNNING status
MockDate.reset();
});
it('count-down case: status RUNNING and behind the time range', () => {
MockDate.set(mockData.endTime + 1);
const wrapper = render(<GiftItem data={mockData} />);
expect(wrapper.find('.countDown').length).toBe(0); // countDown should disappear
expect(wrapper.find('.extra').first().text()).toBe('已结束'); // when current date is out of the time range, text there should be rewrite with ignoring the RUNNING status
MockDate.reset();
});
});