test/phylorefs.js
/*
* Test phyloreferences.
*/
// Require phyx.js, our PHYX library, and Chai for testing.
const chai = require('chai');
const phyx = require('../src');
// Use owlterms so we don't have to repeat OWL terms.
const owlterms = require('../src/utils/owlterms');
// We use Chai's Expect API.
const expect = chai.expect;
/*
* Phyloref tests cover three aspects of phyloreferences:
* - Whether we can create a phyloref with a particular set of specifiers,
* and whether we can correctly change the type of a specifer (from 'External'
* to 'Internal'), delete specifiers, and retrieve specifier labels.
* - Whether we can determine to which node a phyloref is expected to resolve to
* by using additionalNodeProperties.
* - Whether we can update the phyloref's status several times and retrieve the
* full history of its status changes.
*/
describe('PhylorefWrapper', function () {
// Some specifiers to use in testing.
const specifier1 = {
'@type': phyx.TaxonomicUnitWrapper.TYPE_SPECIMEN,
occurrenceID: 'MVZ:225749',
};
const specifier2 = {
'@type': phyx.TaxonomicUnitWrapper.TYPE_SPECIMEN,
occurrenceID: 'MVZ:191016',
};
const specifier3 = {
'@type': phyx.TaxonomicUnitWrapper.TYPE_TAXON_CONCEPT,
hasName: {
'@type': phyx.TaxonNameWrapper.TYPE_TAXON_NAME,
nomenclaturalCode: owlterms.ICZN_CODE,
nameComplete: 'Rana boylii',
},
};
const specifier4 = {
'@type': phyx.TaxonomicUnitWrapper.TYPE_TAXON_CONCEPT,
hasName: {
'@type': phyx.TaxonNameWrapper.TYPE_TAXON_NAME,
nomenclaturalCode: owlterms.ICN_CODE,
nameComplete: 'Mangifera indica',
},
};
describe('given an empty phyloreference', function () {
const wrapper = new phyx.PhylorefWrapper({});
describe('#constructor', function () {
it('should return a PhylorefWrapper', function () {
expect(wrapper).to.be.an.instanceOf(phyx.PhylorefWrapper);
});
});
describe('#label', function () {
it('should return undefined', function () {
expect(wrapper.label).to.be.undefined;
});
it('should be settable by assigning to .label', function () {
wrapper.label = 'phyloref1';
expect(wrapper.label).equals('phyloref1');
});
});
describe('#specifiers', function () {
it('should initially return an empty list', function () {
expect(wrapper.specifiers).to.be.empty;
});
it('should initially return a nomenclatural code of unknown', function () {
expect(wrapper.defaultNomenCode).to.equal(owlterms.UNKNOWN_CODE);
});
describe('when a new external specifier is added using .externalSpecifiers', function () {
it('should return a list with the new specifier', function () {
wrapper.externalSpecifiers.push(specifier3);
expect(wrapper.specifiers).to.deep.equal([specifier3]);
});
it('should return a nomenclatural code of ICZN', function () {
expect(wrapper.defaultNomenCode).to.equal(owlterms.ICZN_CODE);
});
});
describe('when a new external specifier is added using .externalSpecifiers', function () {
it('should return a list with the new specifier', function () {
wrapper.externalSpecifiers.push(specifier2);
expect(wrapper.specifiers).to.deep.equal([specifier3, specifier2]);
});
it('should return two nomenclatural codes, one for each specifier', function () {
expect(wrapper.uniqNomenCodes).to.have.lengthOf(2);
expect(wrapper.uniqNomenCodes).to.include(owlterms.ICZN_CODE);
expect(wrapper.uniqNomenCodes).to.include(owlterms.UNKNOWN_CODE);
});
it('should still return a nomenclatural code of ICZN', function () {
expect(wrapper.defaultNomenCode).to.equal(owlterms.ICZN_CODE);
});
});
describe('when a new internal specifier is added using .internalSpecifiers', function () {
it('should return a list with the new specifier', function () {
wrapper.internalSpecifiers.push(specifier4);
expect(wrapper.specifiers).to.deep.equal([specifier4, specifier3, specifier2]);
});
it('should return three nomenclatural codes, one for each specifier', function () {
expect(wrapper.uniqNomenCodes).to.have.lengthOf(3);
expect(wrapper.uniqNomenCodes).to.include(owlterms.ICZN_CODE);
expect(wrapper.uniqNomenCodes).to.include(owlterms.UNKNOWN_CODE);
expect(wrapper.uniqNomenCodes).to.include(owlterms.ICN_CODE);
});
it('should change to a default nomenclatural code of owlterms.UNKNOWN_CODE', function () {
expect(wrapper.defaultNomenCode).to.equal(owlterms.UNKNOWN_CODE);
});
});
describe('when specifiers are deleted using .deleteSpecifier', function () {
it('should return the updated list', function () {
// Delete an external specifier.
wrapper.deleteSpecifier(specifier2);
// Delete an internal specifier.
wrapper.deleteSpecifier(specifier4);
// Only the first specifier should be left.
expect(wrapper.specifiers).to.deep.equal([specifier3]);
});
});
describe('when a specifier is added using .externalSpecifiers', function () {
it('should return the updated list', function () {
wrapper.externalSpecifiers.push(specifier1);
expect(wrapper.specifiers).to.deep.equal([specifier3, specifier1]);
});
});
describe('when a specifier is changed to an internal specifier using .setSpecifierType', function () {
it('should remain in the list of specifiers', function () {
wrapper.setSpecifierType(specifier1, 'Internal');
expect(wrapper.specifiers).to.deep.equal([specifier1, specifier3]);
});
});
describe('when a specifier is added using .internalSpecifiers', function () {
it('should be included in the list of all specifiers', function () {
wrapper.internalSpecifiers.push(specifier2);
expect(wrapper.specifiers).to.deep.equal([specifier1, specifier2, specifier3]);
});
});
});
describe('#getSpecifierType', function () {
it('should return the correct specifier type for each specifier', function () {
expect(wrapper.getSpecifierType(specifier1)).to.equal('Internal');
expect(wrapper.getSpecifierType(specifier2)).to.equal('Internal');
expect(wrapper.getSpecifierType(specifier3)).to.equal('External');
});
});
describe('#getSpecifierLabel as TaxonomicUnitWrapper', function () {
it('should return the correct label for each specifier', function () {
expect((new phyx.TaxonomicUnitWrapper(specifier1)).label).to.equal('Specimen MVZ:225749');
expect((new phyx.TaxonomicUnitWrapper(specifier2)).label).to.equal('Specimen MVZ:191016');
expect((new phyx.TaxonomicUnitWrapper(specifier3)).label).to.equal('Rana boylii');
});
});
});
describe('given a particular phylogeny', function () {
// Some phylogenies to use in testing.
const phylogeny1 = {
newick: '((MVZ225749, MVZ191016)Test, "Rana boylii")',
additionalNodeProperties: {
Test: {
expectedPhyloreferenceNamed: 'phyloref1',
},
},
};
describe('#getExpectedNodeLabels', function () {
it('should be able to determine expected node labels for a phylogeny', function () {
const phyloref1 = new phyx.PhylorefWrapper({
label: 'phyloref1',
internalSpecifiers: [specifier1, specifier2],
externalSpecifiers: [specifier3],
});
expect(phyloref1.getExpectedNodeLabels(phylogeny1))
.to.deep.equal(['Test']);
});
});
});
describe('given an empty phyloreference', function () {
const wrapper = new phyx.PhylorefWrapper({});
describe('#getCurrentStatus', function () {
it('should return "pso:draft" as the default initial status', function () {
// Initially, an empty phyloref should report a status of 'pso:draft'.
expect(wrapper.getCurrentStatus().statusCURIE).to.equal('pso:draft');
});
});
describe('#setStatus', function () {
it('should throw an error if given a mistyped status', function () {
expect(function () { wrapper.setStatus('pso:retracted-from_publication'); })
.to.throw(
TypeError,
'setStatus() called with invalid status CURIE \'pso:retracted-from_publication\'',
'PhylorefWrapper throws TypeError on a mistyped status'
);
});
});
describe('#getStatusChanges', function () {
it('should return the empty list', function () {
expect(wrapper.getStatusChanges()).to.be.empty;
});
describe('when modified by using .setStatus', function () {
it('should return the updated list', function () {
wrapper.setStatus('pso:final-draft');
wrapper.setStatus('pso:under-review');
wrapper.setStatus('pso:submitted');
wrapper.setStatus('pso:published');
wrapper.setStatus('pso:retracted-from-publication');
// And see if we get the statuses back in the correct order.
const statusChanges = wrapper.getStatusChanges();
expect(statusChanges.length, 'number of status changes should be 5').to.equal(5);
expect(statusChanges[0].statusCURIE, 'first status change should be "pso:final-draft"').to.equal('pso:final-draft');
expect(statusChanges[1].statusCURIE, 'second status change should be "pso:under-review"').to.equal('pso:under-review');
expect(statusChanges[2].statusCURIE, 'third status change should be a "pso:submitted"').to.equal('pso:submitted');
expect(statusChanges[3].statusCURIE, 'fourth status change should be a "pso:published"').to.equal('pso:published');
expect(statusChanges[4].statusCURIE, 'fifth status change should be a "pso:retracted-from-publication"').to.equal('pso:retracted-from-publication');
});
});
});
});
describe('#asJSONLD', function () {
it('should preserve an existing @id on input phylorefs', function () {
const jsonld = new phyx.PhylorefWrapper({
'@id': '#providedId',
internalSpecifiers: [specifier1],
externalSpecifiers: [specifier2],
}).asJSONLD('#phyloref0');
expect(jsonld).to.have.property('@id');
expect(jsonld['@id']).to.equal('#providedId');
});
it('should generate a new @id on input phylorefs', function () {
const jsonld = new phyx.PhylorefWrapper({
internalSpecifiers: [specifier1],
externalSpecifiers: [specifier2],
}).asJSONLD('#phyloref0');
expect(jsonld).to.have.property('@id');
expect(jsonld['@id']).to.equal('#phyloref0');
});
it('should generate the expected equivClass expression for 1 int, 1 ext phyloref', function () {
const jsonld = new phyx.PhylorefWrapper({
internalSpecifiers: [specifier1],
externalSpecifiers: [specifier2],
}).asJSONLD('#');
expect(jsonld).to.have.property('equivalentClass');
expect(jsonld.equivalentClass).to.deep.equal({
'@type': owlterms.OWL_CLASS,
intersectionOf: [
{
'@type': owlterms.OWL_RESTRICTION,
onProperty: owlterms.PHYLOREF_INCLUDES_TU,
someValuesFrom: {
'@type': owlterms.OWL_RESTRICTION,
hasValue: 'MVZ:225749',
onProperty: owlterms.DWC_OCCURRENCE_ID,
},
},
{
'@type': owlterms.OWL_RESTRICTION,
onProperty: owlterms.PHYLOREF_EXCLUDES_TU,
someValuesFrom: {
'@type': owlterms.OWL_RESTRICTION,
hasValue: 'MVZ:191016',
onProperty: owlterms.DWC_OCCURRENCE_ID,
},
},
],
});
});
it('should generate the expected equivClass expression for 2 int phyloref', function () {
const jsonld = new phyx.PhylorefWrapper({
internalSpecifiers: [specifier2, specifier3],
}).asJSONLD('#');
expect(jsonld).to.have.property('equivalentClass');
expect(jsonld.equivalentClass).to.deep.equal({
'@type': owlterms.OWL_RESTRICTION,
onProperty: owlterms.CDAO_HAS_CHILD,
someValuesFrom: {
'@type': owlterms.OWL_CLASS,
intersectionOf: [
{
'@type': owlterms.OWL_RESTRICTION,
onProperty: owlterms.PHYLOREF_EXCLUDES_TU,
someValuesFrom: {
'@type': owlterms.OWL_RESTRICTION,
hasValue: 'MVZ:191016',
onProperty: owlterms.DWC_OCCURRENCE_ID,
},
},
{
'@type': owlterms.OWL_RESTRICTION,
onProperty: owlterms.PHYLOREF_INCLUDES_TU,
someValuesFrom: {
'@type': owlterms.OWL_RESTRICTION,
onProperty: owlterms.TDWG_VOC_HAS_NAME,
someValuesFrom: {
'@type': owlterms.OWL_CLASS,
intersectionOf: [{
'@type': owlterms.OWL_RESTRICTION,
hasValue: 'Rana boylii',
onProperty: owlterms.TDWG_VOC_NAME_COMPLETE,
}, {
'@type': owlterms.OWL_RESTRICTION,
hasValue: {
'@id': owlterms.ICZN_CODE,
},
onProperty: owlterms.NOMENCLATURAL_CODE,
}],
},
},
},
],
},
});
});
});
});