diff --git a/.gitlab/merge_request_templates/default.md b/.gitlab/merge_request_templates/default.md index 8b164c75d09c71ee45fb7e18edcc3d268c717c7f..694ccc6881c54e3bb0f0854f2a6c6d8f51d24ce3 100644 --- a/.gitlab/merge_request_templates/default.md +++ b/.gitlab/merge_request_templates/default.md @@ -1,9 +1,9 @@ # Related to #000 -| :triangular_flag_on_post: Give your MR title the same name that the desired squash commit. In doubt, check the conventional commit [doc][conventional-commits]. examples | -| --- | -| **feat(profile)**: add... | -| **fix(annuaire)**: remove... | +| :triangular_flag_on_post: Give your MR title the same name that the desired squash commit. Check the [conventional commit documentation][conventional-commits]. Examples : | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **feat(profile)**: add... | +| **fix(annuaire)**: remove... | ## What does this MR do and why? @@ -21,6 +21,8 @@ _List all steps to set up and validate the changes on local environment._ _To be completed by the chosen reviewer._ +Follow the [conventional comments][conventional-comments] for easier communication and intention ! + <!--- Using checklists improves quality in software engineering and other jobs such as with surgeons and airline pilots. More reading on checklists can be found in the "Checklist Manifesto": http://atulgawande.com/book/the-checklist-manifesto/ @@ -28,7 +30,18 @@ More reading on checklists can be found in the "Checklist Manifesto": http://atu "It is common to misconceive how checklists function in complex lines of work. They are not comprehensive how-to guides, whether for building a skyscraper or getting a plane out of trouble. They are quick and simple tools aimed to buttress the skills of expert professionals." - Gawande, Atul. The Checklist Manifesto ---> -### Quality [](https://sonarqube.forge.grandlyon.com/dashboard?id=ecolyo-mr) - [](https://sonarqube.forge.grandlyon.com/dashboard?id=ecolyo-mr) +### Quality + +#### Bugs & smells summary + +_đ Re-run Quality job to update_ + +| Current MR | Dev | +| ----------------------------------------- | ------------------------------------------- | +| [![Bugs][bugs_mr]][dashboard_mr] | [![Bugs][bugs_dev]][dashboard_dev] | +| [![Code Smells][smells_mr]][dashboard_mr] | [![Code Smells][smells_dev]][dashboard_dev] | + +#### Quality checklist - For the code that this change impacts, I believe that the **automated tests validate functionality** that is **highly important to users**. If the existing automated tests do not cover this functionality, I have **added the necessary additional tests** or I have added an issue to describe the automation testing gap and linked it to this MR. - I have made sure that the **sonar quality coverage is up to standards**. @@ -55,4 +68,10 @@ More reading on checklists can be found in the "Checklist Manifesto": http://atu - When featured on a self-data project release, I have made sure my **app version** in the manifest and package.json is **incremented** and any relative **changes to the permissions are clearly written and transmitted to Cozy**. [conventional-commits]: https://www.conventionalcommits.org/en/v1.0.0/ - +[conventional-comments]: https://conventionalcomments.org/ +[bugs_mr]: https://sonarqube.forge.grandlyon.com/api/project_badges/measure?project=ecolyo-mr&metric=bugs&token=3d678f5d0b1736ae2a81986f8bf3bcb32672231c +[bugs_dev]: https://sonarqube.forge.grandlyon.com/api/project_badges/measure?project=ecolyo&metric=bugs&token=95187318baacb2c9aeab7c8f3046b4b66f08e9b1 +[smells_mr]: https://sonarqube.forge.grandlyon.com/api/project_badges/measure?project=ecolyo-mr&metric=code_smells&token=3d678f5d0b1736ae2a81986f8bf3bcb32672231c +[smells_dev]: https://sonarqube.forge.grandlyon.com/api/project_badges/measure?project=ecolyo&metric=code_smells&token=95187318baacb2c9aeab7c8f3046b4b66f08e9b1 +[dashboard_mr]: https://sonarqube.forge.grandlyon.com/dashboard?id=ecolyo-mr +[dashboard_dev]: https://sonarqube.forge.grandlyon.com/dashboard?id=ecolyo diff --git a/.vscode/settings.json b/.vscode/settings.json index 471272b3125f5203b5ad76980906e1113e308e7d..b3f02083321ce59814f15b98a5ef2b92aa387f21 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -80,6 +80,7 @@ "eglgrandlyon", "elec", "Ă©lec", + "emas", "enedis", "Enedis", "ENEDIS", @@ -89,6 +90,7 @@ "firstname", "fluidchart", "fluidchartslide", + "FLUIDNAME", "fluidsprices", "fluidtype", "Folinge", @@ -100,6 +102,7 @@ "grdfgrandlyon", "Hypervitesse", "kĂ©sako", + "kmodal", "Konnected", "konnector", "konnectors", @@ -125,6 +128,7 @@ "notif", "numerique", "oeufs", + "pefs", "picto", "Picto", "PIV'EAU", @@ -147,12 +151,13 @@ "timestep", "TIMESTEP", "timesteps", - "UNCOMING", "Unstarted", "UNSTARTED", "usageevent", "Usain", - "userchallenge" + "userchallenge", + "weektype" ], - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "conventionalCommits.scopes": ["ui", "ecogestures"] } diff --git a/app.config.environment.alpha.js b/app.config.environment.alpha.js index 346a1c982aca46a5971ea7cfa8d91eceb0b32876..e3fe4c88a600f589777550e4dd9136b5ac6fabd2 100644 --- a/app.config.environment.alpha.js +++ b/app.config.environment.alpha.js @@ -19,22 +19,25 @@ module.exports = { __STACK_ASSETS__: target !== 'mobile', __PIWIK_TRACKER_URL__: JSON.stringify('https://statweb.grandlyon.com/'), __PIWIK_SITEID__: 117, - __SENTRY_DSN__: JSON.stringify( - 'https://c868f6010f3f431d95be8f70d7f37666@grandlyon.errors.cozycloud.cc/6' - ), __SAU_LINK__: JSON.stringify( 'https://portail-citoyen-sau.guichet-recette.grandlyon.com/ecolyo/' ), __SAU_IDEA_DIRECT_LINK__: JSON.stringify( 'https://demarches-sau.guichet-recette.grandlyon.com/retour-ecolyo/ecolyo-une-idee/' ), + __SAU_ISSUE_DIRECT_LINK__: JSON.stringify( + 'https://demarches-sau.guichet-recette.grandlyon.com/retour-ecolyo/ecolyo-un-probleme/' + ), + __SENTRY_DSN__: JSON.stringify( + 'https://c868f6010f3f431d95be8f70d7f37666@grandlyon.errors.cozycloud.cc/6' + ), }), ], optimization: { minimizer: [ new TerserPlugin({ parallel: true, - //To fix a SAfari 10 bug : https://github.com/zeit/next.js/issues/5630 + // To fix a SAfari 10 bug : https://github.com/zeit/next.js/issues/5630 terserOptions: { safari10: true, }, diff --git a/app.config.environment.dev.js b/app.config.environment.dev.js index 120aa2a2de67d54d8631f0eddd1dfe898241a24d..e8326334e093c0ec4fe7b203e422945e81ce0fe3 100644 --- a/app.config.environment.dev.js +++ b/app.config.environment.dev.js @@ -29,6 +29,9 @@ const stackProvidedLibsConfig = { __SAU_IDEA_DIRECT_LINK__: JSON.stringify( 'https://demarches-sau.guichet-recette.grandlyon.com/retour-ecolyo/ecolyo-une-idee/' ), + __SAU_ISSUE_DIRECT_LINK__: JSON.stringify( + 'https://demarches-sau.guichet-recette.grandlyon.com/retour-ecolyo/ecolyo-un-probleme/' + ), __SENTRY_DSN__: JSON.stringify( 'https://c868f6010f3f431d95be8f70d7f37666@grandlyon.errors.cozycloud.cc/6' ), diff --git a/app.config.environment.prod.js b/app.config.environment.prod.js index 6688983f2ee64a0289d0d71c5b218a6b26440948..b6b24c48cda1b9a04270be6e6f9e13af064f1fd7 100644 --- a/app.config.environment.prod.js +++ b/app.config.environment.prod.js @@ -23,6 +23,9 @@ module.exports = { __SAU_IDEA_DIRECT_LINK__: JSON.stringify( 'https://demarches-support.grandlyon.com/retour-ecolyo/ecolyo-une-idee/' ), + __SAU_ISSUE_DIRECT_LINK__: JSON.stringify( + 'https://demarches-support.grandlyon.com/retour-ecolyo/ecolyo-un-probleme/' + ), __SENTRY_DSN__: JSON.stringify( 'https://c868f6010f3f431d95be8f70d7f37666@grandlyon.errors.cozycloud.cc/6' ), @@ -32,7 +35,7 @@ module.exports = { minimizer: [ new TerserPlugin({ parallel: true, - //To fix a SAfari 10 bug : https://github.com/zeit/next.js/issues/5630 + // To fix a SAfari 10 bug : https://github.com/zeit/next.js/issues/5630 terserOptions: { safari10: true, }, diff --git a/manifest.webapp b/manifest.webapp index 37f256c9d8be3c63a3d719a68302624c8180dee5..9b8765136d8184fcee9df429554880e53eea2980 100644 --- a/manifest.webapp +++ b/manifest.webapp @@ -243,6 +243,10 @@ "type": "org.ecolyo.dju_v3", "verbs": ["GET"] }, + "ecolyo-avg-temperature": { + "type": "org.ecolyo.avg-temperature", + "verbs": ["GET"] + }, "dacc": { "type": "cc.cozycloud.dacc_v2", "verbs": ["ALL"] diff --git a/package.json b/package.json index ef2e8b6e378b435ddd2f88e4347d2cbe17331628..cec9c047f404ecb4241fcf5f29c3f4686e7e2980 100644 --- a/package.json +++ b/package.json @@ -86,9 +86,9 @@ "devDependencies": { "@babel/preset-typescript": "^7.7.4", "@testing-library/dom": "^9.3.3", - "@testing-library/jest-dom": "^6.1.4", - "@testing-library/react": "^14.1.2", - "@testing-library/user-event": "^14.5.1", + "@testing-library/jest-dom": "^6.4.2", + "@testing-library/react": "^14.3.0", + "@testing-library/user-event": "^14.5.2", "@types/d3": "^6.0.0", "@types/file-saver": "^2.0.5", "@types/history": "^5.0.0", diff --git a/scripts/createConnections.js b/scripts/createConnections.js index 728531c3b77fe2b8c8aaeabd37294406c72348a5..80a42cca29c97e4b56491e2be571224144e4e3ff 100644 --- a/scripts/createConnections.js +++ b/scripts/createConnections.js @@ -9,7 +9,11 @@ const headers = { 'content-type': 'application/json', } +const COZY_PREFIX = 'cozy35ba44d2d1749e6f21646edce51e7190' + +const ENEDIS_ACCOUNT_ID = '70e68b8450cee09fe2f077610901094d' const dataEnedisAccount = JSON.stringify({ + id: ENEDIS_ACCOUNT_ID, account_type: 'enedissgegrandlyon', name: '', auth: { @@ -25,31 +29,32 @@ const dataEnedisAccount = JSON.stringify({ expirationDate: '2023-09-26', offPeakHours: '22H00-6H00', }, - id: '70e68b8450cee09fe2f077610901094d', identifier: 'address', state: null, }) +const GRDF_ACCOUNT_ID = '89e68b8450cee09fe2f077610901094d' const dataGrdfAccount = JSON.stringify({ + id: GRDF_ACCOUNT_ID, account_type: 'grdfgrandlyon', auth: { credentials_encrypted: 'bmFjbMKrNCS+4Liakxdu+xNu9I3sSyvda8iAp0o3U3OAymbIeoLhLtxPdsa+3mu/8yTnDudBcJo=', login: 'test', }, - id: '89e68b8450cee09fe2f077610901094d', identifier: 'login', state: null, }) +const EGL_ACCOUNT_ID = '90e68b8450cee09fe2f077610901094d' const dataEglAccount = JSON.stringify({ + id: EGL_ACCOUNT_ID, account_type: 'eglgrandlyon', auth: { credentials_encrypted: 'bmFjbHI5OoL+VNCT6JDFYea1dNiBGGNJM1zY0M4uWcjhALJcQT9uk9p9WPD7+1OryCAoYf9eaSE=', login: 'test', }, - id: '90e68b8450cee09fe2f077610901094d', identifier: 'login', state: null, }) @@ -59,14 +64,14 @@ const dataEnedisTrigger = JSON.stringify({ attributes: { _id: '3ed832cec67e6e0b2c6382edd30df11c', domain: 'cozy.tools:8080', - prefix: 'cozy35ba44d2d1749e6f21646edce51e7190', + prefix: COZY_PREFIX, type: '@cron', worker: 'konnector', arguments: '0 47 8 * * *', debounce: '', options: null, message: { - account: '70e68b8450cee09fe2f077610901094d', + account: ENEDIS_ACCOUNT_ID, konnector: 'enedissgegrandlyon', }, cozyMetadata: { @@ -85,14 +90,14 @@ const dataGrdfTrigger = JSON.stringify({ attributes: { _id: '4ed832cec67e6e0b2c6382edd30df11c', domain: 'cozy.tools:8080', - prefix: 'cozy35ba44d2d1749e6f21646edce51e7190', + prefix: COZY_PREFIX, type: '@cron', worker: 'konnector', arguments: '0 47 8 * * *', debounce: '', options: null, message: { - account: '89e68b8450cee09fe2f077610901094d', + account: GRDF_ACCOUNT_ID, konnector: 'grdfgrandlyon', }, cozyMetadata: { @@ -111,14 +116,14 @@ const dataEglTrigger = JSON.stringify({ attributes: { _id: '5ed832cec67e6e0b2c6382edd30df11c', domain: 'cozy.tools:8080', - prefix: 'cozy35ba44d2d1749e6f21646edce51e7190', + prefix: COZY_PREFIX, type: '@cron', worker: 'konnector', arguments: '0 47 8 * * *', debounce: '', options: null, message: { - account: '90e68b8450cee09fe2f077610901094d', + account: EGL_ACCOUNT_ID, konnector: 'eglgrandlyon', }, cozyMetadata: { @@ -136,7 +141,7 @@ async function launch() { // Enedis await axios({ method: 'put', - url: 'http://cozy.tools:8080/data/io.cozy.accounts/70e68b8450cee09fe2f077610901094d', + url: `http://cozy.tools:8080/data/io.cozy.accounts/${ENEDIS_ACCOUNT_ID}`, headers: headers, data: dataEnedisAccount, }) @@ -163,7 +168,7 @@ async function launch() { // GRDF await axios({ method: 'put', - url: 'http://cozy.tools:8080/data/io.cozy.accounts/89e68b8450cee09fe2f077610901094d', + url: `http://cozy.tools:8080/data/io.cozy.accounts/${GRDF_ACCOUNT_ID}`, headers: headers, data: dataGrdfAccount, }) @@ -190,7 +195,7 @@ async function launch() { // EGL await axios({ method: 'put', - url: 'http://cozy.tools:8080/data/io.cozy.accounts/90e68b8450cee09fe2f077610901094d', + url: `http://cozy.tools:8080/data/io.cozy.accounts/${EGL_ACCOUNT_ID}`, headers: headers, data: dataEglAccount, }) diff --git a/src/assets/icons/ico/exclamationMark.svg b/src/assets/icons/ico/exclamationMark.svg new file mode 100644 index 0000000000000000000000000000000000000000..987c0f422467e601221a6d5cc1cc389f30716d69 --- /dev/null +++ b/src/assets/icons/ico/exclamationMark.svg @@ -0,0 +1,28 @@ +<svg + viewBox="0 0 16 16" + version="1.1" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + <circle + cx="7.8000002" + cy="7.8000002" + r="7.3000002" + id="circle2" + style="fill:none;" /> + <rect + x="7.1999998" + y="7.1999998" + width="1.2" + height="5.4000001" + rx="0.60000002" + id="rect4" + style="fill:#1b1c22" /> + <rect + x="7.1999998" + y="3.5999904" + width="1.2" + height="1.8" + rx="0.60000002" + id="rect6" + style="fill:#1b1c22" /> +</svg> diff --git a/src/assets/icons/visu/equipments/OUTSIDE.svg b/src/assets/icons/visu/equipments/OUTSIDE.svg new file mode 100644 index 0000000000000000000000000000000000000000..5529a9c0e29e34e55646c5c1a670fb1c231f4529 --- /dev/null +++ b/src/assets/icons/visu/equipments/OUTSIDE.svg @@ -0,0 +1,6 @@ +<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M25.7714 6.27598L25.6335 6.42525C25.0024 7.11492 24.6132 7.50794 24.292 7.76302C23.9865 7.36436 23.6835 6.96378 23.3831 6.5613C22.837 5.85463 22.3836 5.34257 21.9187 4.95145C21.3519 4.47152 20.7888 4.19565 20.1823 4.19565C19.863 4.19565 19.5531 4.27501 19.2546 4.4205C18.5233 4.77573 17.9111 5.44272 17.0911 6.58964C16.9872 6.73324 16.7283 7.10358 16.5734 7.32654L16.4543 7.4966L16.254 7.77435L16.2238 7.75168C15.8856 7.49282 15.4793 7.09791 14.8086 6.39691L14.6782 6.26087C13.6711 5.20275 13.3971 4.925 12.9758 4.58111C12.6319 4.30146 12.3352 4.12196 12.0121 4.0426C11.7173 3.96167 11.4027 3.9969 11.1331 4.14102C10.8634 4.28515 10.6594 4.52719 10.5629 4.81729C10.0481 6.24554 9.7866 7.75267 9.79007 9.27083C9.79007 16.2355 13.5993 21.0084 20.1823 21.0084C26.7653 21.0084 30.5745 16.2355 30.5745 9.27083C30.578 7.7525 30.3158 6.24527 29.7998 4.81729C29.7042 4.52714 29.5004 4.28497 29.2309 4.14107C28.9614 3.99716 28.6468 3.96258 28.3525 4.04449C28.0313 4.12574 27.7403 4.30902 27.4077 4.59244C26.9996 4.93822 26.7464 5.20653 25.7714 6.27409V6.27598Z" fill="#121212"/> +<path d="M19 19.1333C19 18.8328 19.158 18.5445 19.4393 18.3319C19.7206 18.1194 20.1022 18 20.5 18C20.8978 18 21.2794 18.1194 21.5607 18.3319C21.842 18.5445 22 18.8328 22 19.1333V33.8667C22 34.1672 21.842 34.4555 21.5607 34.6681C21.2794 34.8806 20.8978 35 20.5 35C20.1022 35 19.7206 34.8806 19.4393 34.6681C19.158 34.4555 19 34.1672 19 33.8667V19.1333Z" fill="#121212"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M31.3446 26.7304C31.2269 26.6705 30.8617 26.5428 30.0185 26.6696C28.8316 26.8482 27.2528 27.4993 25.6937 28.7196C24.1346 29.9399 23.1233 31.316 22.6648 32.4252C22.3391 33.2132 22.3753 33.5985 22.4052 33.727C22.5228 33.7869 22.8881 33.9147 23.7313 33.7878C24.9181 33.6093 26.4969 32.9581 28.056 31.7378C29.6151 30.5176 30.6265 29.1414 31.085 28.0322C31.4107 27.2442 31.3744 26.859 31.3446 26.7304ZM29.9051 34.1003C33.6853 31.1416 35.3931 27.0097 33.7196 24.8715C32.0461 22.7333 27.6249 23.3985 23.8447 26.3572C20.0645 29.3159 18.3567 33.4477 20.0302 35.5859C21.7037 37.7241 26.1248 37.059 29.9051 34.1003Z" fill="#121212"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M9.40545 26.7304C9.52306 26.6705 9.88835 26.5428 10.7315 26.6696C11.9184 26.8482 13.4972 27.4993 15.0563 28.7196C16.6154 29.9399 17.6267 31.316 18.0852 32.4252C18.4109 33.2132 18.3747 33.5985 18.3448 33.727C18.2272 33.7869 17.8619 33.9147 17.0187 33.7878C15.8319 33.6093 14.2531 32.9581 12.694 31.7378C11.1349 30.5176 10.1235 29.1414 9.66503 28.0322C9.33932 27.2442 9.37555 26.859 9.40545 26.7304ZM10.8449 34.1003C7.06472 31.1416 5.3569 27.0097 7.03042 24.8715C8.70394 22.7333 13.1251 23.3985 16.9053 26.3572C20.6855 29.3159 22.3933 33.4477 20.7198 35.5859C19.0463 37.7241 14.6252 37.059 10.8449 34.1003Z" fill="#121212"/> +</svg> diff --git a/src/assets/png/temperatures/cold.svg b/src/assets/png/temperatures/cold.svg new file mode 100644 index 0000000000000000000000000000000000000000..e466a2bbefc073668f9c368d6fb8caf0fa6803f8 --- /dev/null +++ b/src/assets/png/temperatures/cold.svg @@ -0,0 +1,36 @@ +<svg width="288" height="60" viewBox="0 0 288 60" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_15704_1893)"> +<g opacity="0.5" filter="url(#filter0_d_15704_1893)"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M256.694 67.2605C256.671 67.2607 256.647 67.2608 256.623 67.2608C251.596 67.2608 247.521 62.8458 247.521 57.3997C247.521 51.9536 251.596 47.5387 256.623 47.5387C256.672 47.5387 256.721 47.5391 256.77 47.5399C257.884 38.9789 265.205 32.3674 274.07 32.3674C280.755 32.3674 286.562 36.1277 289.491 41.6485C290.15 41.531 290.826 41.4699 291.516 41.4699C298.219 41.4699 303.653 47.2433 303.653 54.3651C303.653 61.4869 298.219 67.2602 291.516 67.2603V67.2605H256.694Z" fill="#4D5C6E"/> +</g> +<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M195.958 67.2605C195.982 67.2607 196.005 67.2608 196.029 67.2608C201.057 67.2608 205.132 62.8458 205.132 57.3997C205.132 51.9536 201.057 47.5387 196.029 47.5387C195.98 47.5387 195.931 47.5391 195.882 47.5399C194.768 38.9789 187.447 32.3674 178.583 32.3674C171.897 32.3674 166.09 36.1277 163.161 41.6485C162.503 41.531 161.826 41.4699 161.136 41.4699C154.433 41.4699 149 47.2433 149 54.3651C149 61.4869 154.433 67.2602 161.136 67.2603V67.2605H195.958Z" fill="#4D5C6E"/> +<g filter="url(#filter1_d_15704_1893)"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M189.232 77.5223C189.207 77.5224 189.182 77.5225 189.157 77.5225C180.582 77.5225 173.63 69.9913 173.63 60.7011C173.63 51.4109 180.582 43.8797 189.157 43.8797C189.241 43.8797 189.325 43.8804 189.408 43.8819C191.309 29.2782 203.797 18 218.919 18C230.323 18 240.229 24.4144 245.226 33.8322C246.349 33.6318 247.502 33.5275 248.679 33.5275C260.113 33.5275 269.382 43.376 269.382 55.5247C269.382 67.6733 260.113 77.5218 248.68 77.5219V77.5223H189.232Z" fill="#4D5C6E"/> +</g> +</g> +<defs> +<filter id="filter0_d_15704_1893" x="225.021" y="17.8674" width="101.132" height="79.8933" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="8"/> +<feGaussianBlur stdDeviation="11.25"/> +<feComposite in2="hardAlpha" operator="out"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_15704_1893"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_15704_1893" result="shape"/> +</filter> +<filter id="filter1_d_15704_1893" x="151.13" y="3.5" width="140.753" height="104.522" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="8"/> +<feGaussianBlur stdDeviation="11.25"/> +<feComposite in2="hardAlpha" operator="out"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_15704_1893"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_15704_1893" result="shape"/> +</filter> +<clipPath id="clip0_15704_1893"> +<rect width="288" height="60" rx="4" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/src/assets/png/temperatures/hot.svg b/src/assets/png/temperatures/hot.svg new file mode 100644 index 0000000000000000000000000000000000000000..5e817db1668234ab5d9ec226d2ee9902e12fce27 --- /dev/null +++ b/src/assets/png/temperatures/hot.svg @@ -0,0 +1,48 @@ +<svg width="288" height="60" viewBox="0 0 288 60" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_15704_1892)"> +<g filter="url(#filter0_d_15704_1892)"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M251.15 67.2604C251.125 67.2605 251.1 67.2606 251.075 67.2606C244.843 67.2606 239.79 61.787 239.79 55.0349C239.79 48.2828 244.843 42.8092 251.075 42.8092C251.136 42.8092 251.197 42.8097 251.257 42.8107C252.639 32.1969 261.715 24 272.705 24C280.994 24 288.194 28.662 291.825 35.5067C292.641 35.3611 293.48 35.2853 294.335 35.2853C302.645 35.2853 309.382 42.4432 309.382 51.2728C309.382 60.1023 302.646 67.26 294.336 67.2603V67.2604H251.15Z" fill="white"/> +</g> +<g filter="url(#filter1_d_15704_1892)"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M164.174 73.893C164.15 73.8932 164.126 73.8933 164.102 73.8933C159.075 73.8933 155 69.4784 155 64.0323C155 58.5862 159.075 54.1712 164.102 54.1712C164.152 54.1712 164.201 54.1717 164.25 54.1725C165.364 45.6115 172.684 39 181.549 39C188.234 39 194.042 42.7602 196.971 48.2811C197.629 48.1636 198.306 48.1024 198.995 48.1024C205.698 48.1024 211.132 53.8758 211.132 60.9977C211.132 68.1194 205.698 73.8928 198.996 73.8929V73.893H164.174Z" fill="white" fill-opacity="0.35" shape-rendering="crispEdges"/> +</g> +<g filter="url(#filter2_d_15704_1892)"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M200.602 77.5223C200.577 77.5224 200.552 77.5225 200.527 77.5225C191.952 77.5225 185 69.9913 185 60.7011C185 51.4109 191.952 43.8797 200.527 43.8797C200.611 43.8797 200.695 43.8804 200.778 43.8819C202.679 29.2782 215.167 18 230.289 18C241.693 18 251.599 24.4144 256.596 33.8322C257.719 33.6318 258.873 33.5275 260.049 33.5275C271.483 33.5275 280.753 43.376 280.753 55.5247C280.753 67.6733 271.484 77.5218 260.05 77.5219V77.5223H200.602Z" fill="white"/> +</g> +</g> +<defs> +<filter id="filter0_d_15704_1892" x="217.29" y="9.5" width="114.592" height="88.2606" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="8"/> +<feGaussianBlur stdDeviation="11.25"/> +<feComposite in2="hardAlpha" operator="out"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_15704_1892"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_15704_1892" result="shape"/> +</filter> +<filter id="filter1_d_15704_1892" x="132.5" y="24.5" width="101.132" height="79.8933" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="8"/> +<feGaussianBlur stdDeviation="11.25"/> +<feComposite in2="hardAlpha" operator="out"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_15704_1892"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_15704_1892" result="shape"/> +</filter> +<filter id="filter2_d_15704_1892" x="162.5" y="3.5" width="140.753" height="104.522" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="8"/> +<feGaussianBlur stdDeviation="11.25"/> +<feComposite in2="hardAlpha" operator="out"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_15704_1892"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_15704_1892" result="shape"/> +</filter> +<clipPath id="clip0_15704_1892"> +<rect width="288" height="60" rx="4" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/src/components/Action/ActionBegin/ActionBegin.spec.tsx b/src/components/Action/ActionBegin/ActionBegin.spec.tsx index f4e0ba50d1ed2e4016e6142ab11685a0f3bf6ede..99bab9320fa6c3606ad87e4aa26ae48d12fb5808 100644 --- a/src/components/Action/ActionBegin/ActionBegin.spec.tsx +++ b/src/components/Action/ActionBegin/ActionBegin.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { act, render, screen, waitFor } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -62,8 +62,7 @@ describe('ActionBegin component', () => { </Provider> ) await waitFor(() => null, { container }) - const title = screen.getByRole('heading') - expect(title).toHaveTextContent('Coup de vent') + expect(screen.getByText('Coup de vent')).toBeInTheDocument() }) it('should open launch Modal', async () => { const store = createMockEcolyoStore({ @@ -71,7 +70,7 @@ describe('ActionBegin component', () => { profile: mockProfileState, }) - render( + const { container } = render( <Provider store={store}> <ActionBegin action={defaultEcogestureData[1]} @@ -80,7 +79,10 @@ describe('ActionBegin component', () => { /> </Provider> ) - await userEvent.click(await screen.findByText('action.apply')) + await waitFor(() => null, { container }) + await act(async () => { + await userEvent.click(screen.getByText('action.apply')) + }) expect(await screen.findByRole('dialog')).toBeInTheDocument() }) }) diff --git a/src/components/Action/ActionCard/ActionCard.spec.tsx b/src/components/Action/ActionCard/ActionCard.spec.tsx index 6da3d6ac9cbbf3d88c792275d9ff1d3878f25f85..44a22ec8a316dfd5f773800f83fed2f55f3d8fb8 100644 --- a/src/components/Action/ActionCard/ActionCard.spec.tsx +++ b/src/components/Action/ActionCard/ActionCard.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { act, render, screen, waitFor } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -27,7 +27,7 @@ describe('ActionCard component', () => { expect(container).toMatchSnapshot() }) it('should open Ecogesture modal', async () => { - render( + const { container } = render( <Provider store={store}> <ActionCard setShowList={jest.fn()} @@ -36,7 +36,10 @@ describe('ActionCard component', () => { /> </Provider> ) - await userEvent.click(await screen.findByRole('button')) + await waitFor(() => null, { container }) + await act(async () => { + await userEvent.click(await screen.findByRole('button')) + }) expect(await screen.findByRole('dialog')).toBeInTheDocument() }) }) diff --git a/src/components/Action/ActionChoose/ActionChoose.spec.tsx b/src/components/Action/ActionChoose/ActionChoose.spec.tsx index c7f251918247054dcc6f0eacbb711aa37269d06c..936d23c5f8b50519f15a656dd8338415d5fde881 100644 --- a/src/components/Action/ActionChoose/ActionChoose.spec.tsx +++ b/src/components/Action/ActionChoose/ActionChoose.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { act, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -12,6 +12,9 @@ import { import { userChallengeData } from 'tests/__mocks__/userChallengeData.mock' import ActionChoose from './ActionChoose' +// jest.mock('components/Action/ActionBegin/ActionBegin', () => 'mock-ActionBegin') +jest.mock('components/Action/ActionList/ActionList', () => 'mock-ActionList') + jest.mock('services/ecogesture.service', () => { return jest.fn(() => ({ getEcogesturesByIds: jest.fn(() => []), @@ -65,8 +68,13 @@ describe('ActionChoose component', () => { <ActionChoose userChallenge={userChallengeData[1]} /> </Provider> ) - await userEvent.click(await screen.findByText('action.other')) - const elements = container.getElementsByClassName('action-list-container') - expect(elements.item(0)).toBeInTheDocument() + await waitFor(() => null, { container }) + + await act(async () => { + await userEvent.click(await screen.findByText('action.other')) + }) + + const list = container.getElementsByTagName('mock-ActionList')[0] + expect(list).toBeInTheDocument() }) }) diff --git a/src/components/Action/ActionDone/ActionDone.spec.tsx b/src/components/Action/ActionDone/ActionDone.spec.tsx index 09c2d698ff27ff4764736c633b536282298775cc..038a6dd206fee247504430ae81e435a579e305b0 100644 --- a/src/components/Action/ActionDone/ActionDone.spec.tsx +++ b/src/components/Action/ActionDone/ActionDone.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -35,7 +35,9 @@ describe('ActionDone component', () => { <ActionDone currentChallenge={userChallengeData[1]} /> </Provider> ) - await userEvent.click(await screen.findByText('action.ok')) + await act(async () => { + await userEvent.click(screen.getByText('action.ok')) + }) expect(updateChallengeSpy).toHaveBeenCalledTimes(1) }) }) diff --git a/src/components/Action/ActionModal/ActionModal.spec.tsx b/src/components/Action/ActionModal/ActionModal.spec.tsx index a3c59c54d2523c2f93e0c264740a6389efe9afd2..138d005c3e36aa4cecbea8a50376afe0ac90490b 100644 --- a/src/components/Action/ActionModal/ActionModal.spec.tsx +++ b/src/components/Action/ActionModal/ActionModal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -47,7 +47,9 @@ describe('ActionModal component', () => { /> </Provider> ) - await userEvent.click(await screen.findByText('action_modal.accept')) + await act(async () => { + await userEvent.click(screen.getByText('action_modal.accept')) + }) expect(updateChallengeSpy).toHaveBeenCalledTimes(1) }) }) diff --git a/src/components/Action/ActionOnGoing/ActionOnGoing.spec.tsx b/src/components/Action/ActionOnGoing/ActionOnGoing.spec.tsx index 899e6e3cdeb3b9216ac26e2433901bf913bc08cb..ed1789703e930b669c776175fc812a17f48beef9 100644 --- a/src/components/Action/ActionOnGoing/ActionOnGoing.spec.tsx +++ b/src/components/Action/ActionOnGoing/ActionOnGoing.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { act, render, screen, waitFor } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import { UserActionState } from 'enums' import { DateTime } from 'luxon' @@ -39,13 +39,14 @@ describe('ActionOnGoing component', () => { state: UserActionState.ONGOING, } - const { container } = render( + render( <Provider store={store}> <ActionOnGoing userAction={userAction1} /> </Provider> ) - await waitFor(() => null, { container }) - await userEvent.click(await screen.findByText('action.details')) + await act(async () => { + await userEvent.click(screen.getByText('action.details')) + }) expect(screen.findByRole('dialog')).toBeTruthy() }) }) diff --git a/src/components/Action/ActionView.tsx b/src/components/Action/ActionView.tsx index 705a66031d0b2d791d1041f7bc8f874c019f5ace..7b5fbab5d6852111c2d6781d1045870ecc459227 100644 --- a/src/components/Action/ActionView.tsx +++ b/src/components/Action/ActionView.tsx @@ -3,7 +3,7 @@ import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' import { UserActionState } from 'enums' import { UserChallenge } from 'models' -import React, { useState } from 'react' +import React from 'react' import { useAppSelector } from 'store/hooks' import ActionChoose from './ActionChoose/ActionChoose' import ActionDone from './ActionDone/ActionDone' @@ -11,7 +11,6 @@ import ActionOnGoing from './ActionOnGoing/ActionOnGoing' const ActionView = () => { const { currentChallenge } = useAppSelector(state => state.ecolyo.challenge) - const [headerHeight, setHeaderHeight] = useState<number>(0) const renderAction = (challenge: UserChallenge) => { switch (challenge.action.state) { @@ -29,14 +28,8 @@ const ActionView = () => { return ( <> <CozyBar titleKey="common.title_action" displayBackArrow={true} /> - <Header - setHeaderHeight={setHeaderHeight} - desktopTitleKey="common.title_action" - displayBackArrow={true} - /> - <Content heightOffset={headerHeight}> - {currentChallenge && renderAction(currentChallenge)} - </Content> + <Header desktopTitleKey="common.title_action" displayBackArrow={true} /> + <Content>{currentChallenge && renderAction(currentChallenge)}</Content> </> ) } diff --git a/src/components/Action/__snapshots__/ActionView.spec.tsx.snap b/src/components/Action/__snapshots__/ActionView.spec.tsx.snap index 6197c430c16f23523cbf9163f61e433bc862f146..cd7ed71bda2e62a0f90c8c17df8c29aee1794b1d 100644 --- a/src/components/Action/__snapshots__/ActionView.spec.tsx.snap +++ b/src/components/Action/__snapshots__/ActionView.spec.tsx.snap @@ -10,9 +10,7 @@ exports[`ActionView component should render match snapshot with "Notification" s desktoptitlekey="common.title_action" displaybackarrow="true" /> - <mock-content - heightoffset="0" - > + <mock-content> <mock-action-done currentchallenge="[object Object]" /> @@ -30,9 +28,7 @@ exports[`ActionView component should render match snapshot with "Unstarted" stat desktoptitlekey="common.title_action" displaybackarrow="true" /> - <mock-content - heightoffset="0" - > + <mock-content> <mock-action-choose userchallenge="[object Object]" /> @@ -50,9 +46,7 @@ exports[`ActionView component should render match snapshot with "onGoing" state desktoptitlekey="common.title_action" displaybackarrow="true" /> - <mock-content - heightoffset="0" - > + <mock-content> <mock-action-ongoing useraction="[object Object]" /> @@ -70,9 +64,7 @@ exports[`ActionView component should render match snapshot with default case 1`] desktoptitlekey="common.title_action" displaybackarrow="true" /> - <mock-content - heightoffset="0" - > + <mock-content> <mock-action-choose userchallenge="[object Object]" /> diff --git a/src/components/Analysis/AnalysisView.tsx b/src/components/Analysis/AnalysisView.tsx index 0047d6c501a1a73776a2652111f7b460de643e6e..85e543aca28f0ce4ed04e8fab04ce12505fd3326 100644 --- a/src/components/Analysis/AnalysisView.tsx +++ b/src/components/Analysis/AnalysisView.tsx @@ -26,7 +26,6 @@ const AnalysisView = () => { profile: { monthlyAnalysisDate, mailToken }, } = useAppSelector(state => state.ecolyo) const dispatch = useAppDispatch() - const [headerHeight, setHeaderHeight] = useState<number>(0) const dateChartService = new DateChartService() @@ -106,10 +105,7 @@ const AnalysisView = () => { return ( <> <CozyBar titleKey="common.title_analysis" /> - <Header - setHeaderHeight={setHeaderHeight} - desktopTitleKey="common.title_analysis" - > + <Header desktopTitleKey="common.title_analysis"> <DateNavigator disableNext={isLastDateReached(analysisMonth, TimeStep.MONTH)} disablePrev={false} @@ -120,7 +116,7 @@ const AnalysisView = () => { timeStep={TimeStep.MONTH} /> </Header> - <Content heightOffset={headerHeight}> + <Content> <MonthlyAnalysis saveLastScrollPosition={saveLastScrollPosition} scrollPosition={scrollPosition} diff --git a/src/components/Analysis/Comparison/Comparison.tsx b/src/components/Analysis/Comparison/Comparison.tsx index 1e2a29a532da7792671ffcc0d5044d9ac173b108..afa648f2e30a0f14085aaa7d0678d0ca748840d9 100644 --- a/src/components/Analysis/Comparison/Comparison.tsx +++ b/src/components/Analysis/Comparison/Comparison.tsx @@ -11,6 +11,7 @@ import { setPeriod } from 'store/analysis/analysis.slice' import { setCurrentTimeStep, setShowCompare } from 'store/chart/chart.slice' import { useAppDispatch, useAppSelector } from 'store/hooks' import FluidPerformanceIndicator from './FluidPerformanceIndicator' +import TemperatureComparison from './TemperatureComparison' import './comparison.scss' const Comparison = ({ @@ -26,7 +27,6 @@ const Comparison = ({ const { global: { fluidTypes }, analysis: { period, analysisMonth }, - chart: { showCompare }, } = useAppSelector(state => state.ecolyo) const dispatch = useAppDispatch() const [yearPerformanceIndicators, setYearPerformanceIndicators] = useState< @@ -158,7 +158,7 @@ const Comparison = ({ <Loader /> </div> )} - + {!isLoading && <TemperatureComparison />} {/* Placeholder when no data is found */} {!isLoading && fluidsWithData.length === 0 && diff --git a/src/components/Analysis/Comparison/TemperatureComparison.spec.tsx b/src/components/Analysis/Comparison/TemperatureComparison.spec.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8d3383f30fbf92a4b590a9677db2eb2d621d8408 --- /dev/null +++ b/src/components/Analysis/Comparison/TemperatureComparison.spec.tsx @@ -0,0 +1,75 @@ +import { act, render, screen, waitFor } from '@testing-library/react' +import { userEvent } from '@testing-library/user-event' +import { DateTime } from 'luxon' +import React from 'react' +import { Provider } from 'react-redux' +import { createMockEcolyoStore } from 'tests/__mocks__/store' +import TemperatureComparison from './TemperatureComparison' + +const mockFetchAvgTemperature = jest.fn<Promise<number | null>, []>() +jest.mock('services/consumption.service', () => { + return jest.fn(() => ({ + fetchAvgTemperature: mockFetchAvgTemperature, + })) +}) + +jest + .spyOn(DateTime.prototype, 'toLocaleString') + .mockReturnValue('novembre 2022') + +describe('TemperatureComparison component', () => { + const store = createMockEcolyoStore() + afterEach(() => { + jest.clearAllMocks() + }) + it('should be rendered correctly with hot result', async () => { + mockFetchAvgTemperature.mockResolvedValueOnce(1) + mockFetchAvgTemperature.mockResolvedValueOnce(2) + const { container } = render( + <Provider store={store}> + <TemperatureComparison /> + </Provider> + ) + await waitFor(() => null, { container }) + expect(container).toMatchSnapshot() + }) + it('should be rendered correctly with cold result', async () => { + mockFetchAvgTemperature.mockResolvedValueOnce(2) + mockFetchAvgTemperature.mockResolvedValueOnce(1) + const { container } = render( + <Provider store={store}> + <TemperatureComparison /> + </Provider> + ) + await waitFor(() => null, { container }) + expect(container).toMatchSnapshot() + }) + it('should be rendered correctly with no data', async () => { + mockFetchAvgTemperature.mockResolvedValueOnce(null) + mockFetchAvgTemperature.mockResolvedValueOnce(0) + const { container } = render( + <Provider store={store}> + <TemperatureComparison /> + </Provider> + ) + await waitFor(() => null, { container }) + expect(container).toMatchSnapshot() + }) + + it('should open the modal', async () => { + mockFetchAvgTemperature.mockResolvedValueOnce(1) + mockFetchAvgTemperature.mockResolvedValueOnce(2) + const { container } = render( + <Provider store={store}> + <TemperatureComparison /> + </Provider> + ) + await waitFor(() => container) + await act(async () => { + await userEvent.click( + screen.getByLabelText('analysis.temperature_comparison.info_button') + ) + }) + expect(screen.getByRole('dialog')).toBeInTheDocument() + }) +}) diff --git a/src/components/Analysis/Comparison/TemperatureComparison.tsx b/src/components/Analysis/Comparison/TemperatureComparison.tsx new file mode 100644 index 0000000000000000000000000000000000000000..393df4a980a3608affd5c0d06b2510daf6ea8221 --- /dev/null +++ b/src/components/Analysis/Comparison/TemperatureComparison.tsx @@ -0,0 +1,111 @@ +import ExclamationMarkIcon from 'assets/icons/ico/exclamationMark.svg' +import StyledIconButton from 'components/CommonKit/IconButton/StyledIconButton' +import { useClient } from 'cozy-client' +import { useI18n } from 'cozy-ui/transpiled/react/I18n' +import React, { useEffect, useMemo, useState } from 'react' +import ConsumptionService from 'services/consumption.service' +import { useAppSelector } from 'store/hooks' +import TemperatureComparisonModal from './TemperatureComparisonModal/TemperatureComparisonModal' +import './temperatureComparison.scss' + +const TemperatureComparison = () => { + const { t } = useI18n() + const client = useClient() + const { period, analysisMonth } = useAppSelector( + state => state.ecolyo.analysis + ) + + const [temperatureDifference, setTemperatureDifference] = useState<string>() + const [positive, setPositive] = useState<boolean>(true) + const [openTemperatureComparisonModal, setOpenTemperatureComparisonModal] = + useState<boolean>(false) + const consumptionService = useMemo( + () => new ConsumptionService(client), + [client] + ) + const comparisonDates = useMemo(() => { + const endMonth = analysisMonth.minus({ month: 1 }).startOf('month') + return { + startMonth: endMonth + .minus({ month: period === 'month' ? 1 : 12 }) + .startOf('month'), + endMonth: endMonth, + } + }, [analysisMonth, period]) + + useEffect(() => { + async function handleTemperatureComparison() { + const startMonthTemperature = + await consumptionService.fetchAvgTemperature( + comparisonDates.startMonth.year, + comparisonDates.startMonth.month + ) + + const endMonthTemperature = await consumptionService.fetchAvgTemperature( + comparisonDates.endMonth.year, + comparisonDates.endMonth.month + ) + + if (startMonthTemperature !== null && endMonthTemperature !== null) { + const temperatureDifference = + endMonthTemperature - startMonthTemperature + setTemperatureDifference(temperatureDifference.toFixed(1)) + setPositive(temperatureDifference >= 0) + } + } + handleTemperatureComparison() + }, [ + consumptionService, + comparisonDates.endMonth.month, + comparisonDates.endMonth.year, + comparisonDates.startMonth.month, + comparisonDates.startMonth.year, + ]) + + return ( + <> + {temperatureDifference && ( + <div className={`temperatureComparison ${positive ? 'hot' : 'cold'}`}> + <div className="tc-content"> + <div> + <span className="text-28-bold"> + {positive ? '+' : ''} + {temperatureDifference} + </span> + <span className="text-18"> + {t('analysis.temperature_comparison.unit')} + </span> + </div> + <div> + <span className="tc-text text-12"> + {t('analysis.temperature_comparison.comparison')} + </span> + <span className="tc-period text-12-bold"> + + {comparisonDates.startMonth.toLocaleString({ + month: 'long', + year: 'numeric', + })} + </span> + </div> + </div> + + <StyledIconButton + icon={ExclamationMarkIcon} + sized={16} + onClick={() => setOpenTemperatureComparisonModal(true)} + aria-label={t('analysis.temperature_comparison.info_button')} + className="info-icon" + /> + + <TemperatureComparisonModal + open={openTemperatureComparisonModal} + handleCloseClick={() => setOpenTemperatureComparisonModal(false)} + /> + </div> + )} + </> + ) +} + +export default TemperatureComparison diff --git a/src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.spec.tsx b/src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.spec.tsx new file mode 100644 index 0000000000000000000000000000000000000000..783e566a34c6903a1c00b977362dbbc610f2c628 --- /dev/null +++ b/src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.spec.tsx @@ -0,0 +1,34 @@ +import { act, render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import React from 'react' +import { Provider } from 'react-redux' +import { createMockEcolyoStore } from 'tests/__mocks__/store' +import TemperatureComparisonModal from './TemperatureComparisonModal' + +describe('TemperatureComparisonModal component', () => { + const store = createMockEcolyoStore() + it('should be rendered correctly', () => { + const { baseElement } = render( + <Provider store={store}> + <TemperatureComparisonModal open={true} handleCloseClick={jest.fn()} /> + </Provider> + ) + expect(baseElement).toMatchSnapshot() + }) + + it('should close modal', async () => { + const mockHandleClose = jest.fn() + render( + <Provider store={store}> + <TemperatureComparisonModal + open={true} + handleCloseClick={mockHandleClose} + /> + </Provider> + ) + await act(async () => { + await userEvent.click(screen.getAllByRole('button')[0]) + }) + expect(mockHandleClose).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.tsx b/src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b165c9936433fcd09c2a469195455cd29b56b0cb --- /dev/null +++ b/src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.tsx @@ -0,0 +1,61 @@ +import { IconButton } from '@material-ui/core' +import Dialog from '@material-ui/core/Dialog' +import CloseIcon from 'assets/icons/ico/close.svg' +import { useI18n } from 'cozy-ui/transpiled/react/I18n' +import Icon from 'cozy-ui/transpiled/react/Icon' +import React from 'react' +import { useAppSelector } from 'store/hooks' +import './temperatureComparisonModal.scss' + +interface TemperatureComparisonModalProps { + open: boolean + handleCloseClick: () => void +} + +const TemperatureComparisonModal = ({ + open, + handleCloseClick, +}: TemperatureComparisonModalProps) => { + const { t } = useI18n() + const { period } = useAppSelector(state => state.ecolyo.analysis) + + return ( + <Dialog + open={open} + onClose={handleCloseClick} + aria-label={t('analysis.temperature_comparison.modal.title')} + classes={{ + root: 'modal-root', + paper: 'modal-paper', + }} + > + <div className="modal-start-root"> + <IconButton + aria-label={t('analysis.temperature_comparison.modal.close')} + className="modal-paper-close-button" + onClick={handleCloseClick} + > + <Icon icon={CloseIcon} size={18} /> + </IconButton> + <div className="content"> + <div className="text-20-bold subtitle"> + {t('analysis.temperature_comparison.modal.title')} + </div> + <div> + <p className="text-15-bold"> + {period === 'month' + ? t('analysis.temperature_comparison.modal.month_comparison') + : t('analysis.temperature_comparison.modal.year_comparison')} + </p> + <br /> + <p className="text-15-bold"> + {t('analysis.temperature_comparison.modal.data_info')} + </p> + </div> + </div> + </div> + </Dialog> + ) +} + +export default TemperatureComparisonModal diff --git a/src/components/Analysis/Comparison/TemperatureComparisonModal/__snapshots__/TemperatureComparisonModal.spec.tsx.snap b/src/components/Analysis/Comparison/TemperatureComparisonModal/__snapshots__/TemperatureComparisonModal.spec.tsx.snap new file mode 100644 index 0000000000000000000000000000000000000000..19f0c60c854b994a7206326d40398cf10636f3bd --- /dev/null +++ b/src/components/Analysis/Comparison/TemperatureComparisonModal/__snapshots__/TemperatureComparisonModal.spec.tsx.snap @@ -0,0 +1,92 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TemperatureComparisonModal component should be rendered correctly 1`] = ` +<body + style="padding-right: 0px; overflow: hidden;" +> + <div + aria-hidden="true" + /> + <div + aria-label="analysis.temperature_comparison.modal.title" + class="MuiDialog-root modal-root" + role="presentation" + style="position: fixed; z-index: 1300; right: 0px; bottom: 0px; top: 0px; left: 0px;" + > + <div + aria-hidden="true" + class="MuiBackdrop-root" + style="opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;" + /> + <div + data-test="sentinelStart" + tabindex="0" + /> + <div + class="MuiDialog-container MuiDialog-scrollPaper" + role="none presentation" + style="opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;" + tabindex="-1" + > + <div + class="MuiPaper-root MuiDialog-paper modal-paper MuiDialog-paperScrollPaper MuiDialog-paperWidthSm MuiPaper-elevation24 MuiPaper-rounded" + role="dialog" + > + <div + class="modal-start-root" + > + <button + aria-label="analysis.temperature_comparison.modal.close" + class="MuiButtonBase-root MuiIconButton-root modal-paper-close-button" + tabindex="0" + type="button" + > + <span + class="MuiIconButton-label" + > + <svg + class="styles__icon___23x3R" + height="18" + width="18" + > + <use + xlink:href="#test-file-stub" + /> + </svg> + </span> + <span + class="MuiTouchRipple-root" + /> + </button> + <div + class="content" + > + <div + class="text-20-bold subtitle" + > + analysis.temperature_comparison.modal.title + </div> + <div> + <p + class="text-15-bold" + > + analysis.temperature_comparison.modal.month_comparison + </p> + <br /> + <p + class="text-15-bold" + > + analysis.temperature_comparison.modal.data_info + </p> + </div> + </div> + </div> + </div> + </div> + <div + data-test="sentinelEnd" + tabindex="0" + /> + </div> +</body> +`; diff --git a/src/components/Analysis/Comparison/TemperatureComparisonModal/temperatureComparisonModal.scss b/src/components/Analysis/Comparison/TemperatureComparisonModal/temperatureComparisonModal.scss new file mode 100644 index 0000000000000000000000000000000000000000..2375d955054cc64c033444a5b0d6eac896235e3d --- /dev/null +++ b/src/components/Analysis/Comparison/TemperatureComparisonModal/temperatureComparisonModal.scss @@ -0,0 +1,25 @@ +@import 'src/styles/base/color'; + +.modal-start-root { + display: flex; + flex-direction: column; + align-items: flex-end; + + .content { + text-align: center; + padding: 1rem 0; + display: flex; + flex-direction: column; + gap: 2rem; + flex-grow: 1; + + .subtitle { + color: $gold-shadow; + } + + p { + color: $white; + margin: 0; + } + } +} diff --git a/src/components/Analysis/Comparison/__snapshots__/TemperatureComparison.spec.tsx.snap b/src/components/Analysis/Comparison/__snapshots__/TemperatureComparison.spec.tsx.snap new file mode 100644 index 0000000000000000000000000000000000000000..eac3ee09c526dd84802cca03337293575eb5d0df --- /dev/null +++ b/src/components/Analysis/Comparison/__snapshots__/TemperatureComparison.spec.tsx.snap @@ -0,0 +1,124 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TemperatureComparison component should be rendered correctly with cold result 1`] = ` +<div> + <div + class="temperatureComparison cold" + > + <div + class="tc-content" + > + <div> + <span + class="text-28-bold" + > + -1.0 + + </span> + <span + class="text-18" + > + analysis.temperature_comparison.unit + </span> + </div> + <div> + <span + class="tc-text text-12" + > + analysis.temperature_comparison.comparison + </span> + <span + class="tc-period text-12-bold" + > + + novembre 2022 + </span> + </div> + </div> + <button + aria-label="analysis.temperature_comparison.info_button" + class="MuiButtonBase-root MuiIconButton-root WithStyles(ForwardRef(IconButton))-root-2 info-icon" + tabindex="0" + type="button" + > + <span + class="MuiIconButton-label" + > + <svg + aria-hidden="true" + class="styles__icon___23x3R" + height="16" + width="16" + > + <use + xlink:href="#test-file-stub" + /> + </svg> + </span> + </button> + </div> +</div> +`; + +exports[`TemperatureComparison component should be rendered correctly with hot result 1`] = ` +<div> + <div + class="temperatureComparison hot" + > + <div + class="tc-content" + > + <div> + <span + class="text-28-bold" + > + + + 1.0 + + </span> + <span + class="text-18" + > + analysis.temperature_comparison.unit + </span> + </div> + <div> + <span + class="tc-text text-12" + > + analysis.temperature_comparison.comparison + </span> + <span + class="tc-period text-12-bold" + > + + novembre 2022 + </span> + </div> + </div> + <button + aria-label="analysis.temperature_comparison.info_button" + class="MuiButtonBase-root MuiIconButton-root WithStyles(ForwardRef(IconButton))-root-1 info-icon" + tabindex="0" + type="button" + > + <span + class="MuiIconButton-label" + > + <svg + aria-hidden="true" + class="styles__icon___23x3R" + height="16" + width="16" + > + <use + xlink:href="#test-file-stub" + /> + </svg> + </span> + </button> + </div> +</div> +`; + +exports[`TemperatureComparison component should be rendered correctly with no data 1`] = `<div />`; diff --git a/src/components/Analysis/Comparison/temperatureComparison.scss b/src/components/Analysis/Comparison/temperatureComparison.scss new file mode 100644 index 0000000000000000000000000000000000000000..ce4fc3e5e665d169435eb4df2ffa3975962b3429 --- /dev/null +++ b/src/components/Analysis/Comparison/temperatureComparison.scss @@ -0,0 +1,50 @@ +@import 'src/styles/base/color'; +@import 'src/styles/base/breakpoint'; + +.temperatureComparison { + display: flex; + border: 1px solid $grey-dark; + border-radius: 4px; + padding: 4px 4px 8px 22px; + box-shadow: 0px 4px 16px 0px $black-shadow; + background: linear-gradient(180deg, #323339 0%, #25262b 100%); + background-position: bottom right; + background-repeat: no-repeat; + &.hot { + background-image: url('../../../assets/png/temperatures/hot.svg'), + linear-gradient(259deg, rgba(6, 29, 62, 0) 25.28%, #77aee0 121.36%), + radial-gradient( + 185.82% 146.65% at 50% 79.83%, + rgba(6, 29, 62, 0) 0%, + #e0bc77 100% + ); + } + &.cold { + background-image: url('../../../assets/png/temperatures/cold.svg'), + radial-gradient( + 185.82% 146.65% at 50% 79.83%, + rgba(6, 29, 62, 0) 0%, + #77a3e0 100% + ); + } + + .tc-content { + text-align: left; + padding-top: 4px; + flex-grow: 1; + color: $white; + .tc-text { + font-weight: 200; + } + .tc-period { + font-weight: 900; + } + } + + .info-icon { + padding: 0; + stroke: $white; + align-items: flex-start; + height: 100%; + } +} diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.spec.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.spec.tsx index 079b98bd5a78e418233b508098bda144c53b7faf..4c977c1746a4286808cb9dd47a64990ed6ecbf2f 100644 --- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.spec.tsx +++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { act, render, screen, waitFor } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import { PerformanceIndicator } from 'models' import React from 'react' @@ -99,8 +99,11 @@ describe('ElecHalfHourMonthlyAnalysis component', () => { <ElecHalfHourMonthlyAnalysis perfIndicator={mockPerfIndicator} /> </Provider> ) - await waitFor(() => null, { container }) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getByLabelText('consumption.accessibility.button_previous_value') + ) + }) expect(container.getElementsByClassName('week').length).toBeTruthy() }) @@ -114,13 +117,15 @@ describe('ElecHalfHourMonthlyAnalysis component', () => { ) mockGetPrices.mockResolvedValue(allLastFluidPrices[0]) - const { container } = render( + render( <Provider store={store}> <ElecHalfHourMonthlyAnalysis perfIndicator={mockPerfIndicator} /> </Provider> ) - await waitFor(() => null, { container }) - await userEvent.click(await screen.findByText('special_elec.showModal')) + + await waitFor(async () => { + await userEvent.click(screen.getByText('special_elec.showModal')) + }) expect(await screen.findByRole('dialog')).toBeInTheDocument() }) }) diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.tsx index 332d2177ccc1fb7d3231789e62c24fcdb6be3e43..44bcc2b670387f373045c72260cf6ce98635aa75 100644 --- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.tsx +++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.tsx @@ -16,7 +16,7 @@ import { FluidPrice, PerformanceIndicator, } from 'models' -import React, { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import ConsumptionService from 'services/consumption.service' import EnedisMonthlyAnalysisDataService from 'services/enedisMonthlyAnalysisData.service' import FluidPricesService from 'services/fluidsPrices.service' @@ -35,6 +35,7 @@ const ElecHalfHourMonthlyAnalysis = ({ const { t } = useI18n() const client = useClient() const { analysisMonth } = useAppSelector(state => state.ecolyo.analysis) + const [isWeekend, setIsWeekend] = useState<boolean>(true) const [isHalfHourActivated, setIsHalfHourActivated] = useState<boolean>(true) const [isLoading, setIsLoading] = useState<boolean>(true) @@ -51,19 +52,27 @@ const ElecHalfHourMonthlyAnalysis = ({ setIsWeekend(prev => !prev) }, []) - const toggleOpenModal = useCallback(() => { + const toggleInfoModal = useCallback(() => { setOpenInfoModal(prev => !prev) }, []) - const isDataFullyComplete = useCallback(monthDataloads => { - if ( - monthDataloads?.weekend && - monthDataloads.week && - monthDataloads.weekend[0]?.value !== null && - monthDataloads.week[0]?.value !== null - ) { - return true - } else return false - }, []) + + /** EnedisMonthlyAnalysisDataService*/ + const emas = useMemo( + () => new EnedisMonthlyAnalysisDataService(client), + [client] + ) + + const isDataFullyComplete = useCallback( + (monthDataloads: AggregatedEnedisMonthlyDataloads | undefined) => { + return ( + monthDataloads?.weekend && + monthDataloads.week && + monthDataloads.weekend[0]?.value !== null && + monthDataloads.week[0]?.value !== null + ) + }, + [] + ) const getPowerChart = useCallback((): JSX.Element => { if (monthDataloads && isDataFullyComplete(monthDataloads)) { @@ -73,9 +82,8 @@ const ElecHalfHourMonthlyAnalysis = ({ isWeekend={isWeekend} /> ) - } else { - return <p className="text-20-bold no_data">{t('analysis.no_data')}</p> } + return <p className="text-20-bold no_data">{t('analysis.no_data')}</p> }, [isDataFullyComplete, isWeekend, monthDataloads, t]) useEffect(() => { @@ -88,7 +96,6 @@ const ElecHalfHourMonthlyAnalysis = ({ ) if (!subscribed) return if (isHalfHourLoadActivated) { - const emas = new EnedisMonthlyAnalysisDataService(client) const aggregatedDate = analysisMonth.minus({ month: 1 }) const data = await emas.getEnedisMonthlyAnalysisByDate( aggregatedDate.year, @@ -114,7 +121,7 @@ const ElecHalfHourMonthlyAnalysis = ({ return () => { subscribed = false } - }, [analysisMonth, client, perfIndicator]) + }, [analysisMonth, client, emas, perfIndicator]) useEffect(() => { let subscribed = true @@ -137,12 +144,9 @@ const ElecHalfHourMonthlyAnalysis = ({ useEffect(() => { let subscribed = true - const enedisMonthlyAnalysisDataService = - new EnedisMonthlyAnalysisDataService(client) async function getOffPeakHours() { if (subscribed) { - const offPeakHours = - await enedisMonthlyAnalysisDataService.getOffPeakHours() + const offPeakHours = await emas.getOffPeakHours() if (offPeakHours) { setOffPeakHours(offPeakHours) } @@ -153,7 +157,7 @@ const ElecHalfHourMonthlyAnalysis = ({ return () => { subscribed = false } - }, [client]) + }, [emas]) return ( <div className="special-elec-container"> @@ -190,7 +194,7 @@ const ElecHalfHourMonthlyAnalysis = ({ </div> </div> <IconButton - aria-label={t('consumption.accessibility.button_previous_value')} + aria-label={t('consumption.accessibility.button_next_value')} onClick={handleChangeWeek} className="arrow-next" > @@ -279,7 +283,7 @@ const ElecHalfHourMonthlyAnalysis = ({ </div> </div> - <Button className="btnText" onClick={toggleOpenModal}> + <Button className="btnText" onClick={toggleInfoModal}> {t('special_elec.showModal')} </Button> </div> @@ -291,7 +295,7 @@ const ElecHalfHourMonthlyAnalysis = ({ <ElecInfoModal open={openInfoModal} offPeakHours={offPeakHours} - handleCloseClick={toggleOpenModal} + handleCloseClick={toggleInfoModal} /> </div> ) diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.tsx index de43d3fbb66e2378f195c3cf5b86cff14a6dc645..f755d44ae425545eb0761e878cf378e08c022956 100644 --- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.tsx +++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.tsx @@ -49,31 +49,34 @@ const ElecInfoModal = ({ <Icon icon={CloseIcon} size={16} /> </IconButton> <div className="elecInfoModal"> - <div className="title text-18-bold">{t('elec_info_modal.title1')}</div> - <div className="text"> - {t('elec_info_modal.text1-1')} + <div className="title text-18-bold"> + {t('elec_info_modal.maxPowerTitle')} + </div> + <div> + {t('elec_info_modal.maxPowerDetails-1')} <br /> - {t('elec_info_modal.text1-2')} + {t('elec_info_modal.maxPowerDetails-2')} </div> - <div className="title text-18-bold">{t('elec_info_modal.title2')}</div> {offPeakHours && ( <> <div className="title text-18-bold"> - {t('elec_info_modal.title2')} + {t('elec_info_modal.offPeakTitle')} </div> - <div className="text"> - {t('elec_info_modal.text2-1', { + <div> + {t('elec_info_modal.offPeakDetails-1', { offPeakHours: displayedOffPeakHours, })} </div> </> )} - <div className="text"> - {t('elec_info_modal.text3-1')} + <div className="title text-18-bold"> + {t('elec_info_modal.minPowerTitle')} + </div> + <div> + {t('elec_info_modal.minPowerDetails-1')} <br /> - {t('elec_info_modal.text3-2')} + {t('elec_info_modal.minPowerDetails-2')} <br /> - {t('elec_info_modal.text3-3')} </div> </div> </Dialog> diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecHalfHourChart.spec.tsx.snap b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecHalfHourChart.spec.tsx.snap index 7ec215c0aee87c294490ac0a9bf075397e9e3a7b..1f0c220c490f82d9382dd7825eb5f08f85f72bc1 100644 --- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecHalfHourChart.spec.tsx.snap +++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecHalfHourChart.spec.tsx.snap @@ -112,7 +112,7 @@ exports[`ElecHalfHourChart component should be rendered correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY weekend bounce-3 delay--0" + class="bar-ELECTRICITY weekend bounce-3 delay--0" d=" M0,4 a4,4 0 0 1 4,-4 @@ -128,7 +128,7 @@ exports[`ElecHalfHourChart component should be rendered correctly 1`] = ` > <defs> <lineargradient - class="bar-ELECTRICITY weekend bounce-3 delay--0" + class="bar-ELECTRICITY weekend bounce-3 delay--0" id="gradient" x1="0" x2="0" @@ -146,7 +146,7 @@ exports[`ElecHalfHourChart component should be rendered correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY weekend bounce-3 delay--0" + class="bar-ELECTRICITY weekend bounce-3 delay--0" d=" M0,4 a4,4 0 0 1 4,-4 @@ -195,7 +195,7 @@ exports[`ElecHalfHourChart component should be rendered correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY weekend bounce-3 delay--1" + class="bar-ELECTRICITY weekend bounce-3 delay--1" d=" M0,4 a4,4 0 0 1 4,-4 @@ -211,7 +211,7 @@ exports[`ElecHalfHourChart component should be rendered correctly 1`] = ` > <defs> <lineargradient - class="bar-ELECTRICITY weekend bounce-3 delay--1" + class="bar-ELECTRICITY weekend bounce-3 delay--1" id="gradient" x1="0" x2="0" @@ -229,7 +229,7 @@ exports[`ElecHalfHourChart component should be rendered correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY weekend bounce-3 delay--1" + class="bar-ELECTRICITY weekend bounce-3 delay--1" d=" M0,4 a4,4 0 0 1 4,-4 @@ -278,7 +278,7 @@ exports[`ElecHalfHourChart component should be rendered correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY weekend bounce-3 delay--2" + class="bar-ELECTRICITY weekend bounce-3 delay--2" d=" M0,4 a4,4 0 0 1 4,-4 @@ -294,7 +294,7 @@ exports[`ElecHalfHourChart component should be rendered correctly 1`] = ` > <defs> <lineargradient - class="bar-ELECTRICITY weekend bounce-3 delay--2" + class="bar-ELECTRICITY weekend bounce-3 delay--2" id="gradient" x1="0" x2="0" @@ -312,7 +312,7 @@ exports[`ElecHalfHourChart component should be rendered correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY weekend bounce-3 delay--2" + class="bar-ELECTRICITY weekend bounce-3 delay--2" d=" M0,4 a4,4 0 0 1 4,-4 @@ -361,7 +361,7 @@ exports[`ElecHalfHourChart component should be rendered correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY weekend bounce-3 delay--3" + class="bar-ELECTRICITY weekend bounce-3 delay--3" d=" M0,4 a4,4 0 0 1 4,-4 @@ -377,7 +377,7 @@ exports[`ElecHalfHourChart component should be rendered correctly 1`] = ` > <defs> <lineargradient - class="bar-ELECTRICITY weekend bounce-3 delay--3" + class="bar-ELECTRICITY weekend bounce-3 delay--3" id="gradient" x1="0" x2="0" @@ -395,7 +395,7 @@ exports[`ElecHalfHourChart component should be rendered correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY weekend bounce-3 delay--3" + class="bar-ELECTRICITY weekend bounce-3 delay--3" d=" M0,4 a4,4 0 0 1 4,-4 diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecHalfHourMonthlyAnalysis.spec.tsx.snap b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecHalfHourMonthlyAnalysis.spec.tsx.snap index e4058feef99e3df1d3e78658d97e488bcf281631..381a846028beec7849346c46e4cf5484949a4aee 100644 --- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecHalfHourMonthlyAnalysis.spec.tsx.snap +++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecHalfHourMonthlyAnalysis.spec.tsx.snap @@ -94,7 +94,7 @@ exports[`ElecHalfHourMonthlyAnalysis component should be rendered correctly when </div> </div> <button - aria-label="consumption.accessibility.button_previous_value" + aria-label="consumption.accessibility.button_next_value" class="MuiButtonBase-root MuiIconButton-root arrow-next" tabindex="0" type="button" diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecInfoModal.spec.tsx.snap b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecInfoModal.spec.tsx.snap index 2c21a4d92f1351b49bb033004ac9bc90d3c3f31e..940eea0eba67e229302d406768073180e0ad6356 100644 --- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecInfoModal.spec.tsx.snap +++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecInfoModal.spec.tsx.snap @@ -66,38 +66,31 @@ exports[`ElecInfoModal component should be rendered correctly with off-peak hour <div class="title text-18-bold" > - elec_info_modal.title1 + elec_info_modal.maxPowerTitle </div> - <div - class="text" - > - elec_info_modal.text1-1 + <div> + elec_info_modal.maxPowerDetails-1 <br /> - elec_info_modal.text1-2 + elec_info_modal.maxPowerDetails-2 </div> <div class="title text-18-bold" > - elec_info_modal.title2 + elec_info_modal.offPeakTitle </div> - <div - class="title text-18-bold" - > - elec_info_modal.title2 + <div> + elec_info_modal.offPeakDetails-1 </div> <div - class="text" + class="title text-18-bold" > - elec_info_modal.text2-1 + elec_info_modal.minPowerTitle </div> - <div - class="text" - > - elec_info_modal.text3-1 + <div> + elec_info_modal.minPowerDetails-1 <br /> - elec_info_modal.text3-2 + elec_info_modal.minPowerDetails-2 <br /> - elec_info_modal.text3-3 </div> </div> </div> @@ -176,28 +169,23 @@ exports[`ElecInfoModal component should be rendered correctly without off-peak h <div class="title text-18-bold" > - elec_info_modal.title1 + elec_info_modal.maxPowerTitle </div> - <div - class="text" - > - elec_info_modal.text1-1 + <div> + elec_info_modal.maxPowerDetails-1 <br /> - elec_info_modal.text1-2 + elec_info_modal.maxPowerDetails-2 </div> <div class="title text-18-bold" > - elec_info_modal.title2 + elec_info_modal.minPowerTitle </div> - <div - class="text" - > - elec_info_modal.text3-1 + <div> + elec_info_modal.minPowerDetails-1 <br /> - elec_info_modal.text3-2 + elec_info_modal.minPowerDetails-2 <br /> - elec_info_modal.text3-3 </div> </div> </div> diff --git a/src/components/Analysis/MaxConsumptionCard/MaxConsumptionCard.spec.tsx b/src/components/Analysis/MaxConsumptionCard/MaxConsumptionCard.spec.tsx index 18977eb0ddad4f84dfd778067a7610ab5b7d9b52..1c8b8f3414a79c8d798a69a2dca9598cb1fc9ea7 100644 --- a/src/components/Analysis/MaxConsumptionCard/MaxConsumptionCard.spec.tsx +++ b/src/components/Analysis/MaxConsumptionCard/MaxConsumptionCard.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { act, render, screen, waitFor } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import { FluidType } from 'enums' import React from 'react' @@ -35,9 +35,12 @@ describe('MaxConsumptionCard component', () => { </Provider> ) await waitFor(() => null, { container }) - const [prevButton, nextButton] = screen.getAllByRole('button') - expect(prevButton).toBeDisabled() - expect(nextButton).toBeDisabled() + expect( + screen.getByLabelText('consumption.accessibility.button_previous_value') + ).toBeDisabled() + expect( + screen.getByLabelText('consumption.accessibility.button_next_value') + ).toBeDisabled() }) it('should be rendered with several fluids and click navigate between fluid', async () => { const { container } = render( @@ -48,24 +51,33 @@ describe('MaxConsumptionCard component', () => { </Provider> ) await waitFor(() => null, { container }) - const [prevButton, nextButton] = screen.getAllByRole('button') + const prevButton = screen.getByLabelText( + 'consumption.accessibility.button_previous_value' + ) + const nextButton = screen.getByLabelText( + 'consumption.accessibility.button_next_value' + ) // navigate next - await userEvent.click(nextButton) - await waitFor(() => null, { container }) + await act(async () => { + await userEvent.click(nextButton) + }) expect(screen.getByText('FLUID.GAS.LABEL')).toBeInTheDocument() - await userEvent.click(nextButton) - await waitFor(() => null, { container }) + await act(async () => { + await userEvent.click(nextButton) + }) expect(screen.getByText('FLUID.ELECTRICITY.LABEL')).toBeInTheDocument() // navigate prev - await userEvent.click(prevButton) - await waitFor(() => null, { container }) + await act(async () => { + await userEvent.click(prevButton) + }) expect(screen.getByText('FLUID.GAS.LABEL')).toBeInTheDocument() - await userEvent.click(prevButton) - await waitFor(() => null, { container }) + await act(async () => { + await userEvent.click(prevButton) + }) expect(screen.getByText('FLUID.ELECTRICITY.LABEL')).toBeInTheDocument() }) }) diff --git a/src/components/Analysis/MaxConsumptionCard/__snapshots__/MaxConsumptionCard.spec.tsx.snap b/src/components/Analysis/MaxConsumptionCard/__snapshots__/MaxConsumptionCard.spec.tsx.snap index b731c1f5e66d493e2b636cc801f6d70b22245e9c..91030a58f917c22d05e9efbdecb62d6fb6013d9d 100644 --- a/src/components/Analysis/MaxConsumptionCard/__snapshots__/MaxConsumptionCard.spec.tsx.snap +++ b/src/components/Analysis/MaxConsumptionCard/__snapshots__/MaxConsumptionCard.spec.tsx.snap @@ -152,7 +152,7 @@ exports[`MaxConsumptionCard component should be rendered correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY undefined disabled bounce-3 delay--0" + class="bar-ELECTRICITY disabled bounce-3 delay--0" d=" M0,4 a4,4 0 0 1 4,-4 @@ -168,7 +168,7 @@ exports[`MaxConsumptionCard component should be rendered correctly 1`] = ` > <defs> <lineargradient - class="bar-ELECTRICITY undefined disabled bounce-3 delay--0" + class="bar-ELECTRICITY disabled bounce-3 delay--0" id="gradient" x1="0" x2="0" @@ -186,7 +186,7 @@ exports[`MaxConsumptionCard component should be rendered correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY undefined disabled bounce-3 delay--0" + class="bar-ELECTRICITY disabled bounce-3 delay--0" d=" M0,4 a4,4 0 0 1 4,-4 @@ -235,7 +235,7 @@ exports[`MaxConsumptionCard component should be rendered correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY undefined disabled bounce-3 delay--1" + class="bar-ELECTRICITY disabled bounce-3 delay--1" d=" M0,4 a4,4 0 0 1 4,-4 @@ -251,7 +251,7 @@ exports[`MaxConsumptionCard component should be rendered correctly 1`] = ` > <defs> <lineargradient - class="bar-ELECTRICITY undefined disabled bounce-3 delay--1" + class="bar-ELECTRICITY disabled bounce-3 delay--1" id="gradient" x1="0" x2="0" @@ -269,7 +269,7 @@ exports[`MaxConsumptionCard component should be rendered correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY undefined disabled bounce-3 delay--1" + class="bar-ELECTRICITY disabled bounce-3 delay--1" d=" M0,4 a4,4 0 0 1 4,-4 @@ -318,7 +318,7 @@ exports[`MaxConsumptionCard component should be rendered correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY bar-UNCOMING undefined disabled bounce-3 delay--2" + class="bar-ELECTRICITY bar-UPCOMING disabled bounce-3 delay--2" d=" M0,4 a4,4 0 0 1 4,-4 diff --git a/src/components/Analysis/ProfileComparator/ProfileComparator.spec.tsx b/src/components/Analysis/ProfileComparator/ProfileComparator.spec.tsx index 20b645a30b567aa285443488c39d063fcc157972..7cb6a06109365de9896a3718bc9331d70b7a8684 100644 --- a/src/components/Analysis/ProfileComparator/ProfileComparator.spec.tsx +++ b/src/components/Analysis/ProfileComparator/ProfileComparator.spec.tsx @@ -1,8 +1,7 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { act, render, screen, waitFor } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' -import { profileData } from 'tests/__mocks__/profileData.mock' import { createMockEcolyoStore, mockAnalysisState, @@ -34,7 +33,7 @@ jest.mock( () => 'mock-profileComparatorRow' ) -const profileCompleted = { ...profileData, isProfileTypeCompleted: true } +const profileCompleted = { ...mockProfileState, isProfileTypeCompleted: true } const storeProfileCompleted = createMockEcolyoStore({ profile: profileCompleted, analysis: mockAnalysisState, @@ -98,7 +97,9 @@ describe('AnalysisConsumption component', () => { expect(rows.length).toBe(4) expect(screen.getByTestId('iconGoToProfile')).toBeInTheDocument() expect(screen.queryByTestId('goToProfile')).not.toBeInTheDocument() - await userEvent.click(screen.getByTestId('iconGoToProfile')) + await act(async () => { + await userEvent.click(screen.getByTestId('iconGoToProfile')) + }) expect(mockedNavigate).toHaveBeenCalledWith('/profileType') }) }) diff --git a/src/components/Analysis/ProfileComparator/ProfileComparator.tsx b/src/components/Analysis/ProfileComparator/ProfileComparator.tsx index 284593ddff4158a6a47295e975ef8843fa33fb6d..23e00858b499b9f757f2832af1a12482b3f1a3d5 100644 --- a/src/components/Analysis/ProfileComparator/ProfileComparator.tsx +++ b/src/components/Analysis/ProfileComparator/ProfileComparator.tsx @@ -167,7 +167,7 @@ const ProfileComparator = ({ <div className="average-title">{t(`analysis.comparison`)}</div> </div> <ProfileComparatorRow - fluid={FluidType.MULTIFLUID} + fluidType={FluidType.MULTIFLUID} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} performanceValue={null} @@ -180,7 +180,7 @@ const ProfileComparator = ({ Boolean(indicator.value) && ( <ProfileComparatorRow key={FluidType[index]} - fluid={index} + fluidType={index} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} performanceValue={indicator.value} @@ -194,7 +194,7 @@ const ProfileComparator = ({ {emptyFluidTypes.map(fluid => ( <ProfileComparatorRow key={fluid} - fluid={fluid} + fluidType={fluid} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} performanceValue={null} diff --git a/src/components/Analysis/ProfileComparator/ProfileComparatorRow.spec.tsx b/src/components/Analysis/ProfileComparator/ProfileComparatorRow.spec.tsx index c1f7609879ef1d7bd1bd3e18a9515a17f560732e..62845a52f0cebe1be5cba0856bd46af2285133bc 100644 --- a/src/components/Analysis/ProfileComparator/ProfileComparatorRow.spec.tsx +++ b/src/components/Analysis/ProfileComparator/ProfileComparatorRow.spec.tsx @@ -14,7 +14,7 @@ describe('AnalysisConsumptionRow component', () => { it('should be rendered correctly for Multifluid and at least fluid connected', async () => { const { container } = render( <ProfileComparatorRow - fluid={FluidType.MULTIFLUID} + fluidType={FluidType.MULTIFLUID} userPriceConsumption={20} homePriceConsumption={18} performanceValue={null} @@ -38,7 +38,7 @@ describe('AnalysisConsumptionRow component', () => { const mockConnected = false const { container } = render( <ProfileComparatorRow - fluid={FluidType.MULTIFLUID} + fluidType={FluidType.MULTIFLUID} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} performanceValue={null} @@ -65,7 +65,7 @@ describe('AnalysisConsumptionRow component', () => { it('should be rendered correctly for singleFluid connected for average', async () => { const { container } = render( <ProfileComparatorRow - fluid={FluidType.ELECTRICITY} + fluidType={FluidType.ELECTRICITY} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} performanceValue={performanceValue} @@ -94,7 +94,7 @@ describe('AnalysisConsumptionRow component', () => { const mockConnected = false const { container } = render( <ProfileComparatorRow - fluid={FluidType.ELECTRICITY} + fluidType={FluidType.ELECTRICITY} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} performanceValue={performanceValue} @@ -123,7 +123,7 @@ describe('AnalysisConsumptionRow component', () => { const mockPerformanceValue: number | null = null const { container } = render( <ProfileComparatorRow - fluid={FluidType.ELECTRICITY} + fluidType={FluidType.ELECTRICITY} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} performanceValue={mockPerformanceValue} @@ -162,7 +162,7 @@ describe('AnalysisConsumptionRow component', () => { } const { container } = render( <ProfileComparatorRow - fluid={FluidType.ELECTRICITY} + fluidType={FluidType.ELECTRICITY} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} performanceValue={performanceValue} diff --git a/src/components/Analysis/ProfileComparator/ProfileComparatorRow.tsx b/src/components/Analysis/ProfileComparator/ProfileComparatorRow.tsx index 0ba4dd6f8bd20e733be73cf931d4f7fc505be4e2..5d9fcb185d1862c01d949ba40d0ef8cf7ac7f7df 100644 --- a/src/components/Analysis/ProfileComparator/ProfileComparatorRow.tsx +++ b/src/components/Analysis/ProfileComparator/ProfileComparatorRow.tsx @@ -7,11 +7,11 @@ import { MonthlyForecast } from 'models' import React from 'react' import ConverterService from 'services/converter.service' import { getPicto } from 'utils/picto' -import { formatNumberValues } from 'utils/utils' +import { formatNumberValues, getFluidName } from 'utils/utils' import './profileComparatorRow.scss' interface ProfileComparatorRowProps { - fluid: FluidType + fluidType: FluidType userPriceConsumption: number homePriceConsumption: number performanceValue: number | null @@ -21,7 +21,7 @@ interface ProfileComparatorRowProps { } const ProfileComparatorRow = ({ - fluid, + fluidType, userPriceConsumption, homePriceConsumption, performanceValue, @@ -31,91 +31,86 @@ const ProfileComparatorRow = ({ }: ProfileComparatorRowProps) => { const { t } = useI18n() const converterService: ConverterService = new ConverterService() - const maxPriceConsumption: number = Math.max( + const maxPriceConsumption = Math.max( userPriceConsumption, homePriceConsumption ) - const fluidLoad: number = - forecast && fluid !== FluidType.MULTIFLUID - ? forecast.fluidForecast[fluid].load - : 0 + const isMulti = fluidType === FluidType.MULTIFLUID + const isElecOrGas = + fluidType === FluidType.ELECTRICITY || fluidType === FluidType.GAS + const FLUIDNAME = getFluidName(fluidType).toUpperCase() - const formatFluidConsumptionForConso = (_fluid: FluidType) => { - if (_fluid === FluidType.MULTIFLUID) { + const fluidLoad = + forecast && !isMulti ? forecast.fluidForecast[fluidType].load : 0 + + const formatFluidConsumptionForConso = () => { + if (isMulti) { return `${formatNumberValues(userPriceConsumption).toString()} âŹ` } else { if (performanceValue) { // keeps unit in kWh for electricity and gas - if (_fluid === FluidType.ELECTRICITY || _fluid === FluidType.GAS) { + if (isElecOrGas) { return `${Math.round(performanceValue)} ${t( - `FLUID.${FluidType[_fluid]}.UNIT` + `FLUID.${FLUIDNAME}.UNIT` )}` } return performanceValue >= 1000 || fluidLoad >= 1000 ? formatNumberValues(performanceValue / 1000).toString() + ' ' + - t(`FLUID.${FluidType[_fluid]}.MEGAUNIT`) - : Math.round(performanceValue) + - ' ' + - t(`FLUID.${FluidType[_fluid]}.UNIT`) + t(`FLUID.${FLUIDNAME}.MEGAUNIT`) + : Math.round(performanceValue) + ' ' + t(`FLUID.${FLUIDNAME}.UNIT`) } else { return '-' } } } - const formatFluidConsumptionForForecast = (_fluid: FluidType) => { - if (_fluid === FluidType.MULTIFLUID) { + const formatFluidConsumptionForForecast = () => { + if (isMulti) { return `${formatNumberValues(homePriceConsumption).toString()} âŹ` } else { // keeps unit in kWh for electricity and gas - if (_fluid === FluidType.ELECTRICITY || _fluid === FluidType.GAS) { - return `${Math.round(fluidLoad)} ${t( - `FLUID.${FluidType[_fluid]}.UNIT` - )}` + if (isElecOrGas) { + return `${Math.round(fluidLoad)} ${t('FLUID.' + FLUIDNAME + '.UNIT')}` } return (performanceValue && performanceValue >= 1000) || fluidLoad >= 1000 ? formatNumberValues(fluidLoad / 1000).toString() + ' ' + - t(`FLUID.${FluidType[_fluid]}.MEGAUNIT`) - : Math.round(fluidLoad) + ' ' + t(`FLUID.${FluidType[_fluid]}.UNIT`) + t(`FLUID.${FLUIDNAME}.MEGAUNIT`) + : Math.round(fluidLoad) + ' ' + t(`FLUID.${FLUIDNAME}.UNIT`) } } - const getWidthForConso = (_fluid: FluidType) => { - if (_fluid === FluidType.MULTIFLUID) { + const getWidthForConso = () => { + if (isMulti) { return `${(userPriceConsumption / maxPriceConsumption) * 100}%` } else { return `${ - (converterService.LoadToEuro(performanceValue || 0, _fluid) / + (converterService.LoadToEuro(performanceValue || 0, fluidType) / maxPriceConsumption) * 100 }%` } } - const getWidthForForecast = (_fluid: FluidType) => { - if (_fluid === FluidType.MULTIFLUID) { + const getWidthForForecast = () => { + if (isMulti) { return `${(homePriceConsumption / maxPriceConsumption) * 100}%` } else { - const fluidValue: number = forecast - ? forecast.fluidForecast[_fluid].value - : 0 + const fluidValue = forecast ? forecast.fluidForecast[fluidType].value : 0 return `${(fluidValue / maxPriceConsumption) * 100}%` } } const comparaisonText = connected - ? formatFluidConsumptionForConso(fluid) + ? formatFluidConsumptionForConso() : t(`analysis.no_data`) return ( - <div - className={`analysisRow consumption-${FluidType[fluid].toLowerCase()}`} - > + <div className={`analysisRow consumption-${FLUIDNAME.toLowerCase()}`}> <div className="user-graph"> <div - className={classNames('price', 'text-15-bold', { + className={classNames('price text-15-bold', { ['not-connected']: !connected || noData, })} data-testid="userPrice" @@ -127,7 +122,7 @@ const ProfileComparatorRow = ({ <div className="graph" style={{ - width: getWidthForConso(fluid), + width: getWidthForConso(), }} /> </div> @@ -135,9 +130,7 @@ const ProfileComparatorRow = ({ </div> <div className="icon-container"> <StyledIcon - icon={ - fluid === FluidType.MULTIFLUID ? EuroIcon : getPicto(fluid, true) - } + icon={isMulti ? EuroIcon : getPicto(fluidType, true)} size={22} className={noData ? 'noData' : ''} /> @@ -148,7 +141,7 @@ const ProfileComparatorRow = ({ <div className="graph" style={{ - width: getWidthForForecast(fluid), + width: getWidthForForecast(), }} /> )} @@ -159,7 +152,7 @@ const ProfileComparatorRow = ({ })} data-testid="averagePrice" > - {formatFluidConsumptionForForecast(fluid)} + {formatFluidConsumptionForForecast()} </div> </div> </div> diff --git a/src/components/Analysis/ProfileComparator/__snapshots__/ProfileComparator.spec.tsx.snap b/src/components/Analysis/ProfileComparator/__snapshots__/ProfileComparator.spec.tsx.snap index c8c370b1e1cd54cf3b3e63cd84f919ea15d67680..0ec6fdfe3f1938edb876719ba7253d5b03b1752c 100644 --- a/src/components/Analysis/ProfileComparator/__snapshots__/ProfileComparator.spec.tsx.snap +++ b/src/components/Analysis/ProfileComparator/__snapshots__/ProfileComparator.spec.tsx.snap @@ -136,14 +136,14 @@ exports[`AnalysisConsumption component should be rendered correctly with profile </div> <mock-profilecomparatorrow connected="true" - fluid="3" + fluidtype="3" homepriceconsumption="0" nodata="false" userpriceconsumption="156.161853" /> <mock-profilecomparatorrow connected="true" - fluid="0" + fluidtype="0" homepriceconsumption="0" nodata="false" performancevalue="178.54" @@ -151,7 +151,7 @@ exports[`AnalysisConsumption component should be rendered correctly with profile /> <mock-profilecomparatorrow connected="true" - fluid="1" + fluidtype="1" homepriceconsumption="0" nodata="false" performancevalue="7763.98" @@ -159,7 +159,7 @@ exports[`AnalysisConsumption component should be rendered correctly with profile /> <mock-profilecomparatorrow connected="true" - fluid="2" + fluidtype="2" homepriceconsumption="0" nodata="false" performancevalue="1317.67" diff --git a/src/components/Challenge/ChallengeCardDone/ChallengeCardDone.spec.tsx b/src/components/Challenge/ChallengeCardDone/ChallengeCardDone.spec.tsx index 56ef37bff49697a3fc05c852f9ef7586b8b3c7eb..d62b47d36700bfebc8cddf816d03ef991ab9ba93 100644 --- a/src/components/Challenge/ChallengeCardDone/ChallengeCardDone.spec.tsx +++ b/src/components/Challenge/ChallengeCardDone/ChallengeCardDone.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import ChallengeCardDone from 'components/Challenge/ChallengeCardDone/ChallengeCardDone' import React from 'react' @@ -44,10 +44,10 @@ describe('ChallengeCardDone component', () => { <ChallengeCardDone userChallenge={userChallengeData[0]} /> </Provider> ) - const resetButton = screen.getByRole('button', { - name: 'challenge.card_done.reset_defi', + const resetButton = screen.getByText('challenge.card_done.reset_defi') + await act(async () => { + await userEvent.click(resetButton) }) - await userEvent.click(resetButton) expect(mockDispatch).toHaveBeenCalledWith({ type: 'challenge/updateUserChallengeList', }) @@ -65,10 +65,8 @@ describe('ChallengeCardDone component', () => { <ChallengeCardDone userChallenge={userChallengeData[0]} /> </Provider> ) - const resetButton = screen.getByRole('button', { - name: 'challenge.card_done.reset_defi', - }) - expect(resetButton).toHaveProperty('disabled') + const resetBtn = screen.getByLabelText('challenge.card_done.reset_defi') + expect(resetBtn).toHaveProperty('disabled') expect(mockDispatch).toHaveBeenCalledTimes(0) expect(mockUpdateUserChallenge).toHaveBeenCalledTimes(0) }) @@ -78,10 +76,8 @@ describe('ChallengeCardDone component', () => { <ChallengeCardDone userChallenge={userChallengeData[1]} /> </Provider> ) - const resetButton = screen.getByRole('button', { - name: 'challenge.card_done.reset_defi', - }) - expect(resetButton).toHaveClass('btnPrimaryNegative') + const resetBtn = screen.getByLabelText('challenge.card_done.reset_defi') + expect(resetBtn).toHaveClass('btnPrimaryNegative') }) it('should be secondary button is challenge is won', async () => { render( @@ -89,10 +85,8 @@ describe('ChallengeCardDone component', () => { <ChallengeCardDone userChallenge={userChallengeData[0]} /> </Provider> ) - const resetButton = screen.getByRole('button', { - name: 'challenge.card_done.reset_defi', - }) - expect(resetButton).toHaveClass('btnSecondary') + const resetBtn = screen.getByLabelText('challenge.card_done.reset_defi') + expect(resetBtn).toHaveClass('btnSecondary') }) }) }) diff --git a/src/components/Challenge/ChallengeCardLast/ChallengeCardLast.spec.tsx b/src/components/Challenge/ChallengeCardLast/ChallengeCardLast.spec.tsx index 17fb028e94d666557241b6bbe591b932a966472f..f4825300a48bdda50d2ff32104a254aaca806a28 100644 --- a/src/components/Challenge/ChallengeCardLast/ChallengeCardLast.spec.tsx +++ b/src/components/Challenge/ChallengeCardLast/ChallengeCardLast.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import ChallengeCardLast from './ChallengeCardLast' @@ -16,7 +16,9 @@ describe('ChallengeCardLast component', () => { global.open = jest.fn() render(<ChallengeCardLast />) - await userEvent.click(screen.getByRole('button')) + await act(async () => { + await userEvent.click(screen.getByText('challenge.card_last.button')) + }) expect(window.open).toHaveBeenCalledTimes(1) expect(global.open).toHaveBeenCalledWith( `${__SAU_IDEA_DIRECT_LINK__}?version=0.0.0` diff --git a/src/components/Challenge/ChallengeCardOnGoing/ChallengeCardOnGoing.tsx b/src/components/Challenge/ChallengeCardOnGoing/ChallengeCardOnGoing.tsx index 028d698b59eac6ab1ff51946552ec155364d1d8c..cdeb8d6ced57367c32eee675b20683aba75b66dd 100644 --- a/src/components/Challenge/ChallengeCardOnGoing/ChallengeCardOnGoing.tsx +++ b/src/components/Challenge/ChallengeCardOnGoing/ChallengeCardOnGoing.tsx @@ -19,7 +19,7 @@ import { UserQuizState, } from 'enums' import { UserChallenge } from 'models' -import React, { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' import ChallengeService from 'services/challenge.service' import { updateUserChallengeList } from 'store/challenge/challenge.slice' @@ -59,12 +59,13 @@ const ChallengeCardOnGoing = ({ setIsOneFluidUp(prev => !prev) }, []) + const challengeService = useMemo(() => new ChallengeService(client), [client]) + const goDuel = async () => { setIsLoading(true) // Check if at least one fluid is up if (fluidTypes.length !== 0) { if (userChallenge.duel.state !== UserDuelState.ONGOING) { - const challengeService = new ChallengeService(client) const updatedChallenge = await challengeService.updateUserChallenge( userChallenge, UserChallengeUpdateFlag.DUEL_UPDATE_THRESHOLD, @@ -83,7 +84,6 @@ const ChallengeCardOnGoing = ({ const goQuiz = async () => { if (userChallenge.quiz.state !== UserQuizState.ONGOING) { - const challengeService = new ChallengeService(client) const updatedChallenge = await challengeService.updateUserChallenge( userChallenge, UserChallengeUpdateFlag.QUIZ_RESET @@ -119,7 +119,6 @@ const ChallengeCardOnGoing = ({ }, [userChallenge]) useEffect(() => { - const challengeService = new ChallengeService(client) let subscribed = true async function setChallengeResult() { const isChallengeDone = await challengeService.isChallengeDone( @@ -156,7 +155,7 @@ const ChallengeCardOnGoing = ({ return () => { subscribed = false } - }, [client, currentDataload, userChallenge, dispatch]) + }, [currentDataload, userChallenge, dispatch, challengeService]) const quizButton = () => ( <Button diff --git a/src/components/Challenge/ChallengeCardUnlocked/ChallengeCardUnlocked.spec.tsx b/src/components/Challenge/ChallengeCardUnlocked/ChallengeCardUnlocked.spec.tsx index 46c7d7a5ff3dbaa547b01c59af4ec345d753f0ea..89209571b601b886564d81ed83d6821e989b787f 100644 --- a/src/components/Challenge/ChallengeCardUnlocked/ChallengeCardUnlocked.spec.tsx +++ b/src/components/Challenge/ChallengeCardUnlocked/ChallengeCardUnlocked.spec.tsx @@ -1,6 +1,6 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' -import { FluidType } from 'enums' +import { FluidState, FluidType } from 'enums' import React from 'react' import { Provider } from 'react-redux' import { @@ -27,7 +27,9 @@ describe('ChallengeCardUnlocked component', () => { </Provider> ) expect(screen.getByText('Simone VEILLE')).toBeInTheDocument() - expect(screen.getByRole('button')).toBeInTheDocument() + expect( + screen.getByText('challenge.card_unlocked.button_launch') + ).toBeInTheDocument() expect(screen.queryAllByRole('dialog').length).toBeFalsy() }) @@ -37,7 +39,11 @@ describe('ChallengeCardUnlocked component', () => { <ChallengeCardUnlocked userChallenge={userChallengeData[0]} /> </Provider> ) - await userEvent.click(screen.getByRole('button')) + await act(async () => { + await userEvent.click( + screen.getByText('challenge.card_unlocked.button_launch') + ) + }) expect(screen.queryAllByRole('dialog').length).toBeTruthy() }) @@ -45,7 +51,9 @@ describe('ChallengeCardUnlocked component', () => { const store = createMockEcolyoStore({ global: { fluidTypes: [FluidType.ELECTRICITY], - fluidStatus: [{ ...mockGlobalState.fluidStatus[0], status: 200 }], + fluidStatus: [ + { ...mockGlobalState.fluidStatus[0], status: FluidState.DONE }, + ], }, challenge: mockChallengeState, }) @@ -54,7 +62,11 @@ describe('ChallengeCardUnlocked component', () => { <ChallengeCardUnlocked userChallenge={userChallengeData[0]} /> </Provider> ) - await userEvent.click(screen.getByRole('button')) + await act(async () => { + await userEvent.click( + screen.getByText('challenge.card_unlocked.button_launch') + ) + }) expect(screen.queryAllByRole('dialog').length).toBeFalsy() expect(mockStartUserChallenge).toHaveBeenCalledWith(userChallengeData[0]) }) @@ -72,7 +84,9 @@ describe('ChallengeCardUnlocked component', () => { <ChallengeCardUnlocked userChallenge={userChallengeData[0]} /> </Provider> ) - const resetButton = screen.getByRole('button') - expect(resetButton).toHaveAttribute('disabled') + const launchBtn = screen.getByLabelText( + 'challenge.accessibility.button_launch' + ) + expect(launchBtn).toHaveAttribute('disabled') }) }) diff --git a/src/components/Challenge/ChallengeView.tsx b/src/components/Challenge/ChallengeView.tsx index d55765ebf5596fe03eb72362b2959a7031e826b7..6affdfee88aa882dd7fa7bd22f07be87ee98b64e 100644 --- a/src/components/Challenge/ChallengeView.tsx +++ b/src/components/Challenge/ChallengeView.tsx @@ -21,7 +21,6 @@ const ChallengeView = () => { const cardWidth = window.outerWidth < 500 ? window.outerWidth - marginPx * 6 : 285 const cardHeight = window.outerHeight * 0.65 - const [headerHeight, setHeaderHeight] = useState<number>(0) const [touchStart, setTouchStart] = useState<number>() const [touchEnd, setTouchEnd] = useState<number>() const [index, setIndex] = useState<number>(0) @@ -99,11 +98,8 @@ const ChallengeView = () => { return ( <> <CozyBar titleKey="common.title_challenge" /> - <Header - setHeaderHeight={setHeaderHeight} - desktopTitleKey="common.title_challenge" - /> - <Content heightOffset={headerHeight}> + <Header desktopTitleKey="common.title_challenge" /> + <Content> <div className="challengeSlider" onClick={resetValues} diff --git a/src/components/Challenge/__snapshots__/ChallengeView.spec.tsx.snap b/src/components/Challenge/__snapshots__/ChallengeView.spec.tsx.snap index 3f3366ca5dc5e7798dbbe06ec4710fca25d1e5c3..0f0c1a5cb86a6d1aa750d9fccde498df95890c96 100644 --- a/src/components/Challenge/__snapshots__/ChallengeView.spec.tsx.snap +++ b/src/components/Challenge/__snapshots__/ChallengeView.spec.tsx.snap @@ -8,9 +8,7 @@ exports[`ChallengeView component should be rendered correctly 1`] = ` <mock-header desktoptitlekey="common.title_challenge" /> - <mock-content - heightoffset="0" - > + <mock-content> <div class="challengeSlider" > diff --git a/src/components/Charts/AxisRight.tsx b/src/components/Charts/AxisRight.tsx index 71b7dac5e957d07822b173b39ec51f0656aa850e..7780c52ac0beb1e062554e7160423c5306a35883 100644 --- a/src/components/Charts/AxisRight.tsx +++ b/src/components/Charts/AxisRight.tsx @@ -5,6 +5,7 @@ import { ScaleLinear } from 'd3-scale' import { select, selectAll } from 'd3-selection' import { FluidType } from 'enums' import React, { useEffect, useRef } from 'react' +import { getFluidName } from 'utils/utils' interface AxisRightProps { yScale: ScaleLinear<number, number> @@ -24,12 +25,10 @@ const AxisRight = ({ isAnalysis, }: AxisRightProps) => { const { t } = useI18n() - const fluidStyle = - fluidType === FluidType.MULTIFLUID ? 'MULTIFLUID' : FluidType[fluidType] - const isMultiFluid = fluidStyle !== 'MULTIFLUID' + const isMulti = fluidType === FluidType.MULTIFLUID + const fluidStyle = getFluidName(fluidType).toLocaleUpperCase() const yAxisRef = useRef<SVGGElement>(null) - const newMarginRight = - fluidType === FluidType.MULTIFLUID ? marginRight - 10 : marginRight + const newMarginRight = isMulti ? marginRight - 10 : marginRight const formatFluidValue = (value: NumberValue, unit: 'UNIT' | 'MEGAUNIT') => { const fluidUnit = t(`FLUID.${fluidStyle}.${unit}`) @@ -54,7 +53,7 @@ const AxisRight = ({ .tickSize(-width) .tickSizeOuter(0) .tickFormat(d => - Number(d) >= 1000 && isMultiFluid + Number(d) >= 1000 && !isMulti ? formatMultiFluidValue(d) : formatSingleFluidValue(d) ) diff --git a/src/components/Charts/Bar.spec.tsx b/src/components/Charts/Bar.spec.tsx index adb15d3502c845b9494097d54d3d5d16d6a5e585..73cf573db8ade5ee375ebe7be2e2fac8db7d7549 100644 --- a/src/components/Charts/Bar.spec.tsx +++ b/src/components/Charts/Bar.spec.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react' +import { act, render } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import { scaleLinear } from 'd3' import { FluidType, TimeStep } from 'enums' @@ -122,7 +122,9 @@ describe('Bar component test', () => { </svg> </Provider> ) - await userEvent.click(container.querySelector('rect') as Element) + await act(async () => { + await userEvent.click(container.querySelector('rect') as Element) + }) expect(setSelectedDateSpy).toHaveBeenCalledTimes(1) expect(setSelectedDateSpy).toHaveBeenCalledWith( graphData.actualData[0].date diff --git a/src/components/Charts/Bar.tsx b/src/components/Charts/Bar.tsx index 62ca898f1ec8a8c770095689814a9ab8bb715593..0b6ccdb99804c32a1349444c92be2729f083d9c7 100644 --- a/src/components/Charts/Bar.tsx +++ b/src/components/Charts/Bar.tsx @@ -10,6 +10,7 @@ import { setSelectedDate, } from 'store/chart/chart.slice' import { useAppDispatch, useAppSelector } from 'store/hooks' +import { getFluidName } from 'utils/utils' interface BarProps { index: number @@ -56,9 +57,8 @@ const Bar = ({ const [compareAnimationEnded, setCompareAnimationEnded] = useState(false) const browser = detect() - const fluidStyle = - fluidType === FluidType.MULTIFLUID ? 'MULTIFLUID' : FluidType[fluidType] const isMulti = fluidType === FluidType.MULTIFLUID + const FLUIDNAME = getFluidName(fluidType).toUpperCase() const handleClick = () => { if (!isSwitching && !isDuel && clickable) { @@ -129,11 +129,20 @@ const Bar = ({ const selected = isSelectedDate ? 'selected' : '' const getBarClass = () => { - const upcoming = dataload.value === -1 ? 'bar-UNCOMING' : '' + const upcoming = dataload.value === -1 ? 'bar-UPCOMING' : '' const edgeBrowser = browser && browser.name !== 'edge' const bounce = edgeBrowser ? '1' : '3' - const baseStyles = `bar-${fluidStyle} ${upcoming} ${weekdays} ${selected} ${disabled}` + const baseStyles = [ + `bar-${FLUIDNAME}`, + upcoming, + weekdays, + selected, + disabled, + ] + .filter(Boolean) + .join(' ') + if (clicked) { return `${baseStyles} ${clickedAnim}` } @@ -147,7 +156,7 @@ const Bar = ({ const animate = `bounce-2 delay--${clicked ? 0 : index}` const animationClass = compareAnimationEnded ? '' : animate - return `bar-compare-${fluidStyle} ${selected} ${animationClass} ${clickedAnim}` + return `bar-compare-${FLUIDNAME} ${selected} ${animationClass} ${clickedAnim}` } const barBackgroundClass = isSelectedDate diff --git a/src/components/Charts/UncomingBar.spec.tsx b/src/components/Charts/UpcomingBar.spec.tsx similarity index 84% rename from src/components/Charts/UncomingBar.spec.tsx rename to src/components/Charts/UpcomingBar.spec.tsx index 30f5cb0cc48a9a054940be9528373826093c53b8..a84b0d4f20bdd91c54daa565fea05780efe85ea1 100644 --- a/src/components/Charts/UncomingBar.spec.tsx +++ b/src/components/Charts/UpcomingBar.spec.tsx @@ -3,13 +3,13 @@ import { scaleLinear } from 'd3' import React from 'react' import { dataLoadArray } from 'tests/__mocks__/chartData.mock' import { mockXScale } from 'tests/__mocks__/xScale.mock' -import UncomingBar from './UncomingBar' +import UpcomingBar from './UpcomingBar' -describe('Uncoming component', () => { +describe('Upcoming component', () => { it('should match snapshot', () => { const { container } = render( <svg> - <UncomingBar + <UpcomingBar index={0} average={10} dataload={dataLoadArray[0]} diff --git a/src/components/Charts/UncomingBar.tsx b/src/components/Charts/UpcomingBar.tsx similarity index 95% rename from src/components/Charts/UncomingBar.tsx rename to src/components/Charts/UpcomingBar.tsx index 32dcb3c1544e50f9764f8ac90f9ed4632136451e..7022e3766230b8739f343894cd018bcc4413ff04 100644 --- a/src/components/Charts/UncomingBar.tsx +++ b/src/components/Charts/UpcomingBar.tsx @@ -13,7 +13,7 @@ interface BarProps { average: number } -const UncomingBar = ({ +const UpcomingBar = ({ index, dataload, xScale, @@ -32,7 +32,7 @@ const UncomingBar = ({ animationClass = browser?.name !== 'edge' ? 'bounce-1' : 'bounce-3' animationClass += ` delay--${index % 13}` } - const barClass = `bar-UNCOMING ${animationClass}` + const barClass = `bar-UPCOMING ${animationClass}` const topRoundedRectDashedLine = ( x: number, @@ -114,4 +114,4 @@ const UncomingBar = ({ ) } -export default UncomingBar +export default UpcomingBar diff --git a/src/components/Charts/__snapshots__/Bar.spec.tsx.snap b/src/components/Charts/__snapshots__/Bar.spec.tsx.snap index e3916b074b4eaaf5f36842494457fd46f1c40607..4b70e2896a14a9e91562af3834b1a8ffaae5d9cd 100644 --- a/src/components/Charts/__snapshots__/Bar.spec.tsx.snap +++ b/src/components/Charts/__snapshots__/Bar.spec.tsx.snap @@ -40,7 +40,7 @@ exports[`Bar component test should correctly render Bar with isDuel 1`] = ` </lineargradient> </defs> <path - class="bar-MULTIFLUID undefined bounce-3 delay--4" + class="bar-MULTIFLUID bounce-3 delay--4" d=" M0,-12.295074999999997 a-12.295074999999997,-12.295074999999997 0 0 1 -12.295074999999997,12.295074999999997 @@ -56,7 +56,7 @@ exports[`Bar component test should correctly render Bar with isDuel 1`] = ` > <defs> <lineargradient - class="bar-MULTIFLUID undefined bounce-3 delay--4" + class="bar-MULTIFLUID bounce-3 delay--4" id="gradient" x1="0" x2="0" @@ -130,7 +130,7 @@ exports[`Bar component test should correctly render Bar with isSwitching 1`] = ` </lineargradient> </defs> <path - class="bar-MULTIFLUID undefined selected bounce-2 delay" + class="bar-MULTIFLUID selected bounce-2 delay" d=" M0,-12.295074999999997 a-12.295074999999997,-12.295074999999997 0 0 1 -12.295074999999997,12.295074999999997 @@ -146,7 +146,7 @@ exports[`Bar component test should correctly render Bar with isSwitching 1`] = ` > <defs> <lineargradient - class="bar-MULTIFLUID undefined selected bounce-2 delay" + class="bar-MULTIFLUID selected bounce-2 delay" id="gradient" x1="0" x2="0" @@ -164,7 +164,7 @@ exports[`Bar component test should correctly render Bar with isSwitching 1`] = ` </lineargradient> </defs> <path - class="bar-MULTIFLUID undefined selected bounce-2 delay" + class="bar-MULTIFLUID selected bounce-2 delay" d=" M0,-12.295074999999997 a-12.295074999999997,-12.295074999999997 0 0 1 -12.295074999999997,12.295074999999997 @@ -220,7 +220,7 @@ exports[`Bar component test should correctly render Bar with showCompare 1`] = ` </lineargradient> </defs> <path - class="bar-MULTIFLUID undefined selected bounce-2 delay" + class="bar-MULTIFLUID selected bounce-2 delay" d=" M1.2500000000000002,-12.295074999999997 a-12.295074999999997,-12.295074999999997 0 0 1 -12.295074999999997,12.295074999999997 @@ -236,7 +236,7 @@ exports[`Bar component test should correctly render Bar with showCompare 1`] = ` > <defs> <lineargradient - class="bar-MULTIFLUID undefined selected bounce-2 delay" + class="bar-MULTIFLUID selected bounce-2 delay" id="gradient" x1="0" x2="0" @@ -254,7 +254,7 @@ exports[`Bar component test should correctly render Bar with showCompare 1`] = ` </lineargradient> </defs> <path - class="bar-MULTIFLUID undefined selected bounce-2 delay" + class="bar-MULTIFLUID selected bounce-2 delay" d=" M1.2500000000000002,-12.295074999999997 a-12.295074999999997,-12.295074999999997 0 0 1 -12.295074999999997,12.295074999999997 @@ -344,7 +344,7 @@ exports[`Bar component test should correctly render Electricity Bar 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY undefined selected bounce-2 delay" + class="bar-ELECTRICITY selected bounce-2 delay" d=" M0,-12.295074999999997 a-12.295074999999997,-12.295074999999997 0 0 1 -12.295074999999997,12.295074999999997 @@ -360,7 +360,7 @@ exports[`Bar component test should correctly render Electricity Bar 1`] = ` > <defs> <lineargradient - class="bar-ELECTRICITY undefined selected bounce-2 delay" + class="bar-ELECTRICITY selected bounce-2 delay" id="gradient" x1="0" x2="0" @@ -378,7 +378,7 @@ exports[`Bar component test should correctly render Electricity Bar 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY undefined selected bounce-2 delay" + class="bar-ELECTRICITY selected bounce-2 delay" d=" M0,-12.295074999999997 a-12.295074999999997,-12.295074999999997 0 0 1 -12.295074999999997,12.295074999999997 @@ -434,7 +434,7 @@ exports[`Bar component test should correctly render Gas Bar 1`] = ` </lineargradient> </defs> <path - class="bar-GAS undefined selected bounce-2 delay" + class="bar-GAS selected bounce-2 delay" d=" M0,-12.295074999999997 a-12.295074999999997,-12.295074999999997 0 0 1 -12.295074999999997,12.295074999999997 @@ -450,7 +450,7 @@ exports[`Bar component test should correctly render Gas Bar 1`] = ` > <defs> <lineargradient - class="bar-GAS undefined selected bounce-2 delay" + class="bar-GAS selected bounce-2 delay" id="gradient" x1="0" x2="0" @@ -468,7 +468,7 @@ exports[`Bar component test should correctly render Gas Bar 1`] = ` </lineargradient> </defs> <path - class="bar-GAS undefined selected bounce-2 delay" + class="bar-GAS selected bounce-2 delay" d=" M0,-12.295074999999997 a-12.295074999999997,-12.295074999999997 0 0 1 -12.295074999999997,12.295074999999997 @@ -524,7 +524,7 @@ exports[`Bar component test should correctly render Multifluid Bar 1`] = ` </lineargradient> </defs> <path - class="bar-MULTIFLUID undefined selected bounce-2 delay" + class="bar-MULTIFLUID selected bounce-2 delay" d=" M0,-12.295074999999997 a-12.295074999999997,-12.295074999999997 0 0 1 -12.295074999999997,12.295074999999997 @@ -540,7 +540,7 @@ exports[`Bar component test should correctly render Multifluid Bar 1`] = ` > <defs> <lineargradient - class="bar-MULTIFLUID undefined selected bounce-2 delay" + class="bar-MULTIFLUID selected bounce-2 delay" id="gradient" x1="0" x2="0" @@ -558,7 +558,7 @@ exports[`Bar component test should correctly render Multifluid Bar 1`] = ` </lineargradient> </defs> <path - class="bar-MULTIFLUID undefined selected bounce-2 delay" + class="bar-MULTIFLUID selected bounce-2 delay" d=" M0,-12.295074999999997 a-12.295074999999997,-12.295074999999997 0 0 1 -12.295074999999997,12.295074999999997 @@ -614,7 +614,7 @@ exports[`Bar component test should correctly render Water Bar 1`] = ` </lineargradient> </defs> <path - class="bar-WATER undefined selected bounce-2 delay" + class="bar-WATER selected bounce-2 delay" d=" M0,-12.295074999999997 a-12.295074999999997,-12.295074999999997 0 0 1 -12.295074999999997,12.295074999999997 @@ -630,7 +630,7 @@ exports[`Bar component test should correctly render Water Bar 1`] = ` > <defs> <lineargradient - class="bar-WATER undefined selected bounce-2 delay" + class="bar-WATER selected bounce-2 delay" id="gradient" x1="0" x2="0" @@ -648,7 +648,7 @@ exports[`Bar component test should correctly render Water Bar 1`] = ` </lineargradient> </defs> <path - class="bar-WATER undefined selected bounce-2 delay" + class="bar-WATER selected bounce-2 delay" d=" M0,-12.295074999999997 a-12.295074999999997,-12.295074999999997 0 0 1 -12.295074999999997,12.295074999999997 diff --git a/src/components/Charts/__snapshots__/BarChart.spec.tsx.snap b/src/components/Charts/__snapshots__/BarChart.spec.tsx.snap index 57ffaf1973a018c655d6a057bfe19515c1e904a5..9df650b27b60a6bfb1e770e9d86a1a0f94c6e7a2 100644 --- a/src/components/Charts/__snapshots__/BarChart.spec.tsx.snap +++ b/src/components/Charts/__snapshots__/BarChart.spec.tsx.snap @@ -127,7 +127,7 @@ exports[`BarChart component should render correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY undefined selected bounce-2 delay" + class="bar-ELECTRICITY selected bounce-2 delay" d=" M0,4 a4,4 0 0 1 4,-4 @@ -143,7 +143,7 @@ exports[`BarChart component should render correctly 1`] = ` > <defs> <lineargradient - class="bar-ELECTRICITY undefined selected bounce-2 delay" + class="bar-ELECTRICITY selected bounce-2 delay" id="gradient" x1="0" x2="0" @@ -161,7 +161,7 @@ exports[`BarChart component should render correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY undefined selected bounce-2 delay" + class="bar-ELECTRICITY selected bounce-2 delay" d=" M0,4 a4,4 0 0 1 4,-4 @@ -210,7 +210,7 @@ exports[`BarChart component should render correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY undefined selected bounce-2 delay" + class="bar-ELECTRICITY selected bounce-2 delay" d=" M0,4 a4,4 0 0 1 4,-4 @@ -226,7 +226,7 @@ exports[`BarChart component should render correctly 1`] = ` > <defs> <lineargradient - class="bar-ELECTRICITY undefined selected bounce-2 delay" + class="bar-ELECTRICITY selected bounce-2 delay" id="gradient" x1="0" x2="0" @@ -244,7 +244,7 @@ exports[`BarChart component should render correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY undefined selected bounce-2 delay" + class="bar-ELECTRICITY selected bounce-2 delay" d=" M0,4 a4,4 0 0 1 4,-4 @@ -293,7 +293,7 @@ exports[`BarChart component should render correctly 1`] = ` </lineargradient> </defs> <path - class="bar-ELECTRICITY bar-UNCOMING undefined selected bounce-2 delay" + class="bar-ELECTRICITY bar-UPCOMING selected bounce-2 delay" d=" M0,4 a4,4 0 0 1 4,-4 diff --git a/src/components/Charts/__snapshots__/UncomingBar.spec.tsx.snap b/src/components/Charts/__snapshots__/UpcomingBar.spec.tsx.snap similarity index 84% rename from src/components/Charts/__snapshots__/UncomingBar.spec.tsx.snap rename to src/components/Charts/__snapshots__/UpcomingBar.spec.tsx.snap index 0b62bbcd9d26f68c31cfcd922a16a6c611442d1d..c54a776a9e4502dc173f0c5946ab2e07c3f5fac1 100644 --- a/src/components/Charts/__snapshots__/UncomingBar.spec.tsx.snap +++ b/src/components/Charts/__snapshots__/UpcomingBar.spec.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Uncoming component should match snapshot 1`] = ` +exports[`Upcoming component should match snapshot 1`] = ` <div> <svg> <g> @@ -20,7 +20,7 @@ exports[`Uncoming component should match snapshot 1`] = ` transform="translate(undefined, 10)" > <path - class="bar-UNCOMING bounce-1 delay--0" + class="bar-UPCOMING bounce-1 delay--0" d="M0,40v-36 a4,4 0 0 1 4,-4h-5.5a4,4 0 0 1 4,4v36" fill="url(#gradient)" stroke="#61f0f2" diff --git a/src/components/CommonKit/FormNavigation/FormNavigation.spec.tsx b/src/components/CommonKit/FormNavigation/FormNavigation.spec.tsx index b9be78286030fbeb9deedf9ac78ed3b28526f7e6..0d8be396b947acfd4f3ea589bacc19da9cabb42f 100644 --- a/src/components/CommonKit/FormNavigation/FormNavigation.spec.tsx +++ b/src/components/CommonKit/FormNavigation/FormNavigation.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import FormNavigation from './FormNavigation' @@ -15,10 +15,15 @@ describe('FormNavigation component', () => { disablePrevButton={false} /> ) - const [prevButton, nextButton] = screen.getAllByRole('button') - await userEvent.click(prevButton) + await act(async () => { + await userEvent.click( + screen.getByText('profile_type.form.button_previous') + ) + }) expect(mockHandlePrevious).toHaveBeenCalled() - await userEvent.click(nextButton) + await act(async () => { + await userEvent.click(screen.getByText('profile_type.form.button_next')) + }) expect(mockHandleNext).toHaveBeenCalled() }) }) diff --git a/src/components/CommonKit/Switch/StyledSwitch.tsx b/src/components/CommonKit/Switch/StyledSwitch.tsx index d8136853ef6452b872454f953f6f3ba6c8db0088..bd890d435fe1ef6051610d1ef4280a411016843b 100644 --- a/src/components/CommonKit/Switch/StyledSwitch.tsx +++ b/src/components/CommonKit/Switch/StyledSwitch.tsx @@ -72,20 +72,19 @@ interface StyledSwitchProps extends SwitchProps { } const StyledSwitch = ({ fluidType, ...props }: StyledSwitchProps) => { - if (fluidType !== undefined) { - switch (fluidType) { - case FluidType.ELECTRICITY: - return <SwitchElec {...props} /> - case FluidType.WATER: - return <SwitchWater {...props} /> - case FluidType.GAS: - return <SwitchGas {...props} /> - default: - return <SwitchBase {...props} /> - } - } else { + if (fluidType === undefined) { return <SwitchBase {...props} /> } + switch (fluidType) { + case FluidType.ELECTRICITY: + return <SwitchElec {...props} /> + case FluidType.WATER: + return <SwitchWater {...props} /> + case FluidType.GAS: + return <SwitchGas {...props} /> + default: + return <SwitchBase {...props} /> + } } export default StyledSwitch diff --git a/src/components/Connection/EPGLConnect/EpglForm.tsx b/src/components/Connection/EPGLConnect/EpglForm.tsx index 0ae8fc6a35f01ddadba355bd9d378ed27f144112..14ba3dd2c8b8fb66ccac624b268666b7f2acacc4 100644 --- a/src/components/Connection/EPGLConnect/EpglForm.tsx +++ b/src/components/Connection/EPGLConnect/EpglForm.tsx @@ -34,10 +34,9 @@ const EpglForm = ({ hasCreatedAccount }: { hasCreatedAccount: boolean }) => { }) const changeLogin = (value: string) => { - if ((/\d/.test(value) && value.length <= 7) || value === '') { - setError('') - setLogin(value) - } + if (value.toString().length > 7) return + setError('') + setLogin(value) } const changePassword = (value: string) => { diff --git a/src/components/Connection/ExpiredConsentModal/ExpiredConsentModal.spec.tsx b/src/components/Connection/ExpiredConsentModal/ExpiredConsentModal.spec.tsx index cf6cc00505123e43982dd9e58a8caae962f7ca0e..a5edf19121aeb28e37bb93818c5d2b77a684d0a2 100644 --- a/src/components/Connection/ExpiredConsentModal/ExpiredConsentModal.spec.tsx +++ b/src/components/Connection/ExpiredConsentModal/ExpiredConsentModal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import { FluidType } from 'enums' import React from 'react' @@ -45,9 +45,9 @@ describe('ExpiredConsentModal component', () => { /> </Provider> ) - await userEvent.click( - screen.getByRole('button', { name: 'consent_outdated.go' }) - ) + await act(async () => { + await userEvent.click(screen.getByText('consent_outdated.go')) + }) expect(mockAppDispatch).toHaveBeenCalledTimes(1) expect(mockedNavigate).toHaveBeenCalledTimes(1) }) @@ -65,9 +65,9 @@ describe('ExpiredConsentModal component', () => { /> </Provider> ) - await userEvent.click( - screen.getByRole('button', { name: 'consent_outdated.go' }) - ) + await act(async () => { + await userEvent.click(screen.getByText('consent_outdated.yes')) + }) expect(mockAppDispatch).toHaveBeenCalledTimes(1) expect(mockedNavigate).toHaveBeenCalledTimes(1) }) @@ -82,9 +82,9 @@ describe('ExpiredConsentModal component', () => { /> </Provider> ) - await userEvent.click( - screen.getByRole('button', { name: 'consent_outdated.later' }) - ) + await act(async () => { + await userEvent.click(screen.getByText('consent_outdated.no')) + }) expect(mockHandleCloseClick).toHaveBeenCalled() }) }) diff --git a/src/components/Connection/ExpiredConsentModal/ExpiredConsentModal.tsx b/src/components/Connection/ExpiredConsentModal/ExpiredConsentModal.tsx index f922cb1ce2f99aed1e069ea04521dbcec4e033d6..577a4932ce4f5f05cf82611e0f7622d8e6992f49 100644 --- a/src/components/Connection/ExpiredConsentModal/ExpiredConsentModal.tsx +++ b/src/components/Connection/ExpiredConsentModal/ExpiredConsentModal.tsx @@ -54,9 +54,13 @@ const ExpiredConsentModal = ({ }) ) dispatch(setShouldRefreshConsent(true)) + toggleModal() + navigate(`/consumption/${FluidType[fluidType].toLocaleLowerCase()}`) + } + if (fluidType === FluidType.GAS) { + toggleModal() + navigate(`/connect/${FluidType[fluidType].toLocaleLowerCase()}`) } - toggleModal() - navigate(`/consumption/${FluidType[fluidType].toLocaleLowerCase()}`) }, [dispatch, fluidStatus, fluidType, navigate, toggleModal]) return ( diff --git a/src/components/Connection/ExpiredConsentModal/expiredConsentModal.scss b/src/components/Connection/ExpiredConsentModal/expiredConsentModal.scss index 929dea4460217dddf286f52f02f557ff0b8ec407..758ca858135b774a0515777af0550aed301c214c 100644 --- a/src/components/Connection/ExpiredConsentModal/expiredConsentModal.scss +++ b/src/components/Connection/ExpiredConsentModal/expiredConsentModal.scss @@ -3,7 +3,7 @@ .expired-consent-modal { display: flex; flex-direction: column; - gap: 1rem; + gap: 24px; color: $grey-bright; .icon-main { diff --git a/src/components/Connection/GRDFConnect/GrdfConnectView.tsx b/src/components/Connection/GRDFConnect/GrdfConnectView.tsx index 1ecf2d104ed46ae0f455995c9d6da648fbc828bd..0ec4d939b815b6d39ce3aebf5b7d35bde7cf85d2 100644 --- a/src/components/Connection/GRDFConnect/GrdfConnectView.tsx +++ b/src/components/Connection/GRDFConnect/GrdfConnectView.tsx @@ -20,27 +20,24 @@ export enum GrdfStep { } export const GrdfConnectView = () => { - const [headerHeight, setHeaderHeight] = useState<number>(0) - const [launchConnection, setLaunchConnection] = useState(false) const navigate = useNavigate() + const { data: instanceSettings } = useUserInstanceSettings() const { fluidStatus } = useAppSelector(state => state.ecolyo.global) const currentFluidStatus = fluidStatus[FluidType.GAS] const account = currentFluidStatus.connection.account - const { data: instanceSettings } = useUserInstanceSettings() + const [launchConnection, setLaunchConnection] = useState(false) const [currentStep, setCurrentStep] = useState<GrdfStep>(GrdfStep.Identity) - - // TODO DEBUG VALUES const [formData, setFormData] = useState<AccountGRDFData>({ - lastname: 'Dumont', - firstname: 'Bastien', - email: 'bdumont@grandlyon.com', - postalCode: '69007', - pce: '19170766925335', + lastname: '', + firstname: '', + email: '', + postalCode: '', + pce: '', }) const [formConsent, setFormConsent] = useState({ - dataConsent: true, - pceConfirm: true, + dataConsent: false, + pceConfirm: false, }) const [connect, update] = useKonnectorAuth(FluidType.GAS, { @@ -122,11 +119,10 @@ export const GrdfConnectView = () => { <> <CozyBar titleKey="common.title_gas_connect" displayBackArrow={true} /> <Header - setHeaderHeight={setHeaderHeight} desktopTitleKey="common.title_gas_connect" displayBackArrow={true} /> - <Content heightOffset={headerHeight}> + <Content> <div className="connectView"> <div className="stepContainer"> <FormProgress diff --git a/src/components/Connection/GRDFConnect/GrdfWaitConsent.scss b/src/components/Connection/GRDFConnect/GrdfWaitConsent.scss index 674d6fb8bf78182c8f98fdfc123594bf241c161c..a1e3cbbf8ff4bd038a8c10dfd3be4812a141077c 100644 --- a/src/components/Connection/GRDFConnect/GrdfWaitConsent.scss +++ b/src/components/Connection/GRDFConnect/GrdfWaitConsent.scss @@ -2,17 +2,20 @@ .grdfWait { margin: auto; + margin-top: 16px; display: flex; flex-direction: column; - gap: 1rem; + gap: 16px; align-items: center; text-align: center; + padding-inline: 1rem; + max-width: 600px; .green { - color: var(--gasColor); + color: $gas-color; } - .emailContainer span { + .emailContainer { color: $gold-shadow; font-weight: 700; } diff --git a/src/components/Connection/GRDFConnect/GrdfWaitConsent.tsx b/src/components/Connection/GRDFConnect/GrdfWaitConsent.tsx index 7a9be5885dc6fc78412bf3d45b940ac64aec4975..2f829315f6d3ca9835fc606257dbe435f2275408 100644 --- a/src/components/Connection/GRDFConnect/GrdfWaitConsent.tsx +++ b/src/components/Connection/GRDFConnect/GrdfWaitConsent.tsx @@ -2,21 +2,43 @@ import { Button } from '@material-ui/core' import GRDFMail from 'assets/icons/visu/onboarding/grdf-mail.svg' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' import { useI18n } from 'cozy-ui/transpiled/react/I18n' +import { FluidType } from 'enums' +import { AccountGRDFData, FluidConnection } from 'models' import React from 'react' +import { updateFluidConnection } from 'store/global/global.slice' +import { useAppDispatch, useAppSelector } from 'store/hooks' import './GrdfWaitConsent.scss' export const GrdfWaitConsent = () => { const { t } = useI18n() + const dispatch = useAppDispatch() + const { fluidStatus } = useAppSelector(state => state.ecolyo.global) + const currentFluidStatus = fluidStatus[FluidType.GAS] + const authData = currentFluidStatus.connection.account + ?.auth as AccountGRDFData + + const updateKonnector = async () => { + const updatedConnection: FluidConnection = { + ...currentFluidStatus.connection, + shouldLaunchKonnector: true, + } + dispatch( + updateFluidConnection({ + fluidType: currentFluidStatus.fluidType, + fluidConnection: updatedConnection, + }) + ) + } + return ( <div className="grdfWait"> - <div - className="text-18-normal emailContainer" - dangerouslySetInnerHTML={{ - __html: t('auth.grdfgrandlyon.waiting.mailSent', { - email: 'test@test.com', - }), - }} - /> + <div className="text-18-normal"> + {t('auth.grdfgrandlyon.waiting.mailSent')} + </div> + <div className="text-16-normal"> + {t('auth.grdfgrandlyon.waiting.mailDelay')} + </div> + <span className="emailContainer">{authData.email || ''}</span> <StyledIcon size={80} icon={GRDFMail} /> <div className="text-18-normal"> <span className="text-18-bold green"> @@ -25,7 +47,7 @@ export const GrdfWaitConsent = () => { <br /> <span>{t('auth.grdfgrandlyon.waiting.comeback')}</span> </div> - <Button className="btnPrimary"> + <Button className="btnPrimary" onClick={updateKonnector}> {t('auth.grdfgrandlyon.waiting.button_done')} </Button> </div> diff --git a/src/components/Connection/GRDFConnect/StepIdentity.tsx b/src/components/Connection/GRDFConnect/StepIdentity.tsx index b16b386291e6d64421522809b41de3f2f49bf7c5..4ba5e56579766252c2dbff3a9d7a7334b131c578 100644 --- a/src/components/Connection/GRDFConnect/StepIdentity.tsx +++ b/src/components/Connection/GRDFConnect/StepIdentity.tsx @@ -64,6 +64,7 @@ export const StepIdentity = ({ type="number" id="zipCode" name="zipCode" + required value={formData.postalCode} onChange={e => setFormData(prev => ({ ...prev, postalCode: e.target.value })) diff --git a/src/components/Connection/SGEConnect/SgeConnectView.spec.tsx b/src/components/Connection/SGEConnect/SgeConnectView.spec.tsx index 78c50f9b7869c29d52f36a96039a08bc7c381a1e..8cb9c8bb0363de72c8d0abfff8243b9301abce37 100644 --- a/src/components/Connection/SGEConnect/SgeConnectView.spec.tsx +++ b/src/components/Connection/SGEConnect/SgeConnectView.spec.tsx @@ -2,7 +2,8 @@ import { render, screen } from '@testing-library/react' import React from 'react' import { Provider } from 'react-redux' import { BrowserRouter } from 'react-router-dom' -import { createMockEcolyoStore } from 'tests/__mocks__/store' +import { SgeStatusWithAccount } from 'tests/__mocks__/fluidStatusData.mock' +import { createMockEcolyoStore, mockGlobalState } from 'tests/__mocks__/store' import SgeConnectView from './SgeConnectView' jest.mock('components/Content/Content', () => 'mock-content') @@ -10,6 +11,13 @@ jest.mock('components/Header/CozyBar', () => 'mock-cozybar') const store = createMockEcolyoStore() +const mockConnect = jest.fn() +const mockUpdate = jest.fn() + +jest.mock('components/Hooks/useKonnectorAuth', () => + jest.fn(() => [mockConnect, mockUpdate]) +) + describe('SgeConnectView component', () => { beforeEach(() => { jest.clearAllMocks() @@ -37,13 +45,52 @@ describe('SgeConnectView component', () => { screen.getByText('auth.enedissgegrandlyon.identityTitle') ).toBeInTheDocument() - const prevButton = screen.getByRole('button', { - name: 'profile_type.accessibility.button_previous', - }) - const nextButton = screen.getByRole('button', { - name: 'profile_type.accessibility.button_next', - }) + const prevButton = screen.getByLabelText( + 'profile_type.accessibility.button_previous' + ) + const nextButton = screen.getByLabelText( + 'profile_type.accessibility.button_next' + ) expect(prevButton).toBeDisabled() expect(nextButton).toBeDisabled() }) + + describe('should test methods from useKonnectorAuth hook', () => { + it('should launch account and trigger creation process', async () => { + const store = createMockEcolyoStore({ + global: { + ...mockGlobalState, + sgeConnect: { + ...mockGlobalState.sgeConnect, + shouldLaunchAccount: true, + }, + }, + }) + + render( + <Provider store={store}> + <SgeConnectView /> + </Provider> + ) + expect(mockConnect).toHaveBeenCalled() + }) + it('should launch existing account update process', async () => { + const store = createMockEcolyoStore({ + global: { + ...mockGlobalState, + fluidStatus: [SgeStatusWithAccount], + sgeConnect: { + ...mockGlobalState.sgeConnect, + shouldLaunchAccount: true, + }, + }, + }) + render( + <Provider store={store}> + <SgeConnectView /> + </Provider> + ) + expect(mockUpdate).toHaveBeenCalled() + }) + }) }) diff --git a/src/components/Connection/SGEConnect/SgeConnectView.tsx b/src/components/Connection/SGEConnect/SgeConnectView.tsx index 777e5556efc91f3840a973a9165c46b53b06de1d..2b8a84e087e34da4a116f7981ef9e70ee1360518 100644 --- a/src/components/Connection/SGEConnect/SgeConnectView.tsx +++ b/src/components/Connection/SGEConnect/SgeConnectView.tsx @@ -3,11 +3,15 @@ import FormProgress from 'components/CommonKit/FormProgress/FormProgress' import Content from 'components/Content/Content' import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' -import { SgeStep } from 'enums' +import useKonnectorAuth from 'components/Hooks/useKonnectorAuth' +import { FluidType, SgeStep } from 'enums' import { SgeStore } from 'models' -import React, { useCallback, useState } from 'react' -import { useNavigate } from 'react-router' -import { updateSgeStore } from 'store/global/global.slice' +import React, { useCallback, useEffect, useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { + setShouldRefreshConsent, + updateSgeStore, +} from 'store/global/global.slice' import { useAppDispatch, useAppSelector } from 'store/hooks' import '../connection.scss' import StepAddress from './StepAddress' @@ -33,7 +37,29 @@ const SgeConnectView = () => { const [currentStep, setCurrentStep] = useState<SgeStep>( SgeStep.IdentityAndPDL ) - const [headerHeight, setHeaderHeight] = useState<number>(0) + const { fluidStatus } = useAppSelector(state => state.ecolyo.global) + const currentFluidStatus = fluidStatus[FluidType.ELECTRICITY] + const account = currentFluidStatus.connection.account + + const [connect, update] = useKonnectorAuth(FluidType.ELECTRICITY, { + sgeAuthData: sgeConnect, + }) + + useEffect(() => { + async function launchConnect() { + if (sgeConnect.shouldLaunchAccount) { + dispatch(updateSgeStore({ ...sgeConnect, shouldLaunchAccount: false })) + dispatch(setShouldRefreshConsent(false)) + if (!account) { + await connect() + } else { + await update() + } + navigate('/consumption/electricity') + } + } + launchConnect() + }, [account, connect, dispatch, navigate, sgeConnect, update]) const isNextValid = useCallback(() => { switch (currentStep) { @@ -82,9 +108,8 @@ const SgeConnectView = () => { } setCurrentSgeState(updatedState) dispatch(updateSgeStore(updatedState)) - navigate('/consumption/electricity') } - }, [currentStep, isLoading, dispatch, currentSgeState, navigate]) + }, [currentStep, isLoading, dispatch, currentSgeState]) const handlePrev = useCallback(() => { if (currentStep !== SgeStep.IdentityAndPDL) { @@ -99,18 +124,13 @@ const SgeConnectView = () => { value: string | boolean | number, maxLength?: number ) => { - // TODO duplicate with Form login input - if ( - !maxLength || - value === '' || - (/\d/.test(value.toString()) && value.toString().length <= maxLength) - ) { - const updatedState = { - ...currentSgeState, - [key]: value, - } - setCurrentSgeState(updatedState) + if (maxLength && value.toString().length > maxLength) return + + const updatedState = { + ...currentSgeState, + [key]: value, } + setCurrentSgeState(updatedState) }, [currentSgeState] ) @@ -133,11 +153,10 @@ const SgeConnectView = () => { <> <CozyBar titleKey="common.title_sge_connect" displayBackArrow={true} /> <Header - setHeaderHeight={setHeaderHeight} desktopTitleKey="common.title_sge_connect" displayBackArrow={true} /> - <Content heightOffset={headerHeight}> + <Content> <div className="connectView"> <div className="stepContainer"> <FormProgress diff --git a/src/components/Connection/SGEConnect/SgeInit.spec.tsx b/src/components/Connection/SGEConnect/SgeInit.spec.tsx index b4789725b6d52711005d5e5ebca6808d409b9f20..5bb551e8e671f95b1527a92b4623c5f29cafdd0e 100644 --- a/src/components/Connection/SGEConnect/SgeInit.spec.tsx +++ b/src/components/Connection/SGEConnect/SgeInit.spec.tsx @@ -1,9 +1,8 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' -import { SgeStatusWithAccount } from 'tests/__mocks__/fluidStatusData.mock' -import { createMockEcolyoStore, mockGlobalState } from 'tests/__mocks__/store' +import { createMockEcolyoStore } from 'tests/__mocks__/store' import SgeInit from './SgeInit' const mockedNavigate = jest.fn() @@ -12,13 +11,6 @@ jest.mock('react-router-dom', () => ({ useNavigate: () => mockedNavigate, })) -const mockConnect = jest.fn() -const mockUpdate = jest.fn() - -jest.mock('components/Hooks/useKonnectorAuth', () => - jest.fn(() => [mockConnect, mockUpdate]) -) - describe('SgeInit component', () => { const store = createMockEcolyoStore() it('should be rendered correctly', () => { @@ -35,43 +27,9 @@ describe('SgeInit component', () => { <SgeInit /> </Provider> ) - await userEvent.click(screen.getAllByRole('button')[0]) - expect(mockedNavigate).toHaveBeenCalled() - }) - it('should launch account and trigger creation process', async () => { - const store = createMockEcolyoStore({ - global: { - ...mockGlobalState, - sgeConnect: { - ...mockGlobalState.sgeConnect, - shouldLaunchAccount: true, - }, - }, - }) - - render( - <Provider store={store}> - <SgeInit /> - </Provider> - ) - expect(mockConnect).toHaveBeenCalled() - }) - it('should launch existing account update process', async () => { - const store = createMockEcolyoStore({ - global: { - ...mockGlobalState, - fluidStatus: [SgeStatusWithAccount], - sgeConnect: { - ...mockGlobalState.sgeConnect, - shouldLaunchAccount: true, - }, - }, + await act(async () => { + await userEvent.click(screen.getByText('auth.enedissgegrandlyon.connect')) }) - render( - <Provider store={store}> - <SgeInit /> - </Provider> - ) - expect(mockUpdate).toHaveBeenCalled() + expect(mockedNavigate).toHaveBeenCalled() }) }) diff --git a/src/components/Connection/SGEConnect/SgeInit.tsx b/src/components/Connection/SGEConnect/SgeInit.tsx index 95455867b0d705856efce6fa1409c95ccbe0a967..3576986a69311b85e2506e8217fe25cb48e222ea 100644 --- a/src/components/Connection/SGEConnect/SgeInit.tsx +++ b/src/components/Connection/SGEConnect/SgeInit.tsx @@ -1,16 +1,11 @@ import { Button } from '@material-ui/core' import ElectricityBillIcon from 'assets/icons/visu/onboarding/electricity_bill.svg' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' -import useKonnectorAuth from 'components/Hooks/useKonnectorAuth' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { FluidType } from 'enums' -import React, { useEffect } from 'react' +import React from 'react' import { useNavigate } from 'react-router-dom' import { setShowOfflineData } from 'store/chart/chart.slice' -import { - setShouldRefreshConsent, - updateSgeStore, -} from 'store/global/global.slice' import { useAppDispatch, useAppSelector } from 'store/hooks' const SgeInit = () => { @@ -18,27 +13,7 @@ const SgeInit = () => { const navigate = useNavigate() const { fluidStatus } = useAppSelector(state => state.ecolyo.global) const currentFluidStatus = fluidStatus[FluidType.ELECTRICITY] - const account = currentFluidStatus.connection.account - const { sgeConnect } = useAppSelector(state => state.ecolyo.global) const dispatch = useAppDispatch() - const [connect, update] = useKonnectorAuth(FluidType.ELECTRICITY, { - sgeAuthData: sgeConnect, - }) - - useEffect(() => { - async function launchConnect() { - if (sgeConnect.shouldLaunchAccount) { - dispatch(updateSgeStore({ ...sgeConnect, shouldLaunchAccount: false })) - dispatch(setShouldRefreshConsent(false)) - if (!account) { - await connect() - } else { - await update() - } - } - } - launchConnect() - }, [account, connect, dispatch, sgeConnect, update]) return ( <div className="connection-form"> diff --git a/src/components/Connection/SGEConnect/StepAddress.spec.tsx b/src/components/Connection/SGEConnect/StepAddress.spec.tsx index d5aae6a3af4b54692c0c3549c89de48388bd5bf2..e7e1326c249c7c046f6afce0d3856bfba13ee33a 100644 --- a/src/components/Connection/SGEConnect/StepAddress.spec.tsx +++ b/src/components/Connection/SGEConnect/StepAddress.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { mockGlobalState } from 'tests/__mocks__/store' @@ -30,14 +30,18 @@ describe('StepAddress component', () => { const input = screen.getByRole('textbox', { name: 'auth.enedissgegrandlyon.address', }) - await userEvent.type(input, 't') + await act(async () => { + await userEvent.type(input, 't') + }) expect(mockHandleChange).toHaveBeenCalledWith('address', 't') }) it('should change zipCode value', async () => { const input = screen.getByRole('spinbutton', { name: 'auth.enedissgegrandlyon.zipCode', }) - await userEvent.type(input, '0') + await act(async () => { + await userEvent.type(input, '0') + }) expect(mockHandleChange).toHaveBeenCalledWith('zipCode', '0', 5) }) @@ -45,7 +49,9 @@ describe('StepAddress component', () => { const input = screen.getByRole('textbox', { name: 'auth.enedissgegrandlyon.city', }) - await userEvent.type(input, 'c') + await act(async () => { + await userEvent.type(input, 'c') + }) expect(mockHandleChange).toHaveBeenCalledWith('city', 'c') }) }) diff --git a/src/components/Connection/SGEConnect/StepAddress.tsx b/src/components/Connection/SGEConnect/StepAddress.tsx index d696ff762620ced393205381adc0db5f90899cad..dfbcc2d51dd3dfc20d9cea6d97fedfbd9efba956 100644 --- a/src/components/Connection/SGEConnect/StepAddress.tsx +++ b/src/components/Connection/SGEConnect/StepAddress.tsx @@ -24,6 +24,7 @@ const StepAddress = ({ sgeState, onChange }: StepAddressProps) => { name="address" value={sgeState.address} onChange={e => onChange('address', e.target.value)} + required /> <TextField label={t('auth.enedissgegrandlyon.zipCode')} @@ -33,6 +34,7 @@ const StepAddress = ({ sgeState, onChange }: StepAddressProps) => { name="zipCode" value={sgeState.zipCode ?? undefined} onChange={e => onChange('zipCode', e.target.value, 5)} + required /> <TextField label={t('auth.enedissgegrandlyon.city')} @@ -42,6 +44,7 @@ const StepAddress = ({ sgeState, onChange }: StepAddressProps) => { name="city" value={sgeState.city} onChange={e => onChange('city', e.target.value)} + required /> </div> ) diff --git a/src/components/Connection/SGEConnect/StepConsent.spec.tsx b/src/components/Connection/SGEConnect/StepConsent.spec.tsx index ebca791acb68896b788aee05439f6c553790e861..642ebccb39727a42e96b8fc4a45b59f19bb2d2d4 100644 --- a/src/components/Connection/SGEConnect/StepConsent.spec.tsx +++ b/src/components/Connection/SGEConnect/StepConsent.spec.tsx @@ -24,15 +24,15 @@ describe('StepConsent component', () => { onChange={mockHandleChange} /> ) - const consentCheckbox = screen.getByRole('checkbox', { - name: 'auth.enedissgegrandlyon.consentCheck1', - }) + const consentCheckbox = screen.getByLabelText( + 'auth.enedissgegrandlyon.consentCheck1' + ) await userEvent.click(consentCheckbox) expect(mockHandleChange).toHaveBeenCalledWith('dataConsent', true) - const pdlCheckbox = screen.getByRole('checkbox', { - name: 'auth.enedissgegrandlyon.consentCheck2', - }) + const pdlCheckbox = screen.getByLabelText( + 'auth.enedissgegrandlyon.consentCheck2' + ) await userEvent.click(pdlCheckbox) expect(mockHandleChange).toHaveBeenCalledWith('pdlConfirm', true) }) diff --git a/src/components/Connection/SGEConnect/StepIdentityAndPdl.spec.tsx b/src/components/Connection/SGEConnect/StepIdentityAndPdl.spec.tsx index afb9b5b3902c398071ee639f1c97dcd327935d1b..42846fe60b69c7bbe8b135ec64e32e4bf1b00e53 100644 --- a/src/components/Connection/SGEConnect/StepIdentityAndPdl.spec.tsx +++ b/src/components/Connection/SGEConnect/StepIdentityAndPdl.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' import { mockGlobalState } from 'tests/__mocks__/store' @@ -27,19 +27,25 @@ describe('StepIdentityAndPdl component', () => { const firstNameInput = screen.getByRole('textbox', { name: 'auth.enedissgegrandlyon.firstName', }) - await userEvent.type(firstNameInput, 'n') + await act(async () => { + await userEvent.type(firstNameInput, 'n') + }) expect(mockHandleChange).toHaveBeenCalledWith('firstName', 'n') const lastNameInput = screen.getByRole('textbox', { name: 'auth.enedissgegrandlyon.lastName', }) - await userEvent.type(lastNameInput, 'n') + await act(async () => { + await userEvent.type(lastNameInput, 'n') + }) expect(mockHandleChange).toHaveBeenCalledWith('lastName', 'n') const pdlInput = screen.getByRole('spinbutton', { name: 'auth.enedissgegrandlyon.pdlLabel', }) - await userEvent.type(pdlInput, '0') + await act(async () => { + await userEvent.type(pdlInput, '0') + }) expect(mockHandleChange).toHaveBeenCalledWith('pdl', '0', 14) }) @@ -50,10 +56,11 @@ describe('StepIdentityAndPdl component', () => { onChange={mockHandleChange} /> ) - const pdlModalButton = screen.getByRole('button', { - name: 'auth.enedissgegrandlyon.pdlModal.title', + await act(async () => { + await userEvent.click( + screen.getByText('auth.enedissgegrandlyon.pdlModal.title') + ) }) - await userEvent.click(pdlModalButton) expect(screen.getByRole('dialog')).toBeInTheDocument() }) diff --git a/src/components/Connection/SGEConnect/__snapshots__/SgeConnectView.spec.tsx.snap b/src/components/Connection/SGEConnect/__snapshots__/SgeConnectView.spec.tsx.snap index 9b3fe3495ddab6c2dc20ce859e0800fca2a1b127..cdc58037adb76fba73cd409447e624c664bda733 100644 --- a/src/components/Connection/SGEConnect/__snapshots__/SgeConnectView.spec.tsx.snap +++ b/src/components/Connection/SGEConnect/__snapshots__/SgeConnectView.spec.tsx.snap @@ -76,9 +76,7 @@ exports[`SgeConnectView component should be rendered correctly 1`] = ` class="header-bar" /> </header> - <mock-content - heightoffset="-48" - > + <mock-content> <div class="connectView" > diff --git a/src/components/Connection/SGEConnect/__snapshots__/StepAddress.spec.tsx.snap b/src/components/Connection/SGEConnect/__snapshots__/StepAddress.spec.tsx.snap index fe38f9a0dc1554dfb68198bd24acf049a2550b49..9333648f773064e1c9f2b800faca207d7e80aa25 100644 --- a/src/components/Connection/SGEConnect/__snapshots__/StepAddress.spec.tsx.snap +++ b/src/components/Connection/SGEConnect/__snapshots__/StepAddress.spec.tsx.snap @@ -14,12 +14,19 @@ exports[`StepAddress component should be rendered correctly 1`] = ` class="MuiFormControl-root MuiTextField-root" > <label - class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined" + class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required" data-shrink="false" for="address" id="address-label" > auth.enedissgegrandlyon.address + <span + aria-hidden="true" + class="MuiFormLabel-asterisk MuiInputLabel-asterisk" + > + â + * + </span> </label> <div class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-formControl" @@ -29,6 +36,7 @@ exports[`StepAddress component should be rendered correctly 1`] = ` class="MuiInputBase-input MuiOutlinedInput-input" id="address" name="address" + required="" type="text" value="" /> @@ -41,6 +49,7 @@ exports[`StepAddress component should be rendered correctly 1`] = ` > <span> auth.enedissgegrandlyon.address + * </span> </legend> </fieldset> @@ -50,12 +59,19 @@ exports[`StepAddress component should be rendered correctly 1`] = ` class="MuiFormControl-root MuiTextField-root" > <label - class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined" + class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required" data-shrink="false" for="zipCode" id="zipCode-label" > auth.enedissgegrandlyon.zipCode + <span + aria-hidden="true" + class="MuiFormLabel-asterisk MuiInputLabel-asterisk" + > + â + * + </span> </label> <div class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-formControl" @@ -65,6 +81,7 @@ exports[`StepAddress component should be rendered correctly 1`] = ` class="MuiInputBase-input MuiOutlinedInput-input" id="zipCode" name="zipCode" + required="" type="number" value="" /> @@ -77,6 +94,7 @@ exports[`StepAddress component should be rendered correctly 1`] = ` > <span> auth.enedissgegrandlyon.zipCode + * </span> </legend> </fieldset> @@ -86,12 +104,19 @@ exports[`StepAddress component should be rendered correctly 1`] = ` class="MuiFormControl-root MuiTextField-root" > <label - class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined" + class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required" data-shrink="false" for="city" id="city-label" > auth.enedissgegrandlyon.city + <span + aria-hidden="true" + class="MuiFormLabel-asterisk MuiInputLabel-asterisk" + > + â + * + </span> </label> <div class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-formControl" @@ -101,6 +126,7 @@ exports[`StepAddress component should be rendered correctly 1`] = ` class="MuiInputBase-input MuiOutlinedInput-input" id="city" name="city" + required="" type="text" value="" /> @@ -113,6 +139,7 @@ exports[`StepAddress component should be rendered correctly 1`] = ` > <span> auth.enedissgegrandlyon.city + * </span> </legend> </fieldset> diff --git a/src/components/Consumption/ConsumptionView.spec.tsx b/src/components/Consumption/ConsumptionView.spec.tsx index 124c51accc0156479fc86be1bff71ee0f3e76bf9..96f96183d9ec8a16a75e399a85cdcbc450744fc4 100644 --- a/src/components/Consumption/ConsumptionView.spec.tsx +++ b/src/components/Consumption/ConsumptionView.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { render, screen, waitFor } from '@testing-library/react' import { FluidState, FluidType, TimeStep } from 'enums' import React from 'react' import { Provider } from 'react-redux' @@ -59,7 +59,6 @@ describe('ConsumptionView component', () => { const store = createMockEcolyoStore({ chart: { ...mockChartState, - loading: false, showOfflineData: true, }, global: { @@ -83,27 +82,6 @@ describe('ConsumptionView component', () => { ).toBeTruthy() }) - it('should display a spinner when fluid connected and is loading', () => { - const store = createMockEcolyoStore({ - chart: { - ...mockChartState, - loading: true, - showOfflineData: true, - }, - global: { - fluidStatus: mockFluidStatus, - releaseNotes: mockInitialEcolyoState.global.releaseNotes, - }, - modal: mockModalState, - }) - render( - <Provider store={store}> - <ConsumptionView fluidType={FluidType.ELECTRICITY} /> - </Provider> - ) - expect(screen.getByRole('progressbar')).toBeInTheDocument() - }) - it('should set CurrentTimeStep to WEEK when fluid != ELECTRICITY and timeStep = HALF_AN_HOUR', () => { const store = createMockEcolyoStore({ chart: { @@ -144,7 +122,7 @@ describe('ConsumptionView component', () => { ).toBeTruthy() }) - it('should render mutlifluid consumption if at least one fluid is connected', () => { + it('should render mutlifluid consumption if at least one fluid is connected without konnectorViewerCard', async () => { const store = createMockEcolyoStore({ chart: mockChartStateShowOffline, global: { @@ -158,12 +136,16 @@ describe('ConsumptionView component', () => { <ConsumptionView fluidType={FluidType.MULTIFLUID} /> </Provider> ) + await waitFor(() => null, { container }) expect( - container.getElementsByClassName('consumptionview-content')[0] + container.getElementsByTagName('mock-consumptionDetails').item(0) ).toBeInTheDocument() + expect( + container.getElementsByTagName('mock-konnectorViewerCard').item(0) + ).not.toBeInTheDocument() }) - it('should render Electricity when elec is connected', () => { + it('should render Electricity when elec is connected with konnectorViewCard', async () => { const store = createMockEcolyoStore({ chart: mockChartStateShowOffline, global: { @@ -177,36 +159,71 @@ describe('ConsumptionView component', () => { <ConsumptionView fluidType={FluidType.ELECTRICITY} /> </Provider> ) + await waitFor(() => null, { container }) expect( - container.getElementsByClassName('consumptionview-content').item(0) + container.getElementsByTagName('mock-consumptionDetails').item(0) ).toBeInTheDocument() expect( - container.getElementsByTagName('mock-consumptionDetails').item(0) + container.getElementsByTagName('mock-konnectorViewerCard').item(0) ).toBeInTheDocument() }) - // todo describe and add multiple fluids ? - it('should render partner issue Modal', async () => { - const updatedStatus = mockInitialEcolyoState.global.fluidStatus - updatedStatus[0] = mockExpiredElec - const store = createMockEcolyoStore({ - chart: mockChartStateShowOffline, - global: { - fluidStatus: updatedStatus, - releaseNotes: mockInitialEcolyoState.global.releaseNotes, - }, - modal: { - ...mockModalState, - partnersIssueModal: { enedis: false, grdf: false, egl: true }, - }, + describe('Partner Issue Modal', () => { + it('should render partner issue Modal for electricity', async () => { + const updatedStatus = mockInitialEcolyoState.global.fluidStatus + updatedStatus[0] = { ...updatedStatus[0], maintenance: true } + const store = createMockEcolyoStore({ + chart: mockChartStateShowOffline, + global: { + fluidStatus: updatedStatus, + releaseNotes: mockInitialEcolyoState.global.releaseNotes, + }, + modal: { + ...mockModalState, + partnersIssueModal: { enedis: true, grdf: false, egl: false }, + }, + }) + mockUpdateProfile.mockResolvedValue(mockTestProfile1) + const { container } = render( + <Provider store={store}> + <ConsumptionView fluidType={FluidType.ELECTRICITY} /> + </Provider> + ) + await waitFor(() => null, { container }) + expect( + screen.getByRole('presentation', { + name: 'consumption.partner_issue_modal.accessibility_title', + }) + ).toBeInTheDocument() + }) + + it('should render partner issue Modal for water', async () => { + const updatedStatus = mockInitialEcolyoState.global.fluidStatus + updatedStatus[1] = { ...updatedStatus[1], maintenance: true } + const store = createMockEcolyoStore({ + chart: mockChartStateShowOffline, + global: { + fluidStatus: updatedStatus, + releaseNotes: mockInitialEcolyoState.global.releaseNotes, + }, + modal: { + ...mockModalState, + partnersIssueModal: { enedis: false, grdf: false, egl: true }, + }, + }) + mockUpdateProfile.mockResolvedValue(mockTestProfile1) + const { container } = render( + <Provider store={store}> + <ConsumptionView fluidType={FluidType.WATER} /> + </Provider> + ) + await waitFor(() => null, { container }) + expect( + screen.getByRole('presentation', { + name: 'consumption.partner_issue_modal.accessibility_title', + }) + ).toBeInTheDocument() }) - mockUpdateProfile.mockResolvedValue(mockTestProfile1) - render( - <Provider store={store}> - <ConsumptionView fluidType={FluidType.ELECTRICITY} /> - </Provider> - ) - expect(screen.getByRole('dialog')).toBeInTheDocument() }) it('should show expired modal when a GRDF consent is expired', () => { const updatedStatus = mockInitialEcolyoState.global.fluidStatus diff --git a/src/components/Consumption/ConsumptionView.tsx b/src/components/Consumption/ConsumptionView.tsx index a8658aaf03e96f441098b87cd5ca1b0de822083f..5b13626490267657c865d676f9627973c77d2a73 100644 --- a/src/components/Consumption/ConsumptionView.tsx +++ b/src/components/Consumption/ConsumptionView.tsx @@ -1,4 +1,3 @@ -import classNames from 'classnames' import ExpiredConsentModal from 'components/Connection/ExpiredConsentModal/ExpiredConsentModal' import Content from 'components/Content/Content' import CustomPopupModal from 'components/CustomPopup/CustomPopupModal' @@ -8,13 +7,12 @@ import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' import KonnectorViewerCard from 'components/Konnector/KonnectorViewerCard' import KonnectorViewerList from 'components/Konnector/KonnectorViewerList' -import Loader from 'components/Loader/Loader' import PartnerIssueModal from 'components/PartnerIssue/PartnerIssueModal' import ReleaseNotesModal from 'components/ReleaseNotesModal/ReleaseNotesModal' import { useClient } from 'cozy-client' -import { FluidType, TimeStep } from 'enums' +import { FluidState, FluidType, TimeStep } from 'enums' import { DateTime } from 'luxon' -import React, { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' import DateChartService from 'services/dateChart.service' import ProfileService from 'services/profile.service' @@ -35,50 +33,60 @@ import { } from 'utils/utils' import ConsumptionDetails from './ConsumptionDetails/ConsumptionDetails' import FluidButtons from './FluidButtons/FluidButtons' -import './consumptionView.scss' const ConsumptionView = ({ fluidType }: { fluidType: FluidType }) => { const navigate = useNavigate() const client = useClient() const dispatch = useAppDispatch() - const isMulti = fluidType === FluidType.MULTIFLUID const { - chart: { - currentTimeStep, - loading, - showOfflineData, - selectedDate, - currentIndex, - }, + chart: { currentTimeStep, showOfflineData, selectedDate, currentIndex }, global: { fluidStatus, releaseNotes }, modal: { partnersIssueModal, customPopupModal }, } = useAppSelector(state => state.ecolyo) + const isMulti = fluidType === FluidType.MULTIFLUID + const currentFluidStatus = fluidStatus[fluidType] const dateChartService = new DateChartService() + /** Show wait consent screen when consent is "A valider" */ + const isWaitingForConsent = + fluidType === FluidType.GAS && + currentFluidStatus.status === FluidState.CHALLENGE_ASKED + + const [openExpiredConsentModal, setOpenExpiredConsentModal] = useState(true) const [openReleaseNoteModal, setOpenReleaseNoteModal] = useState<boolean>( releaseNotes.show ) - - const [headerHeight, setHeaderHeight] = useState<number>(0) - const [active, setActive] = useState<boolean>(false) - const [openExpiredConsentModal, setOpenExpiredConsentModal] = - useState<boolean>(true) const [consentExpiredFluids, setConsentExpiredFluids] = useState<FluidType[]>( [] ) + const profileService = useMemo(() => new ProfileService(client), [client]) + const updateKey = - fluidType !== FluidType.MULTIFLUID && fluidStatus[fluidType].lastDataDate - ? `${fluidStatus[fluidType].lastDataDate!.toLocaleString()} + ${ - fluidStatus[fluidType].status + fluidType + !isMulti && currentFluidStatus.lastDataDate + ? `${currentFluidStatus.lastDataDate.toLocaleString()} + ${ + currentFluidStatus.status + fluidType }` : '' const lastDataDateKey = - fluidType !== FluidType.MULTIFLUID && fluidStatus[fluidType].lastDataDate - ? `${fluidStatus[fluidType].lastDataDate!.toLocaleString() + fluidType}` + !isMulti && currentFluidStatus.lastDataDate + ? `${currentFluidStatus.lastDataDate.toLocaleString() + fluidType}` : '' + const getPartnerKey = (fluidType: FluidType): 'enedis' | 'egl' | 'grdf' => { + switch (fluidType) { + case FluidType.ELECTRICITY: + return 'enedis' + case FluidType.WATER: + return 'egl' + case FluidType.GAS: + return 'grdf' + default: + throw new Error('unknown fluidtype') + } + } + const handleCloseReleaseNoteModal = useCallback(() => { setOpenReleaseNoteModal(false) dispatch( @@ -93,45 +101,27 @@ const ConsumptionView = ({ fluidType }: { fluidType: FluidType }) => { } }, [dispatch, navigate, releaseNotes.notes, releaseNotes.redirectLink]) - const getPartnerKey = (fluidType: FluidType): 'enedis' | 'egl' | 'grdf' => { - switch (fluidType) { - case FluidType.ELECTRICITY: - return 'enedis' - case FluidType.WATER: - return 'egl' - case FluidType.GAS: - return 'grdf' - default: - throw new Error('unknown fluidtype') - } - } - - const handleClosePartnerIssueModal = useCallback( - async (fluidType: FluidType) => { - const profileService = new ProfileService(client) - const profileValues = await profileService.getProfile() - if (profileValues) { - const updatedProfile = await profileService.updateProfile({ - partnersIssueSeenDate: { - ...profileValues.partnersIssueSeenDate, - [getPartnerKey(fluidType)]: getTodayDate(), - }, - }) - if (updatedProfile) { - dispatch( - openPartnersModal({ - ...partnersIssueModal, - [getPartnerKey(fluidType)]: false, - }) - ) - } + const handleClosePartnerIssueModal = useCallback(async () => { + const profileValues = await profileService.getProfile() + if (profileValues) { + const updatedProfile = await profileService.updateProfile({ + partnersIssueSeenDate: { + ...profileValues.partnersIssueSeenDate, + [getPartnerKey(fluidType)]: getTodayDate(), + }, + }) + if (updatedProfile) { + dispatch( + openPartnersModal({ + ...partnersIssueModal, + [getPartnerKey(fluidType)]: false, + }) + ) } - }, - [client, dispatch, partnersIssueModal] - ) + } + }, [dispatch, fluidType, partnersIssueModal, profileService]) const handleCloseCustomPopupModal = async () => { - const profileService = new ProfileService(client) const updatedProfile = await profileService.updateProfile({ customPopupDate: getTodayDate(), }) @@ -145,15 +135,18 @@ const ConsumptionView = ({ fluidType }: { fluidType: FluidType }) => { } } - /** Handle time change */ - useEffect(() => { - if ( - fluidType !== FluidType.ELECTRICITY && - currentTimeStep == TimeStep.HALF_AN_HOUR - ) { - dispatch(setCurrentTimeStep(TimeStep.WEEK)) - } - }, [dispatch, fluidType, currentTimeStep]) + useEffect( + /** Reset half-hour timestep for water & gas & multifluid */ + function setDefaultTimeStep() { + if ( + fluidType !== FluidType.ELECTRICITY && + currentTimeStep == TimeStep.HALF_AN_HOUR + ) { + dispatch(setCurrentTimeStep(TimeStep.WEEK)) + } + }, + [dispatch, fluidType, currentTimeStep] + ) /** * If fluid is not connected, display Connect components @@ -164,10 +157,10 @@ const ConsumptionView = ({ fluidType }: { fluidType: FluidType }) => { dispatch(setShowOfflineData(isFluidConnected)) }, [dispatch, fluidStatus, fluidType]) + /** Check if some fluids have expired consent error */ useEffect(() => { let subscribed = true const expiredConsents: FluidType[] = [] - // Check if some fluids have expired consent error for (const fluid of fluidStatus) { const error = fluid.connection.triggerState?.last_error if (error && getKonnectorUpdateError(error) === 'error_update_oauth') { @@ -218,10 +211,7 @@ const ConsumptionView = ({ fluidType }: { fluidType: FluidType }) => { return ( <> <CozyBar titleKey="common.title_consumption" /> - <Header - setHeaderHeight={setHeaderHeight} - desktopTitleKey="common.title_consumption" - > + <Header desktopTitleKey="common.title_consumption"> <DateNavigator disableNext={isLastDateReached(selectedDate, currentTimeStep)} disablePrev={disablePrev} @@ -231,64 +221,27 @@ const ConsumptionView = ({ fluidType }: { fluidType: FluidType }) => { timeStep={currentTimeStep} /> </Header> - <Content heightOffset={headerHeight}> + <Content> <FluidButtons activeFluid={fluidType} key={updateKey} /> - {openReleaseNoteModal && ( - <ReleaseNotesModal - open={openReleaseNoteModal} - handleCloseClick={handleCloseReleaseNoteModal} - /> - )} - {showOfflineData && ( + {showOfflineData && !isWaitingForConsent && ( <> - {loading && ( - <div className="consumptionview-loading"> - <Loader fluidType={fluidType} /> - </div> - )} - <div - className={classNames('consumptionview-content', { - ['--hidden']: loading, - })} - > - <FluidChart - fluidType={fluidType} - setActive={setActive} - key={lastDataDateKey} - /> - <ConsumptionDetails fluidType={fluidType} /> - </div> - {!isMulti && ( - <div className="konnector-section"> - <KonnectorViewerCard - fluidType={fluidType} - isDisconnected={false} - showOfflineData={true} - setActive={setActive} - active={active} - key={fluidType} - /> - </div> - )} + <FluidChart fluidType={fluidType} key={lastDataDateKey} /> + <ConsumptionDetails fluidType={fluidType} /> </> )} - {!showOfflineData && ( - <div className="konnector-section"> - {isMulti ? ( - <KonnectorViewerList /> - ) : ( - <KonnectorViewerCard - fluidType={fluidType} - isDisconnected={true} - showOfflineData={false} - setActive={setActive} - active={active} - /> - )} - </div> - )} + {!isMulti && <KonnectorViewerCard fluidType={fluidType} />} + + {isMulti && !showOfflineData && <KonnectorViewerList />} </Content> + + {/* MODALS */} + {openReleaseNoteModal && ( + <ReleaseNotesModal + open={openReleaseNoteModal} + handleCloseClick={handleCloseReleaseNoteModal} + /> + )} {/* Partner issue modals for individual fluids */} {fluidStatus .filter(fluid => fluid.maintenance) @@ -306,17 +259,15 @@ const ConsumptionView = ({ fluidType }: { fluidType: FluidType }) => { handleCloseClick={handleCloseCustomPopupModal} /> {Boolean(consentExpiredFluids.length) && - consentExpiredFluids.map(fluid => { - return ( - <ExpiredConsentModal - key={fluid} - open={openExpiredConsentModal} - handleCloseClick={() => setOpenExpiredConsentModal(false)} - fluidType={fluid} - toggleModal={() => setOpenExpiredConsentModal(prev => !prev)} - /> - ) - })} + consentExpiredFluids.map(fluid => ( + <ExpiredConsentModal + key={fluid} + open={openExpiredConsentModal} + handleCloseClick={() => setOpenExpiredConsentModal(false)} + fluidType={fluid} + toggleModal={() => setOpenExpiredConsentModal(prev => !prev)} + /> + ))} </> ) } diff --git a/src/components/Consumption/FluidButtons/FluidButton.spec.tsx b/src/components/Consumption/FluidButtons/FluidButton.spec.tsx index 1c8fa5ea6e1821077906d7488b060d1a10013548..09d06751951cf026faba5835fd1c3c13ad34b78f 100644 --- a/src/components/Consumption/FluidButtons/FluidButton.spec.tsx +++ b/src/components/Consumption/FluidButtons/FluidButton.spec.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react' +import { render, screen } from '@testing-library/react' import { FluidState, FluidType } from 'enums' import { GlobalState } from 'models' import React from 'react' @@ -19,15 +19,14 @@ describe('FluidButton component', () => { }) it('should render multifluidButton', () => { - const { container } = render( + render( <Provider store={store}> <FluidButton fluidType={FluidType.MULTIFLUID} isActive={false} /> </Provider> ) - const element = container.getElementsByClassName('multifluid').item(0) - expect(element).toBeInTheDocument() + expect(screen.getByText('FLUID.MULTIFLUID.LABEL')).toBeInTheDocument() }) - it('should render active button', () => { + it('should render active gas button', () => { const { container } = render( <Provider store={store}> <FluidButton fluidType={FluidType.GAS} isActive={true} /> diff --git a/src/components/Consumption/FluidButtons/FluidButton.tsx b/src/components/Consumption/FluidButtons/FluidButton.tsx index f68eaa2d10542bfcbe93bfa8351ea37d049ce2a5..cd08a99446f0f896133baeb5191830565b1f7c07 100644 --- a/src/components/Consumption/FluidButtons/FluidButton.tsx +++ b/src/components/Consumption/FluidButtons/FluidButton.tsx @@ -1,6 +1,7 @@ import { IconButton } from '@material-ui/core' import ErrorNotif from 'assets/icons/ico/notif_error.svg' import PartnerIssueNotif from 'assets/icons/ico/notif_maintenance.svg' +import classNames from 'classnames' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { FluidState, FluidType } from 'enums' @@ -20,50 +21,42 @@ const FluidButton = ({ fluidType, isActive }: FluidButtonProps) => { const navigate = useNavigate() const { fluidStatus } = useAppSelector(state => state.ecolyo.global) const [showError, setShowError] = useState<boolean>(false) + const isMulti = fluidType === FluidType.MULTIFLUID + const fluidName = getFluidName(fluidType) const isConnected = useCallback(() => { - if (fluidType === FluidType.MULTIFLUID) { - return true - } else return isKonnectorActive(fluidStatus, fluidType) - }, [fluidStatus, fluidType]) + if (isMulti) return true + return isKonnectorActive(fluidStatus, fluidType) + }, [fluidStatus, fluidType, isMulti]) const isErrored = useCallback(() => { if ( - (fluidType !== FluidType.MULTIFLUID && - fluidStatus[fluidType].status === FluidState.ERROR) || + (!isMulti && fluidStatus[fluidType].status === FluidState.ERROR) || (fluidType !== FluidType.WATER && - fluidStatus[fluidType].status === FluidState.ERROR_LOGIN_FAILED) + fluidStatus[fluidType].status === FluidState.LOGIN_FAILED) ) { return true } return false - }, [fluidStatus, fluidType]) + }, [fluidStatus, fluidType, isMulti]) const iconType = getNavPicto(fluidType, isActive, isConnected()) const goToFluid = useCallback(async () => { - navigate( - fluidType === FluidType.MULTIFLUID - ? '/consumption' - : `/consumption/${getFluidName(fluidType)}` - ) - }, [navigate, fluidType]) + navigate(isMulti ? '/consumption' : `/consumption/${fluidName}`) + }, [navigate, isMulti, fluidName]) const isFluidMaintenance = () => fluidStatus[fluidType]?.maintenance useEffect(() => { // Show errors only on connected konnectors that are in error, outdated, with no data (specific case), and not in multifluid - if (fluidType !== FluidType.MULTIFLUID && isConnected() && isErrored()) { + if (!isMulti && isConnected() && isErrored()) { setShowError(true) } - }, [fluidStatus, fluidType, isConnected, isErrored]) + }, [fluidStatus, fluidType, isConnected, isErrored, isMulti]) + return ( - <IconButton - className={`fluid-title fluid-button ${FluidType[ - fluidType - ].toLowerCase()}`} - onClick={goToFluid} - > + <IconButton className="fluid-title fluid-button" onClick={goToFluid}> <StyledIcon className="fluid-icon" icon={iconType} @@ -81,11 +74,11 @@ const FluidButton = ({ fluidType, isActive }: FluidButtonProps) => { ) )} <div - className={`fluid-title ${getFluidName(fluidType)} ${ - isActive && 'active' - } text-14-normal`} + className={classNames('fluid-title text-14-normal', { + active: isActive, + })} > - {t(`FLUID.${FluidType[fluidType]}.LABEL`)} + {t(`FLUID.${fluidName.toLocaleUpperCase()}.LABEL`)} </div> </IconButton> ) diff --git a/src/components/Consumption/FluidButtons/__snapshots__/FluidButton.spec.tsx.snap b/src/components/Consumption/FluidButtons/__snapshots__/FluidButton.spec.tsx.snap index fe1b616b62680d6c6b7ee1b248b93117710d356b..8c24c5c5e4a9ee60a15420631cef7af09d6dfdd7 100644 --- a/src/components/Consumption/FluidButtons/__snapshots__/FluidButton.spec.tsx.snap +++ b/src/components/Consumption/FluidButtons/__snapshots__/FluidButton.spec.tsx.snap @@ -3,7 +3,7 @@ exports[`FluidButton component should be rendered correctly 1`] = ` <div> <button - class="MuiButtonBase-root MuiIconButton-root fluid-title fluid-button electricity" + class="MuiButtonBase-root MuiIconButton-root fluid-title fluid-button" tabindex="0" type="button" > @@ -21,7 +21,7 @@ exports[`FluidButton component should be rendered correctly 1`] = ` /> </svg> <div - class="fluid-title electricity false text-14-normal" + class="fluid-title text-14-normal" > FLUID.ELECTRICITY.LABEL </div> diff --git a/src/components/Consumption/FluidButtons/__snapshots__/FluidButtons.spec.tsx.snap b/src/components/Consumption/FluidButtons/__snapshots__/FluidButtons.spec.tsx.snap index d7c42349c2df0fcf66d258cf58ac289aee11f4da..271a057e4ab171d41a738c46816f606037038603 100644 --- a/src/components/Consumption/FluidButtons/__snapshots__/FluidButtons.spec.tsx.snap +++ b/src/components/Consumption/FluidButtons/__snapshots__/FluidButtons.spec.tsx.snap @@ -9,7 +9,7 @@ exports[`FluidButtons component should be rendered correctly 1`] = ` class="content" > <button - class="MuiButtonBase-root MuiIconButton-root fluid-title fluid-button multifluid" + class="MuiButtonBase-root MuiIconButton-root fluid-title fluid-button" tabindex="0" type="button" > @@ -27,7 +27,7 @@ exports[`FluidButtons component should be rendered correctly 1`] = ` /> </svg> <div - class="fluid-title multifluid false text-14-normal" + class="fluid-title text-14-normal" > FLUID.MULTIFLUID.LABEL </div> @@ -37,7 +37,7 @@ exports[`FluidButtons component should be rendered correctly 1`] = ` /> </button> <button - class="MuiButtonBase-root MuiIconButton-root fluid-title fluid-button electricity" + class="MuiButtonBase-root MuiIconButton-root fluid-title fluid-button" tabindex="0" type="button" > @@ -55,7 +55,7 @@ exports[`FluidButtons component should be rendered correctly 1`] = ` /> </svg> <div - class="fluid-title electricity active text-14-normal" + class="fluid-title text-14-normal active" > FLUID.ELECTRICITY.LABEL </div> @@ -65,7 +65,7 @@ exports[`FluidButtons component should be rendered correctly 1`] = ` /> </button> <button - class="MuiButtonBase-root MuiIconButton-root fluid-title fluid-button water" + class="MuiButtonBase-root MuiIconButton-root fluid-title fluid-button" tabindex="0" type="button" > @@ -83,7 +83,7 @@ exports[`FluidButtons component should be rendered correctly 1`] = ` /> </svg> <div - class="fluid-title water false text-14-normal" + class="fluid-title text-14-normal" > FLUID.WATER.LABEL </div> @@ -93,7 +93,7 @@ exports[`FluidButtons component should be rendered correctly 1`] = ` /> </button> <button - class="MuiButtonBase-root MuiIconButton-root fluid-title fluid-button gas" + class="MuiButtonBase-root MuiIconButton-root fluid-title fluid-button" tabindex="0" type="button" > @@ -111,7 +111,7 @@ exports[`FluidButtons component should be rendered correctly 1`] = ` /> </svg> <div - class="fluid-title gas false text-14-normal" + class="fluid-title text-14-normal" > FLUID.GAS.LABEL </div> diff --git a/src/components/Consumption/consumptionView.scss b/src/components/Consumption/consumptionView.scss deleted file mode 100644 index ce636ccffbc6ca9efe5bd83c55e3a7eba6f8b436..0000000000000000000000000000000000000000 --- a/src/components/Consumption/consumptionView.scss +++ /dev/null @@ -1,29 +0,0 @@ -@import 'src/styles/base/breakpoint'; -@import 'src/styles/base/color'; - -.consumptionview-loading { - background-color: $default-background; - height: 80vh; - width: 100%; - display: flex; - justify-content: center; - align-items: center; -} -.consumptionview-content { - background-color: $default-background; - &.--hidden { - display: none; - } -} -.konnector-section { - background-color: $default-background; - margin: 0 auto; - box-sizing: border-box; - padding-bottom: 1rem; - max-width: 45.75rem; - width: 100%; - @media #{$large-phone} { - padding-left: 1rem; - padding-right: 1rem; - } -} diff --git a/src/components/ConsumptionVisualizer/ConsumptionVisualizer.tsx b/src/components/ConsumptionVisualizer/ConsumptionVisualizer.tsx index 3abf83ce6491136f93ef9c8e1b8c661eff2907ba..ab0e5d35ef66101aebf52175ca76c3c1f1b2779c 100644 --- a/src/components/ConsumptionVisualizer/ConsumptionVisualizer.tsx +++ b/src/components/ConsumptionVisualizer/ConsumptionVisualizer.tsx @@ -7,14 +7,7 @@ import { useAppSelector } from 'store/hooks' import InfoDataConsumptionVisualizer from './InfoDataConsumptionVisualizer' import './consumptionVisualizer.scss' -interface ConsumptionVisualizerProps { - fluidType: FluidType - setActive: React.Dispatch<React.SetStateAction<boolean>> -} -const ConsumptionVisualizer = ({ - fluidType, - setActive, -}: ConsumptionVisualizerProps) => { +const ConsumptionVisualizer = ({ fluidType }: { fluidType: FluidType }) => { const { chart: { currentDatachart, currentDatachartIndex }, global: { fluidStatus, fluidTypes }, @@ -53,7 +46,6 @@ const ConsumptionVisualizer = ({ fluidType={fluidType} dataload={dataload} compareDataload={compareDataload} - setActive={setActive} /> <div className="consumptionvisualizer-info"> <InfoDataConsumptionVisualizer diff --git a/src/components/ConsumptionVisualizer/DataloadConsumptionVisualizer.spec.tsx b/src/components/ConsumptionVisualizer/DataloadConsumptionVisualizer.spec.tsx index b3c3b46eaefb8309acf47d6d73350e92f672bf24..e0afcdfbd52fa876d4cb2a597f6d8055a1021657 100644 --- a/src/components/ConsumptionVisualizer/DataloadConsumptionVisualizer.spec.tsx +++ b/src/components/ConsumptionVisualizer/DataloadConsumptionVisualizer.spec.tsx @@ -51,7 +51,6 @@ describe('Dataload consumption visualizer component', () => { fluidType={FluidType.ELECTRICITY} dataload={baseDataLoad} compareDataload={baseDataLoad} - setActive={jest.fn()} /> </Provider> ) @@ -65,7 +64,6 @@ describe('Dataload consumption visualizer component', () => { fluidType={FluidType.ELECTRICITY} dataload={null as unknown as Dataload} compareDataload={baseDataLoad} - setActive={jest.fn()} /> </Provider> ) @@ -81,7 +79,6 @@ describe('Dataload consumption visualizer component', () => { fluidType={FluidType.ELECTRICITY} dataload={{ ...baseDataLoad, date: DateTime.local(9999, 1, 1) }} compareDataload={baseDataLoad} - setActive={jest.fn()} /> </Provider> ) @@ -99,7 +96,6 @@ describe('Dataload consumption visualizer component', () => { fluidType={FluidType.ELECTRICITY} dataload={{ ...baseDataLoad, state: DataloadState.MISSING }} compareDataload={baseDataLoad} - setActive={jest.fn()} /> </Provider> ) @@ -116,7 +112,6 @@ describe('Dataload consumption visualizer component', () => { fluidType={FluidType.MULTIFLUID} dataload={baseMultiFluidDataLoad} compareDataload={null} - setActive={jest.fn()} /> </BrowserRouter> </Provider> @@ -135,7 +130,6 @@ describe('Dataload consumption visualizer component', () => { fluidType={FluidType.WATER} dataload={baseDataLoad} compareDataload={emptyDataLoad} - setActive={jest.fn()} /> </Provider> ) @@ -154,7 +148,6 @@ describe('Dataload consumption visualizer component', () => { fluidType={FluidType.WATER} dataload={baseDataLoad} compareDataload={baseDataLoad} - setActive={jest.fn()} /> </Provider> ) @@ -168,7 +161,6 @@ describe('Dataload consumption visualizer component', () => { fluidType={FluidType.MULTIFLUID} dataload={dataLoadWithValueDetailEmpty} compareDataload={emptyDataLoad} - setActive={jest.fn()} /> </Provider> ) @@ -191,7 +183,6 @@ describe('Dataload consumption visualizer component', () => { fluidType={FluidType.MULTIFLUID} dataload={dataLoadWithValueDetail} compareDataload={emptyDataLoad} - setActive={jest.fn()} /> </BrowserRouter> </Provider> diff --git a/src/components/ConsumptionVisualizer/DataloadConsumptionVisualizer.tsx b/src/components/ConsumptionVisualizer/DataloadConsumptionVisualizer.tsx index b7bc960a35d140af1f14870a7c04f70b0006a962..e28a077ee9150bb6862ee91bb293b8582f007ddd 100644 --- a/src/components/ConsumptionVisualizer/DataloadConsumptionVisualizer.tsx +++ b/src/components/ConsumptionVisualizer/DataloadConsumptionVisualizer.tsx @@ -12,13 +12,11 @@ interface DataloadConsumptionVisualizerProps { fluidType: FluidType dataload: Dataload compareDataload: Dataload | null - setActive: React.Dispatch<React.SetStateAction<boolean>> } const DataloadConsumptionVisualizer = ({ fluidType, dataload, compareDataload, - setActive, }: DataloadConsumptionVisualizerProps) => { const { showCompare } = useAppSelector(state => state.ecolyo.chart) const [openEstimationModal, setOpenEstimationModal] = useState<boolean>(false) @@ -39,11 +37,7 @@ const DataloadConsumptionVisualizer = ({ ) { return ( <div className="dataloadvisualizer-root"> - <DataloadNoValue - dataload={dataload} - setActive={setActive} - fluidType={fluidType} - /> + <DataloadNoValue dataload={dataload} fluidType={fluidType} /> </div> ) } diff --git a/src/components/ConsumptionVisualizer/DataloadNoValue.spec.tsx b/src/components/ConsumptionVisualizer/DataloadNoValue.spec.tsx index 4045300323387423250a533581a554488d9d6b0a..a270965bd3f07f817384b2ee62920df46f23bd5b 100644 --- a/src/components/ConsumptionVisualizer/DataloadNoValue.spec.tsx +++ b/src/components/ConsumptionVisualizer/DataloadNoValue.spec.tsx @@ -1,18 +1,31 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import { DataloadState, FluidType } from 'enums' import React from 'react' +import { Provider } from 'react-redux' +import * as storeHooks from 'store/hooks' import { baseDataLoad } from 'tests/__mocks__/chartData.mock' +import { createMockEcolyoStore } from 'tests/__mocks__/store' import DataloadNoValue from './DataloadNoValue' +const mockDispatch = jest.fn() +jest.spyOn(storeHooks, 'useAppDispatch').mockImplementation(() => mockDispatch) + describe('DataloadNoValue component', () => { + const store = createMockEcolyoStore() + + beforeEach(() => { + jest.clearAllMocks() + }) + it('should render correctly', () => { const { container } = render( - <DataloadNoValue - dataload={baseDataLoad} - fluidType={FluidType.ELECTRICITY} - setActive={jest.fn()} - /> + <Provider store={store}> + <DataloadNoValue + dataload={baseDataLoad} + fluidType={FluidType.ELECTRICITY} + /> + </Provider> ) expect(container).toMatchSnapshot() }) @@ -21,11 +34,12 @@ describe('DataloadNoValue component', () => { it('case state EMPTY', () => { const mockDataLoad = { ...baseDataLoad, state: DataloadState.EMPTY } render( - <DataloadNoValue - dataload={mockDataLoad} - fluidType={FluidType.ELECTRICITY} - setActive={jest.fn()} - /> + <Provider store={store}> + <DataloadNoValue + dataload={mockDataLoad} + fluidType={FluidType.ELECTRICITY} + /> + </Provider> ) expect( screen.getByText('consumption_visualizer.no_data') @@ -34,11 +48,12 @@ describe('DataloadNoValue component', () => { it('case state HOLE', () => { const mockDataLoad = { ...baseDataLoad, state: DataloadState.HOLE } render( - <DataloadNoValue - dataload={mockDataLoad} - fluidType={FluidType.ELECTRICITY} - setActive={jest.fn()} - /> + <Provider store={store}> + <DataloadNoValue + dataload={mockDataLoad} + fluidType={FluidType.ELECTRICITY} + /> + </Provider> ) expect( screen.getByText('consumption_visualizer.no_data') @@ -50,11 +65,12 @@ describe('DataloadNoValue component', () => { state: DataloadState.AGGREGATED_EMPTY, } render( - <DataloadNoValue - dataload={mockDataLoad} - fluidType={FluidType.MULTIFLUID} - setActive={jest.fn()} - /> + <Provider store={store}> + <DataloadNoValue + dataload={mockDataLoad} + fluidType={FluidType.MULTIFLUID} + /> + </Provider> ) expect( screen.getByText('consumption_visualizer.no_data') @@ -66,11 +82,12 @@ describe('DataloadNoValue component', () => { it('case state MISSING', () => { const mockDataLoad = { ...baseDataLoad, state: DataloadState.MISSING } render( - <DataloadNoValue - dataload={mockDataLoad} - fluidType={FluidType.ELECTRICITY} - setActive={jest.fn()} - /> + <Provider store={store}> + <DataloadNoValue + dataload={mockDataLoad} + fluidType={FluidType.ELECTRICITY} + /> + </Provider> ) expect( screen.getByText('consumption_visualizer.missing_data') @@ -82,11 +99,12 @@ describe('DataloadNoValue component', () => { state: DataloadState.AGGREGATED_HOLE_OR_MISSING, } render( - <DataloadNoValue - dataload={mockDataLoad} - fluidType={FluidType.MULTIFLUID} - setActive={jest.fn()} - /> + <Provider store={store}> + <DataloadNoValue + dataload={mockDataLoad} + fluidType={FluidType.MULTIFLUID} + /> + </Provider> ) expect( screen.getByText('consumption_visualizer.missing_data') @@ -94,17 +112,23 @@ describe('DataloadNoValue component', () => { }) }) - it('should call setActive when missing message is clicked', async () => { - const mockSetActive = jest.fn() - const mockDataLoad = { ...baseDataLoad, state: DataloadState.MISSING } + it('should call dispatch to show konnector details when missing message is clicked', async () => { render( - <DataloadNoValue - dataload={mockDataLoad} - fluidType={FluidType.ELECTRICITY} - setActive={mockSetActive} - /> + <Provider store={store}> + <DataloadNoValue + dataload={{ ...baseDataLoad, state: DataloadState.MISSING }} + fluidType={FluidType.ELECTRICITY} + /> + </Provider> ) - await userEvent.click(screen.getByRole('button')) - expect(mockSetActive).toHaveBeenCalledWith(true) + await act(async () => { + await userEvent.click( + screen.getByText('consumption_visualizer.missing_data') + ) + }) + expect(mockDispatch).toHaveBeenCalledWith({ + type: 'chart/setShowConnectionDetails', + payload: true, + }) }) }) diff --git a/src/components/ConsumptionVisualizer/DataloadNoValue.tsx b/src/components/ConsumptionVisualizer/DataloadNoValue.tsx index 3c55e9ed03502b456ecf0c5de3a4923ac3c8cb5d..7598813152c06aaa5ab6fc6d841cc15c10b2af0f 100644 --- a/src/components/ConsumptionVisualizer/DataloadNoValue.tsx +++ b/src/components/ConsumptionVisualizer/DataloadNoValue.tsx @@ -3,23 +3,23 @@ import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { DataloadState, FluidType } from 'enums' import { Dataload } from 'models' import React, { useCallback } from 'react' +import { setShowConnectionDetails } from 'store/chart/chart.slice' +import { useAppDispatch } from 'store/hooks' +import { getFluidName } from 'utils/utils' import './consumptionVisualizer.scss' interface DataloadNoValueProps { dataload: Dataload fluidType: FluidType - setActive: React.Dispatch<React.SetStateAction<boolean>> } -const DataloadNoValue = ({ - dataload, - fluidType, - setActive, -}: DataloadNoValueProps) => { +const DataloadNoValue = ({ dataload, fluidType }: DataloadNoValueProps) => { const { t } = useI18n() + const dispatch = useAppDispatch() + const fluidName = getFluidName(fluidType) - const handleToggleKonnectionCard = useCallback(() => { - setActive(true) + const handleToggleKonnectorCard = useCallback(() => { + dispatch(setShowConnectionDetails(true)) const app = document.querySelector('.app-content') const content = document.querySelector('.content-view') if (content && app) { @@ -37,7 +37,7 @@ const DataloadNoValue = ({ }) }, 300) } - }, [setActive]) + }, [dispatch]) if ( dataload.state === DataloadState.EMPTY || @@ -47,11 +47,7 @@ const DataloadNoValue = ({ return ( <div className="dataloadvisualizer-content text-22-normal"> <div className="dataloadvisualizer-section"> - <div - className={`dataloadvisualizer-value ${FluidType[ - fluidType - ].toLowerCase()} upper`} - > + <div className={`dataloadvisualizer-value ${fluidName} upper`}> {t('consumption_visualizer.no_data')} </div> </div> @@ -65,7 +61,7 @@ const DataloadNoValue = ({ ) { return ( <Button - onClick={handleToggleKonnectionCard} + onClick={handleToggleKonnectorCard} classes={{ root: 'btnText', label: 'text-22-normal' }} > {t('consumption_visualizer.missing_data')} @@ -76,11 +72,7 @@ const DataloadNoValue = ({ return ( <div className="dataloadvisualizer-content text-22-normal"> <div className="dataloadvisualizer-section"> - <div - className={`dataloadvisualizer-value ${FluidType[ - fluidType - ].toLowerCase()} upper to-come`} - > + <div className={`dataloadvisualizer-value ${fluidName} upper to-come`}> {t('consumption_visualizer.data_to_come')} </div> </div> diff --git a/src/components/ConsumptionVisualizer/DataloadSection.tsx b/src/components/ConsumptionVisualizer/DataloadSection.tsx index 16d52ce901cad4382a9606699a12cc0853b53ba8..84a6185eefcd99189f94671696fb370539330759 100644 --- a/src/components/ConsumptionVisualizer/DataloadSection.tsx +++ b/src/components/ConsumptionVisualizer/DataloadSection.tsx @@ -21,17 +21,17 @@ const DataloadSection = ({ toggleEstimationModal, }: DataloadSectionProps) => { const { t } = useI18n() + const isLeft = dataloadSectionType === DataloadSectionType.LEFT + const isRight = dataloadSectionType === DataloadSectionType.RIGHT + const noCompare = dataloadSectionType === DataloadSectionType.NO_COMPARE + const isMulti = fluidType === FluidType.MULTIFLUID + const fluidName = getFluidName(fluidType) - if ( - dataload.value === -1 && - dataloadSectionType === DataloadSectionType.LEFT - ) { + if (dataload.value === -1 && isLeft) { return ( <div className="dataloadvisualizer-section dataloadvisualizer-section-left-novalue"> <div - className={`dataloadvisualizer-novalue ${FluidType[ - fluidType - ].toLowerCase()}-compare text-20-normal`} + className={`dataloadvisualizer-novalue ${fluidName}-compare text-20-normal`} > {t('consumption_visualizer.no_data')} </div> @@ -39,12 +39,6 @@ const DataloadSection = ({ ) } - const isLeft = dataloadSectionType === DataloadSectionType.LEFT - const isRight = dataloadSectionType === DataloadSectionType.RIGHT - const noCompare = dataloadSectionType === DataloadSectionType.NO_COMPARE - const isMulti = fluidType === FluidType.MULTIFLUID - const fluidName = getFluidName(fluidType) - return ( <div className={classNames('dataloadvisualizer-section', { diff --git a/src/components/ConsumptionVisualizer/DataloadSectionValue.spec.tsx b/src/components/ConsumptionVisualizer/DataloadSectionValue.spec.tsx index 4b9d037bbc525929eaa3ba0fa2b1cf77b3d9cdb0..3661724c30e2e89bb0dd5b32c13c437fae2dfab4 100644 --- a/src/components/ConsumptionVisualizer/DataloadSectionValue.spec.tsx +++ b/src/components/ConsumptionVisualizer/DataloadSectionValue.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import { DataloadSectionType, FluidType } from 'enums' import { Dataload } from 'models' @@ -88,7 +88,11 @@ describe('DataloadSectionValue component', () => { toggleEstimationModal={mockToggleEstimationModal} /> ) - await userEvent.click(screen.getByRole('button')) + await act(async () => { + await userEvent.click( + screen.getByText('consumption_visualizer.estimated') + ) + }) expect(mockToggleEstimationModal).toHaveBeenCalled() }) }) diff --git a/src/components/ConsumptionVisualizer/DataloadSectionValue.tsx b/src/components/ConsumptionVisualizer/DataloadSectionValue.tsx index cfb77c138b854bcbd7d459aea4ffd44d84351c1a..77e227da2ad89bcde169a42729c620e712c8955a 100644 --- a/src/components/ConsumptionVisualizer/DataloadSectionValue.tsx +++ b/src/components/ConsumptionVisualizer/DataloadSectionValue.tsx @@ -3,7 +3,7 @@ import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { DataloadSectionType, FluidType } from 'enums' import { Dataload } from 'models' import React from 'react' -import { formatNumberValues } from 'utils/utils' +import { formatNumberValues, getFluidName } from 'utils/utils' interface DataloadSectionValueProps { dataload: Dataload @@ -19,13 +19,14 @@ const DataloadSectionValue = ({ toggleEstimationModal, }: DataloadSectionValueProps) => { const { t } = useI18n() + const FLUIDNAME = getFluidName(fluidType).toUpperCase() if (fluidType === FluidType.MULTIFLUID) { return ( <> {formatNumberValues(dataload.value)} <div className="text-18-normal euroUnit"> - {t(`FLUID.${FluidType[fluidType]}.UNIT`)} + {t(`FLUID.${FLUIDNAME}.UNIT`)} </div> {dataloadSectionType === DataloadSectionType.NO_COMPARE && ( <Button @@ -43,27 +44,21 @@ const DataloadSectionValue = ({ ) } - const formattedValue = formatNumberValues( - dataload.value, - FluidType[fluidType], - true - ) + const formattedValue = formatNumberValues(dataload.value, FLUIDNAME, true) return ( <> {Number(formattedValue) >= 1000 ? ( <> - {formatNumberValues(dataload.value, FluidType[fluidType])} + {formatNumberValues(dataload.value, FLUIDNAME)} <span className="text-18-normal"> - {t(`FLUID.${FluidType[fluidType]}.MEGAUNIT`)} + {t(`FLUID.${FLUIDNAME}.MEGAUNIT`)} </span> </> ) : ( <> {formatNumberValues(dataload.value)} - <span className="text-18-normal"> - {t(`FLUID.${FluidType[fluidType]}.UNIT`)} - </span> + <span className="text-18-normal">{t(`FLUID.${FLUIDNAME}.UNIT`)}</span> </> )} </> diff --git a/src/components/ConsumptionVisualizer/InfoDataConsumptionVisualizer.spec.tsx b/src/components/ConsumptionVisualizer/InfoDataConsumptionVisualizer.spec.tsx index 5c2018b0bdf54affb32cc73edda5c72f6954ecb3..4c6660d40956aaa653649e0b93d0064038c68592 100644 --- a/src/components/ConsumptionVisualizer/InfoDataConsumptionVisualizer.spec.tsx +++ b/src/components/ConsumptionVisualizer/InfoDataConsumptionVisualizer.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import { DataloadState, FluidType } from 'enums' import { DateTime } from 'luxon' @@ -189,7 +189,7 @@ describe('InfoDataConsumptionVisualizer component', () => { }) it('should click on last valid data date and move to it', async () => { - const mockDataLoad = { ...baseDataLoad, state: DataloadState.EMPTY } + const mockDataLoad = { ...baseDataLoad, state: DataloadState.UPCOMING } render( <Provider store={store}> <InfoDataConsumptionVisualizer @@ -199,8 +199,12 @@ describe('InfoDataConsumptionVisualizer component', () => { /> </Provider> ) - await userEvent.click(screen.getByRole('button')) - expect(screen.getByRole('dialog')).toBeInTheDocument() + await act(async () => { + await userEvent.click( + screen.getByText('consumption_visualizer.last_available_data') + ) + }) + expect(mockAppDispatch).toHaveBeenCalled() }) it('should click to display NoDataModal', async () => { @@ -214,7 +218,11 @@ describe('InfoDataConsumptionVisualizer component', () => { /> </Provider> ) - await userEvent.click(screen.getByRole('button')) - expect(mockAppDispatch).toHaveBeenCalled() + await act(async () => { + await userEvent.click( + screen.getByText('consumption_visualizer.why_no_data') + ) + }) + expect(screen.getByRole('dialog')).toBeInTheDocument() }) }) diff --git a/src/components/ConsumptionVisualizer/InfoDataConsumptionVisualizer.tsx b/src/components/ConsumptionVisualizer/InfoDataConsumptionVisualizer.tsx index f88a939c5fd228261016f74c02f0c3a674a14e70..2c0381eb6f25890ee3a081d8f4d7801222095876 100644 --- a/src/components/ConsumptionVisualizer/InfoDataConsumptionVisualizer.tsx +++ b/src/components/ConsumptionVisualizer/InfoDataConsumptionVisualizer.tsx @@ -1,12 +1,10 @@ import { Button } from '@material-ui/core' +import { useMoveToLatestDate } from 'components/Hooks/useMoveToDate' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { DataloadState, FluidType } from 'enums' import { DateTime } from 'luxon' import { Dataload } from 'models' import React, { useCallback, useState } from 'react' -import DateChartService from 'services/dateChart.service' -import { setCurrentIndex, setSelectedDate } from 'store/chart/chart.slice' -import { useAppDispatch, useAppSelector } from 'store/hooks' import NoDataModal from './NoDataModal' import './infoDataConsumptionVisualizer.scss' @@ -22,26 +20,13 @@ const InfoDataConsumptionVisualizer = ({ lastDataDate, }: InfoDataConsumptionVisualizerProps) => { const { t } = useI18n() - const dispatch = useAppDispatch() - const { currentTimeStep } = useAppSelector(state => state.ecolyo.chart) const [openNoDataModal, setOpenNoDataModal] = useState<boolean>(false) + const { moveToLatestDate } = useMoveToLatestDate(lastDataDate) const toggleNoDataModal = useCallback(() => { setOpenNoDataModal(prev => !prev) }, []) - const moveToDate = () => { - if (lastDataDate) { - const dateChartService = new DateChartService() - const updatedIndex = dateChartService.defineDateIndex( - currentTimeStep, - lastDataDate - ) - dispatch(setSelectedDate(lastDataDate)) - dispatch(setCurrentIndex(updatedIndex)) - } - } - if (!dataload) { return <></> } @@ -60,7 +45,7 @@ const InfoDataConsumptionVisualizer = ({ ? 'last_valid_data_multi' : 'last_available_data' return ( - <Button className="btnText" onClick={moveToDate}> + <Button className="btnText" onClick={moveToLatestDate}> {t(`consumption_visualizer.${key}`, { date: lastDate, })} diff --git a/src/components/Content/Content.tsx b/src/components/Content/Content.tsx index c3674fab4235568755913bb00c4ff131373ea883..f30e26160617fbeb18479fcec8327522b93e548a 100644 --- a/src/components/Content/Content.tsx +++ b/src/components/Content/Content.tsx @@ -4,14 +4,12 @@ import React, { useEffect } from 'react' import { changeScreenType } from 'store/global/global.slice' import { useAppDispatch, useAppSelector } from 'store/hooks' import './content.scss' -interface ContentProps { - children: React.ReactNode - heightOffset?: number -} -const Content = ({ children, heightOffset = 0 }: ContentProps) => { +const Content = ({ children }: { children: React.ReactNode }) => { const dispatch = useAppDispatch() - const { screenType } = useAppSelector(state => state.ecolyo.global) + const { screenType, headerHeight } = useAppSelector( + state => state.ecolyo.global + ) const cozyBarHeight = 48 const cozyNavHeight = 56 @@ -44,11 +42,11 @@ const Content = ({ children, heightOffset = 0 }: ContentProps) => { <div className="content-view" style={{ - marginTop: heightOffset, + marginTop: headerHeight, paddingBottom: 0, minHeight: screenType !== ScreenType.DESKTOP - ? `calc(100vh - ${heightOffset}px - ${cozyBarHeight}px - ${cozyNavHeight}px - env(safe-area-inset-top) - env(safe-area-inset-bottom) - env(safe-area-inset-bottom))` + ? `calc(100vh - ${headerHeight}px - ${cozyBarHeight}px - ${cozyNavHeight}px - env(safe-area-inset-top) - env(safe-area-inset-bottom) - env(safe-area-inset-bottom))` : `unset`, }} > diff --git a/src/components/Content/__snapshots__/Content.spec.tsx.snap b/src/components/Content/__snapshots__/Content.spec.tsx.snap index 9fc72e1b3ca438b10ddb746aec2a30352432ed49..608a28e19dae437364ac8de19f5e836218911dc5 100644 --- a/src/components/Content/__snapshots__/Content.spec.tsx.snap +++ b/src/components/Content/__snapshots__/Content.spec.tsx.snap @@ -4,7 +4,7 @@ exports[`Content component should match snapshot 1`] = ` <div> <div class="content-view" - style="margin-top: 0px; padding-bottom: 0px; min-height: calc(100vh - 0px - 48px - 56px - env(safe-area-inset-top) - env(safe-area-inset-bottom) - env(safe-area-inset-bottom));" + style="margin-top: 62px; padding-bottom: 0px; min-height: calc(100vh - 62px - 48px - 56px - env(safe-area-inset-top) - env(safe-area-inset-bottom) - env(safe-area-inset-bottom));" > children </div> diff --git a/src/components/CustomPopup/CustomPopupModal.spec.tsx b/src/components/CustomPopup/CustomPopupModal.spec.tsx index 357565d456e0d360ec9ad0730cafe63ed2b17ed4..5f3786a07ae9a3ee5fa7a06262232da12879cc40 100644 --- a/src/components/CustomPopup/CustomPopupModal.spec.tsx +++ b/src/components/CustomPopup/CustomPopupModal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { act, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' import { @@ -11,13 +11,14 @@ import CustomPopupModal from './CustomPopupModal' const mockHandleClose = jest.fn() describe('CustomPopupModal component', () => { - it('should render correctly', () => { - const { baseElement } = render( + it('should render correctly', async () => { + const { baseElement, container } = render( <CustomPopupModal customPopup={mockCustomPopup} handleCloseClick={mockHandleClose} /> ) + await waitFor(() => null, { container }) expect(baseElement).toMatchSnapshot() }) @@ -36,24 +37,26 @@ describe('CustomPopupModal component', () => { expect(screen.getByRole('img')).toBeInTheDocument() }) - it('should not be rendered, popup not enabled', () => { - render( + it('should not be rendered, popup not enabled', async () => { + const { container } = render( <CustomPopupModal customPopup={mockCustomPopupOff} handleCloseClick={mockHandleClose} /> ) + await waitFor(() => null, { container }) const modal = screen.queryByTestId('title') expect(modal).not.toBeInTheDocument() }) - it('should not be rendered, popup outdated', () => { - render( + it('should not be rendered, popup outdated', async () => { + const { container } = render( <CustomPopupModal customPopup={mockCustomPopupOutdated} handleCloseClick={mockHandleClose} /> ) + await waitFor(() => null, { container }) const modal = screen.queryByTestId('title') expect(modal).not.toBeInTheDocument() }) @@ -65,9 +68,11 @@ describe('CustomPopupModal component', () => { handleCloseClick={mockHandleClose} /> ) - await userEvent.click( - screen.getByText('consumption.partner_issue_modal.ok') - ) + await act(async () => { + await userEvent.click( + screen.getByText('consumption.partner_issue_modal.ok') + ) + }) expect(mockHandleClose).toHaveBeenCalled() }) }) diff --git a/src/components/CustomPopup/__snapshots__/CustomPopupModal.spec.tsx.snap b/src/components/CustomPopup/__snapshots__/CustomPopupModal.spec.tsx.snap index e145623144afc9a9613a33fdbb3d64e1874246a6..eaf9112489058b5343173335bd0e8254e9578380 100644 --- a/src/components/CustomPopup/__snapshots__/CustomPopupModal.spec.tsx.snap +++ b/src/components/CustomPopup/__snapshots__/CustomPopupModal.spec.tsx.snap @@ -71,7 +71,7 @@ exports[`CustomPopupModal component should render correctly 1`] = ` width="100" > <use - xlink:href="#" + xlink:href="#test-file-stub" /> </svg> <div diff --git a/src/components/DateNavigator/DateNavigator.spec.tsx b/src/components/DateNavigator/DateNavigator.spec.tsx index 1708d74e8b7efe019174f6b90eddb8126c5acab5..9d19aca3820b73cde756941e61b9b8c4535196ce 100644 --- a/src/components/DateNavigator/DateNavigator.spec.tsx +++ b/src/components/DateNavigator/DateNavigator.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { TimeStep } from 'enums' import { DateTime } from 'luxon' @@ -44,11 +44,17 @@ describe('DateNavigator component', () => { timeStep={TimeStep.MONTH} /> ) - const [prevIcon, nextIcon] = screen.getAllByRole('button') - await userEvent.click(prevIcon) + await act(async () => { + await userEvent.click( + screen.getByLabelText('consumption.accessibility.button_previous_value') + ) + }) expect(mockHandlePrevDate).toHaveBeenCalledTimes(1) - - await userEvent.click(nextIcon) + await act(async () => { + await userEvent.click( + screen.getByLabelText('consumption.accessibility.button_next_value') + ) + }) expect(mockHandleNextDate).toHaveBeenCalledTimes(1) }) @@ -63,8 +69,13 @@ describe('DateNavigator component', () => { timeStep={TimeStep.MONTH} /> ) - const [prevIcon, nextButton] = screen.getAllByRole('button') - expect(prevIcon).toBeDisabled() - expect(nextButton).toBeDisabled() + const prev = screen.queryByLabelText( + 'consumption.accessibility.button_previous_value' + ) + const next = screen.queryByLabelText( + 'consumption.accessibility.button_next_value' + ) + expect(prev).toBeDisabled() + expect(next).toBeDisabled() }) }) diff --git a/src/components/Duel/DuelChart/DuelBar.tsx b/src/components/Duel/DuelChart/DuelBar.tsx index a23d20f574bc89df850b1a9ed20f4cdca8e53a2b..d90ebddcefa7d14d1763717c7768601a6f7bb6e9 100644 --- a/src/components/Duel/DuelChart/DuelBar.tsx +++ b/src/components/Duel/DuelChart/DuelBar.tsx @@ -1,7 +1,7 @@ import AxisBottom from 'components/Charts/AxisBottom' import AxisRight from 'components/Charts/AxisRight' import Bar from 'components/Charts/Bar' -import UncomingBar from 'components/Charts/UncomingBar' +import UpcomingBar from 'components/Charts/UpcomingBar' import { scaleBand, ScaleBand, scaleLinear, ScaleLinear } from 'd3-scale' import { FluidType, TimeStep } from 'enums' import { DateTime } from 'luxon' @@ -116,7 +116,7 @@ const DuelBar = ({ ) } else { return ( - <UncomingBar + <UpcomingBar key={d.date.toISO()} index={index} dataload={d} diff --git a/src/components/Duel/DuelOngoing/DuelOngoing.tsx b/src/components/Duel/DuelOngoing/DuelOngoing.tsx index d0ea2a2b03882d64ee2de72e8a864fcd7824626d..e5f8335b43654c066cdb4492f4835e49ae55884d 100644 --- a/src/components/Duel/DuelOngoing/DuelOngoing.tsx +++ b/src/components/Duel/DuelOngoing/DuelOngoing.tsx @@ -58,7 +58,6 @@ const DuelOngoing = ({ userChallenge, isFinished }: DuelOngoingProps) => { const average = formatNumberValues(userChallenge.duel.threshold).toString() const setResult = useCallback(async () => { - const challengeService = new ChallengeService(client) const updatedChallenge = await challengeService.updateUserChallenge( userChallenge, winChallenge @@ -74,7 +73,14 @@ const DuelOngoing = ({ userChallenge, isFinished }: DuelOngoingProps) => { } else { navigate('/challenges') } - }, [client, userChallenge, winChallenge, dispatch, isLastDuel, navigate]) + }, [ + challengeService, + userChallenge, + winChallenge, + dispatch, + isLastDuel, + navigate, + ]) useEffect(() => { let subscribed = true diff --git a/src/components/Duel/DuelUnlocked/DuelUnlocked.spec.tsx b/src/components/Duel/DuelUnlocked/DuelUnlocked.spec.tsx index 2020acbf2463e81a138d16451c5542e727c618d3..60e1521ee5c96dc6b2864c1579b9e3d74da7aa67 100644 --- a/src/components/Duel/DuelUnlocked/DuelUnlocked.spec.tsx +++ b/src/components/Duel/DuelUnlocked/DuelUnlocked.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import { FluidType, UserChallengeUpdateFlag } from 'enums' import React from 'react' @@ -38,7 +38,7 @@ describe('DuelUnlocked component', () => { screen.getByText(userChallengeData[1].duel.title) ).toBeInTheDocument() expect(screen.getByText(description)).toBeInTheDocument() - expect(screen.getByRole('button')).toBeInTheDocument() + expect(screen.getByText('duel.button_start')).toBeInTheDocument() }) it('should update userChallenge when launching duel with configured fluid', async () => { @@ -53,7 +53,9 @@ describe('DuelUnlocked component', () => { <DuelUnlocked userChallenge={userChallengeData[0]} /> </Provider> ) - await userEvent.click(screen.getByRole('button')) + await act(async () => { + await userEvent.click(screen.getByText('duel.button_start')) + }) expect(mockUserChallengeUpdateFlag).toHaveBeenCalledWith( userChallengeData[0], UserChallengeUpdateFlag.DUEL_START diff --git a/src/components/Duel/DuelView.tsx b/src/components/Duel/DuelView.tsx index 1c60115ab09cdfac9793d86f43417f5f6763b3c2..b4004e0732b552ae5393eaa7b1ab0d8b32630ec3 100644 --- a/src/components/Duel/DuelView.tsx +++ b/src/components/Duel/DuelView.tsx @@ -3,7 +3,7 @@ import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' import { UserDuelState } from 'enums' import { UserChallenge } from 'models' -import React, { useState } from 'react' +import React from 'react' import { useLocation, useNavigate } from 'react-router-dom' import { useAppSelector } from 'store/hooks' import DuelEmptyValueModal from './DuelEmptyValueModal/DuelEmptyValueModal' @@ -14,7 +14,6 @@ import DuelUnlocked from './DuelUnlocked/DuelUnlocked' const DuelView = () => { const navigate = useNavigate() const { userChallengeList } = useAppSelector(state => state.ecolyo.challenge) - const [headerHeight, setHeaderHeight] = useState<number>(0) const id = new URLSearchParams(useLocation().search).get('id') const challengeToDisplay: UserChallenge | undefined = userChallengeList.find( challenge => challenge.id === id @@ -46,14 +45,8 @@ const DuelView = () => { return ( <> <CozyBar titleKey="common.title_duel" displayBackArrow={true} /> - <Header - setHeaderHeight={setHeaderHeight} - desktopTitleKey="common.title_duel" - displayBackArrow={true} - /> - <Content heightOffset={headerHeight}> - {renderDuel(challengeToDisplay)} - </Content> + <Header desktopTitleKey="common.title_duel" displayBackArrow={true} /> + <Content>{renderDuel(challengeToDisplay)}</Content> </> ) } diff --git a/src/components/Ecogesture/EcogestureEmptyList/EcogestureEmptyList.spec.tsx b/src/components/Ecogesture/EcogestureEmptyList/EcogestureEmptyList.spec.tsx index 700c11a8efa8c5877468079223efc666210373b4..8c7af8fa436ff6f007d0c6531447e7591056a068 100644 --- a/src/components/Ecogesture/EcogestureEmptyList/EcogestureEmptyList.spec.tsx +++ b/src/components/Ecogesture/EcogestureEmptyList/EcogestureEmptyList.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import EcogestureEmptyList from './EcogestureEmptyList' @@ -9,7 +9,6 @@ jest.mock('react-router-dom', () => ({ useNavigate: () => mockedNavigate, })) const mockChangeTab = jest.fn() -const mockHandleClick = jest.fn() describe('EcogestureEmptyList component', () => { it('should be rendered correctly', () => { @@ -18,7 +17,7 @@ describe('EcogestureEmptyList component', () => { setTab={mockChangeTab} isObjective={true} isSelectionDone={false} - handleReinitClick={mockHandleClick} + handleReinitClick={jest.fn()} /> ) expect(container).toMatchSnapshot() @@ -29,10 +28,12 @@ describe('EcogestureEmptyList component', () => { setTab={mockChangeTab} isObjective={false} isSelectionDone={true} - handleReinitClick={mockHandleClick} + handleReinitClick={jest.fn()} /> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click(screen.getByLabelText('ecogesture.emptyList.btn1')) + }) expect(mockChangeTab).toHaveBeenCalledTimes(1) }) it('should launch ecogesture form', async () => { @@ -41,10 +42,12 @@ describe('EcogestureEmptyList component', () => { setTab={mockChangeTab} isObjective={false} isSelectionDone={false} - handleReinitClick={mockHandleClick} + handleReinitClick={jest.fn()} /> ) - await userEvent.click(screen.getAllByRole('button')[1]) + await act(async () => { + await userEvent.click(screen.getByLabelText('ecogesture.emptyList.btn2')) + }) expect(mockedNavigate).toHaveBeenCalledWith('/ecogesture-form') }) it('should render doing text with empty list on completed selection', () => { @@ -53,7 +56,7 @@ describe('EcogestureEmptyList component', () => { setTab={mockChangeTab} isObjective={false} isSelectionDone={true} - handleReinitClick={mockHandleClick} + handleReinitClick={jest.fn()} /> ) expect( @@ -66,7 +69,7 @@ describe('EcogestureEmptyList component', () => { setTab={mockChangeTab} isObjective={true} isSelectionDone={true} - handleReinitClick={mockHandleClick} + handleReinitClick={jest.fn()} /> ) expect( diff --git a/src/components/Ecogesture/EcogestureInitModal/EcogestureInitModal.spec.tsx b/src/components/Ecogesture/EcogestureInitModal/EcogestureInitModal.spec.tsx index 6ff90feae74098a6d7384f0306bebab7d0baa4ae..f42e399cf57084624bb061cff2dc17dae592fb1e 100644 --- a/src/components/Ecogesture/EcogestureInitModal/EcogestureInitModal.spec.tsx +++ b/src/components/Ecogesture/EcogestureInitModal/EcogestureInitModal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import EcogestureInitModal from './EcogestureInitModal' @@ -24,7 +24,9 @@ describe('EcogestureInitModal component', () => { handleLaunchForm={mockHandleLaunchForm} /> ) - await userEvent.click(screen.getAllByRole('button')[2]) + await act(async () => { + await userEvent.click(screen.getByText('ecogesture.initModal.btn1')) + }) expect(mockHandleClose).toHaveBeenCalledTimes(1) }) it('should close modal and launch form', async () => { @@ -35,7 +37,9 @@ describe('EcogestureInitModal component', () => { handleLaunchForm={mockHandleLaunchForm} /> ) - await userEvent.click(screen.getAllByRole('button')[1]) + await act(async () => { + await userEvent.click(screen.getByText('ecogesture.initModal.btn2')) + }) expect(mockHandleLaunchForm).toHaveBeenCalledTimes(1) }) }) diff --git a/src/components/Ecogesture/EcogestureList/EcogestureList.spec.tsx b/src/components/Ecogesture/EcogestureList/EcogestureList.spec.tsx index b230d0fd10a2a02fddb498042d9b629af2034c81..bff4393a53fdfed61b124c92955352205e7fd2a0 100644 --- a/src/components/Ecogesture/EcogestureList/EcogestureList.spec.tsx +++ b/src/components/Ecogesture/EcogestureList/EcogestureList.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -48,7 +48,7 @@ describe('EcogesturesList component', () => { }) it('should open the filter menu and select all ecogesture', async () => { - const { container } = render( + render( <Provider store={store}> <BrowserRouter> <EcogestureList @@ -60,14 +60,17 @@ describe('EcogesturesList component', () => { </BrowserRouter> </Provider> ) - await waitFor(() => null, { container }) - await userEvent.click(screen.getAllByRole('button')[0]) - await userEvent.click(screen.getAllByRole('menuitem')[1]) + await act(async () => { + await userEvent.click(screen.getByText('ecogesture.MENU_TITLE')) + }) + await act(async () => { + await userEvent.click(screen.getAllByRole('menuitem')[1]) + }) expect(updateEcogestureFilter).toHaveBeenCalledTimes(1) }) it('should go to "/ecogesture-selection"', async () => { - const { container } = render( + render( <Provider store={store}> <BrowserRouter> <EcogestureList @@ -79,8 +82,9 @@ describe('EcogesturesList component', () => { </BrowserRouter> </Provider> ) - await waitFor(() => null, { container }) - await userEvent.click(screen.getAllByRole('button')[1]) + await act(async () => { + await userEvent.click(screen.getByText('ecogesture.button_selection')) + }) expect(mockedNavigate).toHaveBeenCalledWith('/ecogesture-selection') }) }) diff --git a/src/components/Ecogesture/EcogestureNotFound/EcogestureNotFound.spec.tsx b/src/components/Ecogesture/EcogestureNotFound/EcogestureNotFound.spec.tsx index 28d77f7ca042e4cd09c9c22769d48655913f13cc..2ca68993812bf704ba4c9caa6ca2024f649beb1b 100644 --- a/src/components/Ecogesture/EcogestureNotFound/EcogestureNotFound.spec.tsx +++ b/src/components/Ecogesture/EcogestureNotFound/EcogestureNotFound.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import EcogestureNotFound from './EcogestureNotFound' @@ -22,7 +22,9 @@ describe('EcogestureNotFound component', () => { }) it('should click on button and be redirected', async () => { render(<EcogestureNotFound text="test" returnPage="ecogestures" />) - await userEvent.click(screen.getByRole('button')) + await act(async () => { + await userEvent.click(screen.getByText('error_page.back')) + }) expect(mockedNavigate).toHaveBeenCalledWith('/ecogestures') }) }) diff --git a/src/components/Ecogesture/EcogestureNotFound/EcogestureNotFound.tsx b/src/components/Ecogesture/EcogestureNotFound/EcogestureNotFound.tsx index 78ef552bf893e62b384b11b20cb1274ab607098a..c4bf6436168bc290dd586869332eacff4c980af0 100644 --- a/src/components/Ecogesture/EcogestureNotFound/EcogestureNotFound.tsx +++ b/src/components/Ecogesture/EcogestureNotFound/EcogestureNotFound.tsx @@ -5,7 +5,7 @@ import Content from 'components/Content/Content' import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import React, { useState } from 'react' +import React from 'react' import { useNavigate } from 'react-router-dom' import './ecogestureNotFound.scss' @@ -18,16 +18,12 @@ const EcogestureNotFound = ({ }) => { const { t } = useI18n() const navigate = useNavigate() - const [headerHeight, setHeaderHeight] = useState<number>(0) return ( <> <CozyBar titleKey="error_page.main" /> - <Header - setHeaderHeight={setHeaderHeight} - desktopTitleKey="error_page.main" - /> - <Content heightOffset={headerHeight}> + <Header desktopTitleKey="error_page.main" /> + <Content> <div className="error-container"> <StyledIcon className="profile-icon" icon={BearIcon} size={250} /> diff --git a/src/components/Ecogesture/EcogestureNotFound/__snapshots__/EcogestureNotFound.spec.tsx.snap b/src/components/Ecogesture/EcogestureNotFound/__snapshots__/EcogestureNotFound.spec.tsx.snap index a991c215c20992f6faf548741dfa93c8fe97e850..c6390dcbde7545e575c73c7134cb4eac3b4bc6a4 100644 --- a/src/components/Ecogesture/EcogestureNotFound/__snapshots__/EcogestureNotFound.spec.tsx.snap +++ b/src/components/Ecogesture/EcogestureNotFound/__snapshots__/EcogestureNotFound.spec.tsx.snap @@ -8,9 +8,7 @@ exports[`EcogestureNotFound component should be rendered correctly 1`] = ` <mock-header desktoptitlekey="error_page.main" /> - <mock-content - heightoffset="0" - > + <mock-content> <div class="error-container" > diff --git a/src/components/Ecogesture/EcogestureTabsView.spec.tsx b/src/components/Ecogesture/EcogestureTabsView.spec.tsx index d9806f63d0fd7d4fb203a9ec11a604a4eaaa5704..92561d3dd52cca64fe62f0a3cb25b666ad025e39 100644 --- a/src/components/Ecogesture/EcogestureTabsView.spec.tsx +++ b/src/components/Ecogesture/EcogestureTabsView.spec.tsx @@ -1,16 +1,29 @@ import { render, screen, waitFor } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import EcogestureTabsView from 'components/Ecogesture/EcogestureTabsView' +import { Ecogesture } from 'models' import React from 'react' import { Provider } from 'react-redux' import * as profileActions from 'store/profile/profile.slice' -import { mockedEcogesturesData } from 'tests/__mocks__/ecogesturesData.mock' +import { mockDoingEcogestures } from 'tests/__mocks__/ecogesturesData.mock' import { createMockEcolyoStore } from 'tests/__mocks__/store' -const mockInitEcogesture = jest.fn().mockResolvedValue(mockedEcogesturesData) +const mockInitEcogesture = jest + .fn< + Promise<{ + ecogestureHash: string + ecogestureList: Ecogesture[] + }>, + [] + >() + .mockResolvedValue({ + ecogestureHash: 'hash', + ecogestureList: mockDoingEcogestures, + }) +const mockGetEcogesture = jest.fn().mockResolvedValue([]) jest.mock('services/ecogesture.service', () => { return jest.fn(() => ({ - getEcogestureListByProfile: jest.fn().mockResolvedValue([]), + getEcogestureListByProfile: mockGetEcogesture, initEcogesture: mockInitEcogesture, })) }) @@ -44,7 +57,7 @@ describe('EcogestureView component', () => { jest.clearAllMocks() }) - it('should be rendered correctly', async () => { + it('should be rendered correctly with 3 clickable tabs', async () => { const { container } = render( <Provider store={store}> <EcogestureTabsView /> @@ -57,44 +70,20 @@ describe('EcogestureView component', () => { it('should render ecogesture init modal', async () => { const updateProfileSpy = jest.spyOn(profileActions, 'updateProfile') - - const { container } = render( + render( <Provider store={store}> <EcogestureTabsView /> </Provider> ) - await waitFor(() => null, { container }) expect(screen.getByRole('dialog')).toBeInTheDocument() - await userEvent.click(screen.getAllByRole('button')[0]) + + await waitFor(async () => { + await userEvent.click( + screen.getByLabelText('feedback.accessibility.button_close') + ) + }) expect(updateProfileSpy).toHaveBeenCalledWith({ haveSeenEcogestureModal: true, }) }) - - it('should render empty list', async () => { - mockInitEcogesture.mockResolvedValueOnce([]) - const { container } = render( - <Provider store={store}> - <EcogestureTabsView /> - </Provider> - ) - await waitFor(() => null, { container }) - expect( - container.getElementsByClassName('ec-empty-container').length - ).toBeTruthy() - }) - - it('should change tab', async () => { - const { container } = render( - <Provider store={store}> - <EcogestureTabsView /> - </Provider> - ) - await waitFor(() => null, { container }) - await userEvent.click(screen.getAllByRole('button')[1]) - await waitFor(() => null, { container }) - expect( - container.getElementsByClassName('ec-empty-container').length - ).toBeTruthy() - }) }) diff --git a/src/components/Ecogesture/EcogestureTabsView.tsx b/src/components/Ecogesture/EcogestureTabsView.tsx index a0491c0974d72c455e4ef61e9ec607394e6a465a..f968257255b60a1a1d08624853602f1be02a1dce 100644 --- a/src/components/Ecogesture/EcogestureTabsView.tsx +++ b/src/components/Ecogesture/EcogestureTabsView.tsx @@ -8,7 +8,7 @@ import { useClient } from 'cozy-client' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { EcogestureTab } from 'enums' import { Ecogesture } from 'models' -import React, { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useLocation, useNavigate } from 'react-router-dom' import EcogestureService from 'services/ecogesture.service' import { useAppDispatch, useAppSelector } from 'store/hooks' @@ -47,7 +47,6 @@ const EcogestureTabsView = () => { state => state.ecolyo ) - const [headerHeight, setHeaderHeight] = useState<number>(0) const [tabValue, setTabValue] = useState<EcogestureTab>( tab ? parseInt(tab) : EcogestureTab.OBJECTIVE ) @@ -66,6 +65,11 @@ const EcogestureTabsView = () => { const [openEcogestureReinitModal, setOpenEcogestureReinitModal] = useState<boolean>(false) + const ecogestureService = useMemo( + () => new EcogestureService(client), + [client] + ) + const handleReinitClick = useCallback(() => { setOpenEcogestureReinitModal(true) }, []) @@ -84,14 +88,13 @@ const EcogestureTabsView = () => { const handleLaunchReinit = useCallback(async () => { setOpenEcogestureReinitModal(false) setIsLoading(true) - const ecogestureService = new EcogestureService(client) const reset = await ecogestureService.reinitAllEcogestures() if (reset) { setOpenEcogestureReinitModal(false) setIsLoading(false) navigate('/ecogesture-form?modal=true') } - }, [client, navigate]) + }, [ecogestureService, navigate]) const handleCloseEcogestureReinitModal = useCallback(() => { setOpenEcogestureReinitModal(false) @@ -135,8 +138,9 @@ const EcogestureTabsView = () => { useEffect(() => { let subscribed = true async function loadEcogestures() { - const ecogestureService = new EcogestureService(client) - + const currentProfile = profile.isProfileTypeCompleted + ? profileType + : profileEcogesture const { ecogestureList, ecogestureHash } = await ecogestureService.initEcogesture(profile.ecogestureHash) @@ -145,10 +149,11 @@ const EcogestureTabsView = () => { } const availableList = - await ecogestureService.getEcogestureListByProfile(profileEcogesture) + await ecogestureService.getEcogestureListByProfile(currentProfile) const filteredList = availableList.filter( ecogesture => ecogesture.viewedInSelection === false ) + if (subscribed && ecogestureList) { const doing = ecogestureList.filter( ecogesture => ecogesture.doing === true @@ -164,19 +169,24 @@ const EcogestureTabsView = () => { } setIsLoading(false) } + loadEcogestures() return () => { subscribed = false } - }, [client, profileEcogesture, profileType, dispatch, profile.ecogestureHash]) + }, [ + profileEcogesture, + profileType, + dispatch, + profile.ecogestureHash, + profile.isProfileTypeCompleted, + ecogestureService, + ]) return ( <> <CozyBar titleKey="common.title_ecogestures" /> - <Header - setHeaderHeight={setHeaderHeight} - desktopTitleKey="common.title_ecogestures" - > + <Header desktopTitleKey="common.title_ecogestures"> <Tabs value={tabValue} className="ecogestures-tabs" @@ -215,7 +225,7 @@ const EcogestureTabsView = () => { </Tabs> </Header> - <Content heightOffset={headerHeight}> + <Content> {isLoading && ( <div className="loaderContainer"> <Loader text={t('ecogestures.loading')} /> diff --git a/src/components/Ecogesture/SingleEcogestureView.spec.tsx b/src/components/Ecogesture/SingleEcogestureView.spec.tsx index 5f89c46a40f1459f52cfd96a25921a9cfa704b22..2d42b995b77be45ca1d7fccafc9cb8f099876ed9 100644 --- a/src/components/Ecogesture/SingleEcogestureView.spec.tsx +++ b/src/components/Ecogesture/SingleEcogestureView.spec.tsx @@ -24,6 +24,9 @@ jest.mock('services/ecogesture.service', () => { }) describe('SingleEcogesture component', () => { + beforeEach(() => { + jest.clearAllMocks() + }) const store = createMockEcolyoStore() it('should be rendered correctly', async () => { mockGetEcogesturesByIds.mockResolvedValue([mockedEcogesturesData[0]]) @@ -41,14 +44,14 @@ describe('SingleEcogesture component', () => { const updatedEcogesture = { ...mockedEcogesturesData[0], objective: true } mockUpdateEcogesture.mockResolvedValueOnce(updatedEcogesture) - const { container } = render( + render( <Provider store={store}> <SingleEcogestureView /> </Provider> ) - await waitFor(() => null, { container }) - const [, objective] = screen.getAllByRole('button') - await userEvent.click(objective) + await waitFor(async () => { + await userEvent.click(screen.getByText('ecogesture.objective')) + }) expect(mockUpdateEcogesture).toHaveBeenCalledWith(updatedEcogesture) }) @@ -57,28 +60,27 @@ describe('SingleEcogesture component', () => { const updatedEcogesture = { ...mockedEcogesturesData[0], doing: true } mockUpdateEcogesture.mockResolvedValueOnce(updatedEcogesture) - const { container } = render( + render( <Provider store={store}> <SingleEcogestureView /> </Provider> ) - await waitFor(() => null, { container }) - const [, , doing] = screen.getAllByRole('button') - await userEvent.click(doing) + await waitFor(async () => { + await userEvent.click(screen.getByText('ecogesture.doing')) + }) expect(mockUpdateEcogesture).toHaveBeenCalledWith(updatedEcogesture) }) it('should toggle more details', async () => { mockGetEcogesturesByIds.mockResolvedValue([mockedEcogesturesData[0]]) - - const { container } = render( + render( <Provider store={store}> <SingleEcogestureView /> </Provider> ) - await waitFor(() => null, { container }) - const [showMore] = screen.getAllByRole('button') - await userEvent.click(showMore) + await waitFor(async () => { + await userEvent.click(screen.getByText('ecogesture_modal.show_more')) + }) expect(screen.getByText('ecogesture_modal.show_less')).toBeInTheDocument() }) }) diff --git a/src/components/Ecogesture/SingleEcogestureView.tsx b/src/components/Ecogesture/SingleEcogestureView.tsx index 7f2c228dfa79fbf77c7a95633d6d9cd4c8211941..3254cab1b5023afba53c6a495c5b653feb01ec27 100644 --- a/src/components/Ecogesture/SingleEcogestureView.tsx +++ b/src/components/Ecogesture/SingleEcogestureView.tsx @@ -42,16 +42,15 @@ const SingleEcogestureView = () => { [client] ) const { currentChallenge } = useAppSelector(state => state.ecolyo.challenge) - const [headerHeight, setHeaderHeight] = useState<number>(0) const [, setValidExploration] = useExploration() const updateEcogesture = useCallback( - async (objective, doing) => { + async (objective: boolean, doing: boolean) => { if (ecogesture) { const updates: Ecogesture = { ...ecogesture, - objective: objective, - doing: doing, + objective, + doing, } const result = await ecogestureService.updateEcogesture(updates) if (result) { @@ -115,11 +114,10 @@ const SingleEcogestureView = () => { <> <CozyBar titleKey="common.title_ecogesture" displayBackArrow={true} /> <Header - setHeaderHeight={setHeaderHeight} desktopTitleKey="common.title_ecogesture" displayBackArrow={true} /> - <Content heightOffset={headerHeight}> + <Content> {isLoading && ( <div className="loaderContainer"> <Loader /> diff --git a/src/components/Ecogesture/__snapshots__/EcogestureTabsView.spec.tsx.snap b/src/components/Ecogesture/__snapshots__/EcogestureTabsView.spec.tsx.snap index e8ada2259dc01776e81f24afd7453e4054a9b768..7ccd151382e3967c0377207e3fe1b6bf5c066cc7 100644 --- a/src/components/Ecogesture/__snapshots__/EcogestureTabsView.spec.tsx.snap +++ b/src/components/Ecogesture/__snapshots__/EcogestureTabsView.spec.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EcogestureView component should be rendered correctly 1`] = ` +exports[`EcogestureView component should be rendered correctly with 3 clickable tabs 1`] = ` <div aria-hidden="true" > @@ -56,7 +56,7 @@ exports[`EcogestureView component should be rendered correctly 1`] = ` > ecogesture.title_tab_1 <br /> - (0) + (2) </span> <span class="MuiTouchRipple-root" @@ -76,7 +76,7 @@ exports[`EcogestureView component should be rendered correctly 1`] = ` > ecogesture.title_tab_2 <br /> - (0) + (3) </span> <span class="MuiTouchRipple-root" @@ -90,9 +90,7 @@ exports[`EcogestureView component should be rendered correctly 1`] = ` </div> </div> </mock-header> - <mock-content - heightoffset="0" - > + <mock-content> <div aria-labelledby="simple-tab-0" id="simple-tabpanel-0" @@ -223,7 +221,14 @@ exports[`EcogestureView component should be rendered correctly 1`] = ` hidden="" id="simple-tabpanel-2" role="tabpanel" - /> + > + <mock-ecogesturelist + displayselection="false" + list="[object Object],[object Object],[object Object]" + selectiontotal="0" + selectionviewed="0" + /> + </div> </mock-content> </div> `; diff --git a/src/components/Ecogesture/__snapshots__/SingleEcogestureView.spec.tsx.snap b/src/components/Ecogesture/__snapshots__/SingleEcogestureView.spec.tsx.snap index d5009811f16c175b7b06d0b1940e281c59c30564..e035305baaeb0458c5215c6fdfcca0a0defea995 100644 --- a/src/components/Ecogesture/__snapshots__/SingleEcogestureView.spec.tsx.snap +++ b/src/components/Ecogesture/__snapshots__/SingleEcogestureView.spec.tsx.snap @@ -10,9 +10,7 @@ exports[`SingleEcogesture component should be rendered correctly 1`] = ` desktoptitlekey="common.title_ecogesture" displaybackarrow="true" /> - <mock-content - heightoffset="0" - > + <mock-content> <div class="single-ecogesture" > diff --git a/src/components/EcogestureForm/EcogestureFormEquipment/EcogestureFormEquipment.spec.tsx b/src/components/EcogestureForm/EcogestureFormEquipment/EcogestureFormEquipment.spec.tsx index 59dfa2b6d36071b61298edabf1f48741dec0b365..2ad4b9d739e95b24815f3f3201351459419d3683 100644 --- a/src/components/EcogestureForm/EcogestureFormEquipment/EcogestureFormEquipment.spec.tsx +++ b/src/components/EcogestureForm/EcogestureFormEquipment/EcogestureFormEquipment.spec.tsx @@ -56,9 +56,7 @@ describe('EcogestureFormEquipment component', () => { container.getElementsByClassName('icons-container').length ).toBeTruthy() expect( - screen.getByRole('button', { - name: 'profile_type.accessibility.button_end', - }) + screen.getByLabelText('profile_type.accessibility.button_end') ).toBeDisabled() }) }) diff --git a/src/components/EcogestureForm/EcogestureFormEquipment/__snapshots__/EcogestureFormEquipment.spec.tsx.snap b/src/components/EcogestureForm/EcogestureFormEquipment/__snapshots__/EcogestureFormEquipment.spec.tsx.snap index 61893059fa3c958e17c6ae4e4dc4d66c90fd1952..69febe91b1c98633b4b08d40e9cecfc9c52df63f 100644 --- a/src/components/EcogestureForm/EcogestureFormEquipment/__snapshots__/EcogestureFormEquipment.spec.tsx.snap +++ b/src/components/EcogestureForm/EcogestureFormEquipment/__snapshots__/EcogestureFormEquipment.spec.tsx.snap @@ -306,6 +306,25 @@ exports[`EcogestureFormEquipment component should be rendered correctly 1`] = ` class="MuiTouchRipple-root" /> </button> + <button + aria-label="ecogesture_profile.equipments.accessible_label" + class="MuiButtonBase-root MuiIconButton-root checkbox-equipment" + style="border-radius: 5px;" + tabindex="0" + type="button" + > + <span + class="MuiIconButton-label" + > + <mock-equipment-icon + equipment="OUTSIDE" + ischecked="false" + /> + </span> + <span + class="MuiTouchRipple-root" + /> + </button> </div> </div> </div> diff --git a/src/components/EcogestureForm/EcogestureFormSingleChoice/EcogestureFormSingleChoice.spec.tsx b/src/components/EcogestureForm/EcogestureFormSingleChoice/EcogestureFormSingleChoice.spec.tsx index b7e89d07dbfa9cbc95c18262d2831f73c54d511e..ae223e032303b4dcde387af005696795ed5240cd 100644 --- a/src/components/EcogestureForm/EcogestureFormSingleChoice/EcogestureFormSingleChoice.spec.tsx +++ b/src/components/EcogestureForm/EcogestureFormSingleChoice/EcogestureFormSingleChoice.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -49,8 +49,11 @@ describe('EcogestureFormSingleChoice component', () => { </Provider> ) await userEvent.click(screen.getAllByRole('radio')[0]) - const [, next] = screen.getAllByRole('button') - await userEvent.click(next) + await act(async () => { + await userEvent.click( + screen.getByLabelText('profile_type.accessibility.button_next') + ) + }) expect(mockHandleNextStep).toHaveBeenCalledTimes(1) }) it('should go back', async () => { @@ -66,8 +69,11 @@ describe('EcogestureFormSingleChoice component', () => { /> </Provider> ) - const [prev] = screen.getAllByRole('button') - await userEvent.click(prev) + await act(async () => { + await userEvent.click( + screen.getByLabelText('profile_type.accessibility.button_previous') + ) + }) expect(mockHandlePreviousStep).toHaveBeenCalledTimes(1) }) it('should keep previous answer', async () => { diff --git a/src/components/EcogestureForm/EcogestureFormView.spec.tsx b/src/components/EcogestureForm/EcogestureFormView.spec.tsx index 501a60176665575e4f897dc6c382aaefa39b5f99..d3aac9ddd9e08290b52c0a27f5dc03d1ec67bb5f 100644 --- a/src/components/EcogestureForm/EcogestureFormView.spec.tsx +++ b/src/components/EcogestureForm/EcogestureFormView.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -69,24 +69,12 @@ describe('EcogestureFormView component', () => { <EcogestureFormView /> </Provider> ) - // await waitFor(() => null, { container }) - await userEvent.click(screen.getAllByRole('radio')[0]) - await userEvent.click(screen.getAllByRole('button')[1]) - expect( - container.getElementsByClassName('ecogesture-form-single').length - ).toBeTruthy() - }) - it('should go to previous step', async () => { - const { container } = render( - <Provider store={store}> - <EcogestureFormView /> - </Provider> - ) - - const [, next] = screen.getAllByRole('button') - await userEvent.click(screen.getAllByRole('radio')[0]) - await userEvent.click(next) - + await act(async () => { + await userEvent.click(screen.getAllByRole('radio')[0]) + await userEvent.click( + screen.getByLabelText('profile_type.accessibility.button_next') + ) + }) expect( container.getElementsByClassName('ecogesture-form-single').length ).toBeTruthy() @@ -112,7 +100,8 @@ describe('EcogestureFormView component', () => { <EcogestureFormView /> </Provider> ) - const [prev] = screen.getAllByRole('button') - expect(prev).toBeDisabled() + expect( + screen.getByLabelText('profile_type.accessibility.button_previous') + ).toBeDisabled() }) }) diff --git a/src/components/EcogestureForm/EcogestureFormView.tsx b/src/components/EcogestureForm/EcogestureFormView.tsx index dc4e57379310b77bddf7fa3b1bf3f3f6011e298a..337592ae8141bb394a64e3f577b6c5ee1c384244 100644 --- a/src/components/EcogestureForm/EcogestureFormView.tsx +++ b/src/components/EcogestureForm/EcogestureFormView.tsx @@ -5,7 +5,7 @@ import Loader from 'components/Loader/Loader' import ProfileTypeView from 'components/ProfileType/ProfileTypeView' import { EcogestureStepForm, ProfileEcogestureAnswerType } from 'enums' import { ProfileEcogesture, ProfileEcogestureAnswer } from 'models' -import React, { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useLocation, useNavigate } from 'react-router-dom' import ProfileEcogestureFormService from 'services/profileEcogestureForm.service' import { useAppDispatch, useAppSelector } from 'store/hooks' @@ -22,9 +22,9 @@ const EcogestureFormView = () => { profile: { isProfileTypeCompleted }, profileEcogesture, } = useAppSelector(state => state.ecolyo) - const [headerHeight, setHeaderHeight] = useState<number>(0) const shouldOpenModal = new URLSearchParams(useLocation().search).get('modal') + const [step, setStep] = useState<EcogestureStepForm>( EcogestureStepForm.HEATING_TYPE ) @@ -33,7 +33,6 @@ const EcogestureFormView = () => { attribute: 'heating', choices: [], }) - const [isLoading, setIsLoading] = useState<boolean>(true) const [openLaunchModal, setOpenLaunchModal] = useState<boolean>( shouldOpenModal !== 'false' ? true : false @@ -41,10 +40,16 @@ const EcogestureFormView = () => { const [viewedStep, setViewedStep] = useState<number>(-1) const [currentProfileEcogesture, setCurrentProfileEcogesture] = useState<ProfileEcogesture>(profileEcogesture) + + /** ProfileEcogestureFormService */ + const pefs = useMemo( + () => new ProfileEcogestureFormService(profileEcogesture), + [profileEcogesture] + ) + const setNextStep = useCallback( (_profileEcogesture: ProfileEcogesture) => { setCurrentProfileEcogesture(_profileEcogesture) - const pefs = new ProfileEcogestureFormService(_profileEcogesture) const nextStep = pefs.getNextFormStep(step) setIsLoading(true) if (nextStep > viewedStep) { @@ -55,17 +60,16 @@ const EcogestureFormView = () => { setAnswerType(_answerType) setStep(nextStep) }, - [step, viewedStep] + [pefs, step, viewedStep] ) const setPreviousStep = useCallback(() => { - const pefs = new ProfileEcogestureFormService(currentProfileEcogesture) const previousStep = pefs.getPreviousFormStep(step) setIsLoading(true) const _answerType = ProfileEcogestureFormService.getAnswerForStep(previousStep) setAnswerType(_answerType) setStep(previousStep) - }, [currentProfileEcogesture, step]) + }, [pefs, step]) const handleEndForm = useCallback(() => { dispatch(newProfileEcogestureEntry(currentProfileEcogesture)) @@ -91,11 +95,8 @@ const EcogestureFormView = () => { return ( <> <CozyBar titleKey="common.title_ecogestures" /> - <Header - setHeaderHeight={setHeaderHeight} - desktopTitleKey="common.title_ecogestures" - /> - <Content heightOffset={headerHeight}> + <Header desktopTitleKey="common.title_ecogestures" /> + <Content> {isLoading && ( <div className="loaderContainer"> <Loader /> diff --git a/src/components/EcogestureForm/EcogestureLaunchFormModal/EcogestureLaunchFormModal.spec.tsx b/src/components/EcogestureForm/EcogestureLaunchFormModal/EcogestureLaunchFormModal.spec.tsx index be8c6d25ed9da77a23d42ded998b09c51d0d2d03..23c3651722ed024a9587a7de8950a35c3174c697 100644 --- a/src/components/EcogestureForm/EcogestureLaunchFormModal/EcogestureLaunchFormModal.spec.tsx +++ b/src/components/EcogestureForm/EcogestureLaunchFormModal/EcogestureLaunchFormModal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import EcogestureLaunchFormModal from './EcogestureLaunchFormModal' @@ -21,7 +21,9 @@ describe('EcogestureLaunchFormModal component', () => { handleCloseClick={mockHandleClose} /> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click(screen.getByText('ecogesture.initModal.btn2')) + }) expect(mockHandleClose).toHaveBeenCalledTimes(1) }) }) diff --git a/src/components/EcogestureForm/__snapshots__/EcogestureFormView.spec.tsx.snap b/src/components/EcogestureForm/__snapshots__/EcogestureFormView.spec.tsx.snap index b759c1dabdb8f7bb39bb2352dfd9b5ce5e328f6a..843e5be16fa4073f91f44198153361d2f5bec129 100644 --- a/src/components/EcogestureForm/__snapshots__/EcogestureFormView.spec.tsx.snap +++ b/src/components/EcogestureForm/__snapshots__/EcogestureFormView.spec.tsx.snap @@ -8,9 +8,7 @@ exports[`EcogestureFormView component should be rendered correctly 1`] = ` <mock-header desktoptitlekey="common.title_ecogestures" /> - <mock-content - heightoffset="0" - > + <mock-content> <div class="ecogesture-profile-container" > diff --git a/src/components/EcogestureSelection/EcogestureSelectionDetail/EcogestureSelectionDetail.spec.tsx b/src/components/EcogestureSelection/EcogestureSelectionDetail/EcogestureSelectionDetail.spec.tsx index 4f5261c876fa60a9f14195280c7176826b2c7e7d..5608a326dbb4161f40386bff9d8e374be2603b14 100644 --- a/src/components/EcogestureSelection/EcogestureSelectionDetail/EcogestureSelectionDetail.spec.tsx +++ b/src/components/EcogestureSelection/EcogestureSelectionDetail/EcogestureSelectionDetail.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { act, render, screen, waitFor } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { mockedEcogesturesData } from 'tests/__mocks__/ecogesturesData.mock' @@ -33,25 +33,35 @@ describe('EcogestureSelectionDetail component', () => { }) it('should toggle more details', async () => { - const [showMore] = screen.getAllByRole('button') - await userEvent.click(showMore) + await act(async () => { + await userEvent.click(screen.getByText('ecogesture_modal.show_more')) + }) expect(screen.getByText('ecogesture_modal.show_less')).toBeInTheDocument() }) it('should call validate with objective to true', async () => { - const [, objective] = screen.getAllByRole('button') - await userEvent.click(objective) + await act(async () => { + await userEvent.click( + screen.getByText('ecogesture_selection.button_objective') + ) + }) expect(mockValidate).toHaveBeenCalledWith(true, false) }) it('should call validate with doing to true', async () => { - const [, , doing] = screen.getAllByRole('button') - await userEvent.click(doing) + await act(async () => { + await userEvent.click( + screen.getByText('ecogesture_selection.button_doing') + ) + }) expect(mockValidate).toHaveBeenCalledWith(false, true) }) it('should call validate with objective and doing to false', async () => { - const [, , , skip] = screen.getAllByRole('button') - await userEvent.click(skip) + await act(async () => { + await userEvent.click( + screen.getByText('ecogesture_selection.button_skip') + ) + }) expect(mockValidate).toHaveBeenCalledWith(false, false) }) }) diff --git a/src/components/EcogestureSelection/EcogestureSelectionEnd/EcogestureSelectionEnd.spec.tsx b/src/components/EcogestureSelection/EcogestureSelectionEnd/EcogestureSelectionEnd.spec.tsx index bcf32fb5e275cd73f0aa02e926b606770626728b..b17eb56aa8c598a687b102eb3b88085a36071122 100644 --- a/src/components/EcogestureSelection/EcogestureSelectionEnd/EcogestureSelectionEnd.spec.tsx +++ b/src/components/EcogestureSelection/EcogestureSelectionEnd/EcogestureSelectionEnd.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import EcogestureSelectionEnd from './EcogestureSelectionEnd' @@ -21,7 +21,9 @@ describe('EcogestureSelectionEnd component', () => { it('should close modal and update profile', async () => { render(<EcogestureSelectionEnd />) - await userEvent.click(screen.getByRole('button')) + await act(async () => { + await userEvent.click(screen.getByText('ecogesture_selection.button_ok')) + }) expect(mockedNavigate).toHaveBeenCalledWith('/ecogestures?tab=0') }) }) diff --git a/src/components/EcogestureSelection/EcogestureSelectionModal/EcogestureSelectionModal.spec.tsx b/src/components/EcogestureSelection/EcogestureSelectionModal/EcogestureSelectionModal.spec.tsx index 11801f5c9df1a75ea26f7c6354b5bf5a05e43c49..6b72660464b8a92259ffd9acfa628856cec44276 100644 --- a/src/components/EcogestureSelection/EcogestureSelectionModal/EcogestureSelectionModal.spec.tsx +++ b/src/components/EcogestureSelection/EcogestureSelectionModal/EcogestureSelectionModal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' import EcogestureSelectionModal from './EcogestureSelectionModal' @@ -22,7 +22,11 @@ describe('EcogestureInitModal component', () => { handleCloseClick={mockHandleClose} /> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getByText('ecogesture_selection.selectionModal.button_close') + ) + }) expect(mockHandleClose).toHaveBeenCalledTimes(1) }) }) diff --git a/src/components/EcogestureSelection/EcogestureSelectionRestart/EcogestureSelectionRestart.spec.tsx b/src/components/EcogestureSelection/EcogestureSelectionRestart/EcogestureSelectionRestart.spec.tsx index 0a5e78b1680da49a01b246af2f290c60ba4c1fb1..e8f57688cdabd241c507f523ee6b74cb2ad9c6e4 100644 --- a/src/components/EcogestureSelection/EcogestureSelectionRestart/EcogestureSelectionRestart.spec.tsx +++ b/src/components/EcogestureSelection/EcogestureSelectionRestart/EcogestureSelectionRestart.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' import EcogestureSelectionRestart from './EcogestureSelectionRestart' @@ -20,13 +20,21 @@ describe('EcogestureSelectionRestart component', () => { it('should call go to the ecogesture view when user click on the button', async () => { render(<EcogestureSelectionRestart listLength={10} restart={mockRestart} />) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getByText('ecogesture_selection.button_go_to_ecogesture') + ) + }) expect(mockedNavigate).toHaveBeenCalledWith('/ecogestures?tab=0') }) it('should call the restart when user click on the button', async () => { render(<EcogestureSelectionRestart listLength={10} restart={mockRestart} />) - await userEvent.click(screen.getAllByRole('button')[1]) + await act(async () => { + await userEvent.click( + screen.getByText('ecogesture_selection.button_continue') + ) + }) expect(mockRestart).toHaveBeenCalledTimes(1) }) }) diff --git a/src/components/EcogestureSelection/EcogestureSelectionView.tsx b/src/components/EcogestureSelection/EcogestureSelectionView.tsx index 749060770df62a29fd23f39092b29e6331b86dd1..2231b9fad5dcebfd100e3cde624a67a7de318066 100644 --- a/src/components/EcogestureSelection/EcogestureSelectionView.tsx +++ b/src/components/EcogestureSelection/EcogestureSelectionView.tsx @@ -19,9 +19,12 @@ const EcogestureSelectionView = () => { const { t } = useI18n() const client = useClient() const navigate = useNavigate() - const { profileEcogesture } = useAppSelector(state => state.ecolyo) + const { + profileType, + profileEcogesture, + profile: { isProfileTypeCompleted }, + } = useAppSelector(state => state.ecolyo) const [isLoading, setIsLoading] = useState(true) - const [headerHeight, setHeaderHeight] = useState<number>(0) const [indexEcogesture, setIndexEcogesture] = useState<number>(0) const [ecogestureList, setEcogestureList] = useState<Ecogesture[]>([]) const [totalViewed, setTotalViewed] = useState<number>(0) @@ -46,16 +49,17 @@ const EcogestureSelectionView = () => { const validateChoice = useCallback( async (objective: boolean, doing: boolean) => { - const updatedEcogesture: Ecogesture = - await ecogestureService.updateEcogesture({ - ...ecogestureList[indexEcogesture], - objective: objective, - doing: doing, - viewedInSelection: true, - }) - const updatedList: Ecogesture[] = ecogestureList - updatedList[indexEcogesture] = updatedEcogesture - setEcogestureList(updatedList) + const updatedEcogesture = await ecogestureService.updateEcogesture({ + ...ecogestureList[indexEcogesture], + objective: objective, + doing: doing, + viewedInSelection: true, + }) + setEcogestureList(prevList => { + const updatedList = [...prevList] + updatedList[indexEcogesture] = updatedEcogesture + return updatedList + }) setIndexEcogesture(prev => prev + 1) }, [ecogestureList, ecogestureService, indexEcogesture] @@ -63,30 +67,37 @@ const EcogestureSelectionView = () => { const restartSelection = useCallback(async () => { setIsLoading(true) - const availableList: Ecogesture[] = - await ecogestureService.getEcogestureListByProfile(profileEcogesture) - const filteredList: Ecogesture[] = availableList.filter( - (ecogesture: Ecogesture) => ecogesture.viewedInSelection === false + const profile = isProfileTypeCompleted ? profileType : profileEcogesture + const availableList = + await ecogestureService.getEcogestureListByProfile(profile) + const filteredList = availableList.filter( + ecogesture => ecogesture.viewedInSelection === false ) const slicedFilteredList = filteredList.slice(0, 10) setTotalViewed(availableList.length - filteredList.length) setEcogestureList(slicedFilteredList) setIndexEcogesture(0) setIsLoading(false) - }, [ecogestureService, profileEcogesture]) + }, [ + ecogestureService, + isProfileTypeCompleted, + profileEcogesture, + profileType, + ]) useEffect(() => { let subscribed = true async function getFilteredList() { - const availableList: Ecogesture[] = - await ecogestureService.getEcogestureListByProfile(profileEcogesture) - const filteredList: Ecogesture[] = availableList.filter( - (ecogesture: Ecogesture) => ecogesture.viewedInSelection === false + const profile = isProfileTypeCompleted ? profileType : profileEcogesture + const availableList = + await ecogestureService.getEcogestureListByProfile(profile) + const filteredList = availableList.filter( + ecogesture => ecogesture.viewedInSelection === false ) const slicedFilteredList = filteredList.slice(0, 10) if (subscribed) { if ( - availableList.length === filteredList.length && + availableList.length === slicedFilteredList.length && slicedFilteredList.length > 0 ) { setOpenEcogestureSelectionModal(true) @@ -98,11 +109,17 @@ const EcogestureSelectionView = () => { setIsLoading(false) } } + getFilteredList() return () => { subscribed = false } - }, [ecogestureService, profileEcogesture]) + }, [ + ecogestureService, + profileType, + profileEcogesture, + isProfileTypeCompleted, + ]) const renderEcogestureSelection = () => { if (indexEcogesture <= ecogestureList.length - 1) { @@ -133,7 +150,6 @@ const EcogestureSelectionView = () => { backFunction={() => navigate('/ecogestures')} /> <Header - setHeaderHeight={setHeaderHeight} desktopTitleKey="common.title_ecogestures_choice" displayBackArrow={true} > @@ -143,7 +159,7 @@ const EcogestureSelectionView = () => { : ''} </div> </Header> - <Content heightOffset={headerHeight}> + <Content> {isLoading && ( <div className="loaderContainer"> <Loader /> diff --git a/src/components/EcogestureSelection/__snapshots__/EcogestureSelectionView.spec.tsx.snap b/src/components/EcogestureSelection/__snapshots__/EcogestureSelectionView.spec.tsx.snap index d27b25cb74a62bbd1fb43d742645ba4059225031..b93b5cb8ea5343e60e55b3450120df58ffaf7ab9 100644 --- a/src/components/EcogestureSelection/__snapshots__/EcogestureSelectionView.spec.tsx.snap +++ b/src/components/EcogestureSelection/__snapshots__/EcogestureSelectionView.spec.tsx.snap @@ -16,9 +16,7 @@ exports[`EcogestureSelection component should be rendered correctly 1`] = ` 1/1 </div> </mock-header> - <mock-content - heightoffset="0" - > + <mock-content> <mock-ecogestureselectiondetail ecogesture="[object Object]" title="Bonhomme de neige" diff --git a/src/components/Exploration/ExplorationFinished.spec.tsx b/src/components/Exploration/ExplorationFinished.spec.tsx index 132eae2437c7893488c4f6a1b81d4c0edda1322a..8dc48fdbcde9eb7af951be0d7a5414cc81e3b824 100644 --- a/src/components/Exploration/ExplorationFinished.spec.tsx +++ b/src/components/Exploration/ExplorationFinished.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -26,7 +26,9 @@ describe('ExplorationFinished', () => { expect( screen.getByText(userChallengeData[0].exploration.message_success) ).toBeInTheDocument() - await userEvent.click(screen.getByRole('button')) + await act(async () => { + await userEvent.click(screen.getByText('exploration.button_confirm')) + }) expect(mockNavigate).toHaveBeenCalledWith(-1) }) }) diff --git a/src/components/Exploration/ExplorationOngoing.tsx b/src/components/Exploration/ExplorationOngoing.tsx index 850f4aabd717e9c86057be61e586670c49080ad6..347081ae2935b8ac21e527414341c2829c2d3059 100644 --- a/src/components/Exploration/ExplorationOngoing.tsx +++ b/src/components/Exploration/ExplorationOngoing.tsx @@ -26,9 +26,11 @@ const ExplorationOngoing = ({ userChallenge }: ExplorationOngoingProps) => { const client = useClient() const dispatch = useAppDispatch() const navigate = useNavigate() + + const challengeService = new ChallengeService(client) + const startExploration = async () => { if (userChallenge.exploration.state !== UserExplorationState.ONGOING) { - const challengeService = new ChallengeService(client) const updatedChallenge = await challengeService.updateUserChallenge( userChallenge, UserChallengeUpdateFlag.EXPLORATION_START @@ -39,7 +41,6 @@ const ExplorationOngoing = ({ userChallenge }: ExplorationOngoingProps) => { } const validExploration = async () => { - const challengeService = new ChallengeService(client) const updatedChallenge = await challengeService.updateUserChallenge( userChallenge, UserChallengeUpdateFlag.EXPLORATION_DONE diff --git a/src/components/Exploration/ExplorationView.tsx b/src/components/Exploration/ExplorationView.tsx index 54106238c9db76a3568939e1d8555de613288457..ccde8fdb71798e3b0d362c581504a822cdfee278 100644 --- a/src/components/Exploration/ExplorationView.tsx +++ b/src/components/Exploration/ExplorationView.tsx @@ -3,7 +3,7 @@ import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' import { UserExplorationState } from 'enums' import { UserChallenge } from 'models' -import React, { useState } from 'react' +import React from 'react' import { useAppSelector } from 'store/hooks' import ExplorationError from './ExplorationError' import ExplorationFinished from './ExplorationFinished' @@ -11,7 +11,6 @@ import ExplorationOngoing from './ExplorationOngoing' const ExplorationView = () => { const { currentChallenge } = useAppSelector(state => state.ecolyo.challenge) - const [headerHeight, setHeaderHeight] = useState<number>(0) const renderExploration = (challenge: UserChallenge) => { switch (challenge.exploration.state) { @@ -30,11 +29,10 @@ const ExplorationView = () => { <> <CozyBar titleKey="common.title_exploration" displayBackArrow={true} /> <Header - setHeaderHeight={setHeaderHeight} desktopTitleKey="common.title_exploration" displayBackArrow={true} /> - <Content heightOffset={headerHeight}> + <Content> {currentChallenge && renderExploration(currentChallenge)} </Content> </> diff --git a/src/components/Feedback/FeedbackModal.spec.tsx b/src/components/Feedback/FeedbackModal.spec.tsx index 3efbb966a636928f2319a6ce8d1e9190cf53a5e8..d4b8e53a77fbdfcd0a68e8f904621914dbaef3c8 100644 --- a/src/components/Feedback/FeedbackModal.spec.tsx +++ b/src/components/Feedback/FeedbackModal.spec.tsx @@ -1,9 +1,8 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import FeedbackModal from 'components/Feedback/FeedbackModal' import React from 'react' import { Provider } from 'react-redux' -import * as storeHooks from 'store/hooks' import { createMockEcolyoStore } from 'tests/__mocks__/store' // Value coming from jest.config @@ -17,8 +16,6 @@ jest.mock('services/environment.service', () => { jest.mock('components/Hooks/useExploration', () => () => ['', jest.fn()]) -const mockAppDispatch = jest.spyOn(storeHooks, 'useAppDispatch') - describe('FeedbackModal component', () => { const store = createMockEcolyoStore({ modal: { isFeedbacksOpen: true } }) beforeEach(() => { @@ -30,40 +27,21 @@ describe('FeedbackModal component', () => { <FeedbackModal /> </Provider> ) + expect(screen.getByRole('dialog')).toBeInTheDocument() expect(baseElement).toMatchSnapshot() }) - describe('FeedbackModal functionalities', () => { - it('should close modal with the "x" button', async () => { - render( - <Provider store={store}> - <FeedbackModal /> - </Provider> - ) - await userEvent.click(screen.getAllByRole('button')[0]) - expect(mockAppDispatch).toHaveBeenCalledTimes(1) - }) - - it('should close modal with the "later" button', async () => { - render( - <Provider store={store}> - <FeedbackModal /> - </Provider> - ) - await userEvent.click(screen.getAllByRole('button')[1]) - expect(mockAppDispatch).toHaveBeenCalledTimes(1) - }) - - it('should open the SAU link', async () => { - global.open = jest.fn() - render( - <Provider store={store}> - <FeedbackModal /> - </Provider> - ) - await userEvent.click(screen.getAllByRole('button')[2]) - expect(window.open).toHaveBeenCalledTimes(1) - expect(global.open).toHaveBeenCalledWith(`${__SAU_LINK__}?version=0.0.0`) + it('should open the SAU link', async () => { + global.open = jest.fn() + render( + <Provider store={store}> + <FeedbackModal /> + </Provider> + ) + await act(async () => { + await userEvent.click(screen.getByText('feedback.lets_go')) }) + expect(window.open).toHaveBeenCalledTimes(1) + expect(global.open).toHaveBeenCalledWith(`${__SAU_LINK__}?version=0.0.0`) }) }) diff --git a/src/components/FluidChart/FluidChart.tsx b/src/components/FluidChart/FluidChart.tsx index 2b5c2e45a73b9e64102dde5608476e04c17051e6..c03ea3ad25e1174fc717a7193223ccb7a4289fc5 100644 --- a/src/components/FluidChart/FluidChart.tsx +++ b/src/components/FluidChart/FluidChart.tsx @@ -3,6 +3,7 @@ import LegendComparisonIcon from 'assets/icons/ico/legendComparison.svg' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' import StyledSwitch from 'components/CommonKit/Switch/StyledSwitch' import useExploration from 'components/Hooks/useExploration' +import { useMoveToLatestDate } from 'components/Hooks/useMoveToDate' import { useClient } from 'cozy-client' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { FluidType, TimeStep, UserExplorationID } from 'enums' @@ -10,13 +11,7 @@ import { DateTime } from 'luxon' import React, { useCallback, useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import ConsumptionService from 'services/consumption.service' -import DateChartService from 'services/dateChart.service' -import { - setCurrentIndex, - setSelectedDate, - setShowCompare, - setShowOfflineData, -} from 'store/chart/chart.slice' +import { setShowCompare, setShowOfflineData } from 'store/chart/chart.slice' import { useAppDispatch, useAppSelector } from 'store/hooks' import { getFluidName, getKonnectorSlug, isKonnectorActive } from 'utils/utils' import FluidChartSwipe from './FluidChartSwipe' @@ -25,12 +20,7 @@ import HalfHourUpcoming from './HalfHourUpcoming/HalfHourUpcoming' import TimeStepSelector from './TimeStepSelector/TimeStepSelector' import './fluidChart.scss' -interface FluidChartProps { - fluidType: FluidType - setActive: React.Dispatch<React.SetStateAction<boolean>> -} - -const FluidChart = ({ fluidType, setActive }: FluidChartProps) => { +const FluidChart = ({ fluidType }: { fluidType: FluidType }) => { const { t } = useI18n() const client = useClient() const { @@ -42,6 +32,9 @@ const FluidChart = ({ fluidType, setActive }: FluidChartProps) => { const currentFluidStatus = fluidStatus[fluidType] const isFluidConnected = isKonnectorActive(fluidStatus, fluidType) + const { moveToLatestDate } = useMoveToLatestDate( + currentFluidStatus?.lastDataDate + ) const [, setValidExploration] = useExploration() const [containsHalfHourData, setContainsHalfHourData] = useState<boolean>(false) @@ -108,19 +101,6 @@ const FluidChart = ({ fluidType, setActive }: FluidChartProps) => { [lowercaseFluidType, lowercaseTimeStep, t] ) - // TODO if we keep this, use the same existing function - const moveToDate = () => { - if (currentFluidStatus?.lastDataDate) { - const dateChartService = new DateChartService() - const updatedIndex = dateChartService.defineDateIndex( - currentTimeStep, - currentFluidStatus.lastDataDate - ) - dispatch(setSelectedDate(currentFluidStatus.lastDataDate)) - dispatch(setCurrentIndex(updatedIndex)) - } - } - const toggleModalConnection = () => { switch (fluidType) { case FluidType.ELECTRICITY: @@ -137,7 +117,7 @@ const FluidChart = ({ fluidType, setActive }: FluidChartProps) => { const LastDataValid = fluidType !== FluidType.MULTIFLUID && ( <div className="lastValidData"> - <Button className="btnText" onClick={moveToDate}> + <Button className="btnText" onClick={moveToLatestDate}> {t('consumption_visualizer.last_valid_data', { date: currentFluidStatus?.lastDataDate?.toFormat('dd/MM/yy') ?? '-', })} @@ -166,7 +146,7 @@ const FluidChart = ({ fluidType, setActive }: FluidChartProps) => { return ( <> <div className="fluidchart-content"> - <FluidChartSwipe fluidType={fluidType} setActive={setActive} /> + <FluidChartSwipe fluidType={fluidType} /> </div> {showCompare && currentTimeStep !== TimeStep.YEAR && ( <Slide direction="right" in={showCompare}> diff --git a/src/components/FluidChart/FluidChartSlide.tsx b/src/components/FluidChart/FluidChartSlide.tsx index 5be0634977f3cfdcd80bd50e2d7f6c64ed82559a..9a66f31ed68559e22efadc6a80a795e3a7ef0fc0 100644 --- a/src/components/FluidChart/FluidChartSlide.tsx +++ b/src/components/FluidChart/FluidChartSlide.tsx @@ -8,7 +8,7 @@ import { Datachart } from 'models' import React, { useEffect, useState } from 'react' import ConsumptionService from 'services/consumption.service' import DateChartService from 'services/dateChart.service' -import { setCurrentDataChart, setLoading } from 'store/chart/chart.slice' +import { setCurrentDataChart } from 'store/chart/chart.slice' import { useAppDispatch, useAppSelector } from 'store/hooks' import './fluidChartSlide.scss' @@ -18,7 +18,6 @@ interface FluidChartSlideProps { width: number height: number isSwitching: boolean - setActive: React.Dispatch<React.SetStateAction<boolean>> } const FluidChartSlide = ({ @@ -27,7 +26,6 @@ const FluidChartSlide = ({ width, height, isSwitching, - setActive, }: FluidChartSlideProps) => { const client = useClient() const dispatch = useAppDispatch() @@ -72,20 +70,19 @@ const FluidChartSlide = ({ ), ]) const consumptionService = new ConsumptionService(client) - const fluidTypeArray: FluidType[] = - fluidType === FluidType.MULTIFLUID ? fluidTypes : [fluidType] + const isMulti = fluidType === FluidType.MULTIFLUID + const fluidTypeArray: FluidType[] = isMulti ? fluidTypes : [fluidType] const graphData = await consumptionService.getGraphData( timePeriod, currentTimeStep, fluidTypeArray, fluidStatus, compareTimePeriod, - fluidType === FluidType.MULTIFLUID + isMulti ) if (subscribed && graphData && graphData?.actualData.length > 0) { setChartData(graphData) setIsDataLoaded(true) - dispatch(setLoading(false)) } } } @@ -120,7 +117,7 @@ const FluidChartSlide = ({ </div> ) : ( <> - <ConsumptionVisualizer fluidType={fluidType} setActive={setActive} /> + <ConsumptionVisualizer fluidType={fluidType} /> <BarChart chartData={chartData} fluidType={fluidType} diff --git a/src/components/FluidChart/FluidChartSwipe.tsx b/src/components/FluidChart/FluidChartSwipe.tsx index 68c3ee7b2105658129224995389a0310f3f88fd9..934e86468f5da625b27ff521a5ef50921edffb37 100644 --- a/src/components/FluidChart/FluidChartSwipe.tsx +++ b/src/components/FluidChart/FluidChartSwipe.tsx @@ -12,15 +12,11 @@ import './fluidChartSwipe.scss' const VirtualizeSwipeableViews = virtualize(SwipeableViews) -interface FluidChartSwipeProps { - fluidType: FluidType - setActive: React.Dispatch<React.SetStateAction<boolean>> -} - -const FluidChartSwipe = ({ fluidType, setActive }: FluidChartSwipeProps) => { +const FluidChartSwipe = ({ fluidType }: { fluidType: FluidType }) => { const dispatch = useAppDispatch() - const { currentIndex, currentTimeStep, selectedDate, loading } = - useAppSelector(state => state.ecolyo.chart) + const { currentIndex, currentTimeStep, selectedDate } = useAppSelector( + state => state.ecolyo.chart + ) const swipe = useRef<HTMLDivElement>(null) const [isSwitching, setIsSwitching] = useState(false) @@ -53,7 +49,7 @@ const FluidChartSwipe = ({ fluidType, setActive }: FluidChartSwipeProps) => { dispatch(setCurrentIndex(updatedIndex)) } - const { height, width } = useChartResize(swipe, loading) + const { height, width } = useChartResize(swipe, false) useEffect(() => { function initIndex() { @@ -75,7 +71,6 @@ const FluidChartSwipe = ({ fluidType, setActive }: FluidChartSwipeProps) => { width={width} height={height} isSwitching={isSwitching} - setActive={setActive} /> ) diff --git a/src/components/FluidChart/TimeStepSelector/TimeStepSelector.spec.tsx b/src/components/FluidChart/TimeStepSelector/TimeStepSelector.spec.tsx index 60ddf0fedc7ea42905fcea16dbdd6798484559ad..91673e4f66235d9212d9dced10cabb021fbb674f 100644 --- a/src/components/FluidChart/TimeStepSelector/TimeStepSelector.spec.tsx +++ b/src/components/FluidChart/TimeStepSelector/TimeStepSelector.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import TimeStepSelector from 'components/FluidChart/TimeStepSelector/TimeStepSelector' import { FluidType, TimeStep } from 'enums' @@ -66,7 +66,9 @@ describe('TimeStepSelector component', () => { <TimeStepSelector fluidType={FluidType.WATER} /> </Provider> ) - await userEvent.click(screen.getAllByRole('listitem')[2]) + await act(async () => { + await userEvent.click(screen.getAllByRole('listitem')[2]) + }) expect(setCurrentTimeStepSpy).toHaveBeenCalledTimes(1) expect(setCurrentTimeStepSpy).toHaveBeenCalledWith(TimeStep.DAY) expect(setCurrentIndexSpy).toHaveBeenCalledTimes(1) @@ -85,7 +87,9 @@ describe('TimeStepSelector component', () => { <TimeStepSelector fluidType={FluidType.WATER} /> </Provider> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click(screen.getByText('timestep.today')) + }) expect(setCurrentTimeStepSpy).toHaveBeenCalledTimes(1) expect(setCurrentTimeStepSpy).toHaveBeenCalledWith(TimeStep.WEEK) expect(setCurrentIndexSpy).toHaveBeenCalledTimes(1) diff --git a/src/components/Header/CozyBar.spec.tsx b/src/components/Header/CozyBar.spec.tsx index 3f747796d146ce793df13cf8de468e548830188a..960d681051bc836eac1b1681b6c5615d73a58b05 100644 --- a/src/components/Header/CozyBar.spec.tsx +++ b/src/components/Header/CozyBar.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import CozyBar from 'components/Header/CozyBar' import { ScreenType } from 'enums' @@ -44,7 +44,11 @@ describe('CozyBar component', () => { </Provider> ) expect(container).toMatchSnapshot() - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getByLabelText('header.accessibility.button_back') + ) + }) expect(mockedNavigate).toHaveBeenCalled() }) @@ -56,7 +60,11 @@ describe('CozyBar component', () => { </Provider> ) expect(container).toMatchSnapshot() - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getByLabelText('header.accessibility.button_back') + ) + }) expect(mockBackFunction).toHaveBeenCalled() }) }) @@ -68,7 +76,11 @@ describe('CozyBar component', () => { </Provider> ) const updateModalSpy = jest.spyOn(ModalAction, 'openFeedbackModal') - await userEvent.click(screen.getByRole('button')) + await act(async () => { + await userEvent.click( + screen.getByLabelText('header.accessibility.button_open_feedbacks') + ) + }) expect(updateModalSpy).toHaveBeenCalledWith(true) }) diff --git a/src/components/Header/Header.spec.tsx b/src/components/Header/Header.spec.tsx index d76305f56773119682e4e2174a3952d2363e121e..0e527784ddfeb7dc0be86ec364b97319f86e6830 100644 --- a/src/components/Header/Header.spec.tsx +++ b/src/components/Header/Header.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import Header from 'components/Header/Header' import React from 'react' @@ -17,7 +17,7 @@ describe('Header component', () => { it('should be rendered correctly', () => { const { container } = render( <Provider store={store}> - <Header setHeaderHeight={jest.fn()} desktopTitleKey="mockKey" /> + <Header desktopTitleKey="mockKey" /> </Provider> ) expect(container).toMatchSnapshot() @@ -28,26 +28,18 @@ describe('Header component', () => { const key = 'titleKey' render( <Provider store={store}> - <Header - desktopTitleKey={key} - displayBackArrow={true} - setHeaderHeight={jest.fn()} - /> + <Header desktopTitleKey={key} displayBackArrow={true} /> </Provider> ) expect(screen.getByText(key)).toBeInTheDocument() expect( - screen.getByRole('button', { name: 'header.accessibility.button_back' }) + screen.getByLabelText('header.accessibility.button_back') ).toBeInTheDocument() }) it('should NOT display back button when displayBackArrow is false', async () => { render( <Provider store={store}> - <Header - desktopTitleKey="test" - displayBackArrow={false} - setHeaderHeight={jest.fn()} - /> + <Header desktopTitleKey="test" displayBackArrow={false} /> </Provider> ) expect( @@ -61,14 +53,14 @@ describe('Header component', () => { it('should call navigate -1 when back button is clicked with NO back fn', async () => { render( <Provider store={store}> - <Header - desktopTitleKey="KEY" - displayBackArrow={true} - setHeaderHeight={jest.fn()} - /> + <Header desktopTitleKey="KEY" displayBackArrow={true} /> </Provider> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getByLabelText('header.accessibility.button_back') + ) + }) expect(mockedNavigate).toHaveBeenCalled() }) it('should call custom back fn when back button is clicked', async () => { @@ -78,12 +70,15 @@ describe('Header component', () => { <Header desktopTitleKey="KEY" displayBackArrow={true} - setHeaderHeight={jest.fn()} backFunction={mockBack} /> </Provider> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getByLabelText('header.accessibility.button_back') + ) + }) expect(mockBack).toHaveBeenCalled() }) }) @@ -91,11 +86,15 @@ describe('Header component', () => { it('should call handleClickFeedbacks when feedback button is clicked', async () => { render( <Provider store={store}> - <Header setHeaderHeight={jest.fn()} desktopTitleKey="mockKey" /> + <Header desktopTitleKey="mockKey" /> </Provider> ) const updateModalSpy = jest.spyOn(ModalAction, 'openFeedbackModal') - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getByLabelText('header.accessibility.button_open_feedbacks') + ) + }) expect(updateModalSpy).toHaveBeenCalledWith(true) }) }) diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 9eba353d66a8156dd0a837e4a749c8b2271de4c3..b200f36a72621b30f136f0bc0c38706c9eb30129 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -6,6 +6,7 @@ import Icon from 'cozy-ui/transpiled/react/Icon' import { ScreenType } from 'enums' import React, { useCallback, useEffect, useRef } from 'react' import { useNavigate } from 'react-router-dom' +import { setHeaderHeight } from 'store/global/global.slice' import { useAppDispatch, useAppSelector } from 'store/hooks' import { openFeedbackModal } from 'store/modal/modal.slice' import './header.scss' @@ -16,7 +17,6 @@ interface HeaderProps { displayBackArrow?: boolean /** additional information to put below the main header */ children?: React.ReactNode - setHeaderHeight(height: number): void backFunction?: () => void } @@ -25,7 +25,6 @@ const Header = ({ desktopTitleKey, displayBackArrow, children, - setHeaderHeight, backFunction, }: HeaderProps) => { const { t } = useI18n() @@ -50,8 +49,8 @@ const Header = ({ useEffect(() => { const headerHeight = header.current?.clientHeight || 0 const adjustment = screenType === ScreenType.MOBILE ? cozyBarHeight : 0 - setHeaderHeight(headerHeight - adjustment) - }, [screenType, children, setHeaderHeight]) + dispatch(setHeaderHeight(headerHeight - adjustment)) + }, [screenType, children, dispatch]) return ( <header ref={header}> diff --git a/src/components/Hooks/useKonnectorAuth.tsx b/src/components/Hooks/useKonnectorAuth.tsx index de2e6ca5b1eda7798437604b1a095fdf1178edcf..77348935820f289bccd2b9d4fc87a30c9b31ac71 100644 --- a/src/components/Hooks/useKonnectorAuth.tsx +++ b/src/components/Hooks/useKonnectorAuth.tsx @@ -12,7 +12,6 @@ import { import { useState } from 'react' import AccountService from 'services/account.service' import ConnectionService from 'services/connection.service' -import { setLoading } from 'store/chart/chart.slice' import { updateFluidConnection } from 'store/global/global.slice' import { useAppDispatch, useAppSelector } from 'store/hooks' import logApp from 'utils/logger' @@ -61,7 +60,6 @@ const formatAuthData = ({ } const useKonnectorAuth = ( - // not needed ? fluidType: FluidType, options: { eglAuthData?: AccountEGLData @@ -103,8 +101,6 @@ const useKonnectorAuth = ( trigger: trigger, shouldLaunchKonnector: true, } - // TODO this should be dispatched - setLoading(false) dispatch( updateFluidConnection({ fluidType: currentFluidStatus.fluidType, @@ -112,7 +108,6 @@ const useKonnectorAuth = ( }) ) } catch (error) { - setLoading(false) logApp.error(error) Sentry.captureException(error) } @@ -148,7 +143,6 @@ const useKonnectorAuth = ( ) } } catch (error) { - setLoading(false) logApp.error(error) Sentry.captureException(error) } diff --git a/src/components/Hooks/useMoveToDate.tsx b/src/components/Hooks/useMoveToDate.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d0a432540b4c12746a40043a270207fe8b38f2f0 --- /dev/null +++ b/src/components/Hooks/useMoveToDate.tsx @@ -0,0 +1,22 @@ +import { DateTime } from 'luxon' +import DateChartService from 'services/dateChart.service' +import { setCurrentIndex, setSelectedDate } from 'store/chart/chart.slice' +import { useAppDispatch, useAppSelector } from 'store/hooks' + +export const useMoveToLatestDate = (lastDataDate: DateTime | null) => { + const dispatch = useAppDispatch() + const { currentTimeStep } = useAppSelector(state => state.ecolyo.chart) + + const moveToLatestDate = () => { + if (!lastDataDate) return + const dateChartService = new DateChartService() + const updatedIndex = dateChartService.defineDateIndex( + currentTimeStep, + lastDataDate + ) + dispatch(setSelectedDate(lastDataDate)) + dispatch(setCurrentIndex(updatedIndex)) + } + + return { moveToLatestDate } +} diff --git a/src/components/Konnector/ConnectionNotFound/ConnectionNotFound.spec.tsx b/src/components/Konnector/ConnectionNotFound/ConnectionNotFound.spec.tsx index 1d4cf06cba60acf8e3a9e649986e10dd30811097..72388b748c22057ec7520884c6de66ef07bdd6ba 100644 --- a/src/components/Konnector/ConnectionNotFound/ConnectionNotFound.spec.tsx +++ b/src/components/Konnector/ConnectionNotFound/ConnectionNotFound.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import ConnectionNotFound from './ConnectionNotFound' @@ -16,8 +16,9 @@ describe('ConnectionNotFound component test', () => { it('should open konnector url when button is clicked', async () => { global.open = jest.fn() render(<ConnectionNotFound konnectorSlug={konnectorSlug} />) - await userEvent.click(screen.getByRole('button')) - + await act(async () => { + await userEvent.click(screen.getByText('konnector_form.button_install')) + }) expect(global.open).toHaveBeenCalledWith( 'http://localhost/#/discover/enedissgegrandlyon', '_blank' diff --git a/src/components/Konnector/ConnectionResult/ConnectionResult.tsx b/src/components/Konnector/ConnectionResult/ConnectionResult.tsx index ed4433a1ed16ced0b1f8de2fde10726ea18cda60..232a418355a9ac57925d69043d25d7e8d4358818 100644 --- a/src/components/Konnector/ConnectionResult/ConnectionResult.tsx +++ b/src/components/Konnector/ConnectionResult/ConnectionResult.tsx @@ -2,12 +2,11 @@ import Button from '@material-ui/core/Button' import warningDark from 'assets/icons/ico/warning-dark.svg' import warningWhite from 'assets/icons/ico/warning-white.svg' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' -import Loader from 'components/Loader/Loader' import { useClient } from 'cozy-client' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { FluidType, KonnectorUpdate } from 'enums' import { DateTime } from 'luxon' -import { Account, AccountSgeData, FluidConnection, FluidStatus } from 'models' +import { AccountSgeData, FluidConnection, FluidStatus } from 'models' import React, { useCallback, useEffect, useState } from 'react' import AccountService from 'services/account.service' import DateChartService from 'services/dateChart.service' @@ -35,10 +34,9 @@ const ConnectionResult = ({ const dispatch = useAppDispatch() const { fluidStatus } = useAppSelector(state => state.ecolyo.global) const currentFluidStatus = fluidStatus[fluidType] - const account: Account | null = currentFluidStatus.connection.account + const account = currentFluidStatus.connection.account const [deleting, setDeleting] = useState<boolean>(false) - const [updating, setUpdating] = useState<boolean>(false) const [lastExecutionDate, setLastExecutionDate] = useState<string | DateTime>( '-' ) @@ -47,7 +45,6 @@ const ConnectionResult = ({ const [outDatedDataDays, setOutDatedDataDays] = useState<number | null>(null) const updateKonnector = async () => { - setUpdating(true) setStatus('') setLastExecutionDate('-') setKonnectorError('') @@ -62,7 +59,6 @@ const ConnectionResult = ({ fluidConnection: updatedConnection, }) ) - setUpdating(false) } const deleteAccountsAndTriggers = useCallback(async () => { @@ -110,37 +106,35 @@ const ConnectionResult = ({ } }, [lastExecutionDate]) - const handleRefreshConsent = useCallback( - (fluidType: FluidType) => { - if (fluidType == FluidType.ELECTRICITY) { - const accountData = currentFluidStatus.connection.account - ?.auth as AccountSgeData - // store the previous account data since the onDelete will remove account from DB - dispatch( - updateSgeStore({ - currentStep: 0, - firstName: accountData.firstname, - lastName: accountData.lastname, - pdl: parseInt(accountData.pointId), - address: accountData.address, - zipCode: parseInt(accountData.postalCode), - city: accountData.city, - dataConsent: true, - pdlConfirm: true, - shouldLaunchAccount: true, - }) - ) - dispatch(setShouldRefreshConsent(true)) - } else { - deleteAccountsAndTriggers() - } - }, - [ - deleteAccountsAndTriggers, - dispatch, - currentFluidStatus.connection.account?.auth, - ] - ) + const handleRefreshConsent = useCallback(() => { + if (fluidType == FluidType.ELECTRICITY) { + const accountData = currentFluidStatus.connection.account + ?.auth as AccountSgeData + // store the previous account data since the onDelete will remove account from DB + dispatch( + updateSgeStore({ + currentStep: 0, + firstName: accountData.firstname, + lastName: accountData.lastname, + pdl: parseInt(accountData.pointId), + address: accountData.address, + zipCode: parseInt(accountData.postalCode), + city: accountData.city, + dataConsent: true, + pdlConfirm: true, + shouldLaunchAccount: true, + }) + ) + dispatch(setShouldRefreshConsent(true)) + } else { + deleteAccountsAndTriggers() + } + }, [ + fluidType, + currentFluidStatus.connection.account?.auth, + dispatch, + deleteAccountsAndTriggers, + ]) useEffect(() => { if (currentFluidStatus.connection.triggerState?.last_success) { @@ -162,9 +156,8 @@ const ConnectionResult = ({ ) ) } - if (isOutdated()) { - setOutDatedDataDays(isOutdated()) - } + const outdated = isOutdated() + if (outdated) setOutDatedDataDays(outdated) }, [currentFluidStatus.connection.triggerState, isOutdated]) const getFluidTypeTranslation = (fluidType: FluidType) => { @@ -178,9 +171,7 @@ const ConnectionResult = ({ } } - const consentError = - konnectorError === KonnectorUpdate.ERROR_CONSENT_FORM_GAS || - konnectorError === KonnectorUpdate.ERROR_UPDATE_OAUTH + const consentError = konnectorError === KonnectorUpdate.ERROR_UPDATE_OAUTH /** * Get Konnector state, possible values: @@ -193,15 +184,12 @@ const ConnectionResult = ({ // First check if there is partner error from backoffice if (currentFluidStatus.maintenance) { return ( - <div className="connection-caption text-16-normal"> - <div className="text-16-normal"> - <div className="connection-caption"> - {t('konnector_form.wait_end_issue')} - </div> - </div> + <div className="connection-caption"> + {t('konnector_form.wait_end_issue')} </div> ) } + // Else check if konnector is in error state if (status === 'errored') { return ( @@ -233,50 +221,40 @@ const ConnectionResult = ({ ) } + const getErrorClassName = (): string => { + if ( + status === 'errored' && + !hasUpdatedToday() && + !currentFluidStatus.maintenance + ) { + return 'connection-update-errored' + } + return '' + } + return ( <div className="connection-update-result"> - <div - className={ - status === 'errored' && - !hasUpdatedToday() && - !currentFluidStatus.maintenance - ? 'connection-update-errored' - : '' - } - > - {getConnectionStatus()} - </div> + <div className={getErrorClassName()}>{getConnectionStatus()}</div> <div className="inline-buttons"> {!consentError && ( <Button aria-label={t('konnector_form.accessibility.button_disconnect')} onClick={deleteAccountsAndTriggers} - disabled={updating || deleting} + disabled={deleting} className="btnSecondary" > - {deleting - ? t('konnector_form.loading') - : t('konnector_form.button_disconnect')} + {t(`konnector_form.${deleting ? 'loading' : 'button_disconnect'}`)} </Button> )} <Button aria-label={t('konnector_form.accessibility.button_update')} - onClick={ - consentError - ? () => handleRefreshConsent(fluidType) - : updateKonnector - } - disabled={updating || deleting} + onClick={consentError ? handleRefreshConsent : updateKonnector} + disabled={deleting} className="btnPrimary" > - {updating && <Loader color="black" />} - {!updating && ( - <div> - {consentError - ? t('konnector_form.button_oauth_reload') - : t('konnector_form.button_update')} - </div> - )} + {consentError + ? t('konnector_form.button_oauth_reload') + : t('konnector_form.button_update')} </Button> </div> </div> diff --git a/src/components/Konnector/KonnectorModal.spec.tsx b/src/components/Konnector/KonnectorModal.spec.tsx index cc72f6b71ae36a23d8600d26eecd55e8dcfacbc0..bd2f5cacac53519318ffdbdbc6cc4952b35729b3 100644 --- a/src/components/Konnector/KonnectorModal.spec.tsx +++ b/src/components/Konnector/KonnectorModal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { FluidType, KonnectorError } from 'enums' import React from 'react' @@ -18,12 +18,11 @@ describe('KonnectorModal component', () => { <Provider store={store}> <KonnectorModal open={true} - isUpdating={false} state={null} error={null} fluidType={FluidType.ELECTRICITY} handleCloseClick={mockHandleCloseClick} - isLogging={false} + isVerifyingIdentity={false} account={null} handleAccountDeletion={jest.fn()} /> @@ -36,12 +35,11 @@ describe('KonnectorModal component', () => { <Provider store={store}> <KonnectorModal open={true} - isUpdating={true} state={null} error={null} fluidType={FluidType.ELECTRICITY} handleCloseClick={mockHandleCloseClick} - isLogging={false} + isVerifyingIdentity={false} account={null} handleAccountDeletion={jest.fn()} /> @@ -56,18 +54,19 @@ describe('KonnectorModal component', () => { <Provider store={store}> <KonnectorModal open={true} - isUpdating={false} state="errored" error={null} fluidType={FluidType.ELECTRICITY} handleCloseClick={mockHandleCloseClick} - isLogging={false} + isVerifyingIdentity={false} account={null} handleAccountDeletion={jest.fn()} /> </Provider> ) - await userEvent.click(screen.getByRole('button')) + await act(async () => { + await userEvent.click(screen.getByText('konnector_modal.button_validate')) + }) expect(mockHandleCloseClick).toHaveBeenCalled() }) it('should render login error', async () => { @@ -75,52 +74,47 @@ describe('KonnectorModal component', () => { <Provider store={store}> <KonnectorModal open={true} - isUpdating={false} state="error" error={KonnectorError.LOGIN_FAILED} fluidType={FluidType.ELECTRICITY} handleCloseClick={mockHandleCloseClick} - isLogging={false} + isVerifyingIdentity={false} account={null} handleAccountDeletion={jest.fn()} /> </Provider> ) expect( - baseElement.getElementsByClassName('kce-picto-txt')[0] + baseElement.getElementsByClassName('headerError')[0] ).toBeInTheDocument() }) it('should render unknown error', async () => { - const { baseElement } = render( + render( <Provider store={store}> <KonnectorModal open={true} - isUpdating={false} state="error" error={null} fluidType={FluidType.ELECTRICITY} handleCloseClick={mockHandleCloseClick} - isLogging={false} + isVerifyingIdentity={false} account={null} handleAccountDeletion={jest.fn()} /> </Provider> ) - expect( - baseElement.getElementsByClassName('err-data-2')[0] - ).toBeInTheDocument() + expect(screen.getByText('konnector_modal.error_data_2')).toBeInTheDocument() }) it('should render update error', async () => { const { baseElement } = render( <Provider store={store}> <KonnectorModal open={true} - isUpdating={true} state={null} error={null} fluidType={FluidType.WATER} handleCloseClick={mockHandleCloseClick} - isLogging={false} + isVerifyingIdentity={false} account={null} handleAccountDeletion={jest.fn()} /> @@ -130,4 +124,22 @@ describe('KonnectorModal component', () => { baseElement.getElementsByClassName('waiting-text')[0] ).toBeInTheDocument() }) + + it('should render nothing if error is CHALLENGE_ASKED on Gas', () => { + render( + <Provider store={store}> + <KonnectorModal + open={true} + state={null} + error={KonnectorError.CHALLENGE_ASKED} + fluidType={FluidType.GAS} + handleCloseClick={mockHandleCloseClick} + isVerifyingIdentity={false} + account={null} + handleAccountDeletion={jest.fn()} + /> + </Provider> + ) + expect(screen.queryByRole('dialog')).toBeNull() + }) }) diff --git a/src/components/Konnector/KonnectorModal.tsx b/src/components/Konnector/KonnectorModal.tsx index c5ebc3ba8a5bd98feda2f6cb770acbd2018b23c0..df53e6c64396724c7d187e5ee6500f9280165246 100644 --- a/src/components/Konnector/KonnectorModal.tsx +++ b/src/components/Konnector/KonnectorModal.tsx @@ -17,14 +17,15 @@ import { FluidType, KonnectorError } from 'enums' import { shuffle } from 'lodash' import { Account } from 'models' import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { useAppSelector } from 'store/hooks' +import { getFluidName } from 'utils/utils' import KonnectorModalFooter from './KonnectorModalFooter' import './konnectorModal.scss' interface KonnectorModalProps { open: boolean - isUpdating: boolean /** Only used for SGE when searching for user identity */ - isLogging: boolean + isVerifyingIdentity: boolean state: string | null error: KonnectorError | null fluidType: FluidType @@ -35,8 +36,7 @@ interface KonnectorModalProps { const KonnectorModal = ({ open, - isUpdating, - isLogging, + isVerifyingIdentity, state, error, fluidType, @@ -45,26 +45,30 @@ const KonnectorModal = ({ account, }: KonnectorModalProps) => { const { t } = useI18n() - const fluidName: string = FluidType[fluidType] + const fluidName = getFluidName(fluidType) + const { fluidStatus } = useAppSelector(state => state.ecolyo.global) + const isUpdating = fluidStatus[fluidType].connection.isUpdating const [index, setIndex] = useState<number>(0) + /** Only used for enedis to see common errors */ + const [showCommonErrors, setShowCommonErrors] = useState(false) + const shuffledWaitingTexts = useMemo(() => { if (fluidType) { return shuffle(connectionWaitingText) - } else { - return connectionWaitingText } + return connectionWaitingText }, [fluidType]) + const firstConnectionWaitingTexts = firstConnectionWaitingText.concat( ...shuffledWaitingTexts ) - const [showCommonErrors, setShowCommonErrors] = useState(false) const getUpdatingText = useCallback(() => { return ( <div className="kmodal-waiting-text text-18-italic"> {shuffledWaitingTexts.map((text, idx) => ( <div - key={text.second} + key={idx} className={classNames('waiting-text', { ['show']: idx === index % shuffledWaitingTexts.length, })} @@ -80,12 +84,12 @@ const KonnectorModal = ({ const getConnectionText = useCallback(() => { return ( <div className="kmodal-waiting-text text-18-italic"> - {isLogging ? ( + {isVerifyingIdentity ? ( <p className="text-18-white">{t('konnector_modal.logging_txt')}</p> ) : ( firstConnectionWaitingTexts.map((text, idx) => ( <div - key={text.second} + key={idx} className={classNames('waiting-text', { ['show']: idx === index % firstConnectionWaitingTexts.length, })} @@ -97,29 +101,29 @@ const KonnectorModal = ({ )} </div> ) - }, [firstConnectionWaitingTexts, index, isLogging, t]) + }, [firstConnectionWaitingTexts, index, isVerifyingIdentity, t]) /** Returns connection success contents, depending on the fluid and update status */ const connectionSuccessContent = () => ( <div className="konnector-config"> <Icon icon={successIcon} size={48} /> - <div className="kcs-picto-txt text-20-bold"> + <div className="headerSuccess text-20-bold"> {t(`konnector_modal.success_${isUpdating ? 'update_' : ''}txt`)} </div> <b> {t( `konnector_modal.success_data_${ isUpdating ? 'update_' : '' - }${fluidName.toLowerCase()}` + }${fluidName}` )} </b> - <p - style={{ fontWeight: 400 }} + <div + className="light" dangerouslySetInnerHTML={{ __html: t( `konnector_modal.success_data_additional_${ isUpdating ? 'update_' : '' - }${fluidName.toLowerCase()}` + }${fluidName}` ), }} /> @@ -137,6 +141,10 @@ const KonnectorModal = ({ } }, [open, state]) + if (fluidType === FluidType.GAS && error === KonnectorError.CHALLENGE_ASKED) { + return null + } + return ( <Dialog open={open} @@ -156,12 +164,13 @@ const KonnectorModal = ({ {t('konnector_modal.accessibility.window_title')} </div> <div className="kmodal-content"> - {open && !state ? ( + {/* NEITHER ERROR NOR SUCCESS => LOADING */} + {!state ? ( <> <Loader fluidType={fluidType} /> - {!isLogging && ( - <div className="kmodal-content-text kmodal-content-text-center text-16-normal"> - <div className="kc-wait text-16-bold"> + {!isVerifyingIdentity && ( + <div className="kmodal-content-text text-16-normal"> + <div className="text-16-bold"> {t( `konnector_modal.loading_data${isUpdating ? '_update' : ''}` )} @@ -173,6 +182,7 @@ const KonnectorModal = ({ </> ) : ( <> + {/* ERROR OR SUCCESS */} <div className="kmodal-info"> {state === ERROR_EVENT && ( <> @@ -180,35 +190,34 @@ const KonnectorModal = ({ // LOGIN FAILED FOR ENEDIS AND EGL <div className="konnector-config"> <Icon icon={errorIcon} size={48} /> - <div className="kce-picto-txt text-20-bold"> + <div className="headerError text-20-bold"> {t('konnector_modal.error_txt')} </div> <div> {t( `konnector_modal.error_credentials_${ isUpdating ? 'update_' : '' - }${fluidName.toLowerCase()}` + }${fluidName}` )} </div> {fluidType === FluidType.ELECTRICITY && !isUpdating && ( <div className="elec-fail"> {t( - `konnector_modal.error_credentials_${fluidName.toLowerCase()}_2` + `konnector_modal.error_credentials_${fluidName}_2` )} </div> )} - {/* Show common errors */} + {/* Show common errors for enedis */} {fluidType === FluidType.ELECTRICITY && ( <> - {!showCommonErrors && ( + {!showCommonErrors ? ( <Button - className="btnText commonErrors" + className="btnText" onClick={() => setShowCommonErrors(true)} > {t('konnector_modal.show_common_error')} </Button> - )} - {showCommonErrors && ( + ) : ( <div className="commonErrorsList" dangerouslySetInnerHTML={{ @@ -226,7 +235,7 @@ const KonnectorModal = ({ isUpdating && fluidType === FluidType.ELECTRICITY && ( // MISMATCH UPDATE ERROR ENEDIS - <div className="kce-picto-txt konnector-config mismatch"> + <div className="headerError konnector-config mismatch"> <Icon icon={EnedisIcon} width={120} height={80} /> <div className="title text-20-bold"> {t('konnector_modal.mismatch.title')} @@ -242,44 +251,41 @@ const KonnectorModal = ({ </div> </div> )} - {error === KonnectorError.CHALLENGE_ASKED && + {error === + KonnectorError.USER_ACTION_NEEDED_ACCOUNT_REMOVED && fluidType === FluidType.GAS && ( - // CONSENT FORM ERROR GRDF <div className="konnector-config"> <Icon icon={errorIcon} size={48} /> - <div className="kce-picto-txt text-20-bold"> + <div className="headerError text-20-bold"> {t('konnector_modal.error_txt')} </div> - <div className="title text-20-bold"> + <div className="title text-16-bold"> {t('konnector_modal.error_consent_form_gas_title')} </div> - <div className="err-data-2"> - {t('konnector_modal.error_consent_form_gas_content')} - </div> - <div className="err-data-2"> - {t( - 'konnector_modal.error_consent_form_gas_content_2' - )} + <div className="light text-14-regular"> + {t('konnector_modal.error_consent_form_gas_report')} </div> </div> )} + {error !== KonnectorError.LOGIN_FAILED && error !== KonnectorError.TERMS_VERSION_MISMATCH && - error !== KonnectorError.CHALLENGE_ASKED && ( + error !== + KonnectorError.USER_ACTION_NEEDED_ACCOUNT_REMOVED && ( // DEFAULT CASE <div className="konnector-config"> <Icon icon={errorIcon} size={48} /> - <div className="kce-picto-txt text-20-bold"> + <div className="headerError text-20-bold"> {t('konnector_modal.error_txt')} </div> - <div> + <div className="text-15-bold"> {t( `konnector_modal.error_data_${ isUpdating ? 'update_' : '' - }${fluidName.toLowerCase()}` + }${fluidName}` )} </div> - <div className="err-data-2"> + <div className="text-14-regular"> {t('konnector_modal.error_data_2')} </div> </div> diff --git a/src/components/Konnector/KonnectorModalFooter.spec.tsx b/src/components/Konnector/KonnectorModalFooter.spec.tsx index 60d45605aa18477f48d48b7f5171550cfcb8ea8d..42e3e61b7f0d93c4aaa63ae640775e09ce726a86 100644 --- a/src/components/Konnector/KonnectorModalFooter.spec.tsx +++ b/src/components/Konnector/KonnectorModalFooter.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { ERROR_EVENT } from 'cozy-harvest-lib/dist/models/flowEvents' import { KonnectorError } from 'enums' @@ -92,22 +92,6 @@ describe('KonnectorModalFooter component', () => { ).toBeInTheDocument() }) - it('should close the modal', async () => { - render( - <Provider store={store}> - <KonnectorModalFooter - state={ERROR_EVENT} - error={KonnectorError.LOGIN_FAILED} - handleCloseClick={mockClose} - handleAccountDeletion={mockDelete} - account={null} - isUpdating={false} - /> - </Provider> - ) - await userEvent.click(screen.getByRole('button')) - expect(mockClose).toHaveBeenCalled() - }) it('should go back to login', async () => { render( <Provider store={store}> @@ -121,7 +105,9 @@ describe('KonnectorModalFooter component', () => { /> </Provider> ) - await userEvent.click(screen.getAllByRole('button')[1]) + await act(async () => { + await userEvent.click(screen.getByText('konnector_modal.button_go')) + }) expect(mockDelete).toHaveBeenCalled() expect(mockedNavigate).toHaveBeenCalledTimes(1) }) @@ -138,7 +124,9 @@ describe('KonnectorModalFooter component', () => { /> </Provider> ) - await userEvent.click(screen.getAllByRole('button')[1]) + await act(async () => { + await userEvent.click(screen.getByText('konnector_modal.button_go')) + }) expect(mockDelete).toHaveBeenCalled() expect(mockedNavigate).toHaveBeenCalledTimes(1) }) @@ -155,7 +143,11 @@ describe('KonnectorModalFooter component', () => { /> </Provider> ) - await userEvent.click(screen.getAllByRole('button')[1]) + await act(async () => { + await userEvent.click( + screen.getByText('konnector_modal.button_check_info') + ) + }) expect(mockedNavigate).toHaveBeenCalledTimes(1) expect(mockDelete).toHaveBeenCalledTimes(0) }) diff --git a/src/components/Konnector/KonnectorModalFooter.tsx b/src/components/Konnector/KonnectorModalFooter.tsx index dbc539dcf5f8877dc1f24f100cc78ecaaf9206b0..2023ed972c3f722676cdf813c66c8fdfe081bb36 100644 --- a/src/components/Konnector/KonnectorModalFooter.tsx +++ b/src/components/Konnector/KonnectorModalFooter.tsx @@ -1,9 +1,6 @@ import Button from '@material-ui/core/Button' import { useClient } from 'cozy-client' -import { - ERROR_EVENT, - SUCCESS_EVENT, -} from 'cozy-harvest-lib/dist/models/flowEvents' +import { SUCCESS_EVENT } from 'cozy-harvest-lib/dist/models/flowEvents' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { KonnectorError } from 'enums' import { Account } from 'models' @@ -12,6 +9,8 @@ import { useNavigate } from 'react-router-dom' import AccountService from 'services/account.service' import './konnectorModal.scss' +declare let __SAU_ISSUE_DIRECT_LINK__: string + interface KonnectorModalFooterProps { state: string | null error: KonnectorError | null @@ -32,6 +31,7 @@ const KonnectorModalFooter = ({ const { t } = useI18n() const client = useClient() const navigate = useNavigate() + const handleSGELoginRetry = useCallback(() => { handleCloseClick(state === SUCCESS_EVENT) navigate('/connect/electricity') @@ -46,73 +46,87 @@ const KonnectorModalFooter = ({ } }, [account, client, handleAccountDeletion, navigate]) - const defaultButton = ( - <Button - aria-label={t('konnector_modal.accessibility.button_close')} - onClick={() => handleCloseClick(state === SUCCESS_EVENT)} - className="btnPrimary" - > - <div>{t('konnector_modal.button_validate')}</div> - </Button> - ) - - const errorButtons = () => { - switch (error) { - case KonnectorError.USER_ACTION_NEEDED: - // INSEE CODE ERROR ENEDIS - return ( - <Button - aria-label={t('konnector_modal.accessibility.button_close')} - onClick={() => handleCloseClick(state === SUCCESS_EVENT)} - className="btnPrimary" - > - <div>{t('konnector_modal.button_understood')}</div> - </Button> - ) - case KonnectorError.LOGIN_FAILED: - case KonnectorError.CHALLENGE_ASKED: - // INCOMPLETE CONSENT FORM - GRDF - return ( - <Button - aria-label={t('konnector_modal.accessibility.button_close')} - onClick={() => handleCloseClick(state === SUCCESS_EVENT)} - className="btnPrimary" - > - <div>{t('konnector_modal.button_try_again')}</div> - </Button> - ) - case KonnectorError.TERMS_VERSION_MISMATCH: - return ( - <div className="buttons"> - <Button - aria-label={t('konnector_modal.accessibility.button_close')} - onClick={() => handleCloseClick(state === SUCCESS_EVENT)} - className="btnSecondary" - > - <div>{t('konnector_modal.button_later')}</div> - </Button> - <Button - aria-label={t('konnector_modal.accessibility.button_close')} - onClick={ - !isUpdating ? handleSGELoginRetry : handleResetSGEAccount - } - className="btnPrimary" - > - <div> - {!isUpdating - ? t('konnector_modal.button_check_info') - : t('konnector_modal.button_go')} - </div> - </Button> + if (error === KonnectorError.USER_ACTION_NEEDED) { + // INSEE CODE ERROR ENEDIS + return ( + <Button + aria-label={t('konnector_modal.accessibility.button_close')} + onClick={() => handleCloseClick(state === SUCCESS_EVENT)} + className="btnPrimary" + > + <div>{t('konnector_modal.button_understood')}</div> + </Button> + ) + } else if (error === KonnectorError.LOGIN_FAILED) { + // INCOMPLETE CONSENT FORM - GRDF // what is this comment ? + return ( + <Button + aria-label={t('konnector_modal.accessibility.button_close')} + onClick={() => handleCloseClick(state === SUCCESS_EVENT)} + className="btnPrimary" + > + <div>{t('konnector_modal.button_try_again')}</div> + </Button> + ) + } else if (error === KonnectorError.CHALLENGE_ASKED) { + return ( + <Button + aria-label={t('konnector_modal.accessibility.button_close')} + onClick={() => handleCloseClick(state === SUCCESS_EVENT)} + className="btnPrimary" + > + <div>{t('konnector_modal.button_come_back_later')}</div> + </Button> + ) + } else if (error === KonnectorError.TERMS_VERSION_MISMATCH) { + return ( + <div className="buttons"> + <Button + aria-label={t('konnector_modal.accessibility.button_close')} + onClick={() => handleCloseClick(state === SUCCESS_EVENT)} + className="btnSecondary" + > + <div>{t('konnector_modal.button_later')}</div> + </Button> + <Button + aria-label={t('konnector_modal.accessibility.button_close')} + onClick={!isUpdating ? handleSGELoginRetry : handleResetSGEAccount} + className="btnPrimary" + > + <div> + {!isUpdating + ? t('konnector_modal.button_check_info') + : t('konnector_modal.button_go')} </div> - ) - default: - // DEFAULT FOOTER BUTTONS - return defaultButton - } + </Button> + </div> + ) + } else if (error === KonnectorError.USER_ACTION_NEEDED_ACCOUNT_REMOVED) { + return ( + <Button + aria-label={t('konnector_modal.accessibility.button_close')} + onClick={() => { + window.open( + `${__SAU_ISSUE_DIRECT_LINK__}?version=${client.appMetadata.version}` + ) + handleCloseClick(state === SUCCESS_EVENT) + }} + className="btnPrimary" + > + <div>{t('konnector_modal.button_contact')}</div> + </Button> + ) + } else { + return ( + <Button + aria-label={t('konnector_modal.accessibility.button_close')} + onClick={() => handleCloseClick(state === SUCCESS_EVENT)} + className="btnPrimary" + > + <div>{t('konnector_modal.button_validate')}</div> + </Button> + ) } - - return <>{state === ERROR_EVENT ? errorButtons() : defaultButton}</> } export default KonnectorModalFooter diff --git a/src/components/Konnector/KonnectorViewerCard.tsx b/src/components/Konnector/KonnectorViewerCard.tsx index d21e51fcc4b80655db43d3ee9e16248b0a7e1e96..91096b619b35cdc7b282dcaa19b014b05f7f02f1 100644 --- a/src/components/Konnector/KonnectorViewerCard.tsx +++ b/src/components/Konnector/KonnectorViewerCard.tsx @@ -42,7 +42,11 @@ import DateChartService from 'services/dateChart.service' import FluidService from 'services/fluid.service' import PartnersInfoService from 'services/partnersInfo.service' import { setChallengeConsumption } from 'store/challenge/challenge.slice' -import { setSelectedDate, setShowOfflineData } from 'store/chart/chart.slice' +import { + setSelectedDate, + setShowConnectionDetails, + setShowOfflineData, +} from 'store/chart/chart.slice' import { setFluidStatus, setLastEpglLogin, @@ -50,32 +54,24 @@ import { updateFluidConnection, } from 'store/global/global.slice' import { useAppDispatch, useAppSelector } from 'store/hooks' +import logApp from 'utils/logger' import { getParamPicto } from 'utils/picto' import { getKonnectorSlug } from 'utils/utils' import ConnectionNotFound from './ConnectionNotFound/ConnectionNotFound' import ConnectionResult from './ConnectionResult/ConnectionResult' import './konnectorViewerCard.scss' -interface KonnectorViewerCardProps { - isDisconnected: boolean - showOfflineData: boolean - active: boolean - fluidType: FluidType - setActive: React.Dispatch<React.SetStateAction<boolean>> -} - const KonnectorViewerCard = ({ - isDisconnected, - showOfflineData, - active, fluidType, - setActive, -}: KonnectorViewerCardProps) => { +}: { + fluidType: Exclude<FluidType, FluidType.MULTIFLUID> +}) => { const { t } = useI18n() const client = useClient() - const dispatch = useAppDispatch() const navigate = useNavigate() + const dispatch = useAppDispatch() const { + chart: { showConnectionDetails, showOfflineData }, challenge: { currentChallenge }, global: { fluidStatus, shouldRefreshConsent, partnersInfo }, } = useAppSelector(state => state.ecolyo) @@ -84,48 +80,49 @@ const KonnectorViewerCard = ({ const fluidState = currentFluidStatus.status const { konnector, account, trigger } = currentFluidStatus.connection const currentFluidName = FluidType[currentFluidStatus.fluidType].toLowerCase() + const [openModal, setOpenModal] = useState(false) - const [isUpdating, setIsUpdating] = useState(false) - const [isLogging, setIsLogging] = useState( + const [isVerifyingIdentity, setIsVerifyingIdentity] = useState( fluidType === FluidType.ELECTRICITY ) const [konnectorErrorDescription, setKonnectorErrorDescription] = useState<KonnectorError | null>(null) const [konnectorState, setKonnectorState] = useState<string | null>(null) const [isOutdatedData, setIsOutdatedData] = useState<number | null>(null) + + const isWaitingForConsent = + fluidType === FluidType.GAS && + currentFluidStatus.status === FluidState.CHALLENGE_ASKED + const fluidService = useMemo(() => new FluidService(client), [client]) + const accountService = useMemo(() => new AccountService(client), [client]) const partnersInfoService = useMemo( () => new PartnersInfoService(client), [client] ) - const lastDataDate = - fluidType !== FluidType.MULTIFLUID && currentFluidStatus.lastDataDate - ? currentFluidStatus.lastDataDate.toLocaleString() + fluidType - : fluidType - const iconType = getParamPicto(currentFluidStatus.fluidType) const toggleAccordion = () => { - setActive(prev => !prev) + dispatch(setShowConnectionDetails(!showConnectionDetails)) } const updateGlobalFluidStatus = useCallback(async (): Promise< FluidStatus[] > => { - const _updatedFluidStatus = await fluidService.getFluidStatus() + const updatedFluidStatus = await fluidService.getFluidStatus() const refDate = DateTime.fromISO('0001-01-01') - let _lastDataDate = DateTime.fromISO('0001-01-01') - for (const fluid of _updatedFluidStatus) { - if (fluid?.lastDataDate && fluid?.lastDataDate > _lastDataDate) { - _lastDataDate = fluid.lastDataDate + let lastDataDate = DateTime.fromISO('0001-01-01') + for (const fluid of updatedFluidStatus) { + if (fluid?.lastDataDate && fluid?.lastDataDate > lastDataDate) { + lastDataDate = fluid.lastDataDate } } - if (_lastDataDate > refDate) { - dispatch(setSelectedDate(_lastDataDate)) + if (lastDataDate > refDate) { + dispatch(setSelectedDate(lastDataDate)) } - return _updatedFluidStatus + return updatedFluidStatus }, [dispatch, fluidService]) const refreshChallengeState = useCallback(async () => { @@ -159,25 +156,28 @@ const KonnectorViewerCard = ({ const updatedFluidStatus = await fluidService.getFluidStatus(partnersInfo) dispatch(setFluidStatus(updatedFluidStatus)) } - setActive(false) + dispatch(setShowConnectionDetails(false)) }, [ refreshChallengeState, updateGlobalFluidStatus, - setActive, partnersInfoService, fluidService, dispatch, ]) + /** Close modal, update fluid status and reset error state */ const handleConnectionEnd = useCallback( async (isSuccess?: boolean) => { + const { account, isUpdating } = currentFluidStatus.connection + // CASE FOR GLOBAL LOGIN FAILED const isGlobalLoginFailed = konnectorErrorDescription === KonnectorError.LOGIN_FAILED || konnectorErrorDescription === KonnectorError.UNKNOWN_ERROR || - konnectorErrorDescription === KonnectorError.CHALLENGE_ASKED || konnectorErrorDescription === KonnectorError.CRITICAL || - konnectorErrorDescription === KonnectorError.MISSING_SECRET + konnectorErrorDescription === KonnectorError.MISSING_SECRET || + konnectorErrorDescription === + KonnectorError.USER_ACTION_NEEDED_ACCOUNT_REMOVED // CASE FOR ENEDIS CODE INSEE ERROR const isEnedisCodeInseeError = @@ -186,55 +186,40 @@ const KonnectorViewerCard = ({ const shouldDeleteAccount = account && !isSuccess && - currentFluidStatus && - currentFluidStatus.connection.account && + account && (isGlobalLoginFailed || isEnedisCodeInseeError) if (shouldDeleteAccount) { + logApp('info', `shouldDeleteAccount`) // KEEP LAST LOGIN FOR EPGL - let lastEpglLogin = '' - if ( - fluidSlug === FluidSlugType.WATER && - currentFluidStatus.connection.account?.auth - ) { - const auth = currentFluidStatus.connection.account - .auth as AccountEGLData - lastEpglLogin = auth.login + if (fluidSlug === FluidSlugType.WATER && account?.auth) { + const auth = account.auth as AccountEGLData + const lastEpglLogin = auth.login + dispatch(setLastEpglLogin(lastEpglLogin)) } // DELETE ACCOUNT - const accountService = new AccountService(client) await accountService.deleteAccount(account) await handleAccountDeletion() - - // RESTORE LAST KNOWN CREDENTIALS - if (lastEpglLogin) { - dispatch(setLastEpglLogin(lastEpglLogin)) - } } else { const updatedFluidStatus = await fluidService.getFluidStatus(partnersInfo) dispatch(setFluidStatus(updatedFluidStatus)) } - - setActive(false) + dispatch(setShowConnectionDetails(false)) setOpenModal(false) - // TODO null state seems to be read before modal closing and display a success icon in modal setKonnectorState(null) setKonnectorErrorDescription(null) }, [ - account, konnectorErrorDescription, - currentFluidStatus, - isUpdating, fluidType, - setActive, + currentFluidStatus, + dispatch, fluidSlug, - client, + accountService, handleAccountDeletion, fluidService, partnersInfo, - dispatch, ] ) @@ -253,12 +238,6 @@ const KonnectorViewerCard = ({ }, [dispatch, fluidType, navigate]) const getConnectionCard = useCallback(() => { - if ( - fluidType === FluidType.GAS && - fluidState === FluidState.CHALLENGE_ASKED - ) { - return <GrdfWaitConsent /> - } if (showOfflineData && !account) { return ( <Button className="btnPrimary" onClick={toggleModalConnection}> @@ -266,35 +245,35 @@ const KonnectorViewerCard = ({ </Button> ) } - if (fluidState === FluidState.KONNECTOR_NOT_FOUND && !isUpdating) { + + if ( + fluidState === FluidState.KONNECTOR_NOT_FOUND && + !currentFluidStatus.connection.isUpdating + ) { return <ConnectionNotFound konnectorSlug={fluidSlug} /> } + // Handle login failed for EGL - else if ( + if ( (fluidType === FluidType.WATER && - fluidState === FluidState.ERROR_LOGIN_FAILED) || + fluidState === FluidState.LOGIN_FAILED) || (account && currentFluidStatus.status !== FluidState.NOT_CONNECTED) ) { return ( <ConnectionResult handleAccountDeletion={handleAccountDeletion} fluidType={fluidType} - key={lastDataDate} /> ) - } else { - return <Connection fluidType={currentFluidStatus.fluidType} /> } }, [ account, - currentFluidStatus.fluidType, + currentFluidStatus.connection.isUpdating, currentFluidStatus.status, fluidSlug, fluidState, fluidType, handleAccountDeletion, - isUpdating, - lastDataDate, showOfflineData, t, toggleModalConnection, @@ -313,75 +292,66 @@ const KonnectorViewerCard = ({ fluidConnection: updatedConnection, }) ) - Promise.all([ + const [, updatedFluidStatus] = await Promise.all([ await refreshChallengeState(), await updateGlobalFluidStatus(), ]) - setKonnectorState(_state) + + /** + * If waiting for consent, close Konnector modal first and handle connection closed + * Calling handleConnectionEnd here because it won't be called in the KonnectorModal since we don't show it + * + * Else set error for KonnectorModal + */ + if ( + fluidType === FluidType.GAS && + updatedFluidStatus[2].status == FluidState.CHALLENGE_ASKED + ) { + await handleConnectionEnd(false) + } else { + setKonnectorState(_state) + } } }, [ - dispatch, currentFluidStatus.connection, currentFluidStatus.fluidType, + dispatch, + fluidType, + handleConnectionEnd, refreshChallengeState, updateGlobalFluidStatus, ] ) - const getIconForStatus = ( - status: FluidState, - maintenance: boolean, - connection: FluidConnection, - outdatedData: number | null - ) => { - if (maintenance) { - return ( - <StyledIcon - icon={PartnersIssueNotif} - size={24} - className="konnector-state-picto" - /> - ) - } + /** Display konnector icon & its smaller status icon in upper left corner */ + const displayKonnectorIcon = useCallback(() => { + const { maintenance, status, connection } = currentFluidStatus + let statusIcon = null - if ( - (status === FluidState.ERROR || - status === FluidState.ERROR_LOGIN_FAILED) && + if (maintenance) { + statusIcon = PartnersIssueNotif + } else if ( + (status === FluidState.ERROR || status === FluidState.LOGIN_FAILED) && connection.account ) { - return ( - <StyledIcon - icon={ErrorNotif} - size={24} - className="konnector-state-picto" - /> - ) + statusIcon = ErrorNotif + } else if (isOutdatedData && isOutdatedData > 0) { + statusIcon = WarningNotif } - if (outdatedData && outdatedData > 0) { - return ( - <StyledIcon - icon={WarningNotif} - size={24} - className="konnector-state-picto" - /> - ) - } - } - - const displayKonnectorIcon = useCallback(() => { return ( <div className="konnector-icon"> <Icon icon={currentFluidStatus.connection.account ? iconType : OfflinePicto} size={49} /> - {getIconForStatus( - currentFluidStatus.status, - currentFluidStatus.maintenance, - currentFluidStatus.connection, - isOutdatedData + {statusIcon && ( + <StyledIcon + icon={statusIcon} + size={24} + className="konnector-state-picto" + /> )} </div> ) @@ -419,13 +389,12 @@ const KonnectorViewerCard = ({ useEffect(() => { async function deleteAccountForConsentRefresh() { if (shouldRefreshConsent && account) { - const accountService = new AccountService(client) await accountService.deleteAccount(account) await handleAccountDeletion() } } deleteAccountForConsentRefresh() - }, [account, client, handleAccountDeletion, shouldRefreshConsent]) + }, [account, accountService, handleAccountDeletion, shouldRefreshConsent]) useEffect(() => { let subscribed = true @@ -435,8 +404,9 @@ const KonnectorViewerCard = ({ !isKonnectorRunning(trigger) ) { if (subscribed) { - if (currentFluidStatus.connection.isUpdating) setIsUpdating(true) setOpenModal(true) + // Reset any error that might have been set after handleConnectionEnd + setKonnectorErrorDescription(null) const updatedConnection: FluidConnection = { ...currentFluidStatus.connection, shouldLaunchKonnector: false, @@ -450,13 +420,12 @@ const KonnectorViewerCard = ({ } const connectionFlow = new ConnectionFlow(client, trigger, konnector) await connectionFlow.launch() - console.log('connectionFlow', connectionFlow.jobWatcher) - connectionFlow.jobWatcher.on(ERROR_EVENT, () => { + connectionFlow.jobWatcher.on(ERROR_EVENT, async () => { + await callbackResponse(ERROR_EVENT) setKonnectorErrorDescription(connectionFlow.jobWatcher.on()._error) - callbackResponse(ERROR_EVENT) }) connectionFlow.jobWatcher.on(LOGIN_SUCCESS_EVENT, () => { - setIsLogging(false) + setIsVerifyingIdentity(false) }) connectionFlow.jobWatcher.on(SUCCESS_EVENT, () => { callbackResponse(SUCCESS_EVENT) @@ -489,22 +458,22 @@ const KonnectorViewerCard = ({ ]) return ( - <> - {isDisconnected && ( - <AccordionDetails>{getConnectionCard()}</AccordionDetails> + <div className="konnector-section-root"> + {isWaitingForConsent && <GrdfWaitConsent />} + {!isWaitingForConsent && !showOfflineData && ( + <Connection fluidType={fluidType} /> )} - {!isDisconnected && ( + {!isWaitingForConsent && showOfflineData && ( <Accordion - expanded={active} + expanded={showConnectionDetails} onChange={toggleAccordion} classes={{ - root: `expansion-panel-root ${ - !currentFluidStatus.maintenance && - (currentFluidStatus.status === FluidState.ERROR || - currentFluidStatus.status === FluidState.ERROR_LOGIN_FAILED) - ? 'red-border' - : '' - }`, + root: classNames('expansion-panel-root', { + ['red-border']: + !currentFluidStatus.maintenance && + (currentFluidStatus.status === FluidState.ERROR || + currentFluidStatus.status === FluidState.LOGIN_FAILED), + }), }} > <AccordionSummary @@ -541,16 +510,15 @@ const KonnectorViewerCard = ({ )} <KonnectorModal open={openModal} - isUpdating={isUpdating} - isLogging={isLogging} + isVerifyingIdentity={isVerifyingIdentity} state={konnectorState} error={konnectorErrorDescription} - fluidType={currentFluidStatus.fluidType} + fluidType={fluidType} handleCloseClick={handleConnectionEnd} handleAccountDeletion={handleAccountDeletion} account={account} /> - </> + </div> ) } export default KonnectorViewerCard diff --git a/src/components/Konnector/KonnectorViewerList.spec.tsx b/src/components/Konnector/KonnectorViewerList.spec.tsx index 810bd21bbc68243dd8c51fc1405db101f94b022b..d1b3e7928c956e147b4925c503bd2f3710306cce 100644 --- a/src/components/Konnector/KonnectorViewerList.spec.tsx +++ b/src/components/Konnector/KonnectorViewerList.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -28,7 +28,11 @@ describe('KonnectorViewerList component', () => { <KonnectorViewerList /> </Provider> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getByText('konnector_options.label_connect_to_electricity') + ) + }) expect(mockedNavigate).toHaveBeenCalled() }) }) diff --git a/src/components/Konnector/KonnectorViewerList.tsx b/src/components/Konnector/KonnectorViewerList.tsx index 7865473a25658a37d73e444dea7c78d4736d2069..152481df0a1011c53ff7b0a76440d155a106fb02 100644 --- a/src/components/Konnector/KonnectorViewerList.tsx +++ b/src/components/Konnector/KonnectorViewerList.tsx @@ -7,39 +7,42 @@ import { useNavigate } from 'react-router-dom' import { useAppSelector } from 'store/hooks' import { getAddPicto } from 'utils/picto' import { getFluidName } from 'utils/utils' -import './konnectorViewerList.scss' +import './konnectorViewerCard.scss' const KonnectorViewerList = () => { const { t } = useI18n() - const { fluidStatus } = useAppSelector(state => state.ecolyo.global) const navigate = useNavigate() + const { fluidStatus } = useAppSelector(state => state.ecolyo.global) + const goToFluid = (fluidType: FluidType) => { navigate(`/consumption/${getFluidName(fluidType)}`) } return ( - <div className="konnectorsList"> - {fluidStatus.map(fluidStatusItem => ( - <StyledCard - key={fluidStatusItem.fluidType} - className="connection-card" - onClick={() => goToFluid(fluidStatusItem.fluidType)} - fluidType={fluidStatusItem.fluidType} - > - <Icon icon={getAddPicto(fluidStatusItem.fluidType)} size={36} /> - <div - className={`konnector-title text-18-bold ${FluidType[ - fluidStatusItem.fluidType - ].toLowerCase()}`} + <div className="konnector-section-root"> + <div className="konnectorsList"> + {fluidStatus.map(fluidStatusItem => ( + <StyledCard + key={fluidStatusItem.fluidType} + className="connection-card" + onClick={() => goToFluid(fluidStatusItem.fluidType)} + fluidType={fluidStatusItem.fluidType} > - {t( - `konnector_options.label_connect_to_${FluidType[ + <Icon icon={getAddPicto(fluidStatusItem.fluidType)} size={36} /> + <div + className={`konnector-title text-18-bold ${getFluidName( fluidStatusItem.fluidType - ].toLowerCase()}` - )} - </div> - </StyledCard> - ))} + )}`} + > + {t( + `konnector_options.label_connect_to_${getFluidName( + fluidStatusItem.fluidType + )}` + )} + </div> + </StyledCard> + ))} + </div> </div> ) } diff --git a/src/components/Konnector/__snapshots__/KonnectorModal.spec.tsx.snap b/src/components/Konnector/__snapshots__/KonnectorModal.spec.tsx.snap index e4f491c9845a8ee9dd936e89f86e8bdf7412c2ec..0afd9d36255efb8a46293bc32d038cedd180b19a 100644 --- a/src/components/Konnector/__snapshots__/KonnectorModal.spec.tsx.snap +++ b/src/components/Konnector/__snapshots__/KonnectorModal.spec.tsx.snap @@ -62,10 +62,10 @@ exports[`KonnectorModal component should be rendered correctly 1`] = ` </div> </div> <div - class="kmodal-content-text kmodal-content-text-center text-16-normal" + class="kmodal-content-text text-16-normal" > <div - class="kc-wait text-16-bold" + class="text-16-bold" > konnector_modal.loading_data </div> diff --git a/src/components/Konnector/__snapshots__/KonnectorViewerList.spec.tsx.snap b/src/components/Konnector/__snapshots__/KonnectorViewerList.spec.tsx.snap index 8fd0a567d1583af4d3f71d49a5ced00da6b26630..102a761bc6b23ce140012667112e749b927d0bd5 100644 --- a/src/components/Konnector/__snapshots__/KonnectorViewerList.spec.tsx.snap +++ b/src/components/Konnector/__snapshots__/KonnectorViewerList.spec.tsx.snap @@ -3,98 +3,102 @@ exports[`KonnectorViewerList component should be rendered correctly 1`] = ` <div> <div - class="konnectorsList" + class="konnector-section-root" > - <button - class="MuiButtonBase-root MuiCardActionArea-root WithStyles(ForwardRef(CardActionArea))-root-1 connection-card electricity" - tabindex="0" - type="button" + <div + class="konnectorsList" > - <div - class="MuiCardContent-root WithStyles(ForwardRef(CardContent))-root-2" + <button + class="MuiButtonBase-root MuiCardActionArea-root WithStyles(ForwardRef(CardActionArea))-root-1 connection-card electricity" + tabindex="0" + type="button" > - <svg - class="styles__icon___23x3R" - height="36" - width="36" - > - <use - xlink:href="#test-file-stub" - /> - </svg> <div - class="konnector-title text-18-bold electricity" + class="MuiCardContent-root WithStyles(ForwardRef(CardContent))-root-2" > - konnector_options.label_connect_to_electricity + <svg + class="styles__icon___23x3R" + height="36" + width="36" + > + <use + xlink:href="#test-file-stub" + /> + </svg> + <div + class="konnector-title text-18-bold electricity" + > + konnector_options.label_connect_to_electricity + </div> </div> - </div> - <span - class="MuiCardActionArea-focusHighlight" - /> - <span - class="MuiTouchRipple-root" - /> - </button> - <button - class="MuiButtonBase-root MuiCardActionArea-root WithStyles(ForwardRef(CardActionArea))-root-1 connection-card water" - tabindex="0" - type="button" - > - <div - class="MuiCardContent-root WithStyles(ForwardRef(CardContent))-root-2" + <span + class="MuiCardActionArea-focusHighlight" + /> + <span + class="MuiTouchRipple-root" + /> + </button> + <button + class="MuiButtonBase-root MuiCardActionArea-root WithStyles(ForwardRef(CardActionArea))-root-1 connection-card water" + tabindex="0" + type="button" > - <svg - class="styles__icon___23x3R" - height="36" - width="36" - > - <use - xlink:href="#test-file-stub" - /> - </svg> <div - class="konnector-title text-18-bold water" + class="MuiCardContent-root WithStyles(ForwardRef(CardContent))-root-2" > - konnector_options.label_connect_to_water + <svg + class="styles__icon___23x3R" + height="36" + width="36" + > + <use + xlink:href="#test-file-stub" + /> + </svg> + <div + class="konnector-title text-18-bold water" + > + konnector_options.label_connect_to_water + </div> </div> - </div> - <span - class="MuiCardActionArea-focusHighlight" - /> - <span - class="MuiTouchRipple-root" - /> - </button> - <button - class="MuiButtonBase-root MuiCardActionArea-root WithStyles(ForwardRef(CardActionArea))-root-1 connection-card gas" - tabindex="0" - type="button" - > - <div - class="MuiCardContent-root WithStyles(ForwardRef(CardContent))-root-2" + <span + class="MuiCardActionArea-focusHighlight" + /> + <span + class="MuiTouchRipple-root" + /> + </button> + <button + class="MuiButtonBase-root MuiCardActionArea-root WithStyles(ForwardRef(CardActionArea))-root-1 connection-card gas" + tabindex="0" + type="button" > - <svg - class="styles__icon___23x3R" - height="36" - width="36" - > - <use - xlink:href="#test-file-stub" - /> - </svg> <div - class="konnector-title text-18-bold gas" + class="MuiCardContent-root WithStyles(ForwardRef(CardContent))-root-2" > - konnector_options.label_connect_to_gas + <svg + class="styles__icon___23x3R" + height="36" + width="36" + > + <use + xlink:href="#test-file-stub" + /> + </svg> + <div + class="konnector-title text-18-bold gas" + > + konnector_options.label_connect_to_gas + </div> </div> - </div> - <span - class="MuiCardActionArea-focusHighlight" - /> - <span - class="MuiTouchRipple-root" - /> - </button> + <span + class="MuiCardActionArea-focusHighlight" + /> + <span + class="MuiTouchRipple-root" + /> + </button> + </div> </div> </div> `; diff --git a/src/components/Konnector/konnectorModal.scss b/src/components/Konnector/konnectorModal.scss index 1c27774962d8e9e7537666e13136f84ba5fc309b..832aa7c48d32f01259eff7d255a2c4aadb34640c 100644 --- a/src/components/Konnector/konnectorModal.scss +++ b/src/components/Konnector/konnectorModal.scss @@ -22,10 +22,6 @@ min-height: 11.25rem; text-align: center; - .kc-wait { - margin-bottom: 2rem; - } - .waiting-text { display: none; @@ -38,16 +34,14 @@ } } - .kmodal-content-text-center { - text-align: center; - } - .kmodal-info { padding: 1rem; - text-align: center; + display: flex; + flex-direction: column; + gap: 24px; .buttons { display: flex; - gap: 0.825rem; + gap: 1rem; } .konnector-config { align-items: center; @@ -58,32 +52,22 @@ gap: 1rem; .elec-fail { color: $grey-bright; - margin-top: 1rem; } &.mismatch { .title { color: $orange; } - div { - margin-bottom: 1rem; - } .info { color: $grey-bright; } } - .kce-picto-txt { - color: $red-primary; - } - - .kcs-picto-txt { + .headerSuccess { color: $multi-color; } - - .commonErrors { - text-decoration: underline; - cursor: pointer; - margin: 1rem auto 0.5rem; + .headerError { + color: $red-primary; } + .commonErrorsList { text-align: left; span { @@ -104,10 +88,10 @@ } } } + } - button { - margin-top: 1rem; - } + .light { + color: $grey-bright; } } diff --git a/src/components/Konnector/konnectorViewerCard.scss b/src/components/Konnector/konnectorViewerCard.scss index 3eaa94ce1819ed36451695e766475733da0df6fb..7d2977f8d68d7c29d8e5e11cc9832557a2299502 100644 --- a/src/components/Konnector/konnectorViewerCard.scss +++ b/src/components/Konnector/konnectorViewerCard.scss @@ -1,6 +1,18 @@ @import 'src/styles/base/color'; @import 'src/styles/base/breakpoint'; +.konnector-section-root { + margin: 0 auto; + padding-bottom: 1rem; + max-width: 45.75rem; + width: 100%; + box-sizing: border-box; + @media #{$large-phone} { + padding-left: 1rem; + padding-right: 1rem; + } +} + .konnector-icon { margin-right: 1rem; position: relative; @@ -32,3 +44,22 @@ color: $grey-bright; } } + +.konnectorsList { + display: flex; + flex-direction: column; + gap: 1rem; + padding-top: 1rem; + button.connection-card { + height: 80px; + &.electricity { + border: 1px solid var(--elecColor40); + } + &.gas { + border: 1px solid var(--gasColor40); + } + &.water { + border: 1px solid var(--waterColor40); + } + } +} diff --git a/src/components/Konnector/konnectorViewerList.scss b/src/components/Konnector/konnectorViewerList.scss deleted file mode 100644 index bb94bfef8f16e23af9058dbccff1244f001fa293..0000000000000000000000000000000000000000 --- a/src/components/Konnector/konnectorViewerList.scss +++ /dev/null @@ -1,21 +0,0 @@ -@import 'src/styles/base/color'; -@import 'src/styles/base/breakpoint'; - -.konnectorsList { - display: flex; - flex-direction: column; - gap: 1rem; - padding-top: 1rem; - button.connection-card { - height: 80px; - &.electricity { - border: 1px solid var(--elecColor40); - } - &.gas { - border: 1px solid var(--gasColor40); - } - &.water { - border: 1px solid var(--waterColor40); - } - } -} diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index 7fbb1d66fdf1829e508d2cc578d2837c6cefbedc..581cbba1c52a86544306ae6db1d7dfb49ee43f54 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -98,7 +98,10 @@ const Navbar = () => { </ul> </nav> <div role="contentinfo" className="logos-container"> - <img src={logos} alt="ensemble de logos" /> + <img + src={logos} + alt="Logo des financeurs : MĂ©tropole de Lyon, Etat via la Banque des Territoires et son programme France 2030, Union EuropĂ©enne" + /> </div> </aside> ) diff --git a/src/components/Options/ExportData/Modals/exportDoneModal.spec.tsx b/src/components/Options/ExportData/Modals/exportDoneModal.spec.tsx index dc0bd5588f24e50858bd0f75a19d400d8ba6a26d..56cd7cac4a4229f25e985ebecba5e9045cb9cbb4 100644 --- a/src/components/Options/ExportData/Modals/exportDoneModal.spec.tsx +++ b/src/components/Options/ExportData/Modals/exportDoneModal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' import ExportDoneModal from './exportDoneModal' @@ -27,7 +27,9 @@ describe('exportDoneModal component', () => { handleCloseClick={mockHandleClose} /> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click(screen.getByText('export.modal_done.button_close')) + }) expect(mockHandleClose).toHaveBeenCalledTimes(1) }) }) diff --git a/src/components/Options/ExportData/Modals/exportLoadingModal.spec.tsx b/src/components/Options/ExportData/Modals/exportLoadingModal.spec.tsx index 06371f31aa2e375692f2e16e1b1e418b7116d42a..8fe1bce5b001c91dc854b98722a482ba50a95be7 100644 --- a/src/components/Options/ExportData/Modals/exportLoadingModal.spec.tsx +++ b/src/components/Options/ExportData/Modals/exportLoadingModal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { allFluids } from 'utils/utils' @@ -27,7 +27,11 @@ describe('ExportLoadingModal component', () => { selectedFluids={allFluids} /> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getByText('export.modal_loading.button_cancel') + ) + }) expect(mockHandleClose).toHaveBeenCalledTimes(1) }) }) diff --git a/src/components/Options/ExportData/Modals/exportLoadingModal.tsx b/src/components/Options/ExportData/Modals/exportLoadingModal.tsx index f6058bed9c22a9a093ed02e7d276bbccecadb78e..f25b2e28cd389aa8e53eca6aa830f70f4979bf73 100644 --- a/src/components/Options/ExportData/Modals/exportLoadingModal.tsx +++ b/src/components/Options/ExportData/Modals/exportLoadingModal.tsx @@ -12,7 +12,7 @@ import { Datachart, Dataload, TimePeriod } from 'models' import React, { useCallback, useEffect } from 'react' import ConsumptionDataManager from 'services/consumption.service' import EnedisMonthlyAnalysisDataService from 'services/enedisMonthlyAnalysisData.service' -import { formatTwoDigits } from 'utils/utils' +import { formatTwoDigits, getFluidName } from 'utils/utils' import * as XLSX from 'xlsx' import './exportLoadingModal.scss' @@ -65,12 +65,11 @@ const ExportLoadingModal = ({ fluidType: FluidType ): Promise<ExportDataRow> => { const dataRow: ExportDataRow = {} + const fluidName = getFluidName(fluidType) dataRow[t('export.month')] = formatTwoDigits(dataload.date.month) dataRow[t('export.year')] = dataload.date.year dataRow[ - `${t('export.consumption')} (${t( - `FLUID.${FluidType[fluidType]}.UNIT` - )})` + `${t('export.consumption')} (${t('FLUID.' + fluidName + '.UNIT')})` ] = dataload.value if (fluidType === FluidType.ELECTRICITY) { const emas = new EnedisMonthlyAnalysisDataService(client) diff --git a/src/components/Options/ExportData/Modals/exportStartModal.spec.tsx b/src/components/Options/ExportData/Modals/exportStartModal.spec.tsx index 36a16b1b1727c67cf391de45df4aeb0698dc74f1..a903a4151cdf312142c580be8e965d776c50966d 100644 --- a/src/components/Options/ExportData/Modals/exportStartModal.spec.tsx +++ b/src/components/Options/ExportData/Modals/exportStartModal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' import ExportStartModal from './exportStartModal' @@ -24,7 +24,11 @@ describe('exportStartModal component', () => { handleDownloadClick={jest.fn()} /> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getByText('export.modal_start.button_cancel') + ) + }) expect(mockHandleClose).toHaveBeenCalledTimes(1) }) }) diff --git a/src/components/Options/GCU/GCUView.tsx b/src/components/Options/GCU/GCUView.tsx index d5d525e538062eb138d3f08c670643efef9337c3..9f4e06c97017ace8a703e8db29d4229e106b950a 100644 --- a/src/components/Options/GCU/GCUView.tsx +++ b/src/components/Options/GCU/GCUView.tsx @@ -2,20 +2,14 @@ import Content from 'components/Content/Content' import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' import GCUContent from 'components/Options/GCU/GCUContent' -import React, { useState } from 'react' +import React from 'react' const GCUView = () => { - const [headerHeight, setHeaderHeight] = useState<number>(0) - return ( <> <CozyBar titleKey="common.title_gcu" displayBackArrow={true} /> - <Header - setHeaderHeight={setHeaderHeight} - desktopTitleKey="common.title_gcu" - displayBackArrow={true} - /> - <Content heightOffset={headerHeight}> + <Header desktopTitleKey="common.title_gcu" displayBackArrow={true} /> + <Content> <GCUContent /> </Content> </> diff --git a/src/components/Options/GCU/__snapshots__/GCUView.spec.tsx.snap b/src/components/Options/GCU/__snapshots__/GCUView.spec.tsx.snap index f5c466c4d47f425be2d8979d1c5a2995a763982b..918984408f738a1e931094280581679954ed37ce 100644 --- a/src/components/Options/GCU/__snapshots__/GCUView.spec.tsx.snap +++ b/src/components/Options/GCU/__snapshots__/GCUView.spec.tsx.snap @@ -10,9 +10,7 @@ exports[`GCUView component should be rendered correctly 1`] = ` desktoptitlekey="common.title_gcu" displaybackarrow="true" /> - <mock-content - heightoffset="0" - > + <mock-content> <div class="gcu-content-root" > diff --git a/src/components/Options/HelpLink/HelpLink.tsx b/src/components/Options/HelpLink/HelpLink.tsx index 4702a64fac00f1f6c8d99aa4daac80313101200c..a6e8362cc3a43b98e3fb17dcd762443bd0a01623 100644 --- a/src/components/Options/HelpLink/HelpLink.tsx +++ b/src/components/Options/HelpLink/HelpLink.tsx @@ -20,6 +20,8 @@ const HelpLink = () => { <Link className="help-card-link" onClick={() => dispatch(openFeedbackModal(true))} + onKeyDown={() => dispatch(openFeedbackModal(true))} + tabIndex={0} > <div className="card"> <div className="help-card"> diff --git a/src/components/Options/LegalNotice/LegalNoticeView.tsx b/src/components/Options/LegalNotice/LegalNoticeView.tsx index 4c68ee2a1b48cca408a1cae207e24ca4464cd182..660a1f14806934a8e5db9378dc5aed686b227709 100644 --- a/src/components/Options/LegalNotice/LegalNoticeView.tsx +++ b/src/components/Options/LegalNotice/LegalNoticeView.tsx @@ -1,22 +1,19 @@ import Content from 'components/Content/Content' import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' -import React, { useState } from 'react' +import React from 'react' import LegalNoticeContent from './LegalNoticeContent' import './legalNoticeView.scss' const LegalNoticeView = () => { - const [headerHeight, setHeaderHeight] = useState<number>(0) - return ( <> <CozyBar titleKey="common.title_legal_notice" displayBackArrow={true} /> <Header - setHeaderHeight={setHeaderHeight} desktopTitleKey="common.title_legal_notice" displayBackArrow={true} /> - <Content heightOffset={headerHeight}> + <Content> <LegalNoticeContent /> </Content> </> diff --git a/src/components/Options/LegalNotice/__snapshots__/LegalNoticeView.spec.tsx.snap b/src/components/Options/LegalNotice/__snapshots__/LegalNoticeView.spec.tsx.snap index f236decc8ad65984c4fd9640a1be5cb9915c238d..cfcafe24c35d3ffa8d738027f2c04c6107e1a8c2 100644 --- a/src/components/Options/LegalNotice/__snapshots__/LegalNoticeView.spec.tsx.snap +++ b/src/components/Options/LegalNotice/__snapshots__/LegalNoticeView.spec.tsx.snap @@ -10,9 +10,7 @@ exports[`LegalNoticeView component should be rendered correctly 1`] = ` desktoptitlekey="common.title_legal_notice" displaybackarrow="true" /> - <mock-content - heightoffset="0" - > + <mock-content> <div class="legal-notice-root" > diff --git a/src/components/Options/OptionsView.tsx b/src/components/Options/OptionsView.tsx index 8da4b6a0fb4b806e59c4fd3fd206f89c00333724..49f4bb50a5d522088338993dc4987d4f328c4474 100644 --- a/src/components/Options/OptionsView.tsx +++ b/src/components/Options/OptionsView.tsx @@ -2,7 +2,7 @@ import logos from 'assets/png/logos_partenaires.svg' import Content from 'components/Content/Content' import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' -import React, { useState } from 'react' +import React from 'react' import Accessibility from './Accessibility/Accessibility' import ExportData from './ExportData/ExportData' import GCULink from './GCU/GCULink' @@ -14,16 +14,11 @@ import ReportOptions from './ReportOptions/ReportOptions' import Version from './Version/Version' const OptionsView = () => { - const [headerHeight, setHeaderHeight] = useState<number>(0) - return ( <> <CozyBar titleKey="common.title_options" /> - <Header - setHeaderHeight={setHeaderHeight} - desktopTitleKey="common.title_options" - /> - <Content heightOffset={headerHeight}> + <Header desktopTitleKey="common.title_options" /> + <Content> <ProfileTypeOptions /> <ExportData /> <ReportOptions /> @@ -33,7 +28,10 @@ const OptionsView = () => { <Accessibility /> <MatomoOptOut /> <div className="parameters-logos"> - <img src={logos} alt="ensemble de logos" /> + <img + src={logos} + alt="Logo des financeurs : MĂ©tropole de Lyon, Etat via la Banque des Territoires et son programme France 2030, Union EuropĂ©enne" + /> </div> <Version /> </Content> diff --git a/src/components/Options/ProfileTypeOptions/ProfileTypeOptions.spec.tsx b/src/components/Options/ProfileTypeOptions/ProfileTypeOptions.spec.tsx index e5c67e980ecfea5869f0448492ae90c398bdb5f1..1ecad5c9a8a396d1ab3087f8c050d4b78cb3a2ac 100644 --- a/src/components/Options/ProfileTypeOptions/ProfileTypeOptions.spec.tsx +++ b/src/components/Options/ProfileTypeOptions/ProfileTypeOptions.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import ProfileTypeOptions from 'components/Options/ProfileTypeOptions/ProfileTypeOptions' import { @@ -31,7 +31,9 @@ describe('ProfileTypeOptions component', () => { </Provider> ) expect(container.getElementsByClassName('profile-icon').length).toBeTruthy() - await userEvent.click(screen.getByRole('button')) + await act(async () => { + await userEvent.click(screen.getByText('profile_type.read_profile')) + }) expect(mockedNavigate).toHaveBeenCalled() }) it('should be rendered when user complete profile type form', () => { diff --git a/src/components/Options/ReportOptions/ReportOptions.spec.tsx b/src/components/Options/ReportOptions/ReportOptions.spec.tsx index 008e755404fc612cda3a1c5c722377e7d0185da5..14752d0b242c4ba2333d151dca4fb41c539eaeac 100644 --- a/src/components/Options/ReportOptions/ReportOptions.spec.tsx +++ b/src/components/Options/ReportOptions/ReportOptions.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import ReportOptions from 'components/Options/ReportOptions/ReportOptions' import React from 'react' @@ -41,7 +41,9 @@ describe('ReportOptions component', () => { <ReportOptions /> </Provider> ) - await userEvent.click(screen.getAllByRole('checkbox')[0]) + await act(async () => { + await userEvent.click(screen.getAllByRole('checkbox')[0]) + }) expect(updateProfileSpy).toHaveBeenCalledTimes(1) expect(updateProfileSpy).toHaveBeenCalledWith({ sendAnalysisNotification: false, @@ -57,7 +59,9 @@ describe('ReportOptions component', () => { <ReportOptions /> </Provider> ) - await userEvent.click(screen.getAllByRole('checkbox')[0]) + await act(async () => { + await userEvent.click(screen.getAllByRole('checkbox')[0]) + }) expect(updateProfileSpy).toHaveBeenCalledTimes(1) expect(updateProfileSpy).toHaveBeenCalledWith({ sendAnalysisNotification: true, @@ -76,7 +80,9 @@ describe('ReportOptions component', () => { </Provider> ) expect(screen.getAllByRole('checkbox')[1]).not.toBeChecked() - await userEvent.click(screen.getAllByRole('checkbox')[1]) + await act(async () => { + await userEvent.click(screen.getAllByRole('checkbox')[1]) + }) expect(updateProfileSpy).toHaveBeenCalledTimes(1) expect(updateProfileSpy).toHaveBeenCalledWith({ sendConsumptionAlert: true, diff --git a/src/components/Options/ReportOptions/ReportOptions.tsx b/src/components/Options/ReportOptions/ReportOptions.tsx index 50556fd2878273301c853a6d59c8f64ac540df02..ab9bf280a489403943a040a97096087dce19943e 100644 --- a/src/components/Options/ReportOptions/ReportOptions.tsx +++ b/src/components/Options/ReportOptions/ReportOptions.tsx @@ -1,3 +1,4 @@ +import { OutlinedInput } from '@material-ui/core' import StyledSwitch from 'components/CommonKit/Switch/StyledSwitch' import { useClient } from 'cozy-client' import { useI18n } from 'cozy-ui/transpiled/react/I18n' @@ -9,7 +10,6 @@ import ConsumptionDataManager from 'services/consumption.service' import { useAppDispatch, useAppSelector } from 'store/hooks' import { updateProfile } from 'store/profile/profile.slice' import './reportOptions.scss' -import { TextField } from '@material-ui/core' const ReportOptions = () => { const { t } = useI18n() @@ -35,9 +35,11 @@ const ReportOptions = () => { const isWaterConnected = fluidStatus[FluidType.WATER].status !== FluidState.NOT_CONNECTED && fluidStatus[FluidType.WATER].status !== FluidState.KONNECTOR_NOT_FOUND && - fluidStatus[FluidType.WATER].status !== FluidState.ERROR_LOGIN_FAILED + fluidStatus[FluidType.WATER].status !== FluidState.LOGIN_FAILED - const setWaterLimit = (e: React.ChangeEvent<HTMLInputElement>) => { + const setWaterLimit = ( + e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement, Element> + ) => { if (e.target.value !== null && parseInt(e.target.value) > 0) { dispatch( updateProfile({ @@ -134,8 +136,7 @@ const ReportOptions = () => { {t('profile.report.input_label_alert')} </div> <div className="switch-container-alert"> - <TextField - variant="outlined" + <OutlinedInput className="inputNumber text-18" type="number" defaultValue={ @@ -144,7 +145,12 @@ const ReportOptions = () => { : profile.waterDailyConsumptionLimit } onBlur={setWaterLimit} - label={t('profile.accessibility.input_water_alert_report')} + inputProps={{ + min: 0, + }} + aria-label={t( + 'profile.accessibility.input_water_alert_report' + )} inputMode="numeric" /> <span className="switch-label text-16-normal">L</span> diff --git a/src/components/Options/Unsubscribe/Unsubscribe.spec.tsx b/src/components/Options/Unsubscribe/Unsubscribe.spec.tsx index a014ff8cb0b1b83f6483b2673a042a1391bf78be..9a9ac90550c1ace8b3d4adc06a97e27198e8eb61 100644 --- a/src/components/Options/Unsubscribe/Unsubscribe.spec.tsx +++ b/src/components/Options/Unsubscribe/Unsubscribe.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { act, render, screen, waitFor } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import Unsubscribe from './Unsubscribe' @@ -20,8 +20,9 @@ describe('Unsubscribe component', () => { it('should click the subscribe button', async () => { const { container } = render(<Unsubscribe />) await waitFor(() => null, { container }) - const [subscribeBtn] = screen.getAllByRole('button') - await userEvent.click(subscribeBtn) + await act(async () => { + await userEvent.click(screen.getByText('unsubscribe.button_subscribe')) + }) expect(mockUpdateProfile).toHaveBeenCalled() // then should only display one button diff --git a/src/components/Options/__snapshots__/OptionsView.spec.tsx.snap b/src/components/Options/__snapshots__/OptionsView.spec.tsx.snap index eee51c9ad1383e8c55bd7bbb53ab0d99fe3e5af1..7c656548ccaacfba5622169171ff6b2524b8ef7c 100644 --- a/src/components/Options/__snapshots__/OptionsView.spec.tsx.snap +++ b/src/components/Options/__snapshots__/OptionsView.spec.tsx.snap @@ -8,9 +8,7 @@ exports[`OptionsView component should be rendered correctly 1`] = ` <mock-header desktoptitlekey="common.title_options" /> - <mock-content - heightoffset="0" - > + <mock-content> <div class="profile-type-root" > @@ -219,6 +217,7 @@ exports[`OptionsView component should be rendered correctly 1`] = ` </div> <a class="MuiTypography-root MuiLink-root MuiLink-underlineHover help-card-link MuiTypography-colorPrimary" + tabindex="0" > <div class="card" @@ -398,7 +397,7 @@ exports[`OptionsView component should be rendered correctly 1`] = ` class="parameters-logos" > <img - alt="ensemble de logos" + alt="Logo des financeurs : MĂ©tropole de Lyon, Etat via la Banque des Territoires et son programme France 2030, Union EuropĂ©enne" src="test-file-stub" /> </div> diff --git a/src/components/PartnerIssue/PartnerIssueModal.spec.tsx b/src/components/PartnerIssue/PartnerIssueModal.spec.tsx index be3c840762990e6e96e63bab2833f5d69580dfd5..41789bf10ed7bf69f7abe1b9afc611921fb1f6a7 100644 --- a/src/components/PartnerIssue/PartnerIssueModal.spec.tsx +++ b/src/components/PartnerIssue/PartnerIssueModal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import { FluidType } from 'enums' import React from 'react' @@ -49,7 +49,11 @@ describe('PartnerIssueModal component', () => { issuedFluid={FluidType.ELECTRICITY} /> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getByText('consumption.partner_issue_modal.ok') + ) + }) expect(mockHandleClose).toHaveBeenCalled() }) diff --git a/src/components/PartnerIssue/PartnerIssueModal.tsx b/src/components/PartnerIssue/PartnerIssueModal.tsx index 14549c0270cc6531bc3ad17783ccd3e39eeedb0a..d6158bd696ecd68900096f8994e68a8d3f8532e3 100644 --- a/src/components/PartnerIssue/PartnerIssueModal.tsx +++ b/src/components/PartnerIssue/PartnerIssueModal.tsx @@ -41,7 +41,7 @@ const PartnerIssueModal = ({ onClose={(event, reason): void => { event && reason !== 'backdropClick' && handleCloseClick(issuedFluid) }} - aria-labelledby="accessibility-title" + aria-label={t('consumption.partner_issue_modal.accessibility_title')} classes={{ root: 'modal-root', paper: 'modal-paper', diff --git a/src/components/PartnerIssue/__snapshots__/PartnerIssueModal.spec.tsx.snap b/src/components/PartnerIssue/__snapshots__/PartnerIssueModal.spec.tsx.snap index c133766cab34b402c000f5244f9c4ec9c6aed62f..a0196786c26b18a6c58302c49e56c65b3c1a024b 100644 --- a/src/components/PartnerIssue/__snapshots__/PartnerIssueModal.spec.tsx.snap +++ b/src/components/PartnerIssue/__snapshots__/PartnerIssueModal.spec.tsx.snap @@ -8,6 +8,7 @@ exports[`PartnerIssueModal component should render correctly 1`] = ` aria-hidden="true" /> <div + aria-label="consumption.partner_issue_modal.accessibility_title" class="MuiDialog-root modal-root" role="presentation" style="position: fixed; z-index: 1500; right: 0px; bottom: 0px; top: 0px; left: 0px;" @@ -28,7 +29,6 @@ exports[`PartnerIssueModal component should render correctly 1`] = ` tabindex="-1" > <div - aria-labelledby="accessibility-title" class="MuiPaper-root MuiDialog-paper modal-paper MuiDialog-paperScrollPaper MuiDialog-paperWidthSm MuiPaper-elevation24 MuiPaper-rounded" role="dialog" > diff --git a/src/components/ProfileType/ProfileTypeFinished/ProfileTypeFinished.spec.tsx b/src/components/ProfileType/ProfileTypeFinished/ProfileTypeFinished.spec.tsx index 402a3a0bffeea34b3b24e2479491759dbadf4bb7..129114f86fac29eaba1d227ef002015e062b966e 100644 --- a/src/components/ProfileType/ProfileTypeFinished/ProfileTypeFinished.spec.tsx +++ b/src/components/ProfileType/ProfileTypeFinished/ProfileTypeFinished.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -45,7 +45,11 @@ describe('ProfileTypeFinished component', () => { <ProfileTypeFinished profileType={mockProfileType} /> </Provider> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getByLabelText('profile_type.accessibility.button_validate') + ) + }) expect(mockedNavigate).toHaveBeenCalledWith('/ecogesture-selection') }) }) diff --git a/src/components/ProfileType/ProfileTypeFinished/ProfileTypeFinished.tsx b/src/components/ProfileType/ProfileTypeFinished/ProfileTypeFinished.tsx index c7de430773e7f8036822003100eaa29b1b9ebe8f..f32dd2d9d4b37c8253ddd97169bc1aafa6d39a3a 100644 --- a/src/components/ProfileType/ProfileTypeFinished/ProfileTypeFinished.tsx +++ b/src/components/ProfileType/ProfileTypeFinished/ProfileTypeFinished.tsx @@ -53,7 +53,7 @@ const ProfileTypeFinished = ({ profileType }: { profileType: ProfileType }) => { }), } const profileTypeEntityService = new ProfileTypeEntityService(client) - const myProfileTypes: ProfileType[] | null = + const myProfileTypes = await profileTypeEntityService.getAllProfileTypes(chosenPeriod) if (myProfileTypes !== null) { const destroyPT = diff --git a/src/components/ProfileType/ProfileTypeFormNumber/ProfileTypeFormNumber.tsx b/src/components/ProfileType/ProfileTypeFormNumber/ProfileTypeFormNumber.tsx index ded85add46f1431517a14bbe7fbbf1f454691f5c..61d317064e8565d87c1793b4d5d15b0b7c11cb89 100644 --- a/src/components/ProfileType/ProfileTypeFormNumber/ProfileTypeFormNumber.tsx +++ b/src/components/ProfileType/ProfileTypeFormNumber/ProfileTypeFormNumber.tsx @@ -1,4 +1,5 @@ /* eslint-disable jsx-a11y/no-autofocus */ +import { InputAdornment, OutlinedInput } from '@material-ui/core' import FormNavigation from 'components/CommonKit/FormNavigation/FormNavigation' import 'components/ProfileType/profileTypeForm.scss' import { useI18n } from 'cozy-ui/transpiled/react/I18n' @@ -6,7 +7,6 @@ import { ProfileTypeStepForm } from 'enums' import { ProfileType, ProfileTypeAnswer, ProfileTypeValues } from 'models' import React, { useCallback, useEffect, useState } from 'react' import { useAppSelector } from 'store/hooks' -import { OutlinedInput, TextField, InputAdornment } from '@material-ui/core' interface ProfileTypeFormNumberProps { step: ProfileTypeStepForm diff --git a/src/components/ProfileType/ProfileTypeView.spec.tsx b/src/components/ProfileType/ProfileTypeView.spec.tsx index 77e2baebe8ea29e3184babe0745fed2a83d520ec..8b836fb4d73b0243f9c2b20f044efaca51ed26da 100644 --- a/src/components/ProfileType/ProfileTypeView.spec.tsx +++ b/src/components/ProfileType/ProfileTypeView.spec.tsx @@ -35,7 +35,8 @@ describe('ProfileTypeView component', () => { <ProfileTypeView /> </Provider> ) - const [prev] = screen.getAllByRole('button') - expect(prev).toBeDisabled() + expect( + screen.getByLabelText('profile_type.accessibility.button_previous') + ).toBeDisabled() }) }) diff --git a/src/components/ProfileType/ProfileTypeView.tsx b/src/components/ProfileType/ProfileTypeView.tsx index 2436c5bd0203f363949993fd39c456cb4f2e632a..d7c716aab43ddbf67ac87bb35a3b855bb4466f9b 100644 --- a/src/components/ProfileType/ProfileTypeView.tsx +++ b/src/components/ProfileType/ProfileTypeView.tsx @@ -37,7 +37,6 @@ const ProfileTypeView = () => { state => state.ecolyo ) - const [headerHeight, setHeaderHeight] = useState<number>(0) const [currentProfileType, setCurrentProfileType] = useState<ProfileType>({ updateDate: DateTime.local() .setZone('utc', { @@ -225,11 +224,10 @@ const ProfileTypeView = () => { <> <CozyBar titleKey="common.title_profiletype" displayBackArrow={true} /> <Header - setHeaderHeight={setHeaderHeight} desktopTitleKey="common.title_profiletype" displayBackArrow={true} /> - <Content heightOffset={headerHeight}> + <Content> <div className="profileType-view"> <div className="progressContainer"> <FormProgress diff --git a/src/components/Quiz/QuizBegin/QuizBegin.spec.tsx b/src/components/Quiz/QuizBegin/QuizBegin.spec.tsx index aa5330b3cc9dc91ec28ac0ec48172a87e6f9e898..ccaf243e83620d21cffe47d48d5bc75187afdb2a 100644 --- a/src/components/Quiz/QuizBegin/QuizBegin.spec.tsx +++ b/src/components/Quiz/QuizBegin/QuizBegin.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import { UserChallengeUpdateFlag } from 'enums' import React from 'react' @@ -23,7 +23,9 @@ describe('QuizBegin component', () => { </Provider> ) expect(container.getElementsByClassName('quiz-icon')[0]).toBeInTheDocument() - await userEvent.click(screen.getByRole('button')) + await act(async () => { + await userEvent.click(screen.getByText('duel.button_start')) + }) expect(mockUserChallengeUpdateFlag).toHaveBeenCalledWith( userChallengeData[0], UserChallengeUpdateFlag.QUIZ_START diff --git a/src/components/Quiz/QuizFinish/QuizFinish.spec.tsx b/src/components/Quiz/QuizFinish/QuizFinish.spec.tsx index 8b2dc272d6852c08babfdb55b386e176f935185d..9bac2567e4a78b44b8dcf3cf0ee6234b2e75df6e 100644 --- a/src/components/Quiz/QuizFinish/QuizFinish.spec.tsx +++ b/src/components/Quiz/QuizFinish/QuizFinish.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -28,7 +28,9 @@ describe('QuizFinish', () => { </Provider> ) expect(container.getElementsByClassName('quiz-icon')[0]).toBeInTheDocument() - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click(screen.getByText('quiz.button_go_back')) + }) expect(mockUserChallengeUpdateFlag).toHaveBeenCalled() expect(mockedNavigate).toHaveBeenCalledWith('/challenges') }) diff --git a/src/components/Quiz/QuizFinish/QuizFinish.tsx b/src/components/Quiz/QuizFinish/QuizFinish.tsx index 3603c0710a1201c2f4fc0c160a384f8a74833198..c2c1d8ebec6a7fa203fd2208fc38db4e75a389fa 100644 --- a/src/components/Quiz/QuizFinish/QuizFinish.tsx +++ b/src/components/Quiz/QuizFinish/QuizFinish.tsx @@ -17,10 +17,7 @@ const QuizFinish = ({ userChallenge }: { userChallenge: UserChallenge }) => { const client = useClient() const navigate = useNavigate() const dispatch = useAppDispatch() - const challengeService: ChallengeService = useMemo( - () => new ChallengeService(client), - [client] - ) + const challengeService = useMemo(() => new ChallengeService(client), [client]) const retryQuiz = useCallback(async () => { const userChallengeUpdated = await challengeService.updateUserChallenge( diff --git a/src/components/Quiz/QuizQuestion/QuizQuestionContent.spec.tsx b/src/components/Quiz/QuizQuestion/QuizQuestionContent.spec.tsx index aa80b5f09caa65e5868dab46deb0908ccfcfb541..707db20c957bf13a88e956b40872912e4343738a 100644 --- a/src/components/Quiz/QuizQuestion/QuizQuestionContent.spec.tsx +++ b/src/components/Quiz/QuizQuestion/QuizQuestionContent.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -51,7 +51,9 @@ describe('QuizQuestionContent component', () => { /> </Provider> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click(screen.getAllByRole('button')[0]) + }) expect(mockedNavigate).toHaveBeenCalledWith('/challenges') }) }) diff --git a/src/components/Quiz/QuizQuestion/QuizQuestionContent.tsx b/src/components/Quiz/QuizQuestion/QuizQuestionContent.tsx index 74a8effc665cf8a43225ae16b407d246c21f12fc..eb59925814374d01f1b630783c21b082cbb32db6 100644 --- a/src/components/Quiz/QuizQuestion/QuizQuestionContent.tsx +++ b/src/components/Quiz/QuizQuestion/QuizQuestionContent.tsx @@ -5,7 +5,7 @@ import QuizExplanationModal from 'components/Quiz/QuizExplanationModal/QuizExpla import { useClient } from 'cozy-client' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { UserChallengeUpdateFlag } from 'enums' -import { Answer, UserChallenge, UserQuiz } from 'models' +import { UserChallenge } from 'models' import React, { Dispatch, SetStateAction, useCallback, useState } from 'react' import ChallengeService from 'services/challenge.service' import QuizService from 'services/quiz.service' @@ -36,29 +36,28 @@ const QuizQuestionContent = ({ const [questionIndex, setQuestionIndex] = useState<number>(questionIndexLocked) - const quizService: QuizService = new QuizService(client) - const challengeService: ChallengeService = new ChallengeService(client) + const quizService = new QuizService(client) + const challengeService = new ChallengeService(client) const validateQuestion = async () => { - const resultIndex: number = userChallenge.quiz.questions[ + const resultIndex = userChallenge.quiz.questions[ questionIndex ].answers.findIndex(answer => answer.answerLabel === userChoice) - const result: Answer[] = userChallenge.quiz.questions[ - questionIndex - ].answers.filter(answer => answer.answerLabel === userChoice) + const result = userChallenge.quiz.questions[questionIndex].answers.filter( + answer => answer.answerLabel === userChoice + ) setAnswerIndex(resultIndex) setOpenModal(true) - const quizUpdated: UserQuiz = await quizService.updateUserQuiz( + const quizUpdated = await quizService.updateUserQuiz( userChallenge.quiz, result[0].isTrue, questionIndex ) - const userChallengeUpdated: UserChallenge = - await challengeService.updateUserChallenge( - userChallenge, - UserChallengeUpdateFlag.QUIZ_UPDATE, - quizUpdated - ) + const userChallengeUpdated = await challengeService.updateUserChallenge( + userChallenge, + UserChallengeUpdateFlag.QUIZ_UPDATE, + quizUpdated + ) dispatch(updateUserChallengeList(userChallengeUpdated)) } diff --git a/src/components/Quiz/QuizQuestion/QuizQuestionContentCustom.spec.tsx b/src/components/Quiz/QuizQuestion/QuizQuestionContentCustom.spec.tsx index 12809ef896773c3bc99e5b90f74c05926316ed62..5a3c5f3dc6117b1546bd6c73ad10765b158f530e 100644 --- a/src/components/Quiz/QuizQuestion/QuizQuestionContentCustom.spec.tsx +++ b/src/components/Quiz/QuizQuestion/QuizQuestionContentCustom.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -42,7 +42,9 @@ describe('QuizCustomQuestionContent component', () => { /> </Provider> ) - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click(screen.getAllByRole('button')[0]) + }) expect(mockHistoryPush).toHaveBeenCalledWith('/challenges') expect(screen.getByText(questionEntity.questionLabel)).toBeInTheDocument() expect(screen.getAllByRole('radio').length).toBe(3) @@ -73,8 +75,10 @@ describe('QuizCustomQuestionContent component', () => { /> </Provider> ) - await userEvent.click(screen.getAllByRole('radio')[0]) - await userEvent.click(screen.getAllByRole('button')[1]) + await act(async () => { + await userEvent.click(screen.getAllByRole('radio')[0]) + await userEvent.click(screen.getByText('quiz.button_validate')) + }) expect(mockUpdateUserQuiz).toHaveBeenCalledWith( userChallengeData[0].quiz, questionEntity.answers[0].isTrue diff --git a/src/components/Quiz/QuizQuestion/QuizQuestionContentCustom.tsx b/src/components/Quiz/QuizQuestion/QuizQuestionContentCustom.tsx index 72cf01ba2719b26e4da2132d41193389d67eaf70..f37f638feef599af138a08aab4538c714f7485f3 100644 --- a/src/components/Quiz/QuizQuestion/QuizQuestionContentCustom.tsx +++ b/src/components/Quiz/QuizQuestion/QuizQuestionContentCustom.tsx @@ -6,7 +6,7 @@ import QuizExplanationModal from 'components/Quiz/QuizExplanationModal/QuizExpla import { useClient } from 'cozy-client' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { UserChallengeUpdateFlag } from 'enums' -import { Answer, QuestionEntity, UserChallenge, UserQuiz } from 'models' +import { QuestionEntity, UserChallenge } from 'models' import React, { useState } from 'react' import ChallengeService from 'services/challenge.service' import QuizService from 'services/quiz.service' @@ -34,30 +34,29 @@ const QuizQuestionContentCustom = ({ const [userChoice, setUserChoice] = useState<string>('') const [openModal, setOpenModal] = useState<boolean>(false) const [answerIndex, setAnswerIndex] = useState<number>(0) - const quizService: QuizService = new QuizService(client) - const challengeService: ChallengeService = new ChallengeService(client) + const quizService = new QuizService(client) + const challengeService = new ChallengeService(client) const validateQuestion = async () => { if (question) { - const resultIndex: number = question.answers.findIndex( + const resultIndex = question.answers.findIndex( answer => answer.answerLabel === userChoice ) setAnswerIndex(resultIndex) setOpenModal(true) - const result: Answer[] = question.answers.filter( + const result = question.answers.filter( answer => answer.answerLabel === userChoice ) - const quizUpdated: UserQuiz = await quizService.updateUserQuiz( + const quizUpdated = await quizService.updateUserQuiz( userChallenge.quiz, result[0].isTrue ) - const userChallengeUpdated: UserChallenge = - await challengeService.updateUserChallenge( - userChallenge, - UserChallengeUpdateFlag.QUIZ_UPDATE, - quizUpdated - ) + const userChallengeUpdated = await challengeService.updateUserChallenge( + userChallenge, + UserChallengeUpdateFlag.QUIZ_UPDATE, + quizUpdated + ) dispatch(updateUserChallengeList(userChallengeUpdated)) } } @@ -68,11 +67,10 @@ const QuizQuestionContentCustom = ({ const finishQuiz = async () => { setOpenModal(false) - const userChallengeUpdated: UserChallenge = - await challengeService.updateUserChallenge( - userChallenge, - UserChallengeUpdateFlag.QUIZ_DONE - ) + const userChallengeUpdated = await challengeService.updateUserChallenge( + userChallenge, + UserChallengeUpdateFlag.QUIZ_DONE + ) dispatch(updateUserChallengeList(userChallengeUpdated)) } diff --git a/src/components/Quiz/QuizView.tsx b/src/components/Quiz/QuizView.tsx index 380346fbe0fba125cf5b63b232dccee3b2f28b45..fc45df655cf53bcc4fc6851edb1b6ecb2d87a4b0 100644 --- a/src/components/Quiz/QuizView.tsx +++ b/src/components/Quiz/QuizView.tsx @@ -3,7 +3,7 @@ import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' import { UserQuizState } from 'enums' import { UserChallenge } from 'models' -import React, { useState } from 'react' +import React from 'react' import { useAppSelector } from 'store/hooks' import QuizBegin from './QuizBegin/QuizBegin' import QuizFinish from './QuizFinish/QuizFinish' @@ -11,7 +11,6 @@ import QuizQuestion from './QuizQuestion/QuizQuestion' const QuizView = () => { const { currentChallenge } = useAppSelector(state => state.ecolyo.challenge) - const [headerHeight, setHeaderHeight] = useState<number>(0) const renderQuiz = (challenge: UserChallenge) => { switch (challenge.quiz.state) { @@ -29,14 +28,8 @@ const QuizView = () => { return ( <> <CozyBar titleKey="common.title_quiz" displayBackArrow={true} /> - <Header - setHeaderHeight={setHeaderHeight} - desktopTitleKey="common.title_quiz" - displayBackArrow={true} - /> - <Content heightOffset={headerHeight}> - {currentChallenge && renderQuiz(currentChallenge)} - </Content> + <Header desktopTitleKey="common.title_quiz" displayBackArrow={true} /> + <Content>{currentChallenge && renderQuiz(currentChallenge)}</Content> </> ) } diff --git a/src/components/Splash/SplashRoot.spec.tsx b/src/components/Splash/SplashRoot.spec.tsx index a948480c9363206ed0cf6e0b31d2d78cade0a02e..3f57b513afae955128380a203e95c612e23b5b07 100644 --- a/src/components/Splash/SplashRoot.spec.tsx +++ b/src/components/Splash/SplashRoot.spec.tsx @@ -1,5 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' -import { userEvent } from '@testing-library/user-event' +import { render, waitFor } from '@testing-library/react' import React from 'react' import { Provider } from 'react-redux' import { createMockEcolyoStore } from 'tests/__mocks__/store' @@ -22,7 +21,7 @@ describe('SplashRoot component', () => { expect(container).toMatchSnapshot() }) - it('should render the error screen and click on the reload button', async () => { + it('should render the error screen', async () => { const { container } = render( <Provider store={store}> <SplashRoot>children</SplashRoot> @@ -30,6 +29,5 @@ describe('SplashRoot component', () => { ) await waitFor(() => null, { container }) expect(container).toMatchSnapshot() - await userEvent.click(screen.getByRole('button')) }) }) diff --git a/src/components/Splash/SplashScreen.tsx b/src/components/Splash/SplashScreen.tsx index 8a23eaa2b89f00dd3b71e7e6adb902c8fb441e91..7704bccd4961e2a1ee0d11e682cb69493e00ad45 100644 --- a/src/components/Splash/SplashScreen.tsx +++ b/src/components/Splash/SplashScreen.tsx @@ -30,7 +30,10 @@ const SplashScreen = ({ initStep }: { initStep: InitSteps }) => { {t(`splashscreen.step.${initStep}`)} </div> <div className="splash-logos-container"> - <img src={logos} alt="ensemble de logos" /> + <img + src={logos} + alt="Logo des financeurs : MĂ©tropole de Lyon, Etat via la Banque des Territoires et son programme France 2030, Union EuropĂ©enne" + /> </div> </div> ) diff --git a/src/components/Splash/SplashScreenError.spec.tsx b/src/components/Splash/SplashScreenError.spec.tsx index fdc4e5fa594aaaf15732ef0aee420bf1b119dc23..2aefa0711d893cb9594977a0c7f9e51014649ae5 100644 --- a/src/components/Splash/SplashScreenError.spec.tsx +++ b/src/components/Splash/SplashScreenError.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import { InitStepsErrors } from 'models/initialisationSteps.model' import React from 'react' @@ -24,7 +24,9 @@ describe('SplashScreenError component', () => { }) it('should reload the page', async () => { render(<SplashScreenError error={InitStepsErrors.CONSENT_ERROR} />) - await userEvent.click(screen.getByRole('button')) + await act(async () => { + await userEvent.click(screen.getByRole('button')) + }) expect(window.location.reload).toHaveBeenCalled() }) }) diff --git a/src/components/Splash/__snapshots__/SplashRoot.spec.tsx.snap b/src/components/Splash/__snapshots__/SplashRoot.spec.tsx.snap index 4addaf3b692b89fdff97bc23f1eeef967277c2c7..fa4bb3de62203c74ec6b90ba02c140ece728fc6f 100644 --- a/src/components/Splash/__snapshots__/SplashRoot.spec.tsx.snap +++ b/src/components/Splash/__snapshots__/SplashRoot.spec.tsx.snap @@ -41,7 +41,7 @@ exports[`SplashRoot component should be rendered correctly 1`] = ` class="splash-logos-container" > <img - alt="ensemble de logos" + alt="Logo des financeurs : MĂ©tropole de Lyon, Etat via la Banque des Territoires et son programme France 2030, Union EuropĂ©enne" src="test-file-stub" /> </div> @@ -50,7 +50,7 @@ exports[`SplashRoot component should be rendered correctly 1`] = ` </div> `; -exports[`SplashRoot component should render the error screen and click on the reload button 1`] = ` +exports[`SplashRoot component should render the error screen 1`] = ` <div> <div class="splash-root" diff --git a/src/components/Splash/__snapshots__/SplashScreen.spec.tsx.snap b/src/components/Splash/__snapshots__/SplashScreen.spec.tsx.snap index ac994b7876cf9d83b80f0744e268852520b9c52b..312e5c68c2a2ccd1ba7f994716b8cfd3ce1a7788 100644 --- a/src/components/Splash/__snapshots__/SplashScreen.spec.tsx.snap +++ b/src/components/Splash/__snapshots__/SplashScreen.spec.tsx.snap @@ -37,7 +37,7 @@ exports[`SplashScreen component should be rendered correctly 1`] = ` class="splash-logos-container" > <img - alt="ensemble de logos" + alt="Logo des financeurs : MĂ©tropole de Lyon, Etat via la Banque des Territoires et son programme France 2030, Union EuropĂ©enne" src="test-file-stub" /> </div> diff --git a/src/components/Terms/TermsView.spec.tsx b/src/components/Terms/TermsView.spec.tsx index d5206d463f413f6f989b0453f49dc707c5a8e390..98d0aae76563e2cabe38caf2240088104e408396 100644 --- a/src/components/Terms/TermsView.spec.tsx +++ b/src/components/Terms/TermsView.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen, waitFor } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -31,31 +31,65 @@ describe('TermsView component', () => { jest.clearAllMocks() }) - it('should be rendered correctly', () => { + it('should be rendered correctly', async () => { const { container } = render( <Provider store={store}> <TermsView /> </Provider> ) + await waitFor(() => null, { container }) expect(container).toMatchSnapshot() }) it('should be unable to click "validate" button first then enable checkboxes and valid consent', async () => { mockCreateTerm.mockResolvedValueOnce(mockUpToDateTerm) - render( + const { container } = render( <Provider store={store}> <TermsView /> </Provider> ) + await waitFor(() => null, { container }) - const acceptButton = screen.getAllByRole('button')[2] + const acceptButton = screen.getByLabelText( + 'dataShare.accessibility.button_accept' + ) expect(acceptButton).toBeDisabled() expect(mockUpdateProfile).toHaveBeenCalledTimes(0) const [boxGCU, boxLegal] = screen.getAllByRole('checkbox') - await userEvent.click(boxGCU) - await userEvent.click(boxLegal) - await userEvent.click(acceptButton) + await act(async () => { + await userEvent.click(boxGCU) + await userEvent.click(boxLegal) + await userEvent.click(acceptButton) + }) expect(mockAppDispatch).toHaveBeenCalledTimes(3) }) + + it('should open CGU modal', async () => { + render( + <Provider store={store}> + <TermsView /> + </Provider> + ) + await act(async () => { + await userEvent.click( + screen.getByRole('button', { name: 'dataShare.validCGU_button' }) + ) + }) + expect(screen.getByRole('dialog')).toBeInTheDocument() + }) + + it('should open legal notice modal', async () => { + render( + <Provider store={store}> + <TermsView /> + </Provider> + ) + await act(async () => { + await userEvent.click( + screen.getByRole('button', { name: 'dataShare.validLegal_button' }) + ) + }) + expect(screen.getByRole('dialog')).toBeInTheDocument() + }) }) diff --git a/src/components/Terms/TermsView.tsx b/src/components/Terms/TermsView.tsx index 60c43630fc15d3c3c86ad8bd93c39da0da48ad09..4087319de1ea026307e2c0b567cdf5ea598b486c 100644 --- a/src/components/Terms/TermsView.tsx +++ b/src/components/Terms/TermsView.tsx @@ -18,27 +18,24 @@ const TermsView = () => { const navigate = useNavigate() const dispatch = useAppDispatch() const { termsStatus } = useAppSelector(state => state.ecolyo.global) - const [GCUValidation, setGCUValidation] = useState<boolean>(false) - const [dataConsentValidation, setDataConsentValidation] = - useState<boolean>(false) - const [openCGUModal, setOpenCGUModal] = useState<boolean>(false) - const [openLegalNoticeModal, setOpenLegalNoticeModal] = - useState<boolean>(false) + const [GCUValidation, setGCUValidation] = useState(false) + const [dataConsentValidation, setDataConsentValidation] = useState(false) + const [openCGUModal, setOpenCGUModal] = useState(false) + const [openLegalNoticeModal, setOpenLegalNoticeModal] = useState(false) const toggleCGUModal = () => { setOpenCGUModal(prev => !prev) } - const toggleLegalNoticeModal = () => { setOpenLegalNoticeModal(prev => !prev) } - const handleGCUValidate = useCallback(() => { + const handleGCUValidate = () => { setGCUValidation(prev => !prev) - }, []) - const handleDataConsentValidation = useCallback(() => { + } + const handleDataConsentValidation = () => { setDataConsentValidation(prev => !prev) - }, []) + } const handleTermValidate = useCallback(async () => { const termsService = new TermsService(client) diff --git a/src/components/WelcomeModal/WelcomeModal.spec.tsx b/src/components/WelcomeModal/WelcomeModal.spec.tsx index f94a2e6f05297e5a25babbea00a54a3422e67aae..0db03efefffaf7fdb00de4e109c994f91f473d99 100644 --- a/src/components/WelcomeModal/WelcomeModal.spec.tsx +++ b/src/components/WelcomeModal/WelcomeModal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' @@ -70,7 +70,11 @@ describe('WelcomeModal component', () => { ) }) it('should send mail and update profile when user click on the ok button', async () => { - await userEvent.click(screen.getAllByRole('button')[1]) + await act(async () => { + await userEvent.click( + screen.getByText('onboarding.welcomeModal.button_valid') + ) + }) expect(mockSendMail).toHaveBeenCalled() expect(updateProfileSpy).toHaveBeenCalledWith({ isFirstConnection: false, @@ -81,7 +85,13 @@ describe('WelcomeModal component', () => { }) it('should send mail and update profile when modal is closed by user', async () => { - await userEvent.click(screen.getAllByRole('button')[0]) + await act(async () => { + await userEvent.click( + screen.getAllByLabelText( + 'onboarding.welcomeModal.accessibility.button_valid' + )[0] + ) + }) expect(mockSendMail).toHaveBeenCalled() expect(updateProfileSpy).toHaveBeenCalledWith({ isFirstConnection: false, diff --git a/src/db/ecogestureData.json b/src/db/ecogestureData.json index 7e148d5614d73bce385b84431b8d8fd1aecf7b9f..e1a60f1990ca028bfe811fc83a713c1f5b3427a5 100644 --- a/src/db/ecogestureData.json +++ b/src/db/ecogestureData.json @@ -9,7 +9,7 @@ "impactLevel": 8, "efficiency": 4, "difficulty": 1, - "room": [0], + "room": ["ALL"], "season": "Hiver", "equipment": false, "equipmentType": [], @@ -32,7 +32,7 @@ "impactLevel": 8, "efficiency": 4, "difficulty": 1, - "room": [0], + "room": ["ALL"], "season": "EtĂ©", "equipment": true, "equipmentType": ["AIR_CONDITIONING"], @@ -55,7 +55,7 @@ "impactLevel": 8, "efficiency": 4, "difficulty": 1, - "room": [0], + "room": ["ALL"], "season": "Hiver", "equipment": false, "equipmentType": [], @@ -78,7 +78,7 @@ "impactLevel": 5, "efficiency": 2.5, "difficulty": 1, - "room": [1], + "room": ["BATHROOM"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -101,7 +101,7 @@ "impactLevel": 5, "efficiency": 2.5, "difficulty": 1, - "room": [1], + "room": ["BATHROOM"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -124,7 +124,7 @@ "impactLevel": 4, "efficiency": 2, "difficulty": 1, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": ["COMPUTER"], @@ -147,7 +147,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 1, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": true, "equipmentType": ["MICROWAVE"], @@ -170,7 +170,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 1, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -193,7 +193,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 1, - "room": [0], + "room": ["ALL"], "season": "Hiver", "equipment": false, "equipmentType": [], @@ -216,7 +216,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 1, - "room": [1, 2], + "room": ["BATHROOM", "KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -239,7 +239,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 1, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -262,7 +262,7 @@ "impactLevel": 2, "efficiency": 1, "difficulty": 1, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -285,7 +285,7 @@ "impactLevel": 2, "efficiency": 1, "difficulty": 1, - "room": [1, 2, 3], + "room": ["BATHROOM", "KITCHEN", "LAUNDRY"], "season": "Sans saison", "equipment": false, "equipmentType": ["WASHING_MACHINE", "DISHWASHER"], @@ -308,7 +308,7 @@ "impactLevel": 2, "efficiency": 1, "difficulty": 1, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": ["REFREGIRATOR"], @@ -331,7 +331,7 @@ "impactLevel": 2, "efficiency": 1, "difficulty": 1, - "room": [1, 2], + "room": ["BATHROOM", "KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -354,7 +354,7 @@ "impactLevel": 2, "efficiency": 1, "difficulty": 1, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -377,7 +377,7 @@ "impactLevel": 2, "efficiency": 1, "difficulty": 1, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -400,7 +400,7 @@ "impactLevel": 2, "efficiency": 1, "difficulty": 1, - "room": [1, 2, 3], + "room": ["BATHROOM", "KITCHEN", "LAUNDRY"], "season": "Sans saison", "equipment": false, "equipmentType": ["WASHING_MACHINE", "DISHWASHER"], @@ -423,7 +423,7 @@ "impactLevel": 2, "efficiency": 1, "difficulty": 1, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": ["COOKING_PLATES"], @@ -446,7 +446,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 1, - "room": [1, 2, 3], + "room": ["BATHROOM", "KITCHEN", "LAUNDRY"], "season": "Sans saison", "equipment": false, "equipmentType": ["WASHING_MACHINE", "DISHWASHER"], @@ -469,7 +469,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 1, - "room": [1, 2, 3], + "room": ["BATHROOM", "KITCHEN", "LAUNDRY"], "season": "Sans saison", "equipment": true, "equipmentType": ["DRYER"], @@ -492,7 +492,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 1, - "room": [1, 2, 3], + "room": ["BATHROOM", "KITCHEN", "LAUNDRY"], "season": "Sans saison", "equipment": false, "equipmentType": ["WASHING_MACHINE", "DISHWASHER"], @@ -515,7 +515,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 1, - "room": [1, 2, 3], + "room": ["BATHROOM", "KITCHEN", "LAUNDRY"], "season": "Sans saison", "equipment": true, "equipmentType": ["WASHING_MACHINE", "DISHWASHER"], @@ -538,7 +538,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 1, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": ["COOKING_PLATES"], @@ -561,7 +561,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 1, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -584,7 +584,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 1, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -607,7 +607,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 1, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -630,7 +630,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 1, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": ["REFREGIRATOR"], @@ -653,7 +653,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 1, - "room": [1, 2, 3], + "room": ["BATHROOM", "KITCHEN", "LAUNDRY"], "season": "Sans saison", "equipment": false, "equipmentType": ["WASHING_MACHINE"], @@ -676,7 +676,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 1, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": ["REFREGIRATOR"], @@ -699,7 +699,7 @@ "impactLevel": 10, "efficiency": 5, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Hiver", "equipment": false, "equipmentType": [], @@ -722,7 +722,7 @@ "impactLevel": 10, "efficiency": 5, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Hiver", "equipment": false, "equipmentType": [], @@ -745,7 +745,7 @@ "impactLevel": 8, "efficiency": 4, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -768,7 +768,7 @@ "impactLevel": 8, "efficiency": 4, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "EtĂ©", "equipment": true, "equipmentType": ["AIR_CONDITIONING", "FAN"], @@ -791,7 +791,7 @@ "impactLevel": 8, "efficiency": 4, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "EtĂ©", "equipment": true, "equipmentType": ["AIR_CONDITIONING"], @@ -814,7 +814,7 @@ "impactLevel": 8, "efficiency": 4, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Hiver", "equipment": false, "equipmentType": [], @@ -837,7 +837,7 @@ "impactLevel": 8, "efficiency": 4, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Hiver", "equipment": false, "equipmentType": [], @@ -860,7 +860,7 @@ "impactLevel": 8, "efficiency": 4, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Hiver", "equipment": false, "equipmentType": [], @@ -906,7 +906,7 @@ "impactLevel": 7, "efficiency": 3.5, "difficulty": 2, - "room": [1], + "room": ["BATHROOM"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -929,7 +929,7 @@ "impactLevel": 7, "efficiency": 3.5, "difficulty": 2, - "room": [1], + "room": ["BATHROOM"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -952,7 +952,7 @@ "impactLevel": 7, "efficiency": 3.5, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -975,7 +975,7 @@ "impactLevel": 7, "efficiency": 3.5, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "EtĂ©", "equipment": false, "equipmentType": [], @@ -998,7 +998,7 @@ "impactLevel": 6, "efficiency": 3, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1024,7 +1024,7 @@ "room": [], "season": "Sans saison", "equipment": true, - "equipmentType": [], + "equipmentType": ["OUTSIDE"], "equipmentInstallation": false, "investment": "Seau/ Bac de rĂ©cupĂ©ration", "action": false, @@ -1044,7 +1044,7 @@ "impactLevel": 6, "efficiency": 3, "difficulty": 2, - "room": [1, 2, 3], + "room": ["BATHROOM", "KITCHEN", "LAUNDRY"], "season": "Sans saison", "equipment": true, "equipmentType": ["BOILER"], @@ -1067,7 +1067,7 @@ "impactLevel": 4, "efficiency": 2, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": true, "equipmentType": ["CURTAIN"], @@ -1090,7 +1090,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": ["INTERNET_BOX"], @@ -1113,7 +1113,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 2, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": true, "equipmentType": ["MICROWAVE"], @@ -1136,7 +1136,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": true, "equipmentType": ["VENTILATION"], @@ -1159,7 +1159,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1182,7 +1182,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 2, - "room": [3], + "room": ["LAUNDRY"], "season": "Sans saison", "equipment": false, "equipmentType": ["DRYER"], @@ -1205,7 +1205,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1228,7 +1228,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1251,7 +1251,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 2, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": ["REFREGIRATOR", "FREEZER"], @@ -1274,7 +1274,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 2, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1297,7 +1297,7 @@ "impactLevel": 3, "efficiency": 1.5, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": true, "equipmentType": ["VENTILATION"], @@ -1320,7 +1320,7 @@ "impactLevel": 2, "efficiency": 1, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1343,7 +1343,7 @@ "impactLevel": 2, "efficiency": 1, "difficulty": 2, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1366,7 +1366,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 2, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": ["REFREGIRATOR", "FREEZER"], @@ -1389,7 +1389,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 2, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": ["REFREGIRATOR", "FREEZER"], @@ -1412,7 +1412,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 2, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": ["REFREGIRATOR"], @@ -1435,7 +1435,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 2, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": ["REFREGIRATOR", "FREEZER"], @@ -1458,7 +1458,7 @@ "impactLevel": 1, "efficiency": 0.5, "difficulty": 2, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1481,7 +1481,7 @@ "impactLevel": 8, "efficiency": 4, "difficulty": 3, - "room": [0], + "room": ["ALL"], "season": "Hiver", "equipment": false, "equipmentType": [], @@ -1504,7 +1504,7 @@ "impactLevel": 7, "efficiency": 3.5, "difficulty": 3, - "room": [1, 2, 3], + "room": ["BATHROOM", "KITCHEN", "LAUNDRY"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1527,7 +1527,7 @@ "impactLevel": 7, "efficiency": 3.5, "difficulty": 3, - "room": [1, 2, 3], + "room": ["BATHROOM", "KITCHEN", "LAUNDRY"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1550,7 +1550,7 @@ "impactLevel": 6, "efficiency": 3, "difficulty": 3, - "room": [1, 2, 3], + "room": ["BATHROOM", "KITCHEN", "LAUNDRY"], "season": "Sans saison", "equipment": true, "equipmentType": ["BOILER"], @@ -1573,7 +1573,7 @@ "impactLevel": 6, "efficiency": 3, "difficulty": 3, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1596,7 +1596,7 @@ "impactLevel": 6, "efficiency": 3, "difficulty": 3, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": true, "equipmentType": ["HYDRAULIC_HEATING"], @@ -1619,7 +1619,7 @@ "impactLevel": 6, "efficiency": 3, "difficulty": 3, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": true, "equipmentType": [], @@ -1642,7 +1642,7 @@ "impactLevel": 6, "efficiency": 3, "difficulty": 3, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": true, "equipmentType": ["HYDRAULIC_HEATING"], @@ -1665,7 +1665,7 @@ "impactLevel": 5, "efficiency": 2.5, "difficulty": 3, - "room": [0], + "room": ["ALL"], "season": "Sans saison", "equipment": true, "equipmentType": ["HYDRAULIC_HEATING"], @@ -1688,7 +1688,7 @@ "impactLevel": 5, "efficiency": 2.5, "difficulty": 3, - "room": [4], + "room": ["TOILET"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1711,7 +1711,7 @@ "impactLevel": 5, "efficiency": 2.5, "difficulty": 3, - "room": [1], + "room": ["BATHROOM"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1734,7 +1734,7 @@ "impactLevel": 2, "efficiency": 1, "difficulty": 3, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": ["REFREGIRATOR"], @@ -1757,7 +1757,7 @@ "impactLevel": 2, "efficiency": 1, "difficulty": 3, - "room": [3], + "room": ["LAUNDRY"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1773,14 +1773,14 @@ { "_id": "ECOGESTURE0078", "usage": 6, - "fluidTypes": [0,2], + "fluidTypes": [0, 2], "shortName": "Du tout cuit", "longName": "J'Ă©teins mon four avant la fin de la cuisson.", "longDescription": "Lorsque votre plat arrive en fin de cuisson, Ă©teignez le four. Il continuera de cuire grĂące Ă la chaleur gardĂ©e par le four. Vous pouvez Ă©conomiser jusqu'Ă 10 % d'Ă©lectricitĂ© en prenant cette habitude !", "impactLevel": 1, "efficiency": 2, "difficulty": 1, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1803,7 +1803,7 @@ "impactLevel": 2, "efficiency": 1, "difficulty": 1, - "room": [2,3], + "room": ["KITCHEN", "LAUNDRY"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1826,10 +1826,10 @@ "impactLevel": 5, "efficiency": 3, "difficulty": 2, - "room": [0], + "room": ["OUTSIDE"], "season": "EtĂ©", "equipment": false, - "equipmentType": [], + "equipmentType": ["OUTSIDE"], "equipmentInstallation": false, "investment": null, "action": false, @@ -1849,7 +1849,7 @@ "impactLevel": 3, "efficiency": 2, "difficulty": 2, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1872,10 +1872,10 @@ "impactLevel": 4, "efficiency": 1, "difficulty": 2, - "room": [0], + "room": ["OUTSIDE"], "season": "Sans saison", "equipment": false, - "equipmentType": [], + "equipmentType": ["OUTSIDE"], "equipmentInstallation": false, "investment": null, "action": false, @@ -1895,7 +1895,7 @@ "impactLevel": 3, "efficiency": 2, "difficulty": 2, - "room": [2], + "room": ["KITCHEN"], "season": "Sans saison", "equipment": false, "equipmentType": [], @@ -1911,14 +1911,14 @@ { "_id": "ECOGESTURE0084", "usage": 1, - "fluidTypes": [0,2], + "fluidTypes": [0, 2], "shortName": "DĂ©fense d'entrer", "longName": " Jâisole les coffrets des volets roulants.", "longDescription": "Un coffre de volets roulants mal isolĂ© engendre la formation de ponts thermiques, pouvant donc laisser l'air froid sâinfiltrer. Pour empĂȘcher cela, mieux vaut les isoler avec de la laine de roche ou de la mousse multicouche, par exemple.", "impactLevel": 7, "efficiency": 2, "difficulty": 3, - "room": [0], + "room": ["ALL"], "season": "Hiver", "equipment": false, "equipmentType": [], @@ -1934,14 +1934,14 @@ { "_id": "ECOGESTURE0085", "usage": 4, - "fluidTypes": [0,2], + "fluidTypes": [0, 2], "shortName": "Ballon au Chaud", "longName": "J'isole mon ballon d'eau chaude.", "longDescription": "Pour Ă©viter un maximum les pertes de chaleur, vous pouvez vous munir d'un matĂ©riau isolant, comme la laine de roche ou la mousse de polyurĂ©thane. Il vous suffira d'entourer votre ballon d'eau chaude avec celui-ci.", "impactLevel": 6, "efficiency": 3, "difficulty": 3, - "room": [2,3], + "room": ["KITCHEN", "LAUNDRY"], "season": "Sans saison", "equipment": true, "equipmentType": ["BOILER"], @@ -1964,10 +1964,10 @@ "impactLevel": 6, "efficiency": 3, "difficulty": 3, - "room": [0], + "room": ["OUTSIDE"], "season": "EtĂ©", "equipment": false, - "equipmentType": [], + "equipmentType": ["OUTSIDE"], "equipmentInstallation": true, "investment": "PluviomĂštre", "action": false, diff --git a/src/db/profileTypeData.json b/src/db/profileTypeData.json index beda4cf31df9df296467c0c40364b27506faf464..63b94873cec67f27366b8d0095e9dd892ac01630 100644 --- a/src/db/profileTypeData.json +++ b/src/db/profileTypeData.json @@ -12,7 +12,6 @@ "coldWater": "individual", "hotWater": "individual", "individualInsulationWork": ["window_replacement"], - "facilitiesInstallation": "none", "hotWaterEquipment": "solar", "hasInstalledVentilation": "unknown", "hasReplacedHeater": "unknown", diff --git a/src/doctypes/remote/org.ecolyo.avg-temperature.ts b/src/doctypes/remote/org.ecolyo.avg-temperature.ts new file mode 100644 index 0000000000000000000000000000000000000000..ff3c5e7a4365981943d9689e1bf5893bc05189ab --- /dev/null +++ b/src/doctypes/remote/org.ecolyo.avg-temperature.ts @@ -0,0 +1,2 @@ +export const REMOTE_ORG_ECOLYO_AVG_TEMPERATURE = + '/remote/org.ecolyo.avg-temperature' diff --git a/src/enums/ecogesture.enum.ts b/src/enums/ecogesture.enum.ts index c43e069b0065a9f0d1e09ddbcd0ce474c8efdda1..5bcaa0ab43bf4ea131053bbc51e9e70cef43a96d 100644 --- a/src/enums/ecogesture.enum.ts +++ b/src/enums/ecogesture.enum.ts @@ -9,11 +9,12 @@ export enum Usage { } export enum Room { - ALL = 0, - BATHROOM = 1, - KITCHEN = 2, - LAUNDRY = 3, - TOILET = 4, + ALL = 'ALL', + BATHROOM = 'BATHROOM', + KITCHEN = 'KITCHEN', + LAUNDRY = 'LAUNDRY', + TOILET = 'TOILET', + OUTSIDE = 'OUTSIDE', } export enum Season { @@ -38,6 +39,7 @@ export enum EquipmentType { FREEZER = 'FREEZER', BOILER = 'BOILER', HYDRAULIC_HEATING = 'HYDRAULIC_HEATING', + OUTSIDE = 'OUTSIDE', } export enum EcogestureTab { diff --git a/src/enums/fluid.enum.ts b/src/enums/fluid.enum.ts index 4f6608f2c6212950fcd55aaaf83679553a56cec5..e0d99c964422498efa7e84da07bccf184a746b84 100644 --- a/src/enums/fluid.enum.ts +++ b/src/enums/fluid.enum.ts @@ -6,10 +6,10 @@ export enum FluidType { } export enum FluidState { - KONNECTOR_NOT_FOUND = 0, - NOT_CONNECTED = 1, - DONE = 200, - ERROR = 300, - ERROR_LOGIN_FAILED = 301, - CHALLENGE_ASKED = 400, + KONNECTOR_NOT_FOUND = 'KONNECTOR_NOT_FOUND', + NOT_CONNECTED = 'NOT_CONNECTED', + DONE = 'DONE', + ERROR = 'ERROR', + LOGIN_FAILED = 'LOGIN_FAILED', + CHALLENGE_ASKED = 'CHALLENGE_ASKED', } diff --git a/src/enums/konnectorStatus.enum.ts b/src/enums/konnectorStatus.enum.ts index 4e8543316bd8de24d6cf05a1843e1e2a88371467..ca1975dae2695c73ce407198441602ff49f2b1f5 100644 --- a/src/enums/konnectorStatus.enum.ts +++ b/src/enums/konnectorStatus.enum.ts @@ -6,11 +6,11 @@ export enum KonnectorError { UNKNOWN_ERROR = 'UNKNOWN_ERROR', CRITICAL = 'exit status 1', MISSING_SECRET = "Cannot read property 'secret' of null", + USER_ACTION_NEEDED_ACCOUNT_REMOVED = 'USER_ACTION_NEEDED.ACCOUNT_REMOVED', } export enum KonnectorUpdate { ERROR_UPDATE = 'error_update', ERROR_UPDATE_OAUTH = 'error_update_oauth', LOGIN_FAILED = 'login_failed', - ERROR_CONSENT_FORM_GAS = 'error_consent_form_gas', } diff --git a/src/locales/fr.json b/src/locales/fr.json index 27bff7a7a136d240954126d5451898549a5c0456..32c7bb1ea53ea9abf52a61f0ab8c67995eacf43c 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -105,6 +105,18 @@ "title": "Comparateur", "month_tab": "Comparer au mois dernier", "year_tab": "Comparer Ă l'annĂ©e derniĂšre" + }, + "temperature_comparison": { + "unit": "°C", + "comparison": "par rapport Ă ", + "info_button": "Information sur l'indice mĂ©tĂ©o", + "modal": { + "title": "Indice mĂ©tĂ©o", + "month_comparison": "Ăcart de tempĂ©rature moyenne entre le mois observĂ© et le mois prĂ©cĂ©dent.", + "year_comparison": "Ăcart de tempĂ©rature moyenne entre le mois observĂ© et le mĂȘme mois de l'annĂ©e prĂ©cĂ©dente.", + "data_info": "DonnĂ©es MĂ©tĂ©o France issues de la station mĂ©tĂ©o Lyon Bron.", + "close": "Fermer la fenĂȘtre" + } } }, "analysis_error_modal": { @@ -141,15 +153,15 @@ "window_title": "electricity-info-modal", "button_close": "close-modal" }, - "title1": "Quâest-ce que la consommation minimum\u00a0?", - "text1-1": "Elle correspond Ă votre plus petite consommation du mois sur un crĂ©neau d'une demi-heure.", - "text1-2": "Nous extrapolons cette consommation sur 1 mois afin vous donner un aperçu de la consommation de vos consommations Ă©lectriques en veille (box, tĂ©lĂ©, chargeurs, ...) ou encore de celle, incompressible, de vos appareils de froid (frigo, congĂ©lateur).", - "title2": "Sur quelle base est calculĂ© mon ratio Heures Pleines / Heures Creuses\u00a0?", - "text2-1": "D'aprĂšs Enedis, vos plages d'heures creuses sont les suivantes : %{offPeakHours}. Nous avons donc simplement calculĂ©, Ă l'aide vos consommations Ă la demi-heure, quelle part de votre consommation est rĂ©alisĂ©e sur vos heures creuses.", - "title3": "Quâest-ce que la puissance maximum\u00a0?", - "text3-1": "Câest la puissance maximum dĂ©livrĂ©e par tous les appareils fonctionnant au mĂȘme moment dans votre logement.", - "text3-2": "Vous avez choisi une puissance maximum dans votre offre dâĂ©lectricitĂ© (3, 6 ou 9 kVA...) que vous ne devez pas dĂ©passer pour ne pas faire sauter votre compteur. ", - "text3-3": "Cette puissance varie d'un mois Ă l'autre, regardez cette valeur sur l'ensemble de l'annĂ©e pour vĂ©rifier si votre puissance souscrite correspond bien Ă votre usage." + "maxPowerTitle": "Quâest-ce que la puissance maximum\u00a0?", + "maxPowerDetails-1": "Câest la puissance maximum dĂ©livrĂ©e par tous les appareils fonctionnant au mĂȘme moment dans votre logement.", + "maxPowerDetails-2": "Vous avez choisi une puissance maximum dans votre offre dâĂ©lectricitĂ© (3, 6 ou 9 kVA...) que vous ne devez pas dĂ©passer pour ne pas faire sauter votre compteur. ", + "maxPowerDetails-3": "Cette puissance varie d'un mois Ă l'autre, regardez cette valeur sur l'ensemble de l'annĂ©e pour vĂ©rifier si votre puissance souscrite correspond bien Ă votre usage.", + "offPeakTitle": "Sur quelle base est calculĂ© mon ratio Heures Pleines / Heures Creuses\u00a0?", + "offPeakDetails-1": "D'aprĂšs Enedis, vos plages d'heures creuses sont les suivantes : %{offPeakHours}. Nous avons donc simplement calculĂ©, Ă l'aide vos consommations Ă la demi-heure, quelle part de votre consommation est rĂ©alisĂ©e sur vos heures creuses.", + "minPowerTitle": "Quâest-ce que la consommation minimum\u00a0?", + "minPowerDetails-1": "Elle correspond Ă votre plus petite consommation du mois sur un crĂ©neau d'une demi-heure.", + "minPowerDetails-2": "Nous extrapolons cette consommation sur 1 mois afin vous donner un aperçu de la consommation de vos consommations Ă©lectriques en veille (box, tĂ©lĂ©, chargeurs, ...) ou encore de celle, incompressible, de vos appareils de froid (frigo, congĂ©lateur)." }, "auth": { "enedissgegrandlyon": { @@ -205,7 +217,7 @@ "pceHint": "OĂč trouver le numĂ©ro de PCE\u00a0?", "pceModal": { "title": "OĂč trouver le n° de PCE\u00a0?", - "txt1": "Votre numĂ©ro PCE est inscrit en premiĂšre page de votre facture de gaz, Ă la rubrique <span>âvotre contrat dâĂ©nergieâ</span> ou <span>âvotre facture en dĂ©tailâ</span>.", + "txt1": "Votre numĂ©ro de <span>PCE</span> - ou <span>Point de comptage et d'estimation</span> - se trouve sur votre facture au niveau de vos informations contractuelles (parfois en 1Ăšre page... parfois en derniĂšre !)<br/><br/> C'est un identifiant Ă 14 chiffres.", "button": "J'ai compris", "accessibility": "Titre de la modale", "button-accessibility": "Bouton valider" @@ -217,7 +229,8 @@ "consentCheck1": "Je consens Ă partager les donnĂ©es personnelles ci-dessus pour une durĂ©e d'<span>un\u00a0an</span>", "consentCheck2": "Jâatteste ĂȘtre le titulaire du point de livraison (PCE) renseignĂ© Ă lâĂ©tape prĂ©cĂ©dente", "waiting": { - "mailSent": "Un mail va vous ĂȘtre envoyĂ© par GRDF sur lâadresse mail :<br><span>%{email}</span>", + "mailSent": "Un mail vous a Ă©tĂ© envoyĂ©...", + "mailDelay": "Patience, cela peut prendre jusqu'Ă 15 minutes", "validate": "Merci de valider l'autorisation d'accĂšs Ă vos donnĂ©es", "comeback": "Une fois ce clic effectuĂ©, revenez ici pour accĂ©der Ă vos donnĂ©es", "button_done": "Câest fait !" @@ -326,6 +339,7 @@ }, "partner_issue_modal": { "title": "Attention\u00a0!", + "accessibility_title": "Modale de maintenance partenaire", "error_connect_gaz": "La connexion Ă vos donnĂ©es de <span class='gaz'>gaz</span> est actuellement dysfonctionnelle (Maintenance chez notre partenaire <span class='gaz'>GRDF</span> ou dans notre service)", "error_connect_elec": "La connexion Ă vos donnĂ©es d'<span class='elec'>Ă©lectricitĂ©</span> est actuellement dysfonctionnelle (Maintenance chez notre partenaire <span class='elec'>Enedis</span> ou dans notre service)", "error_connect_water": "La connexion Ă vos donnĂ©es d'<span class='water'>eau</span> est actuellement dysfonctionnelle (Maintenance chez notre partenaire <span class='water'>Eau Publique du Grand Lyon</span> ou dans notre service)", @@ -561,7 +575,7 @@ "washing_machine": "Lave-linge", "dishwasher": "Lave-vaisselle", "cooking_plates": "Plaques Ă©lectriques", - "garden": "Jardin", + "outside": "ExtĂ©rieur", "dryer": "SĂšche-linge", "refregirator": "RĂ©frigĂ©rateur", "fan": "Ventilateur", @@ -765,7 +779,6 @@ "error_login_failed": "Identifiants invalides", "error_update": "Un problĂšme est survenu lors du rapatriement de vos donnĂ©es.", "error_update_oauth": "Votre autorisation pour afficher vos donnĂ©es %{fluid} a expirĂ©.", - "error_consent_form_gas": "Vos donnĂ©es ne peuvent ĂȘtre rĂ©cupĂ©rĂ©es car vous n'avez pas cochĂ© l'autorisation d'accĂšs aux donnĂ©es informatives lors de votre partage de consentement.", "button_oauth_reload": "Redonner mon consentement", "OK": "Ok", "konnector_delta": { @@ -820,18 +833,17 @@ "text2": "Reconfigurer mon connecteur\u00a0?", "text3": "La reconfiguration de votre connecteur passe par sa suppression et sa nouvelle installation. Vos donnĂ©es seront conservĂ©es." }, - "error_data_electricity": "Un problĂšme est survenu. Vos donnĂ©es de consommation dâĂ©lectricitĂ© ne seront pas chargĂ©es.", - "error_data_water": "Un problĂšme est survenu. Vos donnĂ©es de consommation dâeau ne seront pas chargĂ©es.", "error_credentials_water": "Une erreur s'est glissĂ©e dans vos identifiants de connexion. Veuillez vĂ©rifier ces Ă©lĂ©ments et tenter de vous reconnecter. L'identifiant est un numĂ©ro Ă 7 chiffres (diffĂ©rent de votre numĂ©ro de contrat).", "error_credentials_electricity": "Il semblerait que les nom(s) et adresse ne concordent pas avec le numĂ©ro de votre compteur.", "error_credentials_electricity_2": "Nous ne pouvons vous donner accĂšs aux donnĂ©es de consommation.", "error_credentials_update_water": "Une erreur s'est glissĂ©e dans vos identifiants de connexion. Veuillez vĂ©rifier ces Ă©lĂ©ments et tenter de vous reconnecter.", "error_credentials_update_electricity": "Un problĂšme a lieu lors de la rĂ©cupĂ©ration de vos donnĂ©es. Merci de supprimer votre connecteur et vous reconnecter.", "error_credentials_update_gas": "Un problĂšme a lieu lors de la rĂ©cupĂ©ration de vos donnĂ©es. Merci de supprimer votre connecteur et vous reconnecter.", - "error_data_gas": "Un problĂšme est survenu. Vos donnĂ©es de consommation de gaz ne seront pas chargĂ©es.", - "error_consent_form_gas_title": "Nous n'avons pas pu connecter vos donnĂ©es de consommation de gaz Ă Ecolyo.", - "error_consent_form_gas_content": "En effet, le partage de vos donnĂ©es de consommation de gaz \"informatives\" doit ĂȘtre acceptĂ©.", - "error_consent_form_gas_content_2": "Merci de cocher \"OUI\" au partage de vos donnĂ©es de consommation de gaz, et Ă \"Autoriser l'accĂšs Ă mes donnĂ©es informatives\".", + "error_consent_form_gas_title": "L'accĂšs Ă vos donnĂ©es a Ă©tĂ© bloquĂ© par GRDF suite Ă un consentement prĂ©cĂ©demment supprimĂ© par vos soins.", + "error_consent_form_gas_report": "Merci de nous signaler le problĂšme.", + "error_data_electricity": "Un problĂšme est survenu. Vos donnĂ©es de consommation dâĂ©lectricitĂ© ne seront pas chargĂ©es.", + "error_data_water": "Un problĂšme est survenu. Vos donnĂ©es de consommation dâeau ne seront pas chargĂ©es.", + "error_data_gas": "Il semblerait que le service de connexion Ă vos donnĂ©es de gaz soit momentanĂ©ment en panne.", "error_data_update_electricity": "Un problĂšme est survenu. Vos donnĂ©es de consommation dâĂ©lectricitĂ© nâont pas Ă©tĂ© mises Ă jour.", "error_data_update_water": "Un problĂšme est survenu. Vos donnĂ©es de consommation dâeau nâont pas Ă©tĂ© mises Ă jour.", "error_data_update_gas": "Un problĂšme est survenu. Vos donnĂ©es de consommation de gaz nâont pas Ă©tĂ© mises Ă jour.", @@ -839,9 +851,11 @@ "button_validate": "Ok", "button_understood": "J'ai compris", "button_try_again": "RĂ©essayer", + "button_contact": "Nous contacter", "button_check_info": "VĂ©rifier les infos", "button_go": "J'y vais", "button_later": "Plus tard", + "button_come_back_later": "Revenir plus tard", "show_common_error": "Voir les erreurs rĂ©currentes", "show_common_error_list": "<span>Le problĂšme peut provenir des cas suivants :</span><ul><li>Vous avez un co-titulaire sur votre contrat. Veillez Ă bien entrer le nom du <span class=\"gold\">titulaire du contrat</span> et non le co-titulaire.</li><li> Votre nom comporte un tiret\u00a0? Tentez sans le tiret.</li><li>Entrez bien le nom de votre commune de rĂ©sidence en entier (tirets et accents inclus)</li><li>Avez-vous bien entrĂ© le <span class=\"gold\">numĂ©ro de votre compteur</span> (PDL)\u00a0? Tout autre numĂ©ro (de contrat, de client) ne fonctionne pas.</li></ul><p>Si vous rencontrez toujours des difficultĂ©s, contactez notre service d'aide </p><div class=\"center\">Avez-vous pensez Ă vĂ©rifier ces informations\u00a0?</div>", "accessibility": { @@ -852,15 +866,15 @@ "consent_outdated": { "title": { "0": "Votre autorisation pour afficher vos donnĂ©es dâĂ©lectricitĂ© a expirĂ©", - "2": "Votre autorisation pour afficher vos donnĂ©es de gaz a expirĂ©" + "2": "Aie !" }, "text1": { "0": "Veuillez re-donner votre consentement pour la transmission et la reconnexion de vos donnĂ©es ENEDIS Ă Ecolyo.", - "2": "Veuillez re-donner votre accord pour que GRDF nous transmette vos donnĂ©es de consommation." + "2": "L'accĂšs Ă vos donnĂ©es de consommation de gaz a expirĂ©." }, "text2": { "0": "Souhaitez-vous renouveler votre accord dĂšs maintenant pour un an\u00a0?", - "2": "Voulez-vous donner votre accord sur votre compte GRDF maintenant\u00a0?" + "2": "Merci de redonner votre consentement pour y accĂ©der." }, "later": "Plus tard", "go": "J'y vais", diff --git a/src/migrations/migration.data.ts b/src/migrations/migration.data.ts index abaf259e49dc579d75feda279dceb962c988d873..650fce8de0a109e7405dd8dbf12e19fa152e8b0a 100644 --- a/src/migrations/migration.data.ts +++ b/src/migrations/migration.data.ts @@ -1,5 +1,6 @@ import { Client } from 'cozy-client' import { + ECOGESTURE_DOCTYPE, EGL_DAY_DOCTYPE, EGL_MONTH_DOCTYPE, EGL_YEAR_DOCTYPE, @@ -17,7 +18,14 @@ import { } from 'doctypes' import { UserQuizState } from 'enums' import { DateTime } from 'luxon' -import { DataloadEntity, Profile, ProfileType, UserChallenge } from 'models' +import { + DataloadEntity, + Ecogesture, + Profile, + ProfileType, + UserChallenge, +} from 'models' +import ecogestureData from '../db/ecogestureData.json' import { Migration } from './migration.type' export const SCHEMA_INITIAL_VERSION = 0 @@ -585,4 +593,26 @@ export const migrations: Migration[] = [ }) }, }, + { + baseSchemaVersion: 23, + targetSchemaVersion: 24, + appVersion: '3.0.0', + description: 'Add garden room & equipment type', + releaseNotes: null, + docTypes: ECOGESTURE_DOCTYPE, + run: async (_client: Client, ecogestures: Ecogesture[]) => { + return ecogestures.map(ecogesture => { + const ecData = ecogestureData.find( + ec => ec._id === ecogesture.id + ) as Ecogesture + + if (!ecData) return ecogesture + + ecogesture.room = ecData.room + ecogesture.equipmentType = ecData.equipmentType + + return ecogesture + }) + }, + }, ] diff --git a/src/models/account.model.ts b/src/models/account.model.ts index 73866654512b67c454c39157727466612dc235f3..bb125bcc9db89a4a5485e39a40b12188d19fd36e 100644 --- a/src/models/account.model.ts +++ b/src/models/account.model.ts @@ -1,4 +1,12 @@ -/* eslint-disable camelcase */ +export interface AccountAttributes { + account_type: string + auth?: AccountEGLData | AccountSgeData | AccountGRDFData + identifier?: string + state?: string | null + name?: string + data?: SgeAccountData +} + export interface Account extends AccountAttributes { _id: string id?: string @@ -12,17 +20,6 @@ export interface SgeAccountData { offPeakHours?: string } -export interface AccountAttributes { - account_type: string - auth?: AccountEGLData | AccountSgeData | AccountGRDFData - identifier?: string - state?: string | null - name?: string - data?: SgeAccountData - // TODO remove this ? - oauth_callback_results?: Record<string, any> -} - export interface AccountEGLData { login: string credentials_encrypted?: string diff --git a/src/models/avgTemperature.model.ts b/src/models/avgTemperature.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..74429edbb9071f6e7c0fefc7abde894a468c4ea7 --- /dev/null +++ b/src/models/avgTemperature.model.ts @@ -0,0 +1,13 @@ +export interface AvgTemperatureResult { + fields: string[] + layer_name: string + nb_results: number + table_href: string + values: AvgTemperatureMeasure[] +} + +interface AvgTemperatureMeasure { + month: string + average_measurement: number + identifiant: string +} diff --git a/src/models/chart.model.ts b/src/models/chart.model.ts index 96fa843eb8d5fb4f190d2e46a740ea4315f866d1..b0e24c8de78daa13a4f5c917ecb356f52fd8f64e 100644 --- a/src/models/chart.model.ts +++ b/src/models/chart.model.ts @@ -7,8 +7,9 @@ export interface ChartState { currentDatachartIndex: number currentIndex: number currentTimeStep: TimeStep - loading: boolean selectedDate: DateTime showCompare: boolean showOfflineData: boolean + /** For KonnectorViewerCard Accordion */ + showConnectionDetails: boolean } diff --git a/src/models/dju.model.ts b/src/models/dju.model.ts index f5f43d6484755320aa4ed6411aee6cf434626dc6..57b9fdc640f67e39c39a3db7b959350804ed397b 100644 --- a/src/models/dju.model.ts +++ b/src/models/dju.model.ts @@ -1,7 +1,7 @@ export interface DjuResult { fields: string[] layer_name: string - nb_result: number + nb_results: number table_href: string values: DjuMeasure[] } diff --git a/src/models/ecogesture.model.ts b/src/models/ecogesture.model.ts index d885795ce1c40b929c147f61ec196a1bdac7fc40..caf04939d8541bd71995fc22eaeb2e3ee8b6b86d 100644 --- a/src/models/ecogesture.model.ts +++ b/src/models/ecogesture.model.ts @@ -25,4 +25,6 @@ export interface Ecogesture { _id: string _rev?: string _type?: string + /** computed value with efficiency, difficulty and current season */ + score?: number } diff --git a/src/models/fluid.model.ts b/src/models/fluid.model.ts index a99af34eb156a60a32ba5ba763421c78c326b0c9..d8ba653c6723780a77904288d76998a1705b2c00 100644 --- a/src/models/fluid.model.ts +++ b/src/models/fluid.model.ts @@ -3,8 +3,19 @@ import { DateTime } from 'luxon' import { Account, Konnector, KonnectorConfig, Trigger } from 'models' import { TriggerState } from './trigger.model' +export interface FluidStatus { + fluidType: FluidType + status: FluidState + maintenance: boolean + firstDataDate: DateTime | null + lastDataDate: DateTime | null + connection: FluidConnection +} + export interface FluidConnection { + /** This toggles the launch of the konnector when set to true */ shouldLaunchKonnector: boolean + /** When enabled, alters the texts of Konnector Modal */ isUpdating: boolean konnector: Konnector | null account: Account | null @@ -12,11 +23,3 @@ export interface FluidConnection { triggerState: TriggerState | null konnectorConfig: KonnectorConfig } -export interface FluidStatus { - fluidType: FluidType - status: FluidState - maintenance: boolean - firstDataDate: DateTime | null - lastDataDate: DateTime | null - connection: FluidConnection -} diff --git a/src/models/global.model.ts b/src/models/global.model.ts index 8885c78cd72d5debe8c04da2c799feef04e131c2..d0542952106660c0f2531adef5293c390a2a96b6 100644 --- a/src/models/global.model.ts +++ b/src/models/global.model.ts @@ -20,4 +20,5 @@ export interface GlobalState { partnersInfo: PartnersInfo ecogestureFilter: Usage lastEpglLogin: string + headerHeight: number } diff --git a/src/models/index.ts b/src/models/index.ts index 99412717d6e8d8ea01c96ebeea6beb395ffc6cf5..e4f280922aadaceb2e89fe834fb0a4cc757d3bdc 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,6 +1,7 @@ export * from './account.model' export * from './action.model' export * from './analysis.model' +export * from './avgTemperature.model' export * from './challenge.model' export * from './chart.model' export * from './config.model' diff --git a/src/models/relation.model.ts b/src/models/relation.model.ts index 8cb5ea239e444cae73f19cc36c3e00fdab5f00ed..be75b5665bac3b963e9c97587dc9dbca286b931a 100644 --- a/src/models/relation.model.ts +++ b/src/models/relation.model.ts @@ -9,7 +9,3 @@ export interface RelationEntitiesObject { quizEntityRelation: Relation explorationEntityRelation: Relation[] } - -export interface GetRelationshipsReturn { - [relName: string]: Array<Relation> -} diff --git a/src/services/account.service.ts b/src/services/account.service.ts index 5692a60656eb186725d3d77575ef4c77890b349b..3f3c782df013c3a84d670bb751526369e09dd119 100644 --- a/src/services/account.service.ts +++ b/src/services/account.service.ts @@ -32,22 +32,11 @@ export default class AccountService { this._client = _client } - // TODO no need for this method - private buildAccountAttributes( - konnector: Konnector, - authData: AccountEGLData | AccountSgeData | AccountGRDFData - ): AccountAttributes { - return build(konnector, authData) - } - public async createAccount( konnector: Konnector, - accountAuthData: AccountEGLData | AccountSgeData | AccountGRDFData + authData: AccountEGLData | AccountSgeData | AccountGRDFData ): Promise<Account> { - const accountAttributes = this.buildAccountAttributes( - konnector, - accountAuthData - ) + const accountAttributes: AccountAttributes = build(konnector, authData) return createAccount(this._client, konnector, accountAttributes) } diff --git a/src/services/action.service.ts b/src/services/action.service.ts index cf27a1fa83e416d012f2a13b6184551d973c78dd..644197d670f148d9f95189f18b40fdf3e03d2ec6 100644 --- a/src/services/action.service.ts +++ b/src/services/action.service.ts @@ -29,8 +29,7 @@ export default class ActionService { public async getAvailableActionList(): Promise<Ecogesture[]> { const userChallenges: UserChallenge[] = await this._challengeService.getAllUserChallengeEntities() - const ecogestures: Ecogesture[] = - await this._ecogestureService.getAllEcogestures() + const ecogestures = await this._ecogestureService.getAllEcogestures() const actionsListIds: string[] = ecogestures .filter(ecogesture => ecogesture.action === true) .map(action => action._id) @@ -48,7 +47,7 @@ export default class ActionService { } }) } - const actionsList: Ecogesture[] = + const actionsList = await this._ecogestureService.getEcogesturesByIds(actionsListIds) return actionsList } @@ -67,7 +66,7 @@ export default class ActionService { public async keepListComplete( actionList: Ecogesture[] ): Promise<Ecogesture[]> { - const defaultActions: Ecogesture[] = await this.getDefaultActions() + const defaultActions = await this.getDefaultActions() let i = 0 while (actionList.length < 3) { actionList.push(defaultActions[i]) @@ -125,11 +124,11 @@ export default class ActionService { public async getCustomActions( connectedFluids: FluidType[] ): Promise<Ecogesture[]> { - const availableActions: Ecogesture[] = await this.getAvailableActionList() - const filteredByFluid: Ecogesture[] = availableActions.filter(action => + const availableActions = await this.getAvailableActionList() + const filteredByFluid = availableActions.filter(action => action.fluidTypes.some(fluid => connectedFluids.includes(fluid)) ) - let filteredBySeason: Ecogesture[] = this.filterBySeason(filteredByFluid) + let filteredBySeason = this.filterBySeason(filteredByFluid) if (filteredBySeason.length > 3) { filteredBySeason = filteredBySeason.slice(0, 3) @@ -137,7 +136,7 @@ export default class ActionService { filteredBySeason = await this.keepListComplete(filteredBySeason) } - const sortedByDifficultyAndEfficiency: Ecogesture[] = orderBy( + const sortedByDifficultyAndEfficiency = orderBy( filteredBySeason, [ action => { diff --git a/src/services/consumption.service.ts b/src/services/consumption.service.ts index b6565ff740072fd5396b760f8c84122651a26223..68551b7a800a16515f6ff630bf7b989f3b86ce8b 100644 --- a/src/services/consumption.service.ts +++ b/src/services/consumption.service.ts @@ -1,9 +1,13 @@ +import * as Sentry from '@sentry/react' import { Client, Q, QueryDefinition, QueryResult } from 'cozy-client' import { Doctype } from 'cozy-client/types/types' +import logger from 'cozy-logger' import { ENEDIS_MINUTE_DOCTYPE } from 'doctypes' +import { REMOTE_ORG_ECOLYO_AVG_TEMPERATURE } from 'doctypes/remote/org.ecolyo.avg-temperature' import { DataloadState, FluidType, TimeStep } from 'enums' import { DateTime } from 'luxon' import { + AvgTemperatureResult, Datachart, Dataload, DataloadEntity, @@ -17,12 +21,16 @@ import ConsumptionFormatterService from 'services/consumptionFormatter.service' import ConsumptionValidatorService from 'services/consumptionValidator.service' import ConverterService from 'services/converter.service' import QueryRunnerService from 'services/queryRunner.service' +import logApp from 'utils/logger' +import { formatTwoDigits } from 'utils/utils' interface ISingleFluidChartData { chartData: Datachart | null chartFluid: FluidType } +const logStack = logger.namespace('consumptionService') + export default class ConsumptionDataManager { private readonly _client: Client private readonly _consumptionFormatterService: ConsumptionFormatterService @@ -36,17 +44,7 @@ export default class ConsumptionDataManager { this._consumptionValidatorService = new ConsumptionValidatorService() } - /** - * Get graph data according on timeStep and fluidType - * @param timePeriod TimePeriod - * @param timeStep TimeStep - * @param fluidTypes FluidType[] - * @param fluidStatus FluidStatus[] - * @param compareTimePeriod - Optional TimePeriod - * @param isHome - Optional boolean - * @param isExport - Optional boolean - * @returns DataChart | null - */ + /** Get graph data according on timeStep and fluidType */ public async getGraphData( timePeriod: TimePeriod, timeStep: TimeStep, @@ -56,28 +54,24 @@ export default class ConsumptionDataManager { isHome?: boolean, isExport?: boolean ): Promise<Datachart | null> { - const InputisValid: boolean = - this._consumptionValidatorService.ValidateGetGraphData( + const InputIsValid = this._consumptionValidatorService.ValidateGetGraphData( + timePeriod, + timeStep, + fluidTypes, + compareTimePeriod, + isExport + ) + if (!InputIsValid) return null + if (fluidTypes.length === 1 && !isHome) { + const fluidType: FluidType = fluidTypes[0] + const fetchedData = await this.fetchSingleFluidGraphData( timePeriod, timeStep, - fluidTypes, - compareTimePeriod, - isExport + fluidType, + compareTimePeriod ) - if (!InputisValid) return null - if (fluidTypes.length === 1 && !isHome) { - const fluidType: FluidType = fluidTypes[0] - // running the query - const fetchedData: Datachart | null = - await this.fetchSingleFluidGraphData( - timePeriod, - timeStep, - fluidType, - compareTimePeriod - ) - // formatting data - const formattedData: Datachart | null = this.formatGraphDataManager( + const formattedData = this.formatGraphDataManager( fetchedData, timeStep, timePeriod, @@ -110,8 +104,7 @@ export default class ConsumptionDataManager { chartFluid: fluidType, }) } - const aggregatedData: Datachart | null = - this.aggregateGraphData(toBeAggregatedData) + const aggregatedData = this.aggregateGraphData(toBeAggregatedData) return aggregatedData } else return null } @@ -148,7 +141,7 @@ export default class ConsumptionDataManager { } } - // fetch last dataload available for a given fluid - return the daily data + /** get last dataload available for a given fluid - return the daily data */ public async getLastDataload( fluidTypes: FluidType ): Promise<Dataload[] | null> { @@ -452,7 +445,6 @@ export default class ConsumptionDataManager { private aggregateGraphData( singleFluidCharts: ISingleFluidChartData[] - // ,withComparison: boolean = true ): Datachart | null { if (singleFluidCharts[0]?.chartData) { const converterService = new ConverterService() @@ -580,11 +572,6 @@ export default class ConsumptionDataManager { return null } - /** - * getLastHourData - * @param {number} month number - * @returns {Promise<DataloadEntity[]>} usageEvent added - */ public async getLastHourData( client: Client, month: number @@ -641,6 +628,39 @@ export default class ConsumptionDataManager { return data.data[0] } + /** + * Try to fetch average temperature from remote doctype + * @returns {Promise<number | null>} avg-temperature + */ + public fetchAvgTemperature = async ( + year: number, + month: number + ): Promise<number | null> => { + const bronStationId = '69123002' + const avgTemperatureDate = `${year}-${formatTwoDigits(month)}` + try { + const result: AvgTemperatureResult = await this._client + .getStackClient() + .fetchJSON( + 'GET', + `${REMOTE_ORG_ECOLYO_AVG_TEMPERATURE}?identifiant=${bronStationId}&month=${avgTemperatureDate}` + ) + if (result && result.nb_results !== 0) { + return result.values[0].average_measurement + } else { + throw new Error( + `No average temperature found for ${avgTemperatureDate}` + ) + } + } catch (error) { + const errorMessage = `fetchAvgTemperature error : ${error}` + logStack('error', errorMessage) + logApp.error(errorMessage) + Sentry.captureException(error) + return null + } + } + /** * Save one doc * @param {DataloadEntity} consumptionDoc - Doc to save diff --git a/src/services/ecogesture.service.spec.ts b/src/services/ecogesture.service.spec.ts index 29518141e36a29ab38afeb3a6ed685cbefc5e3bd..d002f5f570f4570d153231970d9bd059ea4f9de7 100644 --- a/src/services/ecogesture.service.spec.ts +++ b/src/services/ecogesture.service.spec.ts @@ -1,6 +1,11 @@ import { QueryResult } from 'cozy-client' import ecogestureData from 'db/ecogestureData.json' -import { EquipmentType, IndividualOrCollective, WarmingType } from 'enums' +import { + EquipmentType, + IndividualOrCollective, + Season, + WarmingType, +} from 'enums' import { Ecogesture } from 'models' import { ProfileEcogesture } from 'models/profileEcogesture.model' import mockClient from 'tests/__mocks__/client.mock' @@ -10,9 +15,11 @@ import { ecogesturesECSData, ecogesturesHeatingData, mockedEcogesturesData, + mockedEcogesturesSortedData, } from 'tests/__mocks__/ecogesturesData.mock' import { mockProfileEcogesture } from 'tests/__mocks__/profileEcogesture.mock' import { getError } from 'tests/__mocks__/testUtils' +import * as dateUtils from 'utils/date' import { hashFile } from 'utils/hash' import EcogestureService from './ecogesture.service' @@ -33,6 +40,13 @@ const mockQueryResultMockedEcogestures: QueryResult<Ecogesture[]> = { skip: 0, } +const mockQueryResultMockedSortedEcogestures: QueryResult<Ecogesture[]> = { + data: mockedEcogesturesSortedData, + bookmark: '', + next: false, + skip: 0, +} + const mockQueryResultEmpty: QueryResult<Ecogesture[]> = { data: [], bookmark: '', @@ -44,9 +58,11 @@ describe('Ecogesture service', () => { const ecogestureService = new EcogestureService(mockClient) describe('getAllEcogestures', () => { it('should return all ecogestures', async () => { - mockClient.query.mockResolvedValueOnce(mockQueryResultMockedEcogestures) + mockClient.query.mockResolvedValueOnce( + mockQueryResultMockedSortedEcogestures + ) const result = await ecogestureService.getAllEcogestures() - expect(result).toEqual(mockedEcogesturesData) + expect(result).toEqual(mockedEcogesturesSortedData) }) it('should return empty array when no ecogestures stored', async () => { mockClient.query.mockResolvedValueOnce(mockQueryResultEmpty) @@ -181,22 +197,80 @@ describe('Ecogesture service', () => { expect(result.includes(BoilerEcogestureFalse[0])).toBeTruthy() }) }) - describe('getEcogestureListByProfile', () => { - it('should return ecogesture list according to profile ecogesture, sorted and filtered', async () => { - const mockProfileEcogestureFull: ProfileEcogesture = { - ...mockProfileEcogesture, - equipments: [EquipmentType.WASHING_MACHINE, EquipmentType.DISHWASHER], - } + describe('getEcogestureListByProfile', () => { + const mockProfileEcogestureFull: ProfileEcogesture = { + ...mockProfileEcogesture, + equipments: [ + EquipmentType.WASHING_MACHINE, + EquipmentType.DISHWASHER, + EquipmentType.AIR_CONDITIONING, + ], + } + it('should return ecogesture list according to profile ecogesture, sorted and filtered, for SUMMER season', async () => { mockClient.query.mockResolvedValueOnce(mockQueryResultMockedEcogestures) - + jest.spyOn(dateUtils, 'getCurrentSeason').mockReturnValue(Season.SUMMER) + const result = await ecogestureService.getEcogestureListByProfile( + mockProfileEcogestureFull + ) + expect(result[0]).toBe(mockedEcogesturesData[1]) + expect(result[1]).toBe(mockedEcogesturesData[2]) + expect(result[2]).toBe(mockedEcogesturesData[0]) + }) + it('should return ecogesture list according to profile ecogesture, sorted and filtered, for WINTER season', async () => { + mockClient.query.mockResolvedValueOnce(mockQueryResultMockedEcogestures) + jest.spyOn(dateUtils, 'getCurrentSeason').mockReturnValue(Season.WINTER) const result = await ecogestureService.getEcogestureListByProfile( mockProfileEcogestureFull ) - expect(result.length).toBe(2) expect(result[0]).toBe(mockedEcogesturesData[0]) + expect(result[1]).toBe(mockedEcogesturesData[2]) + expect(result[2]).toBe(mockedEcogesturesData[1]) + }) + it('should return ecogesture list according to profile ecogesture, sorted and filtered, for NO season', async () => { + mockClient.query.mockResolvedValueOnce(mockQueryResultMockedEcogestures) + jest.spyOn(dateUtils, 'getCurrentSeason').mockReturnValue(null) + const result = await ecogestureService.getEcogestureListByProfile( + mockProfileEcogestureFull + ) + expect(result[0]).toBe(mockedEcogesturesData[0]) + expect(result[1]).toBe(mockedEcogesturesData[1]) + expect(result[2]).toBe(mockedEcogesturesData[2]) }) }) + + describe('calculateScore', () => { + it('should return correct scores for each ecogesture for SUMMER season', () => { + jest.spyOn(dateUtils, 'getCurrentSeason').mockReturnValue(Season.SUMMER) + const scores = mockedEcogesturesData.map(ecogesture => + ecogestureService.calculateScore(ecogesture) + ) + expect(scores[0]).toBe(0) + expect(scores[1]).toBe(8) + expect(scores[2]).toBe(1) + }) + + it('should return correct scores for each ecogesture for WINTER season', () => { + jest.spyOn(dateUtils, 'getCurrentSeason').mockReturnValue(Season.WINTER) + const scores = mockedEcogesturesData.map(ecogesture => + ecogestureService.calculateScore(ecogesture) + ) + expect(scores[0]).toBe(8) + expect(scores[1]).toBe(0) + expect(scores[2]).toBe(1) + }) + + it('should return correct scores for each ecogesture for NO season', () => { + jest.spyOn(dateUtils, 'getCurrentSeason').mockReturnValue(null) + const scores = mockedEcogesturesData.map(ecogesture => + ecogestureService.calculateScore(ecogesture) + ) + expect(scores[0]).toBe(7) + expect(scores[1]).toBe(7) + expect(scores[2]).toBe(1) + }) + }) + describe('getEcogesturesByIds', () => { it('Should return corresponding ecogestures', async () => { const mockQueryResult: QueryResult<Ecogesture[]> = { diff --git a/src/services/ecogesture.service.ts b/src/services/ecogesture.service.ts index 4f56d58fdfa0a4052b71d124bb97f3c75621cb9a..819959ca2fe0ba3a17142f7c3541d203c1e959ab 100644 --- a/src/services/ecogesture.service.ts +++ b/src/services/ecogesture.service.ts @@ -12,6 +12,7 @@ import { } from 'enums' import { orderBy } from 'lodash' import { Ecogesture, ProfileEcogesture } from 'models' +import { getCurrentSeason, getOppositeSeason } from 'utils/date' import { logDuration } from 'utils/duration' import { hashFile } from 'utils/hash' import logApp from 'utils/logger' @@ -36,7 +37,7 @@ export default class EcogestureService { }> { const startTime = performance.now() const hashEcogestureType = hashFile(ecogestureData) - const ecogestures = await this.getAllEcogestures(undefined, true) + const ecogestures = await this.getAllEcogestures(true) if (!ecogestures || ecogestures?.length === 0) { // Populate data if none ecogesture exists @@ -115,18 +116,36 @@ export default class EcogestureService { } } - // TODO add default params - public async getAllEcogestures( - seasonFilter?: Season, - orderByID?: boolean - ): Promise<Ecogesture[]> { + /** + * Calculate Ecogesture score from efficiency and difficulty + * + * Base score = efficiency * 2 - difficulty + * - If current season, Score + 1 + * - If opposite season, Score = 0 + * - If no season, base score + */ + public calculateScore(ecogesture: Ecogesture): number { + const score = ecogesture.efficiency * 2 - ecogesture.difficulty + + const currentSeason = getCurrentSeason() + + if (ecogesture.season !== Season.NONE) { + if (ecogesture.season === currentSeason) { + return score + 1 + } else if ( + currentSeason && + ecogesture.season === getOppositeSeason(currentSeason) + ) { + return 0 + } + } + return score + } + + public async getAllEcogestures(orderByID?: boolean): Promise<Ecogesture[]> { let query: QueryDefinition = Q(ECOGESTURE_DOCTYPE) - if (seasonFilter && seasonFilter !== Season.NONE) { - query = query - .where({ season: { $ne: seasonFilter } }) - .indexFields(['season']) - .sortBy([{ season: 'desc' }]) - } else if (orderByID) { + + if (orderByID) { query = query .where({}) .indexFields(['_id']) @@ -141,17 +160,13 @@ export default class EcogestureService { const { data: ecogestures }: QueryResult<Ecogesture[]> = await this._client.query(query) - if (seasonFilter && seasonFilter !== Season.NONE) { - const { data: ecogesturesWithSeason }: QueryResult<Ecogesture[]> = - await this._client.query( - Q(ECOGESTURE_DOCTYPE) - .where({ season: { $eq: seasonFilter } }) - .indexFields(['season']) - .sortBy([{ season: 'asc' }]) - ) - return [...ecogesturesWithSeason, ...ecogestures] + for (const ecogesture of ecogestures) { + const score = this.calculateScore(ecogesture) + ecogesture.score = score } - return ecogestures + + const sortedByScoreDesc = orderBy(ecogestures, 'score', 'desc') + return sortedByScoreDesc } /** @@ -212,7 +227,7 @@ export default class EcogestureService { ecogestureList: Ecogesture[], profileEcogesture: ProfileEcogesture ): Ecogesture[] { - const filteredByUsage: Ecogesture[] = ecogestureList.filter(ecogesture => { + const filteredByUsage = ecogestureList.filter(ecogesture => { switch (ecogesture.usage) { case Usage.HEATING: if ( @@ -252,17 +267,14 @@ export default class EcogestureService { ecogestureList: Ecogesture[], profileEcogesture: ProfileEcogesture ): Ecogesture[] { - for (const ecogesture of ecogestureList) { - if (ecogesture.equipment === true) { - for (const equipmentType of ecogesture.equipmentType) { - if (!profileEcogesture.equipments.includes(equipmentType)) { - const index = ecogestureList.indexOf(ecogesture) - ecogestureList.splice(index, 1) - } - } + return ecogestureList.filter(ecogesture => { + if (ecogesture.equipmentType.length === 0) { + return true } - } - return ecogestureList + return ecogesture.equipmentType.some(type => + profileEcogesture.equipments.includes(type) + ) + }) } /** @@ -271,35 +283,33 @@ export default class EcogestureService { public async getEcogestureListByProfile( profileEcogesture: ProfileEcogesture ): Promise<Ecogesture[]> { - const ecogestureList: Ecogesture[] = await this.getAllEcogestures() - const filteredByUsage: Ecogesture[] = this.filterByUsage( + const ecogestureList = await this.getAllEcogestures() + + const filteredByUsage = this.filterByUsage( ecogestureList, profileEcogesture ) - const filteredByEquipment: Ecogesture[] = this.filterByEquipment( + + const filteredByEquipment = this.filterByEquipment( filteredByUsage, profileEcogesture ) - const filteredFlaggedEcogesture: Ecogesture[] = filteredByEquipment.filter( - (ecogesture: Ecogesture) => + + const filteredFlaggedEcogesture = filteredByEquipment.filter( + ecogesture => (ecogesture.objective === false && ecogesture.doing === false && ecogesture.viewedInSelection === false) || ecogesture.viewedInSelection === true ) - const sortedByDifficultyAndEfficiency: Ecogesture[] = orderBy( + + const sortedByScoreDesc = orderBy( filteredFlaggedEcogesture, - [ - ecogesture => { - return ecogesture.difficulty - }, - ecogesture => { - return ecogesture.efficiency - }, - ], - ['asc', 'desc'] + 'score', + 'desc' ) - return sortedByDifficultyAndEfficiency + + return sortedByScoreDesc } /** diff --git a/src/services/fluid.service.ts b/src/services/fluid.service.ts index 6803648785ed0e2a7b8a4257203199d45a193181..f22c17dc550c959396a9838018e09139865a5837 100644 --- a/src/services/fluid.service.ts +++ b/src/services/fluid.service.ts @@ -1,13 +1,6 @@ import { Client } from 'cozy-client' import { FluidState, FluidType } from 'enums' -import { - Account, - FluidStatus, - Konnector, - PartnersInfo, - Trigger, - TriggerState, -} from 'models' +import { FluidStatus, Konnector, PartnersInfo, TriggerState } from 'models' import AccountService from 'services/account.service' import ConsumptionService from 'services/consumption.service' import ConfigService from 'services/fluidConfig.service' @@ -26,16 +19,20 @@ export default class FluidService { konnector: Konnector | null, state: TriggerState | null ): FluidState => { - console.log('đ ~ FluidService ~ state:', state) if (!konnector) return FluidState.KONNECTOR_NOT_FOUND if (!state) return FluidState.NOT_CONNECTED switch (state.status) { case 'done': return FluidState.DONE case 'errored': - if (state?.last_error === 'LOGIN_FAILED') - return FluidState.ERROR_LOGIN_FAILED - else return FluidState.ERROR + if (state?.last_error === 'LOGIN_FAILED') { + return FluidState.LOGIN_FAILED + } + if (state?.last_error === 'CHALLENGE_ASKED') { + return FluidState.CHALLENGE_ASKED + } + return FluidState.ERROR + default: return FluidState.NOT_CONNECTED } @@ -59,52 +56,47 @@ export default class FluidService { ): Promise<FluidStatus[]> => { const fluidConfig = new ConfigService().getFluidConfig() const accountService = new AccountService(this._client) - const [elecAccount, waterAccount, gasAccount]: (Account | null)[] = - await Promise.all([ - accountService.getAccountByType( - fluidConfig[FluidType.ELECTRICITY].konnectorConfig.slug - ), - accountService.getAccountByType( - fluidConfig[FluidType.WATER].konnectorConfig.slug - ), - accountService.getAccountByType( - fluidConfig[FluidType.GAS].konnectorConfig.slug - ), - ]) + const [elecAccount, waterAccount, gasAccount] = await Promise.all([ + accountService.getAccountByType( + fluidConfig[FluidType.ELECTRICITY].konnectorConfig.slug + ), + accountService.getAccountByType( + fluidConfig[FluidType.WATER].konnectorConfig.slug + ), + accountService.getAccountByType( + fluidConfig[FluidType.GAS].konnectorConfig.slug + ), + ]) const konnectorService = new KonnectorService(this._client) - const [elecKonnector, waterKonnector, gasKonnector]: (Konnector | null)[] = - await Promise.all([ - konnectorService.getKonnector( - fluidConfig[FluidType.ELECTRICITY].konnectorConfig.slug - ), - konnectorService.getKonnector( - fluidConfig[FluidType.WATER].konnectorConfig.slug - ), - konnectorService.getKonnector( - fluidConfig[FluidType.GAS].konnectorConfig.slug - ), - ]) + const [elecKonnector, waterKonnector, gasKonnector] = await Promise.all([ + konnectorService.getKonnector( + fluidConfig[FluidType.ELECTRICITY].konnectorConfig.slug + ), + konnectorService.getKonnector( + fluidConfig[FluidType.WATER].konnectorConfig.slug + ), + konnectorService.getKonnector( + fluidConfig[FluidType.GAS].konnectorConfig.slug + ), + ]) const triggerService = new TriggerService(this._client) - const [elecTrigger, waterTrigger, gasTrigger]: (Trigger | null)[] = - await Promise.all([ - elecAccount && elecKonnector - ? triggerService.getTrigger(elecAccount, elecKonnector) - : null, - waterAccount && waterKonnector - ? triggerService.getTrigger(waterAccount, waterKonnector) - : null, - gasAccount && gasKonnector - ? triggerService.getTrigger(gasAccount, gasKonnector) - : null, - ]) + const [elecTrigger, waterTrigger, gasTrigger] = await Promise.all([ + elecAccount && elecKonnector + ? triggerService.getTrigger(elecAccount, elecKonnector) + : null, + waterAccount && waterKonnector + ? triggerService.getTrigger(waterAccount, waterKonnector) + : null, + gasAccount && gasKonnector + ? triggerService.getTrigger(gasAccount, gasKonnector) + : null, + ]) const consumptionService = new ConsumptionService(this._client) - const [elecStatus, waterStatus, gasStatus]: (TriggerState | null)[] = - await Promise.all([ - elecTrigger ? triggerService.fetchTriggerState(elecTrigger) : null, - waterTrigger ? triggerService.fetchTriggerState(waterTrigger) : null, - gasTrigger ? triggerService.fetchTriggerState(gasTrigger) : null, - ]) - console.log('đ ~ FluidService ~ gasStatus:', gasStatus) + const [elecStatus, waterStatus, gasStatus] = await Promise.all([ + elecTrigger ? triggerService.fetchTriggerState(elecTrigger) : null, + waterTrigger ? triggerService.fetchTriggerState(waterTrigger) : null, + gasTrigger ? triggerService.fetchTriggerState(gasTrigger) : null, + ]) const firstDataDates = await consumptionService.fetchAllFirstDateData(allFluids) const lastDataDates = diff --git a/src/services/profileType.service.spec.ts b/src/services/profileType.service.spec.ts index 3c52e9a655addf1f419ac934f4a33e1436802060..cda9941ab6337c5a53250d37ab43b00e0bc96858 100644 --- a/src/services/profileType.service.spec.ts +++ b/src/services/profileType.service.spec.ts @@ -533,11 +533,10 @@ describe('ProfileType service', () => { DateTime.fromISO('2021-01-01T00:00:00.000Z', { zone: 'utc' }) ) - const expectedResult = { + const expectedResult: ProfileType = { ...mockProfile, warmingFluid: null, individualInsulationWork: [], - facilitiesInstallation: [], hotWaterFluid: null, hotWaterEquipment: HotWaterEquipment.OTHER, } @@ -551,7 +550,7 @@ describe('ProfileType service', () => { const mockDjuResult: DjuResult = { fields: [], layer_name: '', - nb_result: 1, + nb_results: 1, table_href: '', values: [ { @@ -569,7 +568,7 @@ describe('ProfileType service', () => { const mockDjuResult: DjuResult = { fields: [], layer_name: '', - nb_result: 0, + nb_results: 0, table_href: '', values: [], } diff --git a/src/services/profileType.service.ts b/src/services/profileType.service.ts index 814a340ed146f2527c8dad96544b56c2742961f0..8bd19f3633b65a0d7f283efe4311c439791f1fab 100644 --- a/src/services/profileType.service.ts +++ b/src/services/profileType.service.ts @@ -451,7 +451,7 @@ export default class ProfileTypeService { const result: DjuResult = await this._client .getStackClient() .fetchJSON('GET', `${REMOTE_ORG_ECOLYO_DJU}?month=${djuDate}`) - if (result && result.nb_result !== 0) { + if (result && result.nb_results !== 0) { return result.values[0].average_measurement } else { return heatingData.dju_average_by_month[month - 1] @@ -479,7 +479,6 @@ export default class ProfileTypeService { if (profileType.heating === IndividualOrCollective.COLLECTIVE) { profileType.warmingFluid = null profileType.individualInsulationWork = [] - profileType.facilitiesInstallation = [] } if (profileType.hotWater === IndividualOrCollective.COLLECTIVE) { profileType.hotWaterFluid = null diff --git a/src/services/queryRunner.service.spec.ts b/src/services/queryRunner.service.spec.ts index 656180de012107559449b5f0b7a78ed61419aa5a..bf9dc310c125935eb9266ce5bdd1094edff7414a 100644 --- a/src/services/queryRunner.service.spec.ts +++ b/src/services/queryRunner.service.spec.ts @@ -1132,12 +1132,11 @@ describe('queryRunner service', () => { skip: 0, } mockClient.query.mockResolvedValue(mockQueryResult) - const result: number | Dataload | null = - await queryRunner.fetchFluidMaxData( - mockTimePeriod, - TimeStep.DAY, - FluidType.ELECTRICITY - ) + const result = await queryRunner.fetchFluidMaxData( + mockTimePeriod, + TimeStep.DAY, + FluidType.ELECTRICITY + ) expect(result).toBe(30.33) }) @@ -1165,12 +1164,11 @@ describe('queryRunner service', () => { mockClient.query .mockResolvedValueOnce(mockQueryResult) .mockResolvedValueOnce(mockQueryResult2) - const result: number | Dataload | null = - await queryRunner.fetchFluidMaxData( - mockTimePeriod, - TimeStep.HALF_AN_HOUR, - FluidType.ELECTRICITY - ) + const result = await queryRunner.fetchFluidMaxData( + mockTimePeriod, + TimeStep.HALF_AN_HOUR, + FluidType.ELECTRICITY + ) expect(result).toBe(7.82) }) @@ -1198,12 +1196,11 @@ describe('queryRunner service', () => { mockClient.query .mockResolvedValueOnce(mockQueryResult) .mockResolvedValueOnce(mockQueryResult2) - const result: number | Dataload | null = - await queryRunner.fetchFluidMaxData( - mockTimePeriod, - TimeStep.HALF_AN_HOUR, - FluidType.ELECTRICITY - ) + const result = await queryRunner.fetchFluidMaxData( + mockTimePeriod, + TimeStep.HALF_AN_HOUR, + FluidType.ELECTRICITY + ) expect(result).toBe(0) }) @@ -1217,12 +1214,11 @@ describe('queryRunner service', () => { }), } mockClient.query.mockRejectedValue(new Error()) - const result: number | Dataload | null = - await queryRunner.fetchFluidMaxData( - mockTimePeriod, - TimeStep.DAY, - FluidType.ELECTRICITY - ) + const result = await queryRunner.fetchFluidMaxData( + mockTimePeriod, + TimeStep.DAY, + FluidType.ELECTRICITY + ) expect(result).toBeNull() }) @@ -1235,12 +1231,11 @@ describe('queryRunner service', () => { zone: 'utc', }), } - const result: number | Dataload | null = - await queryRunner.fetchFluidMaxData( - mockTimePeriod, - TimeStep.DAY, - unknownFluidType - ) + const result = await queryRunner.fetchFluidMaxData( + mockTimePeriod, + TimeStep.DAY, + unknownFluidType + ) expect(result).toBeNull() }) @@ -1253,12 +1248,11 @@ describe('queryRunner service', () => { zone: 'utc', }), } - const result: number | Dataload | null = - await queryRunner.fetchFluidMaxData( - mockTimePeriod, - unknownTimeStep, - FluidType.ELECTRICITY - ) + const result = await queryRunner.fetchFluidMaxData( + mockTimePeriod, + unknownTimeStep, + FluidType.ELECTRICITY + ) expect(result).toBeNull() }) }) diff --git a/src/services/quiz.service.ts b/src/services/quiz.service.ts index ffc47a65192bb41959922668371673eac095e95e..26271500527efae3b6cb4a890b3983a3673a7db7 100644 --- a/src/services/quiz.service.ts +++ b/src/services/quiz.service.ts @@ -260,7 +260,7 @@ export default class QuizService { */ public async getCustomQuestion( customQuestionEntity: CustomQuestionEntity, - fluidType: FluidType[] + fluidTypes: FluidType[] ): Promise<QuestionEntity> { let answers: Answer[] const explanation = @@ -269,22 +269,22 @@ export default class QuizService { customQuestionEntity.interval, customQuestionEntity.period.weekday ? {} : customQuestionEntity.period ) - let useFluidType: FluidType[] = fluidType + let useFluidTypes: FluidType[] = fluidTypes let questionLabel: string = customQuestionEntity.questionLabel let unit = 'âŹ' if (customQuestionEntity.singleFluid === true) { let unitLabel = 'kWh' let fluidLabel = "d'Ă©lectricitĂ©" // Define the right fluidType - if (fluidType.includes(FluidType.ELECTRICITY)) { - useFluidType = [FluidType.ELECTRICITY] + if (fluidTypes.includes(FluidType.ELECTRICITY)) { + useFluidTypes = [FluidType.ELECTRICITY] unit = 'kWh' - } else if (fluidType.includes(FluidType.GAS)) { - useFluidType = [FluidType.GAS] + } else if (fluidTypes.includes(FluidType.GAS)) { + useFluidTypes = [FluidType.GAS] unit = 'kWh' fluidLabel = 'de gaz' } else { - useFluidType = [FluidType.WATER] + useFluidTypes = [FluidType.WATER] unit = 'L' unitLabel = 'litre' fluidLabel = "d'eau" @@ -295,12 +295,11 @@ export default class QuizService { } if (customQuestionEntity.type === CustomQuestionType.DATE) { // Interval - const intervalAsnwer: IntervalAnswer = - await this.getMaxLoadOnLastInterval( - customQuestionEntity.timeStep, - finalInterval, - useFluidType - ) + const intervalAsnwer = await this.getMaxLoadOnLastInterval( + customQuestionEntity.timeStep, + finalInterval, + useFluidTypes + ) answers = this.getAnswersForInterval( intervalAsnwer.date, customQuestionEntity.timeStep, @@ -312,7 +311,7 @@ export default class QuizService { let maxLoad = await consumptionService.getMaxLoad( finalInterval, customQuestionEntity.timeStep, - useFluidType, + useFluidTypes, undefined, !customQuestionEntity.singleFluid ) @@ -320,10 +319,10 @@ export default class QuizService { answers = this.getAnswersForNumberValue(maxLoad as number, unit) } else { // average - const averageLoad: number = await this.getAverageOnGivenPeriod( + const averageLoad = await this.getAverageOnGivenPeriod( customQuestionEntity.timeStep, finalInterval, - useFluidType, + useFluidTypes, customQuestionEntity.period.weekday ? customQuestionEntity.period.weekday : undefined, diff --git a/src/store/chart/chart.slice.spec.ts b/src/store/chart/chart.slice.spec.ts index 9015e59e329b09ed494600f00def39203bac9ad5..02f68b6399afa66dbb82ff070c10dc8d082dbdb1 100644 --- a/src/store/chart/chart.slice.spec.ts +++ b/src/store/chart/chart.slice.spec.ts @@ -8,9 +8,9 @@ import { setCurrentDataChartIndex, setCurrentIndex, setCurrentTimeStep, - setLoading, setSelectedDate, setShowCompare, + setShowConnectionDetails, } from './chart.slice' describe('chart reducer', () => { @@ -100,22 +100,25 @@ describe('chart reducer', () => { }) }) - describe('setLoading', () => { - it('should handle setLoading with payload', () => { - const state = chartSlice.reducer(mockChartState, setLoading(false)) + describe('setShowCompare', () => { + it('should handle setShowCompare', () => { + const state = chartSlice.reducer(mockChartState, setShowCompare(true)) expect(state).toEqual({ ...mockChartState, - loading: false, + showCompare: true, }) }) }) - describe('setShowCompare', () => { - it('should handle setShowCompare', () => { - const state = chartSlice.reducer(mockChartState, setShowCompare(true)) + describe('setShowConnectionDetails', () => { + it('should handle setShowConnectionDetails', () => { + const state = chartSlice.reducer( + mockChartState, + setShowConnectionDetails(true) + ) expect(state).toEqual({ ...mockChartState, - showCompare: true, + showConnectionDetails: true, }) }) }) diff --git a/src/store/chart/chart.slice.ts b/src/store/chart/chart.slice.ts index 60f4790d601c1edbfcf6bf9b35e61af23320d940..558e752a338575e15d2b4fa6896f8845330f975f 100644 --- a/src/store/chart/chart.slice.ts +++ b/src/store/chart/chart.slice.ts @@ -11,9 +11,9 @@ const initialState: ChartState = { currentIndex: 0, currentDatachart: { actualData: [], comparisonData: null }, currentDatachartIndex: 0, - loading: true, showCompare: false, showOfflineData: false, + showConnectionDetails: false, } export const chartSlice = createSlice({ @@ -35,9 +35,6 @@ export const chartSlice = createSlice({ state.showCompare = false } }, - setLoading: (state, action: PayloadAction<boolean>) => { - state.loading = action.payload - }, setSelectedDate: (state, action: PayloadAction<DateTime>) => { state.selectedDate = action.payload }, @@ -47,6 +44,9 @@ export const chartSlice = createSlice({ setShowOfflineData: (state, action: PayloadAction<boolean>) => { state.showOfflineData = action.payload }, + setShowConnectionDetails: (state, action: PayloadAction<boolean>) => { + state.showConnectionDetails = action.payload + }, }, }) @@ -55,8 +55,8 @@ export const { setCurrentDataChartIndex, setCurrentIndex, setCurrentTimeStep, - setLoading, setSelectedDate, setShowCompare, setShowOfflineData, + setShowConnectionDetails, } = chartSlice.actions diff --git a/src/store/global/global.slice.spec.ts b/src/store/global/global.slice.spec.ts index 922f49c5768bb2fdab9e01738d6b75995eb6f5d2..3df92c909f3204595518c0ae66e6a7d913b0e440 100644 --- a/src/store/global/global.slice.spec.ts +++ b/src/store/global/global.slice.spec.ts @@ -11,6 +11,7 @@ import { changeScreenType, globalSlice, setFluidStatus, + setHeaderHeight, setLastEpglLogin, setPartnersInfo, setShouldRefreshConsent, @@ -113,6 +114,13 @@ describe('globalSlice', () => { screenType: ScreenType.DESKTOP, }) }) + it('should handle setHeaderHeight', () => { + const state = globalSlice.reducer(mockGlobalState, setHeaderHeight(100)) + expect(state).toEqual({ + ...mockGlobalState, + headerHeight: 100, + }) + }) it('should handle toggleChallengeExplorationNotification', () => { const state = globalSlice.reducer( mockGlobalState, @@ -276,7 +284,7 @@ describe('globalSlice', () => { firstDataDate: null, lastDataDate: null, maintenance: false, - status: 0, + status: FluidState.KONNECTOR_NOT_FOUND, connection: { shouldLaunchKonnector: true, isUpdating: true, diff --git a/src/store/global/global.slice.ts b/src/store/global/global.slice.ts index f251ba453ab9d70ae6008aa7156ba6695c49aa98..f93aada9ec5f5ef8d0d77b340694d312f902b06b 100644 --- a/src/store/global/global.slice.ts +++ b/src/store/global/global.slice.ts @@ -12,6 +12,7 @@ import { } from 'models' const initialState: GlobalState = { + headerHeight: 62, screenType: ScreenType.MOBILE, releaseNotes: { show: false, @@ -138,9 +139,9 @@ const getFluidTypesFromStatus = (fluidStatus: FluidStatus[]): FluidType[] => { if ( (fluid.status !== FluidState.KONNECTOR_NOT_FOUND && fluid.status !== FluidState.NOT_CONNECTED && - fluid.status !== FluidState.ERROR_LOGIN_FAILED) || + fluid.status !== FluidState.LOGIN_FAILED) || // Handle Login Error case for oauth konnectors - (fluid.status === FluidState.ERROR_LOGIN_FAILED && + (fluid.status === FluidState.LOGIN_FAILED && fluid.fluidType !== FluidType.WATER) ) { fluidTypes.push(fluid.fluidType) @@ -153,6 +154,9 @@ export const globalSlice = createSlice({ name: 'global', initialState, reducers: { + setHeaderHeight: (state, action: PayloadAction<number>) => { + state.headerHeight = action.payload + }, changeScreenType: (state, action: PayloadAction<ScreenType>) => { state.screenType = action.payload }, @@ -200,6 +204,7 @@ export const globalSlice = createSlice({ ) => { state.fluidStatus[fluidType].connection = fluidConnection }, + /** Restore last known credentials */ setLastEpglLogin: (state, action: PayloadAction<string>) => { state.lastEpglLogin = action.payload }, @@ -213,6 +218,7 @@ export const globalSlice = createSlice({ }) export const { + setHeaderHeight, changeScreenType, setFluidStatus, setLastEpglLogin, diff --git a/src/styles/base/_typo-variables.scss b/src/styles/base/_typo-variables.scss index 3e357c1bf88b39ad0cd8b334a1635c84dd5675fa..18bf2b8ee347f14e2d64b3c487376b0eca5779ca 100644 --- a/src/styles/base/_typo-variables.scss +++ b/src/styles/base/_typo-variables.scss @@ -1,5 +1,18 @@ $text-font: Lato, sans-serif; -$text-size: '10' 0.625rem, '13' 0.8125rem, '14' 0.875rem, '15' 0.938rem, - '16' 1rem, '18' 1.125rem, '19' 1.188rem, '20' 1.25rem, '21' 1.313rem, - '22' 1.375rem, '24' 1.5rem, '26' 1.625rem, '28' 1.75rem, '36' 2.25rem; +$text-size: + '10' 0.625rem, + '12' 0.75rem, + '13' 0.8125rem, + '14' 0.875rem, + '15' 0.938rem, + '16' 1rem, + '18' 1.125rem, + '19' 1.188rem, + '20' 1.25rem, + '21' 1.313rem, + '22' 1.375rem, + '24' 1.5rem, + '26' 1.625rem, + '28' 1.75rem, + '36' 2.25rem; diff --git a/src/styles/components/_barchart.scss b/src/styles/components/_barchart.scss index 209e00d3bfa361fa0a0decfb44d9acf7a072bcb3..16fdf31cd70c22b952979b857ebfb9a3cb0b342d 100644 --- a/src/styles/components/_barchart.scss +++ b/src/styles/components/_barchart.scss @@ -127,7 +127,7 @@ } } } -.bar-UNCOMING { +.bar-UPCOMING { fill: $grey-dark; opacity: 0.6; &.selected { diff --git a/src/targets/services/aggregatorUsageEvents.ts b/src/targets/services/aggregatorUsageEvents.ts index f54542bdd4b6ab97fbc94bc198e6fcafc696c819..36beffe25a1f122a126fba16ff2bdab6f2dd8120 100644 --- a/src/targets/services/aggregatorUsageEvents.ts +++ b/src/targets/services/aggregatorUsageEvents.ts @@ -11,7 +11,7 @@ import { } from 'enums' import { uniq } from 'lodash' import { DateTime } from 'luxon' -import { FluidStatus, PerformanceIndicator, UsageEvent } from 'models' +import { PerformanceIndicator, UsageEvent } from 'models' import ConsumptionService from 'services/consumption.service' import EnvironmentService from 'services/environment.service' import FluidService from 'services/fluid.service' @@ -19,7 +19,7 @@ import ProfileService from 'services/profile.service' import ProfileTypeEntityService from 'services/profileTypeEntity.service' import TermsService from 'services/terms.service' import UsageEventService from 'services/usageEvent.service' -import { getFluidType } from 'utils/utils' +import { getFluidName } from 'utils/utils' import { runService } from './service' const logStack = logger.namespace('aggregatorUsageEvents') @@ -121,8 +121,8 @@ const getConnectedKonnectorSlug = ( } const calculateConnectedKonnectorPerDay = async (client: Client) => { - const fluidService: FluidService = new FluidService(client) - const fluidStatus: FluidStatus[] = await fluidService.getFluidStatus() + const fluidService = new FluidService(client) + const fluidStatus = await fluidService.getFluidStatus() const connectedKonnectors = fluidStatus.filter( fluid => fluid.status === FluidState.DONE ) @@ -266,7 +266,7 @@ const calculateConsumptionVariation = async (client: Client) => { FluidType.GAS, FluidType.WATER, ]) - for (const fluidType in [ + for (const fluidType of [ FluidType.ELECTRICITY, FluidType.GAS, FluidType.WATER, @@ -287,14 +287,11 @@ const calculateConsumptionVariation = async (client: Client) => { ? consumptionData[fluidType].percentageVariation : 0, // in percent // eslint-disable-next-line camelcase - group1: { fluid_type: FluidType[fluidType].toLowerCase() }, + group1: { fluid_type: getFluidName(fluidType) }, group2: { seniority: Math.round(seniority).toString() }, group3: { // eslint-disable-next-line camelcase - fluid_usage: await buildProfileWithFluidType( - client, - getFluidType(FluidType[fluidType]) - ), + fluid_usage: await buildProfileWithFluidType(client, fluidType), }, } diff --git a/src/utils/date.spec.ts b/src/utils/date.spec.ts index a1028add5844625ad644a9bd30cdacc365924758..1211870ea45c7af7833c5de84cffd1c79f7aac43 100644 --- a/src/utils/date.spec.ts +++ b/src/utils/date.spec.ts @@ -1,4 +1,4 @@ -import { FluidType, TimeStep } from 'enums' +import { FluidType, Season, TimeStep } from 'enums' import { DateTime } from 'luxon' import { Dataload } from 'models' import { graphData } from 'tests/__mocks__/chartData.mock' @@ -7,6 +7,7 @@ import { convertDateToMonthYearString, convertDateToShortDateString, getActualAnalysisDate, + getCurrentSeason, getLagDays, isLastDateReached, isLastPeriodReached, @@ -436,4 +437,50 @@ describe('date utils', () => { expect(result).toEqual(mockDate) }) }) + + describe('getCurrentSeason test', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + it('should return Season.SUMMER if the current month is between summer dates', () => { + const now = DateTime.local().setZone('utc', { + keepLocalTime: true, + }) + jest + .spyOn(DateTime, 'local') + .mockReturnValue(now.set({ month: 9, day: 21 })) + const currentSeason = getCurrentSeason() + expect(currentSeason).toEqual(Season.SUMMER) + }) + it('should return Season.WINTER if the current month is between winter dates', () => { + const now = DateTime.local().setZone('utc', { + keepLocalTime: true, + }) + jest + .spyOn(DateTime, 'local') + .mockReturnValue(now.set({ month: 3, day: 31 })) + const currentSeason = getCurrentSeason() + expect(currentSeason).toEqual(Season.WINTER) + }) + it('should return null if the current month is between summer and winter dates', () => { + const now = DateTime.local().setZone('utc', { + keepLocalTime: true, + }) + jest + .spyOn(DateTime, 'local') + .mockReturnValue(now.set({ month: 10, day: 2, year: 2024 })) + const currentSeason = getCurrentSeason() + expect(currentSeason).toBeNull() + }) + it('should return null if the current month is between winter and summer dates', () => { + const now = DateTime.local().setZone('utc', { + keepLocalTime: true, + }) + jest + .spyOn(DateTime, 'local') + .mockReturnValue(now.set({ month: 4, day: 20 })) + const currentSeason = getCurrentSeason() + expect(currentSeason).toBeNull() + }) + }) }) diff --git a/src/utils/date.ts b/src/utils/date.ts index e474fdd4923b6b7b8393ae80baac318783b7c447..434975bc19903b67813df3b1867bcd20dcfc8940 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -1,8 +1,20 @@ -import { FluidType, TimeStep } from 'enums' +import { FluidType, Season, TimeStep } from 'enums' import { DateTime } from 'luxon' import { Dataload } from 'models' import { getMonthNameWithPrep } from './utils' +/** Between 21st of June and 20th of September */ +export const SUMMER_WEEK_DATES = { + start: 25, + end: 38, +} as const + +/** Between 31st of October and 30th of March */ +const WINTER_WEEK_DATES = { + start: 44, + end: 13, +} as const + export function compareDates(dateA: DateTime, dateB: DateTime) { return dateA < dateB ? -1 : 1 } @@ -144,3 +156,36 @@ export const getActualAnalysisDate = (): DateTime => { return now.set({ day: 3, month: now.month }) } } + +/** + * Determines the current season based on the month of the year. + * @returns + * - Returns Season.SUMMER if the current month is between summer dates, + * - Returns Season.WINTER if the current month is between winter dates, + * - Otherwise returns null. + */ +export function getCurrentSeason() { + const weekNumber = DateTime.local().weekNumber + if ( + weekNumber >= SUMMER_WEEK_DATES.start && + weekNumber <= SUMMER_WEEK_DATES.end + ) { + return Season.SUMMER + } else if ( + weekNumber >= WINTER_WEEK_DATES.start || + weekNumber <= WINTER_WEEK_DATES.end + ) { + return Season.WINTER + } + return null +} + +export function getOppositeSeason(currentSeason: Season): Season { + if (currentSeason === Season.WINTER) { + return Season.SUMMER + } else if (currentSeason === Season.SUMMER) { + return Season.WINTER + } else { + throw new Error('Invalid current season.') + } +} diff --git a/src/utils/hash.spec.ts b/src/utils/hash.spec.ts index 0edc793c186e28d821bc301fadb7ddb7a2f5dd34..cd4a4d02c8a416016bceb54d98f71f1df89334f7 100644 --- a/src/utils/hash.spec.ts +++ b/src/utils/hash.spec.ts @@ -5,7 +5,7 @@ describe('hash utils test', () => { describe('hashFile test', () => { it('should return the correct hash of the file', () => { const result = hashFile(mockedEcogesturesData) - expect(result).toBe('21c72fc0b67b0393ee457a25956703ef17b5b724') + expect(result).toBe('bc5a72e07c44368c1841021ef0d42d9ed61de250') }) }) }) diff --git a/src/utils/utils.spec.ts b/src/utils/utils.spec.ts index 3fd5c09ab54600a8af02d745bd5cde7390449d59..e36db9b7ba1b1f7afa0529b9a1bca3f8fb6e3a65 100644 --- a/src/utils/utils.spec.ts +++ b/src/utils/utils.spec.ts @@ -14,12 +14,13 @@ import { formatTwoDigits, getChallengeTitleWithLineReturn, getFluidName, - getFluidType, + getFluidTypeTranslation, getKonnectorSlug, getKonnectorUpdateError, getMonthFullName, getMonthName, getMonthNameWithPrep, + getPartnerKey, getSeason, isKonnectorActive, isValidOffPeakHours, @@ -29,22 +30,6 @@ import { } from './utils' describe('utils test', () => { - describe('getFluidType test', () => { - it('should the electricity fluid type', () => { - const result = getFluidType('eLectRicity') - expect(result).toBe(FluidType.ELECTRICITY) - }) - - it('should the water fluid type', () => { - const result = getFluidType('WatER') - expect(result).toBe(FluidType.WATER) - }) - - it('should the gas fluid type', () => { - const result = getFluidType('gas') - expect(result).toBe(FluidType.GAS) - }) - }) describe('getKonnectorSlug', () => { it('should return correct slug for elec', () => { const slug = getKonnectorSlug(FluidType.ELECTRICITY) @@ -58,6 +43,11 @@ describe('utils test', () => { const slug = getKonnectorSlug(FluidType.GAS) expect(slug).toBe(FluidSlugType.GAS) }) + it('should throw error for invalid fluid type', () => { + expect(() => getKonnectorSlug(99 as FluidType.GAS)).toThrow( + 'unknown fluidtype' + ) + }) }) describe('getKonnectorUpdateError', () => { it('should return KonnectorUpdate.ERROR_UPDATE_OAUTH for USER_ACTION_NEEDED.OAUTH_OUTDATED', () => { @@ -70,9 +60,9 @@ describe('utils test', () => { const result = getKonnectorUpdateError('LOGIN_FAILED') expect(result).toBe(KonnectorUpdate.LOGIN_FAILED) }) - it('should return KonnectorUpdate.ERROR_CONSENT_FORM_GAS for CHALLENGE_ASKED', () => { + it('should return KonnectorUpdate.ERROR_UPDATE for CHALLENGE_ASKED', () => { const result = getKonnectorUpdateError('CHALLENGE_ASKED') - expect(result).toBe(KonnectorUpdate.ERROR_CONSENT_FORM_GAS) + expect(result).toBe(KonnectorUpdate.ERROR_UPDATE) }) it('should return KonnectorUpdate.ERROR_UPDATE for an unknown type', () => { const result = getKonnectorUpdateError('UNKNOWN_TYPE') @@ -125,7 +115,7 @@ describe('utils test', () => { fluidType: FluidType.ELECTRICITY, }, { status: FluidState.ERROR, fluidType: FluidType.WATER }, - { status: FluidState.ERROR_LOGIN_FAILED, fluidType: FluidType.GAS }, + { status: FluidState.LOGIN_FAILED, fluidType: FluidType.GAS }, ] as FluidStatus[] expect(isKonnectorActive(fluidStatus, FluidType.ELECTRICITY)).toBe(true) expect(isKonnectorActive(fluidStatus, FluidType.GAS)).toBe(true) @@ -235,6 +225,42 @@ describe('utils test', () => { }) }) + describe('getFluidTypeTranslation', () => { + it('should return electricity', () => { + expect(getFluidTypeTranslation(FluidType.ELECTRICITY)).toBe( + "d'Ă©lectricitĂ©" + ) + }) + it('should return water', () => { + expect(getFluidTypeTranslation(FluidType.WATER)).toBe("d'eau") + }) + it('should return gas', () => { + expect(getFluidTypeTranslation(FluidType.GAS)).toBe('de gaz') + }) + it('should throw error for invalid fluid type', () => { + expect(() => getFluidTypeTranslation(99 as FluidType.GAS)).toThrow( + 'unexpected fluidtype' + ) + }) + }) + + describe('getPartnerKey', () => { + it('should return enedis', () => { + expect(getPartnerKey(FluidType.ELECTRICITY)).toBe('enedis') + }) + it('should return egl', () => { + expect(getPartnerKey(FluidType.WATER)).toBe('egl') + }) + it('should return grdf', () => { + expect(getPartnerKey(FluidType.GAS)).toBe('grdf') + }) + it('should throw error for invalid fluid type', () => { + expect(() => getPartnerKey(99 as FluidType.GAS)).toThrow( + 'unknown fluidtype' + ) + }) + }) + describe('formatListWithAnd', () => { it('should return empty string', () => { expect(formatListWithAnd([])).toBe('') diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 834c7463d74b9489b3aa142199de9e224d1d05d7..fc5bf7edee928e76d7a0bfbb1fb64decbdda30a3 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -8,25 +8,15 @@ import { } from 'enums' import get from 'lodash/get' import { DateTime, Interval } from 'luxon' -import { FluidStatus, GetRelationshipsReturn, Relation } from 'models' +import { FluidStatus, Relation } from 'models' import challengeData from '../db/challengeEntity.json' /** Array of elec, water & gas */ export const allFluids = [FluidType.ELECTRICITY, FluidType.WATER, FluidType.GAS] -export function getFluidType(type: string) { - switch (type.toUpperCase()) { - case 'ELECTRICITY': - return FluidType.ELECTRICITY - case 'WATER': - return FluidType.WATER - case 'GAS': - return FluidType.GAS - default: - return FluidType.ELECTRICITY - } -} -export function getKonnectorSlug(fluidType: FluidType) { +export function getKonnectorSlug( + fluidType: Exclude<FluidType, FluidType.MULTIFLUID> +) { switch (fluidType) { case FluidType.ELECTRICITY: return FluidSlugType.ELECTRICITY @@ -46,14 +36,41 @@ export function getFluidName(fluidType: FluidType) { return FluidType[fluidType].toLowerCase() } +export const getFluidTypeTranslation = ( + fluidType: Exclude<FluidType, FluidType.MULTIFLUID> +) => { + switch (fluidType) { + case FluidType.GAS: + return 'de gaz' + case FluidType.ELECTRICITY: + return "d'Ă©lectricitĂ©" + case FluidType.WATER: + return "d'eau" + default: + throw new Error('unexpected fluidtype') + } +} + +export const getPartnerKey = (fluidType: FluidType) => { + switch (fluidType) { + case FluidType.ELECTRICITY: + return 'enedis' + case FluidType.WATER: + return 'egl' + case FluidType.GAS: + return 'grdf' + default: + throw new Error('unknown fluidtype') + } +} + export function getKonnectorUpdateError(type: string) { switch (type.toUpperCase()) { case 'USER_ACTION_NEEDED.OAUTH_OUTDATED': + case 'USER_ACTION_NEEDED.SCA_REQUIRED': return KonnectorUpdate.ERROR_UPDATE_OAUTH case 'LOGIN_FAILED': return KonnectorUpdate.LOGIN_FAILED - case 'CHALLENGE_ASKED': - return KonnectorUpdate.ERROR_CONSENT_FORM_GAS default: return KonnectorUpdate.ERROR_UPDATE } @@ -121,20 +138,6 @@ export function getRelationshipHasMany<D>(doc: D, relName: string): Relation[] { return get(doc, `relationships.${relName}.data`, []) } -/** - * Get many relations in doc - * @param {object} doc - DocumentEntity - * @param relNameList - Array of name of the relations - */ -export function getRelationships<D>( - doc: D, - relNameList: Array<string> -): GetRelationshipsReturn { - return relNameList.map(relName => ({ - [relName]: get(doc, `relationships.${relName}.data`, []), - }))[0] -} - /** * Import a svg file with format : id.svg */ diff --git a/tests/__mocks__/accountsData.mock.ts b/tests/__mocks__/accountsData.mock.ts index 7775e8b618daa1705c820cedd3c6ce13ca193a9b..911835bf0dfad32f72a0b5622aea9dce6a99ec6c 100644 --- a/tests/__mocks__/accountsData.mock.ts +++ b/tests/__mocks__/accountsData.mock.ts @@ -7,23 +7,24 @@ export const accountsData: Account[] = [ _rev: '1-88e68b8450cee09fe2f077610901094d', account_type: 'enedissgegrandlyon', name: '', - oauth: { - access_token: 'MY_ACCESS_TOKEN', - expires_at: '2020-10-09T08:00:00.285910671+02:00', - refresh_token: '', - token_type: 'Bearer', - }, - oauth_callback_results: { - issued_at: '1592232569642', - refresh_token_issued_at: '1592232569642', - scope: '/my_enedis_scope', - usage_points_id: '', - }, cozyMetadata: { createdAt: '2020-11-10T16:42:11.132Z', metadataVersion: 1, updatedAt: '2020-11-10T16:42:11.132Z', }, + auth: { + address: '6 Rue Vaillant Couturier', + city: 'VĂ©nissieux', + firstname: 'Jane', + lastname: 'Doe', + pointId: '19170766804121', + postalCode: '69200', + }, + data: { + consentId: 43, + expirationDate: '2023-09-26', + offPeakHours: '22H00-6H00', + }, }, { _id: '90e68b8450cee09fe2f077610901094d', @@ -51,16 +52,13 @@ export const accountsData: Account[] = [ _rev: '1-89e68b8450cee09fe2f077610901094d', account_type: 'grdfgrandlyon', name: '', - oauth: { - access_token: 'MY_ACCESS_TOKEN', - expires_at: '2020-10-09T08:00:00.285910671+02:00', - refresh_token: '', - token_type: 'Bearer', - }, - oauth_callback_results: { - id_token: 'MY_ID_TOKEN', - pce: '12345678987654', - scope: '/my_grdf_scope', + identifier: 'email', + auth: { + email: 'jane@grandlyon.com', + firstname: 'Jane', + lastname: 'Doe', + pce: '12345678901234', + postalCode: '69003', }, cozyMetadata: { createdAt: '2020-11-10T16:42:11.132Z', diff --git a/tests/__mocks__/actionData.mock.ts b/tests/__mocks__/actionData.mock.ts index c2a5d716ce300507c3dab2b34a7d7e8deafe53f3..b48522f5b32977717ba9ac17ccafa518ffa637d0 100644 --- a/tests/__mocks__/actionData.mock.ts +++ b/tests/__mocks__/actionData.mock.ts @@ -1,4 +1,4 @@ -import { EquipmentType, Season } from 'enums' +import { EquipmentType, Room, Season } from 'enums' import { Ecogesture } from 'models' export const defaultEcogestureData: Ecogesture[] = [ @@ -14,7 +14,7 @@ export const defaultEcogestureData: Ecogesture[] = [ impactLevel: 3, efficiency: 1.5, difficulty: 1, - room: [1, 2], + room: [Room.BATHROOM, Room.KITCHEN], season: Season.NONE, equipment: false, equipmentType: [], @@ -40,7 +40,7 @@ export const defaultEcogestureData: Ecogesture[] = [ impactLevel: 2, efficiency: 1, difficulty: 1, - room: [2], + room: [Room.KITCHEN], season: Season.NONE, equipment: false, @@ -68,7 +68,7 @@ export const defaultEcogestureData: Ecogesture[] = [ impactLevel: 1, efficiency: 0.5, difficulty: 1, - room: [2], + room: [Room.KITCHEN], season: Season.NONE, equipment: false, equipmentType: [], @@ -96,7 +96,7 @@ export const ecogestureDefault: Ecogesture = { impactLevel: 3, efficiency: 1.5, difficulty: 1, - room: [1, 2], + room: [Room.BATHROOM, Room.KITCHEN], season: Season.NONE, equipment: false, equipmentType: [], @@ -124,7 +124,7 @@ export const AllEcogestureData: Ecogesture[] = [ impactLevel: 8, efficiency: 5, difficulty: 1, - room: [0], + room: [Room.ALL], season: Season.WINTER, equipment: false, equipmentType: [], @@ -149,7 +149,7 @@ export const AllEcogestureData: Ecogesture[] = [ impactLevel: 8, efficiency: 1, difficulty: 1, - room: [0], + room: [Room.ALL], season: Season.SUMMER, equipment: true, equipmentType: [EquipmentType.AIR_CONDITIONING], @@ -175,7 +175,7 @@ export const AllEcogestureData: Ecogesture[] = [ impactLevel: 8, efficiency: 5, difficulty: 2, - room: [0], + room: [Room.ALL], season: Season.WINTER, equipment: false, equipmentType: [], @@ -201,7 +201,7 @@ export const AllEcogestureData: Ecogesture[] = [ impactLevel: 5, efficiency: 2.5, difficulty: 1, - room: [1], + room: [Room.BATHROOM], season: Season.NONE, equipment: false, equipmentType: [], @@ -226,7 +226,7 @@ export const AllEcogestureData: Ecogesture[] = [ impactLevel: 5, efficiency: 2.5, difficulty: 1, - room: [1], + room: [Room.BATHROOM], season: Season.NONE, equipment: false, equipmentType: [], @@ -251,7 +251,7 @@ export const AllEcogestureData: Ecogesture[] = [ impactLevel: 3, efficiency: 1.5, difficulty: 1, - room: [0], + room: [Room.ALL], season: Season.WINTER, equipment: false, equipmentType: [], diff --git a/tests/__mocks__/ecogesturesData.mock.ts b/tests/__mocks__/ecogesturesData.mock.ts index c7a61cc907cfa7af7a4a2301c09c276d97aff0fe..c72c461c3c39c50fb934b4b4aabe63c86c23b0a1 100644 --- a/tests/__mocks__/ecogesturesData.mock.ts +++ b/tests/__mocks__/ecogesturesData.mock.ts @@ -53,7 +53,6 @@ export const mockedEcogesturesData: Ecogesture[] = [ doing: false, objective: false, viewedInSelection: false, - _id: 'ECOGESTURE002', _rev: '1-ef7ddd778254e3b7d331a88fd17f606d', _type: 'com.grandlyon.ecolyo.ecogesture', @@ -65,7 +64,7 @@ export const mockedEcogesturesData: Ecogesture[] = [ "Utilisez la tempĂ©rature la plus basse possible : de nombreux produits nettoyants sont efficaces Ă froid et un cycle Ă 90 °C consomme 3 fois plus d'Ă©nergie qu'un lavage Ă 40 °C. En effet, 80 % de l'Ă©nergie consommĂ©e par un lave-linge ou un lave-vaisselle sert au chauffage de l'eau ! Que ce soit pour la vaisselle ou le linge, les programmes de lavage intensif consomment jusqu'Ă 40 % de plus. Si possible, rincez Ă l'eau froide : la tempĂ©rature de rinçage n'a pas d'effet sur le nettoyage du linge ou de la vaisselle. Attention cependant avec les tissus qui peuvent rĂ©trĂ©cir : ce qui fait rĂ©trĂ©cir, c'est le passage d'une tempĂ©rature Ă une autre. Mieux vaut alors faire le cycle complet Ă l'eau froide pour les premiers lavages de tissus sensibles. Pour du linge ou de la vaisselle peu sales, utilisez la touche \"Eco\". Elle rĂ©duit la tempĂ©rature de lavage et allonge sa durĂ©e (câest le chauffage de lâeau qui consomme le plus). Vous Ă©conomiserez jusquâĂ 45 % par rapport aux cycles longs. NĂ©anmoins, pour vous prĂ©munir contre les bouchons de graisse dans les canalisations, faites quand mĂȘme un cycle Ă chaud une fois par mois environ.", longName: 'Jâutilise le plus souvent les cycles courts Ă basse tempĂ©rature pour laver le linge et la vaisselle.', - shortName: 'Accelerateur de particules', + shortName: 'AccĂ©lĂ©rateur de particules', usage: Usage.ELECTRICITY_SPECIFIC, impactLevel: 2, efficiency: 1, @@ -89,6 +88,98 @@ export const mockedEcogesturesData: Ecogesture[] = [ }, ] +export const mockDoingEcogestures: Ecogesture[] = [ + { ...mockedEcogesturesData[0], doing: true, viewedInSelection: true }, + { ...mockedEcogesturesData[1], doing: false, viewedInSelection: true }, + { ...mockedEcogesturesData[2], doing: true, viewedInSelection: true }, +] + +export const mockedEcogesturesSortedData: Ecogesture[] = [ + { + fluidTypes: [FluidType.ELECTRICITY], + id: 'ECOGESTURE0002', + longDescription: + "Cela permet de garder la fraĂźcheur Ă l'intĂ©rieur. Le climatiseur n'est pas lĂ pour refroidir la rue mais bien la piĂšce.", + longName: 'Je ferme mes fenĂȘtres quand la climatisation est en marche', + shortName: 'Portique thermique', + usage: Usage.AIR_CONDITIONING, + impactLevel: 8, + efficiency: 4, + difficulty: 1, + room: [Room.ALL], + season: Season.SUMMER, + equipment: true, + equipmentType: [EquipmentType.AIR_CONDITIONING], + equipmentInstallation: true, + investment: null, + action: false, + actionName: null, + actionDuration: 3, + doing: false, + objective: false, + viewedInSelection: false, + _id: 'ECOGESTURE0002', + _rev: '19-9b604bcf7e55f8650e2be4f676fddaf0', + _type: 'com.grandlyon.ecolyo.ecogesture', + }, + { + fluidTypes: [FluidType.ELECTRICITY], + id: 'ECOGESTURE0035', + longDescription: + 'RĂ©glez votre climatisation au plus bas Ă 26 °C et veillez Ă ce quâil nây ait jamais plus de 5 Ă 7 °C de diffĂ©rence entre lâintĂ©rieur et lâextĂ©rieur. Attention aux grands Ă©carts de tempĂ©rature qui peuvent provoquer des chocs thermiques.', + longName: + 'Je rĂšgle ma climatisation au plus bas Ă 26°C en veillant Ă ce quâil nây ait pas jamais plus de 5°C Ă 7°C de diffĂ©rence entre lâintĂ©rieur et lâextĂ©rieur.', + shortName: "La Juste Clim'", + usage: Usage.AIR_CONDITIONING, + impactLevel: 8, + efficiency: 4, + difficulty: 2, + room: [Room.ALL], + season: Season.SUMMER, + equipment: true, + equipmentType: [EquipmentType.AIR_CONDITIONING], + equipmentInstallation: true, + investment: null, + action: false, + actionName: null, + actionDuration: 3, + doing: false, + objective: false, + viewedInSelection: false, + _id: 'ECOGESTURE0035', + _rev: '17-666648f24f6c797443470a54785322c5', + _type: 'com.grandlyon.ecolyo.ecogesture', + }, + { + fluidTypes: [FluidType.ELECTRICITY], + id: 'ECOGESTURE0034', + longDescription: + "Cela permet d'Ă©vite des consommations inutiles. Le froid ne restera pas dans la piĂšce. Donc il est prĂ©fĂ©rable d'allumer le ventilateur ou climatiseur seulement quand des personnes sont prĂ©sentes dans la piĂšce.", + longName: + 'Je ne fais pas fonctionner mon ventilateur ou la climatisation dans les piĂšces non occupĂ©es', + shortName: 'Bulles-Ă -part', + usage: Usage.AIR_CONDITIONING, + impactLevel: 8, + efficiency: 4, + difficulty: 2, + room: [Room.ALL], + season: Season.SUMMER, + equipment: true, + equipmentType: [EquipmentType.AIR_CONDITIONING, EquipmentType.FAN], + equipmentInstallation: true, + investment: null, + action: false, + actionName: null, + actionDuration: 3, + doing: false, + objective: false, + viewedInSelection: false, + _id: 'ECOGESTURE0034', + _rev: '25-75810c7b375dcf2742f0b225fda7c6d6', + _type: 'com.grandlyon.ecolyo.ecogesture', + }, +] + export const ecogesturesHeatingData: Ecogesture[] = [ { fluidTypes: [FluidType.ELECTRICITY], diff --git a/tests/__mocks__/enedisMonthlyAnalysisData.mock.ts b/tests/__mocks__/enedisMonthlyAnalysisData.mock.ts index bba9d3cc07c5222e83525b1e029b4752ec3acf3c..e980d11b4023dd69cd3f5a3ef53d46c32c995458 100644 --- a/tests/__mocks__/enedisMonthlyAnalysisData.mock.ts +++ b/tests/__mocks__/enedisMonthlyAnalysisData.mock.ts @@ -13,6 +13,7 @@ export const mockEnedisMonthlyAnalysis: EnedisMonthlyAnalysisData = { year: 2021, minimumLoad: 3, maxPower: 2, + offPeakHoursRatio: null, } export const mockEnedisMonthlyAnalysisArray: EnedisMonthlyAnalysisData[] = [ @@ -23,6 +24,7 @@ export const mockEnedisMonthlyAnalysisArray: EnedisMonthlyAnalysisData[] = [ year: 2021, minimumLoad: 3, maxPower: 2, + offPeakHoursRatio: null, }, { weekDaysHalfHourAverageValues: [0.35, 0.34, 0.33, 0.32, 0.31, 0.3], @@ -31,6 +33,7 @@ export const mockEnedisMonthlyAnalysisArray: EnedisMonthlyAnalysisData[] = [ year: 2021, minimumLoad: 3, maxPower: 2, + offPeakHoursRatio: null, }, { weekDaysHalfHourAverageValues: [0.35, 0.34, 0.33, 0.32, 0.31, 0.3], @@ -39,6 +42,7 @@ export const mockEnedisMonthlyAnalysisArray: EnedisMonthlyAnalysisData[] = [ year: 2021, minimumLoad: 3, maxPower: 2, + offPeakHoursRatio: null, }, { weekDaysHalfHourAverageValues: [0.35, 0.34, 0.33, 0.32, 0.31, 0.3], @@ -47,6 +51,7 @@ export const mockEnedisMonthlyAnalysisArray: EnedisMonthlyAnalysisData[] = [ year: 2021, minimumLoad: 3, maxPower: 2, + offPeakHoursRatio: null, }, ] diff --git a/tests/__mocks__/fluidPrice.mock.ts b/tests/__mocks__/fluidPrice.mock.ts index 63f6b6f5685891810c323a7a27d012aef1357956..b93fac787143fc494cb5e6f1b53dcf392aee787b 100644 --- a/tests/__mocks__/fluidPrice.mock.ts +++ b/tests/__mocks__/fluidPrice.mock.ts @@ -7,6 +7,7 @@ export const fluidPrices: FluidPrice[] = [ fluidType: 0, price: 0.1429, startDate: '2020-08-01T00:00:00.000Z', + UpdatedAt: '', }, { _id: '03045ea1afecc7a86e5443a52e00b07d', @@ -14,6 +15,7 @@ export const fluidPrices: FluidPrice[] = [ fluidType: 0, price: 0.1529, startDate: '2021-01-01T00:00:00.000Z', + UpdatedAt: '', }, { _id: '03045ea1afecc7a86e5443a52e00b07d', @@ -21,6 +23,7 @@ export const fluidPrices: FluidPrice[] = [ fluidType: 0, price: 0.1329, startDate: '2021-10-10T00:00:00.000Z', + UpdatedAt: '', }, { _id: '03045ea1afecc7a86e5443a52e00b07d', @@ -28,6 +31,7 @@ export const fluidPrices: FluidPrice[] = [ fluidType: 1, price: 0.0039, startDate: '2013-08-01T00:00:00.000Z', + UpdatedAt: '', }, { _id: '03045ea1afecc7a86e5443a52e00b07d', @@ -35,6 +39,7 @@ export const fluidPrices: FluidPrice[] = [ fluidType: 2, price: 1.029, startDate: '2013-08-01T00:00:00.000Z', + UpdatedAt: '', }, { _id: '03045ea1afecc7a86e5443a52e00b07d', @@ -42,6 +47,7 @@ export const fluidPrices: FluidPrice[] = [ fluidType: 2, price: 1.029, startDate: '2014-11-01T00:00:00.000Z', + UpdatedAt: '', }, ] @@ -52,6 +58,7 @@ export const allLastFluidPrices: FluidPrice[] = [ fluidType: 0, price: 0.1329, startDate: '2021-10-10T00:00:00.000Z', + UpdatedAt: '', }, { _id: '03045ea1afecc7a86e5443a52e00b07d', @@ -59,6 +66,7 @@ export const allLastFluidPrices: FluidPrice[] = [ fluidType: 1, price: 0.0039, startDate: '2013-08-01T00:00:00.000Z', + UpdatedAt: '', }, { _id: '03045ea1afecc7a86e5443a52e00b07d', @@ -66,5 +74,6 @@ export const allLastFluidPrices: FluidPrice[] = [ fluidType: 2, price: 1.029, startDate: '2014-11-01T00:00:00.000Z', + UpdatedAt: '', }, ] diff --git a/tests/__mocks__/fluidStatusData.mock.ts b/tests/__mocks__/fluidStatusData.mock.ts index eb1338c7612ecdcb31a3940c6203fc3dc4b4645a..e7762e79fc050a29ec3e69d2f8123e984b84d1f6 100644 --- a/tests/__mocks__/fluidStatusData.mock.ts +++ b/tests/__mocks__/fluidStatusData.mock.ts @@ -194,18 +194,6 @@ export const SgeStatusWithAccount: FluidStatus = { _rev: '1-88e68b8450cee09fe2f077610901094d', account_type: 'enedissgegrandlyon', name: '', - oauth: { - access_token: 'MY_ACCESS_TOKEN', - expires_at: '2020-10-09T08:00:00.285910671+02:00', - refresh_token: '', - token_type: 'Bearer', - }, - oauth_callback_results: { - issued_at: '1592232569642', - refresh_token_issued_at: '1592232569642', - scope: '/my_enedis_scope', - usage_points_id: '', - }, cozyMetadata: { createdAt: '2020-11-10T16:42:11.132Z', metadataVersion: 1, @@ -227,7 +215,7 @@ export const SgeStatusWithAccount: FluidStatus = { export const mockExpiredElec: FluidStatus = { fluidType: FluidType.ELECTRICITY, status: FluidState.KONNECTOR_NOT_FOUND, - maintenance: true, + maintenance: false, firstDataDate: null, lastDataDate: null, connection: { @@ -264,7 +252,7 @@ export const mockExpiredElec: FluidStatus = { export const mockExpiredGas: FluidStatus = { fluidType: FluidType.GAS, - status: FluidState.ERROR_LOGIN_FAILED, + status: FluidState.LOGIN_FAILED, maintenance: false, firstDataDate: null, lastDataDate: null, diff --git a/tests/__mocks__/profileData.mock.ts b/tests/__mocks__/profileData.mock.ts deleted file mode 100644 index 82a92073ab89b9f5aed02680d149d039b8d5bdb3..0000000000000000000000000000000000000000 --- a/tests/__mocks__/profileData.mock.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { DateTime } from 'luxon' -import { Profile } from 'models' - -// TODO to remove ? -export const profileData: Profile = { - _id: '4d9403218ef13e65b2e3a8ad1700bc41', - _rev: '16-57473da4fc26315247c217083175dfa0', - id: '4d9403218ef13e65b2e3a8ad1700bc41', - ecogestureHash: '9798a0aaccb47cff906fc4931a2eff5f9371dd8b', - challengeHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', - duelHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', - quizHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', - explorationHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', - isFirstConnection: true, - sendConsumptionAlert: false, - waterDailyConsumptionLimit: 0, - mailToken: '', - lastConnectionDate: DateTime.fromISO('2020-11-03T00:00:00.000Z', { - zone: 'utc', - }), - haveSeenLastAnalysis: true, - monthlyAnalysisDate: DateTime.fromISO('2020-11-03T00:00:00.000Z', { - zone: 'utc', - }), - sendAnalysisNotification: false, - isProfileTypeCompleted: false, - onboarding: { - isWelcomeSeen: false, - }, - haveSeenEcogestureModal: false, - isProfileEcogestureCompleted: false, - activateHalfHourDate: DateTime.fromISO('2020-11-03T00:00:00.000Z', { - zone: 'utc', - }), - customPopupDate: DateTime.fromISO('2020-11-03T00:00:00.000Z', { - zone: 'utc', - }), - partnersIssueSeenDate: { - enedis: DateTime.fromISO('2020-11-03T00:00:00.000Z', { - zone: 'utc', - }), - egl: DateTime.fromISO('2020-11-03T00:00:00.000Z', { - zone: 'utc', - }), - grdf: DateTime.fromISO('2020-11-03T00:00:00.000Z', { - zone: 'utc', - }), - }, -} diff --git a/tests/__mocks__/store/chart.state.mock.ts b/tests/__mocks__/store/chart.state.mock.ts index df355ad572383eb3e1d9e627a43808852e5fe909..76b75c5991de8c41b1cd7d7fe1070b8260f617aa 100644 --- a/tests/__mocks__/store/chart.state.mock.ts +++ b/tests/__mocks__/store/chart.state.mock.ts @@ -10,7 +10,7 @@ export const mockChartState: ChartState = { currentIndex: 0, currentDatachart: { actualData: [], comparisonData: null }, currentDatachartIndex: 0, - loading: true, showCompare: false, showOfflineData: false, + showConnectionDetails: false, } diff --git a/tests/__mocks__/store/global.state.mock.ts b/tests/__mocks__/store/global.state.mock.ts index ddc0d309f58fab3acdb351c8541b654f6df8e8a6..3c19efac32e4edefa90cb524f770d0c4d5b496f8 100644 --- a/tests/__mocks__/store/global.state.mock.ts +++ b/tests/__mocks__/store/global.state.mock.ts @@ -3,6 +3,7 @@ import { FluidSlugType, FluidState, FluidType, ScreenType, Usage } from 'enums' import { GlobalState } from 'models' export const mockGlobalState: GlobalState = { + headerHeight: 62, screenType: ScreenType.MOBILE, challengeExplorationNotification: false, challengeActionNotification: false, diff --git a/yarn.lock b/yarn.lock index cbea5687c2a7662eed79a5b1db34c75a5eb1ad1f..8f3dc7ada817ba30902b36a087f5c3fce418027c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== -"@adobe/css-tools@^4.3.1": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.1.tgz#abfccb8ca78075a2b6187345c26243c1a0842f28" - integrity sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg== +"@adobe/css-tools@^4.3.2": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.3.tgz#90749bde8b89cd41764224f5aac29cd4138f75ff" + integrity sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ== "@alloc/types@^1.2.1": version "1.3.0" @@ -55,7 +55,15 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.22.13": +"@babel/code-frame@^7.10.4": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + +"@babel/code-frame@^7.22.13": version "7.22.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== @@ -565,14 +573,15 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/highlight@^7.22.13": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" - integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== +"@babel/highlight@^7.22.13", "@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== dependencies: "@babel/helper-validator-identifier" "^7.22.20" chalk "^2.4.2" js-tokens "^4.0.0" + picocolors "^1.0.0" "@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.6", "@babel/parser@^7.2.2", "@babel/parser@^7.7.0": version "7.18.6" @@ -1554,7 +1563,7 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.12.0", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.6.tgz#6a1ef59f838debd670421f8c7f2cbb8da9751580" integrity sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ== @@ -1568,6 +1577,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.12.5": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" + integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.16.3": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" @@ -2679,7 +2695,21 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@testing-library/dom@^9.0.0", "@testing-library/dom@^9.3.3": +"@testing-library/dom@^9.0.0": + version "9.3.4" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.4.tgz#50696ec28376926fec0a1bf87d9dbac5e27f60ce" + integrity sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "5.1.3" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" + pretty-format "^27.0.2" + +"@testing-library/dom@^9.3.3": version "9.3.3" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.3.tgz#108c23a5b0ef51121c26ae92eb3179416b0434f5" integrity sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw== @@ -2693,33 +2723,33 @@ lz-string "^1.5.0" pretty-format "^27.0.2" -"@testing-library/jest-dom@^6.1.4": - version "6.1.4" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.1.4.tgz#cf0835c33bc5ef00befb9e672b1e3e6a710e30e3" - integrity sha512-wpoYrCYwSZ5/AxcrjLxJmCU6I5QAJXslEeSiMQqaWmP2Kzpd1LvF/qxmAIW2qposULGWq2gw30GgVNFLSc2Jnw== +"@testing-library/jest-dom@^6.4.2": + version "6.4.2" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.2.tgz#38949f6b63722900e2d75ba3c6d9bf8cffb3300e" + integrity sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw== dependencies: - "@adobe/css-tools" "^4.3.1" + "@adobe/css-tools" "^4.3.2" "@babel/runtime" "^7.9.2" aria-query "^5.0.0" chalk "^3.0.0" css.escape "^1.5.1" - dom-accessibility-api "^0.5.6" + dom-accessibility-api "^0.6.3" lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react@^14.1.2": - version "14.1.2" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.1.2.tgz#a2b9e9ee87721ec9ed2d7cfc51cc04e474537c32" - integrity sha512-z4p7DVBTPjKM5qDZ0t5ZjzkpSNb+fZy1u6bzO7kk8oeGagpPCAtgh4cx1syrfp7a+QWkM021jGqjJaxJJnXAZg== +"@testing-library/react@^14.3.0": + version "14.3.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.3.0.tgz#8183eb5a5f465b5b8cc495fcbd9bad0a16b8dd3b" + integrity sha512-AYJGvNFMbCa5vt1UtDCa/dcaABrXq8gph6VN+cffIx0UeA0qiGqS+sT60+sb+Gjc8tGXdECWYQgaF0khf8b+Lg== dependencies: "@babel/runtime" "^7.12.5" "@testing-library/dom" "^9.0.0" "@types/react-dom" "^18.0.0" -"@testing-library/user-event@^14.5.1": - version "14.5.1" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.1.tgz#27337d72046d5236b32fd977edee3f74c71d332f" - integrity sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg== +"@testing-library/user-event@^14.5.2": + version "14.5.2" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd" + integrity sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ== "@tokenizer/token@^0.3.0": version "0.3.0" @@ -2732,9 +2762,9 @@ integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== "@types/aria-query@^5.0.1": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.2.tgz#6f1225829d89794fd9f891989c9ce667422d7f64" - integrity sha512-PHKZuMN+K5qgKIWhBodXzQslTo5P+K/6LqeKXS6O/4liIDdZqaX5RXrCK++LAw+y/nptN48YmUMFiQHRSWYwtQ== + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" + integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": version "7.1.19" @@ -3121,9 +3151,9 @@ integrity sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg== "@types/prop-types@*": - version "15.7.5" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + version "15.7.12" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" + integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== "@types/q@^1.5.1": version "1.5.5" @@ -3131,9 +3161,9 @@ integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ== "@types/react-dom@^18.0.0": - version "18.2.17" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.17.tgz#375c55fab4ae671bd98448dcfa153268d01d6f64" - integrity sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg== + version "18.2.24" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.24.tgz#8dda8f449ae436a7a6e91efed8035d4ab03ff759" + integrity sha512-cN6upcKd8zkGy4HU9F1+/s98Hrp6D4MOcippK4PoE8OZRngohHZpbJn1GsaDLz87MqvHNoT13nHvNqM9ocRHZg== dependencies: "@types/react" "*" @@ -3169,12 +3199,11 @@ "@types/react" "*" "@types/react@*": - version "18.0.14" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.14.tgz#e016616ffff51dba01b04945610fe3671fdbe06d" - integrity sha512-x4gGuASSiWmo0xjDLpm5mPb52syZHJx02VKbqUKdLmKtAwIh63XClGsiTI1K6DO5q7ox4xAsQrU+Gl3+gGXF9Q== + version "18.2.74" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.74.tgz#2d52eb80e4e7c4ea8812c89181d6d590b53f958c" + integrity sha512-9AEqNZZyBx8OdZpxzQlaFEVCSFUM2YXJH46yPOiOpm078k6ZLOCcuAzGum/zK8YBwY+dbahVNbHrbgrAwIRlqw== dependencies: "@types/prop-types" "*" - "@types/scheduler" "*" csstype "^3.0.2" "@types/react@^18.2.25": @@ -3194,9 +3223,9 @@ redux "^4.0.5" "@types/scheduler@*": - version "0.16.2" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" - integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + version "0.23.0" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.23.0.tgz#0a6655b3e2708eaabca00b7372fafd7a792a7b09" + integrity sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw== "@types/semver@^7.3.12": version "7.3.13" @@ -3850,12 +3879,12 @@ arr-union@^3.1.0: integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== array-buffer-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" - integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== dependencies: - call-bind "^1.0.2" - is-array-buffer "^3.0.1" + call-bind "^1.0.5" + is-array-buffer "^3.0.4" array-flatten@1.1.1: version "1.1.1" @@ -4131,10 +4160,12 @@ autoprefixer@9.7.6: postcss "^7.0.27" postcss-value-parser "^4.0.3" -available-typed-arrays@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" - integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +available-typed-arrays@^1.0.5, available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" aws-sign2@~0.7.0: version "0.7.0" @@ -4889,13 +4920,16 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" call-bind@^1.0.4, call-bind@^1.0.5: version "1.0.5" @@ -6562,9 +6596,9 @@ csstype@^2.5.2: integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA== csstype@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2" - integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA== + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== csswring@7.0.0: version "7.0.0" @@ -6949,14 +6983,14 @@ deep-equal@^1.0.1: regexp.prototype.flags "^1.2.0" deep-equal@^2.0.5: - version "2.2.2" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.2.tgz#9b2635da569a13ba8e1cc159c2f744071b115daa" - integrity sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA== + version "2.2.3" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1" + integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA== dependencies: array-buffer-byte-length "^1.0.0" - call-bind "^1.0.2" + call-bind "^1.0.5" es-get-iterator "^1.1.3" - get-intrinsic "^1.2.1" + get-intrinsic "^1.2.2" is-arguments "^1.1.1" is-array-buffer "^3.0.2" is-date-object "^1.0.5" @@ -6966,11 +7000,11 @@ deep-equal@^2.0.5: object-is "^1.1.5" object-keys "^1.1.1" object.assign "^4.1.4" - regexp.prototype.flags "^1.5.0" + regexp.prototype.flags "^1.5.1" side-channel "^1.0.4" which-boxed-primitive "^1.0.2" which-collection "^1.0.1" - which-typed-array "^1.1.9" + which-typed-array "^1.1.13" deep-equal@~0.2.1: version "0.2.2" @@ -7025,14 +7059,14 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" -define-data-property@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" - integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g== +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== dependencies: - get-intrinsic "^1.2.1" + es-define-property "^1.0.0" + es-errors "^1.3.0" gopd "^1.0.1" - has-property-descriptors "^1.0.0" define-data-property@^1.1.1: version "1.1.1" @@ -7053,7 +7087,7 @@ define-lazy-prop@^3.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== -define-properties@^1.1.2, define-properties@^1.1.3, define-properties@^1.1.4: +define-properties@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== @@ -7061,7 +7095,7 @@ define-properties@^1.1.2, define-properties@^1.1.3, define-properties@^1.1.4: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -define-properties@^1.2.0, define-properties@^1.2.1: +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0, define-properties@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== @@ -7245,11 +7279,16 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: +dom-accessibility-api@^0.5.9: version "0.5.16" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + dom-converter@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -7679,6 +7718,18 @@ es-array-method-boxes-properly@^1.0.0: resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + es-get-iterator@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" @@ -9030,12 +9081,7 @@ fsevents@^2.0.6, fsevents@^2.1.2, fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -function-bind@^1.1.2: +function-bind@^1.1.1, function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== @@ -9091,7 +9137,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: +get-intrinsic@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== @@ -9100,24 +9146,16 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.3" -get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" - integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.3" - -get-intrinsic@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" - integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== +get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== dependencies: - function-bind "^1.1.1" - has "^1.0.3" + es-errors "^1.3.0" + function-bind "^1.1.2" has-proto "^1.0.1" has-symbols "^1.0.3" + hasown "^2.0.0" get-intrinsic@^1.2.2: version "1.2.2" @@ -9481,29 +9519,29 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== dependencies: - get-intrinsic "^1.1.1" + es-define-property "^1.0.0" has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: - has-symbols "^1.0.2" + has-symbols "^1.0.3" has-value@^0.3.1: version "0.3.1" @@ -9537,11 +9575,9 @@ has-values@^1.0.0: kind-of "^4.0.0" has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" + version "1.0.4" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6" + integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== hash-base@^3.0.0: version "3.1.0" @@ -10076,7 +10112,16 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" -internal-slot@^1.0.4, internal-slot@^1.0.5: +internal-slot@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + +internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== @@ -10167,14 +10212,13 @@ is-arguments@^1.0.4, is-arguments@^1.1.1: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" - integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== +is-array-buffer@^3.0.2, is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== dependencies: call-bind "^1.0.2" - get-intrinsic "^1.2.0" - is-typed-array "^1.1.10" + get-intrinsic "^1.2.1" is-arrayish@^0.2.1: version "0.2.1" @@ -10404,10 +10448,10 @@ is-inside-container@^1.0.0: dependencies: is-docker "^3.0.0" -is-map@^2.0.1, is-map@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" - integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== +is-map@^2.0.2, is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== is-negative-zero@^2.0.2: version "2.0.2" @@ -10497,17 +10541,17 @@ is-regex@^1.0.4, is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-set@^2.0.1, is-set@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" - integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== +is-set@^2.0.2, is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" is-stream@^1.1.0: version "1.1.0" @@ -10545,7 +10589,21 @@ is-text-path@^1.0.1: dependencies: text-extensions "^1.0.0" -is-typed-array@^1.1.10, is-typed-array@^1.1.9: +is-typed-array@^1.1.10: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== + dependencies: + which-typed-array "^1.1.14" + +is-typed-array@^1.1.12: + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== + dependencies: + which-typed-array "^1.1.11" + +is-typed-array@^1.1.9: version "1.1.10" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== @@ -10556,13 +10614,6 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.9: gopd "^1.0.1" has-tostringtag "^1.0.0" -is-typed-array@^1.1.12: - version "1.1.12" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" - integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== - dependencies: - which-typed-array "^1.1.11" - is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -10573,10 +10624,10 @@ is-utf8@^0.2.0: resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== is-weakref@^1.0.2: version "1.0.2" @@ -10585,13 +10636,13 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" -is-weakset@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" - integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== +is-weakset@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" + integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" + call-bind "^1.0.7" + get-intrinsic "^1.2.4" is-whitespace-character@^1.0.0: version "1.0.4" @@ -13079,7 +13130,7 @@ object-hash@^3.0.0: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== -object-inspect@^1.12.0, object-inspect@^1.9.0: +object-inspect@^1.12.0: version "1.12.2" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== @@ -13094,7 +13145,7 @@ object-inspect@^1.13.1: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== -object-is@^1.0.1, object-is@^1.1.5: +object-is@^1.0.1: version "1.1.5" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== @@ -13102,6 +13153,14 @@ object-is@^1.0.1, object-is@^1.1.5: call-bind "^1.0.2" define-properties "^1.1.3" +object-is@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -13125,12 +13184,12 @@ object.assign@^4.1.0, object.assign@^4.1.2: object-keys "^1.1.1" object.assign@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" + call-bind "^1.0.5" + define-properties "^1.2.1" has-symbols "^1.0.3" object-keys "^1.1.1" @@ -13844,6 +13903,11 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + post-me@0.4.5: version "0.4.5" resolved "https://registry.yarnpkg.com/post-me/-/post-me-0.4.5.tgz#6171b721c7b86230c51cfbe48ddea047ef8831ce" @@ -15040,16 +15104,11 @@ regenerator-runtime@^0.12.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== -regenerator-runtime@^0.13.11: +regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.4: version "0.13.11" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== -regenerator-runtime@^0.13.4: - version "0.13.9" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" - integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== - regenerator-runtime@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" @@ -15079,7 +15138,7 @@ regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.1, regexp.prototype.f define-properties "^1.1.3" functions-have-names "^1.2.2" -regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1: +regexp.prototype.flags@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== @@ -15700,7 +15759,29 @@ set-function-length@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" -set-function-name@^2.0.0, set-function-name@^2.0.1: +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== @@ -15784,13 +15865,14 @@ shellwords@^0.1.1: integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" sift@^6.0.0: version "6.0.0" @@ -17921,14 +18003,14 @@ which-builtin-type@^1.1.3: which-typed-array "^1.1.9" which-collection@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" - integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== dependencies: - is-map "^2.0.1" - is-set "^2.0.1" - is-weakmap "^2.0.1" - is-weakset "^2.0.1" + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" which-module@^2.0.0: version "2.0.0" @@ -17946,17 +18028,16 @@ which-typed-array@^1.1.11, which-typed-array@^1.1.13: gopd "^1.0.1" has-tostringtag "^1.0.0" -which-typed-array@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" - integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== +which-typed-array@^1.1.14, which-typed-array@^1.1.9: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" for-each "^0.3.3" gopd "^1.0.1" - has-tostringtag "^1.0.0" - is-typed-array "^1.1.10" + has-tostringtag "^1.0.2" which@^1.2.9: version "1.3.1"