diff --git a/superset-frontend/spec/javascripts/explore/components/DateFilterControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/DateFilterControl_spec.jsx index 693c86950c..065f979539 100644 --- a/superset-frontend/spec/javascripts/explore/components/DateFilterControl_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/DateFilterControl_spec.jsx @@ -18,14 +18,21 @@ */ /* eslint-disable no-unused-expressions */ import React from 'react'; +import { OverlayTrigger, Popover, Tab, Tabs, Radio } from 'react-bootstrap'; import sinon from 'sinon'; import { styledMount as mount } from 'spec/helpers/theming'; -import Button from 'src/components/Button'; import Label from 'src/components/Label'; import DateFilterControl from 'src/explore/components/controls/DateFilterControl'; import ControlHeader from 'src/explore/components/ControlHeader'; +// Mock moment.js to use a specific date +jest.mock('moment', () => { + const testDate = new Date('09/07/2020'); + + return () => jest.requireActual('moment')(testDate); +}); + const defaultProps = { animation: false, name: 'date', @@ -41,40 +48,94 @@ describe('DateFilterControl', () => { wrapper = mount(); }); + it('renders', () => { + expect(wrapper.find(DateFilterControl)).toExist(); + }); + it('renders a ControlHeader', () => { const controlHeader = wrapper.find(ControlHeader); expect(controlHeader).toHaveLength(1); }); - it('renders 3 Buttons', () => { - const label = wrapper.find(Label).first(); - label.simulate('click'); - setTimeout(() => { - expect(wrapper.find(Button)).toHaveLength(3); - }, 10); + + it('renders an OverlayTrigger', () => { + expect(wrapper.find(OverlayTrigger)).toExist(); }); - it('loads the right state', () => { - const label = wrapper.find(Label).first(); - label.simulate('click'); - setTimeout(() => { - expect(wrapper.state().num).toBe('90'); - }, 10); + + it('renders a popover', () => { + const { overlay } = wrapper.find(OverlayTrigger).first().props(); + const overlayWrapper = mount(overlay); + + expect(overlayWrapper.find(Popover)).toExist(); }); - it('renders 2 dimmed sections', () => { - const label = wrapper.find(Label).first(); + + it('calls open/close methods on trigger click', () => { + const open = jest.fn(); + const close = jest.fn(); + const props = { + ...defaultProps, + onOpenDateFilterControl: open, + onCloseDateFilterControl: close, + }; + const testWrapper = mount(); + const label = testWrapper.find(Label).first(); + label.simulate('click'); - setTimeout(() => { - expect(wrapper.find(Button)).toHaveLength(3); - }, 10); + expect(open).toBeCalled(); + expect(close).not.toBeCalled(); + label.simulate('click'); + expect(close).toBeCalled(); }); - it('opens and closes', () => { - const label = wrapper.find(Label).first(); - label.simulate('click'); - setTimeout(() => { - expect(wrapper.find('.popover')).toExist(); - expect(wrapper.find('.ok')).first().simulate('click'); - setTimeout(() => { - expect(wrapper.find('.popover')).not.toExist(); - }, 10); - }, 10); + + it('renders two tabs in popover', () => { + const { overlay } = wrapper.find(OverlayTrigger).first().props(); + const overlayWrapper = mount(overlay); + const popover = overlayWrapper.find(Popover).first(); + + expect(popover.find(Tabs)).toExist(); + expect(popover.find(Tab)).toHaveLength(2); + }); + + it('renders default time options', () => { + const { overlay } = wrapper.find(OverlayTrigger).first().props(); + const overlayWrapper = mount(overlay); + const defaultTab = overlayWrapper.find(Tab).first(); + + expect(defaultTab.find(Radio)).toExist(); + expect(defaultTab.find(Radio)).toHaveLength(6); + }); + + it('renders tooltips over timeframe options', () => { + const { overlay } = wrapper.find(OverlayTrigger).first().props(); + const overlayWrapper = mount(overlay); + const defaultTab = overlayWrapper.find(Tab).first(); + const radioTrigger = defaultTab.find(OverlayTrigger); + + expect(radioTrigger).toExist(); + expect(radioTrigger).toHaveLength(6); + }); + + it('renders the correct time range in tooltip', () => { + const { overlay } = wrapper.find(OverlayTrigger).first().props(); + const overlayWrapper = mount(overlay); + const defaultTab = overlayWrapper.find(Tab).first(); + const triggers = defaultTab.find(OverlayTrigger); + + const expectedLabels = { + 'Last day': '2020-09-06 < col < 2020-09-07', + 'Last week': '2020-08-31 < col < 2020-09-07', + 'Last month': '2020-08-07 < col < 2020-09-07', + 'Last quarter': '2020-06-07 < col < 2020-09-07', + 'Last year': '2019-01-01 < col < 2020-01-01', + 'No filter': '-∞ < col < ∞', + }; + + triggers.forEach(trigger => { + const { props } = trigger.props().overlay; + const label = props.id.split('tooltip-')[1]; + + expect(trigger.props().overlay.props.children).toEqual( + expectedLabels[label], + ); + }); }); }); diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl.jsx b/superset-frontend/src/explore/components/controls/DateFilterControl.jsx index f49afda54c..bd30125153 100644 --- a/superset-frontend/src/explore/components/controls/DateFilterControl.jsx +++ b/superset-frontend/src/explore/components/controls/DateFilterControl.jsx @@ -128,22 +128,23 @@ function getStateFromSeparator(value) { function getStateFromCommonTimeFrame(value) { const units = `${value.split(' ')[1]}s`; + let sinceMoment; + + if (value === 'No filter') { + sinceMoment = ''; + } else if (units === 'years') { + sinceMoment = moment().utc().startOf(units).subtract(1, units); + } else { + sinceMoment = moment().utc().startOf('day').subtract(1, units); + } + return { tab: TABS.DEFAULTS, type: TYPES.DEFAULTS, common: value, - since: - value === 'No filter' - ? '' - : moment() - .utc() - .startOf('day') - .subtract(1, units) - .format(MOMENT_FORMAT), + since: sinceMoment === '' ? '' : sinceMoment.format(MOMENT_FORMAT), until: - value === 'No filter' - ? '' - : moment().utc().startOf('day').format(MOMENT_FORMAT), + sinceMoment === '' ? '' : sinceMoment.add(1, units).format(MOMENT_FORMAT), }; } @@ -396,7 +397,6 @@ class DateFilterControl extends React.Component { )); const timeFrames = COMMON_TIME_FRAMES.map(timeFrame => { const nextState = getStateFromCommonTimeFrame(timeFrame); - const timeRange = buildTimeRangeString(nextState.since, nextState.until); return (