diff --git a/android/app/src/main/assets/fonts/INatIcon.ttf b/android/app/src/main/assets/fonts/INatIcon.ttf index 1b7c6fe5b..b9ea17789 100644 Binary files a/android/app/src/main/assets/fonts/INatIcon.ttf and b/android/app/src/main/assets/fonts/INatIcon.ttf differ diff --git a/android/link-assets-manifest.json b/android/link-assets-manifest.json index addc1a356..737c3193a 100644 --- a/android/link-assets-manifest.json +++ b/android/link-assets-manifest.json @@ -3,7 +3,7 @@ "data": [ { "path": "assets/fonts/INatIcon.ttf", - "sha1": "941a07384e3f0c242dd6717e1b01a2afcd863ca9" + "sha1": "5ee81524df59ba0d7ef0efafdf940f528a8ee5af" }, { "path": "assets/fonts/Whitney-BookItalic-Pro.otf", diff --git a/assets/fonts/INatIcon.ttf b/assets/fonts/INatIcon.ttf index 1b7c6fe5b..b9ea17789 100644 Binary files a/assets/fonts/INatIcon.ttf and b/assets/fonts/INatIcon.ttf differ diff --git a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj index 7929209cf..4086ebd64 100644 --- a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj +++ b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ BA2479FA3D7B40A7BEF7B3CD /* Whitney-Medium-Pro.otf in Resources */ = {isa = PBXBuildFile; fileRef = D09FA3A0162844FF80A5EF96 /* Whitney-Medium-Pro.otf */; }; D1A158A7F6C9E77B651BB4AA /* libPods-iNaturalistReactNative-ShareExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADBDD0D061046941F61CA31D /* libPods-iNaturalistReactNative-ShareExtension.a */; }; F005F5581901496B8576FB4E /* BuildFile in Resources */ = {isa = PBXBuildFile; }; + 41DE3088F97E402FBC6E6DD8 /* INatIcon.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 63D7A2871FF24F76861F64DD /* INatIcon.ttf */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -89,6 +90,7 @@ DEB901D3F8FB2DAE1CCED0CC /* Pods-iNaturalistReactNative-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iNaturalistReactNative-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-iNaturalistReactNative-ShareExtension/Pods-iNaturalistReactNative-ShareExtension.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; EE004FD2EC174086A7AB2908 /* inaturalisticons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = inaturalisticons.ttf; path = ../assets/fonts/inaturalisticons.ttf; sourceTree = ""; }; + 63D7A2871FF24F76861F64DD /* INatIcon.ttf */ = {isa = PBXFileReference; name = "INatIcon.ttf"; path = "../assets/fonts/INatIcon.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -226,6 +228,7 @@ D09FA3A0162844FF80A5EF96 /* Whitney-Medium-Pro.otf */, 374CB22E29943E63005885ED /* Whitney-BookItalic-Pro.otf */, EE004FD2EC174086A7AB2908 /* inaturalisticons.ttf */, + 63D7A2871FF24F76861F64DD /* INatIcon.ttf */, ); name = Resources; sourceTree = ""; @@ -356,6 +359,7 @@ BA2479FA3D7B40A7BEF7B3CD /* Whitney-Medium-Pro.otf in Resources */, 4FB3B444D46A4115B867B9CC /* inaturalisticons.ttf in Resources */, F005F5581901496B8576FB4E /* BuildFile in Resources */, + 41DE3088F97E402FBC6E6DD8 /* INatIcon.ttf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/link-assets-manifest.json b/ios/link-assets-manifest.json index addc1a356..737c3193a 100644 --- a/ios/link-assets-manifest.json +++ b/ios/link-assets-manifest.json @@ -3,7 +3,7 @@ "data": [ { "path": "assets/fonts/INatIcon.ttf", - "sha1": "941a07384e3f0c242dd6717e1b01a2afcd863ca9" + "sha1": "5ee81524df59ba0d7ef0efafdf940f528a8ee5af" }, { "path": "assets/fonts/Whitney-BookItalic-Pro.otf", diff --git a/src/components/ObsDetails/DQAVoteButtons.js b/src/components/ObsDetails/DQAVoteButtons.js index fcd77daf6..3f772ff0e 100644 --- a/src/components/ObsDetails/DQAVoteButtons.js +++ b/src/components/ObsDetails/DQAVoteButtons.js @@ -54,6 +54,7 @@ const DQAVoteButtons = ( { if ( userAgrees ) { return ( setVote( metric, true )} @@ -78,6 +80,7 @@ const DQAVoteButtons = ( { if ( userAgrees === null ) { return ( setVote( metric, false )} @@ -87,6 +90,7 @@ const DQAVoteButtons = ( { if ( !userAgrees ) { return ( setVote( metric, false )} diff --git a/src/components/ObsDetails/DataQualityAssessment.js b/src/components/ObsDetails/DataQualityAssessment.js index e7a79e2b3..8b2677dd4 100644 --- a/src/components/ObsDetails/DataQualityAssessment.js +++ b/src/components/ObsDetails/DataQualityAssessment.js @@ -176,10 +176,20 @@ const DataQualityAssessment = ( ): React.Node => { const ifAgree = ifMajorityAgree( metric ); if ( ifAgree || ifAgree === null ) { return ( - ); + + ); } return ( - + ); }; @@ -221,7 +231,11 @@ const DataQualityAssessment = ( ): React.Node => { ); } return ( - + ); }; diff --git a/src/images/icons/withdraw-id.svg b/src/images/icons/withdraw-id.svg index 7d9d8b898..058a3b233 100644 --- a/src/images/icons/withdraw-id.svg +++ b/src/images/icons/withdraw-id.svg @@ -1,4 +1,8 @@ - - - + + + + + diff --git a/tests/unit/components/ObsDetails/DataQualityAssessment.test.js b/tests/unit/components/ObsDetails/DataQualityAssessment.test.js new file mode 100644 index 000000000..4aa4a3742 --- /dev/null +++ b/tests/unit/components/ObsDetails/DataQualityAssessment.test.js @@ -0,0 +1,252 @@ +import { faker } from "@faker-js/faker"; +import { fireEvent, screen } from "@testing-library/react-native"; +import DataQualityAssessment from "components/ObsDetails/DataQualityAssessment"; +import DQAVoteButtons from "components/ObsDetails/DQAVoteButtons"; +import initI18next from "i18n/initI18next"; +import { t } from "i18next"; +import React from "react"; +import { View } from "react-native"; + +import factory from "../../../factory"; +import { renderComponent } from "../../../helpers/render"; + +jest.mock( "sharedHooks/useIsConnected", ( ) => ( { + __esModule: true, + default: ( ) => true +} ) ); + +const mockObservation = factory( "LocalObservation", { + created_at: "2022-11-27T19:07:41-08:00", + time_observed_at: "2023-12-14T21:07:41-09:30", + observed_on: "2023-12-14T21:07:41-09:30", + taxon: factory( "LocalTaxon", { + id: "1234", + rank_level: 10 + } ), + observationPhotos: [ + factory( "LocalObservationPhoto", { + photo: { + id: faker.datatype.number( ), + attribution: faker.lorem.sentence( ), + licenseCode: "cc-by-nc", + url: faker.image.imageUrl( ) + } + } ) + ], + identifications: [factory( "LocalIdentification", { + taxon: factory( "LocalTaxon", { + id: "1234", + rank_level: 10 + } ) + } )], + latitude: Number( faker.address.latitude( ) ), + longitude: Number( faker.address.longitude( ) ), + description: faker.lorem.paragraph( ), + quality_grade: "casual" +} ); + +const mockQualityMetrics = [ + { + id: 0, + agree: true, + metric: "wild", + user_id: "0" + } +]; + +const mockObservationObject = { + date: mockObservation.observed_on, + location: [mockObservation.latitude, mockObservation.longitude], + evidence: mockObservation.observationPhotos, + taxon: { + id: mockObservation.taxon.id, + rank_level: mockObservation.taxon.rank_level + }, + identifications: mockObservation.identifications +}; + +const mockMutate = jest.fn(); +jest.mock( "sharedHooks/useAuthenticatedMutation", () => ( { + __esModule: true, + default: () => ( { + mutate: mockMutate + + } ) +} ) ); + +const mockAttribution = ; +jest.mock( "components/ObsDetails/Attribution", () => ( { + __esModule: true, + default: () => mockAttribution +} ) ); + +jest.mock( "@react-navigation/native", () => { + const actualNav = jest.requireActual( "@react-navigation/native" ); + return { + ...actualNav, + useRoute: () => ( { + params: { + observationUUID: mockObservation.uuid, + observation: mockObservationObject, + qualityGrade: mockObservation.quality_grade + } + } ) + }; +} ); + +describe( "Data Quality Assessment", ( ) => { + beforeAll( async ( ) => { + await initI18next( ); + } ); + test( "renders correct quality grade status", async ( ) => { + renderComponent( ); + + const qualityGrade = await screen.findByTestId( + `QualityGrade.${mockObservation.quality_grade}` + ); + expect( qualityGrade ).toBeTruthy( ); + } ); + test( "renders correct quality grade status title", async ( ) => { + renderComponent( ); + + const qualityGrade = await screen.findByText( + t( "Data-quality-assessment-title-casual" ) + ); + expect( qualityGrade ).toBeTruthy( ); + } ); + test( "renders correct quality grade status description", async ( ) => { + renderComponent( ); + + const qualityGrade = await screen.findByText( + t( "Data-quality-assessment-description-casual" ) + ); + expect( qualityGrade ).toBeTruthy( ); + } ); + test( "renders correct metric titles", async ( ) => { + renderComponent( ); + + const dateSpecified = await screen.findByText( + t( "Data-quality-assessment-date-specified" ) + ); + const locationSpecified = await screen.findByText( + t( "Data-quality-assessment-location-specified" ) + ); + const photosAndSounds = await screen.findByText( + t( "Data-quality-assessment-has-photos-or-sounds" ) + ); + const idSupportedTwoOrMore = await screen.findByText( + t( "Data-quality-assessment-id-supported-by-two-or-more" ) + ); + const communityTaxonSpeciesLevel = await screen.findByText( + t( "Data-quality-assessment-community-taxon-species-level-or-lower" ) + ); + expect( dateSpecified ).toBeTruthy( ); + expect( locationSpecified ).toBeTruthy( ); + expect( photosAndSounds ).toBeTruthy( ); + expect( idSupportedTwoOrMore ).toBeTruthy( ); + expect( communityTaxonSpeciesLevel ).toBeTruthy( ); + } ); + + test( "renders correct metric vote titles", async ( ) => { + renderComponent( ); + + const dateAccurate = await screen.findByText( + t( "Data-quality-assessment-date-is-accurate" ) + ); + const locationAccurate = await screen.findByText( + t( "Data-quality-assessment-location-is-accurate" ) + ); + const organismWild = await screen.findByText( + t( "Data-quality-assessment-organism-is-wild" ) + ); + const organismEvidence = await screen.findByText( + t( "Data-quality-assessment-evidence-of-organism" ) + ); + const recentEvidence = await screen.findByText( + t( "Data-quality-assessment-recent-evidence-of-organism" ) + ); + expect( dateAccurate ).toBeTruthy( ); + expect( locationAccurate ).toBeTruthy( ); + expect( organismWild ).toBeTruthy( ); + expect( organismEvidence ).toBeTruthy( ); + expect( recentEvidence ).toBeTruthy( ); + } ); + test( "renders about section", async ( ) => { + renderComponent( ); + + const title = await screen.findByText( + t( "ABOUT-THE-DQA" ) + ); + const description = await screen.findByText( + t( "About-the-DQA-description" ) + ); + expect( title ).toBeTruthy( ); + expect( description ).toBeTruthy( ); + } ); +} ); + +describe( "DQA Vote Buttons", ( ) => { + beforeAll( async ( ) => { + await initI18next( ); + } ); + test( "renders DQA vote buttons", async ( ) => { + renderComponent( ); + + const emptyDisagreeButtons = await screen.findAllByTestId( "DQAVoteButton.EmptyDisagree" ); + fireEvent.press( emptyDisagreeButtons[0] ); + + expect( await mockMutate ).toHaveBeenCalled(); + } ); + + test( "calls api when DQA disagree button is pressed", async ( ) => { + renderComponent( ); + + const emptyDisagreeButtons = await screen.findAllByTestId( "DQAVoteButton.EmptyDisagree" ); + fireEvent.press( emptyDisagreeButtons[0] ); + + expect( await mockMutate ).toHaveBeenCalled(); + } ); + + test( "calls api when DQA agree button is pressed", async ( ) => { + renderComponent( ); + + const emptyDisagreeButtons = await screen.findAllByTestId( "DQAVoteButton.EmptyAgree" ); + fireEvent.press( emptyDisagreeButtons[0] ); + + expect( await mockMutate ).toHaveBeenCalled(); + } ); + + test( "renders correct DQA user vote", async ( ) => { + renderComponent( ); + + const button = await screen.findByTestId( + "DQAVoteButton.UserAgree" + ); + expect( button ).toBeTruthy( ); + } ); + + test( "renders correct DQA user vote number", async ( ) => { + renderComponent( ); + + const voteNumber = await screen.findByText( + "1" + ); + expect( voteNumber ).toBeTruthy( ); + } ); +} );