diff --git a/tests/data/transcripts/cds_2.js b/tests/data/transcripts/cds_2.js new file mode 100644 index 00000000..f60dce6f --- /dev/null +++ b/tests/data/transcripts/cds_2.js @@ -0,0 +1,5 @@ +define([], + function() { + var str = "ATGGCCGATAACCGCGATCGCAATATTCGAAGAGGCACTTGCCGGATGTCAATTCCATCCAGCAGGATGCGTCCTTGGCTGATCTCGATTAATTGGAAGAGGGCCATCACGGTTGATGATTTTCCGCTACCGGTTCTTCCACAAATACCCAACTACGAATCATTGAATTACCTTTTGTCCAGCTGGTATTTTCAAAGACAAATCTGTGATCACCGGACTTCTATCTACATCGAAGCGCAGAGAGACATTCTCGAAAACGATCTCGCCCCTATTAGGCCAGTCATTGGCCACTTTACAATCGAAGTACTACGCGTCGATCTGATAAACGACGATGACAAAAGACGAAAGATTACCGCGAGACAGTAA"; + return str; +}); diff --git a/tests/data/transcripts/input_10.js b/tests/data/transcripts/input_10.js new file mode 100644 index 00000000..a7489056 --- /dev/null +++ b/tests/data/transcripts/input_10.js @@ -0,0 +1,40 @@ +define([ + 'JBrowse/Model/SimpleFeature' + ], +function (SimpleFeature) { + // This transcript is corresponding to RefSeq_2 + // which is located in file data/RefSeq_2.js + var feature = { + "data": { + "seq_id": "testRefSeq2", + "end": 18, + "start": 5, + "strand": 1, + "subfeatures": [ + { + "data": { + "seq_id": "testRefSeq2", + "end": 18, + "start": 5, + "strand": 1, + "type": "exon" + } + }, + { + "data": { + "seq_id": "testRefSeq2", + "end": 18, + "start": 5, + "strand": 1, + "type": "CDS" + } + } + ], + "type": "transcript" + }, + "normalized": true, +}; + +var transcript = SimpleFeature.fromJSON(feature); +return transcript; +}); diff --git a/tests/data/transcripts/input_11.js b/tests/data/transcripts/input_11.js new file mode 100644 index 00000000..12757d89 --- /dev/null +++ b/tests/data/transcripts/input_11.js @@ -0,0 +1,40 @@ +define([ + 'JBrowse/Model/SimpleFeature' + ], +function (SimpleFeature) { + // This transcript is corresponding to RefSeq_2 + // which is located in file data/RefSeq_2.js + var feature = { + "data": { + "seq_id": "testRefSeq2", + "end": 33, + "start": 21, + "strand": 1, + "subfeatures": [ + { + "data": { + "seq_id": "testRefSeq2", + "end": 33, + "start": 21, + "strand": 1, + "type": "exon" + } + }, + { + "data": { + "seq_id": "testRefSeq2", + "end": 33, + "start": 21, + "strand": 1, + "type": "CDS" + } + } + ], + "type": "transcript" + }, + "normalized": true, +}; + +var transcript = SimpleFeature.fromJSON(feature); +return transcript; +}); diff --git a/tests/data/transcripts/merge_2.js b/tests/data/transcripts/merge_2.js new file mode 100644 index 00000000..91988e2f --- /dev/null +++ b/tests/data/transcripts/merge_2.js @@ -0,0 +1,58 @@ +define([ + 'JBrowse/Model/SimpleFeature' + ], +function (SimpleFeature) { + // This transcript is corresponding to RefSeq_2 + // which is located in file data/RefSeq_2.js + var feature = { + "data": { + "seq_id": "testRefSeq2", + "end": 33, + "start": 5, + "strand": 1, + "subfeatures": [ + { + "data": { + "seq_id": "testRefSeq2", + "end": 18, + "start": 5, + "strand": 1, + "type": "exon" + } + }, + { + "data": { + "seq_id": "testRefSeq2", + "end": 33, + "start": 21, + "strand": 1, + "type": "exon" + } + }, + { + "data": { + "seq_id": "testRefSeq2", + "end": 18, + "start": 5, + "strand": 1, + "type": "CDS" + } + }, + { + "data": { + "seq_id": "testRefSeq2", + "end": 33, + "start": 21, + "strand": 1, + "type": "CDS" + } + } + ], + "type": "transcript" + }, + "normalized": true, +}; + +var transcript = SimpleFeature.fromJSON(feature); +return transcript; +}); diff --git a/tests/data/transcripts/non_canonical_1.js b/tests/data/transcripts/non_canonical_1.js new file mode 100644 index 00000000..ee6bf4c7 --- /dev/null +++ b/tests/data/transcripts/non_canonical_1.js @@ -0,0 +1,46 @@ +define([ + 'JBrowse/Model/SimpleFeature' + ], +function (SimpleFeature) { + // This transcript is corresponding to RefSeq + // which is located in file data/RefSeq.js + // This consist a non canonical stop site + var feature = { + "data": { + "type": "transcript", + "name": "maker-Si_gnF%2Escaffold02797-augustus-gene-9.30-mRNA-1", + "seq_id": "Si_gnF.scaffold02797", + "strand": -1, + "subfeatures": [ + { + "data": { + "type": "exon", + "name": "maker-Si_gnF%2Escaffold02797-augustus-gene-9.30-mRNA-1:exon1", + "seq_id": "Si_gnF.scaffold02797", + "strand": -1, + "start": 981763, + "end": 982146 + }, + "_uniqueID": "SimpleFeature_128" + }, + { + "data": { + "type": "CDS", + "name": "maker-Si_gnF%2Escaffold02797-augustus-gene-9.30-mRNA-1:CDS1", + "seq_id": "Si_gnF.scaffold02797", + "strand": -1, + "start": 981763, + "end": 981913 + }, + "_uniqueID": "SimpleFeature_129" + } + ], + "start": 981763, + "end": 982146 + }, + "_uniqueID": "SimpleFeature_127" +}; + +var transcript = SimpleFeature.fromJSON(feature); +return transcript; +}); \ No newline at end of file diff --git a/tests/data/transcripts/non_canonical_2.js b/tests/data/transcripts/non_canonical_2.js new file mode 100644 index 00000000..416b3304 --- /dev/null +++ b/tests/data/transcripts/non_canonical_2.js @@ -0,0 +1,46 @@ +define([ + 'JBrowse/Model/SimpleFeature' + ], +function (SimpleFeature) { + // This transcript is corresponding to RefSeq + // which is located in file data/RefSeq.js + // This consist a both start/stop non canonical sites + var feature = { + "data": { + "type": "transcript", + "name": "maker-Si_gnF%2Escaffold02797-snap-gene-10.47-mRNA-1", + "seq_id": "Si_gnF.scaffold02797", + "strand": -1, + "subfeatures": [ + { + "data": { + "type": "exon", + "name": "maker-Si_gnF%2Escaffold02797-snap-gene-10.47-mRNA-1:exon1", + "seq_id": "Si_gnF.scaffold02797", + "strand": -1, + "start": 994150, + "end": 994370 + }, + "_uniqueID": "SimpleFeature_1054" + }, + { + "data": { + "type": "CDS", + "name": "maker-Si_gnF%2Escaffold02797-snap-gene-10.47-mRNA-1:CDS1", + "seq_id": "Si_gnF.scaffold02797", + "strand": -1, + "start": 994150, + "end": 994370 + }, + "_uniqueID": "SimpleFeature_1055" + } + ], + "start": 994150, + "end": 994370 + }, + "_uniqueID": "SimpleFeature_1053" +}; + +var transcript = SimpleFeature.fromJSON(feature); +return transcript; +}); \ No newline at end of file diff --git a/tests/data/transcripts/orf_3.js b/tests/data/transcripts/orf_3.js new file mode 100644 index 00000000..ec1c371b --- /dev/null +++ b/tests/data/transcripts/orf_3.js @@ -0,0 +1,88 @@ +define([ + 'JBrowse/Model/SimpleFeature' + ], +function (SimpleFeature) { + // This transcript is corresponding to RefSeq + // which is located in file data/RefSeq.js + var feature = { + "data": { + "type": "transcript", + "seq_id": "Si_gnF.scaffold02797", + "strand": -1, + "subfeatures": [ + { + "data": { + "type": "exon", + "name": "undefined:exon1", + "seq_id": "Si_gnF.scaffold02797", + "strand": -1, + "start": 18796, + "end": 18869 + }, + "_uniqueID": "SimpleFeature_72" + }, + { + "data": { + "type": "exon", + "name": "undefined:exon2", + "seq_id": "Si_gnF.scaffold02797", + "strand": -1, + "start": 19075, + "end": 19210 + }, + "_uniqueID": "SimpleFeature_73" + }, + { + "data": { + "type": "exon", + "name": "undefined:exon3", + "seq_id": "Si_gnF.scaffold02797", + "strand": -1, + "start": 19819, + "end": 19977 + }, + "_uniqueID": "SimpleFeature_74" + }, + { + "data": { + "type": "CDS", + "name": "undefined:CDS1", + "seq_id": "Si_gnF.scaffold02797", + "strand": -1, + "start": 18796, + "end": 18869 + }, + "_uniqueID": "SimpleFeature_75" + }, + { + "data": { + "type": "CDS", + "name": "undefined:CDS2", + "seq_id": "Si_gnF.scaffold02797", + "strand": -1, + "start": 19075, + "end": 19210 + }, + "_uniqueID": "SimpleFeature_76" + }, + { + "data": { + "type": "CDS", + "name": "undefined:CDS3", + "seq_id": "Si_gnF.scaffold02797", + "strand": -1, + "start": 19819, + "end": 19977 + }, + "_uniqueID": "SimpleFeature_77" + } + ], + "start": 18796, + "end": 19977 + }, + "_uniqueID": "SimpleFeature_71" +}; + +var transcript = SimpleFeature.fromJSON(feature); +return transcript; +}); \ No newline at end of file diff --git a/tests/data/transcripts/transcript_data.js b/tests/data/transcripts/transcript_data.js index 4e6cc274..6e0c19fd 100644 --- a/tests/data/transcripts/transcript_data.js +++ b/tests/data/transcripts/transcript_data.js @@ -8,14 +8,21 @@ define([ './input_7', './input_8', './input_9', + './input_10', + './input_11', './resize_1', './resize_2', './resize_3', './resize_4', './orf_1', './orf_2', + './orf_3', + './non_canonical_1', + './non_canonical_2', './cds_1', + './cds_2', './merge_1', + './merge_2', './normalize_1' ], function ( input_1, @@ -27,23 +34,31 @@ define([ input_7, input_8, input_9, + input_10, + input_11, resize_1, resize_2, resize_3, resize_4, orf_1, orf_2, + orf_3, + non_canonical_1, + non_canonical_2, cds_1, + cds_2, merge_1, + merge_2, normalize_1 ) { var transcript_data = { "input": [input_1, input_2, input_3, input_4, input_5, input_6, input_7, - input_8], + input_8, input_9, input_10, input_11], "resize": [resize_1, resize_2, resize_3, resize_4], - "orf": [orf_1, orf_2], - "cds": [cds_1], - "merge": [merge_1], + "orf": [orf_1, orf_2, orf_3], + "non_canonical": [non_canonical_1, non_canonical_2], + "cds": [cds_1, cds_2], + "merge": [merge_1, merge_2], "normalize": [normalize_1] }; diff --git a/tests/js/EditTrack.spec.js b/tests/js/EditTrack.spec.js index aa144b6f..269a0b91 100644 --- a/tests/js/EditTrack.spec.js +++ b/tests/js/EditTrack.spec.js @@ -111,6 +111,67 @@ describe( "Edit Track", function() { expect(editTrack.getWholeCDSCoordinates(transcript_data.input[1])).toEqual([19977, 18796]); }); + it('tests flipStrand', function() { + expect(editTrack.flipStrand(transcript_data.input[0]).get('strand')).toEqual(-1); + }); + + it('tests setLongestORF', function() { + expect(compareFeatures(transcript_data.orf[2], editTrack.setLongestORF(refSeq, transcript_data.input[1]))).toEqual(true); + }); + + it('tests markNonCanonicalSpliceSites', function() { + expect(editTrack.markNonCanonicalSpliceSites(transcript_data.input[0], + refSeq).get('subfeatures')[2].get('type')).toEqual('non_canonical_splice_site'); + }); + + it('tests markNonCanonicalTranslationStartSite', function() { + expect(editTrack.markNonCanonicalTranslationStartSite(transcript_data.non_canonical[1], + refSeq).get('subfeatures')[2].get('type')).toEqual('non_canonical_translation_start_site'); + }); + + it('tests markNonCanonicalTranslationStopSite', function() { + expect(editTrack.markNonCanonicalTranslationStopSite(transcript_data.non_canonical[0], + refSeq).get('subfeatures')[0].get('type')).toEqual('non_canonical_translation_stop_site'); + }); + + it('tests filterFeatures', function() { + expect(editTrack.filterFeatures(transcript_data.input[0], 'exon')[0].get('start')).toEqual(16946); + }); + + it('tests copyFeature', function() { + expect(editTrack.copyFeature(transcript_data.input[0], {start: 16949}).get('start')).toEqual(16949); + }); + + it('tests mergeExons', function() { + var exons = editTrack.filterExons(transcript_data.input[0]); + var merge_these = [exons[0], exons[1]]; + var merged = editTrack.mergeExons(refSeq_2, transcript_data.input[0], merge_these); + expect(merged.get('start')).toEqual(16946); + }); + + it('tests createTranscript', function() { + var subfeatures = editTrack.filterFeatures(transcript_data.input[1], 'CDS'); + var newTranscript = editTrack.createTranscript(subfeatures, 'test-transcript') + expect(newTranscript.get('seq_id')).toBe('Si_gnF.scaffold02797'); + }); + + it('tests getCDNACoordinates', function() { + expect(editTrack.getCDNACoordinates(transcript_data.input[0])[4]).toEqual([21298, 21389]); + }); + + it('tests deleteExons', function() { + var exons = editTrack.filterExons(transcript_data.input[0]); + var delete_these = [exons[0],exons[1]]; + var deleted = editTrack.deleteExons(refSeq_2, transcript_data.input[0], delete_these); + expect(editTrack.filterExons(deleted)[1].get('start')).toEqual(21002); + }); + + it('tests sortAnnotationsByLocation', function() { + var exons = editTrack.filterExons(transcript_data.input[0]); + var sorted = editTrack.sortAnnotationsByLocation(exons); + expect(sorted[0].get('start')).toEqual(16946); + }); + it('tests transcriptToCDNA', function() { expect(editTrack.transcriptToCDNA(transcript_data.input[3], 4)).toEqual(0); }); @@ -156,6 +217,15 @@ describe( "Edit Track", function() { var left = exon.get('start'); outTranscript = editTrack.resizeExon(refSeq, transcript_data["input"][1], exon, left, right); expect(compareFeatures(transcript_data["resize"][2], outTranscript)).toBe(true); + + // check for the translation start changes + exon = editTrack.filterExons(transcript_data.input[1])[0]; + var right = exon.get('end'); + var left = exon.get('start') - 3; + outTranscript = editTrack.resizeExon(refSeq, transcript_data.input[1], exon, left, right); + // Verify if CDS is correct + expect(editTrack.getCDS(refSeq, outTranscript)).toEqual(transcript_data.cds[1]); + expect(outTranscript.get('start')).toEqual(18793); }); it( 'tests areOnSameStrand', function() { @@ -174,6 +244,16 @@ describe( "Edit Track", function() { [transcript_data.input[6], transcript_data.input[7]]), transcript_data.input[6])).toBe(true); + // transcripts on different strands cannot be merged + expect(editTrack.mergeTranscripts(refSeq, [transcript_data.input[0], transcript_data.input[1]])).toEqual(undefined); + + // With CDS + expect(compareFeatures( + editTrack.mergeTranscripts(refSeq_2, + [transcript_data.input[9], transcript_data.input[10]]), + transcript_data.merge[1])).toBe(true); + + // Without CDS expect(compareFeatures( editTrack.mergeTranscripts(refSeq_2, [transcript_data.input[4], transcript_data.input[5]]), diff --git a/www/JBrowse/Model/SimpleFeature.js b/www/JBrowse/Model/SimpleFeature.js index f7dec9b5..d48506db 100644 --- a/www/JBrowse/Model/SimpleFeature.js +++ b/www/JBrowse/Model/SimpleFeature.js @@ -32,10 +32,7 @@ var SimpleFeature = Util.fastDeclare({ if(( subfeatures = this.data.subfeatures )) { for( var i = 0; i < subfeatures.length; i++ ) { if( typeof subfeatures[i].get != 'function' ) { - subfeatures[i] = new SimpleFeature( - { data: subfeatures[i], - parent: this - }); + subfeatures[i] = new SimpleFeature(subfeatures[i]); } } } @@ -115,8 +112,15 @@ SimpleFeature.toJSON = function (feature) { * ids will differ from the original feature. */ SimpleFeature.fromJSON = function (featureJSON) { - var data = JSON.parse(featureJSON); - return new SimpleFeature({data: data}); + // Here parse function requires a string in input + // But featureJSON is complete JSON and therefore + // we need to change JSON to string first and then + // parse it to save in to data. and create new feature + // var data = JSON.parse(JSON.stringify(featureJSON)); + + // var data = JSON.parse(featureJSON); + // return new SimpleFeature({data: data}); + return new SimpleFeature(featureJSON); }; return SimpleFeature; diff --git a/www/JBrowse/View/Track/EditTrack.js b/www/JBrowse/View/Track/EditTrack.js index e2102589..2b8d7e72 100644 --- a/www/JBrowse/View/Track/EditTrack.js +++ b/www/JBrowse/View/Track/EditTrack.js @@ -640,7 +640,7 @@ var EditTrack = declare(DraggableFeatureTrack, * part of the given transcript, or if the given exon is not really an exon. */ resizeExon: function (refSeq, transcript, exonToResize, left, right) { - if (exonToResize.parent() !== transcript || + if (exonToResize.parent() !== transcript && exonToResize.get('type') !== 'exon') { return; } @@ -686,12 +686,12 @@ var EditTrack = declare(DraggableFeatureTrack, * won't have any CDS features either. */ mergeExons: function (refSeq, transcript, exonsToMerge) { - if (!this.areSiblings(exonsToMerge)) { - return; - } + var str_exonsToMerge = _.map(exonsToMerge, function (exon) { + return JSON.stringify(exon); + }); var exons = _.reject(this.filterExons(transcript), function (exon) { - return _.indexOf(exonsToMerge, exon) !== -1; + return _.indexOf(str_exonsToMerge, JSON.stringify(exon)) !== -1; }); var min = _.min(_.map(exonsToMerge, function (exonToMerge) { return exonToMerge.get('start'); @@ -760,9 +760,12 @@ var EditTrack = declare(DraggableFeatureTrack, if (this.filterExons(transcript).length <= 1) { return; } + var str_exonsToDelete = _.map(exonsToDelete, function(exon) { + return JSON.stringify(exon); + }); var exons = _.reject(this.filterExons(transcript), function (exon) { - return _.indexOf(exonsToDelete, exon) !== -1; + return _.indexOf(str_exonsToDelete, JSON.stringify(exon)) !== -1; }); var newTranscript = this.createTranscript(exons, transcript.get('name')); var translationStart = this.getTranslationStart(transcript); @@ -1114,14 +1117,14 @@ var EditTrack = declare(DraggableFeatureTrack, subfeatures: _.map(subfeatures, function (f) { var type = f.get('type'); count[type] = count[type] || 1; - return { + return {'data': { type: type, name: name + ':' + type + count[type]++, seq_id: f.get('seq_id'), strand: f.get('strand'), start: f.get('start'), end: f.get('end'), - }; + }}; }) } });