diff --git a/__fixtures__/stateAndProps.js b/__fixtures__/stateAndProps.js
index 20bf62783..6cc7746fa 100644
--- a/__fixtures__/stateAndProps.js
+++ b/__fixtures__/stateAndProps.js
@@ -1,10 +1,16 @@
import { defaultKeys } from 'lib/default-config'
import {items} from './itemsAndGroups'
+
+export const visibleTimeStart = 1540501200000
+export const visibleTimeEnd = 1540587600000
+
export const props = {
keys: defaultKeys,
lineHeight: 30,
stackItems: true,
- itemHeightRatio: 0.75
+ itemHeightRatio: 0.75,
+ visibleTimeEnd,
+ visibleTimeStart,
}
export const propsNoStack = {
@@ -12,8 +18,6 @@ export const propsNoStack = {
stackItems: false,
}
-export const visibleTimeStart = 1540501200000
-export const visibleTimeEnd = 1540587600000
export const state = {
draggingItem: undefined,
diff --git a/__tests__/components/Headers/CustomHeader.test.js b/__tests__/components/Headers/CustomHeader.test.js
new file mode 100644
index 000000000..34779bb93
--- /dev/null
+++ b/__tests__/components/Headers/CustomHeader.test.js
@@ -0,0 +1,371 @@
+import React from 'react'
+import { render, cleanup, prettyDOM } from 'react-testing-library'
+import Timeline from 'lib/Timeline'
+import DateHeader from 'lib/headers/DateHeader'
+import SidebarHeader from 'lib/headers/SidebarHeader'
+import TimelineHeaders from 'lib/headers/TimelineHeaders'
+import CustomHeader from 'lib/headers/CustomHeader'
+import { RenderHeadersWrapper } from '../../test-utility/header-renderer'
+import { getCustomHeadersInTimeline } from '../../test-utility/headerRenderers'
+import { parsePxToNumbers } from '../../test-utility/index'
+
+import 'jest-dom/extend-expect'
+import moment from 'moment'
+
+describe('CustomHeader Component Test', () => {
+ afterEach(cleanup)
+
+ it('Given CustomHeader When pass a unit to it Then header should render that unit', () => {
+ const { getAllByTestId } = render(
+ getCustomHeadersInTimeline({
+ unit: 'month',
+ timelineState: {
+ timelineUnit: 'month',
+ canvasTimeStart: moment.utc('1/6/2018', 'DD/MM/YYYY').valueOf(),
+ canvasTimeEnd: moment.utc('1/6/2020', 'DD/MM/YYYY').valueOf(),
+ visibleTimeStart: moment.utc('1/1/2019', 'DD/MM/YYYY').valueOf(),
+ visibleTimeEnd: moment.utc('1/1/2020', 'DD/MM/YYYY').valueOf()
+ }
+ })
+ )
+ const intervals = getAllByTestId('customHeaderInterval')
+ const start = moment(intervals[0].textContent, 'DD/MM/YYYY')
+ const end = moment(intervals[1].textContent, 'DD/MM/YYYY')
+ expect(end.diff(start, 'M')).toBe(1)
+ })
+ it('Given CustomHeader When pass a style props with (width, position) Then it should not override the default values', () => {
+ const { getByTestId } = render(
+ getCustomHeadersInTimeline({
+ props: { style: { width: 0, position: 'fixed' } }
+ })
+ )
+ const { width, position } = getComputedStyle(getByTestId('customHeader'))
+ expect(width).not.toBe('0px')
+ expect(position).not.toBe('fixed')
+ })
+
+ it('Given CustomHeader When pass a style props other than (width, position) Then it should rendered Correctly', () => {
+ const { getByTestId } = render(
+ getCustomHeadersInTimeline({ props: { style: { color: 'white' } } })
+ )
+ const { color } = getComputedStyle(getByTestId('customHeader'))
+ expect(color).toBe('white')
+ })
+
+ it('Given CustomHeader When pass an interval style with (width, position and left) Then it should not override the default values', () => {
+ const { getByTestId } = render(
+ getCustomHeadersInTimeline({
+ intervalStyle: {
+ width: 0,
+ position: 'fixed',
+ left: 1222222
+ }
+ })
+ )
+ const { width, position, left } = getComputedStyle(
+ getByTestId('customHeaderInterval')
+ )
+ expect(width).not.toBe('0px')
+ expect(position).not.toBe('fixed')
+ expect(left).not.toBe('1222222px')
+ })
+ it('Given CustomHeader When pass an interval style other than (width, position and left) Then it should rendered correctly', () => {
+ const { getByTestId } = render(
+ getCustomHeadersInTimeline({
+ intervalStyle: {
+ lineHeight: '30px',
+ textAlign: 'center',
+ borderLeft: '1px solid black',
+ cursor: 'pointer',
+ color: 'white'
+ }
+ })
+ )
+ const {
+ lineHeight,
+ textAlign,
+ borderLeft,
+ cursor,
+ color
+ } = getComputedStyle(getByTestId('customHeaderInterval'))
+ expect(lineHeight).toBe('30px')
+ expect(textAlign).toBe('center')
+ expect(borderLeft).toBe('1px solid black')
+ expect(cursor).toBe('pointer')
+ expect(color).toBe('white')
+ })
+
+ it('Given a CustomHeader When not pass any unit prop Then it Should take the default timeline unit', () => {
+ const { getAllByTestId } = render(
+ getCustomHeadersInTimeline({
+ timelineState: {
+ //default unit we are testing
+ timelineUnit: 'month',
+ canvasTimeStart: moment.utc('1/6/2018', 'DD/MM/YYYY').valueOf(),
+ canvasTimeEnd: moment.utc('1/6/2020', 'DD/MM/YYYY').valueOf(),
+ visibleTimeStart: moment.utc('1/1/2019', 'DD/MM/YYYY').valueOf(),
+ visibleTimeEnd: moment.utc('1/1/2020', 'DD/MM/YYYY').valueOf()
+ }
+ })
+ )
+ const intervals = getAllByTestId('customHeaderInterval')
+ const start = moment(intervals[0].textContent, 'DD/MM/YYYY')
+ const end = moment(intervals[1].textContent, 'DD/MM/YYYY')
+ expect(end.diff(start, 'M')).toBe(1)
+ })
+
+ it("Given CustomHeader When rendered Then intervals don't overlap in position", () => {
+ const { getAllByTestId } = render(getCustomHeadersInTimeline())
+ const intervals = getAllByTestId('customHeaderInterval')
+ const intervalsCoordinations = intervals.map(interval => {
+ const { left, width } = getComputedStyle(interval)
+ return {
+ left: parsePxToNumbers(left),
+ right: parsePxToNumbers(left) + parsePxToNumbers(width)
+ }
+ })
+ for (let index = 0; index < intervalsCoordinations.length - 1; index++) {
+ const a = intervalsCoordinations[index]
+ const b = intervalsCoordinations[index + 1]
+ expect(Math.abs(a.right - b.left)).toBeLessThan(0.1)
+ }
+ })
+
+ it('Given CustomHeader When passing child renderer Then showPeriod should be passed', () => {
+ const showPeriod = () => {}
+ const renderer = jest.fn(() => {
+ return
header
+ })
+ render(
+
+
+ {renderer}
+
+
+ )
+ expect(renderer.mock.calls[0][0].showPeriod).toBe(showPeriod)
+ })
+
+ it('Given CustomHeader When passing child renderer Then headerContext should be passed', () => {
+ const renderer = jest.fn(() => {
+ return header
+ })
+ render(
+
+
+ {renderer}
+
+
+ )
+ const headerContext = renderer.mock.calls[0][0].headerContext
+ expect(headerContext).toBeDefined()
+ })
+
+ it('Given CustomHeader When passing child renderer Then headerContext should be passed with intervals and unit', () => {
+ const renderer = jest.fn(() => {
+ return header
+ })
+ render(
+
+
+ {renderer}
+
+
+ )
+ const headerContext = renderer.mock.calls[0][0].headerContext
+ const intervals = headerContext.intervals
+ const unit = headerContext.unit
+ expect(intervals).toBeDefined()
+ expect(intervals).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ startTime: expect.any(moment),
+ endTime: expect.any(moment),
+ labelWidth: expect.any(Number),
+ left: expect.any(Number)
+ })
+ ])
+ )
+ expect(unit).toEqual(expect.any(String))
+ })
+
+ it('Given CustomHeader When passing child renderer Then timelineContext should be passed', () => {
+ const renderer = jest.fn(() => {
+ return header
+ })
+ render(
+
+
+ {renderer}
+
+
+ )
+ const timelineContext = renderer.mock.calls[0][0].timelineContext
+ expect(timelineContext).toBeDefined()
+ expect(timelineContext).toMatchObject({
+ timelineWidth: expect.any(Number),
+ visibleTimeStart: expect.any(Number),
+ visibleTimeEnd: expect.any(Number),
+ canvasTimeStart: expect.any(Number),
+ canvasTimeEnd: expect.any(Number)
+ })
+ })
+
+ describe('CustomHeader Intervals', () => {
+ it('Given intervals Then they should have the same width', () => {
+ const renderer = jest.fn(() => {
+ return header
+ })
+ render(
+
+
+ {renderer}
+
+
+ )
+
+ const headerContext = renderer.mock.calls[0][0].headerContext
+ const intervals = headerContext.intervals
+ const widths = intervals.map(interval => interval.labelWidth)
+ for (let index = 0; index < widths.length - 1; index++) {
+ const a = widths[index]
+ const b = widths[index + 1]
+
+ expect(Math.abs(b - a)).toBeLessThan(0.1)
+ }
+ })
+
+ it('Given intervals Then left property should be different', () => {
+ const renderer = jest.fn(() => {
+ return header
+ })
+ render(
+
+
+ {renderer}
+
+
+ )
+ const headerContext = renderer.mock.calls[0][0].headerContext
+ const intervals = headerContext.intervals
+ const lefts = intervals.map(interval => interval.left)
+ for (let index = 0; index < lefts.length - 1; index++) {
+ const a = lefts[index]
+ const b = lefts[index + 1]
+ expect(a).toBeLessThan(b)
+ }
+ })
+ })
+
+ it('Given CustomHeader When passing extra props Then it will be passed to the renderProp', () => {
+ const renderer = jest.fn(() => {
+ return header
+ })
+ const props = {
+ data: 'some'
+ }
+ render(
+
+
+ {renderer}
+
+
+ )
+ expect(renderer.mock.calls[0][0].data).toBe(props)
+ })
+ // Render The Example In The Docs
+ it('Given CustomHeader When render Then it should render Correctly in the timeline', () => {
+ const { getByTestId } = render(
+
+
+
+ {({ getRootProps }) => {
+ return Left
+ }}
+
+
+
+
+ {({
+ headerContext: { intervals },
+ getRootProps,
+ getIntervalProps,
+ showPeriod
+ }) => {
+ return (
+
+ {intervals.map(interval => {
+ const intervalStyle = {
+ // height: 30,
+ lineHeight: '30px',
+ textAlign: 'center',
+ borderLeft: '1px solid black',
+ cursor: 'pointer',
+ backgroundColor: 'Turquoise',
+ color: 'white'
+ }
+ return (
+
{
+ showPeriod(interval.startTime, interval.endTime)
+ }}
+ {...getIntervalProps({
+ interval,
+ style: intervalStyle
+ })}
+ >
+
+ {interval.startTime.format('YYYY')}
+
+
+ )
+ })}
+
+ )
+ }}
+
+
+
+ )
+
+ expect(getByTestId('customHeader')).toBeInTheDocument()
+ })
+ it('Given Custom Header When passing react stateless component to render prop Then it should render', () => {
+ const Renderer = props => {
+ return header
+ }
+
+ const { getByText } = render(
+
+
+ {Renderer}
+
+
+ )
+ expect(getByText('header')).toBeInTheDocument()
+ })
+
+ it('Given Custom Header When passing react component to render prop Then it should render', () => {
+ class Renderer extends React.Component {
+ render() {
+ return header
+ }
+ }
+
+ const { getByText } = render(
+
+
+ {Renderer}
+
+
+ )
+ expect(getByText('header')).toBeInTheDocument()
+ })
+})
diff --git a/__tests__/components/Headers/DateHeader.test.js b/__tests__/components/Headers/DateHeader.test.js
new file mode 100644
index 000000000..460908629
--- /dev/null
+++ b/__tests__/components/Headers/DateHeader.test.js
@@ -0,0 +1,480 @@
+import React from 'react'
+import { render, cleanup, within, fireEvent } from 'react-testing-library'
+import Timeline from 'lib/Timeline'
+import DateHeader from 'lib/headers/DateHeader'
+import SidebarHeader from 'lib/headers/SidebarHeader'
+import TimelineHeaders from 'lib/headers/TimelineHeaders'
+import 'jest-dom/extend-expect'
+import { RenderHeadersWrapper } from '../../test-utility/header-renderer'
+import moment from 'moment'
+
+describe('Testing DateHeader Component', () => {
+ afterEach(cleanup)
+
+ const format = 'MM/DD/YYYY hh:mm a'
+
+ // Testing The Example In The Docs
+ it('Given DateHeader When rendered Then it should be rendered correctly in the timeLine', () => {
+ const { getAllByTestId } = render(
+
+
+
+ {({ getRootProps }) => {
+ return Left
+ }}
+
+
+
+ {
+ return (
+
+ {intervalContext.intervalText}
+
+ )
+ }}
+ />
+
+
+ )
+
+ expect(getAllByTestId('dateHeader')).toHaveLength(3)
+ })
+
+ describe('DateHeader labelFormat', () => {
+ afterEach(cleanup)
+
+ it('Given Dateheader When pass a string typed labelFormat Then it should render the intervals with the given format', () => {
+ const { getAllByTestId } = render(
+ dateHeaderComponent({ unit: 'day', labelFormat: 'MM/DD' })
+ )
+ expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/25')
+ expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/26')
+ expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/27')
+ })
+
+ it('Given Dateheader When pass a function typed labelFormat Then it should render the intervals with the given format', () => {
+ const formatlabel = jest.fn(interval => interval[0].format('MM/DD/YYYY'))
+ const { getAllByTestId } = render(
+ dateHeaderComponent({ unit: 'day', labelFormat: formatlabel })
+ )
+
+ expect(formatlabel).toHaveBeenCalled()
+
+ expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/25/2018')
+ expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/26/2018')
+ expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/27/2018')
+ })
+
+ it('Given Dateheader When pass a function typed labelFormat Then it should be called with an interval, label width and unit', () => {
+ const formatlabel = jest.fn(interval => interval[0].format('MM/DD/YYYY'))
+ render(dateHeaderComponent({ unit: 'day', labelFormat: formatlabel }))
+
+ expect(formatlabel).toHaveBeenCalled()
+
+ formatlabel.mock.calls.forEach(param => {
+ const [[start, end], unit, labelWidth] = param
+ expect(moment.isMoment(start)).toBeTruthy()
+ expect(moment.isMoment(end)).toBeTruthy()
+ expect(end.diff(start, 'd')).toBe(1)
+ expect(unit).toBe('day')
+ expect(labelWidth).toEqual(expect.any(Number))
+ })
+ })
+ })
+
+ it('Given Dateheader When click on the primary header Then it should change the unit', async () => {
+ const formatlabel = jest.fn(interval => interval[0].format('MM/DD/YYYY'))
+ const showPeriod = jest.fn()
+ const { getByTestId } = render(
+ dateHeaderComponent({ unit: 'day', labelFormat: formatlabel, showPeriod })
+ )
+ // Arrange
+ const primaryHeader = getByTestId('dateHeader')
+
+ // Act
+ const primaryFirstClick = within(primaryHeader).getByText('2018')
+ .parentElement
+ primaryFirstClick.click()
+ expect(showPeriod).toBeCalled()
+ const [start, end] = showPeriod.mock.calls[0]
+ expect(start.format('DD/MM/YYYY hh:mm a')).toBe('01/01/2018 12:00 am')
+ expect(end.format('DD/MM/YYYY hh:mm a')).toBe('31/12/2018 11:59 pm')
+ })
+
+ it('Given Dateheader When pass a className Then it should be applied to DateHeader', () => {
+ const { getAllByTestId } = render(
+ dateHeaderComponent({
+ labelFormat: 'MM/DD/YYYY',
+ className: 'test-class-name'
+ })
+ )
+ expect(getAllByTestId('dateHeader')[1]).toHaveClass('test-class-name')
+ })
+
+ it('Given Interval When pass an override values for (width, left, position) it should not override the default values', () => {
+ const { getAllByTestId } = render(
+ dateHeaderComponent({
+ labelFormat: 'MM/DD/YYYY',
+ props: { style: { width: 100, position: 'fixed', left: 2342 } }
+ })
+ )
+ const { width, position, left } = getComputedStyle(
+ getAllByTestId('interval')[0]
+ )
+ expect(width).not.toBe('100px')
+ expect(position).not.toBe('fixed')
+ expect(left).not.toBe('2342px')
+ })
+
+ it('Given Interval When pass an override (width, position) Then it should ignore these values', () => {
+ const { getAllByTestId, debug } = render(
+ dateHeaderComponent({
+ labelFormat: 'MM/DD/YYYY',
+ props: { style: { width: 100, position: 'fixed' } }
+ })
+ )
+ const { width, position } = getComputedStyle(getAllByTestId('interval')[0])
+ expect(width).not.toBe('1000px')
+ expect(position).toBe('absolute')
+ })
+ it('Given Interval When pass any style other than (position, width, left) through the Dateheader Then it should take it', () => {
+ const { getAllByTestId } = render(
+ dateHeaderComponent({
+ labelFormat: 'MM/DD/YYYY',
+ props: { style: { display: 'flex' } }
+ })
+ )
+ const { display } = getComputedStyle(getAllByTestId('interval')[0])
+
+ expect(display).toBe('flex')
+ })
+
+ it('Given DateHeader component When pass an intervalRenderer prop then it should be called with the right params', () => {
+ const intervalRenderer = jest.fn(
+ ({ getIntervalProps, intervalContext, data }) => (
+
+ {intervalContext.intervalText}
+
+ )
+ )
+ const props = {
+ title: 'some title'
+ }
+ render(
+ dateHeaderWithIntervalRenderer({
+ intervalRenderer: intervalRenderer,
+ props
+ })
+ )
+ const bluePrint = {
+ getIntervalProps: expect.any(Function),
+ intervalContext: expect.any(Object)
+ }
+ expect(intervalRenderer).toBeCalled()
+ expect(intervalRenderer).toReturn()
+ expect(intervalRenderer.mock.calls[0][0].data).toBe(props)
+ expect(intervalRenderer.mock.calls[0][0].getIntervalProps).toEqual(
+ expect.any(Function)
+ )
+ expect(intervalRenderer.mock.calls[0][0].intervalContext).toEqual(
+ expect.any(Object)
+ )
+ })
+
+ describe('DateHeader Unit Values', () => {
+ it('Given DateHeader When not passing a unit then the date header unit should be same as timeline unit', () => {
+ const { getAllByTestId } = render(
+
+
+ interval[0].format(format)} />
+
+
+ )
+ const intervals = getAllByTestId('dateHeaderInterval').map(
+ interval => interval.textContent
+ )
+ for (let index = 0; index < intervals.length - 1; index++) {
+ const a = intervals[index]
+ const b = intervals[index + 1]
+
+ const timeStampA = moment(a, format)
+ const timeStampB = moment(b, format)
+ const diff = timeStampB.diff(timeStampA, 'day')
+ expect(diff).toBe(1)
+ }
+ })
+ it('Given DateHeader When passing a unit then the date header unit should be same as unit passed', () => {
+ const { getAllByTestId } = render(
+
+
+ interval[0].format(format)}
+ />
+
+
+ )
+ const intervals = getAllByTestId('dateHeaderInterval').map(
+ interval => interval.textContent
+ )
+ for (let index = 0; index < intervals.length - 1; index++) {
+ const a = intervals[index]
+ const b = intervals[index + 1]
+
+ const timeStampA = moment(a, format)
+ const timeStampB = moment(b, format)
+ const diff = timeStampB.diff(timeStampA, 'day')
+ expect(diff).toBe(1)
+ }
+ })
+
+ it('Given DateHeader When passing primaryHeader Then the header unit should be bigger the timeline unit', () => {
+ const { getAllByTestId } = render(
+
+
+ interval[0].format(format)}
+ />
+
+
+ )
+ const intervals = getAllByTestId('dateHeaderInterval').map(
+ interval => interval.textContent
+ )
+ for (let index = 0; index < intervals.length - 1; index++) {
+ const a = intervals[index]
+ const b = intervals[index + 1]
+
+ const timeStampA = moment(a, format)
+ const timeStampB = moment(b, format)
+ const diff = timeStampB.diff(timeStampA, 'month')
+ expect(diff).toBe(1)
+ }
+ })
+
+ it('Given DateHeader When not passing unit Then the header unit should be same as the timeline unit', () => {
+ const { getAllByTestId } = render(
+
+
+ interval[0].format(format)} />
+
+
+ )
+ const intervals = getAllByTestId('dateHeaderInterval').map(
+ interval => interval.textContent
+ )
+ for (let index = 0; index < intervals.length - 1; index++) {
+ const a = intervals[index]
+ const b = intervals[index + 1]
+
+ const timeStampA = moment(a, format)
+ const timeStampB = moment(b, format)
+ const diff = timeStampB.diff(timeStampA, 'day')
+ expect(diff).toBe(1)
+ }
+ })
+ })
+
+ describe('DateHeader Interval', () => {
+ it('Given DateHeader Interval When passing on click event to the prop getter Then it should trigger', () => {
+ const onClick = jest.fn()
+ const { getAllByTestId } = render(
+
+
+ {
+ return (
+
+ {intervalContext.intervalText}
+
+ )
+ }}
+ />
+
+
+ )
+ const intervals = getAllByTestId('interval')
+ fireEvent.click(intervals[0])
+ expect(onClick).toHaveBeenCalled()
+ })
+ it('Given DateHeader When passing interval renderer Then it should be rendered', () => {
+ const { getByTestId } = render(
+
+
+ {
+ return (
+
+ {intervalContext.intervalText}
+
+ )
+ }}
+ />
+
+
+ )
+ expect(getByTestId('interval')).toBeInTheDocument()
+ })
+ it("Given DateHeader When passing interval renderer Then it should called with interval's context", () => {
+ const renderer = jest.fn(({ getIntervalProps, intervalContext }) => {
+ return (
+
+ {intervalContext.intervalText}
+
+ )
+ })
+ render(
+
+
+
+
+
+ )
+ expect(renderer.mock.calls[0][0].intervalContext).toEqual(
+ expect.objectContaining({
+ interval: expect.objectContaining({
+ startTime: expect.any(moment),
+ endTime: expect.any(moment),
+ labelWidth: expect.any(Number),
+ left: expect.any(Number)
+ }),
+ intervalText: expect.any(String)
+ })
+ )
+ })
+ })
+ it('Given DateHeader When passing a stateless react component to interval renderer Then it should render', () => {
+ const Renderer = ({ getIntervalProps, intervalContext }) => {
+ return (
+
+ {intervalContext.intervalText}
+
+ )
+ }
+ const { getByTestId } = render(
+
+
+
+ {({ getRootProps }) => {
+ return Left
+ }}
+
+
+
+
+
+
+ )
+
+ expect(getByTestId('interval-a')).toBeInTheDocument()
+ })
+ it('Given DateHeader When passing a react component to interval renderer Then it should render', () => {
+ class Renderer extends React.Component {
+ render() {
+ const { getIntervalProps, intervalContext } = this.props
+ return (
+
+ {intervalContext.intervalText}
+
+ )
+ }
+ }
+ const { getByTestId } = render(
+
+
+
+ {({ getRootProps }) => {
+ return Left
+ }}
+
+
+
+
+
+
+ )
+
+ expect(getByTestId('interval-a')).toBeInTheDocument()
+ })
+})
+
+function dateHeaderComponent({
+ labelFormat,
+ unit,
+ props,
+ className,
+ style,
+ showPeriod
+} = {}) {
+ return (
+
+
+
+ {({ getRootProps }) => {
+ return Left
+ }}
+
+
+ {
+ return (
+
+ {intervalContext.intervalText}
+
+ )
+ }}
+ />
+
+
+
+ )
+}
+
+function dateHeaderWithIntervalRenderer({ intervalRenderer, props } = {}) {
+ return (
+
+
+
+ {({ getRootProps }) => {
+ return Left
+ }}
+
+
+
+
+
+
+ )
+}
diff --git a/__tests__/components/Headers/SideBarHeader.test.js b/__tests__/components/Headers/SideBarHeader.test.js
new file mode 100644
index 000000000..6775547c4
--- /dev/null
+++ b/__tests__/components/Headers/SideBarHeader.test.js
@@ -0,0 +1,159 @@
+import React from 'react'
+import { render, cleanup } from 'react-testing-library'
+import DateHeader from 'lib/headers/DateHeader'
+import SidebarHeader from 'lib/headers/SidebarHeader'
+import TimelineHeaders from 'lib/headers/TimelineHeaders'
+import 'jest-dom/extend-expect'
+import { RenderHeadersWrapper } from '../../test-utility/header-renderer'
+import {
+ renderSidebarHeaderWithCustomValues,
+ renderTwoSidebarHeadersWithCustomValues
+} from '../../test-utility/headerRenderers'
+
+describe('Testing SidebarHeader Component', () => {
+ afterEach(cleanup)
+
+ //TODO: rename test
+ it('Given sidebarHeader When pass style with overridden (width) Then it should not override the default values', () => {
+ const { getByTestId, debug } = renderSidebarHeaderWithCustomValues({
+ props: { style: { width: 250 } }
+ })
+ const { width } = getComputedStyle(getByTestId('sidebarHeader'))
+ expect(width).not.toBe('250px')
+ })
+
+ it('Given sidebarHeader When pass style Then it show on the sidebar', () => {
+ const { getByTestId } = renderSidebarHeaderWithCustomValues({
+ props: { style: { color: 'white' } }
+ })
+ const { color } = getComputedStyle(getByTestId('sidebarHeader'))
+ expect(color).toBe('white')
+ })
+
+ it('Given SidebarHeader When a render function Then it will be rendered', () => {
+ const renderer = jest.fn(({ getRootProps }) => {
+ return (
+
+ Left
+
+ )
+ })
+ const { getByTestId } = render(
+
+
+ {renderer}
+
+
+
+
+ )
+ expect(renderer).toHaveBeenCalled()
+ expect(getByTestId('leftSidebarHeader')).toBeInTheDocument()
+ })
+
+ it('Given SidebarHeader When passing props to SidebarHeader it should be passed to the renderProp', () => {
+ const renderer = jest.fn(({ getRootProps }) => {
+ return (
+
+ Left
+
+ )
+ })
+ const extraProps = {
+ someData: 'data'
+ }
+ const { getByTestId } = render(
+
+
+ {renderer}
+
+
+
+
+ )
+ expect(renderer).toHaveBeenCalled()
+ expect(renderer.mock.calls[0][0].data).toBe(extraProps)
+ })
+
+ // Testing The Example In The Docs
+ it('Given SidebarHeader When rendered Then it should shown correctly in the timeline', () => {
+ const { getByTestId } = render(
+
+
+
+ {({ getRootProps }) => {
+ return (
+
+ Left
+
+ )
+ }}
+
+
+ {({ getRootProps }) => {
+ return (
+
+ Right
+
+ )
+ }}
+
+
+
+
+
+ )
+
+ expect(getByTestId('leftSidebarHeader')).toBeInTheDocument()
+ expect(getByTestId('rightSidebarHeader')).toBeInTheDocument()
+
+ expect(getByTestId('leftSidebarHeader').nextElementSibling).toHaveAttribute(
+ 'data-testid',
+ 'headerContainer'
+ )
+ expect(
+ getByTestId('rightSidebarHeader').previousElementSibling
+ ).toHaveAttribute('data-testid', 'headerContainer')
+ })
+ it('Given SideBarHeader When passing a react stateless component as a child Then it should render', () => {
+ const Renderer = ({ getRootProps }) => {
+ return (
+
+ Left
+
+ )
+ }
+ const { getByText } = render(
+
+
+ {Renderer}
+
+
+
+
+ )
+ expect(getByText('Left')).toBeInTheDocument()
+ })
+ it('Given SideBarHeader When passing a react stateful component as a child Then it should render', () => {
+ class Renderer extends React.Component {
+ render() {
+ const { getRootProps } = this.props
+ return (
+
+ Left
+
+ )
+ }
+ }
+ const { getByText } = render(
+
+
+ {Renderer}
+
+
+
+
+ )
+ expect(getByText('Left')).toBeInTheDocument()
+ })
+})
diff --git a/__tests__/components/Headers/TimelineHeader.test.js b/__tests__/components/Headers/TimelineHeader.test.js
new file mode 100644
index 000000000..3fe7a07a8
--- /dev/null
+++ b/__tests__/components/Headers/TimelineHeader.test.js
@@ -0,0 +1,180 @@
+import { render } from 'react-testing-library'
+import SidebarHeader from 'lib/headers/SidebarHeader'
+import DateHeader from 'lib/headers/DateHeader'
+import TimelineHeaders from 'lib/headers/TimelineHeaders'
+import 'jest-dom/extend-expect'
+import 'react-testing-library/cleanup-after-each'
+
+import React from 'react'
+
+import { RenderHeadersWrapper } from '../../test-utility/header-renderer'
+import {
+ renderSidebarHeaderWithCustomValues,
+ renderTimelineWithVariantSidebar,
+ renderTimelineWithLeftAndRightSidebar
+} from '../../test-utility/headerRenderers'
+
+describe('TimelineHeader', () => {
+ it('Given TimelineHeader When pass a left and right sidebars as children Then it should render a left and right sidebars', () => {
+ const { getByTestId } = renderTimelineWithLeftAndRightSidebar()
+ expect(getByTestId('left-header')).toBeInTheDocument()
+ expect(getByTestId('right-header')).toBeInTheDocument()
+ })
+
+ it('Given TimelineHeader When pass calendarHeaderStyle with overridden (overflow, width) Then it should not override the default values', () => {
+ const { getByTestId } = renderTimelineWithLeftAndRightSidebar({
+ calendarHeaderStyle: { overflow: 'unset', width: 0 }
+ })
+ const headerContainer = getByTestId('headerContainer')
+ const { width, overflow } = getComputedStyle(headerContainer)
+
+ expect(overflow).not.toBe('unset')
+ expect(width).not.toBe('0px')
+ })
+ it('Given TimelineHeader When pass calendarHeaderStyle Then it be added to styles', () => {
+ const { getByTestId } = renderTimelineWithLeftAndRightSidebar({
+ calendarHeaderStyle: { color: 'white', background: 'black' }
+ })
+ const headerContainer = getByTestId('headerContainer')
+ const { color, background } = getComputedStyle(headerContainer)
+
+ expect(color).toBe('white')
+ expect(background).toBe('black')
+ })
+ it('Given TimelineHeader When pass style with overridden (display, width) Then it should not override the default values', () => {
+ const { getByTestId } = renderTimelineWithLeftAndRightSidebar({
+ style: { display: 'none', width: 0 }
+ })
+ const rootDiv = getByTestId('headerRootDiv')
+ const { width, display } = getComputedStyle(rootDiv)
+
+ expect(display).not.toBe('none')
+ expect(width).not.toBe('0px')
+ })
+ it('Given TimelineHeader When pass style Then it should it be added to root`s styles', () => {
+ const { getByTestId } = renderTimelineWithLeftAndRightSidebar({
+ style: { color: 'white', background: 'black' }
+ })
+ const rootDiv = getByTestId('headerRootDiv')
+ const { color, background } = getComputedStyle(rootDiv)
+
+ expect(color).toBe('white')
+ expect(background).toBe('black')
+ })
+ it('Given TimelineHeader When pass calendarHeaderClassName Then it should be applied to the date header container', () => {
+ const { getByTestId } = renderTimelineWithLeftAndRightSidebar({
+ calendarHeaderClassName: 'testClassName'
+ })
+ expect(getByTestId('headerContainer')).toHaveClass('testClassName')
+ })
+
+ it('Given TimelineHeader When pass className Then it should be applied to the root header container', () => {
+ const { getByTestId } = renderTimelineWithLeftAndRightSidebar({
+ className: 'testClassName'
+ })
+ expect(getByTestId('headerRootDiv')).toHaveClass('testClassName')
+ })
+
+ it('Given TimelineHeader When rendered Then it should render the default styles of the date header container', () => {
+ const { getByTestId } = renderTimelineWithLeftAndRightSidebar()
+ const headerContainer = getByTestId('headerContainer')
+ const { overflow } = getComputedStyle(headerContainer)
+ expect(overflow).toBe('hidden')
+ // The JSDOM will not fire the calc css function
+ })
+
+ it('Given TimelineHeader When rendered Then it should render the default styles of the rootStyle', () => {
+ const { getByTestId } = renderTimelineWithLeftAndRightSidebar()
+ const rootDiv = getByTestId('headerRootDiv')
+ const { width, display } = getComputedStyle(rootDiv)
+
+ expect(display).toBe('flex')
+ expect(width).toBe('100%')
+ })
+
+ it('Given SidebarHeader When passing no variant prop Then it should rendered above the left sidebar', () => {
+ const {
+ getByTestId,
+ getAllByTestId
+ } = renderSidebarHeaderWithCustomValues()
+ expect(getByTestId('sidebarHeader')).toBeInTheDocument()
+ expect(getByTestId('sidebarHeader').nextElementSibling).toHaveAttribute(
+ 'data-testid',
+ 'headerContainer'
+ )
+ expect(getAllByTestId('sidebarHeader')).toHaveLength(1)
+ })
+ it('Given SidebarHeader When passing variant prop with left value Then it should rendered above the left sidebar', () => {
+ const { getByTestId, getAllByTestId } = renderSidebarHeaderWithCustomValues(
+ { variant: 'left' }
+ )
+ expect(getByTestId('sidebarHeader')).toBeInTheDocument()
+ expect(getByTestId('sidebarHeader').nextElementSibling).toHaveAttribute(
+ 'data-testid',
+ 'headerContainer'
+ )
+ expect(getAllByTestId('sidebarHeader')).toHaveLength(1)
+ })
+ it('Given SidebarHeader When passing variant prop with right value Then it should rendered above the right sidebar', () => {
+ const { getByTestId, getAllByTestId, debug } = renderSidebarHeaderWithCustomValues(
+ { variant: 'right' }
+ )
+ expect(getByTestId('sidebarHeader')).toBeInTheDocument()
+ expect(getAllByTestId('sidebarHeader')).toHaveLength(2)
+ expect(getAllByTestId('sidebarHeader')[1].previousElementSibling).toHaveAttribute(
+ 'data-testid',
+ 'headerContainer'
+ )
+ })
+
+ it('Given SidebarHeader When passing variant prop with unusual value Then it should rendered above the left sidebar by default', () => {
+ const { getByTestId } = renderSidebarHeaderWithCustomValues({ variant: '' })
+ expect(getByTestId('sidebarHeader')).toBeInTheDocument()
+ expect(getByTestId('sidebarHeader').nextElementSibling).toHaveAttribute(
+ 'data-testid',
+ 'headerContainer'
+ )
+ })
+
+ /**
+ * Testing The Example Provided In The Docs
+ */
+ it('Given TimelineHeader When pass a headers as children Then it should render them correctly', () => {
+ const { getByText, rerender, queryByText } = render(
+
+
+
+ {({ getRootProps }) => {
+ return Left
+ }}
+
+
+ {({ getRootProps }) => {
+ return (
+
+ Right
+
+ )
+ }}
+
+
+
+
+ )
+ expect(getByText('Left')).toBeInTheDocument()
+ expect(getByText('Right')).toBeInTheDocument()
+ rerender(
+
+
+
+ {({ getRootProps }) => {
+ return Left
+ }}
+
+
+
+
+ )
+ expect(queryByText('Right')).toBeNull()
+ })
+})
diff --git a/__tests__/components/Headers/defaultHeaders.js b/__tests__/components/Headers/defaultHeaders.js
new file mode 100644
index 000000000..dbd7722fe
--- /dev/null
+++ b/__tests__/components/Headers/defaultHeaders.js
@@ -0,0 +1,41 @@
+import React from 'react'
+import { render } from 'react-testing-library'
+import { items, groups } from '../../../__fixtures__/itemsAndGroups'
+import {
+ props as defaultProps
+} from '../../../__fixtures__/stateAndProps'
+import 'react-testing-library/cleanup-after-each'
+import Timeline from 'lib/Timeline'
+import 'jest-dom/extend-expect'
+
+/**
+ * Testing The Default Functionality
+ */
+describe('Renders default headers correctly', () => {
+ it('Given Timeline When not using TimelineHeaders then it should render 2 DateHeaders and a left sidebar header by default ', () => {
+ const { getAllByTestId, getByTestId } = renderDefaultTimeline();
+ expect(getAllByTestId('dateHeader')).toHaveLength(2);
+ expect(getByTestId('headerContainer').children).toHaveLength(2);
+ expect(getByTestId('sidebarHeader')).toBeInTheDocument();
+ });
+ it('Given TimelineHeader When pass a rightSidebarWidthWidth Then it should render two sidebar headers', () => {
+ let rightSidebarWidth = 150;
+ const { getAllByTestId } = renderDefaultTimeline({ rightSidebarWidth });
+ const sidebarHeaders = getAllByTestId('sidebarHeader');
+ expect(sidebarHeaders).toHaveLength(2);
+ expect(sidebarHeaders[0]).toBeInTheDocument();
+ expect(sidebarHeaders[1]).toBeInTheDocument();
+ const { width } = getComputedStyle(sidebarHeaders[1]);
+ expect(width).toBe('150px');
+ });
+});
+
+export function renderDefaultTimeline(props = {}) {
+ const timelineProps = {
+ ...defaultProps,
+ items,
+ groups,
+ ...props
+ }
+ return render()
+}
diff --git a/__tests__/test-utility/header-renderer.js b/__tests__/test-utility/header-renderer.js
new file mode 100644
index 000000000..645fb8ee0
--- /dev/null
+++ b/__tests__/test-utility/header-renderer.js
@@ -0,0 +1,53 @@
+import React from 'react'
+import TimelineMarkersRenderer from 'lib/markers/TimelineMarkersRenderer'
+import { TimelineMarkersProvider } from 'lib/markers/TimelineMarkersContext'
+import { TimelineStateProvider } from 'lib/timeline/TimelineStateContext'
+import { state } from '../../__fixtures__/stateAndProps'
+import jest from 'jest'
+import { defaultTimeSteps } from '../../src/lib/default-config'
+import { TimelineHeadersProvider } from '../../src/lib/headers/HeadersContext'
+
+// eslint-disable-next-line
+export const RenderHeadersWrapper = ({
+ children,
+ timelineState = {},
+ headersState = {},
+ showPeriod = () => {},
+ registerScroll = () => {}
+}) => {
+ const defaultTimelineState = {
+ visibleTimeStart: state.visibleTimeStart,
+ visibleTimeEnd: state.visibleTimeEnd,
+ canvasTimeStart: state.canvasTimeStart,
+ canvasTimeEnd: state.canvasTimeEnd,
+ canvasWidth: 2000,
+ showPeriod: showPeriod,
+ timelineUnit: 'day',
+ timelineWidth: 1000
+ }
+
+ const timelineStateProps = {
+ ...defaultTimelineState,
+ ...timelineState
+ }
+
+ const headersStateProps = {
+ registerScroll: registerScroll,
+ timeSteps: defaultTimeSteps,
+ leftSidebarWidth: 150,
+ rightSidebarWidth: 0,
+ ...headersState
+ }
+
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
diff --git a/__tests__/test-utility/headerRenderers.js b/__tests__/test-utility/headerRenderers.js
new file mode 100644
index 000000000..9fa305052
--- /dev/null
+++ b/__tests__/test-utility/headerRenderers.js
@@ -0,0 +1,164 @@
+import React from 'react';
+import { render } from 'react-testing-library';
+import DateHeader from 'lib/headers/DateHeader';
+import SidebarHeader from 'lib/headers/SidebarHeader';
+import TimelineHeaders from 'lib/headers/TimelineHeaders';
+import CustomHeader from 'lib/headers/CustomHeader'
+
+import { RenderHeadersWrapper } from './header-renderer';
+export function renderSidebarHeaderWithCustomValues({ variant = undefined, props, timelineState, headersState, extraProps } = {}) {
+ return render(
+
+
+ {({ getRootProps }) => {
+ return (
+ SidebarHeader
+
Should Be Rendred
+
);
+ }}
+
+
+
+
+ );
+}
+export function renderTwoSidebarHeadersWithCustomValues({ props, timelineState, headersState } = {}) {
+ return render(
+
+
+ {({ getRootProps }) => {
+ return (
+ LeftSideBar
+
Should Be Rendred
+
);
+ }}
+
+
+ {({ getRootProps, data }) => {
+ return RightSideBar
;
+ }}
+
+
+
+
+ );
+}
+
+export function renderTimelineWithLeftAndRightSidebar({
+ calendarHeaderClassName,
+ calendarHeaderStyle,
+ style,
+ className,
+ timelineState,
+ headersState
+} = {}) {
+ return render(
+
+
+
+ {({ getRootProps }) => {
+ return (
+
+ Right
+
+ )
+ }}
+
+
+ {({ getRootProps }) => {
+ return (
+
+ Left
+
+ )
+ }}
+
+
+
+ )
+}
+
+export function renderTimelineWithVariantSidebar({
+ variant,
+ timelineState,
+ headersState
+} = {}) {
+ return render(
+
+
+
+ {({ getRootProps }) => {
+ return (
+
+ Header
+
+ )
+ }}
+
+
+
+ )
+}
+
+export function getCustomHeadersInTimeline({
+ unit,
+ props,
+ intervalStyle,
+ timelineState,
+ headersState
+} = {}) {
+ return (
+
+
+
+ {(
+ {
+ headerContext: { intervals },
+ getRootProps,
+ getIntervalProps,
+ showPeriod,
+ data = { style: { height: 30 } }
+ },
+ ) => {
+ return (
+
+ {intervals.map(interval => {
+ return (
+
{
+ showPeriod(interval.startTime, interval.endTime)
+ }}
+ {...getIntervalProps({
+ interval,
+ style: intervalStyle
+ })}
+ >
+
+ {interval.startTime.format('DD/MM/YYYY')}
+
+
+ )
+ })}
+
+ )
+ }}
+
+
+
+ )
+}
diff --git a/__tests__/test-utility/index.js b/__tests__/test-utility/index.js
index 3cbac0bfb..0f5bb12a3 100644
--- a/__tests__/test-utility/index.js
+++ b/__tests__/test-utility/index.js
@@ -9,3 +9,7 @@ export function sel(selectorString) {
}
export function noop() {}
+
+export function parsePxToNumbers(value) {
+ return +value.replace('px', '')
+}
\ No newline at end of file
diff --git a/__tests__/test-utility/parse-px-to-numbers.js b/__tests__/test-utility/parse-px-to-numbers.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/demo/app/demo-sticky-header/index.js b/demo/app/demo-sticky-header/index.js
deleted file mode 100644
index adb26d533..000000000
--- a/demo/app/demo-sticky-header/index.js
+++ /dev/null
@@ -1,126 +0,0 @@
-import React, { Component } from 'react'
-import moment from 'moment'
-
-import Timeline from 'react-calendar-timeline'
-import containerResizeDetector from '../../../src/resize-detector/container'
-
-// you would use this in real life:
-// import containerResizeDetector from 'react-calendar-timeline/lib/resize-detector/container'
-
-import generateFakeData from '../generate-fake-data'
-
-var keys = {
- groupIdKey: 'id',
- groupTitleKey: 'title',
- groupRightTitleKey: 'rightTitle',
- itemIdKey: 'id',
- itemTitleKey: 'title',
- itemDivTitleKey: 'title',
- itemGroupKey: 'group',
- itemTimeStartKey: 'start',
- itemTimeEndKey: 'end'
-}
-
-export default class App extends Component {
- constructor(props) {
- super(props)
-
- const { groups, items } = generateFakeData()
- const defaultTimeStart = moment()
- .startOf('day')
- .toDate()
- const defaultTimeEnd = moment()
- .startOf('day')
- .add(1, 'day')
- .toDate()
- const width = 80
-
- this.state = {
- groups,
- items,
- defaultTimeStart,
- defaultTimeEnd,
- width
- }
- }
-
- render() {
- const { groups, items, defaultTimeStart, defaultTimeEnd } = this.state
-
- return (
-
- In this example we have a lot of random content above the timeline.
- Try scrolling and see how the timeline header sticks to the screen.
-
- Above The Left
}
- rightSidebarWidth={150}
- rightSidebarContent={Above The Right
}
- canMove
- canResize="right"
- canSelect
- itemsSorted
- itemTouchSendsClick={false}
- stackItems
- itemHeightRatio={0.75}
- resizeDetector={containerResizeDetector}
- defaultTimeStart={defaultTimeStart}
- defaultTimeEnd={defaultTimeEnd}
- />
-
- There is also a lot of stuff below the timeline. Watch the header fix
- itself to the bottom of the component.
-
-
- bla bla bla
-
-
- Here are random pictures of Tom Selleck:
-
-
-
-
-
-
-
-
- Here is another calendar, but this one has stickyOffset set
- to 100, meaning that the header will stick 100px from the
- top. This is useful for example if you already have a sticky navbar.
-
-
- Above The Left}
- rightSidebarWidth={150}
- rightSidebarContent={Above The Right
}
- canMove
- canResize="right"
- canSelect
- itemsSorted
- itemTouchSendsClick={false}
- stackItems
- itemHeightRatio={0.75}
- resizeDetector={containerResizeDetector}
- defaultTimeStart={defaultTimeStart}
- defaultTimeEnd={defaultTimeEnd}
- />
-
-
- )
- }
-}
diff --git a/src/lib/items/Item.js b/src/lib/items/Item.js
index 09d8d7460..2abafba66 100644
--- a/src/lib/items/Item.js
+++ b/src/lib/items/Item.js
@@ -261,7 +261,6 @@ export default class Item extends Component {
if (this.state.dragging) {
let dragTime = this.dragTime(e)
let dragGroupDelta = this.dragGroupDelta(e)
- console.log(dragGroupDelta)
if (this.props.moveResizeValidator) {
dragTime = this.props.moveResizeValidator(
'move',