mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-08-02 02:17:30 -04:00
@@ -160,6 +160,43 @@ class Taxon extends Realm.Object {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverse of mapApiToRealm: maps a live Realm Taxon into the plain,
|
||||
* snake_case ApiTaxon shape the API and most display/search code expect.
|
||||
* Live Realm objects expose camelCase mapTo accessors (preferredCommonName,
|
||||
* defaultPhoto); we read those (falling back to the snake_case keys so a
|
||||
* plain object passes through unchanged) and return a detached POJO. Use
|
||||
* this whenever a live Taxon needs to leave the data layer (React/Zustand
|
||||
* state, async boundaries, child components) so we aren't passing around an
|
||||
* object Realm can invalidate underneath us.
|
||||
*
|
||||
* @param {object} taxon - a live Realm Taxon (or an already-plain object)
|
||||
* @returns {object} plain ApiTaxon-shaped object
|
||||
*/
|
||||
static mapRealmToPojo( taxon ) {
|
||||
if ( !taxon ) return taxon;
|
||||
const photo = taxon.defaultPhoto || taxon.default_photo;
|
||||
return {
|
||||
id: taxon.id,
|
||||
name: taxon.name,
|
||||
rank: taxon.rank,
|
||||
rank_level: taxon.rank_level,
|
||||
iconic_taxon_name: taxon.iconic_taxon_name,
|
||||
ancestor_ids: taxon.ancestor_ids
|
||||
? Array.from( taxon.ancestor_ids )
|
||||
: undefined,
|
||||
preferred_common_name: taxon.preferredCommonName ?? taxon.preferred_common_name,
|
||||
default_photo: photo?.url
|
||||
? {
|
||||
id: photo.id,
|
||||
url: photo.url,
|
||||
attribution: photo.attribution,
|
||||
license_code: photo.license_code ?? photo.licenseCode,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
static uri = item => ( item && item.default_photo ) && { uri: item.default_photo.url };
|
||||
|
||||
static schema = {
|
||||
|
||||
108
tests/unit/models/Taxon.test.js
Normal file
108
tests/unit/models/Taxon.test.js
Normal file
@@ -0,0 +1,108 @@
|
||||
import Taxon from "realmModels/Taxon";
|
||||
import safeRealmWrite from "sharedHelpers/safeRealmWrite";
|
||||
|
||||
describe( "Taxon", ( ) => {
|
||||
describe( "mapRealmToPojo", ( ) => {
|
||||
it( "returns the value unchanged when given null or undefined", ( ) => {
|
||||
expect( Taxon.mapRealmToPojo( null ) ).toBeNull( );
|
||||
expect( Taxon.mapRealmToPojo( undefined ) ).toBeUndefined( );
|
||||
} );
|
||||
|
||||
describe( "with an already-plain snake_case object", ( ) => {
|
||||
const apiTaxon = {
|
||||
id: 745,
|
||||
name: "Silphium perfoliatum",
|
||||
rank: "species",
|
||||
rank_level: 10,
|
||||
iconic_taxon_name: "Plantae",
|
||||
ancestor_ids: [1, 2, 3],
|
||||
preferred_common_name: "Cup Plant",
|
||||
default_photo: {
|
||||
id: 7,
|
||||
url: "https://example.com/photo.jpg",
|
||||
attribution: "(c) someone",
|
||||
license_code: "cc-by",
|
||||
},
|
||||
};
|
||||
|
||||
it( "passes the snake_case fields through unchanged", ( ) => {
|
||||
const result = Taxon.mapRealmToPojo( apiTaxon );
|
||||
expect( result.id ).toBe( 745 );
|
||||
expect( result.name ).toBe( "Silphium perfoliatum" );
|
||||
expect( result.rank ).toBe( "species" );
|
||||
expect( result.rank_level ).toBe( 10 );
|
||||
expect( result.iconic_taxon_name ).toBe( "Plantae" );
|
||||
expect( result.preferred_common_name ).toBe( "Cup Plant" );
|
||||
} );
|
||||
|
||||
it( "maps the default_photo fields", ( ) => {
|
||||
const result = Taxon.mapRealmToPojo( apiTaxon );
|
||||
expect( result.default_photo ).toEqual( {
|
||||
id: 7,
|
||||
url: "https://example.com/photo.jpg",
|
||||
attribution: "(c) someone",
|
||||
license_code: "cc-by",
|
||||
} );
|
||||
} );
|
||||
|
||||
it( "returns a detached array copy of ancestor_ids", ( ) => {
|
||||
const result = Taxon.mapRealmToPojo( apiTaxon );
|
||||
expect( Array.isArray( result.ancestor_ids ) ).toBe( true );
|
||||
expect( result.ancestor_ids ).toEqual( [1, 2, 3] );
|
||||
// a copy, not the same reference we passed in
|
||||
expect( result.ancestor_ids ).not.toBe( apiTaxon.ancestor_ids );
|
||||
} );
|
||||
} );
|
||||
|
||||
it( "leaves default_photo undefined when the photo has no url", ( ) => {
|
||||
const result = Taxon.mapRealmToPojo( {
|
||||
id: 1,
|
||||
name: "Life",
|
||||
default_photo: { id: 9, attribution: "(c) someone" },
|
||||
} );
|
||||
expect( result.default_photo ).toBeUndefined( );
|
||||
} );
|
||||
|
||||
describe( "with a live Realm Taxon", ( ) => {
|
||||
let realmTaxon;
|
||||
|
||||
beforeEach( ( ) => {
|
||||
safeRealmWrite( global.realm, ( ) => {
|
||||
realmTaxon = global.realm.create( "Taxon", {
|
||||
id: 123,
|
||||
name: "Danaus plexippus",
|
||||
rank: "species",
|
||||
rank_level: 10,
|
||||
iconic_taxon_name: "Insecta",
|
||||
ancestor_ids: [48460, 1, 47120],
|
||||
// exercise the camelCase mapTo accessors
|
||||
preferred_common_name: "Monarch",
|
||||
default_photo: {
|
||||
id: 555,
|
||||
url: "https://example.com/monarch.jpg",
|
||||
attribution: "(c) lepidopterist",
|
||||
license_code: "cc-by-nc",
|
||||
},
|
||||
} );
|
||||
}, "create Taxon for mapRealmToPojo test" );
|
||||
} );
|
||||
|
||||
afterEach( ( ) => {
|
||||
safeRealmWrite( global.realm, ( ) => {
|
||||
global.realm.delete( realmTaxon );
|
||||
}, "delete Taxon for mapRealmToPojo test" );
|
||||
} );
|
||||
|
||||
it( "returns a detached plain object, not a live Realm object", ( ) => {
|
||||
const result = Taxon.mapRealmToPojo( realmTaxon );
|
||||
// a plain object literal, not a live Realm.Object the database can invalidate
|
||||
expect( Object.getPrototypeOf( result ) ).toBe( Object.prototype );
|
||||
expect( result.id ).toBe( 123 );
|
||||
expect( result.name ).toBe( "Danaus plexippus" );
|
||||
expect( result.rank ).toBe( "species" );
|
||||
expect( result.rank_level ).toBe( 10 );
|
||||
expect( result.iconic_taxon_name ).toBe( "Insecta" );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
Reference in New Issue
Block a user