From 1fbfd2fb5c4636725774e51bdeace82f04cf32c8 Mon Sep 17 00:00:00 2001 From: Bastien DUMONT <bdumont@grandlyon.com> Date: Thu, 11 May 2023 08:23:21 +0000 Subject: [PATCH] feat: load app faster by splitting ecogestures --- src/components/Action/ActionBegin.spec.tsx | 4 +- .../Ecogesture/EcogestureCard.spec.tsx | 6 +- .../Ecogesture/EcogestureList.spec.tsx | 8 +- .../Ecogesture/EcogestureModal.spec.tsx | 6 +- .../Ecogesture/EcogestureView.spec.tsx | 38 +--- src/components/Ecogesture/EcogestureView.tsx | 63 +++--- .../Ecogesture/SingleEcogesture.spec.tsx | 14 +- .../EcogestureView.spec.tsx.snap | 116 +--------- src/components/Ecogesture/ecogestureList.scss | 2 +- .../EcogestureSelection.spec.tsx | 8 +- .../EcogestureSelectionDetail.spec.tsx | 18 +- .../exportLoadingModal.spec.tsx.snap | 52 +++-- .../KonnectorModal.spec.tsx.snap | 52 +++-- src/components/Loader/Loader.scss | 53 ++--- src/components/Loader/Loader.tsx | 29 +-- src/components/Splash/SplashRoot.tsx | 5 +- .../__snapshots__/SplashRoot.spec.tsx.snap | 2 +- .../__snapshots__/SplashScreen.spec.tsx.snap | 2 +- src/locales/fr.json | 11 +- src/models/initialisationSteps.model.ts | 18 +- src/services/ecogesture.service.spec.ts | 202 ++++++++++++------ src/services/ecogesture.service.ts | 97 ++++++++- src/services/initialization.service.spec.ts | 113 ---------- src/services/initialization.service.ts | 87 -------- src/utils/hash.spec.ts | 6 +- tests/__mocks__/ecogesturesData.mock.ts | 2 +- 26 files changed, 438 insertions(+), 576 deletions(-) diff --git a/src/components/Action/ActionBegin.spec.tsx b/src/components/Action/ActionBegin.spec.tsx index e660968f8..3de8d988d 100644 --- a/src/components/Action/ActionBegin.spec.tsx +++ b/src/components/Action/ActionBegin.spec.tsx @@ -8,7 +8,7 @@ import { AllEcogestureData, defaultEcogestureData, } from '../../../tests/__mocks__/actionData.mock' -import { ecogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' +import { mockedEcogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' import { profileData } from '../../../tests/__mocks__/profileData.mock' import { userChallengeData } from '../../../tests/__mocks__/userChallengeData.mock' @@ -88,7 +88,7 @@ describe('ActionBegin component', () => { const wrapper = mount( <Provider store={store}> <ActionBegin - action={ecogesturesData[1]} + action={mockedEcogesturesData[1]} setShowList={jest.fn()} userChallenge={userChallengeData[1]} /> diff --git a/src/components/Ecogesture/EcogestureCard.spec.tsx b/src/components/Ecogesture/EcogestureCard.spec.tsx index 89d866f43..45f811327 100644 --- a/src/components/Ecogesture/EcogestureCard.spec.tsx +++ b/src/components/Ecogesture/EcogestureCard.spec.tsx @@ -6,7 +6,7 @@ import React from 'react' import { Provider } from 'react-redux' import { BrowserRouter } from 'react-router-dom' import configureStore from 'redux-mock-store' -import { ecogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' +import { mockedEcogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' import { waitForComponentToPaint } from '../../../tests/__mocks__/testUtils' @@ -44,7 +44,7 @@ describe('EcogestureCard component', () => { const wrapper = mount( <Provider store={store}> <BrowserRouter> - <EcogestureCard ecogesture={ecogesturesData[0]} /> + <EcogestureCard ecogesture={mockedEcogesturesData[0]} /> </BrowserRouter> </Provider> ) @@ -62,7 +62,7 @@ describe('EcogestureCard component', () => { const wrapper = mount( <Provider store={store}> <BrowserRouter> - <EcogestureCard ecogesture={ecogesturesData[0]} /> + <EcogestureCard ecogesture={mockedEcogesturesData[0]} /> </BrowserRouter> </Provider> ) diff --git a/src/components/Ecogesture/EcogestureList.spec.tsx b/src/components/Ecogesture/EcogestureList.spec.tsx index 0ea4f27a9..68b746407 100644 --- a/src/components/Ecogesture/EcogestureList.spec.tsx +++ b/src/components/Ecogesture/EcogestureList.spec.tsx @@ -7,7 +7,7 @@ import { Provider } from 'react-redux' import { BrowserRouter } from 'react-router-dom' import configureStore from 'redux-mock-store' import { challengeStateData } from '../../../tests/__mocks__/challengeStateData.mock' -import { ecogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' +import { mockedEcogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' import { waitForComponentToPaint } from '../../../tests/__mocks__/testUtils' @@ -37,7 +37,7 @@ describe('EcogesturesList component', () => { <Provider store={store}> <BrowserRouter> <EcogestureList - list={ecogesturesData} + list={mockedEcogesturesData} displaySelection={false} selectionTotal={0} selectionViewed={0} @@ -61,7 +61,7 @@ describe('EcogesturesList component', () => { <Provider store={store}> <BrowserRouter> <EcogestureList - list={ecogesturesData} + list={mockedEcogesturesData} displaySelection={false} selectionTotal={0} selectionViewed={0} @@ -88,7 +88,7 @@ describe('EcogesturesList component', () => { <Provider store={store}> <BrowserRouter> <EcogestureList - list={ecogesturesData} + list={mockedEcogesturesData} displaySelection={true} selectionTotal={50} selectionViewed={10} diff --git a/src/components/Ecogesture/EcogestureModal.spec.tsx b/src/components/Ecogesture/EcogestureModal.spec.tsx index 778b78ada..f05e4a50e 100644 --- a/src/components/Ecogesture/EcogestureModal.spec.tsx +++ b/src/components/Ecogesture/EcogestureModal.spec.tsx @@ -5,7 +5,7 @@ import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import configureStore from 'redux-mock-store' import { challengeStateData } from '../../../tests/__mocks__/challengeStateData.mock' -import { ecogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' +import { mockedEcogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' jest.mock('cozy-ui/transpiled/react/I18n', () => { @@ -41,7 +41,7 @@ describe('EcogestureModal component', () => { <Provider store={store}> <EcogestureModal open={true} - ecogesture={ecogesturesData[0]} + ecogesture={mockedEcogesturesData[0]} isAction={false} handleCloseClick={jest.fn()} /> @@ -52,7 +52,7 @@ describe('EcogestureModal component', () => { wrapper.update() }) expect(wrapper.find('.em-title').text()).toEqual( - ecogesturesData[0].shortName + mockedEcogesturesData[0].shortName ) }) }) diff --git a/src/components/Ecogesture/EcogestureView.spec.tsx b/src/components/Ecogesture/EcogestureView.spec.tsx index 04f4eb18e..3a2c6b131 100644 --- a/src/components/Ecogesture/EcogestureView.spec.tsx +++ b/src/components/Ecogesture/EcogestureView.spec.tsx @@ -7,7 +7,7 @@ import React from 'react' import * as reactRedux from 'react-redux' import { Provider } from 'react-redux' import * as profileActions from 'store/profile/profile.actions' -import { ecogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' +import { mockedEcogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' import { createMockEcolyoStore, mockInitialProfileState, @@ -27,11 +27,13 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => { }) const mockGetAllEcogestures = jest.fn() const mockGetEcogestureListByProfile = jest.fn() +const mockInitEcogesture = jest.fn() jest.mock('services/ecogesture.service', () => { return jest.fn(() => { return { getAllEcogestures: mockGetAllEcogestures, getEcogestureListByProfile: mockGetEcogestureListByProfile, + initEcogesture: mockInitEcogesture, } }) }) @@ -77,17 +79,19 @@ describe('EcogestureView component', () => { mockGetSeason.mockClear() mockGetAllEcogestures.mockClear() mockGetEcogestureListByProfile.mockClear() - }) - it('should be rendered correctly', async () => { useSelectorSpy.mockReturnValue({ profile: mockInitialProfileState, isProfileTypeCompleted: true, haveSeenEcogestureModal: true, }) + + mockInitEcogesture.mockResolvedValue(mockedEcogesturesData) mockGetSeason.mockReturnValue(Season.WINTER) - mockGetAllEcogestures.mockResolvedValueOnce(ecogesturesData) mockGetEcogestureListByProfile.mockResolvedValue([]) + }) + + it('should be rendered correctly', async () => { const wrapper = mount( <Provider store={store}> <EcogestureView /> @@ -98,17 +102,10 @@ describe('EcogestureView component', () => { expect(wrapper.find(Tab).length).toBe(3) expect(toJson(wrapper)).toMatchSnapshot() }) + it('should render ecogesture init modal', async () => { const updateProfileSpy = jest.spyOn(profileActions, 'updateProfile') - useSelectorSpy.mockReturnValue({ - profile: mockInitialProfileState, - isProfileTypeCompleted: true, - haveSeenEcogestureModal: false, - }) - mockGetSeason.mockReturnValue(Season.WINTER) - mockGetAllEcogestures.mockResolvedValueOnce(ecogesturesData) - mockGetEcogestureListByProfile.mockResolvedValueOnce([]) const wrapper = mount( <Provider store={store}> <EcogestureView /> @@ -126,14 +123,6 @@ describe('EcogestureView component', () => { }) it('should render empty list', async () => { - useSelectorSpy.mockReturnValue({ - profile: mockInitialProfileState, - isProfileTypeCompleted: true, - haveSeenEcogestureModal: false, - }) - mockGetSeason.mockReturnValue(Season.WINTER) - mockGetAllEcogestures.mockResolvedValueOnce([]) - mockGetEcogestureListByProfile.mockResolvedValueOnce([]) const wrapper = mount( <Provider store={store}> <EcogestureView /> @@ -143,15 +132,8 @@ describe('EcogestureView component', () => { expect(wrapper.find(EcogestureEmptyList).exists()).toBeTruthy() }) + it('should change tab', async () => { - useSelectorSpy.mockReturnValue({ - profile: mockInitialProfileState, - isProfileTypeCompleted: true, - haveSeenEcogestureModal: true, - }) - mockGetSeason.mockReturnValue(Season.WINTER) - mockGetAllEcogestures.mockResolvedValueOnce(ecogesturesData) - mockGetEcogestureListByProfile.mockResolvedValueOnce([]) const wrapper = mount( <Provider store={store}> <EcogestureView /> diff --git a/src/components/Ecogesture/EcogestureView.tsx b/src/components/Ecogesture/EcogestureView.tsx index 7b5342b7d..45179a9ba 100644 --- a/src/components/Ecogesture/EcogestureView.tsx +++ b/src/components/Ecogesture/EcogestureView.tsx @@ -15,7 +15,6 @@ import { useLocation, useNavigate } from 'react-router-dom' import EcogestureService from 'services/ecogesture.service' import { AppActionsTypes, AppStore } from 'store' import { updateProfile } from 'store/profile/profile.actions' -import { getSeason } from 'utils/utils' import EcogestureEmptyList from './EcogestureEmptyList' import EcogestureInitModal from './EcogestureInitModal' import EcogestureReinitModal from './EcogestureReinitModal' @@ -53,17 +52,15 @@ const EcogestureView: React.FC = () => { const dispatch = useDispatch<Dispatch<AppActionsTypes>>() const tab = new URLSearchParams(useLocation().search).get('tab') - const { - profile: { haveSeenEcogestureModal, isProfileEcogestureCompleted }, - profileEcogesture, - profileType, - } = useSelector((state: AppStore) => state.ecolyo) + const { profile, profileEcogesture, profileType } = useSelector( + (state: AppStore) => state.ecolyo + ) const [tabValue, setTabValue] = useState<EcogestureTab>( tab ? parseInt(tab) : EcogestureTab.OBJECTIVE ) const navigate = useNavigate() - const [isLoaded, setIsLoaded] = useState<boolean>(false) + const [isLoading, setIsLoading] = useState<boolean>(true) const [allEcogestureList, setAllEcogestureList] = useState<Ecogesture[]>([]) const [doingEcogestureList, setDoingEcogestureList] = useState<Ecogesture[]>( [] @@ -74,7 +71,7 @@ const EcogestureView: React.FC = () => { const [totalViewed, setTotalViewed] = useState<number>(0) const [totalAvailable, setTotalAvailable] = useState<number>(0) const [openEcogestureInitModal, setOpenEcogestureInitModal] = - useState<boolean>(!haveSeenEcogestureModal) + useState<boolean>(!profile.haveSeenEcogestureModal) const [openEcogestureReinitModal, setOpenEcogestureReinitModal] = useState<boolean>(false) @@ -95,12 +92,12 @@ const EcogestureView: React.FC = () => { const handleLaunchReinit = useCallback(async () => { setOpenEcogestureReinitModal(false) - setIsLoaded(false) + setIsLoading(true) const ecogestureService = new EcogestureService(client) const reset = await ecogestureService.reinitAllEcogestures() if (reset) { setOpenEcogestureReinitModal(false) - setIsLoaded(true) + setIsLoading(false) navigate('/ecogesture-form?modal=true') } }, [client, navigate]) @@ -156,42 +153,52 @@ const EcogestureView: React.FC = () => { let subscribed = true async function loadEcogestures() { const ecogestureService = new EcogestureService(client) - const dataAll = await ecogestureService.getAllEcogestures(getSeason()) - const availableList: Ecogesture[] = - await ecogestureService.getEcogestureListByProfile(profileEcogesture) - const filteredList: Ecogesture[] = availableList.filter( - (ecogesture: Ecogesture) => ecogesture.viewedInSelection === false + + const { ecogestureList, ecogestureHash } = + await ecogestureService.initEcogesture(profile.ecogestureHash) + + if (ecogestureHash !== profile.ecogestureHash) { + dispatch(updateProfile({ ecogestureHash })) + } + + const availableList = await ecogestureService.getEcogestureListByProfile( + profileEcogesture + ) + const filteredList = availableList.filter( + ecogesture => ecogesture.viewedInSelection === false ) - if (subscribed && dataAll) { - const doing = dataAll.filter(ecogesture => ecogesture.doing === true) - const objective = dataAll.filter( + if (subscribed && ecogestureList) { + const doing = ecogestureList.filter( + ecogesture => ecogesture.doing === true + ) + const objective = ecogestureList.filter( ecogesture => ecogesture.objective === true ) - setAllEcogestureList(dataAll) + setAllEcogestureList(ecogestureList) setObjectiveEcogestureList(objective) setDoingEcogestureList(doing) setTotalAvailable(availableList.length) setTotalViewed(availableList.length - filteredList.length) } - setIsLoaded(true) + setIsLoading(false) } loadEcogestures() return () => { subscribed = false } - }, [client, profileEcogesture, profileType]) + }, [client, profileEcogesture, profileType, dispatch, profile.ecogestureHash]) return ( <> <CozyBar titleKey={'common.title_ecogestures'} /> - {!isLoaded && ( + {isLoading && ( <Content height={headerHeight}> <div className="ecogesture-spinner" aria-busy="true"> - <Loader /> + <Loader text={t('ecogestures.loading')} /> </div> </Content> )} - {isLoaded && ( + {!isLoading && ( <> <Header setHeaderHeight={defineHeaderHeight} @@ -231,7 +238,7 @@ const EcogestureView: React.FC = () => { <Content height={headerHeight}> <TabPanel value={tabValue} tab={EcogestureTab.OBJECTIVE}> - {isProfileEcogestureCompleted && + {profile.isProfileEcogestureCompleted && (totalAvailable === totalViewed && objectiveEcogestureList.length === 0 ? ( <EcogestureEmptyList @@ -249,7 +256,7 @@ const EcogestureView: React.FC = () => { handleReinitClick={handleReinitClick} /> ))} - {!isProfileEcogestureCompleted && ( + {!profile.isProfileEcogestureCompleted && ( <EcogestureEmptyList setTab={setTabValue} isObjective={true} @@ -260,7 +267,7 @@ const EcogestureView: React.FC = () => { </TabPanel> <TabPanel value={tabValue} tab={EcogestureTab.DOING}> - {isProfileEcogestureCompleted && + {profile.isProfileEcogestureCompleted && (totalAvailable === totalViewed && doingEcogestureList.length === 0 ? ( <EcogestureEmptyList @@ -278,7 +285,7 @@ const EcogestureView: React.FC = () => { handleReinitClick={handleReinitClick} /> ))} - {!isProfileEcogestureCompleted && ( + {!profile.isProfileEcogestureCompleted && ( <EcogestureEmptyList setTab={setTabValue} isObjective={false} diff --git a/src/components/Ecogesture/SingleEcogesture.spec.tsx b/src/components/Ecogesture/SingleEcogesture.spec.tsx index 140a42012..38942976a 100644 --- a/src/components/Ecogesture/SingleEcogesture.spec.tsx +++ b/src/components/Ecogesture/SingleEcogesture.spec.tsx @@ -6,7 +6,7 @@ import React from 'react' import { Provider } from 'react-redux' import configureStore from 'redux-mock-store' import { challengeStateData } from '../../../tests/__mocks__/challengeStateData.mock' -import { ecogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' +import { mockedEcogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' import { waitForComponentToPaint } from '../../../tests/__mocks__/testUtils' @@ -70,7 +70,7 @@ describe('SingleEcogesture component', () => { }, }) - mockGetEcogesturesByIds.mockResolvedValue([ecogesturesData[0]]) + mockGetEcogesturesByIds.mockResolvedValue([mockedEcogesturesData[0]]) const wrapper = mount( <Provider store={store}> <SingleEcogesture /> @@ -88,9 +88,9 @@ describe('SingleEcogesture component', () => { }, }) - mockGetEcogesturesByIds.mockResolvedValue([ecogesturesData[0]]) + mockGetEcogesturesByIds.mockResolvedValue([mockedEcogesturesData[0]]) mockImportIconById.mockReturnValue('') - const updatedEcogesture = { ...ecogesturesData[0], doing: true } + const updatedEcogesture = { ...mockedEcogesturesData[0], doing: true } mockUpdateEcogesture.mockResolvedValueOnce(updatedEcogesture) const wrapper = mount( <Provider store={store}> @@ -110,9 +110,9 @@ describe('SingleEcogesture component', () => { }, }) - mockGetEcogesturesByIds.mockResolvedValue([ecogesturesData[0]]) + mockGetEcogesturesByIds.mockResolvedValue([mockedEcogesturesData[0]]) mockImportIconById.mockReturnValue('icontest') - const updatedEcogesture = { ...ecogesturesData[0], objective: true } + const updatedEcogesture = { ...mockedEcogesturesData[0], objective: true } mockUpdateEcogesture.mockResolvedValueOnce(updatedEcogesture) const wrapper = mount( @@ -133,7 +133,7 @@ describe('SingleEcogesture component', () => { }, }) - mockGetEcogesturesByIds.mockResolvedValue([ecogesturesData[0]]) + mockGetEcogesturesByIds.mockResolvedValue([mockedEcogesturesData[0]]) mockImportIconById.mockReturnValue(undefined) const wrapper = mount( diff --git a/src/components/Ecogesture/__snapshots__/EcogestureView.spec.tsx.snap b/src/components/Ecogesture/__snapshots__/EcogestureView.spec.tsx.snap index d47df1261..caf3bc141 100644 --- a/src/components/Ecogesture/__snapshots__/EcogestureView.spec.tsx.snap +++ b/src/components/Ecogesture/__snapshots__/EcogestureView.spec.tsx.snap @@ -378,7 +378,7 @@ exports[`EcogestureView component should be rendered correctly 1`] = ` <React.Fragment> ecogesture.title_tab_2 <br /> - (3) + (0) </React.Fragment> } onChange={[Function]} @@ -410,7 +410,7 @@ exports[`EcogestureView component should be rendered correctly 1`] = ` <React.Fragment> ecogesture.title_tab_2 <br /> - (3) + (0) </React.Fragment> } onChange={[Function]} @@ -476,7 +476,7 @@ exports[`EcogestureView component should be rendered correctly 1`] = ` > ecogesture.title_tab_2 <br /> - (3) + (0) </span> <WithStyles(memo) center={false} @@ -1242,115 +1242,7 @@ exports[`EcogestureView component should be rendered correctly 1`] = ` hidden={true} id="simple-tabpanel-2" role="tabpanel" - > - <mock-ecogesturelist - displaySelection={false} - list={ - Array [ - Object { - "_id": "ECOGESTURE001", - "_rev": "1-67f1ea36efdd892c96bf64a8943154cd", - "_type": "com.grandlyon.ecolyo.ecogesture", - "action": false, - "actionDuration": 3, - "actionName": null, - "difficulty": 1, - "doing": false, - "efficiency": 4, - "equipment": false, - "equipmentInstallation": true, - "equipmentType": Array [], - "fluidTypes": Array [ - 0, - 2, - ], - "id": "ECOGESTURE001", - "impactLevel": 8, - "investment": null, - "longDescription": "On se demande parfois si cela vaut le coup de \\"couper le chauffage\\" quand on s’absente… dès qu’il s’agit d’un week-end la réponse est « oui sûrement » ! Attention cependant au retour à ne pas faire de la surchauffe ! L’idéal est bien évidemment de régler sa programmation pour que le chauffage se relance quelques heures avant votre retour…", - "longName": "Je baisse le chauffage en mode hors gel lorsque je m'absente plus de 2 jours.", - "objective": false, - "room": Array [ - 0, - ], - "season": "Hiver", - "shortName": "Bonhomme de neige", - "usage": 1, - "viewedInSelection": false, - }, - Object { - "_id": "ECOGESTURE002", - "_rev": "1-ef7ddd778254e3b7d331a88fd17f606d", - "_type": "com.grandlyon.ecolyo.ecogesture", - "action": false, - "actionDuration": 3, - "actionName": null, - "difficulty": 1, - "doing": false, - "efficiency": 4, - "equipment": true, - "equipmentInstallation": true, - "equipmentType": Array [ - "AIR_CONDITIONING", - ], - "fluidTypes": Array [ - 0, - ], - "id": "ECOGESTURE002", - "impactLevel": 8, - "investment": null, - "longDescription": "Cela permet de garder la fraîcheur à l'intérieur. Le climatiseur n'est pas là pour refroidir la rue mais bien la pièce.", - "longName": "Je ferme mes fenêtres quand la climatisation est en marche", - "objective": false, - "room": Array [ - 0, - ], - "season": "Eté", - "shortName": "Coup de vent", - "usage": 2, - "viewedInSelection": false, - }, - Object { - "_id": "ECOGESTURE0013", - "_rev": "1-0b2761dd4aef79556c7aef144060fde6", - "_type": "com.grandlyon.ecolyo.ecogesture", - "action": true, - "actionDuration": 3, - "actionName": "J’utilise le cycle court à basse température pour laver le linge et la vaisselle.", - "difficulty": 1, - "doing": false, - "efficiency": 1, - "equipment": false, - "equipmentInstallation": true, - "equipmentType": Array [ - "WASHING_MACHINE", - "DISHWASHER", - ], - "fluidTypes": Array [ - 1, - ], - "id": "ECOGESTURE0013", - "impactLevel": 2, - "investment": null, - "longDescription": "Utilisez la température la plus basse possible : de nombreux produits nettoyants sont efficaces à froid et un cycle à 90 °C consomme 3 fois plus d'énergie qu'un lavage à 40 °C. En effet, 80 % de l'énergie consommée par un lave-linge ou un lave-vaisselle sert au chauffage de l'eau ! Que ce soit pour la vaisselle ou le linge, les programmes de lavage intensif consomment jusqu'à 40 % de plus. Si possible, rincez à l'eau froide : la température de rinçage n'a pas d'effet sur le nettoyage du linge ou de la vaisselle. Attention cependant avec les tissus qui peuvent rétrécir : ce qui fait rétrécir, c'est le passage d'une température à une autre. Mieux vaut alors faire le cycle complet à l'eau froide pour les premiers lavages de tissus sensibles. Pour du linge ou de la vaisselle peu sales, utilisez la touche \\"Eco\\". Elle réduit la température de lavage et allonge sa durée (c’est le chauffage de l’eau qui consomme le plus). Vous économiserez jusqu’à 45 % par rapport aux cycles longs. Néanmoins, pour vous prémunir contre les bouchons de graisse dans les canalisations, faites quand même un cycle à chaud une fois par mois environ.", - "longName": "J’utilise le plus souvent les cycles courts à basse température pour laver le linge et la vaisselle.", - "objective": false, - "room": Array [ - 1, - 3, - 2, - ], - "season": "Sans saison", - "shortName": "Accelerateur de particules", - "usage": 5, - "viewedInSelection": false, - }, - ] - } - selectionTotal={0} - selectionViewed={0} - /> - </div> + /> </TabPanel> </mock-content> <EcogestureInitModal diff --git a/src/components/Ecogesture/ecogestureList.scss b/src/components/Ecogesture/ecogestureList.scss index 4b0beefe4..6f3b3edcc 100644 --- a/src/components/Ecogesture/ecogestureList.scss +++ b/src/components/Ecogesture/ecogestureList.scss @@ -92,7 +92,6 @@ } .ecogesture-content { display: flex; - justify-content: center; flex-wrap: wrap; max-width: 53rem; animation: appear 600ms ease; @@ -126,6 +125,7 @@ display: flex; flex: 1; flex-basis: 45%; + max-width: 48%; } .ecogesture-list-item > button { height: 100%; diff --git a/src/components/EcogestureSelection/EcogestureSelection.spec.tsx b/src/components/EcogestureSelection/EcogestureSelection.spec.tsx index ea641c50a..0ea212bcf 100644 --- a/src/components/EcogestureSelection/EcogestureSelection.spec.tsx +++ b/src/components/EcogestureSelection/EcogestureSelection.spec.tsx @@ -4,7 +4,7 @@ import toJson from 'enzyme-to-json' import React from 'react' import { Provider } from 'react-redux' import mockClient from '../../../tests/__mocks__/client' -import { ecogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' +import { mockedEcogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' import { createMockEcolyoStore } from '../../../tests/__mocks__/store' import { waitForComponentToPaint } from '../../../tests/__mocks__/testUtils' import EcogestureSelection from './EcogestureSelection' @@ -74,7 +74,7 @@ describe('EcogestureSelection component', () => { }) it('should be rendered correctly', async () => { - mockGetEcogestureListByProfile.mockResolvedValue([ecogesturesData[0]]) + mockGetEcogestureListByProfile.mockResolvedValue([mockedEcogesturesData[0]]) const wrapper = mount( <Provider store={store}> <EcogestureSelection /> @@ -84,7 +84,7 @@ describe('EcogestureSelection component', () => { expect(toJson(wrapper)).toMatchSnapshot() }) it('should render with the EcogestureSelectionModal', async () => { - mockGetEcogestureListByProfile.mockResolvedValue([ecogesturesData[0]]) + mockGetEcogestureListByProfile.mockResolvedValue([mockedEcogesturesData[0]]) const wrapper = mount( <Provider store={store}> <EcogestureSelection /> @@ -94,7 +94,7 @@ describe('EcogestureSelection component', () => { expect(wrapper.find('mock-ecogestureselectionmodal').exists()).toBeTruthy() }) it('should render with the EcogestureSelectionDetail', async () => { - mockGetEcogestureListByProfile.mockResolvedValue([ecogesturesData[0]]) + mockGetEcogestureListByProfile.mockResolvedValue([mockedEcogesturesData[0]]) const wrapper = mount( <Provider store={store}> <EcogestureSelection /> diff --git a/src/components/EcogestureSelection/EcogestureSelectionDetail.spec.tsx b/src/components/EcogestureSelection/EcogestureSelectionDetail.spec.tsx index 927ce93f4..2aa3ec4a6 100644 --- a/src/components/EcogestureSelection/EcogestureSelectionDetail.spec.tsx +++ b/src/components/EcogestureSelection/EcogestureSelectionDetail.spec.tsx @@ -2,7 +2,7 @@ import { Button } from '@material-ui/core' import { mount } from 'enzyme' import toJson from 'enzyme-to-json' import React from 'react' -import { ecogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' +import { mockedEcogesturesData } from '../../../tests/__mocks__/ecogesturesData.mock' import { waitForComponentToPaint } from '../../../tests/__mocks__/testUtils' import EcogestureSelectionDetail from './EcogestureSelectionDetail' @@ -35,9 +35,9 @@ describe('EcogestureSelectionDetail component', () => { mockImportIconById.mockReturnValueOnce('testIcon') const wrapper = mount( <EcogestureSelectionDetail - ecogesture={ecogesturesData[0]} + ecogesture={mockedEcogesturesData[0]} validate={mockValidate} - title={ecogesturesData[0].shortName} + title={mockedEcogesturesData[0].shortName} /> ) await waitForComponentToPaint(wrapper) @@ -48,9 +48,9 @@ describe('EcogestureSelectionDetail component', () => { mockImportIconById.mockReturnValueOnce('testIcon') const wrapper = mount( <EcogestureSelectionDetail - ecogesture={ecogesturesData[0]} + ecogesture={mockedEcogesturesData[0]} validate={mockValidate} - title={ecogesturesData[0].shortName} + title={mockedEcogesturesData[0].shortName} /> ) wrapper.find(Button).at(0).simulate('click') @@ -62,9 +62,9 @@ describe('EcogestureSelectionDetail component', () => { mockImportIconById.mockReturnValueOnce('testIcon') const wrapper = mount( <EcogestureSelectionDetail - ecogesture={ecogesturesData[0]} + ecogesture={mockedEcogesturesData[0]} validate={mockValidate} - title={ecogesturesData[0].shortName} + title={mockedEcogesturesData[0].shortName} /> ) wrapper.find(Button).at(1).simulate('click') @@ -76,9 +76,9 @@ describe('EcogestureSelectionDetail component', () => { mockImportIconById.mockReturnValueOnce('testIcon') const wrapper = mount( <EcogestureSelectionDetail - ecogesture={ecogesturesData[0]} + ecogesture={mockedEcogesturesData[0]} validate={mockValidate} - title={ecogesturesData[0].shortName} + title={mockedEcogesturesData[0].shortName} /> ) wrapper.find(Button).at(2).simulate('click') diff --git a/src/components/Export/__snapshots__/exportLoadingModal.spec.tsx.snap b/src/components/Export/__snapshots__/exportLoadingModal.spec.tsx.snap index 85084920c..06a7f7c28 100644 --- a/src/components/Export/__snapshots__/exportLoadingModal.spec.tsx.snap +++ b/src/components/Export/__snapshots__/exportLoadingModal.spec.tsx.snap @@ -463,20 +463,24 @@ exports[`ExportLoadingModal component should be rendered correctly 1`] = ` class="icon-main" > <div - aria-busy="true" - aria-label="common.accessibility.loading" class="loader gold" - title="common.accessibility.loading" > <div - class="bar" - /> - <div - class="bar" - /> - <div - class="bar" - /> + aria-busy="true" + aria-label="common.accessibility.loading" + class="bars " + title="common.accessibility.loading" + > + <div + class="bar" + /> + <div + class="bar" + /> + <div + class="bar" + /> + </div> </div> </div> <div @@ -860,20 +864,24 @@ exports[`ExportLoadingModal component should be rendered correctly 1`] = ` color="gold" > <div - aria-busy="true" - aria-label="common.accessibility.loading" className="loader gold" - title="common.accessibility.loading" > <div - className="bar" - /> - <div - className="bar" - /> - <div - className="bar" - /> + aria-busy="true" + aria-label="common.accessibility.loading" + className="bars " + title="common.accessibility.loading" + > + <div + className="bar" + /> + <div + className="bar" + /> + <div + className="bar" + /> + </div> </div> </Loader> </div> diff --git a/src/components/Konnector/__snapshots__/KonnectorModal.spec.tsx.snap b/src/components/Konnector/__snapshots__/KonnectorModal.spec.tsx.snap index 7e78bb723..f36f95c46 100644 --- a/src/components/Konnector/__snapshots__/KonnectorModal.spec.tsx.snap +++ b/src/components/Konnector/__snapshots__/KonnectorModal.spec.tsx.snap @@ -433,20 +433,24 @@ exports[`KonnectorModal component should be rendered correctly 1`] = ` class="kmodal-content" > <div - aria-busy="true" - aria-label="common.accessibility.loading" class="loader elec" - title="common.accessibility.loading" > <div - class="bar" - /> - <div - class="bar" - /> - <div - class="bar" - /> + aria-busy="true" + aria-label="common.accessibility.loading" + class="bars " + title="common.accessibility.loading" + > + <div + class="bar" + /> + <div + class="bar" + /> + <div + class="bar" + /> + </div> </div> <div class="kmodal-content-text kmodal-content-text-center text-16-normal" @@ -795,20 +799,24 @@ exports[`KonnectorModal component should be rendered correctly 1`] = ` fluidType={0} > <div - aria-busy="true" - aria-label="common.accessibility.loading" className="loader elec" - title="common.accessibility.loading" > <div - className="bar" - /> - <div - className="bar" - /> - <div - className="bar" - /> + aria-busy="true" + aria-label="common.accessibility.loading" + className="bars " + title="common.accessibility.loading" + > + <div + className="bar" + /> + <div + className="bar" + /> + <div + className="bar" + /> + </div> </div> </Loader> <div diff --git a/src/components/Loader/Loader.scss b/src/components/Loader/Loader.scss index fb60035b9..ea5387705 100644 --- a/src/components/Loader/Loader.scss +++ b/src/components/Loader/Loader.scss @@ -1,13 +1,9 @@ @import 'src/styles/base/color'; .loader { - height: 50px; - margin: auto; display: flex; - align-items: flex-end; - justify-content: center; - gap: 8px; - + flex-direction: column; + gap: 1rem; &.gold { color: $gold; } @@ -28,26 +24,35 @@ color: $dark; } - .bar { - width: 10px; - border-radius: 5px; - background: currentColor; - // Negative delay to fix bar appearing after delay - animation: load 0.4s -0.4s linear infinite alternate; - &:nth-child(1) { - animation-delay: -0.1s; - } - &:nth-child(3) { - animation-delay: -0.55s; + .bars { + height: 50px; + margin: auto; + display: flex; + align-items: flex-end; + justify-content: center; + gap: 8px; + + .bar { + width: 10px; + border-radius: 5px; + background: currentColor; + // Negative delay to fix bar appearing after delay + animation: load 0.4s -0.4s linear infinite alternate; + &:nth-child(1) { + animation-delay: -0.1s; + } + &:nth-child(3) { + animation-delay: -0.55s; + } } - } - @keyframes load { - 0% { - height: 20%; - } - 100% { - height: 100%; + @keyframes load { + 0% { + height: 20%; + } + 100% { + height: 100%; + } } } } diff --git a/src/components/Loader/Loader.tsx b/src/components/Loader/Loader.tsx index 9646685b1..aafdf086d 100644 --- a/src/components/Loader/Loader.tsx +++ b/src/components/Loader/Loader.tsx @@ -3,16 +3,18 @@ import { FluidType } from 'enum/fluid.enum' import React from 'react' import './Loader.scss' -interface color { +interface LoaderProps { color?: 'gold' | 'gaz' | 'elec' | 'water' | 'black' fluidType?: FluidType + text?: string } /** * Loader of Ecolyo, default color is gold - * @param color 'gold' | 'gaz' | 'elec' | 'water' + * @param color {'gold' | 'gaz' | 'elec' | 'water'} Default is gold + * @param text Optional, text to be placed under the loader */ -const Loader = ({ color = 'gold', fluidType }: color) => { +const Loader = ({ color = 'gold', fluidType, text }: LoaderProps) => { const { t } = useI18n() let variant = color @@ -29,15 +31,18 @@ const Loader = ({ color = 'gold', fluidType }: color) => { } return ( - <div - className={`loader ${variant}`} - aria-busy="true" - aria-label={t('common.accessibility.loading')} - title={t('common.accessibility.loading')} - > - <div className="bar" /> - <div className="bar" /> - <div className="bar" /> + <div className={`loader ${variant}`}> + <div + className={`bars `} + aria-busy="true" + aria-label={t('common.accessibility.loading')} + title={t('common.accessibility.loading')} + > + <div className="bar" /> + <div className="bar" /> + <div className="bar" /> + </div> + {text} </div> ) } diff --git a/src/components/Splash/SplashRoot.tsx b/src/components/Splash/SplashRoot.tsx index 32a2e263f..a4f030fa5 100644 --- a/src/components/Splash/SplashRoot.tsx +++ b/src/components/Splash/SplashRoot.tsx @@ -216,23 +216,20 @@ const SplashRoot = ({ fadeTimer = 1000, children }: SplashRootProps) => { if (subscribed && profile) { setValidExploration(UserExplorationID.EXPLORATION007) const [ - ecogestureHash, duelHash, quizHash, challengeHash, explorationHash, analysisResult, ] = await Promise.all([ - initializationService.initEcogesture(profile.ecogestureHash), initializationService.initDuelEntity(profile.duelHash), initializationService.initQuizEntity(profile.quizHash), initializationService.initExplorationEntity(profile.challengeHash), initializationService.initChallengeEntity(profile.explorationHash), initializationService.initAnalysis(profile), ]) - const updatedProfile = { + const updatedProfile: Profile = { ...profile, - ecogestureHash, duelHash, quizHash, challengeHash, diff --git a/src/components/Splash/__snapshots__/SplashRoot.spec.tsx.snap b/src/components/Splash/__snapshots__/SplashRoot.spec.tsx.snap index bda2dc584..143c5d080 100644 --- a/src/components/Splash/__snapshots__/SplashRoot.spec.tsx.snap +++ b/src/components/Splash/__snapshots__/SplashRoot.spec.tsx.snap @@ -36,7 +36,7 @@ exports[`SplashRoot component should be rendered correctly 1`] = ` className="splash-progress-bar-content" style={ Object { - "width": "14%", + "width": "17%", } } /> diff --git a/src/components/Splash/__snapshots__/SplashScreen.spec.tsx.snap b/src/components/Splash/__snapshots__/SplashScreen.spec.tsx.snap index ba2b5e5a0..03f304221 100644 --- a/src/components/Splash/__snapshots__/SplashScreen.spec.tsx.snap +++ b/src/components/Splash/__snapshots__/SplashScreen.spec.tsx.snap @@ -27,7 +27,7 @@ exports[`SplashScreen component should be rendered correctly 1`] = ` className="splash-progress-bar-content" style={ Object { - "width": "14%", + "width": "17%", } } /> diff --git a/src/locales/fr.json b/src/locales/fr.json index 51db254e5..34d999bcf 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -365,6 +365,9 @@ } } }, + "ecogestures": { + "loading": "Chargement des astuces" + }, "duel": { "global_error": "Oups. Une erreur est survenue. Veuillez retourner à l'écran d’accueil des défis", "button_go_back": "Retour", @@ -1223,7 +1226,6 @@ "consent_error": "Vérification de vos consentements pour partager vos données avec Ecolyo", "profile_error": "Chargement de votre profil utilisateur", "profileType_error": "Chargement de votre profil de consommation.", - "ecogesture_error": "Chargement des astuces de consommation", "challenges_error": "Actualisation de votre progression dans les défis", "analysis_error": "Chargement de votre analyse mensuelle", "index_error": "Chargement des index", @@ -1236,10 +1238,9 @@ "0": "Mise à jour de l'application", "1": "Vérification de vos consentements pour partager vos données avec Ecolyo", "2": "Chargement de votre profil", - "3": "Chargement des astuces de consommations", - "4": "Actualisation de votre progression dans les défis", - "5": "Mise à jour des prix", - "6": "Connexion à vos données de consommation" + "3": "Actualisation de votre progression dans les défis", + "4": "Mise à jour des prix", + "5": "Connexion à vos données de consommation" } }, "timestep": { diff --git a/src/models/initialisationSteps.model.ts b/src/models/initialisationSteps.model.ts index 8f2516a38..fd9cb97b4 100644 --- a/src/models/initialisationSteps.model.ts +++ b/src/models/initialisationSteps.model.ts @@ -1,22 +1,20 @@ export enum InitSteps { - MIGRATION = 0, - CONSENT = 1, - PROFILE = 2, - ECOGESTURE = 3, - CHALLENGES = 4, - PRICES = 5, - CONSOS = 6, + MIGRATION, + CONSENT, + PROFILE, + CHALLENGES, + PRICES, // never used + CONSOS, } export enum InitStepsErrors { MIGRATION_ERROR = 'migration_error', CONSENT_ERROR = 'consent_error', PROFILE_ERROR = 'profile_error', PROFILETYPE_ERROR = 'profileType_error', - ECOGESTURE_ERROR = 'ecogesture_error', CHALLENGES_ERROR = 'challenges_error', ANALYSIS_ERROR = 'analysis_error', CONSOS_ERROR = 'consos_error', - PARTNERS_ERROR = 'partners_error', - NETWORK_ERROR = 'network_error', + PARTNERS_ERROR = 'partners_error', // never used + NETWORK_ERROR = 'network_error', // never used UNKNOWN_ERROR = 'unknown_error', } diff --git a/src/services/ecogesture.service.spec.ts b/src/services/ecogesture.service.spec.ts index 7448b0fe1..6e9ebd93f 100644 --- a/src/services/ecogesture.service.spec.ts +++ b/src/services/ecogesture.service.spec.ts @@ -1,41 +1,55 @@ import { QueryResult } from 'cozy-client' +import ecogestureData from 'db/ecogestureData.json' import { EquipmentType } from 'enum/ecogesture.enum' import { IndividualOrCollective, WarmingType } from 'enum/profileType.enum' import { Ecogesture } from 'models' import { ProfileEcogesture } from 'models/profileEcogesture.model' +import { hashFile } from 'utils/hash' import mockClient from '../../tests/__mocks__/client' import { BoilerEcogesture, BoilerEcogestureFalse, - ecogesturesData, ecogesturesECSData, ecogesturesHeatingData, + mockedEcogesturesData, } from '../../tests/__mocks__/ecogesturesData.mock' import { mockProfileEcogesture } from '../../tests/__mocks__/profileEcogesture.mock' import EcogestureService from './ecogesture.service' +// Add types definition to json data +const ecoData = ecogestureData as Ecogesture[] + +const mockQueryResultCreated: QueryResult<boolean> = { + data: true, + bookmark: '', + next: false, + skip: 0, +} + +const mockQueryResultMockedEcogestures: QueryResult<Ecogesture[]> = { + data: mockedEcogesturesData, + bookmark: '', + next: false, + skip: 0, +} + +const mockQueryResultEmpty: QueryResult<Ecogesture[]> = { + data: [], + bookmark: '', + next: false, + skip: 0, +} + describe('Ecogesture service', () => { const ecogestureService = new EcogestureService(mockClient) describe('getAllEcogestures', () => { it('should return all ecogestures', async () => { - const mockQueryResult: QueryResult<Ecogesture[]> = { - data: ecogesturesData, - bookmark: '', - next: false, - skip: 0, - } - mockClient.query.mockResolvedValueOnce(mockQueryResult) + mockClient.query.mockResolvedValueOnce(mockQueryResultMockedEcogestures) const result = await ecogestureService.getAllEcogestures() - expect(result).toEqual(ecogesturesData) + expect(result).toEqual(mockedEcogesturesData) }) it('should return empty array when no ecogestures stored', async () => { - const mockQueryResult: QueryResult<Ecogesture[]> = { - data: [], - bookmark: '', - next: false, - skip: 0, - } - mockClient.query.mockResolvedValueOnce(mockQueryResult) + mockClient.query.mockResolvedValueOnce(mockQueryResultEmpty) const result = await ecogestureService.getAllEcogestures() expect(result).toEqual([]) }) @@ -43,37 +57,19 @@ describe('Ecogesture service', () => { describe('deleteAllEcogestures', () => { it('should return true when 3 ecogestures stored', async () => { - const mockQueryResult: QueryResult<Ecogesture[]> = { - data: ecogesturesData, - bookmark: '', - next: false, - skip: 0, - } - mockClient.query.mockResolvedValueOnce(mockQueryResult) + mockClient.query.mockResolvedValueOnce(mockQueryResultMockedEcogestures) const result = await ecogestureService.deleteAllEcogestures() expect(mockClient.destroy).toBeCalledTimes(3) expect(result).toBe(true) }) it('should return true when no ecogestures stored', async () => { - const mockQueryResult: QueryResult<Ecogesture[]> = { - data: [], - bookmark: '', - next: false, - skip: 0, - } - mockClient.query.mockResolvedValueOnce(mockQueryResult) + mockClient.query.mockResolvedValueOnce(mockQueryResultEmpty) const result = await ecogestureService.deleteAllEcogestures() expect(result).toBe(true) }) it('should throw exception when error happened on deletion', async () => { - const mockQueryResult: QueryResult<Ecogesture[]> = { - data: ecogesturesData, - bookmark: '', - next: false, - skip: 0, - } mockClient.destroy.mockRejectedValue(new Error()) - mockClient.query.mockResolvedValueOnce(mockQueryResult) + mockClient.query.mockResolvedValueOnce(mockQueryResultMockedEcogestures) await expect(ecogestureService.deleteAllEcogestures()).rejects.toThrow( new Error() ) @@ -81,37 +77,19 @@ describe('Ecogesture service', () => { }) describe('reinitAllEcogestures', () => { it('should return true when 3 ecogestures stored', async () => { - const mockQueryResult: QueryResult<Ecogesture[]> = { - data: ecogesturesData, - bookmark: '', - next: false, - skip: 0, - } - mockClient.query.mockResolvedValueOnce(mockQueryResult) + mockClient.query.mockResolvedValueOnce(mockQueryResultMockedEcogestures) const result = await ecogestureService.reinitAllEcogestures() expect(mockClient.save).toBeCalledTimes(3) expect(result).toBe(true) }) it('should return true when no ecogestures stored', async () => { - const mockQueryResult: QueryResult<Ecogesture[]> = { - data: [], - bookmark: '', - next: false, - skip: 0, - } - mockClient.query.mockResolvedValueOnce(mockQueryResult) + mockClient.query.mockResolvedValueOnce(mockQueryResultEmpty) const result = await ecogestureService.reinitAllEcogestures() expect(result).toBe(true) }) it('should throw exception when error happened on reinit', async () => { - const mockQueryResult: QueryResult<Ecogesture[]> = { - data: ecogesturesData, - bookmark: '', - next: false, - skip: 0, - } mockClient.save.mockRejectedValue(new Error()) - mockClient.query.mockResolvedValueOnce(mockQueryResult) + mockClient.query.mockResolvedValueOnce(mockQueryResultMockedEcogestures) await expect(ecogestureService.reinitAllEcogestures()).rejects.toThrow( new Error() ) @@ -170,7 +148,7 @@ describe('Ecogesture service', () => { }) }) describe('filterByEquipment', () => { - it('should return ecogesture list including BOILER equipment and equipment veriication to true', async () => { + it('should return ecogesture list including BOILER equipment and equipment verification to true', async () => { const mockProfileEcogestureBOILER: ProfileEcogesture = { ...mockProfileEcogesture, equipments: [EquipmentType.BOILER], @@ -181,7 +159,7 @@ describe('Ecogesture service', () => { ) expect(result.includes(BoilerEcogesture[0])).toBeTruthy() }) - it('should return ecogesture list excluding BOILER equipment and equipment veriication to true', async () => { + it('should return ecogesture list excluding BOILER equipment and equipment verification to true', async () => { const mockProfileEcogestureBOILER: ProfileEcogesture = { ...mockProfileEcogesture, } @@ -191,7 +169,7 @@ describe('Ecogesture service', () => { ) expect(result.includes(BoilerEcogesture[0])).toBeFalsy() }) - it('should return ecogesture list including BOILER equipment with equipment veriication to false but equipment to BOILER', async () => { + it('should return ecogesture list including BOILER equipment with equipment verification to false but equipment to BOILER', async () => { const mockProfileEcogestureBOILER: ProfileEcogesture = { ...mockProfileEcogesture, equipments: [EquipmentType.BOILER], @@ -209,19 +187,14 @@ describe('Ecogesture service', () => { ...mockProfileEcogesture, equipments: [EquipmentType.WASHING_MACHINE, EquipmentType.DISHWASHER], } - const mockQueryResult: QueryResult<Ecogesture[]> = { - data: ecogesturesData, - bookmark: '', - next: false, - skip: 0, - } - mockClient.query.mockResolvedValueOnce(mockQueryResult) + + mockClient.query.mockResolvedValueOnce(mockQueryResultMockedEcogestures) const result = await ecogestureService.getEcogestureListByProfile( mockProfileEcogestureFull ) expect(result.length).toBe(2) - expect(result[0]).toBe(ecogesturesData[0]) + expect(result[0]).toBe(mockedEcogesturesData[0]) }) }) describe('getEcogesturesByIds', () => { @@ -243,4 +216,95 @@ describe('Ecogesture service', () => { expect(result).toBe(ecogesturesECSData) }) }) + + describe('initEcogesture()', () => { + const hash = hashFile(ecoData) + + beforeEach(() => { + mockClient.query.mockClear() + mockClient.create.mockClear() + }) + + it('should return hash when ecogestures hash is already up to date', async () => { + jest + .spyOn(ecogestureService, 'getAllEcogestures') + .mockResolvedValueOnce(ecoData) + const { ecogestureHash } = await ecogestureService.initEcogesture(hash) + expect(ecogestureHash).toEqual(hash) + }) + it('should match hash and ecogesture number when ecogestures are created', async () => { + jest + .spyOn(ecogestureService, 'getAllEcogestures') + .mockResolvedValueOnce([]) + .mockResolvedValueOnce(ecoData) + mockClient.create.mockResolvedValue(mockQueryResultCreated) + + const { ecogestureHash, ecogestureList } = + await ecogestureService.initEcogesture(hash) + expect(ecogestureHash).toEqual(hash) + expect(ecogestureList.length).toEqual(ecogestureData.length) + }) + it('should throw an error when ecogestures should be created and created ecogestures number does not match', async () => { + jest + .spyOn(ecogestureService, 'getAllEcogestures') + .mockResolvedValueOnce([]) + .mockResolvedValueOnce(mockedEcogesturesData) + + await expect( + ecogestureService.initEcogesture(hashFile(ecogestureData)) + ).rejects.toThrow( + new Error( + 'initEcogesture: Created ecogesture type entities does not match' + ) + ) + }) + it('should throw an error when ecogestures should be created and creation failed', async () => { + jest + .spyOn(ecogestureService, 'getAllEcogestures') + .mockResolvedValueOnce([]) + mockClient.create.mockRejectedValue(new Error()) + await expect( + ecogestureService.initEcogesture(hashFile(ecogestureData)) + ).rejects.toThrow(new Error()) + }) + it('should return hash and ecogesture list when ecogestures are created', async () => { + jest + .spyOn(ecogestureService, 'getAllEcogestures') + .mockResolvedValue(ecoData) + jest + .spyOn(ecogestureService, 'deleteAllEcogestures') + .mockResolvedValue(true) + mockClient.create.mockResolvedValue(mockQueryResultCreated) + + const { ecogestureHash, ecogestureList } = + await ecogestureService.initEcogesture('') + expect(ecogestureHash).toEqual(hash) + expect(ecogestureList.length).toEqual(ecogestureData.length) + }) + it('should throw an error when ecogestures should be updated and created ecogestures number does not match', async () => { + jest + .spyOn(ecogestureService, 'getAllEcogestures') + .mockResolvedValueOnce(ecoData) + .mockResolvedValueOnce(mockedEcogesturesData as Ecogesture[]) + jest + .spyOn(ecogestureService, 'deleteAllEcogestures') + .mockResolvedValue(true) + mockClient.create.mockResolvedValue(mockQueryResultCreated) + await expect(ecogestureService.initEcogesture('')).rejects.toThrow( + new Error( + 'initEcogesture: Created ecogesture type entities does not match' + ) + ) + }) + it('should throw an error when ecogestures should be updated and ecogestures creation failed', async () => { + jest + .spyOn(ecogestureService, 'getAllEcogestures') + .mockResolvedValue(ecoData) + jest + .spyOn(ecogestureService, 'deleteAllEcogestures') + .mockResolvedValue(true) + mockClient.create.mockRejectedValueOnce(new Error()) + expect(ecogestureService.initEcogesture('')).rejects.toThrow(new Error()) + }) + }) }) diff --git a/src/services/ecogesture.service.ts b/src/services/ecogesture.service.ts index 795bc79dd..b7e0ae588 100644 --- a/src/services/ecogesture.service.ts +++ b/src/services/ecogesture.service.ts @@ -1,6 +1,7 @@ import * as Sentry from '@sentry/react' import { Client, Q, QueryDefinition, QueryResult } from 'cozy-client' import logger from 'cozy-logger' +import ecogestureData from 'db/ecogestureData.json' import { ECOGESTURE_DOCTYPE } from 'doctypes' import { Season, Usage } from 'enum/ecogesture.enum' import { FluidType } from 'enum/fluid.enum' @@ -8,6 +9,8 @@ import { IndividualOrCollective, WarmingType } from 'enum/profileType.enum' import { orderBy } from 'lodash' import { Ecogesture } from 'models' import { ProfileEcogesture } from 'models/profileEcogesture.model' +import { logDuration } from 'utils/duration' +import { hashFile } from 'utils/hash' import logApp from 'utils/logger' const logStack = logger.namespace('ecogestureService') @@ -19,6 +22,97 @@ export default class EcogestureService { this._client = _client } + /** + * - Load ecogestures if they exists. + * - If not create them. + * - If hash mismatch, update ecogestures. + */ + public async initEcogesture(hash: string): Promise<{ + ecogestureHash: string + ecogestureList: Ecogesture[] + }> { + const startTime = performance.now() + const hashEcogestureType = hashFile(ecogestureData) + const ecogestures = await this.getAllEcogestures(undefined, true) + + if (!ecogestures || ecogestures?.length === 0) { + // Populate data if none ecogesture exists + try { + for (const ecogesture of ecogestureData) { + await this._client.create(ECOGESTURE_DOCTYPE, ecogesture) + } + // Check of created document based on count + const ecogestures = await this.getAllEcogestures() + if (!ecogestures || ecogestures?.length !== ecogestureData.length) { + throw new Error( + 'initEcogesture: Created ecogesture type entities does not match' + ) + } + logDuration('[Initialization] Ecogesture list created', startTime) + return { + ecogestureHash: hashEcogestureType, + ecogestureList: ecogestures, + } + } catch (error) { + const errorMessage = `Initialization error - initEcogesture: ${JSON.stringify( + error + )}` + logStack('error', errorMessage) + logApp.error(errorMessage) + Sentry.captureException(errorMessage) + throw error + } + } + // Update if the hash is not the same as the one from profile + if (hash !== hashEcogestureType) { + // Update the doctype + try { + // Deletion of all documents + await this.deleteAllEcogestures() + // Population with the data + for (const [index, ecogesture] of ecogestureData.entries()) { + const updateEcogesture = ecogestures[index] + ? { + ...ecogesture, + objective: ecogestures[index].objective, + doing: ecogestures[index].doing, + viewedInSelection: ecogestures[index].viewedInSelection, + } + : ecogesture + await this._client.create(ECOGESTURE_DOCTYPE, updateEcogesture) + } + // Check of created document based on count + const checkCount = await this.getAllEcogestures() + if (!checkCount || checkCount?.length !== ecogestureData.length) { + throw new Error( + 'initEcogesture: Created ecogesture type entities does not match' + ) + } + logDuration('[Initialization] Ecogesture updated', startTime) + return { + ecogestureHash: hashEcogestureType, + ecogestureList: checkCount, + } + } catch (error) { + const errorMessage = `Initialization error - initEcogesture: ${JSON.stringify( + error + )}` + logStack('error', errorMessage) + logApp.error(errorMessage) + Sentry.captureException(errorMessage) + throw error + } + } else { + // Doctype already up to date + logDuration('[Initialization] Ecogesture already up-to-date', startTime) + return { + ecogestureHash: hashEcogestureType, + ecogestureList: ecogestures, + } + } + } + + // TODO add default params public async getAllEcogestures( seasonFilter?: Season, orderByID?: boolean @@ -48,6 +142,7 @@ export default class EcogestureService { } return ecogestures } + /** * @param {string} ids - ecogestures ids * @returns {Promise<Ecogesture[]>} @@ -208,7 +303,7 @@ export default class EcogestureService { /** * Update one ecogesture * @param {Ecogesture} ecogesture - Ecogesture to save - * @returns {Ecogesture} Udpated Ecogesture + * @returns {Ecogesture} Updated Ecogesture */ public async updateEcogesture(ecogesture: Ecogesture): Promise<Ecogesture> { const { data: updatedEcogesture }: QueryResult<Ecogesture> = diff --git a/src/services/initialization.service.spec.ts b/src/services/initialization.service.spec.ts index 3a140471d..e6fdcf338 100644 --- a/src/services/initialization.service.spec.ts +++ b/src/services/initialization.service.spec.ts @@ -1,7 +1,6 @@ import { QueryResult } from 'cozy-client' import challengeEntityData from 'db/challengeEntity.json' import duelEntityData from 'db/duelEntity.json' -import ecogestureData from 'db/ecogestureData.json' import explorationEntityData from 'db/explorationEntity.json' import quizEntityData from 'db/quizEntity.json' import { FluidType } from 'enum/fluid.enum' @@ -13,7 +12,6 @@ import { allChallengeEntityData } from '../../tests/__mocks__/challengeEntity.mo import { graphData } from '../../tests/__mocks__/chartData.mock' import mockClient from '../../tests/__mocks__/client' import { allDuelEntity } from '../../tests/__mocks__/duelData.mock' -import { ecogesturesData } from '../../tests/__mocks__/ecogesturesData.mock' import { allExplorationEntities } from '../../tests/__mocks__/explorationData.mock' import { fluidPrices } from '../../tests/__mocks__/fluidPrice.mock' import { fluidStatusData } from '../../tests/__mocks__/fluidStatusData.mock' @@ -55,17 +53,6 @@ jest.mock('./profile.service', () => { }) }) -const mockGetAllEcogestures = jest.fn() -const mockDeleteAllEcogestures = jest.fn() -jest.mock('./ecogesture.service', () => { - return jest.fn(() => { - return { - getAllEcogestures: mockGetAllEcogestures, - deleteAllEcogestures: mockDeleteAllEcogestures, - } - }) -}) - const mockGetAllChallengeEntities = jest.fn() const mockDeleteAllChallengeEntities = jest.fn() const mockBuildUserChallengeList = jest.fn() @@ -223,106 +210,6 @@ describe('Initialization service', () => { }) }) - describe('initEcoGesture method', () => { - beforeEach(() => { - mockGetAllEcogestures.mockClear() - mockDeleteAllEcogestures.mockClear() - }) - it('should return hash when ecogestures hash is already up to date', async () => { - mockGetAllEcogestures.mockResolvedValueOnce(ecogestureData) - const hash = hashFile(ecogestureData) - await expect(initializationService.initEcogesture(hash)).resolves.toEqual( - hash - ) - }) - it('should return hash when ecogestures are created', async () => { - mockGetAllEcogestures - .mockResolvedValueOnce(null) - .mockResolvedValueOnce(ecogestureData) - const mockQueryResult: QueryResult<boolean> = { - data: true, - bookmark: '', - next: false, - skip: 0, - } - mockClient.create.mockResolvedValue(mockQueryResult) - const hash = hashFile(ecogestureData) - await expect(initializationService.initEcogesture(hash)).resolves.toEqual( - hash - ) - }) - it('should throw an error when ecogestures should be created and created ecogestures number does not match', async () => { - mockGetAllEcogestures - .mockResolvedValueOnce(null) - .mockResolvedValueOnce(ecogesturesData) - const mockQueryResult: QueryResult<boolean> = { - data: true, - bookmark: '', - next: false, - skip: 0, - } - mockClient.create.mockResolvedValue(mockQueryResult) - await expect( - initializationService.initEcogesture(hashFile(ecogestureData)) - ).rejects.toThrow( - new Error( - 'initEcogesture: Created ecogesture type entities does not match' - ) - ) - }) - it('should throw an error when ecogestures should be created and creation failed', async () => { - mockGetAllEcogestures.mockResolvedValueOnce(null) - mockClient.create.mockRejectedValue(new Error()) - await expect( - initializationService.initEcogesture(hashFile(ecogestureData)) - ).rejects.toThrow(new Error()) - }) - it('should return hash when ecogestures are updated', async () => { - mockGetAllEcogestures - .mockResolvedValueOnce(ecogestureData) - .mockResolvedValueOnce(ecogestureData) - mockDeleteAllEcogestures.mockResolvedValue(true) - const mockQueryResult: QueryResult<boolean> = { - data: true, - bookmark: '', - next: false, - skip: 0, - } - mockClient.create.mockResolvedValue(mockQueryResult) - await expect(initializationService.initEcogesture('')).resolves.toEqual( - hashFile(ecogestureData) - ) - }) - it('should throw an error when ecogestures should be updated and created ecogestures number does not match', async () => { - mockGetAllEcogestures - .mockResolvedValueOnce(ecogestureData) - .mockResolvedValueOnce(ecogesturesData) - mockDeleteAllEcogestures.mockResolvedValue(true) - const mockQueryResult: QueryResult<boolean> = { - data: true, - bookmark: '', - next: false, - skip: 0, - } - mockClient.create.mockResolvedValue(mockQueryResult) - await expect(initializationService.initEcogesture('')).rejects.toThrow( - new Error( - 'initEcogesture: Created ecogesture type entities does not match' - ) - ) - }) - it('should throw an error when ecogestures should be updated and ecogestures creation failed', async () => { - mockGetAllEcogestures - .mockResolvedValueOnce(ecogestureData) - .mockResolvedValueOnce(ecogestureData) - mockDeleteAllEcogestures.mockResolvedValue(true) - mockClient.create.mockRejectedValueOnce(new Error()) - expect(initializationService.initEcogesture('')).rejects.toThrow( - new Error() - ) - }) - }) - describe('initFluidPrices method', () => { beforeEach(() => { mockGetAllPrices.mockClear() diff --git a/src/services/initialization.service.ts b/src/services/initialization.service.ts index 13ee9ccb0..9a4634559 100644 --- a/src/services/initialization.service.ts +++ b/src/services/initialization.service.ts @@ -3,14 +3,12 @@ import { Client } from 'cozy-client' import logger from 'cozy-logger' import challengeEntityData from 'db/challengeEntity.json' import duelEntityData from 'db/duelEntity.json' -import ecogestureData from 'db/ecogestureData.json' import explorationEntityData from 'db/explorationEntity.json' import profileData from 'db/profileData' import quizEntityData from 'db/quizEntity.json' import { CHALLENGE_DOCTYPE, DUEL_DOCTYPE, - ECOGESTURE_DOCTYPE, EXPLORATION_DOCTYPE, PROFILE_DOCTYPE, QUIZ_DOCTYPE, @@ -37,7 +35,6 @@ import { ProfileEcogesture } from 'models/profileEcogesture.model' import React from 'react' import ChallengeService from 'services/challenge.service' import DuelService from 'services/duel.service' -import EcogestureService from 'services/ecogesture.service' import ExplorationService from 'services/exploration.service' import FluidService from 'services/fluid.service' import KonnectorStatusService from 'services/konnectorStatus.service' @@ -165,90 +162,6 @@ export default class InitializationService { } } - public async initEcogesture(hash: string): Promise<string> { - const startTime = performance.now() - this._setInitStep(InitSteps.ECOGESTURE) - const hashEcogestureType = hashFile(ecogestureData) - const ecogestureService = new EcogestureService(this._client) - // Populate data if none ecogesture exists - const loadedEcogestures = await ecogestureService.getAllEcogestures( - undefined, - true - ) - if (!loadedEcogestures || loadedEcogestures?.length === 0) { - // Populate the doctype with data - try { - for (const ecogesture of ecogestureData) { - await this._client.create(ECOGESTURE_DOCTYPE, ecogesture) - } - // Check of created document based on count - const checkCount = await ecogestureService.getAllEcogestures() - if (!checkCount || checkCount?.length !== ecogestureData.length) { - this._setInitStepError(InitStepsErrors.ECOGESTURE_ERROR) - throw new Error( - 'initEcogesture: Created ecogesture type entities does not match' - ) - } - logDuration('[Initialization] Ecogesture list created', startTime) - return hashEcogestureType - } catch (error) { - this._setInitStepError(InitStepsErrors.ECOGESTURE_ERROR) - const errorMessage = `Initialization error - initEcogesture: ${JSON.stringify( - error - )}` - logStack('error', errorMessage) - logApp.error(errorMessage) - Sentry.captureException(errorMessage) - throw error - } - } - // Update if the hash is not the same as the one from profile - if (hash !== hashEcogestureType) { - // Update the doctype - try { - // Deletion of all documents - await ecogestureService.deleteAllEcogestures() - // Population with the data - for (const [index, ecogesture] of ecogestureData.entries()) { - const updateEcogesture = loadedEcogestures[index] - ? { - ...ecogesture, - objective: loadedEcogestures[index].objective ? true : false, - doing: loadedEcogestures[index].doing ? true : false, - viewedInSelection: loadedEcogestures[index].viewedInSelection - ? true - : false, - } - : ecogesture - await this._client.create(ECOGESTURE_DOCTYPE, updateEcogesture) - } - // Check of created document based on count - const checkCount = await ecogestureService.getAllEcogestures() - if (!checkCount || checkCount?.length !== ecogestureData.length) { - this._setInitStepError(InitStepsErrors.ECOGESTURE_ERROR) - throw new Error( - 'initEcogesture: Created ecogesture type entities does not match' - ) - } - logDuration('[Initialization] Ecogesture updated', startTime) - return hashEcogestureType - } catch (error) { - this._setInitStepError(InitStepsErrors.ECOGESTURE_ERROR) - const errorMessage = `Initialization error - initEcogesture: ${JSON.stringify( - error - )}` - logStack('error', errorMessage) - logApp.error(errorMessage) - Sentry.captureException(errorMessage) - throw error - } - } else { - // Doctype already up to date - logDuration('[Initialization] Ecogesture already up-to-date', startTime) - return hashEcogestureType - } - } - public async initFluidPrices(): Promise<boolean> { const startTime = performance.now() const fpService = new FluidPricesService(this._client) diff --git a/src/utils/hash.spec.ts b/src/utils/hash.spec.ts index de6088124..bc97808de 100644 --- a/src/utils/hash.spec.ts +++ b/src/utils/hash.spec.ts @@ -1,10 +1,10 @@ -import { ecogesturesData } from '../../tests/__mocks__/ecogesturesData.mock' +import { mockedEcogesturesData } from '../../tests/__mocks__/ecogesturesData.mock' import { hashFile } from './hash' -describe('hash utilis test', () => { +describe('hash utils test', () => { describe('hashFile test', () => { it('should return the correct hash of the file', () => { - const result = hashFile(ecogesturesData) + const result = hashFile(mockedEcogesturesData) expect(result).toBe('21c72fc0b67b0393ee457a25956703ef17b5b724') }) }) diff --git a/tests/__mocks__/ecogesturesData.mock.ts b/tests/__mocks__/ecogesturesData.mock.ts index 3e7b7d7ad..827eb511a 100644 --- a/tests/__mocks__/ecogesturesData.mock.ts +++ b/tests/__mocks__/ecogesturesData.mock.ts @@ -2,7 +2,7 @@ import { EquipmentType, Room, Season, Usage } from 'enum/ecogesture.enum' import { FluidType } from 'enum/fluid.enum' import { Ecogesture } from 'models' -export const ecogesturesData: Ecogesture[] = [ +export const mockedEcogesturesData: Ecogesture[] = [ { fluidTypes: [FluidType.ELECTRICITY, FluidType.GAS], id: 'ECOGESTURE001', -- GitLab