diff --git a/src/components/Analysis/AnalysisView.tsx b/src/components/Analysis/AnalysisView.tsx index b6e55107785cbddb4c813e0eb92aba5f39d158df..fcdb3602877da1a5336299d8c0d1a80f2483c9f6 100644 --- a/src/components/Analysis/AnalysisView.tsx +++ b/src/components/Analysis/AnalysisView.tsx @@ -8,8 +8,9 @@ import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' import Content from 'components/Content/Content' import MonthlyAnalysis from 'components/Analysis/MonthlyAnalysis' -import { convertDateToMonthYearString } from 'utils/date' import './analysisView.scss' +import DateNavigator from 'components/DateNavigator/DateNavigator' +import { DateTime } from 'luxon' const AnalysisView: React.FC = () => { const [headerHeight, setHeaderHeight] = useState<number>(0) @@ -17,6 +18,11 @@ const AnalysisView: React.FC = () => { global: { analysisNotification }, profile: { monthlyAnalysisDate }, } = useSelector((state: AppStore) => state.ecolyo) + + const { selectedDate } = useSelector((state: AppStore) => state.ecolyo.chart) + const [currentAnalysisDate, setCurrentAnalysisDate] = useState<DateTime>( + monthlyAnalysisDate + ) const dispatch = useDispatch() const defineHeaderHeight = useCallback((height: number) => { @@ -30,9 +36,8 @@ const AnalysisView: React.FC = () => { dispatch(toggleAnalysisNotification(false)) } } - updateAnalysisNotification() - }, [dispatch, analysisNotification]) + }, [dispatch, analysisNotification, monthlyAnalysisDate, selectedDate]) return ( <> @@ -41,12 +46,13 @@ const AnalysisView: React.FC = () => { setHeaderHeight={defineHeaderHeight} desktopTitleKey={'analysis.viewTitle'} > - <div className="text-20-bold-capitalize analysis-view-title">{`${convertDateToMonthYearString( - monthlyAnalysisDate.plus({ month: -1 }) - )}`}</div> + <DateNavigator + currentAnalysisDate={currentAnalysisDate} + setCurrentAnalysisDate={setCurrentAnalysisDate} + /> </Header> <Content height={headerHeight}> - <MonthlyAnalysis /> + <MonthlyAnalysis analysisDate={currentAnalysisDate} /> </Content> </> ) diff --git a/src/components/Analysis/MonthlyAnalysis.spec.tsx b/src/components/Analysis/MonthlyAnalysis.spec.tsx index 3f8bfbecbfa97c368cea17c2d4eea8d7d76607d7..fc432cab6f73efc7b6d7e0860f3bb92bff75ab8f 100644 --- a/src/components/Analysis/MonthlyAnalysis.spec.tsx +++ b/src/components/Analysis/MonthlyAnalysis.spec.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { shallow } from 'enzyme' +import { mount } from 'enzyme' import MonthlyAnalysis from 'components/Analysis/MonthlyAnalysis' import { DateTime } from 'luxon' @@ -7,6 +7,10 @@ import { DateTime } from 'luxon' import * as reactRedux from 'react-redux' import { userChallengeExplo1OnGoing } from '../../../tests/__mocks__/userChallengeData.mock' +import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' +import { Provider } from 'react-redux' +import configureStore from 'redux-mock-store' + jest.mock('cozy-ui/transpiled/react/I18n', () => { return { useI18n: jest.fn(() => { @@ -16,21 +20,28 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => { }), } }) +const mockStore = configureStore([]) const mockUseSelector = jest.spyOn(reactRedux, 'useSelector') -const analysisDate = DateTime.fromISO('2020-11-17T00:00:00.000+01:00') describe('MonthlyAnalysis component', () => { mockUseSelector.mockReturnValue(userChallengeExplo1OnGoing) - const setAnalysisDate = jest.fn() - const setLoad = jest.fn() - const useStateSpy = jest.spyOn(React, 'useState') - const useStateMock: any = (init: any) => [analysisDate, setAnalysisDate] - const useStateLoad: any = (init: any) => [true, setLoad] - useStateSpy.mockImplementationOnce(useStateMock) - useStateSpy.mockImplementationOnce(useStateLoad) + it('should be rendered correctly', () => { - const component = shallow(<MonthlyAnalysis />).getElement() - expect(component).toMatchSnapshot() + const store = mockStore({ + ecolyo: { + global: globalStateData, + }, + }) + const wrapper = mount( + <Provider store={store}> + <MonthlyAnalysis + analysisDate={DateTime.fromISO('2021-07-01T00:00:00.000Z', { + zone: 'utc', + })} + /> + </Provider> + ).getElement() + expect(wrapper).toMatchSnapshot() }) }) diff --git a/src/components/Analysis/MonthlyAnalysis.tsx b/src/components/Analysis/MonthlyAnalysis.tsx index d41a7ee44685f030a4edc6f6e5958b60a9d1de7e..63c9654647d3c461b6f36e31908a7ea0d67c85fd 100644 --- a/src/components/Analysis/MonthlyAnalysis.tsx +++ b/src/components/Analysis/MonthlyAnalysis.tsx @@ -22,14 +22,19 @@ import AnalysisConsumption from './AnalysisConsumption' import { useHistory } from 'react-router-dom' import StyledSpinner from 'components/CommonKit/Spinner/StyledSpinner' import AnalysisErrorModal from './AnalysisErrorModal' +import { DateTime } from 'luxon' -const MonthlyAnalysis: React.FC = () => { +interface MonthlyAnalysisProps { + analysisDate: DateTime +} + +const MonthlyAnalysis: React.FC<MonthlyAnalysisProps> = ({ + analysisDate, +}: MonthlyAnalysisProps) => { const { t } = useI18n() const client = useClient() const history = useHistory() const { fluidTypes } = useSelector((state: AppStore) => state.ecolyo.global) - const profile = useSelector((state: AppStore) => state.ecolyo.profile) - const analysisDate = profile.monthlyAnalysisDate const [performanceIndicators, setPerformanceIndicators] = useState< PerformanceIndicator[] >([]) @@ -52,6 +57,7 @@ const MonthlyAnalysis: React.FC = () => { } useEffect(() => { + setIsLoaded(false) let subscribed = true async function populateData() { const consumptionService = new ConsumptionService(client) @@ -141,6 +147,7 @@ const MonthlyAnalysis: React.FC = () => { performanceIndicator={aggregatedPerformanceIndicators} timeStep={timeStep} fluidLackOfData={fluidLackOfData} + analysisDate={analysisDate} /> <div> <div className="analysis-header text-16-normal-uppercase"> diff --git a/src/components/Analysis/__snapshots__/MonthlyAnalysis.spec.tsx.snap b/src/components/Analysis/__snapshots__/MonthlyAnalysis.spec.tsx.snap index f654ebe5783ac204580355712f00667bc2604a56..9835494487c6bd07f9bfb47b8b08a91aa150839a 100644 --- a/src/components/Analysis/__snapshots__/MonthlyAnalysis.spec.tsx.snap +++ b/src/components/Analysis/__snapshots__/MonthlyAnalysis.spec.tsx.snap @@ -1,15 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`MonthlyAnalysis component should be rendered correctly 1`] = ` -<React.Fragment> - <div - aria-busy="true" - className="analysis-container-spinner" - > - <StyledSpinner - fluidType={3} - size="5em" - /> - </div> -</React.Fragment> +<Provider + store={ + Object { + "clearActions": [Function], + "dispatch": [Function], + "getActions": [Function], + "getState": [Function], + "replaceReducer": [Function], + "subscribe": [Function], + } + } +> + <MonthlyAnalysis + analysisDate={"2021-07-01T00:00:00.000Z"} + /> +</Provider> `; diff --git a/src/components/DateNavigator/DateNavigator.spec.tsx b/src/components/DateNavigator/DateNavigator.spec.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ade638c42578e6725027e83de255e03cd0aa39a0 --- /dev/null +++ b/src/components/DateNavigator/DateNavigator.spec.tsx @@ -0,0 +1,184 @@ +import React from 'react' +import { mount } from 'enzyme' + +import { DateTime } from 'luxon' + +import * as reactRedux from 'react-redux' +import { act } from '@testing-library/react' + +import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' +import { Provider } from 'react-redux' +import configureStore from 'redux-mock-store' +import DateNavigator from './DateNavigator' +import { mockInitialChartState } from '../../../tests/__mocks__/store' +import { IconButton } from '@material-ui/core' + +jest.mock('cozy-ui/transpiled/react/I18n', () => { + return { + useI18n: jest.fn(() => { + return { + t: (str: string) => str, + } + }), + } +}) +const mockStore = configureStore([]) + +const mockUseDispatch = jest.spyOn(reactRedux, 'useDispatch') + +describe('DateNavigator component', () => { + it('should be rendered correctly', () => { + const store = mockStore({ + ecolyo: { + global: globalStateData, + chart: mockInitialChartState, + }, + }) + const wrapper = mount( + <Provider store={store}> + <DateNavigator + currentAnalysisDate={DateTime.fromISO('2021-07-01T00:00:00.000Z', { + zone: 'utc', + })} + /> + </Provider> + ).getElement() + expect(wrapper).toMatchSnapshot() + }) + it('should click on left arrow and change date', async () => { + const store = mockStore({ + ecolyo: { + global: globalStateData, + chart: mockInitialChartState, + }, + }) + const wrapper = mount( + <Provider store={store}> + <DateNavigator + currentAnalysisDate={DateTime.fromISO('2021-07-01T00:00:00.000Z', { + zone: 'utc', + })} + /> + </Provider> + ) + await act(async () => { + await new Promise(resolve => setTimeout(resolve)) + wrapper.update() + }) + wrapper + .find(IconButton) + .first() + .simulate('click') + expect(mockUseDispatch).toHaveBeenCalledTimes(2) + }) + it('should click on right arrow and change date', async () => { + const store = mockStore({ + ecolyo: { + global: globalStateData, + chart: mockInitialChartState, + }, + }) + const wrapper = mount( + <Provider store={store}> + <DateNavigator currentAnalysisDate={DateTime.now()} /> + </Provider> + ) + await act(async () => { + await new Promise(resolve => setTimeout(resolve)) + wrapper.update() + }) + wrapper + .find(IconButton) + .at(1) + .simulate('click') + expect(mockUseDispatch).toHaveBeenCalledTimes(3) + }) + it('should be rendered without analysis date and change to previous date', async () => { + const store = mockStore({ + ecolyo: { + global: globalStateData, + chart: mockInitialChartState, + }, + }) + const wrapper = mount( + <Provider store={store}> + <DateNavigator /> + </Provider> + ) + await act(async () => { + await new Promise(resolve => setTimeout(resolve)) + wrapper.update() + }) + wrapper + .find(IconButton) + .at(0) + .simulate('click') + expect(mockUseDispatch).toHaveBeenCalledTimes(4) + }) + it('should be rendered without analysis date and change to previous index', async () => { + const store = mockStore({ + ecolyo: { + global: globalStateData, + chart: mockInitialChartState, + }, + }) + const wrapper = mount( + <Provider store={store}> + <DateNavigator /> + </Provider> + ) + await act(async () => { + await new Promise(resolve => setTimeout(resolve)) + wrapper.update() + }) + wrapper + .find(IconButton) + .at(1) + .simulate('click') + expect(mockUseDispatch).toHaveBeenCalledTimes(5) + }) + it('should be rendered without analysis date and change to next index', async () => { + const store = mockStore({ + ecolyo: { + global: globalStateData, + chart: mockInitialChartState, + }, + }) + const wrapper = mount( + <Provider store={store}> + <DateNavigator /> + </Provider> + ) + await act(async () => { + await new Promise(resolve => setTimeout(resolve)) + wrapper.update() + }) + wrapper + .find(IconButton) + .at(2) + .simulate('click') + expect(mockUseDispatch).toHaveBeenCalledTimes(6) + }) + it('should be rendered without analysis date and change to next date', async () => { + const store = mockStore({ + ecolyo: { + global: globalStateData, + chart: mockInitialChartState, + }, + }) + const wrapper = mount( + <Provider store={store}> + <DateNavigator /> + </Provider> + ) + await act(async () => { + await new Promise(resolve => setTimeout(resolve)) + wrapper.update() + }) + wrapper + .find(IconButton) + .at(3) + .simulate('click') + expect(mockUseDispatch).toHaveBeenCalledTimes(7) + }) +}) diff --git a/src/components/DateNavigator/DateNavigator.tsx b/src/components/DateNavigator/DateNavigator.tsx index daa227f34c790ecd60ac6794415108c0e577c4c1..799d64d7eee907023c45a3f3c78df297aee348fc 100644 --- a/src/components/DateNavigator/DateNavigator.tsx +++ b/src/components/DateNavigator/DateNavigator.tsx @@ -17,19 +17,31 @@ import DoubleRightArrowIcon from 'assets/icons/ico/double-right-arrow.svg' import DoubleLeftArrowIcon from 'assets/icons/ico/double-left-arrow.svg' import IconButton from '@material-ui/core/IconButton' import Icon from 'cozy-ui/transpiled/react/Icon' +import { TimeStep } from 'enum/timeStep.enum' -const DateNavigator: React.FC = () => { +interface DateNavigatorProps { + currentAnalysisDate?: DateTime + setCurrentAnalysisDate?: React.Dispatch<React.SetStateAction<DateTime>> +} + +const DateNavigator: React.FC<DateNavigatorProps> = ({ + currentAnalysisDate, + setCurrentAnalysisDate, +}: DateNavigatorProps) => { const { t } = useI18n() const dispatch = useDispatch() const { currentTimeStep, selectedDate, currentIndex } = useSelector( (state: AppStore) => state.ecolyo.chart ) + const disablePrev = selectedDate < DateTime.local(0, 1, 1).setZone('utc', { keepLocalTime: true, }) - const disableNext: boolean = isLastDateReached(selectedDate, currentTimeStep) + const disableNext: boolean = currentAnalysisDate + ? isLastDateReached(currentAnalysisDate, TimeStep.MONTH) + : isLastDateReached(selectedDate, currentTimeStep) const disableNextSlide: boolean = isLastPeriodReached( selectedDate, currentTimeStep @@ -37,17 +49,26 @@ const DateNavigator: React.FC = () => { const dateChartService = new DateChartService() const handleClickMove = (increment: number) => { - const updatedDate: DateTime = dateChartService.incrementeDate( - currentTimeStep, - selectedDate, - increment - ) - const updatedIndex: number = dateChartService.defineDateIndex( - currentTimeStep, - updatedDate - ) - dispatch(setSelectedDate(updatedDate)) - dispatch(setCurrentIndex(updatedIndex)) + if (!currentAnalysisDate) { + const updatedDate: DateTime = dateChartService.incrementeDate( + currentTimeStep, + selectedDate, + increment + ) + const updatedIndex: number = dateChartService.defineDateIndex( + currentTimeStep, + updatedDate + ) + dispatch(setSelectedDate(updatedDate)) + dispatch(setCurrentIndex(updatedIndex)) + } else { + const updatedDate: DateTime = dateChartService.incrementeDate( + TimeStep.MONTH, + currentAnalysisDate, + increment + ) + setCurrentAnalysisDate && setCurrentAnalysisDate(updatedDate) + } } const handleChangePrevIndex = () => { @@ -84,15 +105,17 @@ const DateNavigator: React.FC = () => { return ( <div className="date-navigator"> <div> - <IconButton - aria-label={t('consumption.accessibility.button_previous_period')} - className={classNames('date-navigator-button', { - ['disable']: disablePrev, - })} - onClick={() => handleChangePrevIndex()} - > - <Icon icon={DoubleLeftArrowIcon} size={16} /> - </IconButton> + {!currentAnalysisDate && ( + <IconButton + aria-label={t('consumption.accessibility.button_previous_period')} + className={classNames('date-navigator-button', { + ['disable']: disablePrev, + })} + onClick={() => handleChangePrevIndex()} + > + <Icon icon={DoubleLeftArrowIcon} size={16} /> + </IconButton> + )} </div> <div> <IconButton @@ -105,7 +128,14 @@ const DateNavigator: React.FC = () => { <Icon icon={LeftArrowIcon} size={16} /> </IconButton> </div> - <DateNavigatorFormat timeStep={currentTimeStep} date={selectedDate} /> + <DateNavigatorFormat + timeStep={currentAnalysisDate ? TimeStep.MONTH : currentTimeStep} + date={ + currentAnalysisDate + ? currentAnalysisDate.minus({ month: 1 }) + : selectedDate + } + /> <div> <IconButton @@ -119,15 +149,17 @@ const DateNavigator: React.FC = () => { </IconButton> </div> <div> - <IconButton - aria-label={t('consumption.accessibility.button_next_period')} - className={classNames('date-navigator-button', { - ['disable']: disableNext || disableNextSlide, - })} - onClick={() => handleChangeNextIndex()} - > - <Icon icon={DoubleRightArrowIcon} size={16} /> - </IconButton> + {!currentAnalysisDate && ( + <IconButton + aria-label={t('consumption.accessibility.button_next_period')} + className={classNames('date-navigator-button', { + ['disable']: disableNext || disableNextSlide, + })} + onClick={() => handleChangeNextIndex()} + > + <Icon icon={DoubleRightArrowIcon} size={16} /> + </IconButton> + )} </div> </div> ) diff --git a/src/components/DateNavigator/__snapshots__/DateNavigator.spec.tsx.snap b/src/components/DateNavigator/__snapshots__/DateNavigator.spec.tsx.snap new file mode 100644 index 0000000000000000000000000000000000000000..e58a9227a17de891a3736c3f46af672aaf4b52d8 --- /dev/null +++ b/src/components/DateNavigator/__snapshots__/DateNavigator.spec.tsx.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DateNavigator component should be rendered correctly 1`] = ` +<Provider + store={ + Object { + "clearActions": [Function], + "dispatch": [Function], + "getActions": [Function], + "getState": [Function], + "replaceReducer": [Function], + "subscribe": [Function], + } + } +> + <DateNavigator + currentAnalysisDate={"2021-07-01T00:00:00.000Z"} + /> +</Provider> +`; diff --git a/src/components/PerformanceIndicator/PerformanceIndicatorContent.tsx b/src/components/PerformanceIndicator/PerformanceIndicatorContent.tsx index 172ea56387ce65a0c7863e9dd357d82727616f62..286cdd842de0f806a9922218e0d1d80675222e21 100644 --- a/src/components/PerformanceIndicator/PerformanceIndicatorContent.tsx +++ b/src/components/PerformanceIndicator/PerformanceIndicatorContent.tsx @@ -14,22 +14,22 @@ import GreyIndicatorIcon from 'assets/icons/visu/indicator/grey.svg' import ErrorIndicatorIcon from 'assets/icons/visu/indicator/error.svg' import { FluidType } from 'enum/fluid.enum' import './fluidPerformanceIndicator.scss' -import { useSelector } from 'react-redux' -import { AppStore } from 'store' import { convertDateToMonthString } from 'utils/date' +import { DateTime } from 'luxon' interface PerformanceIndicatorContentProps { performanceIndicator: PerformanceIndicator timeStep: TimeStep fluidLackOfData?: Array<FluidType> + analysisDate?: DateTime } const PerformanceIndicatorContent: React.FC<PerformanceIndicatorContentProps> = ({ performanceIndicator, fluidLackOfData = [], + analysisDate, }: PerformanceIndicatorContentProps) => { const { t } = useI18n() - const profile = useSelector((state: AppStore) => state.ecolyo.profile) let displayedValue: string if (performanceIndicator && performanceIndicator.value) displayedValue = formatNumberValues(performanceIndicator.value).toString() @@ -90,9 +90,10 @@ const PerformanceIndicatorContent: React.FC<PerformanceIndicatorContentProps> = /> <div className="evolution-text"> {t('performance_indicator.bilan.text1')} - {convertDateToMonthString( - profile.monthlyAnalysisDate.plus({ month: -2 }) - ).substring(3)}{' '} + {analysisDate && + convertDateToMonthString( + analysisDate.plus({ month: -2 }) + ).substring(3)}{' '} : <span className={`fpi-content-perf-indicator-kpi ${perfStatus[0]} card-text-bold`} diff --git a/tests/__mocks__/globalStateData.mock.ts b/tests/__mocks__/globalStateData.mock.ts index 8f851920a7012498ddaf264f749a15752a4c7e51..03a9222e6c96858d24168c9b85e21452f37e1c52 100644 --- a/tests/__mocks__/globalStateData.mock.ts +++ b/tests/__mocks__/globalStateData.mock.ts @@ -4,7 +4,9 @@ import { GlobalState } from 'models' export const globalStateData: GlobalState = { screenType: ScreenType.MOBILE, - challengeNotification: false, + challengeExplorationNotification: false, + challengeActionNotification: false, + challengeDuelNotification: false, analysisNotification: false, fluidStatus: [ {