From 79fa859805108f96848c71c0afb97402abe31c46 Mon Sep 17 00:00:00 2001
From: Bastien DUMONT <bdumont@grandlyon.com>
Date: Thu, 23 Jun 2022 07:02:38 +0000
Subject: [PATCH] feat(matomo): tacking + opt out in settings

---
 app.config.environment.alpha.js               |    2 +
 app.config.environment.dev.js                 |    3 +
 app.config.environment.prod.js                |    2 +
 docker/docker-compose.matomo.yml              |   38 +
 jest.config.js                                |    1 +
 src/components/Action/ActionView.spec.tsx     |   15 +-
 src/components/Analysis/AnalysisView.spec.tsx |    1 -
 src/components/App.tsx                        |   23 +-
 .../Challenge/ChallengeView.spec.tsx          |   29 +-
 .../__snapshots__/ChallengeView.spec.tsx.snap | 2131 ++++++++++-------
 src/components/Content/Content.tsx            |   10 +-
 src/components/Duel/DuelView.spec.tsx         |    4 +
 .../Ecogesture/EcogestureView.spec.tsx        |    1 -
 .../Exploration/ExplorationView.spec.tsx      |   52 +-
 src/components/FAQ/FAQView.spec.tsx           |    3 +
 .../FAQ/__snapshots__/FAQView.spec.tsx.snap   |   40 +-
 src/components/GCU/GCUView.spec.tsx           |    4 +
 .../GCU/__snapshots__/GCUView.spec.tsx.snap   |    8 +-
 src/components/Home/ConsumptionView.spec.tsx  |   12 +-
 .../LegalNotice/LegalNoticeView.spec.tsx      |    4 +
 .../LegalNoticeView.spec.tsx.snap             |    8 +-
 src/components/Options/MatomoOptOut.spec.tsx  |   20 +
 src/components/Options/MatomoOptOut.tsx       |   24 +
 src/components/Options/OptionsView.spec.tsx   |    5 +-
 src/components/Options/OptionsView.tsx        |    2 +
 src/components/Options/UnSubscribe.spec.tsx   |    9 +-
 .../__snapshots__/MatomoOptOut.spec.tsx.snap  |   25 +
 .../__snapshots__/OptionsView.spec.tsx.snap   |    9 +-
 .../__snapshots__/UnSubscribe.spec.tsx.snap   |  198 +-
 src/components/Options/matomoOptOut.scss      |   27 +
 .../ProfileType/ProfileTypeView.spec.tsx      |   14 +-
 .../ProfileType/ProfileTypeView.tsx           |    2 +-
 src/components/Quiz/QuizView.spec.tsx         |   11 +-
 src/locales/fr.json                           |    3 +
 src/services/environment.service.ts           |    5 +
 src/targets/browser/index.ejs                 |   12 +-
 src/targets/browser/index.tsx                 |   75 +-
 src/targets/vendor/assets/serviceWorker.js    |    4 +
 src/types/cozy-ui.d.ts                        |    1 +
 src/types/custom.d.ts                         |    2 +
 src/utils/bar.ts                              |   59 -
 src/utils/client.ts                           |   25 -
 src/utils/matomoTracker.ts                    |  109 +
 43 files changed, 1929 insertions(+), 1103 deletions(-)
 create mode 100644 docker/docker-compose.matomo.yml
 create mode 100644 src/components/Options/MatomoOptOut.spec.tsx
 create mode 100644 src/components/Options/MatomoOptOut.tsx
 create mode 100644 src/components/Options/__snapshots__/MatomoOptOut.spec.tsx.snap
 create mode 100644 src/components/Options/matomoOptOut.scss
 delete mode 100644 src/utils/bar.ts
 delete mode 100644 src/utils/client.ts
 create mode 100644 src/utils/matomoTracker.ts

diff --git a/app.config.environment.alpha.js b/app.config.environment.alpha.js
index 65e849511..e0c4b0b3e 100644
--- a/app.config.environment.alpha.js
+++ b/app.config.environment.alpha.js
@@ -17,6 +17,8 @@ module.exports = {
       __DEVELOPMENT__: false,
       __DEVTOOLS__: false,
       __STACK_ASSETS__: target !== 'mobile',
+      __PIWIK_TRACKER_URL__: JSON.stringify('https://statweb.grandlyon.com/'),
+      __PIWIK_SITEID__: 117,
     }),
   ],
   optimization: {
diff --git a/app.config.environment.dev.js b/app.config.environment.dev.js
index 86282e3bc..31cbe4a8c 100644
--- a/app.config.environment.dev.js
+++ b/app.config.environment.dev.js
@@ -19,7 +19,10 @@ const stackProvidedLibsConfig = {
     new webpack.DefinePlugin({
       'process.env.NODE_ENV': JSON.stringify('development'),
       __IS_ALPHA__: true,
+      __DEVELOPMENT__: true,
       __STACK_ASSETS__: true,
+      __PIWIK_TRACKER_URL__: JSON.stringify('http://localhost:9800/'),
+      __PIWIK_SITEID__: 1,
     }),
   ],
   module: {
diff --git a/app.config.environment.prod.js b/app.config.environment.prod.js
index 9e5401b8d..7dc15dbcf 100644
--- a/app.config.environment.prod.js
+++ b/app.config.environment.prod.js
@@ -17,6 +17,8 @@ module.exports = {
       __DEVELOPMENT__: false,
       __DEVTOOLS__: false,
       __STACK_ASSETS__: target !== 'mobile',
+      __PIWIK_TRACKER_URL__: JSON.stringify('https://statweb.grandlyon.com/'),
+      __PIWIK_SITEID__: 118,
     }),
   ],
   optimization: {
diff --git a/docker/docker-compose.matomo.yml b/docker/docker-compose.matomo.yml
new file mode 100644
index 000000000..a0a90254e
--- /dev/null
+++ b/docker/docker-compose.matomo.yml
@@ -0,0 +1,38 @@
+version: '3.8'
+
+services:
+  db:
+    image: mariadb
+    command: --max-allowed-packet=64MB
+    restart: always
+    environment:
+      - MYSQL_ROOT_PASSWORD=root
+      - MYSQL_PASSWORD=password
+      - MYSQL_DATABASE=matomo
+      - MYSQL_USER=matomo
+    volumes:
+      - db:/var/lib/mysql
+
+  app:
+    image: matomo:latest
+    restart: always
+    depends_on:
+      - db
+    environment:
+      - MATOMO_DATABASE_HOST=db
+      - MATOMO_DATABASE_ADAPTER=mysql
+      - MATOMO_DATABASE_TABLES_PREFIX=matomo_
+      - MATOMO_DATABASE_USERNAME=matomo
+      - MATOMO_DATABASE_PASSWORD=password
+      - MATOMO_DATABASE_DBNAME=matomo
+      - PHP_MEMORY_LIMIT=2048M
+    volumes:
+      # - ./config:/var/www/html/config:rw
+      # - ./logs:/var/www/html/logs
+      - matomo:/var/www/html
+    ports:
+      - 9800:80
+
+volumes:
+  db:
+  matomo:
diff --git a/jest.config.js b/jest.config.js
index 6cf339521..b6a027a5b 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -18,6 +18,7 @@ module.exports = {
   globals: {
     __ALLOW_HTTP__: false,
     __TARGET__: 'browser',
+    __PIWIK_TRACKER_URL__: 'http://localhost:9800/',
     cozy: {},
   },
 }
diff --git a/src/components/Action/ActionView.spec.tsx b/src/components/Action/ActionView.spec.tsx
index 8442277f4..736802cd2 100644
--- a/src/components/Action/ActionView.spec.tsx
+++ b/src/components/Action/ActionView.spec.tsx
@@ -21,6 +21,9 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => {
     }),
   }
 })
+jest.mock('components/Header/CozyBar', () => 'mock-cozybar')
+jest.mock('components/Header/Header', () => 'mock-header')
+jest.mock('components/Content/Content', () => 'mock-content')
 
 const mockStore = configureStore([])
 describe('ActionView component', () => {
@@ -34,7 +37,7 @@ describe('ActionView component', () => {
     }
     const store = mockStore({
       ecolyo: {
-        challenge: userChallenge,
+        challenge: { currentChallenge: userChallenge },
         global: { ...globalStateData, fluidTypes: [0, 1, 2] },
         profile: profileData,
         modal: modalStateData,
@@ -45,7 +48,7 @@ describe('ActionView component', () => {
         <ActionView />
       </Provider>
     )
-    expect(wrapper.find(ActionChoose).exists())
+    expect(wrapper.find(ActionChoose).exists()).toBeTruthy()
     expect(wrapper).toMatchSnapshot()
   })
   it('should render ActionDone component', () => {
@@ -58,7 +61,7 @@ describe('ActionView component', () => {
     }
     const store = mockStore({
       ecolyo: {
-        challenge: userChallenge,
+        challenge: { currentChallenge: userChallenge },
         global: { ...globalStateData, fluidTypes: [0, 1, 2] },
         profile: profileData,
         modal: modalStateData,
@@ -69,7 +72,7 @@ describe('ActionView component', () => {
         <ActionView />
       </Provider>
     )
-    expect(wrapper.find(ActionDone).exists())
+    expect(wrapper.find(ActionDone).exists()).toBeTruthy()
   })
   it('should render ActionOnGoing component', () => {
     const userChallenge = {
@@ -81,7 +84,7 @@ describe('ActionView component', () => {
     }
     const store = mockStore({
       ecolyo: {
-        challenge: userChallenge,
+        challenge: { currentChallenge: userChallenge },
         global: { ...globalStateData, fluidTypes: [0, 1, 2] },
         modal: modalStateData,
         profile: profileData,
@@ -92,6 +95,6 @@ describe('ActionView component', () => {
         <ActionView />
       </Provider>
     )
-    expect(wrapper.find(ActionOnGoing).exists())
+    expect(wrapper.find(ActionOnGoing).exists()).toBeTruthy()
   })
 })
diff --git a/src/components/Analysis/AnalysisView.spec.tsx b/src/components/Analysis/AnalysisView.spec.tsx
index 286f6cf6b..e3c5062d5 100644
--- a/src/components/Analysis/AnalysisView.spec.tsx
+++ b/src/components/Analysis/AnalysisView.spec.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/display-name */
 import React from 'react'
 import { mount } from 'enzyme'
 import * as reactRedux from 'react-redux'
diff --git a/src/components/App.tsx b/src/components/App.tsx
index 86d54f5d4..3d4fedc31 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -1,7 +1,6 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
-import React from 'react'
+import React, { useEffect } from 'react'
 import { HashRouter } from 'react-router-dom'
-import { createBrowserHistory } from 'history'
 import { Layout, Main, Content } from 'cozy-ui/transpiled/react/Layout'
 import { useSelector } from 'react-redux'
 import { AppStore } from 'store'
@@ -12,14 +11,30 @@ import SplashRoot from 'components/Splash/SplashRoot'
 import SplashScreen from 'components/Splash/SplashScreen'
 import SplashScreenError from 'components/Splash/SplashScreenError'
 import WelcomeModal from 'components/Onboarding/WelcomeModal'
+import MatomoTracker from 'utils/matomoTracker'
+import EnvironmentService from 'services/environment.service'
 
-export const history = createBrowserHistory()
+interface AppProps {
+  tracker: MatomoTracker
+}
 
-export const App = () => {
+export const App = ({ tracker }: AppProps) => {
   const { onboarding, isProfileEcogestureCompleted } = useSelector(
     (state: AppStore) => state.ecolyo.profile
   )
   const { termsStatus } = useSelector((state: AppStore) => state.ecolyo.global)
+  const isDev = new EnvironmentService().isLocal()
+
+  useEffect(() => {
+    if (tracker && !isDev) {
+      if (termsStatus.accepted) {
+        tracker.connectToHistory()
+      }
+      return () => {
+        tracker.disconnectFromHistory()
+      }
+    }
+  }, [termsStatus.accepted, tracker])
 
   return (
     <HashRouter {...history}>
diff --git a/src/components/Challenge/ChallengeView.spec.tsx b/src/components/Challenge/ChallengeView.spec.tsx
index aff70fdae..1e33a6132 100644
--- a/src/components/Challenge/ChallengeView.spec.tsx
+++ b/src/components/Challenge/ChallengeView.spec.tsx
@@ -1,8 +1,13 @@
 import React from 'react'
-import { shallow } from 'enzyme'
+import { mount } from 'enzyme'
+import toJson from 'enzyme-to-json'
 import ChallengeView from 'components/Challenge/ChallengeView'
 import * as reactRedux from 'react-redux'
 import { challengeStateDataFull } from '../../../tests/__mocks__/challengeStateData.mock'
+import {
+  createMockStore,
+  mockInitialEcolyoState,
+} from '../../../tests/__mocks__/store'
 
 const mockUseSelector = jest.spyOn(reactRedux, 'useSelector')
 
@@ -16,10 +21,28 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => {
   }
 })
 
+jest.mock('components/Header/CozyBar', () => 'mock-cozybar')
+jest.mock('components/Header/Header', () => 'mock-header')
+jest.mock('components/Content/Content', () => 'mock-content')
+jest.mock('components/Challenge/ChallengeCard', () => 'mock-challengecard')
+
+const useSelectorSpy = jest.spyOn(reactRedux, 'useSelector')
+
 describe('ChallengeView component', () => {
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  let store: any
+  beforeEach(() => {
+    store = createMockStore(mockInitialEcolyoState)
+    useSelectorSpy.mockClear()
+  })
+
   it('should be rendered correctly', () => {
     mockUseSelector.mockReturnValue(challengeStateDataFull)
-    const component = shallow(<ChallengeView />).getElement()
-    expect(component).toMatchSnapshot()
+    const wrapper = mount(
+      <reactRedux.Provider store={store}>
+        <ChallengeView />
+      </reactRedux.Provider>
+    )
+    expect(toJson(wrapper)).toMatchSnapshot()
   })
 })
diff --git a/src/components/Challenge/__snapshots__/ChallengeView.spec.tsx.snap b/src/components/Challenge/__snapshots__/ChallengeView.spec.tsx.snap
index ee1c09130..3d17797f4 100644
--- a/src/components/Challenge/__snapshots__/ChallengeView.spec.tsx.snap
+++ b/src/components/Challenge/__snapshots__/ChallengeView.spec.tsx.snap
@@ -1,953 +1,1260 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`ChallengeView component should be rendered correctly 1`] = `
-<React.Fragment>
-  <CozyBar
-    titleKey="common.title_challenge"
-  />
-  <Header
-    desktopTitleKey="common.title_challenge"
-    setHeaderHeight={[Function]}
-  />
-  <Content
-    height={0}
-  >
-    <div
-      className="challengeSlider"
-      onClick={[Function]}
-      onMouseDown={[Function]}
-      onMouseMove={[Function]}
-      onMouseUp={[Function]}
-      onTouchEnd={[Function]}
-      onTouchMove={[Function]}
-      onTouchStart={[Function]}
+<Provider
+  store={
+    Object {
+      "clearActions": [Function],
+      "dispatch": [Function],
+      "getActions": [Function],
+      "getState": [Function],
+      "replaceReducer": [Function],
+      "subscribe": [Function],
+    }
+  }
+>
+  <ChallengeView>
+    <mock-cozybar
+      titleKey="common.title_challenge"
+    />
+    <mock-header
+      desktopTitleKey="common.title_challenge"
+      setHeaderHeight={[Function]}
+    />
+    <mock-content
+      height={0}
     >
       <div
-        className="challenge-container"
-        style={
-          Object {
-            "transform": "translateX(16px)",
-          }
-        }
+        className="challengeSlider"
+        onClick={[Function]}
+        onMouseDown={[Function]}
+        onMouseMove={[Function]}
+        onMouseUp={[Function]}
+        onTouchEnd={[Function]}
+        onTouchMove={[Function]}
+        onTouchStart={[Function]}
       >
-        <ChallengeCard
-          cardHeight={384}
-          cardWidth={285}
-          index={0}
-          indexSlider={0}
-          moveToSlide={[Function]}
-          userChallenge={
+        <div
+          className="challenge-container"
+          style={
             Object {
-              "action": Object {
-                "ecogesture": null,
-                "startDate": null,
-                "state": 0,
-              },
-              "description": "Description challenge 1",
-              "duel": Object {
-                "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine",
-                "duration": "P30D",
-                "fluidTypes": Array [],
-                "id": "DUEL001",
-                "startDate": null,
-                "state": 0,
-                "threshold": 0,
-                "title": "Title DUEL001",
-                "userConsumption": 0,
-              },
-              "endingDate": null,
-              "exploration": Object {
-                "complementary_description": "Refaire un tour dans son profil si déjà fait",
-                "date": null,
-                "description": "Avoir complété son profil",
-                "ecogesture_id": "",
-                "fluid_condition": Array [],
-                "id": "EXPLORATION001",
-                "message_success": "Vous avez complété votre profil ou refait un tour dans votre profil",
-                "progress": 0,
-                "state": 0,
-                "target": 1,
-                "type": 1,
-              },
-              "id": "CHALLENGE0001",
-              "progress": Object {
-                "actionProgress": 0,
-                "explorationProgress": 0,
-                "quizProgress": 0,
-              },
-              "quiz": Object {
-                "customQuestion": Object {
-                  "interval": 20,
-                  "period": Object {},
-                  "questionLabel": "Custom1",
-                  "result": 0,
-                  "singleFluid": false,
-                  "timeStep": 20,
-                  "type": 0,
+              "transform": "translateX(-874.2px)",
+            }
+          }
+        >
+          <mock-challengecard
+            cardHeight={384}
+            cardWidth={285}
+            index={0}
+            indexSlider={3}
+            key="0"
+            moveToSlide={[Function]}
+            userChallenge={
+              Object {
+                "action": Object {
+                  "ecogesture": null,
+                  "startDate": null,
+                  "state": 0,
                 },
-                "id": "QUIZ001",
-                "questions": Array [
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "86 km",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "78 km",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "56 km",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "L’aqueduc du Gier est un des aqueducs antiques de Lyon desservant la ville antique de Lugdunum. Avec ses 86 km il est le plus long des quatre aqueducs ayant alimenté la ville en eau, et celui dont les structures sont le mieux conservées. Il doit son nom au fait qu'il puise aux sources du Gier, affluent du Rhône",
-                    "questionLabel": "Quelle longueur faisait l’aqueduc du Gier pour acheminer l’eau sur Lyon à l’époque romaine ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 800 habitants.",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 400 habitants.",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 200 habitants.",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "En 1800 à Lyon, combien de points d'eau y avait-il par habitants ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "François Mitterrand",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "Napoléon Ier",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "Napoléon III",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "Qui officialise la création de la Compagnie Générale des eaux ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "string",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "string",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "Aristide Dumont",
-                        "isTrue": true,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "Quel ingénieur est à l’origine du projet d’alimentation en eau en 1856 ?",
+                "description": "Description challenge 1",
+                "duel": Object {
+                  "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine",
+                  "duration": "P30D",
+                  "fluidTypes": Array [],
+                  "id": "DUEL001",
+                  "startDate": null,
+                  "state": 0,
+                  "threshold": 0,
+                  "title": "Title DUEL001",
+                  "userConsumption": 0,
+                },
+                "endingDate": null,
+                "exploration": Object {
+                  "complementary_description": "Refaire un tour dans son profil si déjà fait",
+                  "date": null,
+                  "description": "Avoir complété son profil",
+                  "ecogesture_id": "",
+                  "fluid_condition": Array [],
+                  "id": "EXPLORATION001",
+                  "message_success": "Vous avez complété votre profil ou refait un tour dans votre profil",
+                  "progress": 0,
+                  "state": 0,
+                  "target": 1,
+                  "type": 1,
+                },
+                "id": "CHALLENGE0001",
+                "progress": Object {
+                  "actionProgress": 0,
+                  "explorationProgress": 0,
+                  "quizProgress": 0,
+                },
+                "quiz": Object {
+                  "customQuestion": Object {
+                    "interval": 20,
+                    "period": Object {},
+                    "questionLabel": "Custom1",
                     "result": 0,
-                    "source": "string",
+                    "singleFluid": false,
+                    "timeStep": 20,
+                    "type": 0,
                   },
-                ],
-                "result": 0,
+                  "id": "QUIZ001",
+                  "questions": Array [
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "86 km",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "78 km",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "56 km",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "L’aqueduc du Gier est un des aqueducs antiques de Lyon desservant la ville antique de Lugdunum. Avec ses 86 km il est le plus long des quatre aqueducs ayant alimenté la ville en eau, et celui dont les structures sont le mieux conservées. Il doit son nom au fait qu'il puise aux sources du Gier, affluent du Rhône",
+                      "questionLabel": "Quelle longueur faisait l’aqueduc du Gier pour acheminer l’eau sur Lyon à l’époque romaine ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 800 habitants.",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 400 habitants.",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 200 habitants.",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "En 1800 à Lyon, combien de points d'eau y avait-il par habitants ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "François Mitterrand",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "Napoléon Ier",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "Napoléon III",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "Qui officialise la création de la Compagnie Générale des eaux ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "string",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "string",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "Aristide Dumont",
+                          "isTrue": true,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "Quel ingénieur est à l’origine du projet d’alimentation en eau en 1856 ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                  ],
+                  "result": 0,
+                  "startDate": null,
+                  "state": 0,
+                },
                 "startDate": null,
-                "state": 0,
-              },
-              "startDate": null,
-              "state": 4,
-              "success": 2,
-              "target": 15,
-              "title": "Challenge 1",
+                "state": 4,
+                "success": 2,
+                "target": 15,
+                "title": "Challenge 1",
+              }
             }
-          }
-        />
-        <ChallengeCard
-          cardHeight={384}
-          cardWidth={285}
-          index={1}
-          indexSlider={0}
-          moveToSlide={[Function]}
-          userChallenge={
-            Object {
-              "action": Object {
-                "ecogesture": null,
-                "startDate": null,
-                "state": 0,
-              },
-              "description": "Description challenge 2",
-              "duel": Object {
-                "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine",
-                "duration": "P30D",
-                "fluidTypes": Array [],
-                "id": "DUEL001",
-                "startDate": null,
-                "state": 0,
-                "threshold": 0,
-                "title": "Title DUEL001",
-                "userConsumption": 0,
-              },
-              "endingDate": null,
-              "exploration": Object {
-                "complementary_description": "Refaire un tour dans son profil si déjà fait",
-                "date": null,
-                "description": "Avoir complété son profil",
-                "ecogesture_id": "",
-                "fluid_condition": Array [],
-                "id": "EXPLORATION001",
-                "message_success": "Vous avez complété votre profil ou refait un tour dans votre profil",
-                "progress": 0,
-                "state": 0,
-                "target": 1,
-                "type": 1,
-              },
-              "id": "CHALLENGE0002",
-              "progress": Object {
-                "actionProgress": 0,
-                "explorationProgress": 0,
-                "quizProgress": 0,
-              },
-              "quiz": Object {
-                "customQuestion": Object {
-                  "interval": 20,
-                  "period": Object {},
-                  "questionLabel": "Custom1",
-                  "result": 0,
-                  "singleFluid": false,
-                  "timeStep": 20,
-                  "type": 0,
+          />
+          <mock-challengecard
+            cardHeight={384}
+            cardWidth={285}
+            index={1}
+            indexSlider={3}
+            key="1"
+            moveToSlide={[Function]}
+            userChallenge={
+              Object {
+                "action": Object {
+                  "ecogesture": null,
+                  "startDate": null,
+                  "state": 0,
                 },
-                "id": "QUIZ001",
-                "questions": Array [
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "86 km",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "78 km",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "56 km",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "L’aqueduc du Gier est un des aqueducs antiques de Lyon desservant la ville antique de Lugdunum. Avec ses 86 km il est le plus long des quatre aqueducs ayant alimenté la ville en eau, et celui dont les structures sont le mieux conservées. Il doit son nom au fait qu'il puise aux sources du Gier, affluent du Rhône",
-                    "questionLabel": "Quelle longueur faisait l’aqueduc du Gier pour acheminer l’eau sur Lyon à l’époque romaine ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 800 habitants.",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 400 habitants.",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 200 habitants.",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "En 1800 à Lyon, combien de points d'eau y avait-il par habitants ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "François Mitterrand",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "Napoléon Ier",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "Napoléon III",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "Qui officialise la création de la Compagnie Générale des eaux ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "string",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "string",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "Aristide Dumont",
-                        "isTrue": true,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "Quel ingénieur est à l’origine du projet d’alimentation en eau en 1856 ?",
+                "description": "Description challenge 2",
+                "duel": Object {
+                  "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine",
+                  "duration": "P30D",
+                  "fluidTypes": Array [],
+                  "id": "DUEL001",
+                  "startDate": null,
+                  "state": 0,
+                  "threshold": 0,
+                  "title": "Title DUEL001",
+                  "userConsumption": 0,
+                },
+                "endingDate": null,
+                "exploration": Object {
+                  "complementary_description": "Refaire un tour dans son profil si déjà fait",
+                  "date": null,
+                  "description": "Avoir complété son profil",
+                  "ecogesture_id": "",
+                  "fluid_condition": Array [],
+                  "id": "EXPLORATION001",
+                  "message_success": "Vous avez complété votre profil ou refait un tour dans votre profil",
+                  "progress": 0,
+                  "state": 0,
+                  "target": 1,
+                  "type": 1,
+                },
+                "id": "CHALLENGE0002",
+                "progress": Object {
+                  "actionProgress": 0,
+                  "explorationProgress": 0,
+                  "quizProgress": 0,
+                },
+                "quiz": Object {
+                  "customQuestion": Object {
+                    "interval": 20,
+                    "period": Object {},
+                    "questionLabel": "Custom1",
                     "result": 0,
-                    "source": "string",
+                    "singleFluid": false,
+                    "timeStep": 20,
+                    "type": 0,
                   },
-                ],
-                "result": 0,
+                  "id": "QUIZ001",
+                  "questions": Array [
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "86 km",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "78 km",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "56 km",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "L’aqueduc du Gier est un des aqueducs antiques de Lyon desservant la ville antique de Lugdunum. Avec ses 86 km il est le plus long des quatre aqueducs ayant alimenté la ville en eau, et celui dont les structures sont le mieux conservées. Il doit son nom au fait qu'il puise aux sources du Gier, affluent du Rhône",
+                      "questionLabel": "Quelle longueur faisait l’aqueduc du Gier pour acheminer l’eau sur Lyon à l’époque romaine ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 800 habitants.",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 400 habitants.",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 200 habitants.",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "En 1800 à Lyon, combien de points d'eau y avait-il par habitants ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "François Mitterrand",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "Napoléon Ier",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "Napoléon III",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "Qui officialise la création de la Compagnie Générale des eaux ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "string",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "string",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "Aristide Dumont",
+                          "isTrue": true,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "Quel ingénieur est à l’origine du projet d’alimentation en eau en 1856 ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                  ],
+                  "result": 0,
+                  "startDate": null,
+                  "state": 0,
+                },
                 "startDate": null,
-                "state": 0,
-              },
-              "startDate": null,
-              "state": 4,
-              "success": 1,
-              "target": 15,
-              "title": "Challenge 2",
+                "state": 4,
+                "success": 1,
+                "target": 15,
+                "title": "Challenge 2",
+              }
             }
-          }
-        />
-        <ChallengeCard
-          cardHeight={384}
-          cardWidth={285}
-          index={2}
-          indexSlider={0}
-          moveToSlide={[Function]}
-          userChallenge={
-            Object {
-              "action": Object {
-                "ecogesture": null,
-                "startDate": null,
-                "state": 0,
-              },
-              "description": "Description challenge 3",
-              "duel": Object {
-                "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine",
-                "duration": "P30D",
-                "fluidTypes": Array [],
-                "id": "DUEL001",
-                "startDate": null,
-                "state": 0,
-                "threshold": 0,
-                "title": "Title DUEL001",
-                "userConsumption": 0,
-              },
-              "endingDate": null,
-              "exploration": Object {
-                "complementary_description": "Refaire un tour dans son profil si déjà fait",
-                "date": null,
-                "description": "Avoir complété son profil",
-                "ecogesture_id": "",
-                "fluid_condition": Array [],
-                "id": "EXPLORATION001",
-                "message_success": "Vous avez complété votre profil ou refait un tour dans votre profil",
-                "progress": 0,
-                "state": 0,
-                "target": 1,
-                "type": 1,
-              },
-              "id": "CHALLENGE0003",
-              "progress": Object {
-                "actionProgress": 0,
-                "explorationProgress": 0,
-                "quizProgress": 0,
-              },
-              "quiz": Object {
-                "customQuestion": Object {
-                  "interval": 20,
-                  "period": Object {},
-                  "questionLabel": "Custom1",
-                  "result": 0,
-                  "singleFluid": false,
-                  "timeStep": 20,
-                  "type": 0,
+          />
+          <mock-challengecard
+            cardHeight={384}
+            cardWidth={285}
+            index={2}
+            indexSlider={3}
+            key="2"
+            moveToSlide={[Function]}
+            userChallenge={
+              Object {
+                "action": Object {
+                  "ecogesture": null,
+                  "startDate": null,
+                  "state": 0,
                 },
-                "id": "QUIZ001",
-                "questions": Array [
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "86 km",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "78 km",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "56 km",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "L’aqueduc du Gier est un des aqueducs antiques de Lyon desservant la ville antique de Lugdunum. Avec ses 86 km il est le plus long des quatre aqueducs ayant alimenté la ville en eau, et celui dont les structures sont le mieux conservées. Il doit son nom au fait qu'il puise aux sources du Gier, affluent du Rhône",
-                    "questionLabel": "Quelle longueur faisait l’aqueduc du Gier pour acheminer l’eau sur Lyon à l’époque romaine ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 800 habitants.",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 400 habitants.",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 200 habitants.",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "En 1800 à Lyon, combien de points d'eau y avait-il par habitants ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "François Mitterrand",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "Napoléon Ier",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "Napoléon III",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "Qui officialise la création de la Compagnie Générale des eaux ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "string",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "string",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "Aristide Dumont",
-                        "isTrue": true,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "Quel ingénieur est à l’origine du projet d’alimentation en eau en 1856 ?",
+                "description": "Description challenge 3",
+                "duel": Object {
+                  "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine",
+                  "duration": "P30D",
+                  "fluidTypes": Array [],
+                  "id": "DUEL001",
+                  "startDate": null,
+                  "state": 0,
+                  "threshold": 0,
+                  "title": "Title DUEL001",
+                  "userConsumption": 0,
+                },
+                "endingDate": null,
+                "exploration": Object {
+                  "complementary_description": "Refaire un tour dans son profil si déjà fait",
+                  "date": null,
+                  "description": "Avoir complété son profil",
+                  "ecogesture_id": "",
+                  "fluid_condition": Array [],
+                  "id": "EXPLORATION001",
+                  "message_success": "Vous avez complété votre profil ou refait un tour dans votre profil",
+                  "progress": 0,
+                  "state": 0,
+                  "target": 1,
+                  "type": 1,
+                },
+                "id": "CHALLENGE0003",
+                "progress": Object {
+                  "actionProgress": 0,
+                  "explorationProgress": 0,
+                  "quizProgress": 0,
+                },
+                "quiz": Object {
+                  "customQuestion": Object {
+                    "interval": 20,
+                    "period": Object {},
+                    "questionLabel": "Custom1",
                     "result": 0,
-                    "source": "string",
+                    "singleFluid": false,
+                    "timeStep": 20,
+                    "type": 0,
                   },
-                ],
-                "result": 0,
+                  "id": "QUIZ001",
+                  "questions": Array [
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "86 km",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "78 km",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "56 km",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "L’aqueduc du Gier est un des aqueducs antiques de Lyon desservant la ville antique de Lugdunum. Avec ses 86 km il est le plus long des quatre aqueducs ayant alimenté la ville en eau, et celui dont les structures sont le mieux conservées. Il doit son nom au fait qu'il puise aux sources du Gier, affluent du Rhône",
+                      "questionLabel": "Quelle longueur faisait l’aqueduc du Gier pour acheminer l’eau sur Lyon à l’époque romaine ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 800 habitants.",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 400 habitants.",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 200 habitants.",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "En 1800 à Lyon, combien de points d'eau y avait-il par habitants ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "François Mitterrand",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "Napoléon Ier",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "Napoléon III",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "Qui officialise la création de la Compagnie Générale des eaux ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "string",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "string",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "Aristide Dumont",
+                          "isTrue": true,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "Quel ingénieur est à l’origine du projet d’alimentation en eau en 1856 ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                  ],
+                  "result": 0,
+                  "startDate": null,
+                  "state": 0,
+                },
                 "startDate": null,
-                "state": 0,
-              },
-              "startDate": null,
-              "state": 2,
-              "success": 0,
-              "target": 15,
-              "title": "Challenge 3",
+                "state": 2,
+                "success": 0,
+                "target": 15,
+                "title": "Challenge 3",
+              }
             }
-          }
-        />
-        <ChallengeCard
-          cardHeight={384}
-          cardWidth={285}
-          index={3}
-          indexSlider={0}
-          moveToSlide={[Function]}
-          userChallenge={
-            Object {
-              "action": Object {
-                "ecogesture": null,
-                "startDate": null,
-                "state": 0,
-              },
-              "description": "Description challenge 4",
-              "duel": Object {
-                "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine",
-                "duration": "P30D",
-                "fluidTypes": Array [],
-                "id": "DUEL001",
-                "startDate": null,
-                "state": 0,
-                "threshold": 0,
-                "title": "Title DUEL001",
-                "userConsumption": 0,
-              },
-              "endingDate": null,
-              "exploration": Object {
-                "complementary_description": "Refaire un tour dans son profil si déjà fait",
-                "date": null,
-                "description": "Avoir complété son profil",
-                "ecogesture_id": "",
-                "fluid_condition": Array [],
-                "id": "EXPLORATION001",
-                "message_success": "Vous avez complété votre profil ou refait un tour dans votre profil",
-                "progress": 0,
-                "state": 0,
-                "target": 1,
-                "type": 1,
-              },
-              "id": "CHALLENGE0004",
-              "progress": Object {
-                "actionProgress": 0,
-                "explorationProgress": 0,
-                "quizProgress": 0,
-              },
-              "quiz": Object {
-                "customQuestion": Object {
-                  "interval": 20,
-                  "period": Object {},
-                  "questionLabel": "Custom1",
-                  "result": 0,
-                  "singleFluid": false,
-                  "timeStep": 20,
-                  "type": 0,
+          />
+          <mock-challengecard
+            cardHeight={384}
+            cardWidth={285}
+            index={3}
+            indexSlider={3}
+            key="3"
+            moveToSlide={[Function]}
+            userChallenge={
+              Object {
+                "action": Object {
+                  "ecogesture": null,
+                  "startDate": null,
+                  "state": 0,
                 },
-                "id": "QUIZ001",
-                "questions": Array [
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "86 km",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "78 km",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "56 km",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "L’aqueduc du Gier est un des aqueducs antiques de Lyon desservant la ville antique de Lugdunum. Avec ses 86 km il est le plus long des quatre aqueducs ayant alimenté la ville en eau, et celui dont les structures sont le mieux conservées. Il doit son nom au fait qu'il puise aux sources du Gier, affluent du Rhône",
-                    "questionLabel": "Quelle longueur faisait l’aqueduc du Gier pour acheminer l’eau sur Lyon à l’époque romaine ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 800 habitants.",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 400 habitants.",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 200 habitants.",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "En 1800 à Lyon, combien de points d'eau y avait-il par habitants ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "François Mitterrand",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "Napoléon Ier",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "Napoléon III",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "Qui officialise la création de la Compagnie Générale des eaux ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "string",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "string",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "Aristide Dumont",
-                        "isTrue": true,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "Quel ingénieur est à l’origine du projet d’alimentation en eau en 1856 ?",
+                "description": "Description challenge 4",
+                "duel": Object {
+                  "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine",
+                  "duration": "P30D",
+                  "fluidTypes": Array [],
+                  "id": "DUEL001",
+                  "startDate": null,
+                  "state": 0,
+                  "threshold": 0,
+                  "title": "Title DUEL001",
+                  "userConsumption": 0,
+                },
+                "endingDate": null,
+                "exploration": Object {
+                  "complementary_description": "Refaire un tour dans son profil si déjà fait",
+                  "date": null,
+                  "description": "Avoir complété son profil",
+                  "ecogesture_id": "",
+                  "fluid_condition": Array [],
+                  "id": "EXPLORATION001",
+                  "message_success": "Vous avez complété votre profil ou refait un tour dans votre profil",
+                  "progress": 0,
+                  "state": 0,
+                  "target": 1,
+                  "type": 1,
+                },
+                "id": "CHALLENGE0004",
+                "progress": Object {
+                  "actionProgress": 0,
+                  "explorationProgress": 0,
+                  "quizProgress": 0,
+                },
+                "quiz": Object {
+                  "customQuestion": Object {
+                    "interval": 20,
+                    "period": Object {},
+                    "questionLabel": "Custom1",
                     "result": 0,
-                    "source": "string",
+                    "singleFluid": false,
+                    "timeStep": 20,
+                    "type": 0,
                   },
-                ],
-                "result": 0,
+                  "id": "QUIZ001",
+                  "questions": Array [
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "86 km",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "78 km",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "56 km",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "L’aqueduc du Gier est un des aqueducs antiques de Lyon desservant la ville antique de Lugdunum. Avec ses 86 km il est le plus long des quatre aqueducs ayant alimenté la ville en eau, et celui dont les structures sont le mieux conservées. Il doit son nom au fait qu'il puise aux sources du Gier, affluent du Rhône",
+                      "questionLabel": "Quelle longueur faisait l’aqueduc du Gier pour acheminer l’eau sur Lyon à l’époque romaine ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 800 habitants.",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 400 habitants.",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 200 habitants.",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "En 1800 à Lyon, combien de points d'eau y avait-il par habitants ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "François Mitterrand",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "Napoléon Ier",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "Napoléon III",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "Qui officialise la création de la Compagnie Générale des eaux ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "string",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "string",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "Aristide Dumont",
+                          "isTrue": true,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "Quel ingénieur est à l’origine du projet d’alimentation en eau en 1856 ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                  ],
+                  "result": 0,
+                  "startDate": null,
+                  "state": 0,
+                },
                 "startDate": null,
-                "state": 0,
-              },
-              "startDate": null,
-              "state": 1,
-              "success": 0,
-              "target": 15,
-              "title": "Challenge 4",
+                "state": 1,
+                "success": 0,
+                "target": 15,
+                "title": "Challenge 4",
+              }
             }
-          }
-        />
-        <ChallengeCard
-          cardHeight={384}
-          cardWidth={285}
-          index={4}
-          indexSlider={0}
-          moveToSlide={[Function]}
-          userChallenge={
-            Object {
-              "action": Object {
-                "ecogesture": null,
-                "startDate": null,
-                "state": 0,
-              },
-              "description": "Description challenge 5",
-              "duel": Object {
-                "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine",
-                "duration": "P30D",
-                "fluidTypes": Array [],
-                "id": "DUEL001",
-                "startDate": null,
-                "state": 0,
-                "threshold": 0,
-                "title": "Title DUEL001",
-                "userConsumption": 0,
-              },
-              "endingDate": null,
-              "exploration": Object {
-                "complementary_description": "Refaire un tour dans son profil si déjà fait",
-                "date": null,
-                "description": "Avoir complété son profil",
-                "ecogesture_id": "",
-                "fluid_condition": Array [],
-                "id": "EXPLORATION001",
-                "message_success": "Vous avez complété votre profil ou refait un tour dans votre profil",
-                "progress": 0,
-                "state": 0,
-                "target": 1,
-                "type": 1,
-              },
-              "id": "CHALLENGE0005",
-              "progress": Object {
-                "actionProgress": 0,
-                "explorationProgress": 0,
-                "quizProgress": 0,
-              },
-              "quiz": Object {
-                "customQuestion": Object {
-                  "interval": 20,
-                  "period": Object {},
-                  "questionLabel": "Custom1",
-                  "result": 0,
-                  "singleFluid": false,
-                  "timeStep": 20,
-                  "type": 0,
+          />
+          <mock-challengecard
+            cardHeight={384}
+            cardWidth={285}
+            index={4}
+            indexSlider={3}
+            key="4"
+            moveToSlide={[Function]}
+            userChallenge={
+              Object {
+                "action": Object {
+                  "ecogesture": null,
+                  "startDate": null,
+                  "state": 0,
                 },
-                "id": "QUIZ001",
-                "questions": Array [
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "86 km",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "78 km",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "56 km",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "L’aqueduc du Gier est un des aqueducs antiques de Lyon desservant la ville antique de Lugdunum. Avec ses 86 km il est le plus long des quatre aqueducs ayant alimenté la ville en eau, et celui dont les structures sont le mieux conservées. Il doit son nom au fait qu'il puise aux sources du Gier, affluent du Rhône",
-                    "questionLabel": "Quelle longueur faisait l’aqueduc du Gier pour acheminer l’eau sur Lyon à l’époque romaine ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 800 habitants.",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 400 habitants.",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 200 habitants.",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "En 1800 à Lyon, combien de points d'eau y avait-il par habitants ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "François Mitterrand",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "Napoléon Ier",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "Napoléon III",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "Qui officialise la création de la Compagnie Générale des eaux ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "string",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "string",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "Aristide Dumont",
-                        "isTrue": true,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "Quel ingénieur est à l’origine du projet d’alimentation en eau en 1856 ?",
+                "description": "Description challenge 5",
+                "duel": Object {
+                  "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine",
+                  "duration": "P30D",
+                  "fluidTypes": Array [],
+                  "id": "DUEL001",
+                  "startDate": null,
+                  "state": 0,
+                  "threshold": 0,
+                  "title": "Title DUEL001",
+                  "userConsumption": 0,
+                },
+                "endingDate": null,
+                "exploration": Object {
+                  "complementary_description": "Refaire un tour dans son profil si déjà fait",
+                  "date": null,
+                  "description": "Avoir complété son profil",
+                  "ecogesture_id": "",
+                  "fluid_condition": Array [],
+                  "id": "EXPLORATION001",
+                  "message_success": "Vous avez complété votre profil ou refait un tour dans votre profil",
+                  "progress": 0,
+                  "state": 0,
+                  "target": 1,
+                  "type": 1,
+                },
+                "id": "CHALLENGE0005",
+                "progress": Object {
+                  "actionProgress": 0,
+                  "explorationProgress": 0,
+                  "quizProgress": 0,
+                },
+                "quiz": Object {
+                  "customQuestion": Object {
+                    "interval": 20,
+                    "period": Object {},
+                    "questionLabel": "Custom1",
                     "result": 0,
-                    "source": "string",
+                    "singleFluid": false,
+                    "timeStep": 20,
+                    "type": 0,
                   },
-                ],
-                "result": 0,
+                  "id": "QUIZ001",
+                  "questions": Array [
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "86 km",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "78 km",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "56 km",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "L’aqueduc du Gier est un des aqueducs antiques de Lyon desservant la ville antique de Lugdunum. Avec ses 86 km il est le plus long des quatre aqueducs ayant alimenté la ville en eau, et celui dont les structures sont le mieux conservées. Il doit son nom au fait qu'il puise aux sources du Gier, affluent du Rhône",
+                      "questionLabel": "Quelle longueur faisait l’aqueduc du Gier pour acheminer l’eau sur Lyon à l’époque romaine ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 800 habitants.",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 400 habitants.",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 200 habitants.",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "En 1800 à Lyon, combien de points d'eau y avait-il par habitants ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "François Mitterrand",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "Napoléon Ier",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "Napoléon III",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "Qui officialise la création de la Compagnie Générale des eaux ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "string",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "string",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "Aristide Dumont",
+                          "isTrue": true,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "Quel ingénieur est à l’origine du projet d’alimentation en eau en 1856 ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                  ],
+                  "result": 0,
+                  "startDate": null,
+                  "state": 0,
+                },
                 "startDate": null,
                 "state": 0,
-              },
-              "startDate": null,
-              "state": 0,
-              "success": 0,
-              "target": 15,
-              "title": "Challenge 5",
+                "success": 0,
+                "target": 15,
+                "title": "Challenge 5",
+              }
             }
-          }
-        />
-        <ChallengeCard
-          cardHeight={384}
-          cardWidth={285}
-          index={5}
-          indexSlider={0}
-          moveToSlide={[Function]}
-          userChallenge={
-            Object {
-              "action": Object {
-                "ecogesture": null,
-                "startDate": null,
-                "state": 0,
-              },
-              "description": "Description challenge 6",
-              "duel": Object {
-                "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine",
-                "duration": "P30D",
-                "fluidTypes": Array [],
-                "id": "DUEL001",
-                "startDate": null,
-                "state": 0,
-                "threshold": 0,
-                "title": "Title DUEL001",
-                "userConsumption": 0,
-              },
-              "endingDate": null,
-              "exploration": Object {
-                "complementary_description": "Refaire un tour dans son profil si déjà fait",
-                "date": null,
-                "description": "Avoir complété son profil",
-                "ecogesture_id": "",
-                "fluid_condition": Array [],
-                "id": "EXPLORATION001",
-                "message_success": "Vous avez complété votre profil ou refait un tour dans votre profil",
-                "progress": 0,
-                "state": 0,
-                "target": 1,
-                "type": 1,
-              },
-              "id": "CHALLENGE0006",
-              "progress": Object {
-                "actionProgress": 0,
-                "explorationProgress": 0,
-                "quizProgress": 0,
-              },
-              "quiz": Object {
-                "customQuestion": Object {
-                  "interval": 20,
-                  "period": Object {},
-                  "questionLabel": "Custom1",
-                  "result": 0,
-                  "singleFluid": false,
-                  "timeStep": 20,
-                  "type": 0,
+          />
+          <mock-challengecard
+            cardHeight={384}
+            cardWidth={285}
+            index={5}
+            indexSlider={3}
+            key="5"
+            moveToSlide={[Function]}
+            userChallenge={
+              Object {
+                "action": Object {
+                  "ecogesture": null,
+                  "startDate": null,
+                  "state": 0,
                 },
-                "id": "QUIZ001",
-                "questions": Array [
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "86 km",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "78 km",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "56 km",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "L’aqueduc du Gier est un des aqueducs antiques de Lyon desservant la ville antique de Lugdunum. Avec ses 86 km il est le plus long des quatre aqueducs ayant alimenté la ville en eau, et celui dont les structures sont le mieux conservées. Il doit son nom au fait qu'il puise aux sources du Gier, affluent du Rhône",
-                    "questionLabel": "Quelle longueur faisait l’aqueduc du Gier pour acheminer l’eau sur Lyon à l’époque romaine ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 800 habitants.",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 400 habitants.",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "1 point d’eau public pour 200 habitants.",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "En 1800 à Lyon, combien de points d'eau y avait-il par habitants ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "François Mitterrand",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "Napoléon Ier",
-                        "isTrue": true,
-                      },
-                      Object {
-                        "answerLabel": "Napoléon III",
-                        "isTrue": false,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "Qui officialise la création de la Compagnie Générale des eaux ?",
-                    "result": 0,
-                    "source": "string",
-                  },
-                  Object {
-                    "answers": Array [
-                      Object {
-                        "answerLabel": "string",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "string",
-                        "isTrue": false,
-                      },
-                      Object {
-                        "answerLabel": "Aristide Dumont",
-                        "isTrue": true,
-                      },
-                    ],
-                    "explanation": "string",
-                    "questionLabel": "Quel ingénieur est à l’origine du projet d’alimentation en eau en 1856 ?",
+                "description": "Description challenge 6",
+                "duel": Object {
+                  "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine",
+                  "duration": "P30D",
+                  "fluidTypes": Array [],
+                  "id": "DUEL001",
+                  "startDate": null,
+                  "state": 0,
+                  "threshold": 0,
+                  "title": "Title DUEL001",
+                  "userConsumption": 0,
+                },
+                "endingDate": null,
+                "exploration": Object {
+                  "complementary_description": "Refaire un tour dans son profil si déjà fait",
+                  "date": null,
+                  "description": "Avoir complété son profil",
+                  "ecogesture_id": "",
+                  "fluid_condition": Array [],
+                  "id": "EXPLORATION001",
+                  "message_success": "Vous avez complété votre profil ou refait un tour dans votre profil",
+                  "progress": 0,
+                  "state": 0,
+                  "target": 1,
+                  "type": 1,
+                },
+                "id": "CHALLENGE0006",
+                "progress": Object {
+                  "actionProgress": 0,
+                  "explorationProgress": 0,
+                  "quizProgress": 0,
+                },
+                "quiz": Object {
+                  "customQuestion": Object {
+                    "interval": 20,
+                    "period": Object {},
+                    "questionLabel": "Custom1",
                     "result": 0,
-                    "source": "string",
+                    "singleFluid": false,
+                    "timeStep": 20,
+                    "type": 0,
                   },
-                ],
-                "result": 0,
+                  "id": "QUIZ001",
+                  "questions": Array [
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "86 km",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "78 km",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "56 km",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "L’aqueduc du Gier est un des aqueducs antiques de Lyon desservant la ville antique de Lugdunum. Avec ses 86 km il est le plus long des quatre aqueducs ayant alimenté la ville en eau, et celui dont les structures sont le mieux conservées. Il doit son nom au fait qu'il puise aux sources du Gier, affluent du Rhône",
+                      "questionLabel": "Quelle longueur faisait l’aqueduc du Gier pour acheminer l’eau sur Lyon à l’époque romaine ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 800 habitants.",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 400 habitants.",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "1 point d’eau public pour 200 habitants.",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "En 1800 à Lyon, combien de points d'eau y avait-il par habitants ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "François Mitterrand",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "Napoléon Ier",
+                          "isTrue": true,
+                        },
+                        Object {
+                          "answerLabel": "Napoléon III",
+                          "isTrue": false,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "Qui officialise la création de la Compagnie Générale des eaux ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                    Object {
+                      "answers": Array [
+                        Object {
+                          "answerLabel": "string",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "string",
+                          "isTrue": false,
+                        },
+                        Object {
+                          "answerLabel": "Aristide Dumont",
+                          "isTrue": true,
+                        },
+                      ],
+                      "explanation": "string",
+                      "questionLabel": "Quel ingénieur est à l’origine du projet d’alimentation en eau en 1856 ?",
+                      "result": 0,
+                      "source": "string",
+                    },
+                  ],
+                  "result": 0,
+                  "startDate": null,
+                  "state": 0,
+                },
                 "startDate": null,
                 "state": 0,
-              },
-              "startDate": null,
-              "state": 0,
-              "success": 0,
-              "target": 15,
-              "title": "Challenge 6",
+                "success": 0,
+                "target": 15,
+                "title": "Challenge 6",
+              }
             }
-          }
-        />
+          />
+        </div>
       </div>
-    </div>
-    <div
-      className="sliderButtons"
-    >
-      <StyledIconButton
-        aria-label="challenge.accessibility.button_slider_previous"
-        icon="test-file-stub"
-        onClick={[Function]}
-        size={16}
-      />
-      <StyledIconButton
-        aria-label="challenge.accessibility.button_slider_next"
-        icon="test-file-stub"
-        onClick={[Function]}
-        size={16}
-      />
-    </div>
-  </Content>
-</React.Fragment>
+      <div
+        className="sliderButtons"
+      >
+        <StyledIconButton
+          aria-label="challenge.accessibility.button_slider_previous"
+          icon="test-file-stub"
+          onClick={[Function]}
+          size={16}
+        >
+          <WithStyles(WithStyles(ForwardRef(IconButton)))
+            aria-label="challenge.accessibility.button_slider_previous"
+            onClick={[Function]}
+          >
+            <WithStyles(ForwardRef(IconButton))
+              aria-label="challenge.accessibility.button_slider_previous"
+              classes={
+                Object {
+                  "root": "WithStyles(ForwardRef(IconButton))-root-1",
+                }
+              }
+              onClick={[Function]}
+            >
+              <ForwardRef(IconButton)
+                aria-label="challenge.accessibility.button_slider_previous"
+                classes={
+                  Object {
+                    "colorInherit": "MuiIconButton-colorInherit",
+                    "colorPrimary": "MuiIconButton-colorPrimary",
+                    "colorSecondary": "MuiIconButton-colorSecondary",
+                    "disabled": "Mui-disabled",
+                    "edgeEnd": "MuiIconButton-edgeEnd",
+                    "edgeStart": "MuiIconButton-edgeStart",
+                    "label": "MuiIconButton-label",
+                    "root": "MuiIconButton-root WithStyles(ForwardRef(IconButton))-root-1",
+                    "sizeSmall": "MuiIconButton-sizeSmall",
+                  }
+                }
+                onClick={[Function]}
+              >
+                <WithStyles(ForwardRef(ButtonBase))
+                  aria-label="challenge.accessibility.button_slider_previous"
+                  centerRipple={true}
+                  className="MuiIconButton-root WithStyles(ForwardRef(IconButton))-root-1"
+                  disabled={false}
+                  focusRipple={true}
+                  onClick={[Function]}
+                >
+                  <ForwardRef(ButtonBase)
+                    aria-label="challenge.accessibility.button_slider_previous"
+                    centerRipple={true}
+                    className="MuiIconButton-root WithStyles(ForwardRef(IconButton))-root-1"
+                    classes={
+                      Object {
+                        "disabled": "Mui-disabled",
+                        "focusVisible": "Mui-focusVisible",
+                        "root": "MuiButtonBase-root",
+                      }
+                    }
+                    disabled={false}
+                    focusRipple={true}
+                    onClick={[Function]}
+                  >
+                    <button
+                      aria-label="challenge.accessibility.button_slider_previous"
+                      className="MuiButtonBase-root MuiIconButton-root WithStyles(ForwardRef(IconButton))-root-1"
+                      disabled={false}
+                      onBlur={[Function]}
+                      onClick={[Function]}
+                      onDragLeave={[Function]}
+                      onFocus={[Function]}
+                      onKeyDown={[Function]}
+                      onKeyUp={[Function]}
+                      onMouseDown={[Function]}
+                      onMouseLeave={[Function]}
+                      onMouseUp={[Function]}
+                      onTouchEnd={[Function]}
+                      onTouchMove={[Function]}
+                      onTouchStart={[Function]}
+                      tabIndex={0}
+                      type="button"
+                    >
+                      <span
+                        className="MuiIconButton-label"
+                      >
+                        <StyledIcon
+                          icon="test-file-stub"
+                          size={16}
+                        >
+                          <Icon
+                            aria-hidden={true}
+                            icon="test-file-stub"
+                            size={16}
+                            spin={false}
+                          >
+                            <Component
+                              aria-hidden={true}
+                              className="styles__icon___23x3R"
+                              height={16}
+                              style={Object {}}
+                              width={16}
+                            >
+                              <svg
+                                aria-hidden={true}
+                                className="styles__icon___23x3R"
+                                height={16}
+                                style={Object {}}
+                                width={16}
+                              >
+                                <use
+                                  xlinkHref="#test-file-stub"
+                                />
+                              </svg>
+                            </Component>
+                          </Icon>
+                        </StyledIcon>
+                      </span>
+                      <NoSsr>
+                        <WithStyles(memo)
+                          center={true}
+                        >
+                          <ForwardRef(TouchRipple)
+                            center={true}
+                            classes={
+                              Object {
+                                "child": "MuiTouchRipple-child",
+                                "childLeaving": "MuiTouchRipple-childLeaving",
+                                "childPulsate": "MuiTouchRipple-childPulsate",
+                                "ripple": "MuiTouchRipple-ripple",
+                                "ripplePulsate": "MuiTouchRipple-ripplePulsate",
+                                "rippleVisible": "MuiTouchRipple-rippleVisible",
+                                "root": "MuiTouchRipple-root",
+                              }
+                            }
+                          >
+                            <span
+                              className="MuiTouchRipple-root"
+                            >
+                              <TransitionGroup
+                                childFactory={[Function]}
+                                component={null}
+                                exit={true}
+                              />
+                            </span>
+                          </ForwardRef(TouchRipple)>
+                        </WithStyles(memo)>
+                      </NoSsr>
+                    </button>
+                  </ForwardRef(ButtonBase)>
+                </WithStyles(ForwardRef(ButtonBase))>
+              </ForwardRef(IconButton)>
+            </WithStyles(ForwardRef(IconButton))>
+          </WithStyles(WithStyles(ForwardRef(IconButton)))>
+        </StyledIconButton>
+        <StyledIconButton
+          aria-label="challenge.accessibility.button_slider_next"
+          icon="test-file-stub"
+          onClick={[Function]}
+          size={16}
+        >
+          <WithStyles(WithStyles(ForwardRef(IconButton)))
+            aria-label="challenge.accessibility.button_slider_next"
+            onClick={[Function]}
+          >
+            <WithStyles(ForwardRef(IconButton))
+              aria-label="challenge.accessibility.button_slider_next"
+              classes={
+                Object {
+                  "root": "WithStyles(ForwardRef(IconButton))-root-1",
+                }
+              }
+              onClick={[Function]}
+            >
+              <ForwardRef(IconButton)
+                aria-label="challenge.accessibility.button_slider_next"
+                classes={
+                  Object {
+                    "colorInherit": "MuiIconButton-colorInherit",
+                    "colorPrimary": "MuiIconButton-colorPrimary",
+                    "colorSecondary": "MuiIconButton-colorSecondary",
+                    "disabled": "Mui-disabled",
+                    "edgeEnd": "MuiIconButton-edgeEnd",
+                    "edgeStart": "MuiIconButton-edgeStart",
+                    "label": "MuiIconButton-label",
+                    "root": "MuiIconButton-root WithStyles(ForwardRef(IconButton))-root-1",
+                    "sizeSmall": "MuiIconButton-sizeSmall",
+                  }
+                }
+                onClick={[Function]}
+              >
+                <WithStyles(ForwardRef(ButtonBase))
+                  aria-label="challenge.accessibility.button_slider_next"
+                  centerRipple={true}
+                  className="MuiIconButton-root WithStyles(ForwardRef(IconButton))-root-1"
+                  disabled={false}
+                  focusRipple={true}
+                  onClick={[Function]}
+                >
+                  <ForwardRef(ButtonBase)
+                    aria-label="challenge.accessibility.button_slider_next"
+                    centerRipple={true}
+                    className="MuiIconButton-root WithStyles(ForwardRef(IconButton))-root-1"
+                    classes={
+                      Object {
+                        "disabled": "Mui-disabled",
+                        "focusVisible": "Mui-focusVisible",
+                        "root": "MuiButtonBase-root",
+                      }
+                    }
+                    disabled={false}
+                    focusRipple={true}
+                    onClick={[Function]}
+                  >
+                    <button
+                      aria-label="challenge.accessibility.button_slider_next"
+                      className="MuiButtonBase-root MuiIconButton-root WithStyles(ForwardRef(IconButton))-root-1"
+                      disabled={false}
+                      onBlur={[Function]}
+                      onClick={[Function]}
+                      onDragLeave={[Function]}
+                      onFocus={[Function]}
+                      onKeyDown={[Function]}
+                      onKeyUp={[Function]}
+                      onMouseDown={[Function]}
+                      onMouseLeave={[Function]}
+                      onMouseUp={[Function]}
+                      onTouchEnd={[Function]}
+                      onTouchMove={[Function]}
+                      onTouchStart={[Function]}
+                      tabIndex={0}
+                      type="button"
+                    >
+                      <span
+                        className="MuiIconButton-label"
+                      >
+                        <StyledIcon
+                          icon="test-file-stub"
+                          size={16}
+                        >
+                          <Icon
+                            aria-hidden={true}
+                            icon="test-file-stub"
+                            size={16}
+                            spin={false}
+                          >
+                            <Component
+                              aria-hidden={true}
+                              className="styles__icon___23x3R"
+                              height={16}
+                              style={Object {}}
+                              width={16}
+                            >
+                              <svg
+                                aria-hidden={true}
+                                className="styles__icon___23x3R"
+                                height={16}
+                                style={Object {}}
+                                width={16}
+                              >
+                                <use
+                                  xlinkHref="#test-file-stub"
+                                />
+                              </svg>
+                            </Component>
+                          </Icon>
+                        </StyledIcon>
+                      </span>
+                      <NoSsr>
+                        <WithStyles(memo)
+                          center={true}
+                        >
+                          <ForwardRef(TouchRipple)
+                            center={true}
+                            classes={
+                              Object {
+                                "child": "MuiTouchRipple-child",
+                                "childLeaving": "MuiTouchRipple-childLeaving",
+                                "childPulsate": "MuiTouchRipple-childPulsate",
+                                "ripple": "MuiTouchRipple-ripple",
+                                "ripplePulsate": "MuiTouchRipple-ripplePulsate",
+                                "rippleVisible": "MuiTouchRipple-rippleVisible",
+                                "root": "MuiTouchRipple-root",
+                              }
+                            }
+                          >
+                            <span
+                              className="MuiTouchRipple-root"
+                            >
+                              <TransitionGroup
+                                childFactory={[Function]}
+                                component={null}
+                                exit={true}
+                              />
+                            </span>
+                          </ForwardRef(TouchRipple)>
+                        </WithStyles(memo)>
+                      </NoSsr>
+                    </button>
+                  </ForwardRef(ButtonBase)>
+                </WithStyles(ForwardRef(ButtonBase))>
+              </ForwardRef(IconButton)>
+            </WithStyles(ForwardRef(IconButton))>
+          </WithStyles(WithStyles(ForwardRef(IconButton)))>
+        </StyledIconButton>
+      </div>
+    </mock-content>
+  </ChallengeView>
+</Provider>
 `;
diff --git a/src/components/Content/Content.tsx b/src/components/Content/Content.tsx
index 6e141e273..0df5633eb 100644
--- a/src/components/Content/Content.tsx
+++ b/src/components/Content/Content.tsx
@@ -1,13 +1,12 @@
 import React, { useCallback, useEffect } from 'react'
 import './content.scss'
-import { history } from 'components/App'
 import { useSelector, useDispatch } from 'react-redux'
 import { AppStore } from 'store'
 import { changeScreenType } from 'store/global/global.actions'
 import { updateModalIsFeedbacksOpen } from 'store/modal/modal.actions'
 import { ScreenType } from 'enum/screen.enum'
 import FeedbackModal from 'components/Feedback/FeedbackModal'
-
+import { useHistory } from 'react-router-dom'
 interface ContentProps {
   children?: React.ReactNode
   height?: number
@@ -20,6 +19,7 @@ const Content: React.FC<ContentProps> = ({
   background = 'inherit',
 }: ContentProps) => {
   const dispatch = useDispatch()
+  const history = useHistory()
   const { screenType } = useSelector((state: AppStore) => state.ecolyo.global)
   const { isFeedbacksOpen } = useSelector(
     (state: AppStore) => state.ecolyo.modal
@@ -38,10 +38,10 @@ const Content: React.FC<ContentProps> = ({
   /**
    * Handle Desktop scroll
    */
-  const handleWindowScroll = () => {
+  const handleWindowScroll = useCallback(() => {
     app && app.scrollTo(0, 0)
     window.scrollTo(0, 0)
-  }
+  }, [app])
 
   // Set listners for scroll
   useEffect(() => {
@@ -50,7 +50,7 @@ const Content: React.FC<ContentProps> = ({
       // remove listner subscription
       listner()
     }
-  }, [])
+  }, [handleWindowScroll, history])
 
   useEffect(() => {
     function handleResize() {
diff --git a/src/components/Duel/DuelView.spec.tsx b/src/components/Duel/DuelView.spec.tsx
index 5d6eece3c..f1fa4c7f1 100644
--- a/src/components/Duel/DuelView.spec.tsx
+++ b/src/components/Duel/DuelView.spec.tsx
@@ -10,6 +10,10 @@ import DuelUnlocked from './DuelUnlocked'
 import DuelOngoing from './DuelOngoing'
 import { UserDuelState } from 'enum/userDuel.enum'
 
+jest.mock('components/Header/CozyBar', () => 'mock-cozybar')
+jest.mock('components/Header/Header', () => 'mock-header')
+jest.mock('components/Content/Content', () => 'mock-content')
+
 const mockUseSelector = jest.spyOn(reactRedux, 'useSelector')
 
 describe('DuelView component', () => {
diff --git a/src/components/Ecogesture/EcogestureView.spec.tsx b/src/components/Ecogesture/EcogestureView.spec.tsx
index 0c7ab368b..cc3aa53c2 100644
--- a/src/components/Ecogesture/EcogestureView.spec.tsx
+++ b/src/components/Ecogesture/EcogestureView.spec.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/display-name */
 import React from 'react'
 import { mount } from 'enzyme'
 import { Provider } from 'react-redux'
diff --git a/src/components/Exploration/ExplorationView.spec.tsx b/src/components/Exploration/ExplorationView.spec.tsx
index afc90e310..af8c18175 100644
--- a/src/components/Exploration/ExplorationView.spec.tsx
+++ b/src/components/Exploration/ExplorationView.spec.tsx
@@ -8,19 +8,39 @@ import { challengeStateData } from '../../../tests/__mocks__/challengeStateData.
 import ExplorationFinished from './ExplorationFinished'
 import ExplorationError from './ExplorationError'
 import ExplorationOngoing from './ExplorationOngoing'
+import { UserExplorationState } from 'enum/userExploration.enum'
+
+jest.mock('components/Header/CozyBar', () => 'mock-cozybar')
+jest.mock('components/Header/Header', () => 'mock-header')
+jest.mock('components/Content/Content', () => 'mock-content')
 
 const mockUseSelector = jest.spyOn(reactRedux, 'useSelector')
 
 describe('ExplorationView', () => {
-  it('should be rendered with ExplorationView component when no exploration state', () => {
-    mockUseSelector.mockReturnValue(challengeStateData)
+  it('should be rendered with ExplorationError component when unknown exploration state', () => {
+    const updatedUserChallenge = {
+      ...userChallengeData[0],
+      exploration: {
+        ...userChallengeData[0].exploration,
+        state: 99,
+      },
+    }
+    const updatedChallengeState = {
+      ...challengeStateData,
+      currentChallenge: updatedUserChallenge,
+    }
+    mockUseSelector.mockReturnValue(updatedChallengeState)
     const wrapper = shallow(<ExplorationView />)
-    expect(wrapper.find(ExplorationError).exists())
+    expect(wrapper.find(ExplorationError).exists()).toBeTruthy()
   })
-  it('should be rendered with ExplorationView component when exploration state = unlocked', () => {
+
+  it('should be rendered with ExplorationOngoing component when exploration state = unlocked', () => {
     const updatedUserChallenge = {
       ...userChallengeData[0],
-      quiz: { ...userChallengeData[0].quiz, state: UserQuizState.UNLOCKED },
+      exploration: {
+        ...userChallengeData[0].exploration,
+        state: UserExplorationState.UNLOCKED,
+      },
     }
     const updatedChallengeState = {
       ...challengeStateData,
@@ -28,12 +48,16 @@ describe('ExplorationView', () => {
     }
     mockUseSelector.mockReturnValue(updatedChallengeState)
     const wrapper = shallow(<ExplorationView />)
-    expect(wrapper.find(ExplorationOngoing).exists())
+    expect(wrapper.find(ExplorationOngoing).exists()).toBeTruthy()
   })
-  it('should be rendered with ExplorationView component when exploration state = ongoing', () => {
+
+  it('should be rendered with ExplorationOngoing component when exploration state = ongoing', () => {
     const updatedUserChallenge = {
       ...userChallengeData[0],
-      quiz: { ...userChallengeData[0].quiz, state: UserQuizState.ONGOING },
+      exploration: {
+        ...userChallengeData[0].exploration,
+        state: UserExplorationState.ONGOING,
+      },
     }
     const updatedChallengeState = {
       ...challengeStateData,
@@ -41,12 +65,16 @@ describe('ExplorationView', () => {
     }
     mockUseSelector.mockReturnValue(updatedChallengeState)
     const wrapper = shallow(<ExplorationView />)
-    expect(wrapper.find(ExplorationOngoing).exists())
+    expect(wrapper.find(ExplorationOngoing).exists()).toBeTruthy()
   })
-  it('should be rendered with ExplorationView component when exploration state = Done', () => {
+
+  it('should be rendered with ExplorationFinished component when exploration state = Done', () => {
     const updatedUserChallenge = {
       ...userChallengeData[0],
-      quiz: { ...userChallengeData[0].quiz, state: UserQuizState.DONE },
+      exploration: {
+        ...userChallengeData[0].exploration,
+        state: UserExplorationState.DONE,
+      },
     }
     const updatedChallengeState = {
       ...challengeStateData,
@@ -54,6 +82,6 @@ describe('ExplorationView', () => {
     }
     mockUseSelector.mockReturnValue(updatedChallengeState)
     const wrapper = shallow(<ExplorationView />)
-    expect(wrapper.find(ExplorationFinished).exists())
+    expect(wrapper.find(ExplorationFinished).exists()).toBeTruthy()
   })
 })
diff --git a/src/components/FAQ/FAQView.spec.tsx b/src/components/FAQ/FAQView.spec.tsx
index 0cf009bf0..74f74ef60 100644
--- a/src/components/FAQ/FAQView.spec.tsx
+++ b/src/components/FAQ/FAQView.spec.tsx
@@ -11,6 +11,9 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => {
     }),
   }
 })
+jest.mock('components/Header/CozyBar', () => 'mock-cozybar')
+jest.mock('components/Header/Header', () => 'mock-header')
+jest.mock('components/Content/Content', () => 'mock-content')
 
 describe('FAQView component', () => {
   it('should render only the parent component', () => {
diff --git a/src/components/FAQ/__snapshots__/FAQView.spec.tsx.snap b/src/components/FAQ/__snapshots__/FAQView.spec.tsx.snap
index d39d3478b..285ef8a29 100644
--- a/src/components/FAQ/__snapshots__/FAQView.spec.tsx.snap
+++ b/src/components/FAQ/__snapshots__/FAQView.spec.tsx.snap
@@ -1,20 +1,20 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`FAQView component should render only the parent component 1`] = `
-<React.Fragment>
-  <CozyBar
-    displayBackArrow={true}
-    titleKey="common.title_faq"
-  />
-  <Header
-    desktopTitleKey="common.title_faq"
-    displayBackArrow={true}
-    setHeaderHeight={[Function]}
-  />
-  <Content
-    height={0}
-  >
-    <FAQContent />
-  </Content>
-</React.Fragment>
-`;
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FAQView component should render only the parent component 1`] = `
+<React.Fragment>
+  <mock-cozybar
+    displayBackArrow={true}
+    titleKey="common.title_faq"
+  />
+  <mock-header
+    desktopTitleKey="common.title_faq"
+    displayBackArrow={true}
+    setHeaderHeight={[Function]}
+  />
+  <mock-content
+    height={0}
+  >
+    <FAQContent />
+  </mock-content>
+</React.Fragment>
+`;
diff --git a/src/components/GCU/GCUView.spec.tsx b/src/components/GCU/GCUView.spec.tsx
index 809feee5f..6ede9dbdc 100644
--- a/src/components/GCU/GCUView.spec.tsx
+++ b/src/components/GCU/GCUView.spec.tsx
@@ -2,6 +2,10 @@ import React from 'react'
 import { shallow } from 'enzyme'
 import GCUView from 'components/GCU/GCUView'
 
+jest.mock('components/Header/CozyBar', () => 'mock-cozybar')
+jest.mock('components/Header/Header', () => 'mock-header')
+jest.mock('components/Content/Content', () => 'mock-content')
+
 describe('GCUView component', () => {
   it('should be rendered correctly', () => {
     const component = shallow(<GCUView />).getElement()
diff --git a/src/components/GCU/__snapshots__/GCUView.spec.tsx.snap b/src/components/GCU/__snapshots__/GCUView.spec.tsx.snap
index 33e490ab2..bcc93b8a7 100644
--- a/src/components/GCU/__snapshots__/GCUView.spec.tsx.snap
+++ b/src/components/GCU/__snapshots__/GCUView.spec.tsx.snap
@@ -2,19 +2,19 @@
 
 exports[`GCUView component should be rendered correctly 1`] = `
 <React.Fragment>
-  <CozyBar
+  <mock-cozybar
     displayBackArrow={true}
     titleKey="common.title_gcu"
   />
-  <Header
+  <mock-header
     desktopTitleKey="common.title_gcu"
     displayBackArrow={true}
     setHeaderHeight={[Function]}
   />
-  <Content
+  <mock-content
     height={0}
   >
     <GCUContent />
-  </Content>
+  </mock-content>
 </React.Fragment>
 `;
diff --git a/src/components/Home/ConsumptionView.spec.tsx b/src/components/Home/ConsumptionView.spec.tsx
index a272d2bf2..1692736e7 100644
--- a/src/components/Home/ConsumptionView.spec.tsx
+++ b/src/components/Home/ConsumptionView.spec.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/display-name */
 import React from 'react'
 import { mount } from 'enzyme'
 import { Provider } from 'react-redux'
@@ -12,6 +11,8 @@ import * as chartActions from 'store/chart/chart.actions'
 import { FluidState, FluidType } from 'enum/fluid.enum'
 import { TimeStep } from 'enum/timeStep.enum'
 import StyledSpinner from 'components/CommonKit/Spinner/StyledSpinner'
+import FluidButtons from 'components/Home/FluidButtons'
+import KonnectorViewerList from 'components/Konnector/KonnectorViewerList'
 import ConsumptionView from './ConsumptionView'
 import { FluidStatus } from 'models'
 import { mockTestProfile1 } from '../../../tests/__mocks__/profileType.mock'
@@ -77,6 +78,7 @@ describe('ConsumptionView component', () => {
       loading: false,
       fluidStatus: mockFluidStatus,
       releaseNotes: mockInitialEcolyoState.global.releaseNotes,
+      openPartnersIssueModal: false,
     })
     const wrapper = mount(
       <Provider store={store}>
@@ -100,6 +102,7 @@ describe('ConsumptionView component', () => {
       loading: true,
       fluidStatus: mockFluidStatus,
       releaseNotes: mockInitialEcolyoState.global.releaseNotes,
+      openPartnersIssueModal: false,
     })
     const wrapper = mount(
       <Provider store={store}>
@@ -118,6 +121,7 @@ describe('ConsumptionView component', () => {
       loading: true,
       fluidStatus: mockInitialEcolyoState.global.fluidStatus,
       releaseNotes: mockInitialEcolyoState.global.releaseNotes,
+      openPartnersIssueModal: false,
     })
     mount(
       <Provider store={store}>
@@ -127,12 +131,14 @@ describe('ConsumptionView component', () => {
     expect(setCurrentTimeStepSpy).toBeCalledTimes(1)
     expect(setCurrentTimeStepSpy).toHaveBeenCalledWith(TimeStep.WEEK)
   })
+
   it('should render konnector list when no fluid is connected', () => {
     useSelectorSpy.mockReturnValue({
       currentTimeStep: TimeStep.WEEK,
       loading: true,
       fluidStatus: mockInitialEcolyoState.global.fluidStatus,
       releaseNotes: mockInitialEcolyoState.global.releaseNotes,
+      openPartnersIssueModal: false,
     })
     const wrapper = mount(
       <Provider store={store}>
@@ -141,6 +147,7 @@ describe('ConsumptionView component', () => {
     )
     expect(wrapper.find('mock-consumptiondetails').exists()).toBeTruthy()
   })
+
   it('should render mutlifluid consumption if at least one fluid is connected', () => {
     const updatedStatus: FluidStatus[] =
       mockInitialEcolyoState.global.fluidStatus
@@ -151,6 +158,7 @@ describe('ConsumptionView component', () => {
       loading: true,
       fluidStatus: updatedStatus,
       releaseNotes: mockInitialEcolyoState.global.releaseNotes,
+      openPartnersIssueModal: false,
     })
     const wrapper = mount(
       <Provider store={store}>
@@ -159,6 +167,7 @@ describe('ConsumptionView component', () => {
     )
     expect(wrapper.find('.consumptionview-content').exists()).toBeTruthy()
   })
+
   it('should render Electricity when elec is connected', () => {
     const updatedStatus: FluidStatus[] =
       mockInitialEcolyoState.global.fluidStatus
@@ -168,6 +177,7 @@ describe('ConsumptionView component', () => {
       loading: true,
       fluidStatus: updatedStatus,
       releaseNotes: mockInitialEcolyoState.global.releaseNotes,
+      openPartnersIssueModal: false,
     })
     const wrapper = mount(
       <Provider store={store}>
diff --git a/src/components/LegalNotice/LegalNoticeView.spec.tsx b/src/components/LegalNotice/LegalNoticeView.spec.tsx
index d63efac8a..2c3c35247 100644
--- a/src/components/LegalNotice/LegalNoticeView.spec.tsx
+++ b/src/components/LegalNotice/LegalNoticeView.spec.tsx
@@ -2,6 +2,10 @@ import React from 'react'
 import { shallow } from 'enzyme'
 import LegalNoticeView from 'components/LegalNotice/LegalNoticeView'
 
+jest.mock('components/Header/CozyBar', () => 'mock-cozybar')
+jest.mock('components/Header/Header', () => 'mock-header')
+jest.mock('components/Content/Content', () => 'mock-content')
+
 describe('LegalNoticeView component', () => {
   it('should be rendered correctly', () => {
     const component = shallow(<LegalNoticeView />).getElement()
diff --git a/src/components/LegalNotice/__snapshots__/LegalNoticeView.spec.tsx.snap b/src/components/LegalNotice/__snapshots__/LegalNoticeView.spec.tsx.snap
index 50fb84232..52b42634c 100644
--- a/src/components/LegalNotice/__snapshots__/LegalNoticeView.spec.tsx.snap
+++ b/src/components/LegalNotice/__snapshots__/LegalNoticeView.spec.tsx.snap
@@ -2,19 +2,19 @@
 
 exports[`LegalNoticeView component should be rendered correctly 1`] = `
 <React.Fragment>
-  <CozyBar
+  <mock-cozybar
     displayBackArrow={true}
     titleKey="common.title_legal_notice"
   />
-  <Header
+  <mock-header
     desktopTitleKey="common.title_legal_notice"
     displayBackArrow={true}
     setHeaderHeight={[Function]}
   />
-  <Content
+  <mock-content
     height={0}
   >
     <LegalNoticeContent />
-  </Content>
+  </mock-content>
 </React.Fragment>
 `;
diff --git a/src/components/Options/MatomoOptOut.spec.tsx b/src/components/Options/MatomoOptOut.spec.tsx
new file mode 100644
index 000000000..11ca22001
--- /dev/null
+++ b/src/components/Options/MatomoOptOut.spec.tsx
@@ -0,0 +1,20 @@
+import React from 'react'
+import { shallow } from 'enzyme'
+import { MatomoOptOut } from './MatomoOptOut'
+
+jest.mock('cozy-ui/transpiled/react/I18n', () => {
+  return {
+    useI18n: jest.fn(() => {
+      return {
+        t: (str: string) => str,
+      }
+    }),
+  }
+})
+
+describe('MatomoOptOut component', () => {
+  it('should be rendered correctly', () => {
+    const component = shallow(<MatomoOptOut />).getElement()
+    expect(component).toMatchSnapshot()
+  })
+})
diff --git a/src/components/Options/MatomoOptOut.tsx b/src/components/Options/MatomoOptOut.tsx
new file mode 100644
index 000000000..69e16ae49
--- /dev/null
+++ b/src/components/Options/MatomoOptOut.tsx
@@ -0,0 +1,24 @@
+import React from 'react'
+import { useI18n } from 'cozy-ui/transpiled/react/I18n'
+import './matomoOptOut.scss'
+
+declare let __PIWIK_TRACKER_URL__: string
+
+export const MatomoOptOut: React.FC = () => {
+  const { t } = useI18n()
+  const baseUrl = __PIWIK_TRACKER_URL__
+
+  return (
+    <div className="matomo-opt-out-container">
+      <div className="matomo-opt-out">
+        <div className="opt-out-header text-16-normal-uppercase">
+          {t('matomo.matomo_title')}
+        </div>
+        <iframe
+          style={{ height: '250px' }}
+          src={`${baseUrl}index.php?module=CoreAdminHome&action=optOut&language=fr&backgroundColor=121212&fontColor=e0e0e0&fontSize=&fontFamily=sans-serif`}
+        ></iframe>
+      </div>
+    </div>
+  )
+}
diff --git a/src/components/Options/OptionsView.spec.tsx b/src/components/Options/OptionsView.spec.tsx
index a1fc5688d..0f2f73c8d 100644
--- a/src/components/Options/OptionsView.spec.tsx
+++ b/src/components/Options/OptionsView.spec.tsx
@@ -2,9 +2,12 @@ import React from 'react'
 import { shallow } from 'enzyme'
 import OptionsView from 'components/Options/OptionsView'
 
+jest.mock('components/Header/CozyBar', () => 'mock-cozybar')
+jest.mock('components/Header/Header', () => 'mock-header')
+jest.mock('components/Content/Content', () => 'mock-content')
 jest.mock(
   'components/Konnector/KonnectorViewerList',
-  () => 'KonnectorViewerList'
+  () => 'mock-konnectorviewerlist'
 )
 
 describe('OptionsView component', () => {
diff --git a/src/components/Options/OptionsView.tsx b/src/components/Options/OptionsView.tsx
index a6fb18867..906e5c209 100644
--- a/src/components/Options/OptionsView.tsx
+++ b/src/components/Options/OptionsView.tsx
@@ -10,6 +10,7 @@ import Version from 'components/Version/Version'
 import ProfileTypeOptions from './ProfileTypeOptions'
 
 import logos from 'assets/png/logos.png'
+import { MatomoOptOut } from './MatomoOptOut'
 
 const OptionsView: React.FC = () => {
   const [headerHeight, setHeaderHeight] = useState<number>(0)
@@ -29,6 +30,7 @@ const OptionsView: React.FC = () => {
         <FAQLink />
         <LegalNoticeLink />
         <GCULink />
+        <MatomoOptOut />
         <div className="parameters-logos">
           <img src={logos} alt="ensemble de logos" />
         </div>
diff --git a/src/components/Options/UnSubscribe.spec.tsx b/src/components/Options/UnSubscribe.spec.tsx
index e77f60d6a..bd7b90d46 100644
--- a/src/components/Options/UnSubscribe.spec.tsx
+++ b/src/components/Options/UnSubscribe.spec.tsx
@@ -1,5 +1,6 @@
 import React from 'react'
 import { mount } from 'enzyme'
+import toJson from 'enzyme-to-json'
 import { Provider } from 'react-redux'
 import {
   createMockStore,
@@ -18,6 +19,9 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => {
     }),
   }
 })
+jest.mock('components/Header/CozyBar', () => 'mock-cozybar')
+jest.mock('components/Header/Header', () => 'mock-header')
+jest.mock('components/Content/Content', () => 'mock-content')
 
 const mockUpdateProfile = jest.fn()
 jest.mock('services/profile.service', () => {
@@ -27,7 +31,6 @@ jest.mock('services/profile.service', () => {
     }
   })
 })
-// const useDispatchSpy = jest.spyOn(reactRedux, 'useDispatch')
 const updateProfileSpy = jest.spyOn(profileActions, 'updateProfile')
 
 describe('UnSubscribe component', () => {
@@ -43,8 +46,8 @@ describe('UnSubscribe component', () => {
       <Provider store={store}>
         <UnSubscribe />
       </Provider>
-    ).getElement()
-    expect(wrapper).toMatchSnapshot()
+    )
+    expect(toJson(wrapper)).toMatchSnapshot()
   })
 
   it('should click on button and deactivate report', () => {
diff --git a/src/components/Options/__snapshots__/MatomoOptOut.spec.tsx.snap b/src/components/Options/__snapshots__/MatomoOptOut.spec.tsx.snap
new file mode 100644
index 000000000..599b28c0a
--- /dev/null
+++ b/src/components/Options/__snapshots__/MatomoOptOut.spec.tsx.snap
@@ -0,0 +1,25 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`MatomoOptOut component should be rendered correctly 1`] = `
+<div
+  className="matomo-opt-out-container"
+>
+  <div
+    className="matomo-opt-out"
+  >
+    <div
+      className="opt-out-header text-16-normal-uppercase"
+    >
+      matomo.matomo_title
+    </div>
+    <iframe
+      src="http://localhost:9800/index.php?module=CoreAdminHome&action=optOut&language=fr&backgroundColor=121212&fontColor=e0e0e0&fontSize=&fontFamily=sans-serif"
+      style={
+        Object {
+          "height": "250px",
+        }
+      }
+    />
+  </div>
+</div>
+`;
diff --git a/src/components/Options/__snapshots__/OptionsView.spec.tsx.snap b/src/components/Options/__snapshots__/OptionsView.spec.tsx.snap
index a77207e38..3cdb93ecb 100644
--- a/src/components/Options/__snapshots__/OptionsView.spec.tsx.snap
+++ b/src/components/Options/__snapshots__/OptionsView.spec.tsx.snap
@@ -2,14 +2,14 @@
 
 exports[`OptionsView component should be rendered correctly 1`] = `
 <React.Fragment>
-  <CozyBar
+  <mock-cozybar
     titleKey="common.title_options"
   />
-  <Header
+  <mock-header
     desktopTitleKey="common.title_options"
     setHeaderHeight={[Function]}
   />
-  <Content
+  <mock-content
     height={0}
   >
     <ProfileTypeOptions />
@@ -17,6 +17,7 @@ exports[`OptionsView component should be rendered correctly 1`] = `
     <FAQLink />
     <LegalNoticeLink />
     <GCULink />
+    <MatomoOptOut />
     <div
       className="parameters-logos"
     >
@@ -26,6 +27,6 @@ exports[`OptionsView component should be rendered correctly 1`] = `
       />
     </div>
     <Version />
-  </Content>
+  </mock-content>
 </React.Fragment>
 `;
diff --git a/src/components/Options/__snapshots__/UnSubscribe.spec.tsx.snap b/src/components/Options/__snapshots__/UnSubscribe.spec.tsx.snap
index fc7a7ac67..850bd8297 100644
--- a/src/components/Options/__snapshots__/UnSubscribe.spec.tsx.snap
+++ b/src/components/Options/__snapshots__/UnSubscribe.spec.tsx.snap
@@ -13,6 +13,202 @@ exports[`UnSubscribe component should be rendered correctly 1`] = `
     }
   }
 >
-  <UnSubscribe />
+  <UnSubscribe>
+    <mock-cozybar
+      titleKey="common.title_analysis"
+    />
+    <mock-header
+      desktopTitleKey="common.title_analysis"
+      setHeaderHeight={[Function]}
+    />
+    <mock-content
+      height={0}
+    >
+      <div
+        className="unsubscribe-container"
+      >
+        <StyledIcon
+          className="profile-icon"
+          icon="test-file-stub"
+          size={250}
+        >
+          <Icon
+            aria-hidden={true}
+            className="profile-icon"
+            icon="test-file-stub"
+            size={250}
+            spin={false}
+          >
+            <Component
+              aria-hidden={true}
+              className="profile-icon styles__icon___23x3R"
+              height={250}
+              style={Object {}}
+              width={250}
+            >
+              <svg
+                aria-hidden={true}
+                className="profile-icon styles__icon___23x3R"
+                height={250}
+                style={Object {}}
+                width={250}
+              >
+                <use
+                  xlinkHref="#test-file-stub"
+                />
+              </svg>
+            </Component>
+          </Icon>
+        </StyledIcon>
+        <div
+          className="text-20-bold head"
+        >
+          unsubscribe.title
+        </div>
+        <div
+          className="text-16-normal question"
+        >
+          unsubscribe.content
+        </div>
+        <WithStyles(ForwardRef(Button))
+          aria-label="unsubscribe.button_accessibility"
+          classes={
+            Object {
+              "label": "text-18-bold",
+              "root": "btn-highlight",
+            }
+          }
+          onClick={[Function]}
+          type="submit"
+          variant="contained"
+        >
+          <ForwardRef(Button)
+            aria-label="unsubscribe.button_accessibility"
+            classes={
+              Object {
+                "colorInherit": "MuiButton-colorInherit",
+                "contained": "MuiButton-contained",
+                "containedPrimary": "MuiButton-containedPrimary",
+                "containedSecondary": "MuiButton-containedSecondary",
+                "containedSizeLarge": "MuiButton-containedSizeLarge",
+                "containedSizeSmall": "MuiButton-containedSizeSmall",
+                "disableElevation": "MuiButton-disableElevation",
+                "disabled": "Mui-disabled",
+                "endIcon": "MuiButton-endIcon",
+                "focusVisible": "Mui-focusVisible",
+                "fullWidth": "MuiButton-fullWidth",
+                "iconSizeLarge": "MuiButton-iconSizeLarge",
+                "iconSizeMedium": "MuiButton-iconSizeMedium",
+                "iconSizeSmall": "MuiButton-iconSizeSmall",
+                "label": "MuiButton-label text-18-bold",
+                "outlined": "MuiButton-outlined",
+                "outlinedPrimary": "MuiButton-outlinedPrimary",
+                "outlinedSecondary": "MuiButton-outlinedSecondary",
+                "outlinedSizeLarge": "MuiButton-outlinedSizeLarge",
+                "outlinedSizeSmall": "MuiButton-outlinedSizeSmall",
+                "root": "MuiButton-root btn-highlight",
+                "sizeLarge": "MuiButton-sizeLarge",
+                "sizeSmall": "MuiButton-sizeSmall",
+                "startIcon": "MuiButton-startIcon",
+                "text": "MuiButton-text",
+                "textPrimary": "MuiButton-textPrimary",
+                "textSecondary": "MuiButton-textSecondary",
+                "textSizeLarge": "MuiButton-textSizeLarge",
+                "textSizeSmall": "MuiButton-textSizeSmall",
+              }
+            }
+            onClick={[Function]}
+            type="submit"
+            variant="contained"
+          >
+            <WithStyles(ForwardRef(ButtonBase))
+              aria-label="unsubscribe.button_accessibility"
+              className="MuiButton-root btn-highlight MuiButton-contained"
+              component="button"
+              disabled={false}
+              focusRipple={true}
+              focusVisibleClassName="Mui-focusVisible"
+              onClick={[Function]}
+              type="submit"
+            >
+              <ForwardRef(ButtonBase)
+                aria-label="unsubscribe.button_accessibility"
+                className="MuiButton-root btn-highlight MuiButton-contained"
+                classes={
+                  Object {
+                    "disabled": "Mui-disabled",
+                    "focusVisible": "Mui-focusVisible",
+                    "root": "MuiButtonBase-root",
+                  }
+                }
+                component="button"
+                disabled={false}
+                focusRipple={true}
+                focusVisibleClassName="Mui-focusVisible"
+                onClick={[Function]}
+                type="submit"
+              >
+                <button
+                  aria-label="unsubscribe.button_accessibility"
+                  className="MuiButtonBase-root MuiButton-root btn-highlight MuiButton-contained"
+                  disabled={false}
+                  onBlur={[Function]}
+                  onClick={[Function]}
+                  onDragLeave={[Function]}
+                  onFocus={[Function]}
+                  onKeyDown={[Function]}
+                  onKeyUp={[Function]}
+                  onMouseDown={[Function]}
+                  onMouseLeave={[Function]}
+                  onMouseUp={[Function]}
+                  onTouchEnd={[Function]}
+                  onTouchMove={[Function]}
+                  onTouchStart={[Function]}
+                  tabIndex={0}
+                  type="submit"
+                >
+                  <span
+                    className="MuiButton-label text-18-bold"
+                  >
+                    unsubscribe.button_text
+                  </span>
+                  <NoSsr>
+                    <WithStyles(memo)
+                      center={false}
+                    >
+                      <ForwardRef(TouchRipple)
+                        center={false}
+                        classes={
+                          Object {
+                            "child": "MuiTouchRipple-child",
+                            "childLeaving": "MuiTouchRipple-childLeaving",
+                            "childPulsate": "MuiTouchRipple-childPulsate",
+                            "ripple": "MuiTouchRipple-ripple",
+                            "ripplePulsate": "MuiTouchRipple-ripplePulsate",
+                            "rippleVisible": "MuiTouchRipple-rippleVisible",
+                            "root": "MuiTouchRipple-root",
+                          }
+                        }
+                      >
+                        <span
+                          className="MuiTouchRipple-root"
+                        >
+                          <TransitionGroup
+                            childFactory={[Function]}
+                            component={null}
+                            exit={true}
+                          />
+                        </span>
+                      </ForwardRef(TouchRipple)>
+                    </WithStyles(memo)>
+                  </NoSsr>
+                </button>
+              </ForwardRef(ButtonBase)>
+            </WithStyles(ForwardRef(ButtonBase))>
+          </ForwardRef(Button)>
+        </WithStyles(ForwardRef(Button))>
+      </div>
+    </mock-content>
+  </UnSubscribe>
 </Provider>
 `;
diff --git a/src/components/Options/matomoOptOut.scss b/src/components/Options/matomoOptOut.scss
new file mode 100644
index 000000000..184e9e44e
--- /dev/null
+++ b/src/components/Options/matomoOptOut.scss
@@ -0,0 +1,27 @@
+@import 'src/styles/base/color';
+@import 'src/styles/base/breakpoint';
+@import 'src/styles/base/typo-variables';
+
+.matomo-opt-out-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 0 1.5rem;
+  margin: 1rem 0;
+
+  .matomo-opt-out {
+    margin: 0 auto;
+    width: 100%;
+    @media (min-width: $width-large-phone) {
+      width: 45.75rem;
+    }
+    .opt-out-header {
+      color: $grey-bright;
+    }
+
+    * {
+      font-family: $text-font;
+    }
+  }
+}
diff --git a/src/components/ProfileType/ProfileTypeView.spec.tsx b/src/components/ProfileType/ProfileTypeView.spec.tsx
index 8bc146023..8c3385e1f 100644
--- a/src/components/ProfileType/ProfileTypeView.spec.tsx
+++ b/src/components/ProfileType/ProfileTypeView.spec.tsx
@@ -2,9 +2,6 @@ import React from 'react'
 import { Provider } from 'react-redux'
 import { mount } from 'enzyme'
 import ProfileTypeView from 'components/ProfileType/ProfileTypeView'
-import CozyBar from 'components/Header/CozyBar'
-import Header from 'components/Header/Header'
-import Content from 'components/Content/Content'
 import {
   createMockStore,
   mockInitialEcolyoState,
@@ -20,6 +17,10 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => {
   }
 })
 
+jest.mock('components/Header/CozyBar', () => 'mock-cozybar')
+jest.mock('components/Header/Header', () => 'mock-header')
+jest.mock('components/Content/Content', () => 'mock-content')
+
 describe('ProfileTypeView component', () => {
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   let store: any
@@ -33,8 +34,9 @@ describe('ProfileTypeView component', () => {
         <ProfileTypeView />
       </Provider>
     )
-    expect(wrapper.find(CozyBar)).toBeTruthy()
-    expect(wrapper.find(Header)).toBeTruthy()
-    expect(wrapper.find(Content)).toBeTruthy()
+    expect(wrapper.find('mock-cozybar').exists()).toBeTruthy()
+    expect(wrapper.find('mock-header').exists()).toBeTruthy()
+    expect(wrapper.find('mock-content').exists()).toBeTruthy()
+    expect(wrapper.find('.profile-type-container').exists()).toBeTruthy()
   })
 })
diff --git a/src/components/ProfileType/ProfileTypeView.tsx b/src/components/ProfileType/ProfileTypeView.tsx
index 8d43c4f1f..545ce04c3 100644
--- a/src/components/ProfileType/ProfileTypeView.tsx
+++ b/src/components/ProfileType/ProfileTypeView.tsx
@@ -16,11 +16,11 @@ import {
   IndividualOrCollective,
   OutsideFacingWalls,
   ProfileTypeFormType,
+  ProfileTypeStepForm,
   ThreeChoicesAnswer,
   WarmingType,
 } from 'enum/profileType.enum'
 import { FluidType } from 'enum/fluid.enum'
-import { ProfileTypeStepForm } from 'enum/profileType.enum'
 import ProfileTypeFormMultiChoice from 'components/ProfileType/ProfileTypeFormMultiChoice'
 import ProfileTypeFormNumber from 'components/ProfileType/ProfileTypeFormNumber'
 import ProfileTypeFormNumberSelection from 'components/ProfileType/ProfileTypeFormNumberSelection'
diff --git a/src/components/Quiz/QuizView.spec.tsx b/src/components/Quiz/QuizView.spec.tsx
index 788a3fe49..fec5d9281 100644
--- a/src/components/Quiz/QuizView.spec.tsx
+++ b/src/components/Quiz/QuizView.spec.tsx
@@ -9,6 +9,9 @@ import QuizBegin from './QuizBegin'
 import QuizQuestion from './QuizQuestion'
 import QuizFinish from './QuizFinish'
 
+jest.mock('components/Header/CozyBar', () => 'mock-cozybar')
+jest.mock('components/Header/Header', () => 'mock-header')
+jest.mock('components/Content/Content', () => 'mock-content')
 const mockUseSelector = jest.spyOn(reactRedux, 'useSelector')
 
 describe('QuizView component', () => {
@@ -23,7 +26,7 @@ describe('QuizView component', () => {
     }
     mockUseSelector.mockReturnValue(updatedChallengeState)
     const wrapper = shallow(<QuizView />)
-    expect(wrapper.find(QuizBegin).exists())
+    expect(wrapper.find(QuizBegin).exists()).toBeTruthy()
   })
 
   it('should be rendered with QuizQuestion component when quiz state = ongoing', () => {
@@ -37,7 +40,7 @@ describe('QuizView component', () => {
     }
     mockUseSelector.mockReturnValue(updatedChallengeState)
     const wrapper = shallow(<QuizView />)
-    expect(wrapper.find(QuizQuestion).exists())
+    expect(wrapper.find(QuizQuestion).exists()).toBeTruthy()
   })
 
   it('should be rendered with QuizFinish component when quiz state = done', () => {
@@ -51,7 +54,7 @@ describe('QuizView component', () => {
     }
     mockUseSelector.mockReturnValue(updatedChallengeState)
     const wrapper = shallow(<QuizView />)
-    expect(wrapper.find(QuizFinish).exists())
+    expect(wrapper.find(QuizFinish).exists()).toBeTruthy()
   })
 
   it('should be rendered with QuizBegin component when quiz state = null', () => {
@@ -65,6 +68,6 @@ describe('QuizView component', () => {
     }
     mockUseSelector.mockReturnValue(updatedChallengeState)
     const wrapper = shallow(<QuizView />)
-    expect(wrapper.find(QuizBegin).exists())
+    expect(wrapper.find(QuizBegin).exists()).toBeTruthy()
   })
 })
diff --git a/src/locales/fr.json b/src/locales/fr.json
index beadc6d12..390739d58 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -676,6 +676,9 @@
     "title": "Conditions générales d’utilisation",
     "read_gcu": "Lire les CGU"
   },
+  "matomo": {
+    "matomo_title": "Suivi statistiques d'usage Matomo"
+  },
   "header": {
     "accessibility": {
       "button_back": "Retour à la page précédente",
diff --git a/src/services/environment.service.ts b/src/services/environment.service.ts
index 0fb89f8f4..116c1bcc9 100644
--- a/src/services/environment.service.ts
+++ b/src/services/environment.service.ts
@@ -1,4 +1,5 @@
 declare const __IS_ALPHA__: boolean
+declare const __DEVELOPMENT__: boolean
 
 export default class EnvironmentService {
   private isAlpha() {
@@ -21,4 +22,8 @@ export default class EnvironmentService {
       return 'https://ecolyo-agent-rec.grandlyon.com'
     }
   }
+
+  public isLocal() {
+    return __DEVELOPMENT__
+  }
 }
diff --git a/src/targets/browser/index.ejs b/src/targets/browser/index.ejs
index 3f5bf59a4..a30958e2e 100644
--- a/src/targets/browser/index.ejs
+++ b/src/targets/browser/index.ejs
@@ -28,25 +28,19 @@
     {{.ThemeCSS}}
 
     <% if (__TARGET__ === 'mobile') { %>
-
     <meta name="format-detection" content="telephone=no">
     <script src="cordova.js" defer></script>
     <% } else if (__STACK_ASSETS__) { %>
     {{.CozyBar}}
     <% } %>
+    <script src="//{{.Domain}}/assets/js/piwik.js"></script>
+
   </head>
   <body>
   <div
     role="application"
     class="application"
-    data-cozy-token="{{.Token}}"
-    data-cozy-domain="{{.Domain}}"
-    data-cozy-locale="{{.Locale}}"
-    data-cozy-app-name="{{.AppName}}"
-    data-cozy-app-slug="{{.AppSlug}}"
-    data-cozy-app-name-prefix="{{.AppNamePrefix}}"
-    data-cozy-app-editor="{{.AppEditor}}"
-    data-cozy-icon-path="{{.IconPath}}"
+    data-cozy="{{.CozyData}}"
   >
   <% _.forEach(htmlWebpackPlugin.files.js, function(file) { %>
       <script src="<%- file %>"></script>
diff --git a/src/targets/browser/index.tsx b/src/targets/browser/index.tsx
index 8337a39ba..93fcbc519 100644
--- a/src/targets/browser/index.tsx
+++ b/src/targets/browser/index.tsx
@@ -1,50 +1,91 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 /* eslint-disable @typescript-eslint/no-var-requires */
 /* global cozy */
+declare let cozy: any
+declare let __PIWIK_TRACKER_URL__: string
+declare let __PIWIK_SITEID__: number
+declare let Piwik: any
+
 import '../../styles/index.scss'
 
 import React from 'react'
 import { render } from 'react-dom'
-import { Client, CozyProvider } from 'cozy-client'
+import CozyClient, { Client, CozyProvider } from 'cozy-client'
 import { Provider } from 'react-redux'
 import configureStore from 'store'
 import { I18n, initTranslation } from 'cozy-ui/transpiled/react/I18n'
 import { handleOAuthResponse } from 'cozy-harvest-lib/dist/helpers/oauth'
 import { memoize } from 'lodash'
-import { getClient } from '../../utils/client'
-import { getValues, initBar } from '../../utils/bar'
+import manifest from '../../../manifest.webapp'
+import schema from 'doctypes'
+import { createBrowserHistory, History } from 'history'
+import { HashRouter } from 'react-router-dom'
+import MatomoTracker from 'utils/matomoTracker'
 
 const setupApp = memoize(() => {
+  const history: History = createBrowserHistory()
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
   const root: any = document.querySelector('[role=application]')
-  const { lang }: any = getValues(root.dataset)
-  const polyglot: any = initTranslation(lang, lang =>
+  const data = JSON.parse(root.dataset.cozy)
+  const protocol = window.location.protocol
+  const cozyUrl = `${protocol}//${data.domain}`
+
+  const locale = 'fr'
+  const polyglot: any = initTranslation(locale, lang =>
     require(`locales/${lang}`)
   )
-  const client: Client = getClient()
+
+  const client: Client = new CozyClient({
+    uri: cozyUrl,
+    token: data.token,
+    appMetadata: {
+      slug: manifest.name,
+      version: manifest.version,
+    },
+    schema,
+  })
+
   const persistedState: any = {}
   const store: any = configureStore(client, persistedState)
 
-  /** I don't know why I need to for this... But if I don't it seems that
-   * we have a race between configureStore and initBar resulting in
-   * an error from cozy-client "store is already defined"
-   */
-  setTimeout(() => {
-    initBar(client)
-  }, 0)
+  cozy.bar.init({
+    appName: data.app.name,
+    appEditor: data.app.editor,
+    cozyClient: client,
+    iconPath: data.app.icon,
+    lang: data.locale,
+    replaceTitleOnMobile: false,
+    appSlug: data.app.slug,
+    appNamePrefix: data.app.prefix,
+  })
+
+  let tracker
+  if (window.Piwik) {
+    Piwik.getTracker()
+    tracker = new MatomoTracker({
+      cozyUrl: cozyUrl,
+      url: __PIWIK_TRACKER_URL__,
+      siteId: __PIWIK_SITEID__,
+      history: history,
+      phpFilename: 'matomo.php',
+    })
+  }
 
-  return { root, store, client, lang, polyglot }
+  return { root, store, client, locale, polyglot, history, tracker }
 })
 
 const init = () => {
-  const { root, store, client, lang, polyglot } = setupApp()
+  const { root, store, client, locale, polyglot, history, tracker } = setupApp()
 
   if (handleOAuthResponse()) return
   const App = require('components/App').default
   render(
     <Provider store={store}>
       <CozyProvider client={client}>
-        <I18n lang={lang} polyglot={polyglot}>
-          <App />
+        <I18n lang={locale} polyglot={polyglot}>
+          <HashRouter {...history}>
+            <App tracker={tracker} />
+          </HashRouter>
         </I18n>
       </CozyProvider>
     </Provider>,
diff --git a/src/targets/vendor/assets/serviceWorker.js b/src/targets/vendor/assets/serviceWorker.js
index ba1c298b7..0f14f5b13 100644
--- a/src/targets/vendor/assets/serviceWorker.js
+++ b/src/targets/vendor/assets/serviceWorker.js
@@ -12,6 +12,10 @@ self.addEventListener('install', event => {
 
 // Listen for requests
 self.addEventListener('fetch', event => {
+  if (event.request.url.indexOf('statweb') !== -1) {
+    return false
+  }
+
   event.respondWith(
     caches.match(event.request).then(() => {
       return fetch(event.request).catch(() => caches.match('offline.html'))
diff --git a/src/types/cozy-ui.d.ts b/src/types/cozy-ui.d.ts
index 01a043d29..a13bfbd3f 100644
--- a/src/types/cozy-ui.d.ts
+++ b/src/types/cozy-ui.d.ts
@@ -3,6 +3,7 @@
 declare module 'cozy-ui/transpiled/react/Icon'
 declare module 'cozy-ui/transpiled/react/Spinner'
 declare module 'cozy-ui/transpiled/react/Layout'
+declare module 'cozy-ui/transpiled/react/helpers/appDataset'
 
 declare module 'cozy-ui/transpiled/react/I18n' {
   interface IPropsIcon {
diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts
index 0016563a7..7112effa6 100644
--- a/src/types/custom.d.ts
+++ b/src/types/custom.d.ts
@@ -6,3 +6,5 @@ declare module '*.svg' {
   const content: string
   export default content
 }
+
+declare module '*.webapp'
diff --git a/src/utils/bar.ts b/src/utils/bar.ts
deleted file mode 100644
index ef818bc84..000000000
--- a/src/utils/bar.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-/* global cozy */
-
-import { Client } from 'cozy-client'
-import manifest from '../../manifest.webapp'
-
-interface GetValues {
-  cozyAppName: string
-  cozyAppNamePrefix: string
-  cozyIconPath: string
-  cozyLocale: string
-}
-
-const getDataOrDefault = (data: any, defaultData: any) =>
-  /^\{\{\..*\}\}$/.test(data) ? defaultData : data //NOSONAR
-
-/**
- * default data will allow to display correctly the cozy-bar
- * in the standalone (without cozy-stack connexion)
- */
-export const getValues = ({
-  cozyAppName,
-  cozyAppNamePrefix,
-  cozyIconPath,
-}: GetValues) => {
-  const defaultValues = {
-    appIconDefault: require('../targets/vendor/assets/icon.svg'),
-    appNamePrefixDefault: manifest.name_prefix,
-    appNameDefault: manifest.name,
-    appLocaleDefault: 'fr',
-  }
-  return {
-    appName: getDataOrDefault(cozyAppName, defaultValues.appNameDefault),
-    appNamePrefix: getDataOrDefault(
-      cozyAppNamePrefix,
-      defaultValues.appNamePrefixDefault
-    ),
-    iconPath: getDataOrDefault(cozyIconPath, defaultValues.appIconDefault),
-    lang: defaultValues.appLocaleDefault,
-  }
-}
-
-/**
- * Cozy bar initialization
- * @param {object} client - cozy client
- */
-export const initBar = (client: Client) => {
-  const root: any = document.querySelector('[role=application]')
-  const { appName, appNamePrefix, iconPath, lang } = getValues(root.dataset)
-
-  cozy.bar.init({
-    appName,
-    appNamePrefix,
-    cozyClient: client,
-    iconPath,
-    lang,
-    replaceTitleOnMobile: false,
-  })
-}
diff --git a/src/utils/client.ts b/src/utils/client.ts
deleted file mode 100644
index c5826f38a..000000000
--- a/src/utils/client.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import CozyClient from 'cozy-client'
-import manifest from '../../manifest.webapp'
-import schema from 'doctypes'
-
-/**
- * Returns cozy client instance
- * @returns {object} cozy client instance
- */
-export const getClient = () => {
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  const root: any = document.querySelector('[role=application]')
-  const data = root.dataset
-  const protocol = window.location.protocol
-  const cozyUrl = `${protocol}//${data.cozyDomain}`
-
-  return new CozyClient({
-    uri: cozyUrl,
-    token: data.cozyToken,
-    appMetadata: {
-      slug: manifest.name,
-      version: manifest.version,
-    },
-    schema,
-  })
-}
diff --git a/src/utils/matomoTracker.ts b/src/utils/matomoTracker.ts
new file mode 100644
index 000000000..1afb34486
--- /dev/null
+++ b/src/utils/matomoTracker.ts
@@ -0,0 +1,109 @@
+import { History, Location, UnregisterCallback } from 'history'
+import { readCozyDataFromDOM } from 'cozy-ui/transpiled/react/helpers/appDataset'
+
+interface InitSettings {
+  cozyUrl: string
+  url: string
+  siteId: number
+  history: History
+  phpFilename?: string
+}
+
+declare global {
+  interface Window {
+    _paq: any
+    Piwik: any
+  }
+}
+
+export default class MatomoTracker {
+  cozyUrl: string
+  url: string
+  siteId: number
+  phpFilename: string
+  history: History
+  unlistenFromHistory: UnregisterCallback
+
+  constructor({
+    cozyUrl,
+    url,
+    siteId,
+    history,
+    phpFilename = 'matomo.php',
+  }: InitSettings) {
+    if (url === undefined || siteId === undefined) {
+      throw new Error(
+        'MatomoTracker cannot be initialized! SiteId and url are mandatory.'
+      )
+    }
+    this.cozyUrl = cozyUrl
+    this.url = url
+    this.siteId = siteId
+    this.phpFilename = phpFilename
+    this.history = history
+    this.unlistenFromHistory = () => null
+    this.init()
+  }
+
+  init() {
+    if (typeof window !== 'undefined') {
+      window._paq = window._paq || []
+      MatomoTracker.push(['enableHeartBeatTimer', 30])
+      MatomoTracker.push(['setSiteId', this.siteId])
+      MatomoTracker.push(['setReferrerUrl', 'https://ecolyo.com'])
+      MatomoTracker.push(['setTrackerUrl', `${this.url + this.phpFilename}`])
+      MatomoTracker.push(['enableLinkTracking'])
+    }
+    return {
+      push: MatomoTracker.push,
+      track: this.track,
+      connectToHistory: this.connectToHistory,
+      disconnectFromHistory: this.disconnectFromHistory,
+    }
+  }
+
+  static push(args: (number[] | string[] | number | string)[]) {
+    window._paq.push(args)
+  }
+
+  configure() {
+    let cozyDomain
+    let userId
+    const root: any = document.querySelector('[role=application]')
+    if (root && root.dataset) {
+      cozyDomain = readCozyDataFromDOM('cozyDomain')
+    }
+    if (cozyDomain) {
+      userId = cozyDomain
+      const indexOfPort = cozyDomain.indexOf(':')
+      if (indexOfPort >= 0) {
+        userId = userId.substring(0, indexOfPort)
+      }
+    }
+  }
+
+  connectToHistory() {
+    this.unlistenFromHistory = this.history.listen(loc => {
+      this.track(loc)
+    })
+  }
+
+  disconnectFromHistory() {
+    if (this.unlistenFromHistory) {
+      this.unlistenFromHistory()
+      return true
+    }
+    return false
+  }
+
+  track(loc: Location) {
+    if (typeof window === 'undefined') {
+      return
+    }
+    const currentPath = loc.hash.substring(1)
+
+    MatomoTracker.push(['setDocumentTitle', currentPath.substring(1)])
+    MatomoTracker.push(['setCustomUrl', 'https://ecolyo.com' + currentPath])
+    MatomoTracker.push(['trackPageView'])
+  }
+}
-- 
GitLab