From 46e581fb2cb01cd831da5e473da3c5d237f5a7b8 Mon Sep 17 00:00:00 2001 From: Audrey Lebret Date: Wed, 9 Nov 2022 17:10:41 +0100 Subject: [PATCH] fix(accessibility): maplist (#1506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(accessibilite): searchRegionByPostalCode * test(accessibilite): extractedPoiTypesFromArticles * fix(accessibilite): dépendance circulaire * fix(accessibilite): ouvrir liste des POIs * fix(accessibilte): refacto * fix(accessibilite): retours de PR * fix(accessibilite): retours PR * fix(accessibilite): retours de PR --- ...erLocationOrPostalCodeCoords.component.tsx | 26 +-- .../search/aroundMePoiList.component.tsx | 6 +- .../search/aroundMeMapAndList.component.tsx | 34 ++-- .../src/utils/aroundMe/aroundMe.util.test.ts | 37 +++- front/src/utils/aroundMe/aroundMe.util.ts | 20 +++ front/src/utils/index.ts | 4 +- front/src/utils/number/number.util.test.ts | 19 +++ front/src/utils/number/number.util.ts | 2 + front/src/utils/search/search.util.test.ts | 159 ++++++++++++++++++ front/src/utils/{ => search}/search.util.ts | 25 ++- 10 files changed, 298 insertions(+), 34 deletions(-) create mode 100644 front/src/utils/number/number.util.test.ts create mode 100644 front/src/utils/number/number.util.ts create mode 100644 front/src/utils/search/search.util.test.ts rename front/src/utils/{ => search}/search.util.ts (53%) diff --git a/front/src/components/aroundMe/searchUserLocationOrPostalCodeCoords.component.tsx b/front/src/components/aroundMe/searchUserLocationOrPostalCodeCoords.component.tsx index 3238d3e7e..0b4163e7d 100644 --- a/front/src/components/aroundMe/searchUserLocationOrPostalCodeCoords.component.tsx +++ b/front/src/components/aroundMe/searchUserLocationOrPostalCodeCoords.component.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from "react"; import type { LatLng } from "react-native-maps"; import { AroundMeConstants } from "../../constants"; -import { AroundMeUtils } from "../../utils"; +import { SearchUtils } from "../../utils"; interface Props { triggerGetUserLocation: boolean; @@ -72,21 +72,6 @@ const SearchUserLocationOrPostalCodeCoords: FC = ({ } }; - const searchRegionByPostalCode = async () => { - if ( - postalCodeInput.length !== AroundMeConstants.POSTAL_CODE_MAX_LENGTH || - isNaN(Number(postalCodeInput)) - ) { - postalCodeIsInvalid(); - return; - } - const postalCodeCoords = await AroundMeUtils.getPostalCodeCoords( - postalCodeInput - ); - - setCoordinates(postalCodeCoords); - }; - useEffect(() => { setComponentIsInitialized(true); }, []); @@ -97,7 +82,14 @@ const SearchUserLocationOrPostalCodeCoords: FC = ({ }, [triggerGetUserLocation]); useEffect(() => { - if (componentIsInitialized) void searchRegionByPostalCode(); + if (componentIsInitialized) { + void SearchUtils.getCoordinatesByPostalCode( + postalCodeInput, + postalCodeIsInvalid + ).then((coordinates) => { + setCoordinates(coordinates); + }); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [triggerGetPostalCodeCoords]); diff --git a/front/src/components/search/aroundMePoiList.component.tsx b/front/src/components/search/aroundMePoiList.component.tsx index 15cddcc60..422adfa52 100644 --- a/front/src/components/search/aroundMePoiList.component.tsx +++ b/front/src/components/search/aroundMePoiList.component.tsx @@ -1,6 +1,6 @@ import type { Poi } from "@socialgouv/nos1000jours-lib"; import type { FC } from "react"; -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import * as React from "react"; import { StyleSheet } from "react-native"; import type { Region } from "react-native-maps"; @@ -31,6 +31,10 @@ const AroundMePoiList: FC = ({ const [triggerSearchByGpsCoords, setTriggerSearchByGpsCoords] = useState(false); + useEffect(() => { + setTriggerSearchByGpsCoords(poiArray.length == 0); + }, []); + const handlePois = useCallback( (pois: Poi[]) => { updatePoiArray(pois); diff --git a/front/src/screens/search/aroundMeMapAndList.component.tsx b/front/src/screens/search/aroundMeMapAndList.component.tsx index 393b519fd..d7c87f2c7 100644 --- a/front/src/screens/search/aroundMeMapAndList.component.tsx +++ b/front/src/screens/search/aroundMeMapAndList.component.tsx @@ -1,7 +1,7 @@ import type { RouteProp } from "@react-navigation/native"; import type { StackNavigationProp } from "@react-navigation/stack"; import type { Poi } from "@socialgouv/nos1000jours-lib"; -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import * as React from "react"; import { StyleSheet } from "react-native"; import type { LatLng, Region } from "react-native-maps"; @@ -10,7 +10,7 @@ import { AroundMeMap, AroundMePoiList } from "../../components"; import { BackButton, View } from "../../components/baseComponents"; import { Margins } from "../../styles"; import type { TabSearchParamList } from "../../types"; -import { AccessibilityUtils } from "../../utils"; +import { AccessibilityUtils, AroundMeUtils } from "../../utils"; interface Props { route: RouteProp< @@ -34,7 +34,7 @@ const AroundMeMapAndList: React.FC = ({ navigation, route }) => { ); const [poisArray, setPoisArray] = useState([]); const [selectedPoiIndex, setSelectedPoiIndex] = useState(-1); - const [displayMap, setDisplayMap] = useState(true); + const [displayMap, setDisplayMap] = useState(); const [userLocation] = useState( route.params.displayUserLocation ? coordinates : undefined ); @@ -56,20 +56,34 @@ const AroundMeMapAndList: React.FC = ({ navigation, route }) => { const updatePoiArray = useCallback((newPoiArray: Poi[]) => { setPoisArray(newPoiArray); + }, []); + + useEffect(() => { + checkAccessibility(); + updateRegionManually(); + }, []); - // Si le lecteur d'écran est activé, on affiche la liste des POI une fois que la première recherche a été faite - const goToListIfScreenReaderIsEnabled = async () => { + const checkAccessibility = useCallback(() => { + const checkAccessibilityMode = async () => { const isScreenReaderEnabled = await AccessibilityUtils.isScreenReaderEnabled(); - if (isScreenReaderEnabled) { - setDisplayMap(false); + + // Si le lecteur d'écran est activé, on affiche la liste des POI une fois que la première recherche a été faite + if (isScreenReaderEnabled) setDisplayMap(false); + else { + setTimeout(() => { + setDisplayMap(true); + }, 500); } }; - setTimeout(() => { - void goToListIfScreenReaderIsEnabled(); - }, 500); + void checkAccessibilityMode(); }, []); + const updateRegionManually = useCallback(() => { + if (!displayMap && !region && coordinates) + setRegion(AroundMeUtils.calculateRegionManually(coordinates)); + }, [coordinates, displayMap, region]); + return ( diff --git a/front/src/utils/aroundMe/aroundMe.util.test.ts b/front/src/utils/aroundMe/aroundMe.util.test.ts index 18e273782..81c140f1f 100644 --- a/front/src/utils/aroundMe/aroundMe.util.test.ts +++ b/front/src/utils/aroundMe/aroundMe.util.test.ts @@ -1,7 +1,7 @@ import type { LatLng, Region } from "react-native-maps"; import { AroundMeConstants } from "../../constants"; -import { getLatLngPoint } from "./aroundMe.util"; +import { calculateRegionManually, getLatLngPoint } from "./aroundMe.util"; describe("AroundMeUtils", () => { describe("getLatLngPoint", () => { @@ -56,4 +56,39 @@ describe("AroundMeUtils", () => { ).toEqual(expected); }); }); + + describe("calculateRegionManually", () => { + it("Should return region with delta approximatively", () => { + const point = { + latitude: 47.223324097274556, + longitude: -1.538015529513359, + }; + + const regionByMapView: Region = { + latitude: 47.223324097274556, + latitudeDelta: 0.13430321917147126, + longitude: -1.538015529513359, + longitudeDelta: 0.13483263552188873, + }; + const ERROR_MARGIN = 0.03; + + const newRegion = calculateRegionManually(point); + expect(newRegion.latitude).toEqual(regionByMapView.latitude); + expect(newRegion.longitude).toEqual(regionByMapView.longitude); + + expect(newRegion.longitudeDelta).toBeLessThanOrEqual( + regionByMapView.longitudeDelta + ERROR_MARGIN + ); + expect(newRegion.longitudeDelta).toBeGreaterThanOrEqual( + regionByMapView.longitudeDelta - ERROR_MARGIN + ); + + expect(newRegion.latitudeDelta).toBeLessThanOrEqual( + regionByMapView.latitudeDelta + ERROR_MARGIN + ); + expect(newRegion.latitudeDelta).toBeGreaterThanOrEqual( + regionByMapView.latitudeDelta - ERROR_MARGIN + ); + }); + }); }); diff --git a/front/src/utils/aroundMe/aroundMe.util.ts b/front/src/utils/aroundMe/aroundMe.util.ts index cba314f05..40ec519c9 100644 --- a/front/src/utils/aroundMe/aroundMe.util.ts +++ b/front/src/utils/aroundMe/aroundMe.util.ts @@ -2,6 +2,7 @@ import type { LatLng, Region } from "react-native-maps"; import { AroundMeConstants } from "../../constants"; import { PLATFORM_IS_IOS } from "../../constants/platform.constants"; +import { deg2rad, rad2deg } from "../number/number.util"; export const getPostalCodeCoords = async ( postalCodeInput: string @@ -99,3 +100,22 @@ export const triggerFunctionAfterTimeout = ( PLATFORM_IS_IOS ? 1000 : 500 ); }; + +export const calculateRegionManually = (coordinates: LatLng): Region => { + const EARTH_RADIUS_IN_KM = 6371; + const RADIUS_IN_KM = 12; + const ASPECT_RATIO = 1; + const radiusInRad = RADIUS_IN_KM / EARTH_RADIUS_IN_KM; + + const longitudeDelta = rad2deg( + radiusInRad / Math.cos(deg2rad(coordinates.latitude)) + ); + const latitudeDelta = ASPECT_RATIO * rad2deg(radiusInRad); + + return { + latitude: coordinates.latitude, + latitudeDelta: latitudeDelta, + longitude: coordinates.longitude, + longitudeDelta: longitudeDelta, + }; +}; diff --git a/front/src/utils/index.ts b/front/src/utils/index.ts index a9185820e..bb15a0f08 100644 --- a/front/src/utils/index.ts +++ b/front/src/utils/index.ts @@ -15,9 +15,10 @@ import { initMonitoring, reportError } from "./logging.util"; import * as MoodboardUtils from "./moodboard/moodboard.util"; import * as NotificationUtils from "./notifications/notification.util"; import * as NotificationToggleUtils from "./notifications/notificationToggle.util"; +import * as NumberUtils from "./number/number.util"; import * as RootNavigation from "./rootNavigation.util"; import * as ScaleNormalize from "./scaleNormalize.util"; -import * as SearchUtils from "./search.util"; +import * as SearchUtils from "./search/search.util"; import * as StepUtils from "./step/step.util"; import * as StorageUtils from "./storage.util"; import * as StringUtils from "./strings/strings.util"; @@ -45,6 +46,7 @@ export { MoodboardUtils, NotificationToggleUtils, NotificationUtils, + NumberUtils, reportError, RootNavigation, ScaleNormalize, diff --git a/front/src/utils/number/number.util.test.ts b/front/src/utils/number/number.util.test.ts new file mode 100644 index 000000000..b79818620 --- /dev/null +++ b/front/src/utils/number/number.util.test.ts @@ -0,0 +1,19 @@ +import { deg2rad, rad2deg } from "./number.util"; + +describe("Number utils", () => { + describe("deg2rad", () => { + it("Should return radian", () => { + const deg = 180; + const expected = (deg / 180) * Math.PI; + expect(deg2rad(deg)).toEqual(expected); + }); + }); + + describe("rad2deg", () => { + it("Should return degree", () => { + const rad = 3; + const expected = (rad / Math.PI) * 180; + expect(rad2deg(rad)).toEqual(expected); + }); + }); +}); diff --git a/front/src/utils/number/number.util.ts b/front/src/utils/number/number.util.ts new file mode 100644 index 000000000..0d9c03071 --- /dev/null +++ b/front/src/utils/number/number.util.ts @@ -0,0 +1,2 @@ +export const deg2rad = (angle: number) => (angle / 180) * Math.PI; +export const rad2deg = (angle: number) => (angle / Math.PI) * 180; diff --git a/front/src/utils/search/search.util.test.ts b/front/src/utils/search/search.util.test.ts new file mode 100644 index 000000000..629a821c8 --- /dev/null +++ b/front/src/utils/search/search.util.test.ts @@ -0,0 +1,159 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; +import type { PoiType } from "@socialgouv/nos1000jours-lib"; +import type { LatLng } from "react-native-maps"; + +import { StorageKeysConstants } from "../../constants"; +import type { Article } from "../../types"; +import { AroundMeUtils, StorageUtils } from ".."; +import { + extractedPoiTypesFromArticles, + getCoordinatesByPostalCode, +} from "./search.util"; + +describe("Search Util", () => { + describe("searchRegionByPostalCode", () => { + let mockGetPostalCodeCoords: any = undefined; + const mockData: LatLng = { latitude: 47.220676, longitude: -1.563173 }; + + beforeAll(() => { + mockGetPostalCodeCoords = jest + .spyOn(AroundMeUtils, "getPostalCodeCoords") + .mockResolvedValue(mockData); + }); + + it("should return lat/long by postal code", async () => { + const result = await getCoordinatesByPostalCode("44000", () => {}); + expect(result).toEqual(mockData); + + mockGetPostalCodeCoords.mockRestore(); + }); + + it("should return error for invalid postal code (too short)", async () => { + const result = await getCoordinatesByPostalCode("440", () => {}); + expect(result).toEqual(undefined); + + mockGetPostalCodeCoords.mockRestore(); + }); + + it("should return error for invalid postal code (not a number)", async () => { + const result = await getCoordinatesByPostalCode("hello", () => {}); + expect(result).toEqual(undefined); + + mockGetPostalCodeCoords.mockRestore(); + }); + }); + + describe("extractedPoiTypesFromArticles", () => { + const articleList: Article[] = [ + { + cartographie_pois_types: [ + { categorie: "", nom: "Maternité" }, + { categorie: "", nom: "Maison de naissance" }, + { categorie: "", nom: "PMI" }, + { categorie: "", nom: "CPAM" }, + ], + enbrefIcone1: "", + enbrefIcone2: "", + enbrefIcone3: "", + enbrefTexte1: "", + enbrefTexte2: "", + enbrefTexte3: "", + id: 128, + leSaviezVous: "", + lienTitre1: "", + lienTitre2: "", + lienTitre3: "", + lienTitre4: "", + lienUrl1: "", + lienUrl2: "", + lienUrl3: "", + lienUrl4: "", + resume: "Solliciter de l'aide de la part d'un professionnel de santé", + texte1: "", + texte2: ",", + texteTitre1: "", + texteTitre2: "", + thematiques: [], + titre: "Mettre de côté tabac et alcool", + visuel: { + hash: "tabac_7755f320e9", + height: 788, + id: "226", + url: "https://backoffice-1000jours-preprod.dev.fabrique.social.gouv.fr/uploads/tabac_7755f320e9.png", + width: 940, + }, + }, + { + cartographie_pois_types: [ + { categorie: "", nom: "Maternité" }, + { categorie: "", nom: "PMI" }, + { categorie: "", nom: "Pédiatre" }, + { categorie: "", nom: "Sage-femme" }, + ], + enbrefIcone1: "", + enbrefIcone2: "", + enbrefIcone3: "", + enbrefTexte1: "", + enbrefTexte2: "", + enbrefTexte3: "", + id: 171, + leSaviezVous: "", + lienTitre1: "", + lienTitre2: "", + lienTitre3: "", + lienTitre4: "", + lienUrl1: "", + lienUrl2: "", + lienUrl3: "", + lienUrl4: "", + resume: "Solliciter de l'aide de la part d'un professionnel de santé", + texte1: "", + texte2: ",", + texteTitre1: "", + texteTitre2: "", + thematiques: [], + titre: "Mettre de côté tabac et alcool", + visuel: { + hash: "tabac_7755f320e9", + height: 788, + id: "226", + url: "https://backoffice-1000jours-preprod.dev.fabrique.social.gouv.fr/uploads/tabac_7755f320e9.png", + width: 940, + }, + }, + ]; + + afterEach(() => { + void AsyncStorage.clear(); + }); + + it("Should save POI types in local storage and return PoiType list", async () => { + const result = extractedPoiTypesFromArticles(articleList); + const poiTypeExpected: PoiType[] = [ + { categorie: "", nom: "Maternité" }, + { categorie: "", nom: "Maison de naissance" }, + { categorie: "", nom: "PMI" }, + { categorie: "", nom: "CPAM" }, + { categorie: "", nom: "Pédiatre" }, + { categorie: "", nom: "Sage-femme" }, + ]; + expect(result).toEqual(poiTypeExpected); + + // Check localStorage + const storedTypes = await StorageUtils.getObjectValue( + StorageKeysConstants.cartoFilterKey + ); + const expectedTypes = { + types: [ + "Maternité", + "Maison de naissance", + "PMI", + "CPAM", + "Pédiatre", + "Sage-femme", + ], + }; + expect(storedTypes).toEqual(expectedTypes); + }); + }); +}); diff --git a/front/src/utils/search.util.ts b/front/src/utils/search/search.util.ts similarity index 53% rename from front/src/utils/search.util.ts rename to front/src/utils/search/search.util.ts index 53c2d6214..040f99356 100644 --- a/front/src/utils/search.util.ts +++ b/front/src/utils/search/search.util.ts @@ -1,9 +1,11 @@ import type { PoiType } from "@socialgouv/nos1000jours-lib"; +import type { LatLng } from "react-native-maps"; -import { StorageKeysConstants } from "../constants"; -import type { CartoFilterStorage } from "../type"; -import type { Article } from "../types"; -import { storeObjectValue } from "./storage.util"; +import { AroundMeConstants, StorageKeysConstants } from "../../constants"; +import type { CartoFilterStorage } from "../../type"; +import type { Article } from "../../types"; +import * as AroundMeUtils from "../aroundMe/aroundMe.util"; +import { storeObjectValue } from "../storage.util"; export const extractedPoiTypesFromArticles = ( articles: Article[] @@ -30,3 +32,18 @@ export const extractedPoiTypesFromArticles = ( return finalCartographieTypes; }; + +export const getCoordinatesByPostalCode = async ( + postalCodeInput: string, + onInvalidPostalCode: () => void +): Promise => { + if ( + postalCodeInput.length !== AroundMeConstants.POSTAL_CODE_MAX_LENGTH || + isNaN(Number(postalCodeInput)) + ) { + onInvalidPostalCode(); + return; + } + + return AroundMeUtils.getPostalCodeCoords(postalCodeInput); +};