UNPKG

sequaljs

Version:

JavaScript/TypeScript library for parsing and manipulating ProForma peptide sequence notation

600 lines (599 loc) 30 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const sequence_1 = require("../sequence"); const modification_1 = require("../modification"); describe('ChimericProforma', () => { test('split chimeric proforma basic', () => { const inputStr = 'EMEVEESPEK/2+ELVISLIVER/3'; const result = (0, sequence_1.splitChimericProforma)(inputStr); expect(result).toEqual(['EMEVEESPEK/2', 'ELVISLIVER/3']); }); test('split chimeric proforma with modifications', () => { const inputStr = 'S[+79.966]EQMENPEK/2+ELVISLIVER/3'; const result = (0, sequence_1.splitChimericProforma)(inputStr); expect(result).toEqual(['S[+79.966]EQMENPEK/2', 'ELVISLIVER/3']); }); test('split chimeric proforma ionic species', () => { const inputStr = 'PEPTIDE/1[+Na+]+OTHERSEQ/2'; const result = (0, sequence_1.splitChimericProforma)(inputStr); expect(result).toEqual(['PEPTIDE/1[+Na+]', 'OTHERSEQ/2']); }); test('split chimeric proforma terminal mods', () => { const inputStr = '[Acetyl]-PEPTIDE-[Amidated]/1+OTHERSEQ/2'; const result = (0, sequence_1.splitChimericProforma)(inputStr); expect(result).toEqual(['[Acetyl]-PEPTIDE-[Amidated]/1', 'OTHERSEQ/2']); }); test('split chimeric proforma complex brackets', () => { const inputStr = 'PEPT[+Phospho (something [special])]-[Amidated]/2+OTHERSEQ/3'; const result = (0, sequence_1.splitChimericProforma)(inputStr); expect(result).toEqual([ 'PEPT[+Phospho (something [special])]-[Amidated]/2', 'OTHERSEQ/3' ]); }); test('split chimeric proforma multiple peptidoforms', () => { const inputStr = 'SEQ1/1+SEQ2/2+SEQ3/3'; const result = (0, sequence_1.splitChimericProforma)(inputStr); expect(result).toEqual(['SEQ1/1', 'SEQ2/2', 'SEQ3/3']); }); }); describe('ProForma', () => { test('basic peptide with modification', () => { const proforma = "PEP[Phospho]TIDE"; const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).toBe("PEPTIDE"); expect(seq.seq[2].mods[0].modValue.primaryValue).toBe("Phospho"); expect(seq.toProforma()).toBe(proforma); }); test('mass shift notation', () => { const proforma = "PEP[+79.966]TIDE"; const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).toBe("PEPTIDE"); expect(seq.seq[2].mods[0].modValue.primaryValue).toBe("+79.966"); expect(Math.abs(seq.seq[2].mods[0].mass - 79.966)).toBeLessThan(0.0001); expect(seq.toProforma()).toBe("PEP[+79.966]TIDE"); }); test('multiple modifications', () => { const proforma = "PEPS[Phospho][Acetyl]TIDE"; const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).toBe("PEPSTIDE"); expect(seq.seq[3].mods.length).toBe(2); expect(seq.seq[3].mods[0].modValue.primaryValue).toBe("Phospho"); expect(seq.seq[3].mods[1].modValue.primaryValue).toBe("Acetyl"); expect(seq.toProforma()).toBe(proforma); }); test('terminal modifications', () => { // Test single terminal modifications const proforma1 = "[Acetyl]-PEPTIDE-[Amidated]"; const seq1 = sequence_1.Sequence.fromProforma(proforma1); expect(seq1.toStrippedString()).toBe("PEPTIDE"); // Check N-terminal modification const nTermMods1 = seq1.mods.get(-1); expect(nTermMods1).toBeDefined(); if (nTermMods1) { expect(nTermMods1[0].modValue.primaryValue).toBe("Acetyl"); } // Check C-terminal modification const cTermMods1 = seq1.mods.get(-2); expect(cTermMods1).toBeDefined(); if (cTermMods1) { expect(cTermMods1[0].modValue.primaryValue).toBe("Amidated"); } // Test multiple terminal modifications const proforma2 = "[Acetyl][Methyl]-PEPTIDE-[Amidated][Phosphorylated]"; const seq2 = sequence_1.Sequence.fromProforma(proforma2); expect(seq2.toStrippedString()).toBe("PEPTIDE"); // Check multiple N-terminal modifications const nTermMods2 = seq2.mods.get(-1); expect(nTermMods2).toBeDefined(); if (nTermMods2) { expect(nTermMods2.length).toBe(2); expect(nTermMods2[0].modValue.primaryValue).toBe("Acetyl"); expect(nTermMods2[1].modValue.primaryValue).toBe("Methyl"); } // Check multiple C-terminal modifications const cTermMods2 = seq2.mods.get(-2); expect(cTermMods2).toBeDefined(); if (cTermMods2) { expect(cTermMods2.length).toBe(2); expect(cTermMods2[0].modValue.primaryValue).toBe("Amidated"); expect(cTermMods2[1].modValue.primaryValue).toBe("Phosphorylated"); } // Test terminal modifications with hyphens in their names const proforma3 = "[N-Terminal-Acetyl]-PEPTIDE-[C-Terminal-Amidation]"; const seq3 = sequence_1.Sequence.fromProforma(proforma3); expect(seq3.toStrippedString()).toBe("PEPTIDE"); // Check N-terminal modification with hyphen in name const nTermMods3 = seq3.mods.get(-1); expect(nTermMods3).toBeDefined(); if (nTermMods3) { expect(nTermMods3[0].modValue.primaryValue).toBe("N-Terminal-Acetyl"); } // Check C-terminal modification with hyphen in name const cTermMods3 = seq3.mods.get(-2); expect(cTermMods3).toBeDefined(); if (cTermMods3) { expect(cTermMods3[0].modValue.primaryValue).toBe("C-Terminal-Amidation"); } // Multiple modifications with hyphens in their names const proforma4 = "[N-Acetyl][alpha-amino]-PEPTIDE-[C-Terminal][beta-COOH]"; const seq4 = sequence_1.Sequence.fromProforma(proforma4); expect(seq4.toStrippedString()).toBe("PEPTIDE"); const nTermMods4 = seq4.mods.get(-1); expect(nTermMods4).toBeDefined(); if (nTermMods4) { expect(nTermMods4.length).toBe(2); expect(nTermMods4[0].modValue.primaryValue).toBe("N-Acetyl"); expect(nTermMods4[1].modValue.primaryValue).toBe("alpha-amino"); } const cTermMods4 = seq4.mods.get(-2); expect(cTermMods4).toBeDefined(); if (cTermMods4) { expect(cTermMods4.length).toBe(2); expect(cTermMods4[0].modValue.primaryValue).toBe("C-Terminal"); expect(cTermMods4[1].modValue.primaryValue).toBe("beta-COOH"); } // Verify roundtrip for all cases expect(seq1.toProforma()).toBe(proforma1); expect(seq2.toProforma()).toBe(proforma2); expect(seq3.toProforma()).toBe(proforma3); expect(seq4.toProforma()).toBe(proforma4); }); test('ambiguous modifications', () => { const proforma = "PEPS{Phospho}TIDE"; const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).toBe("PEPSTIDE"); expect(seq.seq[3].mods[0].modValue.primaryValue).toBe("Phospho"); expect(seq.seq[3].mods[0].modType).toBe("ambiguous"); expect(seq.toProforma()).toBe(proforma); }); test('complex sequence', () => { const proforma = "[Acetyl]-PEP[Phospho]T{Oxidation}IDE-[Amidated]"; const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).toBe("PEPTIDE"); // Check N-terminal modification const nTermMods = seq.mods.get(-1); expect(nTermMods).toBeDefined(); if (nTermMods) { expect(nTermMods[0].modValue.primaryValue).toBe("Acetyl"); } // Check residue modifications expect(seq.seq[2].mods[0].modValue.primaryValue).toBe("Phospho"); expect(seq.seq[3].mods[0].modValue.primaryValue).toBe("Oxidation"); expect(seq.seq[3].mods[0].modType).toBe("ambiguous"); // Check C-terminal modification const cTermMods = seq.mods.get(-2); expect(cTermMods).toBeDefined(); if (cTermMods) { expect(cTermMods[0].modValue.primaryValue).toBe("Amidated"); } expect(seq.toProforma()).toBe(proforma); }); test('negative mass shift', () => { const proforma = "PEP[-17.027]TIDE"; const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).toBe("PEPTIDE"); expect(seq.seq[2].mods[0].modValue.primaryValue).toBe("-17.027"); expect(Math.abs(seq.seq[2].mods[0].mass + 17.027)).toBeLessThan(0.0001); }); test('conversion from sequence to proforma', () => { const seq = new sequence_1.Sequence("PEPTIDE"); seq.seq[2].addModification(new modification_1.Modification("Phospho")); const proforma = seq.toProforma(); expect(proforma).toBe("PEP[Phospho]TIDE"); const seq2 = sequence_1.Sequence.fromProforma(proforma); expect(seq2.toStrippedString()).toBe("PEPTIDE"); expect(seq2.seq[2].mods[0].modValue.primaryValue).toBe("Phospho"); }); test('inter chain crosslinks', () => { const proforma = "SEK[XLMOD:02001#XL1]UENCE//EMEVTK[#XL1]SESPEK"; const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.isMultiChain).toBe(true); expect(seq.chains.length).toBe(2); expect(seq.chains[0].toStrippedString()).toBe("SEKUENCE"); expect(seq.chains[0].seq[2].mods[0].modValue.primaryValue).toBe("02001"); expect(seq.chains[0].seq[2].mods[0].crosslinkId).toBe("XL1"); expect(seq.chains[1].toStrippedString()).toBe("EMEVTKSESPEK"); expect(seq.chains[1].seq[5].mods[0].isCrosslinkRef).toBe(true); expect(seq.chains[1].seq[5].mods[0].crosslinkId).toBe("XL1"); expect(seq.toProforma()).toBe(proforma); }); test('disulfide bonds', () => { const proforma = "EVTSEKC[XLMOD:00034#XL1]LEMSC[#XL1]EFD"; const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).not.toBe("EVTSEKCLEMMSCEFF"); expect(seq.seq[6].mods[0].modValue.primaryValue).toBe("00034"); expect(seq.seq[6].mods[0].source).toBe("XLMOD"); expect(seq.seq[6].mods[0].crosslinkId).toBe("XL1"); expect(seq.seq[11].mods[0].isCrosslinkRef).toBe(true); expect(seq.toProforma()).toBe(proforma); }); test('branched peptides', () => { const proforma = "ETFGD[MOD:00093#BRANCH]LEMSEFD"; const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).toBe("ETFGDLEMSEFD"); expect(seq.seq[4].mods[0].source).toBe("MOD"); expect(seq.seq[4].mods[0].modValue.primaryValue).toBe("00093"); expect(seq.seq[4].mods[0].isBranch).toBe(true); expect(seq.toProforma()).toBe(proforma); const proforma2 = "ETFGD[MOD:00093#BRANCH]LEMS[#BRANCH]EFD"; const seq2 = sequence_1.Sequence.fromProforma(proforma2); expect(seq2.seq[8].mods[0].isBranchRef).toBe(true); expect(seq2.toProforma()).toBe(proforma2); }); test('valid labile modifications', () => { const proformaStr = "{Glycan:Hex}EMEVNESPEK"; const seq = sequence_1.Sequence.fromProforma(proformaStr); expect(seq.toStrippedString()).toBe("EMEVNESPEK"); const labileMods = seq.mods.get(-3); expect(labileMods).toBeDefined(); if (labileMods) { expect(labileMods.length).toBe(1); expect(labileMods[0].modValue.primaryValue).toBe("Hex"); expect(labileMods[0].source).toBe("Glycan"); } expect(seq.toProforma()).toBe(proformaStr); const proformaStr2 = "{Glycan:Hex}{Glycan:NeuAc}EMEVNESPEK"; const seq2 = sequence_1.Sequence.fromProforma(proformaStr2); expect(seq2.toStrippedString()).toBe("EMEVNESPEK"); const labileMods2 = seq2.mods.get(-3); expect(labileMods2).toBeDefined(); if (labileMods2) { expect(labileMods2.length).toBe(2); expect(labileMods2[0].modValue.primaryValue).toBe("Hex"); expect(labileMods2[1].modValue.primaryValue).toBe("NeuAc"); } expect(seq2.toProforma()).toBe(proformaStr2); }); test('unknown position modifications', () => { const seq = sequence_1.Sequence.fromProforma("[Phospho]?EMEVNESPEK"); expect(seq.toStrippedString()).toBe("EMEVNESPEK"); const unknownMods = seq.mods.get(-4); expect(unknownMods).toBeDefined(); if (unknownMods) { expect(unknownMods.length).toBe(1); expect(unknownMods[0].modValue.primaryValue).toBe("Phospho"); } expect(seq.toProforma()).toBe("[Phospho]?EMEVNESPEK"); const seq2 = sequence_1.Sequence.fromProforma("[Phospho][Phospho]?EMEVNESPEK"); expect(seq2.toStrippedString()).toBe("EMEVNESPEK"); const unknownMods2 = seq2.mods.get(-4); expect(unknownMods2).toBeDefined(); if (unknownMods2) { expect(unknownMods2.length).toBe(2); expect(unknownMods2.every(mod => mod.modValue.primaryValue === "Phospho")).toBe(true); } expect(seq2.toProforma()).toBe("[Phospho]^2?EMEVNESPEK"); const seq3 = sequence_1.Sequence.fromProforma("[Phospho]^2?EMEVNESPEK"); expect(seq3.toStrippedString()).toBe("EMEVNESPEK"); const unknownMods3 = seq3.mods.get(-4); expect(unknownMods3).toBeDefined(); if (unknownMods3) { expect(unknownMods3.length).toBe(2); expect(unknownMods3.every(mod => mod.modValue.primaryValue === "Phospho")).toBe(true); } expect(seq3.toProforma()).toBe("[Phospho]^2?EMEVNESPEK"); }); test('delta mass notation', () => { const proforma = "PEP[+42.011]TIDE"; const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).toBe("PEPTIDE"); expect(seq.seq[2].mods[0].modValue.primaryValue).toBe("+42.011"); expect(Math.abs(seq.seq[2].mods[0].mass - 42.011)).toBeLessThan(0.0001); expect(seq.toProforma()).toBe(proforma); }); test('formula notation', () => { // Valid formulas const validProforma = [ "PEPT[Formula:C2H3NO]IDE", "PEPT[Formula:C-1H2]IDE" ]; for (const proforma of validProforma) { const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).toBe("PEPTIDE"); expect(seq.seq[3].mods[0].source).toBe("Formula"); expect(seq.toProforma()).toBe(proforma); } }); test('glycan notation', () => { const proformaStrings = [ "PEPT[Glycan:HexNAc]IDE", "PEPT[Glycan:HexNAc(1)Hex(1)]IDE", ]; for (const proforma of proformaStrings) { const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).toBe("PEPTIDE"); expect(seq.seq[3].mods[0].source).toBe("Glycan"); expect(seq.seq[3].mods[0].modValue.pipeValues[0].isValidGlycan).toBe(true); expect(seq.toProforma()).toBe(proforma); } }); test('range modifications', () => { // Simple range const proforma = "PRT(ESFRMS)[+19.0523]ISK"; const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).toBe("PRTESFRMSISK"); // Check modification is applied to range (positions 3-8) for (let i = 3; i <= 8; i++) { const rangeMods = seq.seq[i].mods; expect(rangeMods.length).toBeGreaterThanOrEqual(1); expect(rangeMods[0].modValue.primaryValue).toBe("+19.0523"); } expect(seq.toProforma()).toBe(proforma); // Range containing modification const proforma2 = "PRT(EC[Carbamidomethyl]FRMS)[+19.0523]ISK"; const seq2 = sequence_1.Sequence.fromProforma(proforma2); // Check nested modification const nestedMods = seq2.seq[4].mods; expect(nestedMods.some(mod => mod.modValue.primaryValue === "Carbamidomethyl")).toBe(true); // Check range modification still applies for (let i = 3; i <= 8; i++) { const rangeMods = seq2.seq[i].mods; expect(rangeMods.some(mod => mod.modValue.primaryValue === "+19.0523")).toBe(true); } expect(seq2.toProforma()).toBe(proforma2); }); test('localization scores', () => { // Basic example with localization scores const proforma = "EM[Oxidation]EVT[#g1(0.01)]S[#g1(0.09)]ES[Phospho#g1(0.90)]PEK"; const seq = sequence_1.Sequence.fromProforma(proforma); // Check localization scores expect(seq.seq[4].mods[0].modValue.localizationScore).toBe(0.01); expect(seq.seq[5].mods[0].modValue.localizationScore).toBe(0.09); expect(seq.seq[7].mods[0].modValue.localizationScore).toBe(0.90); expect(seq.toProforma()).toBe(proforma); }); test('multiple modifications same residue', () => { // Test multiple modifications on single residue const proforma = "PEPTIDEK[Acetyl][Methyl]"; const seq = sequence_1.Sequence.fromProforma(proforma); const mods = seq.seq[7].mods; expect(mods.length).toBe(2); // Check both modifications exist const modValues = mods.map(m => m.modValue.primaryValue); expect(modValues).toContain("Acetyl"); expect(modValues).toContain("Methyl"); expect(seq.toProforma()).toBe(proforma); }); test('global modifications', () => { // Isotope labeling const proforma1 = "<15N>PEPTIDE"; const seq1 = sequence_1.Sequence.fromProforma(proforma1); expect(seq1.globalMods.length).toBe(1); expect(seq1.globalMods[0].modValue.primaryValue).toBe("15N"); expect(seq1.globalMods[0].globalModType).toBe("isotope"); // Fixed protein modifications with target residues const proforma2 = "<[Carbamidomethyl]@C>PEPTCDE"; const seq2 = sequence_1.Sequence.fromProforma(proforma2); expect(seq2.globalMods.length).toBe(1); expect(seq2.globalMods[0].modValue.primaryValue).toBe("Carbamidomethyl"); expect(seq2.globalMods[0].globalModType).toBe("fixed"); expect(seq2.globalMods[0].targetResidues).toContain("C"); expect(seq2.toProforma()).toBe(proforma2); }); test('sequence ambiguity', () => { // Test simple ambiguity const proforma = "(?DQ)NGTWEM[Oxidation]ESNENFEGYM[Oxidation]K"; const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).toBe("NGTWEMESNENFEGYMK"); // Check ambiguity expect(seq.sequenceAmbiguities.length).toBe(1); expect(seq.sequenceAmbiguities[0].value).toBe("DQ"); expect(seq.sequenceAmbiguities[0].position).toBe(0); // Check modifications are still parsed correctly expect(seq.seq[5].mods[0].modValue.primaryValue).toBe("Oxidation"); expect(seq.seq[15].mods[0].modValue.primaryValue).toBe("Oxidation"); expect(seq.toProforma()).toBe(proforma); }); test('info tags', () => { // Simple info tag const proforma = "ELVIS[Phospho|INFO:newly discovered]K"; const seq = sequence_1.Sequence.fromProforma(proforma); // Check the modification has an info tag expect(seq.seq[4].mods[0].modValue.primaryValue).toBe("Phospho"); expect(seq.seq[4].mods[0].infoTags.length).toBe(1); expect(seq.seq[4].mods[0].infoTags[0]).toBe("newly discovered"); // Multiple info tags const proforma2 = "ELVIS[Phospho|INFO:newly discovered|INFO:Created on 2021-06]K"; const seq2 = sequence_1.Sequence.fromProforma(proforma2); expect(seq2.seq[4].mods[0].infoTags.length).toBe(2); expect(seq2.seq[4].mods[0].infoTags[0]).toBe("newly discovered"); expect(seq2.seq[4].mods[0].infoTags[1]).toBe("Created on 2021-06"); expect(seq2.toProforma()).toBe(proforma2); }); test('gno notation', () => { // Test GNO notation with observed mass const proforma = "NEEYN[GNO:G59626AS]K"; const seq = sequence_1.Sequence.fromProforma(proforma); expect(seq.toStrippedString()).toBe("NEEYNK"); expect(seq.seq[4].mods[0].modValue.primaryValue).toBe("G59626AS"); expect(seq.seq[4].mods[0].source).toBe("GNO"); expect(seq.seq[4].mods[0].modValue.pipeValues[0].isValidGlycan).toBe(true); expect(seq.toProforma()).toBe(proforma); }); test('joint representation', () => { // Basic case with interpretation and mass const proforma = "ELVIS[U:Phospho|+79.966331]K"; const seq = sequence_1.Sequence.fromProforma(proforma); const mod = seq.seq[4].mods[0]; expect(mod.modValue.primaryValue).toBe("Phospho"); expect(mod.source).toBe("U"); expect(mod.modValue.pipeValues[1].mass).toBe(79.966331); // Case with observed mass const proforma2 = "ELVIS[U:Phospho|Obs:+79.978]K"; const seq2 = sequence_1.Sequence.fromProforma(proforma2); const mod2 = seq2.seq[4].mods[0]; expect(mod2.modValue.primaryValue).toBe("Phospho"); expect(mod2.source).toBe("U"); expect(mod2.modValue.pipeValues[1].observedMass).toBe(79.978); expect(seq2.toProforma()).toBe(proforma2); }); test('crosslink joint representation', () => { // Crosslink with mass shift and info tag const proforma = "PEPTK[XL:DSS#XL1|+138.068|INFO:reaction=NHS]IDE"; const seq = sequence_1.Sequence.fromProforma(proforma); const mod = seq.seq[4].mods[0]; expect(mod.modValue.primaryValue).toBe("DSS"); expect(mod.source).toBe("XL"); expect(mod.crosslinkId).toBe("XL1"); if (mod.modValue.pipeValues[1].mass === undefined || mod.modValue.pipeValues[1].mass === null) { throw new Error("Mass value is incorrect"); } expect(Math.abs(mod.modValue.pipeValues[1].mass - 138.068)).toBeLessThan(0.0001); expect(mod.infoTags).toContain("reaction=NHS"); expect(seq.toProforma()).toBe(proforma); }); test('complex multi feature representation', () => { const proforma = "PEP[U:Deamidation|+0.984]T[U:Phospho#1(0.75)|+79.966]K[XL:DSS#XL2]IDE"; const seq = sequence_1.Sequence.fromProforma(proforma); // Check deamidation const mod1 = seq.seq[2].mods[0]; expect(mod1.modValue.primaryValue).toBe("Deamidation"); if (mod1.modValue.pipeValues[1].mass === undefined || mod1.modValue.pipeValues[1].mass === null) { throw new Error("Mass value is incorrect"); } expect(Math.abs(mod1.modValue.pipeValues[1].mass - 0.984)).toBeLessThan(0.0001); // Check phosphorylation with ambiguity group const mod2 = seq.seq[3].mods[0]; expect(mod2.modValue.primaryValue).toBe("Phospho"); if (mod2.modValue.pipeValues[1].mass === undefined || mod2.modValue.pipeValues[1].mass === null) { throw new Error("Mass value is incorrect"); } expect(Math.abs(mod2.modValue.pipeValues[1].mass - 79.966)).toBeLessThan(0.0001); expect(mod2.ambiguityGroup).toBe("1"); expect(mod2.modValue.pipeValues[0].localizationScore).toBe(0.75); // Check crosslink const mod3 = seq.seq[4].mods[0]; expect(mod3.modValue.primaryValue).toBe("DSS"); expect(mod3.crosslinkId).toBe("XL2"); expect(seq.toProforma()).toBe(proforma); }); test('gap notation', () => { // Test gaps of known mass const proforma = "RTAAX[+367.0537]WT"; const seq = sequence_1.Sequence.fromProforma(proforma); // Check sequence contains 'X' expect(seq.toStrippedString()).toBe("RTAAXWT"); // Check gap modification expect(seq.seq[4].value).toBe("X"); expect(seq.seq[4].mods[0].modType).toBe("gap"); expect(seq.seq[4].mods[0].modValue.primaryValue).toBe("+367.0537"); // Verify mass of the gap expect(Math.abs(seq.seq[4].mods[0].mass - 367.0537)).toBeLessThan(0.0001); // Verify roundtrip expect(seq.toProforma()).toBe(proforma); // Test with a negative mass gap const proforma2 = "PEPTX[-10.0]IDE"; const seq2 = sequence_1.Sequence.fromProforma(proforma2); expect(seq2.toStrippedString()).toBe("PEPTXIDE"); expect(seq2.seq[4].mods[0].modValue.primaryValue).toBe("-10.0"); expect(seq2.toProforma()).toBe("PEPTX[-10]IDE"); }); test('from proforma basic chimeric', () => { const inputStr = 'PEPTIDE/2+ANOTHER/3'; const seq = sequence_1.Sequence.fromProforma(inputStr); // Check basic properties expect(seq.isChimeric).toBe(true); expect(seq.peptidoforms.length).toBe(2); // Check first peptidoform expect(seq.toStrippedString()).toBe('PEPTIDE'); expect(seq.charge).toBe(2); // Check second peptidoform expect(seq.peptidoforms[1].toStrippedString()).toBe('ANOTHER'); expect(seq.peptidoforms[1].charge).toBe(3); }); test('from proforma complex chimeric', () => { const inputStr = '[Acetyl]-PEP[+79.966]TIDE-[Amidated]/2[+Na+]+S[Phospho]EQ/3'; const seq = sequence_1.Sequence.fromProforma(inputStr); // Check chimeric properties expect(seq.isChimeric).toBe(true); expect(seq.peptidoforms.length).toBe(2); // First peptidoform specifics expect(seq.toStrippedString()).toBe('PEPTIDE'); expect(seq.charge).toBe(2); expect(seq.ionicSpecies).toBe('+Na+'); const nTermMods = seq.mods.get(-1); if (nTermMods) { expect(nTermMods[0].modValue.primaryValue).toBe('Acetyl'); } const cTermMods = seq.mods.get(-2); if (cTermMods) { expect(cTermMods[0].modValue.primaryValue).toBe('Amidated'); } // Second peptidoform specifics const second = seq.peptidoforms[1]; expect(second.toStrippedString()).toBe('SEQ'); expect(second.charge).toBe(3); expect(second.seq[0].mods.length).toBeGreaterThan(0); // S has phospho mod }); test('to proforma chimeric', () => { // Test round-trip conversion const inputStr = 'PEPTIDE/2+ANOTHER/3'; const seq = sequence_1.Sequence.fromProforma(inputStr); // Generate ProForma string and parse again const proforma = seq.toProforma(); const parsedAgain = sequence_1.Sequence.fromProforma(proforma); // Check key properties match expect(parsedAgain.isChimeric).toBe(true); expect(parsedAgain.peptidoforms.length).toBe(2); expect(parsedAgain.toStrippedString()).toBe('PEPTIDE'); expect(parsedAgain.charge).toBe(2); expect(parsedAgain.peptidoforms[1].toStrippedString()).toBe('ANOTHER'); expect(parsedAgain.peptidoforms[1].charge).toBe(3); }); test('multi chain with chimeric', () => { const inputStr = 'PEP/1+QRS/2//QWR/3+AAC/4'; const seq = sequence_1.Sequence.fromProforma(inputStr); // Check multi-chain properties expect(seq.isMultiChain).toBe(true); expect(seq.chains.length).toBe(2); // Check first chain is chimeric expect(seq.chains[0].isChimeric).toBe(true); expect(seq.chains[0].peptidoforms.length).toBe(2); expect(seq.chains[0].toStrippedString()).toBe('PEP'); expect(seq.chains[0].peptidoforms[1].toStrippedString()).toBe('QRS'); // Check second chain is chimeric expect(seq.chains[1].isChimeric).toBe(true); expect(seq.chains[1].peptidoforms.length).toBe(2); expect(seq.chains[1].toStrippedString()).toBe('QWR'); expect(seq.chains[1].peptidoforms[1].toStrippedString()).toBe('AAC'); }); test('charge representation', () => { // Test basic positive charge const proforma1 = 'EMEVEESPEK/2'; const seq1 = sequence_1.Sequence.fromProforma(proforma1); expect(seq1.toStrippedString()).toBe('EMEVEESPEK'); expect(seq1.charge).toBe(2); expect(seq1.ionicSpecies).toBeNull(); expect(seq1.toProforma()).toBe(proforma1); // Test negative charge const proforma2 = 'EMEVEESPEK/-2'; const seq2 = sequence_1.Sequence.fromProforma(proforma2); expect(seq2.toStrippedString()).toBe('EMEVEESPEK'); expect(seq2.charge).toBe(-2); expect(seq2.ionicSpecies).toBeNull(); expect(seq2.toProforma()).toBe(proforma2); // Test with ionic species const proforma3 = 'EMEVEESPEK/2[+2Na+,+H+]'; const seq3 = sequence_1.Sequence.fromProforma(proforma3); expect(seq3.toStrippedString()).toBe('EMEVEESPEK'); expect(seq3.charge).toBe(2); expect(seq3.ionicSpecies).toBe('+2Na+,+H+'); expect(seq3.toProforma()).toBe(proforma3); // Test with modifications and charge const proforma4 = 'EM[U:Oxidation]EVEES[U:Phospho]PEK/3'; const seq4 = sequence_1.Sequence.fromProforma(proforma4); expect(seq4.toStrippedString()).toBe('EMEVEESPEK'); expect(seq4.charge).toBe(3); expect(seq4.seq[1].mods[0].value).toBe('Oxidation'); expect(seq4.seq[6].mods[0].value).toBe('Phospho'); expect(seq4.toProforma()).toBe(proforma4); // Test with complex structure const proforma5 = '<[Carbamidomethyl]@C>[Acetyl]-PEPTCDE-[Amidated]/1[+Na+]'; const seq5 = sequence_1.Sequence.fromProforma(proforma5); expect(seq5.toStrippedString()).toBe('PEPTCDE'); expect(seq5.charge).toBe(1); expect(seq5.ionicSpecies).toBe('+Na+'); expect(seq5.toProforma()).toBe(proforma5); }); });