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',