{
this.handleTimeChange(timeData);
}}
className={`${timeClass} ${colorPalette}`}
ref={this.checkTimeIsActive(timeData) ? this.dropDownActiveTime : null}
>
{time}
{meridiem ? {meridiem} : null}
);
});
}
render() {
const { timeMode, timeConfig = {} } = this.props;
const timeDatas = timeMode === 12
? timeHelper.get12ModeTimes(timeConfig)
: timeHelper.get24ModeTimes(timeConfig);
return (
(this.childNode = c)} className={outsideClass}>
{this.props.children}
);
}
}
OutsideClickHandler.propTypes = propTypes;
OutsideClickHandler.defaultProps = defaultProps;
export default OutsideClickHandler;
================================================
FILE: src/components/Picker/PickerDragHandler.jsx
================================================
import React from 'react';
import PropTypes from 'prop-types';
import {
PICKER_RADIUS,
POINTER_RADIUS,
MAX_ABSOLUTE_POSITION,
MIN_ABSOLUTE_POSITION,
} from '../../utils/constant.js';
import darg from '../../utils/drag';
const propTypes = {
time: PropTypes.number,
step: PropTypes.number,
draggable: PropTypes.bool,
pointerRotate: PropTypes.number,
minLength: PropTypes.number,
maxLength: PropTypes.number,
minuteStep: PropTypes.number,
limitDrag: PropTypes.bool,
rotateState: PropTypes.shape({
top: PropTypes.number,
height: PropTypes.number,
pointerRotate: PropTypes.number
}),
handleTimePointerClick: PropTypes.func
};
const defaultProps = {
time: 0,
step: 0,
pointerRotate: 0,
rotateState: {
top: 0,
height: 0,
pointerRotate: 0
},
minLength: MIN_ABSOLUTE_POSITION,
maxLength: MAX_ABSOLUTE_POSITION,
minuteStep: 5,
limitDrag: false,
handleTimePointerClick: Function.prototype
};
class PickerDragHandler extends React.PureComponent {
constructor(props) {
super(props);
this.startX = 0;
this.startY = 0;
this.originX = null;
this.originY = null;
this.dragCenterX = null;
this.dragCenterY = null;
this.offsetDragCenterX = 0;
this.offsetDragCenterY = 0;
this.state = this.initialRotationAndLength();
this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleMouseUp = this.handleMouseUp.bind(this);
this.resetOrigin = this.resetOrigin.bind(this);
}
componentDidMount() {
this.resetOrigin();
if (window.addEventListener) {
window.addEventListener('resize', this.resetOrigin, true);
} else {
window.addEventListener('onresize', this.resetOrigin);
}
if (document.addEventListener) {
document.addEventListener('scroll', this.resetOrigin, true);
document.addEventListener('mousemove', this.handleMouseMove, true);
document.addEventListener('mouseup', this.handleMouseUp, true);
document.addEventListener('touchmove', this.handleMouseMove, true);
document.addEventListener('touchend', this.handleMouseUp, true);
} else {
document.addEventListener('onscroll', this.resetOrigin);
document.attachEvent('onmousemove', this.handleMouseMove);
document.attachEvent('onmouseup', this.handleMouseUp);
document.attachEvent('ontouchmove', this.handleMouseMove);
document.attachEvent('ontouchend', this.handleMouseUp);
}
}
componentWillUnmount() {
if (window.addEventListener) {
window.removeEventListener('resize', this.resetOrigin, true);
} else {
window.detachEvent('onresize', this.resetOrigin);
}
if (document.removeEventListener) {
document.removeEventListener('scroll', this.resetOrigin, true);
document.removeEventListener('mousemove', this.handleMouseMove, true);
document.removeEventListener('mouseup', this.handleMouseUp, true);
document.removeEventListener('touchmove', this.handleMouseMove, true);
document.removeEventListener('touchend', this.handleMouseUp, true);
} else {
document.detachEvent('onscroll', this.resetOrigin);
document.detachEvent('onmousemove', this.handleMouseMove);
document.detachEvent('onmouseup', this.handleMouseUp);
document.detachEvent('ontouchmove', this.handleMouseMove);
document.detachEvent('ontouchend', this.handleMouseUp);
}
}
componentDidUpdate(prevProps) {
const { step, time, rotateState } = this.props;
const { draging } = this.state;
const prevStep = prevProps.step;
const prevTime = prevProps.time;
const PrevRotateState = prevProps.rotateState;
if ((step !== prevStep
|| time !== prevTime
|| rotateState.pointerRotate !== PrevRotateState.pointerRotate)
&& !draging) {
this.resetState();
}
}
initialRotationAndLength() {
const { rotateState } = this.props;
const {
top,
height,
pointerRotate
} = rotateState;
this.initialHeight = height;
return {
top,
height,
pointerRotate,
draging: false
};
}
resetState() {
this.setState(this.initialRotationAndLength());
}
resetOrigin() {
const centerPoint = this.pickerCenter;
const centerPointPos = centerPoint.getBoundingClientRect();
this.originX =
centerPointPos.left +
(centerPoint.clientWidth / 2) +
Math.max(document.documentElement.scrollLeft, document.body.scrollLeft) + POINTER_RADIUS;
this.originY =
centerPointPos.top +
(centerPoint.clientHeight / 2) +
Math.max(document.documentElement.scrollTop, document.body.scrollTop) + POINTER_RADIUS;
this.resetDragCenter();
}
resetDragCenter() {
this.offsetDragCenterX = 0;
this.offsetDragCenterY = 0;
const dragCenterPoint = this.dragCenter;
const dragCenterPointPos = dragCenterPoint.getBoundingClientRect();
this.dragCenterX =
dragCenterPointPos.left +
(dragCenterPoint.clientWidth / 2) +
Math.max(document.documentElement.scrollLeft, document.body.scrollLeft);
this.dragCenterY =
dragCenterPointPos.top +
(dragCenterPoint.clientHeight / 2) +
Math.max(document.documentElement.scrollTop, document.body.scrollTop);
}
getRadian(x, y) {
let sRad = Math.atan2(y - this.originY, x - this.originX);
sRad -= Math.atan2(
this.startY - this.originY,
this.startX - this.originX
);
if (sRad > Math.PI) {
sRad -= Math.PI * 2;
} else if (sRad < -Math.PI) {
sRad += Math.PI * 2;
}
sRad += darg.degree2Radian(this.props.rotateState.pointerRotate);
return sRad;
}
getAbsolutePosition(x, y) {
return Math.sqrt(
Math.pow((x - this.originX), 2) + Math.pow((y - this.originY), 2)
);
}
getPointerRotate(options = {}) {
const {
dragX,
dragY,
} = options;
const {
step,
limitDrag,
minuteStep,
} = this.props;
const sRad = this.getRadian(dragX, dragY);
let pointerRotate = sRad * (360 / (2 * Math.PI));
if (limitDrag) {
const degree = sRad * (360 / (2 * Math.PI));
const isHour = step === 0;
const sectionCount = isHour ? 12 : (60 / minuteStep);
const roundSeg = Math.round(degree / (360 / sectionCount));
pointerRotate = roundSeg * (360 / sectionCount);
}
return pointerRotate;
}
handleTimePointerChange(options = {}) {
const {
dragX,
dragY,
autoMode = null,
pointerRotate = null,
} = options;
const {
step,
timeMode,
minLength,
maxLength,
minuteStep,
handleTimePointerClick,
} = this.props;
const sRad = this.getRadian(dragX, dragY);
const degree = sRad * (360 / (2 * Math.PI));
const isHour = step === 0;
const sectionCount = isHour ? 12 : (60 / minuteStep);
let roundSeg = Math.round(degree / (360 / sectionCount));
let absolutePosition = this.getAbsolutePosition(dragX, dragY);
absolutePosition = darg.validatePosition(
absolutePosition,
minLength,
maxLength
);
if (minLength < absolutePosition && absolutePosition < maxLength) {
if ((absolutePosition - minLength) > (maxLength - minLength) / 2) {
absolutePosition = maxLength;
} else {
absolutePosition = minLength;
}
}
while (roundSeg > sectionCount) {
roundSeg -= sectionCount;
}
let time = absolutePosition === minLength
? roundSeg
: roundSeg + sectionCount;
if (isHour) {
if (absolutePosition === minLength && time < 0) {
time += 12;
} else if (absolutePosition !== minLength && time < 12) {
time = 24 + (time - 12);
}
time = time === 24 ? 12 : time;
if (time === 12 && Number(timeMode) === 12) time = 0;
} else {
time = (time * minuteStep === 60 ? 0 : time * minuteStep);
time = time < 0 ? 60 + time : time;
}
handleTimePointerClick && handleTimePointerClick({
time,
autoMode,
pointerRotate
});
}
handleMouseDown(e) {
if (!this.state.draging) {
const event = e || window.event;
event.preventDefault();
event.stopPropagation();
const pos = darg.mousePosition(event);
this.startX = pos.x;
this.startY = pos.y;
this.resetDragCenter();
this.offsetDragCenterX = this.dragCenterX - this.startX;
this.offsetDragCenterY = this.dragCenterY - this.startY;
this.setState({
draging: true
});
}
}
handleMouseMove(e) {
if (this.state.draging) {
const {
minLength,
maxLength,
} = this.props;
const pos = darg.mousePosition(e);
const dragX = pos.x + this.offsetDragCenterX;
const dragY = pos.y + this.offsetDragCenterY;
if (this.originX !== dragX && this.originY !== dragY) {
const pointerRotate = this.getPointerRotate({ dragX, dragY });
const absolutePosition = this.getAbsolutePosition(dragX, dragY);
const height = darg.validatePosition(
absolutePosition,
minLength - POINTER_RADIUS,
maxLength - POINTER_RADIUS
);
const top = PICKER_RADIUS - height;
this.setState({
top,
height,
pointerRotate
});
this.handleTimePointerChange({
dragX,
dragY,
autoMode: false
});
}
}
}
handleMouseUp(e) {
if (this.state.draging) {
this.setState({
draging: false
});
const pos = darg.mousePosition(e);
const endX = pos.x + this.offsetDragCenterX;
const endY = pos.y + this.offsetDragCenterY;
let pointerRotate = this.getPointerRotate({
dragX: endX,
dragY: endY
});
const remainder = pointerRotate % 30;
const base = Math.floor(pointerRotate / 30);
pointerRotate = (base + (remainder >= 15 ? 1 : 0)) * 30;
this.setState({ pointerRotate });
this.handleTimePointerChange({
dragX: endX,
dragY: endY,
pointerRotate,
});
}
}
render() {
const { time, draggable } = this.props;
const { draging, height, top, pointerRotate } = this.state;
const pickerPointerClass = draging
? 'picker_pointer'
: 'picker_pointer animation';
return (
(this.dragCenter = r)}
className={`pointer_drag ${draggable ? 'draggable' : ''}`}
style={darg.rotateStyle(-pointerRotate)}
onMouseDown={draggable ? this.handleMouseDown : Function.prototype}
onTouchStart={draggable ? this.handleMouseDown : Function.prototype}
>
{time}
(this.pickerCenter = p)}
/>
);
}
}
PickerDragHandler.propTypes = propTypes;
PickerDragHandler.defaultProps = defaultProps;
export default PickerDragHandler;
================================================
FILE: src/components/Picker/PickerPoint.jsx
================================================
import React from 'react';
import PropTypes from 'prop-types';
import darg from '../../utils/drag';
const propTypes = {
index: PropTypes.number,
angle: PropTypes.number,
onClick: PropTypes.func,
pointClass: PropTypes.string,
};
const defaultProps = {
index: 0,
angle: 0,
onClick: Function.prototype,
pointClass: 'picker_point point_outter',
};
const PickerPoint = (props) => {
const {
index,
angle,
onClick,
pointClass,
pointerRotate,
} = props;
const inlineStyle = darg.inlineRotateStyle(angle);
const wrapperStyle = darg.rotateStyle(-angle);
return (
{
let relativeRotate = angle - (pointerRotate % 360);
if (relativeRotate >= 180) {
relativeRotate -= 360;
} else if (relativeRotate < -180) {
relativeRotate += 360;
}
onClick && onClick({
time: index,
pointerRotate: relativeRotate + pointerRotate
});
}}
onMouseDown={darg.disableMouseDown}
>
{index}
);
};
PickerPoint.propTypes = propTypes;
PickerPoint.defaultProps = defaultProps;
export default PickerPoint;
================================================
FILE: src/components/Picker/PickerPointGenerator.jsx
================================================
import React from 'react';
import {
HOURS,
MINUTES,
TWELVE_HOURS
} from '../../utils/constant.js';
import PickerPoint from './PickerPoint';
const pickerPointGenerator = (type = 'hour', mode = 24) =>
class PickerPointGenerator extends React.PureComponent {
addAnimation() {
this.pickerPointerContainer.className = 'animation';
}
removeAnimation() {
this.pickerPointerContainer.className = '';
}
renderMinutePointes() {
return MINUTES.map((_, index) => {
const angle = (360 * index) / 60;
if (index % 5 === 0) {
return (
);
}
return null;
});
}
renderHourPointes() {
const hours = parseInt(mode, 10) === 24 ? HOURS : TWELVE_HOURS;
return hours.map((_, index) => {
const pointClass = index < 12
? 'picker_point point_inner'
: 'picker_point point_outter';
const angle = index < 12
? (360 * index) / 12
: (360 * (index - 12)) / 12;
return (
);
});
}
render() {
return (
(this.pickerPointerContainer = ref)}
className="picker_pointer_container"
>
{type === 'hour'
? this.renderHourPointes()
: this.renderMinutePointes()}
);
}
};
export default pickerPointGenerator;
================================================
FILE: src/components/TimePicker.jsx
================================================
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import OutsideClickHandler from './OutsideClickHandler';
import Button from './Common/Button';
import timeHelper from '../utils/time.js';
import languageHelper from '../utils/language';
import ICONS from '../utils/icons';
import { is } from '../utils/func';
import asyncComponent from './Common/AsyncComponent';
const DialPlates = {
material: asyncComponent(
() => System.import('./MaterialTheme')
.then(component => component.default)
),
classic: asyncComponent(
() => System.import('./ClassicTheme')
.then(component => component.default)
),
};
// aliases for defaultProps readability
const TIME = timeHelper.time({ useTz: false });
TIME.current = timeHelper.current();
const propTypes = {
autoMode: PropTypes.bool,
autoClose: PropTypes.bool,
colorPalette: PropTypes.string,
draggable: PropTypes.bool,
focused: PropTypes.bool,
language: PropTypes.string,
meridiem: PropTypes.string,
onFocusChange: PropTypes.func,
onTimeChange: PropTypes.func,
onTimezoneChange: PropTypes.func,
phrases: PropTypes.object,
placeholder: PropTypes.string,
showTimezone: PropTypes.bool,
theme: PropTypes.string,
time: PropTypes.string,
timeMode: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
timezone: PropTypes.string,
timezoneIsEditable: PropTypes.bool,
trigger: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object,
PropTypes.element,
PropTypes.array,
PropTypes.node,
PropTypes.instanceOf(React.Component),
PropTypes.instanceOf(React.PureComponent)
]),
withoutIcon: PropTypes.bool,
minuteStep: PropTypes.number,
limitDrag: PropTypes.bool,
timeFormat: PropTypes.string,
timeFormatter: PropTypes.func,
useTz: PropTypes.bool,
closeOnOutsideClick: PropTypes.bool,
timeConfig: PropTypes.object,
disabled: PropTypes.bool,
focusDropdownOnTime: PropTypes.bool,
};
const defaultProps = {
autoMode: true,
autoClose: true,
colorPalette: 'light',
draggable: true,
focused: false,
language: 'en',
meridiem: TIME.meridiem,
onFocusChange: Function.prototype,
onTimeChange: Function.prototype,
onTimezoneChange: Function.prototype,
placeholder: '',
showTimezone: false,
theme: 'material',
time: '',
timeMode: TIME.mode,
trigger: null,
withoutIcon: false,
minuteStep: 5,
limitDrag: false,
timeFormat: '',
timeFormatter: null,
useTz: true,
closeOnOutsideClick: true,
timeConfig: {
step: 30,
unit: 'minutes'
},
disabled: false,
focusDropdownOnTime: true,
};
class TimePicker extends React.PureComponent {
constructor(props) {
super(props);
const { focused, timezone, onTimezoneChange } = props;
const timeData = this.timeData(false);
const timezoneData = timeHelper.tzForName(timeData.timezone);
this.state = {
focused,
timezoneData,
timeChanged: false
};
this.onBlur = this.onBlur.bind(this);
this.onFocus = this.onFocus.bind(this);
this.timeData = this.timeData.bind(this);
this.handleTimeChange = this.handleTimeChange.bind(this);
this.handleHourChange = this.handleHourChange.bind(this);
this.handleMinuteChange = this.handleMinuteChange.bind(this);
this.handleMeridiemChange = this.handleMeridiemChange.bind(this);
this.handleHourAndMinuteChange = this.handleHourAndMinuteChange.bind(this);
// if a timezone value was not passed in,
// call the callback with the default value used for timezone
if (!timezone) {
onTimezoneChange(timezoneData);
}
}
componentWillReceiveProps(nextProps) {
const { focused } = nextProps;
if (focused !== this.props.focused) {
this.setState({ focused });
}
}
onFocus() {
const { focused } = this.state;
if (!focused) {
this.onFocusChange(!focused);
}
}
onBlur() {
const { focused } = this.state;
if (focused) {
this.onFocusChange(!focused);
}
}
onFocusChange(focused) {
const { disabled } = this.props;
if (disabled) return;
this.setState({ focused });
const { onFocusChange } = this.props;
onFocusChange && onFocusChange(focused);
}
timeData(timeChanged) {
const {
time,
useTz,
timeMode,
timezone,
meridiem,
} = this.props;
const timeData = timeHelper.time({
time,
meridiem,
timeMode,
tz: timezone,
useTz: !time && !timeChanged && useTz
});
return timeData;
}
get languageData() {
const { language, phrases = {} } = this.props;
return Object.assign({}, languageHelper.get(language), phrases);
}
get hourAndMinute() {
const { timeMode } = this.props;
const timeData = this.timeData(this.state.timeChanged);
// Since someone might pass a time in 24h format, etc., we need to get it from
// timeData to 'translate' it into the local format, including its accurate meridiem
const hour = (parseInt(timeMode, 10) === 12)
? (parseInt(timeData.hour12, 10) === 12 ? '00' : timeData.hour12)
: (parseInt(timeData.hour24, 10) === 24 ? '00' : timeData.hour24);
const minute = timeData.minute;
return [hour, minute];
}
get formattedTime() {
const {
timeMode,
timeFormat,
timeFormatter,
} = this.props;
const [hour, minute] = this.hourAndMinute;
const validTimeMode = timeHelper.validateTimeMode(timeMode);
let times = '';
if (timeFormatter && is.func(timeFormatter)) {
times = timeFormatter({
hour,
minute,
meridiem: this.meridiem
});
} else if (timeFormat && is.string(timeFormat)) {
times = timeFormat;
if (/HH?/.test(times) || /MM?/.test(times)) {
if (validTimeMode === 12) {
console.warn('It seems you are using 12 hours mode with 24 hours time format. Please check your timeMode and timeFormat props');
}
} else if (/hh?/.test(times) || /mm?/.test(times)) {
if (validTimeMode === 24) {
console.warn('It seems you are using 24 hours mode with 12 hours time format. Please check your timeMode and timeFormat props');
}
}
times = times.replace(/(HH|hh)/g, hour);
times = times.replace(/(MM|mm)/g, minute);
times = times.replace(/(H|h)/g, Number(hour));
times = times.replace(/(M|m)/g, Number(minute));
} else {
times = (validTimeMode === 12)
? `${hour} : ${minute} ${this.meridiem}`
: `${hour} : ${minute}`;
}
return times;
}
get meridiem() {
const { meridiem } = this.props;
const timeData = this.timeData(this.state.timeChanged);
const localMessages = this.languageData;
// eslint-disable-next-line no-unneeded-ternary
const m = (meridiem) ? meridiem : timeData.meridiem;
// eslint-disable-next-line no-extra-boolean-cast
return m && !!(m.match(/^am|pm/i)) ? localMessages[m.toLowerCase()] : m;
}
onTimeChanged(timeChanged) {
this.setState({ timeChanged });
}
handleHourChange(hour) {
const validateHour = timeHelper.validate(hour);
const minute = this.hourAndMinute[1];
this.handleTimeChange({
hour: validateHour,
minute,
meridiem: this.meridiem
});
}
handleMinuteChange(minute) {
const validateMinute = timeHelper.validate(minute);
const hour = this.hourAndMinute[0];
this.handleTimeChange({
hour,
minute: validateMinute,
meridiem: this.meridiem
});
}
handleMeridiemChange(meridiem) {
const [hour, minute] = this.hourAndMinute;
this.handleTimeChange({
hour,
minute,
meridiem
});
}
handleTimeChange(options) {
const { onTimeChange } = this.props;
onTimeChange && onTimeChange(options);
this.onTimeChanged(true);
}
handleHourAndMinuteChange(time) {
this.onTimeChanged(true);
const { onTimeChange, autoClose } = this.props;
if (autoClose) this.onBlur();
return onTimeChange && onTimeChange(time);
}
renderDialPlate() {
const {
theme,
disabled,
timeMode,
autoMode,
autoClose,
draggable,
language,
limitDrag,
minuteStep,
timeConfig,
colorPalette,
showTimezone,
onTimezoneChange,
timezoneIsEditable,
focusDropdownOnTime,
} = this.props;
if (disabled) return null;
const dialTheme = theme === 'material' ? theme : 'classic';
const DialPlate = DialPlates[dialTheme];
const { timezoneData } = this.state;
const [hour, minute] = this.hourAndMinute;
return (
);
}
render() {
const {
trigger,
disabled,
placeholder,
withoutIcon,
colorPalette,
closeOnOutsideClick
} = this.props;
const { focused } = this.state;
const times = this.formattedTime;
const pickerPreviewClass = cx(
'time_picker_preview',
focused && 'active',
disabled && 'disabled'
);
const containerClass = cx(
'time_picker_container',
colorPalette === 'dark' && 'dark'
);
const previewContainerClass = cx(
'preview_container',
withoutIcon && 'without_icon'
);
return (
{trigger || (
)}
{this.renderDialPlate()}
);
}
}
TimePicker.propTypes = propTypes;
TimePicker.defaultProps = defaultProps;
export default TimePicker;
================================================
FILE: src/components/Timezone/TimezonePicker.jsx
================================================
import React from 'react';
import PropTypes from 'prop-types';
import { Typeahead } from 'react-bootstrap-typeahead';
import timeHelper from '../../utils/time';
import ICONS from '../../utils/icons';
import Button from '../Common/Button';
class TimezonePicker extends React.PureComponent {
constructor(props) {
super(props);
this.handleTimezoneChange = this.handleTimezoneChange.bind(this);
}
handleTimezoneChange(selection) {
const { handleTimezoneChange, onClearFocus } = this.props;
const zoneObject = selection[0];
if (zoneObject) {
handleTimezoneChange && handleTimezoneChange(zoneObject);
onClearFocus();
}
}
render() {
const { phrases, onClearFocus } = this.props;
return (
{ICONS.chevronLeft}
{phrases.timezonePickerTitle}
`${option.city} - ${option.zoneAbbr}`}
options={timeHelper.tzMaps}
maxResults={5}
minLength={3}
placeholder={phrases.timezonePickerLabel}
/>
);
}
}
TimezonePicker.propTypes = {
phrases: PropTypes.object,
onClearFocus: PropTypes.func,
handleTimezoneChange: PropTypes.func
};
TimezonePicker.defaultProps = {
onClearFocus: Function.prototype,
handleTimezoneChange: Function.prototype
};
export default TimezonePicker;
================================================
FILE: src/components/Timezone/index.jsx
================================================
import React from 'react';
import PropTypes from 'prop-types';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import timeHelper from '../../utils/time';
import TimezonePicker from './TimezonePicker';
const TIME = timeHelper.time();
TIME.tz = timeHelper.guessUserTz();
class Timezone extends React.PureComponent {
constructor(props) {
super(props);
const { timezone } = this.props;
this.state = {
focused: false,
timezone,
};
this.onClearFocus = this.onClearFocus.bind(this);
this.handleFocusedChange = this.handleFocusedChange.bind(this);
this.handleTimezoneChange = this.handleTimezoneChange.bind(this);
}
onClearFocus() {
this.setState({ focused: false });
}
handleFocusedChange() {
if (!this.props.timezoneIsEditable) return;
const { focused } = this.state;
this.setState({ focused: !focused });
}
handleTimezoneChange(timezone) {
this.setState({ timezone });
const { onTimezoneChange } = this.props;
onTimezoneChange && onTimezoneChange(timezone);
}
render() {
const { focused, timezone } = this.state;
const { phrases, timezoneIsEditable } = this.props;
const footerClass = timezoneIsEditable
? 'time_picker_modal_footer clickable'
: 'time_picker_modal_footer';
const timeZonePicker = () => {
if (!timezoneIsEditable || !focused) return '';
return (
);
};
return (
{timezone.zoneName} {timezone.zoneAbbr}
{timeZonePicker()}
);
}
}
Timezone.propTypes = {
phrases: PropTypes.object,
timezone: PropTypes.shape({
city: PropTypes.string,
zoneAbbr: PropTypes.string,
zoneName: PropTypes.string,
}),
timezoneIsEditable: PropTypes.bool,
onTimezoneChange: PropTypes.func,
};
Timezone.defaultProps = {
timezone: TIME.tz,
timezoneIsEditable: false,
onTimezoneChange: Function.prototype,
};
export default Timezone;
================================================
FILE: src/utils/constant.js
================================================
const getArray = length => new Array(length).join('0').split('');
export const HOURS = getArray(24 + 1);
export const TWELVE_HOURS = getArray(12 + 1);
export const MINUTES = getArray(60 + 1);
const PICKER_WIDTH = 260;
const POINTER_WIDTH = 35;
export const PICKER_RADIUS = PICKER_WIDTH / 2;
export const MAX_ABSOLUTE_POSITION = 125;
export const MIN_ABSOLUTE_POSITION = 90;
export const POINTER_RADIUS = POINTER_WIDTH / 2;
export const BROWSER_COMPATIBLE = [
'',
'O',
'Moz',
'Ms',
'ms',
'Webkit'
];
export const MERIDIEMS = ['AM', 'PM'];
================================================
FILE: src/utils/drag.js
================================================
import { BROWSER_COMPATIBLE } from './constant';
const getScrollPosition = () => {
const position = {
x: document.documentElement.scrollLeft
|| document.body.scrollLeft
|| 0,
y: document.documentElement.scrollTop
|| document.body.scrollTop
|| 0,
};
return position;
};
const mousePosition = (e) => {
const event = e || window.event;
let xPos;
const scrollPosition = getScrollPosition();
if (event.pageX) {
xPos = event.pageX;
} else if ((event.clientX + scrollPosition.x) - document.body.clientLeft) {
xPos = (event.clientX + scrollPosition.x) - document.body.clientLeft;
} else if (event.touches[0]) {
xPos = event.touches[0].clientX;
} else {
xPos = event.changedTouches[0].clientX;
}
let yPos;
if (event.pageY) {
yPos = event.pageY;
} else if ((event.clientY + scrollPosition.y) - document.body.clientTop) {
yPos = (event.clientY + scrollPosition.y) - document.body.clientTop;
} else if (event.touches[0]) {
yPos = event.touches[0].clientY;
} else {
yPos = event.changedTouches[0].clientY;
}
return {
x: xPos,
y: yPos,
};
};
const disableMouseDown = (e) => {
const event = e || window.event;
event.preventDefault();
event.stopPropagation();
};
const browserStyles = (type, style) => BROWSER_COMPATIBLE.reduce((dict, browser) => {
const key = browser
? `${browser}${type[0].toUpperCase()}${type.slice(1)}`
: type;
dict[key] = style;
return dict;
}, {});
const getRotateStyle = degree =>
browserStyles('transform', `rotate(${degree}deg)`);
const getInlineRotateStyle = degree =>
browserStyles('transform', `translateX(-50%) rotate(${degree}deg)`);
const getInitialPointerStyle = (height, top, degree) =>
Object.assign({
height: `${height}px`,
top: `${top}px`,
}, browserStyles('transform', `translateX(-50%) rotate(${degree}deg)`));
const getStandardAbsolutePosition = (position, minPosition, maxPosition) => {
let p = position;
if (p < minPosition) {
p = minPosition;
}
if (p > maxPosition) {
p = maxPosition;
}
return p;
};
const degree2Radian = degree => (degree * (2 * Math.PI)) / 360;
export default {
degree2Radian,
mousePosition,
disableMouseDown,
rotateStyle: getRotateStyle,
inlineRotateStyle: getInlineRotateStyle,
initialPointerStyle: getInitialPointerStyle,
validatePosition: getStandardAbsolutePosition
};
================================================
FILE: src/utils/func.js
================================================
// simple utils for working with sequences like Array or string
const checkType = (val, result) =>
Object.prototype.toString.call(val) === result;
export const is = {
object: val => checkType(val, '[object Object]'),
array: val => Array.isArray(val),
func: val => checkType(val, '[object Function]'),
string: val => checkType(val, '[object String]'),
undefined: val => typeof val === 'undefined',
};
export const isSeq = seq => (is.string(seq) || is.array(seq));
export const head = seq => isSeq(seq) ? seq[0] : null;
export const first = head;
export const tail = seq => isSeq(seq) ? seq.slice(1) : null;
export const rest = tail;
export const last = seq => isSeq(seq) ? seq[seq.length - 1] : null;
================================================
FILE: src/utils/icons.js
================================================
import React from 'react';
const time = (
);
const chevronLeft = (
);
export default {
time,
chevronLeft
};
================================================
FILE: src/utils/language.js
================================================
const LANGUAGES = {
en: {
confirm: 'confirm',
cancel: 'cancel',
close: 'close',
timezonePickerTitle: 'Pick a timezone',
timezonePickerLabel: 'Closest city or timezone',
am: 'AM',
pm: 'PM'
},
'zh-cn': {
confirm: '确认',
cancel: '取消',
close: '关闭',
timezonePickerTitle: '选择时区',
timezonePickerLabel: '最近的城市或时区',
am: '上午',
pm: '下午'
},
'zh-tw': {
confirm: '確認',
cancel: '取消',
close: '關閉',
timezonePickerTitle: '選擇時區',
timezonePickerLabel: '最近的城市或時區',
am: '上午',
pm: '下午'
},
fr: {
confirm: 'Confirmer',
cancel: 'Annulé',
close: 'Arrêter',
timezonePickerTitle: 'Choisissez un timezone',
timezonePickerLabel: 'Ville la plus proche ou timezone',
am: 'AM',
pm: 'PM'
},
ja: {
confirm: '確認します',
cancel: 'キャンセル',
close: 'クローズ',
timezonePickerTitle: 'タイムゾーンを選択する',
timezonePickerLabel: '最も近い都市またはTimezone',
am: 'AM',
pm: 'PM'
}
};
const language = (type = 'en') => LANGUAGES[type];
export default {
get: language
};
================================================
FILE: src/utils/time.js
================================================
import moment from 'moment-timezone';
import { head, last, is } from './func';
// loads moment-timezone's timezone data, which comes from the
// IANA Time Zone Database at https://www.iana.org/time-zones
moment.tz.load({
zones: [],
links: [],
version: 'latest',
});
const guessUserTz = () => {
// User-Agent sniffing is not always reliable, but is the recommended technique
// for determining whether or not we're on a mobile device according to MDN
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#Mobile_Tablet_or_Desktop
const isMobile = global.navigator !== undefined
? global.navigator.userAgent.match(/Mobi/)
: false;
const supportsIntl = global.Intl !== undefined;
let userTz;
if (isMobile && supportsIntl) {
// moment-timezone gives preference to the Intl API regardless of device type,
// so unset global.Intl to trick moment-timezone into using its fallback
// see https://github.com/moment/moment-timezone/issues/441
// TODO: Clean this up when that issue is resolved
const globalIntl = global.Intl;
global.Intl = undefined;
userTz = moment.tz.guess();
global.Intl = globalIntl;
} else {
userTz = moment.tz.guess();
}
// return GMT if we're unable to guess or the system is using UTC
if (!userTz || userTz === 'UTC') return getTzForName('Etc/Greenwich');
try {
return getTzForName(userTz);
} catch (e) {
console.error(e);
return getTzForName('Etc/Greenwich');
}
};
/**
* Create a time data object using moment.
* If a time is provided, just format it; if not, use the current time.
*
* @function getValidTimeData
* @param {string} time a time; defaults to now
* @param {string} meridiem AM or PM; defaults to AM via moment
* @param {Number} timeMode 12 or 24-hour mode
* @param {string} tz a timezone name; defaults to guessing a user's tz or GMT
* @return {Object} a key-value representation of time data
*/
const getValidTimeData = (options = {}) => {
const {
tz,
time,
timeMode,
useTz = true,
meridiem = null,
} = options;
const validMeridiem = getValidMeridiem(meridiem);
// when we only have a valid meridiem, that implies a 12h mode
const mode = (validMeridiem && !timeMode) ? 12 : timeMode || 24;
const timezone = tz || guessUserTz().zoneName;
const validMode = getValidateTimeMode(mode);
const validTime = getValidTimeString(time, validMeridiem);
const format12 = 'hh:mmA';
const format24 = 'HH:mmA';
// What format is the hour we provide to moment below in?
const hourFormat = (validMode === 12) ? format12 : format24;
let time24;
let time12;
const formatTime = moment(`1970-01-01 ${validTime}`, `YYYY-MM-DD ${hourFormat}`, 'en');
if (time || !useTz) {
time24 = ((validTime)
? formatTime.format(format24)
: moment().format(format24)).split(/:/);
time12 = ((validTime)
? formatTime.format(format12)
: moment().format(format12)).split(/:/);
} else {
time24 = ((validTime)
? formatTime.tz(timezone).format(format24)
: moment().tz(timezone).format(format24)).split(/:/);
time12 = ((validTime)
? formatTime.tz(timezone).format(format12)
: moment().tz(timezone).format(format12)).split(/:/);
}
const timeData = {
timezone,
mode: validMode,
hour24: head(time24),
minute: last(time24).slice(0, 2),
hour12: head(time12).replace(/^0/, ''),
meridiem: validMode === 12 ? last(time12).slice(2) : null,
};
return timeData;
};
/**
* Format the current time as a string
* @function getCurrentTime
* @return {string}
*/
const getCurrentTime = () => {
const time = getValidTimeData();
return `${time.hour24}:${time.minute}`;
};
/**
* Get an integer representation of a time.
* @function getValidateIntTime
* @param {string} time
* @return {Number}
*/
const getValidateIntTime = (time) => {
if (isNaN(parseInt(time, 10))) { return 0; }
return parseInt(time, 10);
};
/**
* Validate, set a default for, and stringify time data.
* @function getValidateTime
* @param {string}
* @return {string}
*/
const getValidateTime = (time) => {
let result = time;
if (is.undefined(result)) { result = '00'; }
if (isNaN(parseInt(result, 10))) { result = '00'; }
if (parseInt(result, 10) < 10) { result = `0${parseInt(result, 10)}`; }
return `${result}`;
};
/**
* Given a time and meridiem, produce a time string to pass to moment
* @function getValidTimeString
* @param {string} time
* @param {string} meridiem
* @return {string}
*/
const getValidTimeString = (time, meridiem) => {
if (is.string(time)) {
let validTime = (time && time.indexOf(':').length >= 0)
? time.split(/:/).map(t => getValidateTime(t)).join(':')
: time;
const hourAsInt = parseInt(head(validTime.split(/:/)), 10);
const is12hTime = (hourAsInt > 0 && hourAsInt <= 12);
validTime = (validTime && meridiem && is12hTime)
? `${validTime} ${meridiem}`
: validTime;
return validTime;
}
return time;
};
/**
* Given a meridiem, try to ensure that it's formatted for use with moment
* @function getValidMeridiem
* @param {string} meridiem
* @return {string}
*/
const getValidMeridiem = (meridiem) => {
if (is.string(meridiem)) {
return (meridiem && meridiem.match(/am|pm/i)) ? meridiem.toLowerCase() : null;
}
return meridiem;
};
/**
* Ensure that a meridiem passed as a prop has a valid value
* @function getValidateMeridiem
* @param {string} time
* @param {string|Number} timeMode
* @return {string|null}
*/
const getValidateMeridiem = (time, timeMode) => {
const validateTime = time || getCurrentTime();
const mode = parseInt(timeMode, 10);
// eslint-disable-next-line no-unused-vars
let hour = validateTime.split(/:/)[0];
hour = getValidateIntTime(hour);
if (mode === 12) return (hour > 12) ? 'PM' : 'AM';
return null;
};
/**
* Validate and set a sensible default for time modes.
*
* @function getValidateTimeMode
* @param {string|Number} timeMode
* @return {Number}
*/
const getValidateTimeMode = (timeMode) => {
const mode = parseInt(timeMode, 10);
if (isNaN(mode)) { return 24; }
if (mode !== 24 && mode !== 12) { return 24; }
return mode;
};
const tzNames = (() => {
// We want to subset the existing timezone data as much as possible, both for efficiency
// and to avoid confusing the user. Here, we focus on removing reduntant timezone names
// and timezone names for timezones we don't necessarily care about, like Antarctica, and
// special timezone names that exist for convenience.
const scrubbedPrefixes = ['Antarctica', 'Arctic', 'Chile'];
const scrubbedSuffixes = ['ACT', 'East', 'Knox_IN', 'LHI', 'North', 'NSW', 'South', 'West'];
const tznames = moment.tz.names()
.filter(name => name.indexOf('/') >= 0)
.filter(name => !scrubbedPrefixes.indexOf(name.split('/')[0]) >= 0)
.filter(name => !scrubbedSuffixes.indexOf(name.split('/').slice(-1)[0]) >= 0);
return tznames;
})();
// We need a human-friendly city name for each timezone identifier
// counting Canada/*, Mexico/*, and US/* allows users to search for
// things like 'Eastern' or 'Mountain' and get matches back
const tzCities = tzNames
.map(name => (['Canada', 'Mexico', 'US'].indexOf(name.split('/')[0]) >= 0)
? name : name.split('/').slice(-1)[0])
.map(name => name.replace(/_/g, ' '));
// Provide a mapping between a human-friendly city name and its corresponding
// timezone identifier and timezone abbreviation as a named export.
// We can fuzzy match on any of these.
const tzMaps = tzCities.map((city) => {
const tzMap = {};
const tzName = tzNames[tzCities.indexOf(city)];
tzMap.city = city;
tzMap.zoneName = tzName;
tzMap.zoneAbbr = moment().tz(tzName).zoneAbbr();
return tzMap;
});
const getTzForCity = (city) => {
const val = city.toLowerCase();
const maps = tzMaps.filter(tzMap => tzMap.city.toLowerCase() === val);
return head(maps);
};
const getTzCountryAndCity = (name) => {
const sections = name.split('/');
return {
country: sections[0].toLowerCase(),
city: sections.slice(-1)[0].toLowerCase()
};
};
const _matchTzByName = (target, name) => {
const v1 = getTzCountryAndCity(target);
const v2 = getTzCountryAndCity(name);
return v1.country === v2.country && v1.city === v2.city;
};
const getTzForName = (name) => {
let maps = tzMaps.filter(tzMap => tzMap.zoneName === name);
if (!maps.length && /\//.test(name)) {
maps = tzMaps.filter(tzMap => tzMap.zoneAbbr === name);
}
if (!maps.length) {
maps = tzMaps.filter(tzMap => _matchTzByName(tzMap.zoneName, name));
}
if (!maps.length) {
throw new Error(`Can not find target timezone for ${name}`);
}
return head(maps);
};
const hourFormatter = (hour, defaultTime = '00:00') => {
if (!hour) return defaultTime;
let [h, m, meridiem] = `${hour}`.split(/[:|\s]/);
if (meridiem && meridiem.toLowerCase() === 'pm') meridiem = 'PM';
if (meridiem && meridiem.toLowerCase() === 'am') meridiem = 'AM';
if (meridiem && meridiem !== 'AM' && meridiem !== 'PM') meridiem = 'AM';
if (!h || isNaN(h)) h = '0';
if (!meridiem && Number(h > 24)) h = Number(h) - 24;
if (meridiem && Number(h > 12)) h = Number(h) - 12;
if (!m || isNaN(m) || Number(m) >= 60) m = '0';
if (Number(h) < 10) h = `0${Number(h)}`;
if (Number(m) < 10) m = `0${Number(m)}`;
return meridiem ? `${h}:${m} ${meridiem}` : `${h}:${m}`;
};
const withoutMeridiem = hour => hour.replace(/\s[P|A]M$/, '');
const getStartAndEnd = (from, to) => {
const current = moment();
const date = current.format('YYYY-MM-DD');
const nextDate = current.add(1, 'day').format('YYYY-MM-DD');
const f = hourFormatter(from, '00:00');
const t = hourFormatter(to, '23:30');
let start = `${date} ${withoutMeridiem(f)}`;
const endTmp = withoutMeridiem(t);
let end = moment(`${date} ${endTmp}`) <= moment(start)
? `${nextDate} ${endTmp}`
: `${date} ${endTmp}`;
if (/PM$/.test(f)) start = moment(start).add(12, 'hours').format('YYYY-MM-DD HH:mm');
if (/PM$/.test(t)) end = moment(end).add(12, 'hours').format('YYYY-MM-DD HH:mm');
return {
start,
end
};
};
const get12ModeTimes = ({ from, to, step = 30, unit = 'minutes' }) => {
const {
start,
end
} = getStartAndEnd(from, to);
const times = [];
let time = moment(start);
while (time <= moment(end)) {
const hour = Number(time.format('HH'));
times.push(`${time.format('hh:mm')} ${hour >= 12 ? 'PM' : 'AM'}`);
time = time.add(step, unit);
}
return times;
};
const get24ModeTimes = ({ from, to, step = 30, unit = 'minutes' }) => {
const {
start,
end
} = getStartAndEnd(from, to);
const times = [];
let time = moment(start);
while (time <= moment(end)) {
times.push(time.format('HH:mm'));
time = time.add(step, unit);
}
return times;
};
export default {
tzMaps,
guessUserTz,
hourFormatter,
getStartAndEnd,
get12ModeTimes,
get24ModeTimes,
withoutMeridiem,
time: getValidTimeData,
current: getCurrentTime,
tzForCity: getTzForCity,
tzForName: getTzForName,
validate: getValidateTime,
validateInt: getValidateIntTime,
validateMeridiem: getValidateMeridiem,
validateTimeMode: getValidateTimeMode,
};
================================================
FILE: stories/ClassicThemePicker.js
================================================
import '../css/classic/default.css';
import React from 'react';
import TimePickerWrapper from '../examples/TimePickerWrapper';
import { storiesOf } from '@storybook/react';
storiesOf('Classic Theme', module)
.addWithInfo('basic', () => (
))
.addWithInfo('with default time', () => (
))
.addWithInfo('dropdown focus on time/default time', () => (
))
.addWithInfo('dark color', () => (
))
.addWithInfo('12 hours mode', () => (
))
.addWithInfo('limit start, end, step for 12 hours mode', () => (
))
.addWithInfo('limit start, end, step for 24 hours mode', () => (
))
.addWithInfo('focused at setup', () => (
))
.addWithInfo('Set default time', () => (
))
.addWithInfo('Focus dropdown on time', () => (
));
================================================
FILE: stories/CustomTrigger.js
================================================
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withKnobs } from '@storybook/addon-knobs';
import TimePickerWrapper from '../examples/TimePickerWrapper';
import '../css/material/default.css';
storiesOf('Custom TimePicker Trigger', module)
.addDecorator(withKnobs)
.addWithInfo('basic example', () => (
))
.addWithInfo('any custom DOM', () => (
))
.addWithInfo('only render picker modal', () => (
}
closeOnOutsideClick={false}
/>
));
================================================
FILE: stories/DarkColor.js
================================================
import '../css/material/default.css';
import React from 'react';
import TimePickerWrapper from '../examples/TimePickerWrapper';
import { storiesOf } from '@storybook/react';
storiesOf('DarkColor', module)
.addWithInfo('basic', () => (
))
.addWithInfo('with default time', () => (
))
.addWithInfo('focused at setup', () => (
))
.addWithInfo('without icon', () => (
));
================================================
FILE: stories/DifferentLanguage.js
================================================
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withKnobs, text } from '@storybook/addon-knobs';
import TimePickerWrapper from '../examples/TimePickerWrapper';
import '../css/material/default.css';
storiesOf('Different Languages', module)
.addDecorator(withKnobs)
.addWithInfo('English (basic)', () => (
))
.addWithInfo('汉语 - 简体', () => (
))
.addWithInfo('汉语 - 繁体', () => (
))
.addWithInfo('Français', () => (
))
.addWithInfo('日本語', () => (
))
.addWithInfo('custom phrases', () => {
const confirm = text('confirm', 'okey dokey');
const cancel = text('cancel', 'hold it there!');
const close = text('close', 'DONE');
const am = text('am', 'Ante');
const pm = text('pm', 'Post');
return (
);
});
================================================
FILE: stories/TimePicker.js
================================================
import React from 'react';
import { storiesOf } from '@storybook/react';
import '../css/material/default.css';
import { text, withKnobs } from '@storybook/addon-knobs';
import TimePickerWrapper from '../examples/TimePickerWrapper';
storiesOf('Default TimePicker', module)
.addDecorator(withKnobs)
.addWithInfo('basic', () => (
))
.addWithInfo('disabled', () => (
))
.addWithInfo('with default time', () => {
const aDefaultTime = text('set default time', '13:20');
return (
);
})
.addWithInfo('focused at setup', () => (
))
.addWithInfo('not auto change time panel', () => (
))
.addWithInfo('undraggable', () => (
))
.addWithInfo('disable outside click close', () => (
))
.addWithInfo('custom minute step', () => (
))
.addWithInfo('limit drag', () => (
))
.addWithInfo('custom HH-MM format', () => (
))
.addWithInfo('custom H-M format', () => (
))
.addWithInfo('custom time formatter', () => (
`${hour} & ${minute}`}
/>
));
================================================
FILE: stories/TimePicker2.js
================================================
import React from 'react';
import { storiesOf } from '@storybook/react';
import '../css/material/default.css';
import { text, withKnobs } from '@storybook/addon-knobs';
import TimePickerWrapper2 from '../examples/TimePickerWrapper2';
storiesOf('Multi TimePicker', module)
.addDecorator(withKnobs)
.addWithInfo('basic', () => (
));
================================================
FILE: stories/TwelveHoursMode.js
================================================
import React from 'react';
import TimePickerWrapper from '../examples/TimePickerWrapper';
import { storiesOf } from '@storybook/react';
import '../css/material/default.css';
storiesOf('TwelveHoursMode', module)
.addWithInfo('basic', () => (
))
.addWithInfo('with default time', () => (
))
.addWithInfo('focused at setup, no icon', () => (
))
.addWithInfo('custom minute step', () => (
))
.addWithInfo('limit drag', () => (
))
.addWithInfo('disable outside click close', () => (
));
================================================
FILE: stories/WithTimeZones.js
================================================
import '../css/material/default.css';
import { withKnobs } from '@storybook/addon-knobs';
import React from 'react';
import TimePickerWrapper from '../examples/TimePickerWrapper';
import TimeZonesPickerWrapper from '../examples/TimeZonesPickerWrapper';
import { storiesOf } from '@storybook/react';
import timeHelper from '../src/utils/time.js';
const tzForCity = timeHelper.tzForCity('Kuala Lumpur');
storiesOf('TimeZones', module)
.addDecorator(withKnobs)
.addWithInfo('with default (detected) timezone', () => (
))
.addWithInfo('with default (custom) timezone', () => (
))
.addWithInfo('with timezone search', () => (
))
.addWithInfo('with 12 hour (custom) time', () => (
))
.addWithInfo('with dark theme', () => (
))
.addWithInfo('timezone picker', () => );
================================================
FILE: test/_helpers/adapter.js
================================================
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
================================================
FILE: test/_helpers/ignoreSVGStrings.jsx
================================================
require.extensions['.svg'] = (obj) => {
obj.exports = () => (
);
};
================================================
FILE: test/components/ClassicTheme_spec.jsx
================================================
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import '../_helpers/adapter';
import ClassicTheme from '../../src/components/ClassicTheme';
describe('ClassicTheme', () => {
describe('ClassicTheme render', () => {
it('should render correctly', () => {
const wrapper = shallow();
expect(wrapper.is('.classic_theme_container')).to.equal(true);
});
});
});
================================================
FILE: test/components/MaterialTheme_spec.jsx
================================================
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import MaterialTheme from '../../src/components/MaterialTheme';
import languageHelper from '../../src/utils/language';
import '../_helpers/adapter';
const phrases = languageHelper.get('en');
describe('MaterialTheme', () => {
describe('MaterialTheme Timezone render', () => {
it('should render the Timezone footer', () => {
const mockTimezone = {
zoneName: 'Some Zone',
zoneAbbr: 'SZ'
};
const wrapper = shallow(
);
expect(wrapper.find('Timezone')).to.have.lengthOf(1);
});
it('should not render the Timezone footer', () => {
const wrapper = shallow();
expect(wrapper.find('Timezone')).to.have.lengthOf(0);
});
});
describe('MaterialTheme render correctly', () => {
it('should render with className', () => {
const wrapper = shallow();
expect(wrapper.is('.modal_container')).to.equal(true);
expect(wrapper.is('.time_picker_modal_container')).to.equal(true);
});
});
});
================================================
FILE: test/components/PickerDargHandler_spec.jsx
================================================
import React from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import PickerDragHandler from '../../src/components/Picker/PickerDragHandler';
import { JSDOM } from 'jsdom';
import '../_helpers/adapter';
const jsdom = new JSDOM('');
const { window } = jsdom;
function copyProps(src, target) {
const props = Object.getOwnPropertyNames(src)
.filter(prop => typeof target[prop] === 'undefined')
.reduce((result, prop) => ({
...result,
[prop]: Object.getOwnPropertyDescriptor(src, prop),
}), {});
Object.defineProperties(target, props);
}
global.window = window;
global.document = window.document;
global.navigator = {
userAgent: 'node.js',
};
copyProps(window, global);
describe('PickerDragHandler', () => {
describe('PickerDragHandler Init', () => {
const wrapper = mount();
it('should render component correctly', () => {
expect(wrapper.find('.picker_handler').length).to.equal(1);
});
it('should render correct draging state', () => {
expect(wrapper.state().draging).to.equal(false);
});
});
});
================================================
FILE: test/components/PickerPointGenerator_spec.jsx
================================================
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import PickerPoint from '../../src/components/Picker/PickerPoint';
import pickerPointGenerator from '../../src/components/Picker/PickerPointGenerator';
import '../_helpers/adapter';
describe('PickerPointGenerator', () => {
describe('Render 24 hours', () => {
const PickerPointGenerator = pickerPointGenerator('hour');
const wrapper = shallow();
it('should render with currect wrapper', () => {
expect(wrapper.is('.picker_pointer_container')).to.equal(true);
});
it('should render with 24 PickerPoint', () => {
expect(wrapper.find(PickerPoint)).to.have.lengthOf(24);
});
});
describe('Render 12 hours', () => {
const PickerPointGenerator = pickerPointGenerator('hour', 12);
const wrapper = shallow();
it('should render with currect wrapper', () => {
expect(wrapper.is('.picker_pointer_container')).to.equal(true);
});
it('should render with 12 PickerPoint', () => {
expect(wrapper.find(PickerPoint)).to.have.lengthOf(12);
});
});
describe('Render minutes', () => {
const PickerPointGenerator = pickerPointGenerator('minute');
const wrapper = shallow();
it('should render with 12 PickerPoint', () => {
expect(wrapper.find(PickerPoint)).to.have.lengthOf(12);
});
});
});
================================================
FILE: test/components/PickerPoint_spec.jsx
================================================
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import PickerPoint from '../../src/components/Picker/PickerPoint';
import '../_helpers/adapter';
describe('PickerPoint', () => {
const wrapper = shallow(
);
it('should render with currect wrapper', () => {
expect(wrapper.is('.picker_point.point_outter')).to.equal(true);
});
});
================================================
FILE: test/components/TimePicker_func_spec.jsx
================================================
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon-sandbox';
import languageHelper from '../../src/utils/language';
import TimePicker from '../../src/components/TimePicker';
import '../_helpers/adapter';
describe('TimePicker func', () => {
describe('handle focus change func', () => {
it('should focus', () => {
const wrapper = shallow();
wrapper.instance().onFocus();
expect(wrapper.state().focused).to.equal(true);
});
it('should clear focus', () => {
const wrapper = shallow();
wrapper.instance().onBlur();
expect(wrapper.state().focused).to.equal(false);
});
it('should callback when focus', () => {
const onFocusChangeStub = sinon.stub();
const wrapper = shallow();
wrapper.instance().onFocus();
expect(onFocusChangeStub.callCount).to.equal(1);
});
});
describe('handle hour change func', () => {
// it('should change hour', () => {
// const wrapper = shallow();
// wrapper.instance().handleHourChange(11);
// expect(wrapper.props().time.split(':')[0]).to.equal('11');
// });
//
// it('should change to validate hour', () => {
// const wrapper = shallow();
// wrapper.instance().handleHourChange(1);
// expect(wrapper.props().time.split(':')[1]).to.equal('01');
// });
it('should change callback when hour change', () => {
const onTimeChangeStub = sinon.stub();
const wrapper = shallow();
wrapper.instance().handleHourChange(1);
expect(onTimeChangeStub.callCount).to.equal(1);
});
});
describe('handle minute change func', () => {
// it('should change minute', () => {
// const wrapper = shallow();
// wrapper.instance().handleMinuteChange(59);
// expect(wrapper.state().minute).to.equal('59');
// });
//
// it('should change to validate minute', () => {
// const wrapper = shallow();
// wrapper.instance().handleMinuteChange(9);
// expect(wrapper.state().minute).to.equal('09');
// });
it('should change callback when minute change', () => {
const onTimeChangeStub = sinon.stub();
const wrapper = shallow();
wrapper.instance().handleMinuteChange(1);
expect(onTimeChangeStub.callCount).to.equal(1);
});
});
describe('languageData func', () => {
it('should return the default language messages when no phrases provided', () => {
const wrapper = shallow();
const messages = wrapper.instance().languageData;
expect(messages).to.deep.equal(languageHelper.get('en'));
});
it('should return the phrases when all phrases provided', () => {
const phrases = {
confirm: 'foo',
cancel: 'bar',
close: 'baz',
timezonePickerLabel: 'This is a Label',
timezonePickerTitle: 'This is a Title',
am: 'fizz',
pm: 'buzz'
};
const wrapper = shallow();
const messages = wrapper.instance().languageData;
expect(messages).to.deep.equal(phrases);
});
it('should return the default language messages for any phrases not provided', () => {
const phrases = {
cancel: 'bar',
close: 'baz'
};
const expectedMessages = Object.assign({}, languageHelper.get('en'), phrases);
const wrapper = shallow();
const messages = wrapper.instance().languageData;
expect(messages).to.deep.equal(expectedMessages);
});
});
});
================================================
FILE: test/components/TimePicker_init_spec.jsx
================================================
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
// import ClassicTheme from '../../src/components/ClassicTheme';
// import MaterialTheme from '../../src/components/MaterialTheme';
import OutsideClickHandler from '../../src/components/OutsideClickHandler';
import PickerDragHandler from '../../src/components/Picker/PickerDragHandler';
import TimePicker from '../../src/components/TimePicker';
import timeHelper from '../../src/utils/time';
import '../_helpers/adapter';
describe('TimePicker initial', () => {
describe('render basic picker', () => {
it('should be wrappered by div.time_picker_container', () => {
const wrapper = shallow();
expect(wrapper.is('.time_picker_container')).to.equal(true);
});
it('renders an OutsideClickHandler', () => {
const wrapper = shallow();
expect(wrapper.find(OutsideClickHandler)).to.have.lengthOf(1);
});
// it('renders an MaterialTheme', () => {
// const wrapper = shallow();
// expect(wrapper.contains(MaterialTheme)).to.have.lengthOf(1);
// });
// it('renders an ClassicTheme', () => {
// const wrapper = shallow();
// expect(wrapper.contains(ClassicTheme)).to.have.lengthOf(1);
// });
it('renders an PickerDragHandler', () => {
const wrapper = shallow();
expect(wrapper.find(PickerDragHandler)).to.have.lengthOf(0);
});
});
describe('render with props', () => {
it('should be wrapped by div.time_picker_container.dark', () => {
const wrapper = shallow();
expect(wrapper.is('.time_picker_container.dark')).to.equal(true);
});
it('should render with focused', () => {
const wrapper = shallow();
expect(wrapper.find('.time_picker_preview.active')).to.have.lengthOf(1);
expect(wrapper.find(OutsideClickHandler).props().closeOnOutsideClick).to.equal(true);
});
it('should render disabled component', () => {
const wrapper = shallow();
expect(wrapper.find('.time_picker_preview.disabled')).to.have.lengthOf(1);
expect(wrapper.find(OutsideClickHandler).props().closeOnOutsideClick).to.equal(false);
});
it('should render with focused on child', () => {
const wrapper = shallow();
expect(wrapper.find(OutsideClickHandler).props().focused).to.equal(true);
});
it('should render with no onOutsideClick handler', () => {
const wrapper = shallow();
expect(wrapper.find(OutsideClickHandler).props().focused).to.equal(true);
wrapper.find(OutsideClickHandler).simulate('click');
expect(wrapper.find(OutsideClickHandler).props().focused).to.equal(true);
});
it('should render without icon', () => {
const wrapper = shallow();
expect(wrapper.find('.preview_container.without_icon')).to.have.lengthOf(1);
});
// it('should render with default time in child props', () => {
// const wrapper = mount();
// const time = timeHelper.time({ time: '22:23' });
// console.log(wrapper.find(MaterialTheme));
// expect(wrapper.find(MaterialTheme).props().hour).to.equal(time.hour24);
// expect(wrapper.find(MaterialTheme).props().minute).to.equal(time.minute);
// });
it('should render with default time in DOM', () => {
const wrapper = shallow();
const time = timeHelper.time({ time: '22:23' });
expect(wrapper.find('.preview_container').text()).to.equal(`${time.hour24} : ${time.minute}`);
});
// it('should render with current time in child props', () => {
// const wrapper = shallow();
// const time = timeHelper.time({
// time: timeHelper.current()
// });
// expect(wrapper.find('#MaterialTheme').props().hour).to.equal(time.hour24);
// expect(wrapper.find('#MaterialTheme').props().minute).to.equal(time.minute);
// });
it('should render with current time in DOM', () => {
const wrapper = shallow();
const time = timeHelper.time({
time: timeHelper.current()
});
expect(wrapper.find('.preview_container').text()).to.equal(`${time.hour24} : ${time.minute}`);
});
it('should render with current time format HH&MM', () => {
const wrapper = shallow();
const time = timeHelper.time({ time: '22:23' });
expect(wrapper.find('.preview_container').text()).to.equal(`${time.hour24}&23`);
});
it('should render with current time format hh&mm', () => {
const wrapper = shallow();
const time = timeHelper.time({ time: '12:23' });
expect(wrapper.find('.preview_container').text()).to.equal('00&23');
});
});
});
================================================
FILE: test/components/Time_zone_spec.jsx
================================================
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import Timezone from '../../src/components/Timezone';
import languageHelper from '../../src/utils/language';
import '../_helpers/adapter';
const phrases = languageHelper.get('en');
const mockTimezone = {
zoneName: 'Some Zone',
zoneAbbr: 'SZ'
};
describe('Timezone', () => {
describe('Timezone render', () => {
const wrapper = shallow(
);
it('should render the Timezone footer', () => {
expect(wrapper.find('.time_picker_modal_footer_timezone')).to.have.lengthOf(1);
});
it('should render the Timezone Name and Abbreviation', () => {
expect(wrapper.find('.time_picker_modal_footer_timezone').text())
.to.equal(`${mockTimezone.zoneName} ${mockTimezone.zoneAbbr}`);
});
});
describe('props', () => {
describe('when timezoneIsEditable is true', () => {
it('should render the Time Picker modal footer clickable', () => {
const wrapper = shallow(
);
expect(wrapper.find('.time_picker_modal_footer').hasClass('clickable')).to.equal(true);
});
describe('when focused is true', () => {
it('should render the TimezonePicker', () => {
const wrapper = shallow(
);
wrapper.setState({ focused: true });
expect(wrapper.find('TimezonePicker')).to.have.lengthOf(1);
});
});
describe('when focused is false', () => {
it('should not render the TimezonePicker', () => {
const wrapper = shallow(
);
wrapper.setState({ focused: false });
expect(wrapper.find('TimezonePicker')).to.have.lengthOf(0);
});
});
});
describe('when timezoneIsEditable is false', () => {
it('should not render the Time Picker modal footer clickable', () => {
const wrapper = shallow(
);
expect(wrapper.find('.time_picker_modal_footer').hasClass('clickable')).to.equal(false);
});
describe('when focused is true', () => {
it('should not render the TimezonePicker', () => {
const wrapper = shallow(
);
wrapper.setState({ focused: true });
expect(wrapper.find('TimezonePicker')).to.have.lengthOf(0);
});
});
describe('when focused is false', () => {
it('should not render the TimezonePicker', () => {
const wrapper = shallow(
);
wrapper.setState({ focused: false });
expect(wrapper.find('TimezonePicker')).to.have.lengthOf(0);
});
});
});
});
describe('onClearFocus Func', () => {
it('should clear focused', () => {
const wrapper = shallow(
);
wrapper.setState({ focused: true });
wrapper.instance().onClearFocus();
expect(wrapper.state().focused).to.equal(false);
});
});
describe('handleFocusedChange Func', () => {
const wrapper = shallow(
);
it('should toggle focused', () => {
wrapper.setState({ focused: true });
wrapper.instance().handleFocusedChange();
expect(wrapper.state().focused).to.equal(false);
wrapper.instance().handleFocusedChange();
expect(wrapper.state().focused).to.equal(true);
});
it('should toggle focused onClick of modal footer', () => {
wrapper.setState({ focused: false });
wrapper.find('.time_picker_modal_footer').simulate('click');
expect(wrapper.state().focused).to.equal(true);
});
});
describe('handleTimezoneChange Func', () => {
it('should set the timezone', () => {
const wrapper = shallow(
);
wrapper.instance().handleTimezoneChange(mockTimezone);
expect(wrapper.state().timezone).to.equal(mockTimezone);
});
});
});
================================================
FILE: test/components/Timezone_Picker_spec.jsx
================================================
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon-sandbox';
import TimezonePicker from '../../src/components/Timezone/TimezonePicker';
import languageHelper from '../../src/utils/language';
import '../_helpers/adapter';
const phrases = languageHelper.get('en');
const mockTimezone = {
zoneName: 'Some Zone',
zoneAbbr: 'SZ'
};
describe('TimezonePicker', () => {
describe('TimezonePicker render', () => {
const wrapper = shallow(
);
it('should render a header with a title', () => {
expect(wrapper.find('.timezone_picker_header_title').text()).to.equal(phrases.timezonePickerTitle);
});
it('should render a Typeahead', () => {
expect(wrapper.find('OnClickOutside(Typeahead)')).to.have.lengthOf(1);
});
it('should render a close button', () => {
expect(wrapper.find('Button').prop('children')).to.equal(phrases.close);
});
});
describe('onClearFocus func', () => {
it('should callback when onClick header "back" icon', () => {
const onFocusChangeStub = sinon.stub();
const wrapper = shallow(
);
wrapper.find('.timezone_picker_modal_header').find('svg').parent().simulate('click');
expect(onFocusChangeStub.callCount).to.equal(1);
});
it('should callback when onClick Button', () => {
const onFocusChangeStub = sinon.stub();
const wrapper = shallow(
);
wrapper.find('Button').simulate('click');
expect(onFocusChangeStub.callCount).to.equal(1);
});
it('should callback when timezone change', () => {
const onFocusChangeStub = sinon.stub();
const wrapper = shallow(
);
wrapper.instance().handleTimezoneChange([mockTimezone]);
expect(onFocusChangeStub.callCount).to.equal(1);
});
});
describe('handle timezone change func', () => {
it('should callback when timezone change', () => {
const onTimezoneChangeStub = sinon.stub();
const wrapper = shallow(
);
wrapper.instance().handleTimezoneChange([mockTimezone]);
expect(onTimezoneChangeStub.callCount).to.equal(1);
expect(onTimezoneChangeStub.calledWith(mockTimezone));
});
});
});
================================================
FILE: test/components/TwelveHoursTheme_spec.jsx
================================================
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon-sandbox';
import TwelveHoursMode from '../../src/components/MaterialTheme/TwelveHoursMode';
import PickerDragHandler from '../../src/components/Picker/PickerDragHandler';
import languageHelper from '../../src/utils/language';
import '../_helpers/adapter';
const phrases = languageHelper.get('en');
describe('TwelveHoursMode', () => {
describe('TwelveHoursMode init with defaultTime', () => {
const wrapper = shallow(
);
it('should render component correctly', () => {
expect(wrapper.find('.meridiem')).to.have.lengthOf(1);
});
it('should render PickerDragHandler component', () => {
expect(wrapper.find(PickerDragHandler)).to.have.lengthOf(2);
});
it('should init correct state', () => {
expect(wrapper.state()).to.deep.equal({
hourPointerRotate: 30,
minutePointerRotate: 270
});
});
});
describe('TwelveHoursMode Func', () => {
const handleHourChange = sinon.stub();
const handleMinuteChange = sinon.stub();
const handleMeridiemChange = sinon.stub();
const wrapper = shallow(
);
it('should handleHourPointerClick', () => {
wrapper.instance().handleHourPointerClick({
time: 3,
pointerRotate: 90
});
expect(wrapper.state().hourPointerRotate).to.equal(90);
expect(handleHourChange.callCount).to.equal(1);
});
it('should handleHourPointerClick', () => {
wrapper.instance().handleMinutePointerClick({
time: 30,
pointerRotate: 180
});
expect(wrapper.state().minutePointerRotate).to.equal(180);
expect(handleMinuteChange.callCount).to.equal(1);
});
it('should handleMeridiemChange', () => {
wrapper.instance().handleMeridiemChange();
expect(handleMeridiemChange.callCount).to.equal(1);
wrapper.instance().handleMeridiemChange();
expect(handleMeridiemChange.callCount).to.equal(2);
});
});
});
================================================
FILE: test/components/TwentyFourHoursMode_spec.jsx
================================================
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon-sandbox';
import TwentyFourHoursMode from '../../src/components/MaterialTheme/TwentyFourHoursMode';
import PickerDragHandler from '../../src/components/Picker/PickerDragHandler';
import languageHelper from '../../src/utils/language';
import '../_helpers/adapter';
const phrases = languageHelper.get('en');
describe('TwentyFourHoursMode', () => {
describe('TwentyFourHoursMode Init', () => {
const wrapper = shallow(
);
it('should render PickerDragHandler component', () => {
expect(wrapper.find(PickerDragHandler)).to.have.lengthOf(1);
});
it('should init currect state', () => {
expect(wrapper.state()).to.deep.equal({
step: 0,
pointerRotate: 90
});
});
});
describe('TwentyFourHoursMode Func', () => {
const handleHourChange = sinon.stub();
const handleMinuteChange = sinon.stub();
const wrapper = shallow(
);
it('should handleHourChange', () => {
wrapper.instance().handleTimePointerClick({
time: 6,
pointerRotate: 180
});
expect(wrapper.state().pointerRotate).to.equal(180);
expect(handleHourChange.callCount).to.equal(1);
});
it('should handleStepChange', () => {
wrapper.instance().handleStepChange(1);
expect(wrapper.state()).to.deep.equal({
step: 0,
pointerRotate: 180
});
setTimeout(() => {
expect(wrapper.state()).to.deep.equal({
step: 1,
pointerRotate: 270
});
}, 300);
});
it('should handleMinuteChange', () => {
const newWrapper = shallow(
);
newWrapper.instance().handleTimePointerClick({
time: 30,
pointerRotate: 180
});
// after click minute, we close the panel & reset step state.
expect(newWrapper.state().pointerRotate).to.equal(30);
expect(newWrapper.state().step).to.equal(0);
expect(handleMinuteChange.callCount).to.equal(1);
});
});
});
================================================
FILE: test/utils_spec.js
================================================
import moment from 'moment-timezone';
import { expect } from 'chai';
import timeHelper from '../src/utils/time';
import drag from '../src/utils/drag';
import {
MAX_ABSOLUTE_POSITION,
MIN_ABSOLUTE_POSITION
} from '../src/utils/constant';
import { isSeq, head, tail, last } from '../src/utils/func';
describe('Functional utils', () => {
describe('isSeq', () => {
it('should correctly detect a sequence', () => {
const isSequence = [isSeq('foo'), isSeq('foo'.split())].every(e => e === true);
const isNotSequence = [isSeq({ message: 'foo' }), isSeq(8), isSeq(true)].every(e => e === false);
expect(isSequence).to.equal(true);
expect(isNotSequence).to.equal(true);
});
});
describe('head', () => {
it('should return the first element of a sequence', () => {
expect(head('foo')).to.equal('f');
expect(head('foo'.split(''))).to.equal('f');
});
});
describe('tail', () => {
it('should return the last elements of a sequence', () => {
expect(tail('foo')).to.equal('oo');
expect(tail('foo'.split(''))).to.deep.equal(['o', 'o']);
});
});
describe('last', () => {
it('should return the last element of a sequence', () => {
expect(last('foo')).to.equal('o');
expect(last('foo'.split(''))).to.equal('o');
});
});
});
// because mocha doesn't play nice with arrow functions 😞
const tz = timeHelper.guessUserTz();
const time24 = moment().tz(tz.zoneName).format('HH:mmA').split(/:/);
const time12 = moment().tz(tz.zoneName).format('hh:mmA').split(/:/);
const modes = [24, 12];
const meridies = ['AM', 'PM']; // yes, this is the correct plural 😜
describe('Time utils', () => {
describe('getCurrentTime()', () => {
it('should return the current time as a string in 24h format', () => {
const timeString = timeHelper.current();
expect(timeString).to.equal(time24.join(':').slice(0, 5));
});
});
describe('given a call to getValidTimeData()', () => {
describe('when passed no arguments', () => {
it('then it should default to the current local time in 24h mode', () => {
const testTimeData = timeHelper.time();
const timeData = {
hour12: head(time12).replace(/^0/, ''),
hour24: head(time24),
minute: last(time24).slice(0, 2),
meridiem: null,
mode: 24,
timezone: tz.zoneName
};
expect(testTimeData).to.deep.equal(timeData);
});
});
describe('when passed only a mode', () => {
it('then it should default to the current local time, with user-specified mode', () => {
modes.forEach((mode) => {
const testTimeData = timeHelper.time({
timeMode: mode
});
const timeData = {
mode,
hour12: head(time12).replace(/^0/, ''),
hour24: head(time24),
minute: last(time24).slice(0, 2),
meridiem: mode === 12 ? last(time12).slice(2) : null,
timezone: tz.zoneName
};
expect(testTimeData).to.deep.equal(timeData);
});
});
});
describe('when we passed only a meridiem', () => {
it('then it should default to the current local time, in 12h mode, ignoring meridiem', () => {
meridies.forEach((meridiem) => {
const testTimeData = timeHelper.time({ meridiem });
const timeData = {
hour12: head(time12).replace(/^0/, ''),
hour24: head(time24),
minute: last(time24).slice(0, 2),
meridiem: last(time12).slice(2),
mode: 12,
timezone: tz.zoneName
};
expect(testTimeData).to.deep.equal(timeData);
});
});
});
});
describe('Test getValidateTime func', () => {
it('should return 00 when get undefined', () => {
expect(timeHelper.validate()).to.equal('00');
});
it('should return 00 when get NaN', () => {
expect(timeHelper.validate('abc')).to.equal('00');
});
it('should return itself when validate', () => {
expect(timeHelper.validate('12')).to.equal('12');
});
it('should return a string with 0', () => {
expect(timeHelper.validate('2')).to.equal('02');
});
});
describe('Test getValidateIntTime func', () => {
it('should return 0', () => {
expect(timeHelper.validateInt('a')).to.equal(0);
});
it('should return int', () => {
expect(timeHelper.validateInt('11')).to.equal(11);
});
it('should return 0', () => {
expect(timeHelper.validateInt(null)).to.equal(0);
});
});
describe('Test getStandardAbsolutePosition func', () => {
it('should return the MinPosition', () => {
expect(
drag.validatePosition(
MIN_ABSOLUTE_POSITION - 1, MIN_ABSOLUTE_POSITION, MAX_ABSOLUTE_POSITION
)
).to.equal(MIN_ABSOLUTE_POSITION);
});
it('should return the MaxPosition', () => {
expect(
drag.validatePosition(
MAX_ABSOLUTE_POSITION + 1, MAX_ABSOLUTE_POSITION, MAX_ABSOLUTE_POSITION
)
).to.equal(MAX_ABSOLUTE_POSITION);
});
});
describe('Test timezone utils function', () => {
it('should get timezone by name', () => {
expect(
timeHelper.tzForName('America/Indianapolis').zoneName
).to.equal('America/Indiana/Indianapolis');
});
it('should get timezone by city', () => {
expect(
timeHelper.tzForCity('shanghai').zoneName
).to.equal('Asia/Shanghai');
});
});
describe('Test time format function', () => {
it('should format hour', () => {
expect(timeHelper.hourFormatter('8')).to.equal('08:00');
expect(timeHelper.hourFormatter('13:1')).to.equal('13:01');
expect(timeHelper.hourFormatter('2:60')).to.equal('02:00');
});
it('should format hour with default time', () => {
expect(timeHelper.hourFormatter('', '11:11')).to.equal('11:11');
expect(timeHelper.hourFormatter()).to.equal('00:00');
});
it('should format hour with meridiem', () => {
expect(timeHelper.hourFormatter('2:6 pm')).to.equal('02:06 PM');
expect(timeHelper.hourFormatter('2:6 12')).to.equal('02:06 AM');
expect(timeHelper.hourFormatter('13:00 pm')).to.equal('01:00 PM');
});
it('should remove meridiem in time', () => {
expect(timeHelper.withoutMeridiem('08:00 PM')).to.equal('08:00');
expect(timeHelper.withoutMeridiem('08:00 AM')).to.equal('08:00');
expect(timeHelper.withoutMeridiem('08:00')).to.equal('08:00');
})
});
describe('Test times render function', () => {
it('should render full 24 hour times with 30 minutes step', () => {
const times = timeHelper.get24ModeTimes({});
expect(times.length).to.equal(48);
expect(times[0]).to.equal('00:00');
expect(times[47]).to.equal('23:30');
});
it('should render full 24 hour times with 1 hour step', () => {
const times = timeHelper.get24ModeTimes({ step: 1, unit: 'hour' });
expect(times.length).to.equal(24);
expect(times[0]).to.equal('00:00');
expect(times[23]).to.equal('23:00');
});
it('should render 24 hour times cross one day with 1 hour step', () => {
const times = timeHelper.get24ModeTimes({
from: '20',
to: '8',
step: 1,
unit: 'hour'
});
expect(times.length).to.equal(13);
expect(times[0]).to.equal('20:00');
expect(times[12]).to.equal('08:00');
});
it('should render full 12 hour times with 30 minutes step', () => {
const times = timeHelper.get12ModeTimes({});
expect(times.length).to.equal(48);
expect(times[0]).to.equal('12:00 AM');
expect(times[47]).to.equal('11:30 PM');
});
it('should render full 12 hour times with 1 hour step', () => {
const times = timeHelper.get12ModeTimes({ step: 1, unit: 'hour' });
expect(times.length).to.equal(24);
expect(times[0]).to.equal('12:00 AM');
expect(times[23]).to.equal('11:00 PM');
});
it('should render 12 hour times cross one day with 1 hour step', () => {
const times = timeHelper.get12ModeTimes({
from: '08:00 PM',
to: '08:00 AM',
step: 1,
unit: 'hour'
});
expect(times.length).to.equal(13);
expect(times[0]).to.equal('08:00 PM');
expect(times[12]).to.equal('08:00 AM');
});
});
});