Skip to content

Commit

Permalink
Add functionality for advanced iNaturalist mode (#2591)
Browse files Browse the repository at this point in the history
* Add functionality for advanced iNaturalist mode

* Fix language settings test by toggling advanced mode on

* Fix e2e tests by adding advanced user toggle

* Fix more tests in Settings with advanced toggle
  • Loading branch information
albullington authored Dec 27, 2024
1 parent 31ce899 commit 1c59f89
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 82 deletions.
4 changes: 4 additions & 0 deletions e2e/sharedFlows/switchPowerMode.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export default async function switchPowerMode() {
const settingsDrawerMenuItem = element( by.id( "settings" ) );
await waitFor( settingsDrawerMenuItem ).toBeVisible().withTimeout( 10000 );
await settingsDrawerMenuItem.tap();
// Tap the settings radio button for advanced interface mode
const advancedInterfaceRadioButton = element( by.id( "advanced-interface-option" ) );
await waitFor( advancedInterfaceRadioButton ).toBeVisible().withTimeout( 10000 );
await advancedInterfaceRadioButton.tap();
// Tap the settings radio button for power user mode
const powerUserRadioButton = element( by.id( "all-observation-option" ) );
await waitFor( powerUserRadioButton ).toBeVisible().withTimeout( 10000 );
Expand Down
7 changes: 4 additions & 3 deletions src/components/Settings/LanguageSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Heading4,
PickerSheet
} from "components/SharedComponents";
import { View } from "components/styledComponents";
import _ from "lodash";
import React, { useEffect, useState } from "react";
import changeLanguage from "sharedHelpers/changeLanguage.ts";
Expand Down Expand Up @@ -51,8 +52,8 @@ const LanguageSetting = ( { onChange }: Props ) => {
}

return (
<>
<Heading4 className="mt-7">{t( "APP-LANGUAGE" )}</Heading4>
<View className="mb-9">
<Heading4>{t( "APP-LANGUAGE" )}</Heading4>
<Button
className="mt-4"
text={t( "CHANGE-APP-LANGUAGE" )}
Expand All @@ -75,7 +76,7 @@ const LanguageSetting = ( { onChange }: Props ) => {
pickerValues={webLocalesOptions}
/>
)}
</>
</View>
);
};

Expand Down
157 changes: 84 additions & 73 deletions src/components/Settings/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,9 @@ const Settings = ( ) => {
updateUserMutation.mutate( payload );
}, [settings?.id, updateUserMutation] );

const renderLoggedIn = ( ) => (
<View>
{( isSaving || isLoading ) && (
<View className="absolute z-10 bg-white/80
w-full h-full flex items-center justify-center"
>
<ActivityIndicator size={50} />
</View>
)}
<Heading4 className="mt-7">{t( "TAXON-NAMES-DISPLAY" )}</Heading4>
const renderTaxonNamesSection = ( ) => (
<View className="mb-9">
<Heading4>{t( "TAXON-NAMES-DISPLAY" )}</Heading4>
<Body2 className="mt-3">{t( "This-is-how-taxon-names-will-be-displayed" )}</Body2>
<View className="mt-[22px]">
<RadioButtonRow
Expand All @@ -180,6 +173,19 @@ const Settings = ( ) => {
label={t( "Scientific-Name" )}
/>
</View>
</View>
);

const renderLoggedIn = ( ) => (
<View>
{( isSaving || isLoading ) && (
<View className="absolute z-10 bg-white/80
w-full h-full flex items-center justify-center"
>
<ActivityIndicator size={50} />
</View>
)}
{!isDefaultMode && renderTaxonNamesSection( )}
<LanguageSetting
onChange={newLocale => {
QueueItem.enqueue(
Expand All @@ -194,93 +200,98 @@ const Settings = ( ) => {
);
}}
/>
<Heading4 className="mt-7">{t( "INATURALIST-ACCOUNT-SETTINGS" )}</Heading4>
<Body2 className="mt-2">{t( "Edit-your-profile-change-your-settings" )}</Body2>
<Button
className="mt-4"
text={t( "ACCOUNT-SETTINGS" )}
onPress={() => {
confirmInternetConnection( );
if ( !isConnected ) { return; }
setShowingWebViewSettings( true );
<View>
<Heading4>{t( "INATURALIST-ACCOUNT-SETTINGS" )}</Heading4>
<Body2 className="mt-2">{t( "Edit-your-profile-change-your-settings" )}</Body2>
<Button
className="mt-4"
text={t( "ACCOUNT-SETTINGS" )}
onPress={() => {
confirmInternetConnection( );
if ( !isConnected ) { return; }
setShowingWebViewSettings( true );

navigation.navigate( "FullPageWebView", {
title: t( "ACCOUNT-SETTINGS" ),
loggedIn: true,
initialUrl: SETTINGS_URL,
blurEvent: FINISHED_WEB_SETTINGS,
clickablePathnames: ["/users/delete"],
skipSetSourceInShouldStartLoadWithRequest: true,
shouldLoadUrl: url => {
async function signOutGoHome() {
Alert.alert(
t( "Account-Deleted" ),
t( "It-may-take-up-to-an-hour-to-remove-content" )
);
// sign out
await signOut( { realm, clearRealm: true, queryClient } );
// navigate to My Obs
navigation.navigate( "ObsList" );
}
// If the webview navigates to a URL that indicates the account
// was deleted, sign the current user out of the app
if ( url === `${Config.OAUTH_API_URL}/?account_deleted=true` ) {
signOutGoHome( );
return false;
navigation.navigate( "FullPageWebView", {
title: t( "ACCOUNT-SETTINGS" ),
loggedIn: true,
initialUrl: SETTINGS_URL,
blurEvent: FINISHED_WEB_SETTINGS,
clickablePathnames: ["/users/delete"],
skipSetSourceInShouldStartLoadWithRequest: true,
shouldLoadUrl: url => {
async function signOutGoHome() {
Alert.alert(
t( "Account-Deleted" ),
t( "It-may-take-up-to-an-hour-to-remove-content" )
);
// sign out
await signOut( { realm, clearRealm: true, queryClient } );
// navigate to My Obs
navigation.navigate( "ObsList" );
}
// If the webview navigates to a URL that indicates the account
// was deleted, sign the current user out of the app
if ( url === `${Config.OAUTH_API_URL}/?account_deleted=true` ) {
signOutGoHome( );
return false;
}
return true;
}
return true;
}
} );
}}
accessibilityLabel={t( "INATURALIST-SETTINGS" )}
/>
} );
}}
accessibilityLabel={t( "INATURALIST-SETTINGS" )}
/>
</View>
</View>
);

return (
<ScrollViewWrapper>
<StatusBar barStyle="dark-content" />
<View className="p-5">
<View className="mb-5">
<Heading4>{t( "INATURALIST-INTERFACE-MODE" )}</Heading4>
<View className="mt-[22px] pr-5">
<View className="mb-9">
<Heading4>{t( "INATURALIST-MODE" )}</Heading4>
<View className="mt-[22px]">
<RadioButtonRow
smallLabel
checked={isDefaultMode}
onPress={( ) => setIsDefaultMode( true )}
label={t( "Default--interface-mode" )}
/>
</View>
<View className="mt-4 pr-5">
<View className="mt-4">
<RadioButtonRow
testID="advanced-interface-option"
smallLabel
checked={!isDefaultMode}
onPress={( ) => setIsDefaultMode( false )}
label={t( "Advanced--interface-mode" )}
label={t( "Advanced--interface-mode-with-explainer" )}
/>
</View>
</View>
<View className="mb-5">
<Heading4>{t( "OBSERVATION-BUTTON" )}</Heading4>
<Body2 className="mt-3">{t( "When-tapping-the-green-observation-button" )}</Body2>
<View className="mt-[22px] pr-5">
<RadioButtonRow
smallLabel
checked={!isAllAddObsOptionsMode}
onPress={() => setIsAllAddObsOptionsMode( false )}
label={t( "iNaturalist-AI-Camera" )}
/>
</View>
<View className="mt-4 pr-5">
<RadioButtonRow
testID="all-observation-option"
smallLabel
checked={isAllAddObsOptionsMode}
onPress={() => setIsAllAddObsOptionsMode( true )}
label={t( "All-observation-option" )}
/>
{!isDefaultMode && (
<View className="mb-9">
<Heading4>{t( "OBSERVATION-BUTTON" )}</Heading4>
<Body2 className="mt-3">{t( "When-tapping-the-green-observation-button" )}</Body2>
<View className="mt-[22px] pr-5">
<RadioButtonRow
smallLabel
checked={!isAllAddObsOptionsMode}
onPress={() => setIsAllAddObsOptionsMode( false )}
label={t( "iNaturalist-AI-Camera" )}
/>
</View>
<View className="mt-4 pr-5">
<RadioButtonRow
testID="all-observation-option"
smallLabel
checked={isAllAddObsOptionsMode}
onPress={() => setIsAllAddObsOptionsMode( true )}
label={t( "All-observation-option" )}
/>
</View>
</View>
</View>
)}
{currentUser && renderLoggedIn( )}
</View>
</ScrollViewWrapper>
Expand Down
4 changes: 2 additions & 2 deletions src/i18n/l10n/en.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Add-optional-notes = Add optional notes
Adds-your-vote-of-agreement = Adds your vote of agreement
# Hint for a button that adds a vote of disagreement
Adds-your-vote-of-disagreement = Adds your vote of disagreement
Advanced--interface-mode = Advanced
Advanced--interface-mode-with-explainer = Advanced (Upload multiple photos and sounds)
Affiliation = Affiliation: { $site }
# Label for button that adds an identification of the same taxon as another identification
Agree = Agree
Expand Down Expand Up @@ -581,14 +581,14 @@ iNaturalist-has-no-ID-suggestions-for-this-photo = iNaturalist has no ID suggest
INATURALIST-HELP-PAGE = INATURALIST HELP PAGE
iNaturalist-helps-you-identify = iNaturalist helps you identify the plants and animals around you while generating data for science and conservation. Get connected with a community of millions scientists and naturalists who can help you learn more about nature!
iNaturalist-identification-suggestions-are-based-on = iNaturalist's identification suggestions are based on observations and identifications made by the iNaturalist community, including { $user1 }, { $user2 }, { $user3 }, and many others.
INATURALIST-INTERFACE-MODE = INATURALIST INTERFACE MODE
iNaturalist-is-a-501 = iNaturalist is a 501(c)(3) non-profit in the United States of America (Tax ID/EIN 92-1296468).
iNaturalist-is-a-community-of-naturalists = iNaturalist is a community of naturalists that works together to create and identify wild biodiversity observations.
iNaturalist-is-loading-ID-suggestions = iNaturalist is loading ID suggestions...
iNaturalist-is-supported-by = iNaturalist is supported by an independent, 501(c)(3) nonprofit organization based in the United States of America. The iNaturalist platform includes this app, Seek by iNaturalist, the iNaturalist website, and more.
iNaturalist-is-supported-by-our-community = iNaturalist is supported by our amazing community. From everyday naturalists who add observations and identifications, to curators who manage our taxonomy and help with moderation, to the volunteer translators who make iNaturalist more accessible to worldwide audiences, to our community-based donors, we are extraordinarily grateful to all the people in our community who make iNaturalist the platform it is.
iNaturalist-mission-is-to-connect = iNaturalist's mission is to connect people to nature and advance biodiversity science and conservation.
INATURALIST-MISSION-VISION = INATURALIST'S MISSION & VISION
INATURALIST-MODE = INATURALIST MODE
INATURALIST-NETWORK = INATURALIST NETWORK
INATURALIST-SETTINGS = INATURALIST SETTINGS
# Label for the role a user plays on iNaturalist, e.g. "INATURALIST STAFF"
Expand Down
4 changes: 2 additions & 2 deletions src/i18n/l10n/en.ftl.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"Add-optional-notes": "Add optional notes",
"Adds-your-vote-of-agreement": "Adds your vote of agreement",
"Adds-your-vote-of-disagreement": "Adds your vote of disagreement",
"Advanced--interface-mode": "Advanced",
"Advanced--interface-mode-with-explainer": "Advanced (Upload multiple photos and sounds)",
"Affiliation": "Affiliation: { $site }",
"Agree": "Agree",
"AGREE": "AGREE",
Expand Down Expand Up @@ -337,14 +337,14 @@
"INATURALIST-HELP-PAGE": "INATURALIST HELP PAGE",
"iNaturalist-helps-you-identify": "iNaturalist helps you identify the plants and animals around you while generating data for science and conservation. Get connected with a community of millions scientists and naturalists who can help you learn more about nature!",
"iNaturalist-identification-suggestions-are-based-on": "iNaturalist's identification suggestions are based on observations and identifications made by the iNaturalist community, including { $user1 }, { $user2 }, { $user3 }, and many others.",
"INATURALIST-INTERFACE-MODE": "INATURALIST INTERFACE MODE",
"iNaturalist-is-a-501": "iNaturalist is a 501(c)(3) non-profit in the United States of America (Tax ID/EIN 92-1296468).",
"iNaturalist-is-a-community-of-naturalists": "iNaturalist is a community of naturalists that works together to create and identify wild biodiversity observations.",
"iNaturalist-is-loading-ID-suggestions": "iNaturalist is loading ID suggestions...",
"iNaturalist-is-supported-by": "iNaturalist is supported by an independent, 501(c)(3) nonprofit organization based in the United States of America. The iNaturalist platform includes this app, Seek by iNaturalist, the iNaturalist website, and more.",
"iNaturalist-is-supported-by-our-community": "iNaturalist is supported by our amazing community. From everyday naturalists who add observations and identifications, to curators who manage our taxonomy and help with moderation, to the volunteer translators who make iNaturalist more accessible to worldwide audiences, to our community-based donors, we are extraordinarily grateful to all the people in our community who make iNaturalist the platform it is.",
"iNaturalist-mission-is-to-connect": "iNaturalist's mission is to connect people to nature and advance biodiversity science and conservation.",
"INATURALIST-MISSION-VISION": "INATURALIST'S MISSION & VISION",
"INATURALIST-MODE": "INATURALIST MODE",
"INATURALIST-NETWORK": "INATURALIST NETWORK",
"INATURALIST-SETTINGS": "INATURALIST SETTINGS",
"INATURALIST-STAFF": "{ $inaturalist } STAFF",
Expand Down
4 changes: 2 additions & 2 deletions src/i18n/strings.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Add-optional-notes = Add optional notes
Adds-your-vote-of-agreement = Adds your vote of agreement
# Hint for a button that adds a vote of disagreement
Adds-your-vote-of-disagreement = Adds your vote of disagreement
Advanced--interface-mode = Advanced
Advanced--interface-mode-with-explainer = Advanced (Upload multiple photos and sounds)
Affiliation = Affiliation: { $site }
# Label for button that adds an identification of the same taxon as another identification
Agree = Agree
Expand Down Expand Up @@ -581,14 +581,14 @@ iNaturalist-has-no-ID-suggestions-for-this-photo = iNaturalist has no ID suggest
INATURALIST-HELP-PAGE = INATURALIST HELP PAGE
iNaturalist-helps-you-identify = iNaturalist helps you identify the plants and animals around you while generating data for science and conservation. Get connected with a community of millions scientists and naturalists who can help you learn more about nature!
iNaturalist-identification-suggestions-are-based-on = iNaturalist's identification suggestions are based on observations and identifications made by the iNaturalist community, including { $user1 }, { $user2 }, { $user3 }, and many others.
INATURALIST-INTERFACE-MODE = INATURALIST INTERFACE MODE
iNaturalist-is-a-501 = iNaturalist is a 501(c)(3) non-profit in the United States of America (Tax ID/EIN 92-1296468).
iNaturalist-is-a-community-of-naturalists = iNaturalist is a community of naturalists that works together to create and identify wild biodiversity observations.
iNaturalist-is-loading-ID-suggestions = iNaturalist is loading ID suggestions...
iNaturalist-is-supported-by = iNaturalist is supported by an independent, 501(c)(3) nonprofit organization based in the United States of America. The iNaturalist platform includes this app, Seek by iNaturalist, the iNaturalist website, and more.
iNaturalist-is-supported-by-our-community = iNaturalist is supported by our amazing community. From everyday naturalists who add observations and identifications, to curators who manage our taxonomy and help with moderation, to the volunteer translators who make iNaturalist more accessible to worldwide audiences, to our community-based donors, we are extraordinarily grateful to all the people in our community who make iNaturalist the platform it is.
iNaturalist-mission-is-to-connect = iNaturalist's mission is to connect people to nature and advance biodiversity science and conservation.
INATURALIST-MISSION-VISION = INATURALIST'S MISSION & VISION
INATURALIST-MODE = INATURALIST MODE
INATURALIST-NETWORK = INATURALIST NETWORK
INATURALIST-SETTINGS = INATURALIST SETTINGS
# Label for the role a user plays on iNaturalist, e.g. "INATURALIST STAFF"
Expand Down
8 changes: 8 additions & 0 deletions tests/integration/LanguageSettings.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ beforeAll( uniqueRealmBeforeAll );
afterAll( uniqueRealmAfterAll );
// /UNIQUE REALM SETUP

const toggleAdvancedMode = async ( ) => {
const advancedRadioButton = await screen
.findByText( /Advanced/ );
fireEvent.press( advancedRadioButton );
};

describe( "LanguageSettings", ( ) => {
test( "uses locale preference of the local device", ( ) => {
renderAppWithComponent( <Settings /> );
Expand Down Expand Up @@ -63,12 +69,14 @@ describe( "LanguageSettings", ( ) => {

test( "uses locale preference from server", async ( ) => {
renderAppWithComponent( <Settings /> );
await toggleAdvancedMode( );
const sciNameText = await screen.findByText( /Научное название/ );
expect( sciNameText ).toBeVisible( );
} );

test( "changes locales and updates server with new locale", async ( ) => {
renderAppWithComponent( <Settings /> );
await toggleAdvancedMode( );
const changeLocaleButton = await screen.findByText( /CHANGE APP LANGUAGE/ );
fireEvent.press( changeLocaleButton );
const picker = await screen.findByTestId( "ReactNativePicker" );
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/components/Settings/Settings.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ jest.mock( "@react-navigation/native", ( ) => {

const initialStoreState = useStore.getState( );

const toggleAdvancedMode = async ( ) => {
const advancedRadioButton = await screen
.findByText( /Advanced/ );
fireEvent.press( advancedRadioButton );
};

beforeAll( async ( ) => {
useStore.setState( initialStoreState, true );
// userEvent recommends fake timers
Expand All @@ -48,6 +54,7 @@ beforeEach( ( ) => {
describe( "Settings", ( ) => {
it( "should toggle the green observation button", async ( ) => {
renderComponent( <Settings /> );
await toggleAdvancedMode( );
const aiCameraRow = await screen.findByLabelText( "iNaturalist AI Camera" );
expect( aiCameraRow ).toHaveProp( "accessibilityState", expect.objectContaining( {
checked: true
Expand All @@ -73,6 +80,7 @@ describe( "Settings", ( ) => {

it( "should toggle taxon names display", async ( ) => {
renderComponent( <Settings /> );
await toggleAdvancedMode( );
const sciNameFirst = await screen.findByLabelText( "Scientific Name (Common Name)" );
expect( sciNameFirst ).toHaveProp( "accessibilityState", expect.objectContaining( {
checked: false
Expand Down Expand Up @@ -110,6 +118,7 @@ describe( "Settings", ( ) => {

it( "should not change state if taxon names toggled with no internet", async ( ) => {
renderComponent( <Settings /> );
await toggleAdvancedMode( );
const sciNameFirst = await screen.findByLabelText( "Scientific Name (Common Name)" );
expect( sciNameFirst ).toHaveProp( "accessibilityState", expect.objectContaining( {
checked: false
Expand All @@ -135,6 +144,7 @@ describe( "Settings", ( ) => {

test( "should change language immediately via language picker via online results", async ( ) => {
renderComponent( <Settings /> );
await toggleAdvancedMode( );
const changeLanguageButton = await screen.findByText( /CHANGE APP LANGUAGE/ );
fireEvent.press( changeLanguageButton );
const picker = await screen.findByTestId( "ReactNativePicker" );
Expand Down

0 comments on commit 1c59f89

Please sign in to comment.