From 67f130fe59aa5e313de069b0166f3bc01f377ba0 Mon Sep 17 00:00:00 2001 From: Amanda Bullington Date: Thu, 4 Nov 2021 16:36:52 -0700 Subject: [PATCH] Code cleanup; add accessibility engine to test suite --- .eslintrc.js | 3 +- components/ObsDetails/DataTab.js | 2 +- components/ObsDetails/ObsDetails.js | 4 +- .../ObsDetails/hooks/fetchObsDetails.js | 192 ------------------ .../ObsDetails/hooks/fetchObsFromRealm.js | 4 +- components/Observations/EmptyList.js | 1 + components/Observations/ObsCard.js | 2 + components/Observations/ObsList.js | 4 +- .../Observations/__tests__/ObsCard.test.js | 26 ++- .../Observations/__tests__/ObsList.test.js | 19 +- ...sFromRealm.js => fetchObsListFromRealm.js} | 4 +- components/SharedComponents/Footer.js | 10 +- .../UserProfile/__tests__/UserProfile.test.js | 12 ++ components/UserProfile/hooks/fetchUser.js | 10 +- package-lock.json | 53 ++++- package.json | 1 + 16 files changed, 112 insertions(+), 235 deletions(-) delete mode 100644 components/ObsDetails/hooks/fetchObsDetails.js rename components/Observations/hooks/{fetchObsFromRealm.js => fetchObsListFromRealm.js} (97%) create mode 100644 components/UserProfile/__tests__/UserProfile.test.js diff --git a/.eslintrc.js b/.eslintrc.js index 6011f4cd4..4ebc237d7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,5 +10,6 @@ module.exports = { // need this so jest doesn't show as undefined in jest.setup.js env: { "jest": true - } + }, + ignorePatterns: ["/coverage/*"] }; diff --git a/components/ObsDetails/DataTab.js b/components/ObsDetails/DataTab.js index 55fcc48a5..459643e9c 100644 --- a/components/ObsDetails/DataTab.js +++ b/components/ObsDetails/DataTab.js @@ -3,7 +3,7 @@ import * as React from "react"; import { View, Text } from "react-native"; -import { textStyles, viewStyles } from "../../styles/obsDetails"; +// import { textStyles, viewStyles } from "../../styles/obsDetails"; import Map from "../SharedComponents/Map"; type Props = { diff --git a/components/ObsDetails/ObsDetails.js b/components/ObsDetails/ObsDetails.js index d87543d9d..cb7033b5d 100644 --- a/components/ObsDetails/ObsDetails.js +++ b/components/ObsDetails/ObsDetails.js @@ -9,7 +9,7 @@ import { useNavigation, useRoute } from "@react-navigation/core"; import { viewStyles, textStyles } from "../../styles/obsDetails"; // import useFetchObsDetails from "./hooks/fetchObsDetails"; -import useFetchObsFromRealm from "./hooks/fetchObsFromRealm"; +import useFetchObsDetailsFromRealm from "./hooks/fetchObsFromRealm"; import ActivityTab from "./ActivityTab"; import UserIcon from "../SharedComponents/UserIcon"; import PhotoScroll from "./PhotoScroll"; @@ -22,7 +22,7 @@ const ObsDetails = ( ): Node => { const { params } = useRoute( ); const uuid = params.obsId; - const observation = useFetchObsFromRealm( uuid ); + const observation = useFetchObsDetailsFromRealm( uuid ); const navToUserProfile = ( ) => navigation.navigate( "UserProfile" ); diff --git a/components/ObsDetails/hooks/fetchObsDetails.js b/components/ObsDetails/hooks/fetchObsDetails.js deleted file mode 100644 index 9f59bcaae..000000000 --- a/components/ObsDetails/hooks/fetchObsDetails.js +++ /dev/null @@ -1,192 +0,0 @@ -// // @flow - -// import { useEffect, useMemo, useCallback, useRef, useState } from "react"; -// import inatjs from "inaturalistjs"; -// import Realm from "realm"; - -// import realmConfig from "../../../models/index"; - -// const useFetchObsDetails = ( uuid: string ): Object => { -// const [comments, setComments] = useState( [] ); -// const [fetched, setFetched] = useState( false ); -// const [ids, setIds] = useState( [] ); -// const [photos, setPhotos] = useState( [] ); -// const realmRef = useRef( null ); - -// const openRealm = useCallback( async ( ) => { -// try { -// const realm = await Realm.open( realmConfig ); -// realmRef.current = realm; -// } -// catch ( err ) { -// console.error( "Error opening realm: ", err.message ); -// } -// }, [realmRef] ); - -// const closeRealm = useCallback( ( ) => { -// const realm = realmRef.current; -// realm?.close( ); -// realmRef.current = null; -// // setComments( [] ); -// }, [realmRef] ); - -// useEffect( ( ) => { -// openRealm( ); - -// // Return a cleanup callback to close the realm to prevent memory leaks -// return closeRealm; -// }, [openRealm, closeRealm] ); - -// const FIELDS = useMemo( ( ) => { -// // similar fields as web: -// // https://github.com/inaturalist/inaturalist/blob/df6572008f60845b8ef5972a92a9afbde6f67829/app/webpack/observations/show/ducks/observation.js -// const TAXON_FIELDS = { -// ancestry: true, -// ancestor_ids: true, -// ancestors: { -// id: true, -// uuid: true, -// name: true, -// iconic_taxon_name: true, -// is_active: true, -// preferred_common_name: true, -// rank: true, -// rank_level: true -// }, -// default_photo: { -// attribution: true, -// license_code: true, -// url: true, -// square_url: true -// }, -// iconic_taxon_name: true, -// id: true, -// is_active: true, -// name: true, -// preferred_common_name: true, -// rank: true, -// rank_level: true -// }; - -// const PHOTO_FIELDS = { -// id: true, -// uuid: true, -// url: true, -// license_code: true -// }; - -// const USER_FIELDS = { -// login: true, -// icon_url: true -// }; - -// const MODERATOR_ACTION_FIELDS = { -// action: true, -// id: true, -// created_at: true, -// reason: true, -// user: USER_FIELDS -// }; - -// const ID_FIELDS = { -// body: true, -// category: true, -// created_at: true, -// current: true, -// disagreement: true, -// flags: { id: true }, -// hidden: true, -// moderator_actions: MODERATOR_ACTION_FIELDS, -// previous_observation_taxon: TAXON_FIELDS, -// spam: true, -// taxon: TAXON_FIELDS, -// taxon_change: { id: true, type: true }, -// updated_at: true, -// user: Object.assign( { }, USER_FIELDS, { id: true } ), -// uuid: true, -// vision: true -// }; - -// const COMMENT_FIELDS = { -// body: true, -// created_at: true, -// id: true, -// user: USER_FIELDS -// }; - -// return { -// comments: COMMENT_FIELDS, -// identifications: ID_FIELDS, -// photos: PHOTO_FIELDS -// }; -// }, [] ); - -// const createIdentificationForRealm = ( id ) => ( { -// uuid: id.uuid, -// body: id.body, -// category: id.category, -// commonName: id.taxon.preferred_common_name, -// createdAt: id.created_at, -// id: id.id, -// name: id.taxon.name, -// rank: id.taxon.rank, -// taxonPhoto: id.taxon.default_photo.square_url, -// userIcon: id.user.icon_url, -// userLogin: id.user.login, -// vision: id.vision -// } ); - -// const writeToDatabase = useCallback( ( results ) => { -// if ( results.length === 0 ) { -// return; -// } -// const realm = realmRef.current; -// results.forEach( id => { -// const newId = createIdentificationForRealm( id ); -// realm?.write( ( ) => { -// const linkedObservation = realm.objects( "Observation" ).filtered( `uuid = '${uuid}'` ); -// console.log( linkedObservation, "linked obs" ); -// // linkedObservation.identifications.push( newId ); -// } ); -// } ); -// }, [uuid] ); - -// useEffect( ( ) => { -// let isCurrent = true; -// const fetchIds = async ( ) => { -// try { -// const params = { -// per_page: 50, -// fields: FIELDS -// }; -// const response = await inatjs.observations.fetch( [uuid], params ); -// const results = response.results; -// const obsIds = results[0].identifications; -// const obsPhotos = results[0].photos; -// // const obsComments = results[0].comments; -// if ( !isCurrent || obsIds.length === 0 || fetched ) { return; } -// setIds( obsIds ); -// writeToDatabase( obsIds ); -// setPhotos( obsPhotos ); -// // setComments( obsComments ); -// setFetched( true ); -// // writeToDatabase( results ); -// } catch ( e ) { -// if ( !isCurrent ) { return; } -// console.log( e, "couldn't save ids to realm" ); -// } -// }; - -// fetchIds( ); -// return ( ) => { -// isCurrent = false; -// }; -// }, [FIELDS, comments, uuid, fetched, writeToDatabase] ); - -// return { -// ids, -// photos -// }; -// }; - -// export default useFetchObsDetails; diff --git a/components/ObsDetails/hooks/fetchObsFromRealm.js b/components/ObsDetails/hooks/fetchObsFromRealm.js index 517d360f1..012170bd8 100644 --- a/components/ObsDetails/hooks/fetchObsFromRealm.js +++ b/components/ObsDetails/hooks/fetchObsFromRealm.js @@ -5,7 +5,7 @@ import Realm from "realm"; import realmConfig from "../../../models/index"; -const useFetchObsFromRealm = ( uuid: string ): Object => { +const useFetchObsDetailsFromRealm = ( uuid: string ): Object => { const [observation, setObservation] = useState( null ); const realmRef = useRef( null ); @@ -37,4 +37,4 @@ const useFetchObsFromRealm = ( uuid: string ): Object => { return observation; }; -export default useFetchObsFromRealm; +export default useFetchObsDetailsFromRealm; diff --git a/components/Observations/EmptyList.js b/components/Observations/EmptyList.js index 14f013304..78cc0b18c 100644 --- a/components/Observations/EmptyList.js +++ b/components/Observations/EmptyList.js @@ -16,6 +16,7 @@ const EmptyList = ( ): Node => { learn more diff --git a/components/Observations/ObsCard.js b/components/Observations/ObsCard.js index 863c4504c..ab9814f3a 100644 --- a/components/Observations/ObsCard.js +++ b/components/Observations/ObsCard.js @@ -16,6 +16,8 @@ const ObsCard = ( { item, handlePress }: Props ): Node => ( onPress={( ) => handlePress( item )} style={viewStyles.row} testID="ObsList.obsCard" + accessibilityRole="link" + accessibilityLabel="Navigate to observation details screen" > { const navigation = useNavigation( ); const navToObsDetails = observation => navigation.navigate( "ObsDetails", { obsId: observation.uuid } ); - const localObservations = useFetchObsFromRealm( ); + const localObservations = useFetchObsListFromRealm( ); // this custom hook fetches on first component render // (and anytime you save while in debug - hot reloading mode ) useFetchObservations( ); diff --git a/components/Observations/__tests__/ObsCard.test.js b/components/Observations/__tests__/ObsCard.test.js index 00cce14a0..2f075fab7 100644 --- a/components/Observations/__tests__/ObsCard.test.js +++ b/components/Observations/__tests__/ObsCard.test.js @@ -1,20 +1,23 @@ import React from "react"; import { fireEvent, render } from "@testing-library/react-native"; +import AccessibilityEngine from "react-native-accessibility-engine"; import ObsCard from "../ObsCard"; +const testObservation = { + userPhoto: "amazon_url", + commonName: "Insects", + placeGuess: "SF", + timeObservedAt: "May 1, 2021", + identificationCount: 3, + commentCount: 0, + qualityGrade: "research" +}; + test( "renders text passed into observation card", ( ) => { const { getByTestId, getByText } = render( ); @@ -45,3 +48,8 @@ test( "handles button press", ( ) => { fireEvent.press( button ); expect( fakeNavigation.navigate ).toBeCalledWith( "ObsDetails" ); } ); + +test( "should not have accessibility errors", ( ) => { + const obsCard = ; + expect( ( ) => AccessibilityEngine.check( obsCard ) ).not.toThrow(); +} ); diff --git a/components/Observations/__tests__/ObsList.test.js b/components/Observations/__tests__/ObsList.test.js index 6944befcb..c76e16f2b 100644 --- a/components/Observations/__tests__/ObsList.test.js +++ b/components/Observations/__tests__/ObsList.test.js @@ -1,10 +1,10 @@ -// import React from "react"; -// import { FlatList } from "react-native"; -// import { render, waitForElement } from "@testing-library/react-native"; -// import { NavigationContainer } from "@react-navigation/native"; +import React from "react"; +import { render } from "@testing-library/react-native"; +import { NavigationContainer } from "@react-navigation/native"; import inatjs from "inaturalistjs"; +import AccessibilityEngine from "react-native-accessibility-engine"; -// import ObsList from "../ObsList"; +import ObsList from "../ObsList"; // import EmptyList from "../EmptyList"; // test( "it renders all inputs as expected", ( ) => { @@ -48,4 +48,11 @@ test( "searches using the passed in parameters", async ( ) => { expect( response ).toEqual( identifications ); } ); - +test( "should not have accessibility errors", ( ) => { + const obsList = ( + + + + ); + expect( ( ) => AccessibilityEngine.check( obsList ) ).not.toThrow(); +} ); diff --git a/components/Observations/hooks/fetchObsFromRealm.js b/components/Observations/hooks/fetchObsListFromRealm.js similarity index 97% rename from components/Observations/hooks/fetchObsFromRealm.js rename to components/Observations/hooks/fetchObsListFromRealm.js index d204adafc..4dcbce4ca 100644 --- a/components/Observations/hooks/fetchObsFromRealm.js +++ b/components/Observations/hooks/fetchObsListFromRealm.js @@ -5,7 +5,7 @@ import Realm from "realm"; import realmConfig from "../../../models/index"; -const useFetchObsFromRealm = ( ): Array => { +const useFetchObsListFromRealm = ( ): Array => { // The tasks will be set once the realm has opened and the collection has been queried. const [observations, setObservations] = useState( [] ); // We store a reference to our realm using useRef that allows us to access it via @@ -73,4 +73,4 @@ const useFetchObsFromRealm = ( ): Array => { return observations; }; -export default useFetchObsFromRealm; +export default useFetchObsListFromRealm; diff --git a/components/SharedComponents/Footer.js b/components/SharedComponents/Footer.js index 4ae8fa337..a0cb95ae2 100644 --- a/components/SharedComponents/Footer.js +++ b/components/SharedComponents/Footer.js @@ -13,19 +13,19 @@ const Footer = ( ): React.Node => { return ( - + menu - + explore - + camera - + obs list - + notifications diff --git a/components/UserProfile/__tests__/UserProfile.test.js b/components/UserProfile/__tests__/UserProfile.test.js new file mode 100644 index 000000000..f506789e9 --- /dev/null +++ b/components/UserProfile/__tests__/UserProfile.test.js @@ -0,0 +1,12 @@ +import React from "react"; +import AccessibilityEngine from "react-native-accessibility-engine"; + +import UserProfile from "../UserProfile"; + +test( "should not have accessibility errors", ( ) => { + // need to mock inatjs fetch + // right now this throws Cannot read property 'then' of undefined error + // because promise / results from iNatAPI are undefined + const userProfile = ; + expect( ( ) => AccessibilityEngine.check( userProfile ) ).not.toThrow(); +} ); diff --git a/components/UserProfile/hooks/fetchUser.js b/components/UserProfile/hooks/fetchUser.js index 4a2a06669..4ce06ca1b 100644 --- a/components/UserProfile/hooks/fetchUser.js +++ b/components/UserProfile/hooks/fetchUser.js @@ -14,14 +14,14 @@ const useFetchUser = ( ): Object => { name: true }; - return { - user: USER_FIELDS + return { + user: USER_FIELDS }; }, [] ); useEffect( ( ) => { let isCurrent = true; - const fetchObservations = async ( ) => { + const fetchUserProfile = async ( ) => { try { const testId = 1132118; const ids = `${testId}?fields=name,login,icon_url,created_at,roles,site_id`; @@ -31,11 +31,11 @@ const useFetchUser = ( ): Object => { setUser( results[0] ); } catch ( e ) { if ( !isCurrent ) { return; } - console.log( "Couldn't fetch user:", e.message, ); + console.log( "Couldn't fetch user:", e.message ); } }; - fetchObservations( ); + fetchUserProfile( ); return ( ) => { isCurrent = false; }; diff --git a/package-lock.json b/package-lock.json index 6bc71fcdf..920242dcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "jest": "^26.6.3", "jest-fetch-mock": "^3.0.3", "metro-react-native-babel-preset": "^0.66.0", + "react-native-accessibility-engine": "^0.6.0", "react-native-clean-project": "^3.6.7", "react-native-codegen": "^0.0.7", "react-test-renderer": "17.0.2" @@ -10701,12 +10702,12 @@ } }, "node_modules/jsx-ast-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", - "integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz", + "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==", "dev": true, "dependencies": { - "array-includes": "^3.1.2", + "array-includes": "^3.1.3", "object.assign": "^4.1.2" }, "engines": { @@ -10796,6 +10797,12 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -12622,6 +12629,20 @@ "react": "17.0.2" } }, + "node_modules/react-native-accessibility-engine": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/react-native-accessibility-engine/-/react-native-accessibility-engine-0.6.0.tgz", + "integrity": "sha512-Ey3D/VlGzJQ6YGYWUHtGexRufuSLYw4N4SgiSE/AF79YZCJ94a01eYGDNyJaVUhnHVWHMxXxZV+Vsd1c8/J7rw==", + "dev": true, + "dependencies": { + "lodash.groupby": "^4.6.0", + "react-test-renderer": "^17.0.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-clean-project": { "version": "3.6.7", "resolved": "https://registry.npmjs.org/react-native-clean-project/-/react-native-clean-project-3.6.7.tgz", @@ -23718,12 +23739,12 @@ } }, "jsx-ast-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", - "integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz", + "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==", "dev": true, "requires": { - "array-includes": "^3.1.2", + "array-includes": "^3.1.3", "object.assign": "^4.1.2" } }, @@ -23795,6 +23816,12 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, + "lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=", + "dev": true + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -25250,6 +25277,16 @@ } } }, + "react-native-accessibility-engine": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/react-native-accessibility-engine/-/react-native-accessibility-engine-0.6.0.tgz", + "integrity": "sha512-Ey3D/VlGzJQ6YGYWUHtGexRufuSLYw4N4SgiSE/AF79YZCJ94a01eYGDNyJaVUhnHVWHMxXxZV+Vsd1c8/J7rw==", + "dev": true, + "requires": { + "lodash.groupby": "^4.6.0", + "react-test-renderer": "^17.0.1" + } + }, "react-native-clean-project": { "version": "3.6.7", "resolved": "https://registry.npmjs.org/react-native-clean-project/-/react-native-clean-project-3.6.7.tgz", diff --git a/package.json b/package.json index 77b976cde..41ecf58f0 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "jest": "^26.6.3", "jest-fetch-mock": "^3.0.3", "metro-react-native-babel-preset": "^0.66.0", + "react-native-accessibility-engine": "^0.6.0", "react-native-clean-project": "^3.6.7", "react-native-codegen": "^0.0.7", "react-test-renderer": "17.0.2"