react-schedule-selector
Advanced tools
Comparing version
@@ -14,2 +14,6 @@ 'use strict'; | ||
var _add_minutes = require('date-fns/add_minutes'); | ||
var _add_minutes2 = _interopRequireDefault(_add_minutes); | ||
var _add_hours = require('date-fns/add_hours'); | ||
@@ -58,8 +62,2 @@ | ||
var formatHour = function formatHour(hour) { | ||
var h = hour === 0 || hour === 12 || hour === 24 ? 12 : hour % 12; | ||
var abb = hour < 12 || hour === 24 ? 'am' : 'pm'; | ||
return '' + h + abb; | ||
}; | ||
var Wrapper = (0, _styledComponents2.default)('div').withConfig({ | ||
@@ -126,13 +124,13 @@ displayName: 'ScheduleSelector__Wrapper', | ||
var labels = [React.createElement(DateLabel, { key: -1 })]; // Ensures time labels start at correct location | ||
for (var t = _this.props.minTime; t <= _this.props.maxTime; t += 1) { | ||
_this.dates[0].forEach(function (time) { | ||
labels.push(React.createElement( | ||
TimeLabelCell, | ||
{ key: t }, | ||
{ key: time.toString() }, | ||
React.createElement( | ||
TimeText, | ||
null, | ||
formatHour(t) | ||
(0, _format2.default)(time, _this.props.timeFormat) | ||
) | ||
)); | ||
} | ||
}); | ||
return React.createElement( | ||
@@ -148,3 +146,3 @@ Column, | ||
Column, | ||
{ key: dayOfTimes[0], margin: _this.props.margin }, | ||
{ key: dayOfTimes[0].toString(), margin: _this.props.margin }, | ||
React.createElement( | ||
@@ -221,6 +219,9 @@ GridCell, | ||
_this.cellToDate = new Map(); | ||
var minutesInChunk = Math.floor(60 / props.hourlyChunks); | ||
for (var d = 0; d < props.numDays; d += 1) { | ||
var currentDay = []; | ||
for (var h = props.minTime; h <= props.maxTime; h += 1) { | ||
currentDay.push((0, _add_hours2.default)((0, _add_days2.default)(startTime, d), h)); | ||
for (var h = props.minTime; h < props.maxTime; h += 1) { | ||
for (var c = 0; c < props.hourlyChunks; c += 1) { | ||
currentDay.push((0, _add_minutes2.default)((0, _add_hours2.default)((0, _add_days2.default)(startTime, d), h), c * minutesInChunk)); | ||
} | ||
} | ||
@@ -297,4 +298,7 @@ _this.dates.push(currentDay); | ||
var targetElement = document.elementFromPoint(clientX, clientY); | ||
var cellTime = this.cellToDate.get(targetElement); | ||
return cellTime; | ||
if (targetElement) { | ||
var cellTime = this.cellToDate.get(targetElement); | ||
return cellTime; | ||
} | ||
return null; | ||
}; | ||
@@ -419,3 +423,5 @@ | ||
maxTime: 23, | ||
hourlyChunks: 1, | ||
startDate: new Date(), | ||
timeFormat: 'ha', | ||
dateFormat: 'M/D', | ||
@@ -422,0 +428,0 @@ margin: 3, |
{ | ||
"name": "react-schedule-selector", | ||
"version": "1.0.3", | ||
"version": "1.0.4", | ||
"description": "A mobile-friendly when2meet-style grid-based schedule selector", | ||
@@ -21,5 +21,6 @@ "author": "Bibek Ghimire", | ||
"postpublish": "yarn docs:deploy", | ||
"build": "yarn lib:build && yarn docs:build", | ||
"build": "yarn clean && yarn lib:build && yarn docs:build", | ||
"lint": "eslint src/**/*.{js,jsx} --quiet", | ||
"format": "prettier --write \"src/**/*.{js,jsx}\"", | ||
"clean": "rm -rf dist", | ||
"cover": "jest --coverage && cat ./coverage/lcov.info | coveralls", | ||
@@ -67,3 +68,3 @@ "test": "jest", | ||
"eslint-plugin-react": "^7.7.0", | ||
"flow-bin": "^0.71.0", | ||
"flow-bin": "^0.132.0", | ||
"jest": "^23.2.0", | ||
@@ -73,3 +74,3 @@ "jest-styled-components": "^6.2.0", | ||
"moment": "^2.22.2", | ||
"parcel-bundler": "^1.8.0", | ||
"parcel-bundler": "^1.12.4", | ||
"prettier": "^1.14.2", | ||
@@ -76,0 +77,0 @@ "react": "^16.0.0", |
@@ -5,3 +5,3 @@ # React Schedule Selector | ||
A mobile-friendly when2meet-style grid-based schedule selector built with [styled components](https://github.com/styled-components/styled-components) and [date-fns](https://date-fns.org/). | ||
A mobile-friendly when2meet-style grid-based schedule selector built with [styled components](https://github.com/styled-components/styled-components) and [date-fns](https://date-fns.org/). | ||
@@ -35,2 +35,3 @@ [Live example](http://react-schedule-selector.surge.sh/) | ||
maxTime={22} | ||
hourlyChunks={2} | ||
onChange={this.handleChange} | ||
@@ -84,3 +85,3 @@ /> | ||
**description**: The date on which the grid should start (time portion is ignored, specify start time via `minTime`) | ||
**description**: The date on which the grid should start (time portion is ignored, specify start time via `minTime`) | ||
@@ -95,3 +96,3 @@ **required**: no | ||
**description**: The number of days to show, startin from today | ||
**description**: The number of days to show, starting from today | ||
@@ -102,2 +103,12 @@ **required**: no | ||
#### `hourlyChunks` | ||
**type**: `number` | ||
**description**: How many chunks to divide each hour into (e.g. `2` divides the hour into half-hour steps, `4` into 15-minute steps) | ||
**required**: no | ||
**default value**: `1` | ||
#### `minTime` | ||
@@ -133,2 +144,12 @@ | ||
#### `timeFormat` | ||
**type**: `string` | ||
**description**: The [time format](https://date-fns.org/v1.29.0/docs/format) to be used for the row labels | ||
**required**: no | ||
**default value**: `'ha'` | ||
#### `margin` | ||
@@ -135,0 +156,0 @@ |
@@ -14,23 +14,45 @@ // @flow | ||
} | ||
* { | ||
box-sizing: border-box; | ||
} | ||
` | ||
const MainDiv = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
` | ||
const IntroText = styled.div` | ||
width: 100%; | ||
text-align: center; | ||
` | ||
const ScheduleSelectorCard = styled.div` | ||
border-radius: 25px; | ||
box-shadow: 0px 0px 2px #222222; | ||
box-shadow: 10px 2px 30px rgba(0, 0, 0, 0.15); | ||
padding: 20px; | ||
width: 90%; | ||
max-width: 800px; | ||
& > * { | ||
flex-grow: 1; | ||
} | ||
` | ||
const EmojiCell = styled.span.attrs({ | ||
role: 'img', | ||
'aria-label': 'checked' | ||
})` | ||
text-align: center; | ||
const Links = styled.div` | ||
display: flex; | ||
justify-content: center; | ||
border: 1px solid rgba(0, 0, 0, 0.3); | ||
&:hover { | ||
background-color: rgba(236, 146, 64, 0.3); | ||
} | ||
margin-top: 20px; | ||
` | ||
const ExternalLink = styled.a` | ||
background-color: ${props => props.color}; | ||
color: white; | ||
padding: 10px; | ||
border-radius: 3px; | ||
cursor: pointer; | ||
text-decoration: none; | ||
margin: 5px; | ||
` | ||
type StateType = { | ||
@@ -47,9 +69,9 @@ schedule: Array<Date> | ||
// eslint-disable-next-line | ||
renderCustomCell = (time: Date, selected: boolean, innerRef: (HTMLElement => void)) => (selected ? <EmojiCell innerRef={innerRef}>✅</EmojiCell> : <EmojiCell innerRef={innerRef}>❌</EmojiCell>) | ||
render(): React.Element<*> { | ||
return ( | ||
<div> | ||
<h1>Schedule Selector with Custom Renderer</h1> | ||
<MainDiv> | ||
<IntroText> | ||
<h1>React Schedule Selector</h1> | ||
<p>Tap to select one time or drag to select multiple times at once.</p> | ||
</IntroText> | ||
<ScheduleSelectorCard> | ||
@@ -59,11 +81,21 @@ <ScheduleSelector | ||
maxTime={20} | ||
numDays={5} | ||
startDate={new Date('Fri May 18 2018 17:57:06 GMT-0700 (PDT)')} | ||
numDays={7} | ||
selection={this.state.schedule} | ||
onChange={this.handleDateChange} | ||
renderDateCell={this.renderCustomCell} | ||
dateFormat="ddd" | ||
hourlyChunks={2} | ||
timeFormat="h:mma" | ||
/> | ||
</ScheduleSelectorCard> | ||
</div> | ||
<Links> | ||
<ExternalLink color="#24292e" href="https://github.com/bibekg/react-schedule-selector"> | ||
GitHub | ||
</ExternalLink> | ||
<ExternalLink color="#cb3838" href="https://npmjs.com/package/react-schedule-selector"> | ||
NPM | ||
</ExternalLink> | ||
<ExternalLink color="#292929" href="https://medium.com/@bibekg/react-schedule-selector-6cd5bf1f4968"> | ||
Medium | ||
</ExternalLink> | ||
</Links> | ||
</MainDiv> | ||
) | ||
@@ -70,0 +102,0 @@ } |
@@ -7,2 +7,3 @@ // @flow | ||
// Import only the methods we need from date-fns in order to keep build size small | ||
import addMinutes from 'date-fns/add_minutes' | ||
import addHours from 'date-fns/add_hours' | ||
@@ -18,8 +19,2 @@ import addDays from 'date-fns/add_days' | ||
const formatHour = (hour: number): string => { | ||
const h = hour === 0 || hour === 12 || hour === 24 ? 12 : hour % 12 | ||
const abb = hour < 12 || hour === 24 ? 'am' : 'pm' | ||
return `${h}${abb}` | ||
} | ||
const Wrapper = styled.div` | ||
@@ -96,3 +91,5 @@ display: flex; | ||
maxTime: number, | ||
hourlyChunks: number, | ||
dateFormat: string, | ||
timeFormat: string, | ||
margin: number, | ||
@@ -131,3 +128,3 @@ unselectedColor: string, | ||
static defaultProps = { | ||
static defaultProps: PropsType = { | ||
selection: [], | ||
@@ -138,3 +135,5 @@ selectionScheme: 'square', | ||
maxTime: 23, | ||
hourlyChunks: 1, | ||
startDate: new Date(), | ||
timeFormat: 'ha', | ||
dateFormat: 'M/D', | ||
@@ -155,6 +154,9 @@ margin: 3, | ||
this.cellToDate = new Map() | ||
const minutesInChunk = Math.floor(60 / props.hourlyChunks) | ||
for (let d = 0; d < props.numDays; d += 1) { | ||
const currentDay = [] | ||
for (let h = props.minTime; h <= props.maxTime; h += 1) { | ||
currentDay.push(addHours(addDays(startTime, d), h)) | ||
for (let h = props.minTime; h < props.maxTime; h += 1) { | ||
for (let c = 0; c < props.hourlyChunks; c += 1) { | ||
currentDay.push(addMinutes(addHours(addDays(startTime, d), h), c * minutesInChunk)) | ||
} | ||
} | ||
@@ -224,4 +226,7 @@ this.dates.push(currentDay) | ||
const targetElement = document.elementFromPoint(clientX, clientY) | ||
const cellTime = this.cellToDate.get(targetElement) | ||
return cellTime | ||
if (targetElement) { | ||
const cellTime = this.cellToDate.get(targetElement) | ||
return cellTime | ||
} | ||
return null | ||
} | ||
@@ -305,9 +310,9 @@ | ||
const labels = [<DateLabel key={-1} />] // Ensures time labels start at correct location | ||
for (let t = this.props.minTime; t <= this.props.maxTime; t += 1) { | ||
this.dates[0].forEach(time => { | ||
labels.push( | ||
<TimeLabelCell key={t}> | ||
<TimeText>{formatHour(t)}</TimeText> | ||
<TimeLabelCell key={time.toString()}> | ||
<TimeText>{formatDate(time, this.props.timeFormat)}</TimeText> | ||
</TimeLabelCell> | ||
) | ||
} | ||
}) | ||
return <Column margin={this.props.margin}>{labels}</Column> | ||
@@ -317,3 +322,3 @@ } | ||
renderDateColumn = (dayOfTimes: Array<Date>) => ( | ||
<Column key={dayOfTimes[0]} margin={this.props.margin}> | ||
<Column key={dayOfTimes[0].toString()} margin={this.props.margin}> | ||
<GridCell margin={this.props.margin}> | ||
@@ -320,0 +325,0 @@ <DateLabel>{formatDate(dayOfTimes[0], this.props.dateFormat)}</DateLabel> |
@@ -22,174 +22,195 @@ /* eslint-disable flowtype/* */ | ||
beforeAll(() => { | ||
document.elementFromPoint = jest.fn() | ||
document.removeEventListener = jest.fn() | ||
}) | ||
describe('snapshot tests', () => { | ||
it('renders correctly with default render logic', () => { | ||
const component = renderer.create( | ||
<ScheduleSelector selection={getTestSchedule()} startDate={startDate} numDays={5} onChange={() => undefined} /> | ||
) | ||
const tree = component.toJSON() | ||
expect(tree).toMatchSnapshot() | ||
describe('ScheduleSelector', () => { | ||
beforeAll(() => { | ||
const fakeElement = document.createElement('div') | ||
document.elementFromPoint = jest.fn().mockReturnValue(fakeElement) | ||
document.removeEventListener = jest.fn() | ||
}) | ||
it('renders correctly with custom render prop', () => { | ||
const customDateCellRenderer = (date, selected) => ( | ||
<div className={`${selected && 'selected'} test-date-cell-renderer`}>{date.toDateString()}</div> | ||
) | ||
describe('snapshot tests', () => { | ||
it('renders correctly with default render logic', () => { | ||
const component = renderer.create( | ||
<ScheduleSelector selection={getTestSchedule()} startDate={startDate} numDays={5} onChange={() => undefined} /> | ||
) | ||
const tree = component.toJSON() | ||
expect(tree).toMatchSnapshot() | ||
}) | ||
const component = renderer.create( | ||
<ScheduleSelector | ||
selection={getTestSchedule()} | ||
startDate={startDate} | ||
numDays={5} | ||
onChange={() => undefined} | ||
renderDateCell={customDateCellRenderer} | ||
/> | ||
) | ||
it('renders correctly with custom render prop', () => { | ||
const customDateCellRenderer = (date, selected) => ( | ||
<div className={`${selected && 'selected'} test-date-cell-renderer`}>{date.toDateString()}</div> | ||
) | ||
const tree = component.toJSON() | ||
expect(tree).toMatchSnapshot() | ||
const component = renderer.create( | ||
<ScheduleSelector | ||
selection={getTestSchedule()} | ||
startDate={startDate} | ||
numDays={5} | ||
onChange={() => undefined} | ||
renderDateCell={customDateCellRenderer} | ||
/> | ||
) | ||
const tree = component.toJSON() | ||
expect(tree).toMatchSnapshot() | ||
}) | ||
}) | ||
}) | ||
it('getTimeFromTouchEvent returns the time for that cell', () => { | ||
const component = shallow(<ScheduleSelector />) | ||
const mainSpy = jest.spyOn(component.instance(), 'getTimeFromTouchEvent') | ||
const mockCellTime = new Date() | ||
const mockEvent = { | ||
touches: [{ clientX: 1, clientY: 2 }] | ||
} | ||
const cellToDateSpy = jest.spyOn(component.instance().cellToDate, 'get').mockReturnValue(mockCellTime) | ||
it('getTimeFromTouchEvent returns the time for that cell', () => { | ||
const component = shallow(<ScheduleSelector />) | ||
const mainSpy = jest.spyOn(component.instance(), 'getTimeFromTouchEvent') | ||
const mockCellTime = new Date() | ||
const mockEvent = { | ||
touches: [{ clientX: 1, clientY: 2 }] | ||
} | ||
const cellToDateSpy = jest.spyOn(component.instance().cellToDate, 'get').mockReturnValue(mockCellTime) | ||
component.instance().getTimeFromTouchEvent(mockEvent) | ||
component.instance().getTimeFromTouchEvent(mockEvent) | ||
expect(document.elementFromPoint).toHaveBeenCalledWith(mockEvent.touches[0].clientX, mockEvent.touches[0].clientY) | ||
expect(cellToDateSpy).toHaveBeenCalled() | ||
expect(mainSpy).toHaveReturnedWith(mockCellTime) | ||
expect(document.elementFromPoint).toHaveBeenCalledWith(mockEvent.touches[0].clientX, mockEvent.touches[0].clientY) | ||
expect(cellToDateSpy).toHaveBeenCalled() | ||
expect(mainSpy).toHaveReturnedWith(mockCellTime) | ||
mainSpy.mockRestore() | ||
cellToDateSpy.mockRestore() | ||
}) | ||
mainSpy.mockRestore() | ||
cellToDateSpy.mockRestore() | ||
}) | ||
it('endSelection calls the onChange prop and resets selection state', () => { | ||
const changeSpy = jest.fn() | ||
const component = shallow(<ScheduleSelector onChange={changeSpy} />) | ||
const setStateSpy = jest.spyOn(component.instance(), 'setState') | ||
it('endSelection calls the onChange prop and resets selection state', () => { | ||
const changeSpy = jest.fn() | ||
const component = shallow(<ScheduleSelector onChange={changeSpy} />) | ||
const setStateSpy = jest.spyOn(component.instance(), 'setState') | ||
component.instance().endSelection() | ||
component.instance().endSelection() | ||
expect(changeSpy).toHaveBeenCalledWith(component.state('selectionDraft')) | ||
expect(setStateSpy).toHaveBeenCalledWith({ | ||
selectionType: null, | ||
selectionStart: null | ||
}) | ||
expect(changeSpy).toHaveBeenCalledWith(component.state('selectionDraft')) | ||
expect(setStateSpy).toHaveBeenCalledWith({ | ||
selectionType: null, | ||
selectionStart: null | ||
setStateSpy.mockRestore() | ||
}) | ||
setStateSpy.mockRestore() | ||
}) | ||
describe('mouse handlers', () => { | ||
const spies = {} | ||
let component | ||
let anInstance | ||
describe('mouse handlers', () => { | ||
const spies = {} | ||
let component | ||
let anInstance | ||
beforeAll(() => { | ||
spies.onMouseDown = jest.spyOn(ScheduleSelector.prototype, 'handleSelectionStartEvent') | ||
spies.onMouseEnter = jest.spyOn(ScheduleSelector.prototype, 'handleMouseEnterEvent') | ||
spies.onMouseUp = jest.spyOn(ScheduleSelector.prototype, 'handleMouseUpEvent') | ||
component = shallow(<ScheduleSelector />) | ||
anInstance = component.find('.rgdp__grid-cell').first() | ||
}) | ||
beforeAll(() => { | ||
spies.onMouseDown = jest.spyOn(ScheduleSelector.prototype, 'handleSelectionStartEvent') | ||
spies.onMouseEnter = jest.spyOn(ScheduleSelector.prototype, 'handleMouseEnterEvent') | ||
spies.onMouseUp = jest.spyOn(ScheduleSelector.prototype, 'handleMouseUpEvent') | ||
component = shallow(<ScheduleSelector />) | ||
anInstance = component.find('.rgdp__grid-cell').first() | ||
}) | ||
test.each([['onMouseDown'], ['onMouseEnter'], ['onMouseUp']])('calls the handler for %s', name => { | ||
anInstance.prop(name)() | ||
expect(spies[name]).toHaveBeenCalled() | ||
spies[name].mockClear() | ||
}) | ||
test.each([['onMouseDown'], ['onMouseEnter'], ['onMouseUp']])('calls the handler for %s', name => { | ||
anInstance.prop(name)() | ||
expect(spies[name]).toHaveBeenCalled() | ||
spies[name].mockClear() | ||
}) | ||
afterAll(() => { | ||
Object.keys(spies).forEach(spyName => { | ||
spies[spyName].mockRestore() | ||
afterAll(() => { | ||
Object.keys(spies).forEach(spyName => { | ||
spies[spyName].mockRestore() | ||
}) | ||
}) | ||
}) | ||
}) | ||
describe('touch handlers', () => { | ||
const spies = {} | ||
let component | ||
let anInstance | ||
const mockEvent = {} | ||
describe('touch handlers', () => { | ||
const spies = {} | ||
let component | ||
let anInstance | ||
const mockEvent = {} | ||
beforeAll(() => { | ||
spies.onTouchStart = jest.spyOn(ScheduleSelector.prototype, 'handleSelectionStartEvent') | ||
spies.onTouchMove = jest.spyOn(ScheduleSelector.prototype, 'handleTouchMoveEvent') | ||
spies.onTouchEnd = jest.spyOn(ScheduleSelector.prototype, 'handleTouchEndEvent') | ||
component = shallow(<ScheduleSelector />) | ||
anInstance = component.find('.rgdp__grid-cell').first() | ||
mockEvent.touches = [{ clientX: 1, clientY: 2 }, { clientX: 100, clientY: 200 }] | ||
}) | ||
beforeAll(() => { | ||
spies.onTouchStart = jest.spyOn(ScheduleSelector.prototype, 'handleSelectionStartEvent') | ||
spies.onTouchMove = jest.spyOn(ScheduleSelector.prototype, 'handleTouchMoveEvent') | ||
spies.onTouchEnd = jest.spyOn(ScheduleSelector.prototype, 'handleTouchEndEvent') | ||
component = shallow(<ScheduleSelector />) | ||
anInstance = component.find('.rgdp__grid-cell').first() | ||
mockEvent.touches = [{ clientX: 1, clientY: 2 }, { clientX: 100, clientY: 200 }] | ||
}) | ||
test.each([['onTouchStart', []], ['onTouchMove', [mockEvent]], ['onTouchEnd', []]])( | ||
'calls the handler for %s', | ||
(name, args) => { | ||
anInstance.prop(name)(...args) | ||
expect(spies[name]).toHaveBeenCalled() | ||
spies[name].mockClear() | ||
} | ||
) | ||
test.each([['onTouchStart', []], ['onTouchMove', [mockEvent]], ['onTouchEnd', []]])( | ||
'calls the handler for %s', | ||
(name, args) => { | ||
anInstance.prop(name)(...args) | ||
expect(spies[name]).toHaveBeenCalled() | ||
spies[name].mockClear() | ||
} | ||
) | ||
afterAll(() => { | ||
Object.keys(spies).forEach(spyName => { | ||
spies[spyName].mockRestore() | ||
afterAll(() => { | ||
Object.keys(spies).forEach(spyName => { | ||
spies[spyName].mockRestore() | ||
}) | ||
}) | ||
}) | ||
}) | ||
it('handleTouchMoveEvent updates the availability draft', () => { | ||
const mockCellTime = new Date() | ||
const getTimeSpy = jest.spyOn(ScheduleSelector.prototype, 'getTimeFromTouchEvent').mockReturnValue(mockCellTime) | ||
const updateDraftSpy = jest.spyOn(ScheduleSelector.prototype, 'updateAvailabilityDraft') | ||
it('handleTouchMoveEvent updates the availability draft', () => { | ||
const mockCellTime = new Date() | ||
const getTimeSpy = jest.spyOn(ScheduleSelector.prototype, 'getTimeFromTouchEvent').mockReturnValue(mockCellTime) | ||
const updateDraftSpy = jest.spyOn(ScheduleSelector.prototype, 'updateAvailabilityDraft') | ||
const component = shallow(<ScheduleSelector />) | ||
component.instance().handleTouchMoveEvent({}) | ||
expect(updateDraftSpy).toHaveBeenCalledWith(mockCellTime) | ||
const component = shallow(<ScheduleSelector />) | ||
component.instance().handleTouchMoveEvent({}) | ||
expect(updateDraftSpy).toHaveBeenCalledWith(mockCellTime) | ||
getTimeSpy.mockRestore() | ||
updateDraftSpy.mockRestore() | ||
}) | ||
getTimeSpy.mockRestore() | ||
updateDraftSpy.mockRestore() | ||
}) | ||
describe('updateAvailabilityDraft', () => { | ||
it.each([['add', 1], ['remove', 1], ['add', -1], ['remove', -1]])( | ||
'updateAvailabilityDraft handles addition and removals, for forward and reversed drags', | ||
(type, amount, done) => { | ||
const start = moment(startDate) | ||
.add(5, 'hours') | ||
.toDate() | ||
const end = moment(start) | ||
.add(amount, 'hours') | ||
.toDate() | ||
const outOfRangeOne = moment(start) | ||
.add(amount + 5, 'hours') | ||
.toDate() | ||
describe('updateAvailabilityDraft', () => { | ||
it.each([['add', 1], ['remove', 1], ['add', -1], ['remove', -1]])( | ||
'updateAvailabilityDraft handles addition and removals, for forward and reversed drags', | ||
(type, amount, done) => { | ||
const start = moment(startDate) | ||
.add(5, 'hours') | ||
.toDate() | ||
const end = moment(start) | ||
.add(amount, 'hours') | ||
.toDate() | ||
const outOfRangeOne = moment(start) | ||
.add(amount + 5, 'hours') | ||
.toDate() | ||
const setStateSpy = jest.spyOn(ScheduleSelector.prototype, 'setState') | ||
const component = shallow( | ||
<ScheduleSelector | ||
// Initialize the initial selection based on whether this test is adding or removing | ||
selection={type === 'remove' ? [start, end, outOfRangeOne] : [outOfRangeOne]} | ||
startDate={start} | ||
numDays={5} | ||
minTime={0} | ||
maxTime={23} | ||
/> | ||
) | ||
component.setState( | ||
{ | ||
selectionType: type, | ||
selectionStart: start | ||
}, | ||
() => { | ||
component.instance().updateAvailabilityDraft(end, () => { | ||
expect(setStateSpy).toHaveBeenLastCalledWith({ selectionDraft: expect.arrayContaining([]) }) | ||
setStateSpy.mockRestore() | ||
done() | ||
}) | ||
} | ||
) | ||
} | ||
) | ||
it('updateAvailabilityDraft handles a single cell click correctly', done => { | ||
const setStateSpy = jest.spyOn(ScheduleSelector.prototype, 'setState') | ||
const component = shallow( | ||
<ScheduleSelector | ||
// Initialize the initial selection based on whether this test is adding or removing | ||
selection={type === 'remove' ? [start, end, outOfRangeOne] : [outOfRangeOne]} | ||
startDate={start} | ||
numDays={5} | ||
minTime={0} | ||
maxTime={23} | ||
/> | ||
) | ||
const component = shallow(<ScheduleSelector />) | ||
const start = startDate | ||
component.setState( | ||
{ | ||
selectionType: type, | ||
selectionType: 'add', | ||
selectionStart: start | ||
}, | ||
() => { | ||
component.instance().updateAvailabilityDraft(end, () => { | ||
expect(setStateSpy).toHaveBeenLastCalledWith({ selectionDraft: expect.arrayContaining([]) }) | ||
component.instance().updateAvailabilityDraft(null, () => { | ||
expect(setStateSpy).toHaveBeenCalledWith({ selectionDraft: expect.arrayContaining([]) }) | ||
setStateSpy.mockRestore() | ||
@@ -200,111 +221,124 @@ done() | ||
) | ||
} | ||
) | ||
it('updateAvailabilityDraft handles a single cell click correctly', done => { | ||
const setStateSpy = jest.spyOn(ScheduleSelector.prototype, 'setState') | ||
const component = shallow(<ScheduleSelector />) | ||
const start = startDate | ||
component.setState( | ||
{ | ||
selectionType: 'add', | ||
selectionStart: start | ||
}, | ||
() => { | ||
component.instance().updateAvailabilityDraft(null, () => { | ||
expect(setStateSpy).toHaveBeenCalledWith({ selectionDraft: expect.arrayContaining([]) }) | ||
setStateSpy.mockRestore() | ||
done() | ||
}) | ||
} | ||
) | ||
}) | ||
}) | ||
}) | ||
describe('componentDidMount', () => { | ||
it('runs properly on a full mount', () => { | ||
mount(<ScheduleSelector />) | ||
describe('componentDidMount', () => { | ||
it('runs properly on a full mount', () => { | ||
mount(<ScheduleSelector />) | ||
}) | ||
}) | ||
}) | ||
describe('componentWillUnmount', () => { | ||
it('removes the mouseup event listener', () => { | ||
const component = shallow(<ScheduleSelector />) | ||
const endSelectionMethod = component.instance().endSelection | ||
component.unmount() | ||
expect(document.removeEventListener).toHaveBeenCalledWith('mouseup', endSelectionMethod) | ||
}) | ||
describe('componentWillUnmount', () => { | ||
it('removes the mouseup event listener', () => { | ||
const component = shallow(<ScheduleSelector />) | ||
const endSelectionMethod = component.instance().endSelection | ||
component.unmount() | ||
expect(document.removeEventListener).toHaveBeenCalledWith('mouseup', endSelectionMethod) | ||
}) | ||
it('removes the touchmove event listeners from the date cells', () => { | ||
const component = shallow(<ScheduleSelector />) | ||
const mockDateCell = { | ||
removeEventListener: jest.fn() | ||
} | ||
component.instance().cellToDate.set(mockDateCell, new Date()) | ||
component.unmount() | ||
it('removes the touchmove event listeners from the date cells', () => { | ||
const component = shallow(<ScheduleSelector />) | ||
const mockDateCell = { | ||
removeEventListener: jest.fn() | ||
} | ||
component.instance().cellToDate.set(mockDateCell, new Date()) | ||
component.unmount() | ||
expect(mockDateCell.removeEventListener).toHaveBeenCalledWith('touchmove', expect.anything()) | ||
expect(mockDateCell.removeEventListener).toHaveBeenCalledWith('touchmove', expect.anything()) | ||
}) | ||
}) | ||
}) | ||
describe('componentWillReceiveProps', () => { | ||
it('makes the selection prop override the existing selection draft', () => { | ||
const setStateSpy = jest.spyOn(ScheduleSelector.prototype, 'setState') | ||
const component = shallow(<ScheduleSelector />) | ||
const mockNextProps = { | ||
selection: ['foo', 'bar'] | ||
} | ||
component.instance().componentWillReceiveProps(mockNextProps) | ||
expect(setStateSpy).toHaveBeenCalledWith({ | ||
selectionDraft: expect.arrayContaining(mockNextProps.selection) | ||
describe('componentWillReceiveProps', () => { | ||
it('makes the selection prop override the existing selection draft', () => { | ||
const setStateSpy = jest.spyOn(ScheduleSelector.prototype, 'setState') | ||
const component = shallow(<ScheduleSelector />) | ||
const mockNextProps = { | ||
selection: ['foo', 'bar'] | ||
} | ||
component.instance().componentWillReceiveProps(mockNextProps) | ||
expect(setStateSpy).toHaveBeenCalledWith({ | ||
selectionDraft: expect.arrayContaining(mockNextProps.selection) | ||
}) | ||
}) | ||
}) | ||
}) | ||
describe('handleTouchEndEvent', () => { | ||
const component = shallow(<ScheduleSelector />) | ||
const setStateSpy = jest.spyOn(component.instance(), 'setState') | ||
const updateDraftSpy = jest.spyOn(component.instance(), 'updateAvailabilityDraft').mockImplementation((a, b) => b()) | ||
const endSelectionSpy = jest.spyOn(component.instance(), 'endSelection').mockImplementation(jest.fn()) | ||
describe('handleTouchEndEvent', () => { | ||
const component = shallow(<ScheduleSelector />) | ||
const setStateSpy = jest.spyOn(component.instance(), 'setState') | ||
const updateDraftSpy = jest.spyOn(component.instance(), 'updateAvailabilityDraft').mockImplementation((a, b) => b()) | ||
const endSelectionSpy = jest.spyOn(component.instance(), 'endSelection').mockImplementation(jest.fn()) | ||
it('handles regular events correctly', () => { | ||
component.instance().handleTouchEndEvent() | ||
it('handles regular events correctly', () => { | ||
component.instance().handleTouchEndEvent() | ||
expect(setStateSpy).toHaveBeenLastCalledWith({ isTouchDragging: false }) | ||
expect(updateDraftSpy).toHaveBeenCalled() | ||
expect(endSelectionSpy).toHaveBeenCalled() | ||
expect(setStateSpy).toHaveBeenLastCalledWith({ isTouchDragging: false }) | ||
expect(updateDraftSpy).toHaveBeenCalled() | ||
expect(endSelectionSpy).toHaveBeenCalled() | ||
setStateSpy.mockClear() | ||
updateDraftSpy.mockClear() | ||
endSelectionSpy.mockClear() | ||
setStateSpy.mockClear() | ||
updateDraftSpy.mockClear() | ||
endSelectionSpy.mockClear() | ||
}) | ||
it('handles single-touch-tap events correctly', done => { | ||
// Set touch dragging to true and make sure updateDraftSpy doesn't get called | ||
component.setState( | ||
{ | ||
isTouchDragging: true | ||
}, | ||
() => { | ||
component.instance().handleTouchEndEvent() | ||
expect(updateDraftSpy).not.toHaveBeenCalled() | ||
expect(endSelectionSpy).toHaveBeenCalled() | ||
expect(setStateSpy).toHaveBeenLastCalledWith({ isTouchDragging: false }) | ||
setStateSpy.mockRestore() | ||
updateDraftSpy.mockRestore() | ||
endSelectionSpy.mockRestore() | ||
done() | ||
} | ||
) | ||
}) | ||
}) | ||
it('handles single-touch-tap events correctly', done => { | ||
// Set touch dragging to true and make sure updateDraftSpy doesn't get called | ||
component.setState( | ||
{ | ||
isTouchDragging: true | ||
}, | ||
() => { | ||
component.instance().handleTouchEndEvent() | ||
expect(updateDraftSpy).not.toHaveBeenCalled() | ||
expect(endSelectionSpy).toHaveBeenCalled() | ||
expect(setStateSpy).toHaveBeenLastCalledWith({ isTouchDragging: false }) | ||
setStateSpy.mockRestore() | ||
updateDraftSpy.mockRestore() | ||
endSelectionSpy.mockRestore() | ||
done() | ||
describe('preventScroll', () => { | ||
it('prevents the event default', () => { | ||
const event = { | ||
preventDefault: jest.fn() | ||
} | ||
) | ||
preventScroll(event) | ||
expect(event.preventDefault).toHaveBeenCalled() | ||
}) | ||
}) | ||
}) | ||
describe('preventScroll', () => { | ||
it('prevents the event default', () => { | ||
const event = { | ||
preventDefault: jest.fn() | ||
} | ||
preventScroll(event) | ||
expect(event.preventDefault).toHaveBeenCalled() | ||
describe('minute-level resolution', () => { | ||
it('splits hours using the hourlyChunks prop', () => { | ||
// 15-minute resolution | ||
const component = shallow(<ScheduleSelector minTime={1} maxTime={2} hourlyChunks={4} />) | ||
expect(component.find('ScheduleSelector__TimeLabelCell')).toHaveLength(4) | ||
// 5-minute resolution | ||
const componentTwo = shallow(<ScheduleSelector minTime={1} maxTime={2} hourlyChunks={12} />) | ||
expect(componentTwo.find('ScheduleSelector__TimeLabelCell')).toHaveLength(12) | ||
}) | ||
it('formats the time column using the timeFormat prop', () => { | ||
// 15-minute resolution | ||
const component = shallow(<ScheduleSelector minTime={1} maxTime={2} timeFormat="h:mma" hourlyChunks={4} />) | ||
expect( | ||
component | ||
.find('ScheduleSelector__TimeLabelCell') | ||
.at(1) | ||
.render() | ||
.text() | ||
).toEqual('1:15am') | ||
// 5-minute resolution | ||
const componentTwo = shallow(<ScheduleSelector minTime={1} maxTime={2} timeFormat="h:mma" hourlyChunks={12} />) | ||
expect( | ||
componentTwo | ||
.find('ScheduleSelector__TimeLabelCell') | ||
.at(1) | ||
.render() | ||
.text() | ||
).toEqual('1:05am') | ||
}) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
39
2.63%2288
5.15%196
12%774069
-6.93%