diff --git a/ketcher-autotests/tests/Macromolecule-editor/Clear-Tool/clear-tool.spec.ts b/ketcher-autotests/tests/Macromolecule-editor/Clear-Tool/clear-tool.spec.ts index 80f87b8f76..988a9ef617 100644 --- a/ketcher-autotests/tests/Macromolecule-editor/Clear-Tool/clear-tool.spec.ts +++ b/ketcher-autotests/tests/Macromolecule-editor/Clear-Tool/clear-tool.spec.ts @@ -1,6 +1,6 @@ import { test } from '@playwright/test'; import { - addMonomerToCanvas, + addSingleMonomerToCanvas, selectClearCanvasTool, selectSingleBondTool, takeEditorScreenshot, @@ -27,7 +27,7 @@ test.describe('Clear Canvas Tool', () => { const MONOMER_NAME = 'Tza___3-thiazolylalanine'; const MONOMER_ALIAS = 'Tza'; - const peptide1 = await addMonomerToCanvas( + const peptide1 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -35,7 +35,7 @@ test.describe('Clear Canvas Tool', () => { 300, 0, ); - const peptide2 = await addMonomerToCanvas( + const peptide2 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -43,7 +43,7 @@ test.describe('Clear Canvas Tool', () => { 400, 1, ); - const peptide3 = await addMonomerToCanvas( + const peptide3 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -51,7 +51,7 @@ test.describe('Clear Canvas Tool', () => { 500, 2, ); - const peptide4 = await addMonomerToCanvas( + const peptide4 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, diff --git a/ketcher-autotests/tests/Macromolecule-editor/Erase-Tool/erase-tool.spec.ts b/ketcher-autotests/tests/Macromolecule-editor/Erase-Tool/erase-tool.spec.ts index 75a392d0e1..42196b3cec 100644 --- a/ketcher-autotests/tests/Macromolecule-editor/Erase-Tool/erase-tool.spec.ts +++ b/ketcher-autotests/tests/Macromolecule-editor/Erase-Tool/erase-tool.spec.ts @@ -1,6 +1,6 @@ import { test } from '@playwright/test'; import { - addMonomerToCanvas, + addSingleMonomerToCanvas, selectEraseTool, selectSingleBondTool, takeEditorScreenshot, @@ -29,7 +29,7 @@ test.describe('Erase Tool', () => { const MONOMER_NAME = 'Tza___3-thiazolylalanine'; const MONOMER_ALIAS = 'Tza'; - const peptide1 = await addMonomerToCanvas( + const peptide1 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -37,7 +37,7 @@ test.describe('Erase Tool', () => { 300, 0, ); - const peptide2 = await addMonomerToCanvas( + const peptide2 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -45,7 +45,7 @@ test.describe('Erase Tool', () => { 400, 1, ); - const peptide3 = await addMonomerToCanvas( + const peptide3 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -53,7 +53,7 @@ test.describe('Erase Tool', () => { 500, 2, ); - const peptide4 = await addMonomerToCanvas( + const peptide4 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, diff --git a/ketcher-autotests/tests/Macromolecule-editor/Modal-window/modal-window.spec.ts b/ketcher-autotests/tests/Macromolecule-editor/Modal-window/modal-window.spec.ts index 48c6352313..bb472cd090 100644 --- a/ketcher-autotests/tests/Macromolecule-editor/Modal-window/modal-window.spec.ts +++ b/ketcher-autotests/tests/Macromolecule-editor/Modal-window/modal-window.spec.ts @@ -1,6 +1,6 @@ import { Locator, test } from '@playwright/test'; import { - addMonomerToCanvas, + addSingleMonomerToCanvas, selectSingleBondTool, takeEditorScreenshot, waitForPageInit, @@ -19,7 +19,7 @@ test.describe('Modal window', () => { const MONOMER_NAME = 'Test-6-Ch___Test-6-AP-Chem'; const MONOMER_ALIAS = 'Test-6-Ch'; - peptide1 = await addMonomerToCanvas( + peptide1 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -27,7 +27,7 @@ test.describe('Modal window', () => { 200, 0, ); - peptide2 = await addMonomerToCanvas( + peptide2 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -51,7 +51,15 @@ test.describe('Modal window', () => { */ // Create bonds between peptides - await bondTwoMonomers(page, peptide1, peptide2); + await bondTwoMonomers( + page, + peptide1, + peptide2, + undefined, + undefined, + false, + false, + ); }); test('"Connect" button is active', async ({ page }) => { @@ -62,10 +70,6 @@ test.describe('Modal window', () => { */ // Create bonds between peptides - await bondTwoMonomers(page, peptide1, peptide2); - - // Chose attachment points - await page.locator('button[title=R1]').nth(0).click(); - await page.locator('button[title=R2]').nth(1).click(); + await bondTwoMonomers(page, peptide1, peptide2, 'R1', 'R2', true, false); }); }); diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/attachment-point-hover.spec.ts b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/attachment-point-hover.spec.ts index 6c4ff1b3f1..602f1040db 100644 --- a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/attachment-point-hover.spec.ts +++ b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/attachment-point-hover.spec.ts @@ -1,6 +1,6 @@ import { test } from '@playwright/test'; import { - addMonomerToCanvas, + addSingleMonomerToCanvas, selectSingleBondTool, waitForPageInit, takeEditorScreenshot, @@ -20,7 +20,7 @@ test.describe('Check attachment point hover', () => { const MONOMER_NAME = 'Tza___3-thiazolylalanine'; const MONOMER_ALIAS = 'Tza'; const coordinatesStart = { x: 300, y: 300 }; - const peptide1 = await addMonomerToCanvas( + const peptide1 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -29,7 +29,7 @@ test.describe('Check attachment point hover', () => { 0, ); const coordinatesEnd = { x: 400, y: 400 }; - const peptide2 = await addMonomerToCanvas( + const peptide2 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/attachment-point-rotation.spec.ts b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/attachment-point-rotation.spec.ts index c706478747..c681e164b8 100644 --- a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/attachment-point-rotation.spec.ts +++ b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/attachment-point-rotation.spec.ts @@ -1,6 +1,6 @@ import { test } from '@playwright/test'; import { - addMonomerToCanvas, + addSingleMonomerToCanvas, dragMouseTo, selectRectangleSelectionTool, selectSingleBondTool, @@ -28,7 +28,7 @@ test.describe('Check attachment point rotation', () => { const MONOMER_ALIAS = 'Tza'; // Get 4 peptides locators - const peptide1 = await addMonomerToCanvas( + const peptide1 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -36,7 +36,7 @@ test.describe('Check attachment point rotation', () => { 300, 0, ); - const peptide2 = await addMonomerToCanvas( + const peptide2 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -44,7 +44,7 @@ test.describe('Check attachment point rotation', () => { 400, 1, ); - const peptide3 = await addMonomerToCanvas( + const peptide3 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -52,7 +52,7 @@ test.describe('Check attachment point rotation', () => { 500, 2, ); - const peptide4 = await addMonomerToCanvas( + const peptide4 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -98,7 +98,7 @@ test.describe('Check attachment point rotation', () => { const MONOMER_ALIAS = 'Tza'; // Get 4 peptides locators - const peptide1 = await addMonomerToCanvas( + const peptide1 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -106,7 +106,7 @@ test.describe('Check attachment point rotation', () => { 300, 0, ); - const peptide2 = await addMonomerToCanvas( + const peptide2 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/polymer-bond-tool.spec.ts b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/polymer-bond-tool.spec.ts index 3886302de8..009ad1ce72 100644 --- a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/polymer-bond-tool.spec.ts +++ b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/polymer-bond-tool.spec.ts @@ -3,7 +3,7 @@ import { selectSingleBondTool, waitForPageInit, takeEditorScreenshot, - addMonomerToCanvas, + addSingleMonomerToCanvas, } from '@utils'; import { hideMonomerPreview, @@ -31,7 +31,7 @@ test.describe('Polymer Bond Tool', () => { const MONOMER_NAME = 'Tza___3-thiazolylalanine'; const MONOMER_ALIAS = 'Tza'; - const peptide1 = await addMonomerToCanvas( + const peptide1 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -39,7 +39,7 @@ test.describe('Polymer Bond Tool', () => { 300, 0, ); - const peptide2 = await addMonomerToCanvas( + const peptide2 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -47,7 +47,7 @@ test.describe('Polymer Bond Tool', () => { 400, 1, ); - const peptide3 = await addMonomerToCanvas( + const peptide3 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -55,7 +55,7 @@ test.describe('Polymer Bond Tool', () => { 500, 2, ); - const peptide4 = await addMonomerToCanvas( + const peptide4 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -126,7 +126,7 @@ test.describe('Signle Bond Tool', () => { */ const MONOMER_NAME = 'Tza___3-thiazolylalanine'; const MONOMER_ALIAS = 'Tza'; - const peptide1 = await addMonomerToCanvas( + const peptide1 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -134,7 +134,7 @@ test.describe('Signle Bond Tool', () => { 300, 0, ); - const peptide2 = await addMonomerToCanvas( + const peptide2 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts index 062c51a707..ea83c850ac 100644 --- a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts +++ b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts @@ -1,12 +1,14 @@ import { Page, test, expect } from '@playwright/test'; import { - addMonomerToCanvas, + addSingleMonomerToCanvas, + addRnaPresetOnCanvas, clickRedo, clickUndo, selectSingleBondTool, selectSnakeBondTool, takeEditorScreenshot, waitForPageInit, + addBondedMonomersToCanvas, } from '@utils'; import { turnOnMacromoleculesEditor } from '@utils/macromolecules'; import { bondTwoMonomers } from '@utils/macromolecules/polymerBond'; @@ -21,7 +23,7 @@ async function createBondedMonomers(page: Page) { const MONOMER_NAME_MEC = 'meC___N-Methyl-Cysteine'; const MONOMER_ALIAS_MEC = 'meC'; - const peptide1 = await addMonomerToCanvas( + const peptide1 = await addSingleMonomerToCanvas( page, MONOMER_NAME_DSEC, MONOMER_ALIAS_DSEC, @@ -30,24 +32,18 @@ async function createBondedMonomers(page: Page) { 0, ); - const peptide2 = await addMonomerToCanvas( + const [peptide2, peptide3] = await addBondedMonomersToCanvas( page, MONOMER_NAME_TZA, MONOMER_ALIAS_TZA, 100, 100, - 0, - ); - const peptide3 = await addMonomerToCanvas( - page, - MONOMER_NAME_TZA, - MONOMER_ALIAS_TZA, - 150, - 150, - 1, + 50, + 50, + 2, ); - const peptide4 = await addMonomerToCanvas( + const peptide4 = await addSingleMonomerToCanvas( page, MONOMER_NAME_MEC, MONOMER_ALIAS_MEC, @@ -59,7 +55,6 @@ async function createBondedMonomers(page: Page) { await selectSingleBondTool(page); await bondTwoMonomers(page, peptide1, peptide2); - await bondTwoMonomers(page, peptide2, peptide3); await bondTwoMonomers(page, peptide3, peptide4); } @@ -76,24 +71,17 @@ test.describe('Snake Bond Tool', () => { */ await selectSnakeBondTool(page); - - const peptide1 = await addMonomerToCanvas( + const [, peptide2] = await addBondedMonomersToCanvas( page, MONOMER_NAME_TZA, MONOMER_ALIAS_TZA, 300, 300, - 0, - ); - const peptide2 = await addMonomerToCanvas( - page, - MONOMER_NAME_TZA, - MONOMER_ALIAS_TZA, - 400, - 400, - 1, + 100, + 100, + 2, ); - const peptide3 = await addMonomerToCanvas( + const peptide3 = await addSingleMonomerToCanvas( page, MONOMER_NAME_TZA, MONOMER_ALIAS_TZA, @@ -101,7 +89,7 @@ test.describe('Snake Bond Tool', () => { 500, 2, ); - const peptide4 = await addMonomerToCanvas( + const peptide4 = await addSingleMonomerToCanvas( page, MONOMER_NAME_TZA, MONOMER_ALIAS_TZA, @@ -112,161 +100,415 @@ test.describe('Snake Bond Tool', () => { await selectSingleBondTool(page); - await bondTwoMonomers(page, peptide1, peptide2); await bondTwoMonomers(page, peptide2, peptide3); await bondTwoMonomers(page, peptide3, peptide4); await takeEditorScreenshot(page); }); - test('Check snake mode arrange', async ({ page }) => { + test('Check snake mode arrange for peptides chain', async ({ page }) => { /* Test case: #3280 - Check snake mode Description: Snake bond tool */ - const peptide1 = await addMonomerToCanvas( + await addBondedMonomersToCanvas( page, MONOMER_NAME_TZA, MONOMER_ALIAS_TZA, 100, 100, + 50, + 50, + 12, + ); + + await selectSnakeBondTool(page); + + await takeEditorScreenshot(page); + }); + + test('Check finding right chain sequence using snake mode', async ({ + page, + }) => { + /* + Test case: #3280 - Check finding right chain sequence using snake mode + Description: Snake bond tool + */ + await createBondedMonomers(page); + await takeEditorScreenshot(page); + await selectSnakeBondTool(page); + await takeEditorScreenshot(page); + }); + + test('Button is not active after undo', async ({ page }) => { + const snakeModeButton = page.getByTestId('snake-mode'); + await createBondedMonomers(page); + await expect(snakeModeButton).not.toHaveClass(/active/); + + await selectSnakeBondTool(page); + await expect(snakeModeButton).toHaveClass(/active/); + + await clickUndo(page); + await expect(snakeModeButton).not.toHaveClass(/active/); + + await clickRedo(page); + await expect(snakeModeButton).toHaveClass(/active/); + }); + + test('Create snake bond between RNA nucleotides', async ({ page }) => { + await page.getByText('RNA').click(); + await selectSnakeBondTool(page); + + const { phosphate } = await addRnaPresetOnCanvas( + page, + 'A_A_R_P', + 300, + 300, + 0, 0, ); - const peptide2 = await addMonomerToCanvas( + const { sugar: sugar1, phosphate: phosphate1 } = await addRnaPresetOnCanvas( page, - MONOMER_NAME_TZA, - MONOMER_ALIAS_TZA, - 150, - 150, + 'C_C_R_P', + 400, + 600, + 1, 1, ); - const peptide3 = await addMonomerToCanvas( + const { sugar: sugar2 } = await addRnaPresetOnCanvas( page, - MONOMER_NAME_TZA, - MONOMER_ALIAS_TZA, - 200, - 200, + 'G_G_R_P', + 600, + 400, + 2, 2, ); - const peptide4 = await addMonomerToCanvas( - page, - MONOMER_NAME_TZA, - MONOMER_ALIAS_TZA, - 250, - 250, - 3, - ); - const peptide5 = await addMonomerToCanvas( + + await selectSingleBondTool(page); + + await bondTwoMonomers(page, phosphate, sugar1); + await bondTwoMonomers(page, phosphate1, sugar2); + + await takeEditorScreenshot(page); + }); + + test('Check snake mode arrange for RNA chain', async ({ page }) => { + await page.getByText('RNA').click(); + + const { phosphate } = await addRnaPresetOnCanvas( page, - MONOMER_NAME_TZA, - MONOMER_ALIAS_TZA, + 'A_A_R_P', 300, 300, - 4, + 0, + 0, ); - const peptide6 = await addMonomerToCanvas( + const { sugar: sugar1, phosphate: phosphate1 } = await addRnaPresetOnCanvas( page, - MONOMER_NAME_TZA, - MONOMER_ALIAS_TZA, - 350, - 350, - 5, + 'C_C_R_P', + 400, + 600, + 1, + 1, ); - const peptide7 = await addMonomerToCanvas( + const { sugar: sugar2, phosphate: phosphate2 } = await addRnaPresetOnCanvas( page, - MONOMER_NAME_TZA, - MONOMER_ALIAS_TZA, - 400, + 'G_G_R_P', + 600, 400, - 6, + 2, + 2, ); - const peptide8 = await addMonomerToCanvas( + const { sugar: sugar3, phosphate: phosphate3 } = await addRnaPresetOnCanvas( page, - MONOMER_NAME_TZA, - MONOMER_ALIAS_TZA, - 450, - 450, - 7, + 'T_T_R_P', + 800, + 200, + 3, + 3, ); - const peptide9 = await addMonomerToCanvas( + const { sugar: sugar4 } = await addRnaPresetOnCanvas( + page, + 'T_T_R_P', + 100, + 100, + 4, + 4, + ); + + await selectSingleBondTool(page); + + await bondTwoMonomers(page, phosphate, sugar1); + await bondTwoMonomers(page, phosphate1, sugar2); + await bondTwoMonomers(page, phosphate2, sugar3); + await bondTwoMonomers(page, phosphate3, sugar4); + + await selectSnakeBondTool(page); + await takeEditorScreenshot(page); + }); + + test('Create snake bond for mix chains with nucleotides and peptides', async ({ + page, + }) => { + const [peptide1] = await addBondedMonomersToCanvas( page, MONOMER_NAME_TZA, MONOMER_ALIAS_TZA, 500, 500, - 8, + 100, + 100, + 3, ); - const peptide10 = await addMonomerToCanvas( + + await addBondedMonomersToCanvas( page, - MONOMER_NAME_TZA, - MONOMER_ALIAS_TZA, - 550, - 550, - 9, + 'Bal___beta-Alanine', + 'Bal', + 700, + 500, + 50, + 50, + 4, ); + await page.getByText('RNA').click(); - const peptide11 = await addMonomerToCanvas( + const { phosphate } = await addRnaPresetOnCanvas( page, - MONOMER_NAME_TZA, - MONOMER_ALIAS_TZA, - 600, - 600, - 10, + 'A_A_R_P', + 200, + 200, + 0, + 0, ); - const peptide12 = await addMonomerToCanvas( + const { sugar: sugar1, phosphate: phosphate1 } = await addRnaPresetOnCanvas( page, - MONOMER_NAME_TZA, - MONOMER_ALIAS_TZA, - 650, - 650, - 11, + 'C_C_R_P', + 300, + 500, + 1, + 1, + ); + const { sugar: sugar2, phosphate: phosphate2 } = await addRnaPresetOnCanvas( + page, + 'G_G_R_P', + 400, + 300, + 2, + 2, ); await selectSingleBondTool(page); - await bondTwoMonomers(page, peptide1, peptide2); - await bondTwoMonomers(page, peptide2, peptide3); - await bondTwoMonomers(page, peptide3, peptide4); - await bondTwoMonomers(page, peptide4, peptide5); - await bondTwoMonomers(page, peptide5, peptide6); - await bondTwoMonomers(page, peptide6, peptide7); - await bondTwoMonomers(page, peptide7, peptide8); - await bondTwoMonomers(page, peptide8, peptide9); - await bondTwoMonomers(page, peptide9, peptide10); - await bondTwoMonomers(page, peptide10, peptide11); - await bondTwoMonomers(page, peptide11, peptide12); + await bondTwoMonomers(page, phosphate, sugar1); + await bondTwoMonomers(page, phosphate1, sugar2); + await bondTwoMonomers(page, phosphate2, peptide1, undefined, 'R1'); + + await takeEditorScreenshot(page); await selectSnakeBondTool(page); + await takeEditorScreenshot(page); + await selectSnakeBondTool(page); await takeEditorScreenshot(page); }); - test('Check finding right chain sequence using snake mode', async ({ - page, - }) => { - /* - Test case: #3280 - Check finding right chain sequence using snake mode - Description: Snake bond tool - */ - await createBondedMonomers(page); + test('Create snake bond for chain with nucleoside', async ({ page }) => { + await page.getByText('RNA').click(); + await selectSnakeBondTool(page); + + const { phosphate } = await addRnaPresetOnCanvas( + page, + 'A_A_R_P', + 200, + 200, + 0, + 0, + ); + const { sugar } = await addRnaPresetOnCanvas( + page, + 'G_G_R_P', + 700, + 300, + 1, + 1, + ); + + await page.getByTestId('summary-Sugars').click(); + const sugarOfNucleoside = await addSingleMonomerToCanvas( + page, + 'R___Ribose', + 'R', + 500, + 500, + 2, + ); + await page.getByTestId('summary-Bases').click(); + const baseOfNucleoside = await addSingleMonomerToCanvas( + page, + 'A___Adenine', + 'A', + 600, + 600, + 1, + ); + + await selectSingleBondTool(page); + await bondTwoMonomers(page, sugarOfNucleoside, baseOfNucleoside); + await bondTwoMonomers(page, phosphate, sugarOfNucleoside); + await bondTwoMonomers(page, sugarOfNucleoside, sugar); + await takeEditorScreenshot(page); + + await selectSnakeBondTool(page); + await takeEditorScreenshot(page); + await selectSnakeBondTool(page); await takeEditorScreenshot(page); }); - test('Button is not active after undo', async ({ page }) => { - const snakeModeButton = page.getByTestId('snake-mode'); - await createBondedMonomers(page); - await expect(snakeModeButton).not.toHaveClass(/active/); + test('Create snake bond for chain with side chains', async ({ page }) => { + await page.getByText('RNA').click(); + const { phosphate } = await addRnaPresetOnCanvas( + page, + 'C_C_R_P', + 200, + 200, + 0, + 0, + ); + const { sugar: sugar1, phosphate: phosphate1 } = await addRnaPresetOnCanvas( + page, + 'G_G_R_P', + 500, + 300, + 1, + 1, + ); + const { sugar: sugar2, phosphate: phosphate2 } = await addRnaPresetOnCanvas( + page, + 'T_T_R_P', + 700, + 300, + 2, + 2, + ); + await addRnaPresetOnCanvas(page, 'U_U_R_P', 900, 300, 3, 3); + await page.getByTestId('summary-Sugars').click(); + const sugarOfNucleoside = await addSingleMonomerToCanvas( + page, + 'R___Ribose', + 'R', + 350, + 350, + 4, + ); + await page.getByTestId('summary-Bases').click(); + const baseOfNucleoside = await addSingleMonomerToCanvas( + page, + 'nC6n8A___6-Aminohexyl-8-aminoadenine', + 'nC6n8A', + 350, + 500, + 0, + ); + + await page.getByTestId('PEPTIDES-TAB').click(); + const [peptide] = await addBondedMonomersToCanvas( + page, + 'A___Alanine', + 'A', + 500, + 500, + 50, + 50, + 3, + ); + + const [hcyPeptide, hcyPeptide1] = await addBondedMonomersToCanvas( + page, + 'Hcy___homocysteine', + 'Hcy', + 600, + 500, + 50, + 0, + 2, + ); + + const [balPeptide] = await addBondedMonomersToCanvas( + page, + 'Bal___beta-Alanine', + 'Bal', + 700, + 700, + 50, + 0, + 2, + ); + const balPeptide1 = await addSingleMonomerToCanvas( + page, + 'Bal___beta-Alanine', + 'Bal', + 850, + 650, + 2, + ); + + await selectSingleBondTool(page); + await bondTwoMonomers(page, sugarOfNucleoside, baseOfNucleoside); + await bondTwoMonomers(page, baseOfNucleoside, peptide, 'R2', 'R1'); + + await bondTwoMonomers(page, phosphate, sugarOfNucleoside); + await bondTwoMonomers(page, sugarOfNucleoside, sugar1); + await bondTwoMonomers(page, phosphate1, sugar2); + await bondTwoMonomers(page, phosphate2, hcyPeptide, undefined, 'R1'); + await bondTwoMonomers(page, hcyPeptide1, balPeptide); + await bondTwoMonomers(page, hcyPeptide1, balPeptide1, undefined, 'R1'); + await takeEditorScreenshot(page); await selectSnakeBondTool(page); - await expect(snakeModeButton).toHaveClass(/active/); + await takeEditorScreenshot(page); + }); - await clickUndo(page); - await expect(snakeModeButton).not.toHaveClass(/active/); + test('Create snake mode for single monomer and nucleoside', async ({ + page, + }) => { + await addSingleMonomerToCanvas( + page, + 'Bal___beta-Alanine', + 'Bal', + 300, + 300, + 0, + ); + await page.getByText('RNA').click(); + await page.getByTestId('summary-Sugars').click(); + const sugarOfNucleoside = await addSingleMonomerToCanvas( + page, + 'R___Ribose', + 'R', + 500, + 500, + 0, + ); + await page.getByTestId('summary-Bases').click(); + const baseOfNucleoside = await addSingleMonomerToCanvas( + page, + 'A___Adenine', + 'A', + 600, + 600, + 0, + ); - await clickRedo(page); - await expect(snakeModeButton).toHaveClass(/active/); + await selectSingleBondTool(page); + await bondTwoMonomers(page, sugarOfNucleoside, baseOfNucleoside); + + await takeEditorScreenshot(page); + + await selectSnakeBondTool(page); + await takeEditorScreenshot(page); }); }); diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Check-snake-mode-arrange-for-RNA-chain-1-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Check-snake-mode-arrange-for-RNA-chain-1-chromium-linux.png new file mode 100644 index 0000000000..35b38b190c Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Check-snake-mode-arrange-for-RNA-chain-1-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Check-snake-mode-arrange-for-peptides-chain-1-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Check-snake-mode-arrange-for-peptides-chain-1-chromium-linux.png new file mode 100644 index 0000000000..5e00bcc305 Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Check-snake-mode-arrange-for-peptides-chain-1-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-between-RNA-nucleotides-1-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-between-RNA-nucleotides-1-chromium-linux.png new file mode 100644 index 0000000000..12edb8f5d1 Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-between-RNA-nucleotides-1-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-between-peptides-1-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-between-peptides-1-chromium-linux.png index d704249164..4064845271 100644 Binary files a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-between-peptides-1-chromium-linux.png and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-between-peptides-1-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-nucleoside-1-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-nucleoside-1-chromium-linux.png new file mode 100644 index 0000000000..7af39b6f5e Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-nucleoside-1-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-nucleoside-2-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-nucleoside-2-chromium-linux.png new file mode 100644 index 0000000000..22043ab724 Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-nucleoside-2-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-nucleoside-3-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-nucleoside-3-chromium-linux.png new file mode 100644 index 0000000000..dac8ed6258 Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-nucleoside-3-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-side-chains-1-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-side-chains-1-chromium-linux.png new file mode 100644 index 0000000000..deffcbb092 Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-side-chains-1-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-side-chains-2-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-side-chains-2-chromium-linux.png new file mode 100644 index 0000000000..a34efd8bde Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-chain-with-side-chains-2-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-different-chains-1-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-different-chains-1-chromium-linux.png new file mode 100644 index 0000000000..f940b79fd3 Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-different-chains-1-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-different-chains-2-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-different-chains-2-chromium-linux.png new file mode 100644 index 0000000000..29e076e902 Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-different-chains-2-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-different-chains-3-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-different-chains-3-chromium-linux.png new file mode 100644 index 0000000000..8a55abb1ff Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-different-chains-3-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-mix-chains-with-nucleotides-and-peptides-1-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-mix-chains-with-nucleotides-and-peptides-1-chromium-linux.png new file mode 100644 index 0000000000..126b14eadf Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-mix-chains-with-nucleotides-and-peptides-1-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-mix-chains-with-nucleotides-and-peptides-2-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-mix-chains-with-nucleotides-and-peptides-2-chromium-linux.png new file mode 100644 index 0000000000..b56025d9a8 Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-mix-chains-with-nucleotides-and-peptides-2-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-mix-chains-with-nucleotides-and-peptides-3-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-mix-chains-with-nucleotides-and-peptides-3-chromium-linux.png new file mode 100644 index 0000000000..f9d4506e1d Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-bond-for-mix-chains-with-nucleotides-and-peptides-3-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-mode-for-single-monomer-and-nucleoside-1-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-mode-for-single-monomer-and-nucleoside-1-chromium-linux.png new file mode 100644 index 0000000000..161989374b Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-mode-for-single-monomer-and-nucleoside-1-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-mode-for-single-monomer-and-nucleoside-2-chromium-linux.png b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-mode-for-single-monomer-and-nucleoside-2-chromium-linux.png new file mode 100644 index 0000000000..89cea56f79 Binary files /dev/null and b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/snake-bond-tool.spec.ts-snapshots/Snake-Bond-Tool-Create-snake-mode-for-single-monomer-and-nucleoside-2-chromium-linux.png differ diff --git a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/zoom-tool.spec.ts b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/zoom-tool.spec.ts index 1b840c7718..2451f7e94d 100644 --- a/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/zoom-tool.spec.ts +++ b/ketcher-autotests/tests/Macromolecule-editor/Polymer-Bond-Tool/zoom-tool.spec.ts @@ -1,7 +1,7 @@ import { Locator, test } from '@playwright/test'; import { LeftPanelButton, - addMonomerToCanvas, + addSingleMonomerToCanvas, selectRectangleArea, selectRectangleSelectionTool, selectSingleBondTool, @@ -25,7 +25,7 @@ test.describe('Zoom Tool', () => { test.beforeEach(async ({ page }) => { await waitForPageInit(page); await turnOnMacromoleculesEditor(page); - peptide = await addMonomerToCanvas( + peptide = await addSingleMonomerToCanvas( page, MONOMER_NAME_TZA, MONOMER_ALIAS_TZA, diff --git a/ketcher-autotests/tests/Macromolecule-editor/Rectangle-Selection-Tool/rectangle-selection-tool.spec.ts b/ketcher-autotests/tests/Macromolecule-editor/Rectangle-Selection-Tool/rectangle-selection-tool.spec.ts index 47cca7e590..a85220a951 100644 --- a/ketcher-autotests/tests/Macromolecule-editor/Rectangle-Selection-Tool/rectangle-selection-tool.spec.ts +++ b/ketcher-autotests/tests/Macromolecule-editor/Rectangle-Selection-Tool/rectangle-selection-tool.spec.ts @@ -1,7 +1,7 @@ import { Page, test } from '@playwright/test'; import { addChemOnCanvas, - addMonomerToCanvas, + addSingleMonomerToCanvas, addPeptideOnCanvas, dragMouseTo, getCoordinatesOfTheMiddleOfTheScreen, @@ -49,7 +49,7 @@ test.describe('Rectangle Selection Tool', () => { const MONOMER_NAME = 'Tza___3-thiazolylalanine'; const MONOMER_ALIAS = 'Tza'; - const peptide1 = await addMonomerToCanvas( + const peptide1 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -57,7 +57,7 @@ test.describe('Rectangle Selection Tool', () => { 300, 0, ); - const peptide2 = await addMonomerToCanvas( + const peptide2 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -65,7 +65,7 @@ test.describe('Rectangle Selection Tool', () => { 400, 1, ); - const peptide3 = await addMonomerToCanvas( + const peptide3 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -73,7 +73,7 @@ test.describe('Rectangle Selection Tool', () => { 500, 2, ); - const peptide4 = await addMonomerToCanvas( + const peptide4 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -187,7 +187,7 @@ test.describe('Rectangle Selection Tool', () => { const MONOMER_NAME = 'Tza___3-thiazolylalanine'; const MONOMER_ALIAS = 'Tza'; - const peptide1 = await addMonomerToCanvas( + const peptide1 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -195,7 +195,7 @@ test.describe('Rectangle Selection Tool', () => { 300, 0, ); - const peptide2 = await addMonomerToCanvas( + const peptide2 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -203,7 +203,7 @@ test.describe('Rectangle Selection Tool', () => { 400, 1, ); - const peptide3 = await addMonomerToCanvas( + const peptide3 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -211,7 +211,7 @@ test.describe('Rectangle Selection Tool', () => { 500, 2, ); - const peptide4 = await addMonomerToCanvas( + const peptide4 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, diff --git a/ketcher-autotests/tests/Macromolecule-editor/Undo-Redo/undo-redo.spec.ts b/ketcher-autotests/tests/Macromolecule-editor/Undo-Redo/undo-redo.spec.ts index ee182057cf..6d3434a685 100644 --- a/ketcher-autotests/tests/Macromolecule-editor/Undo-Redo/undo-redo.spec.ts +++ b/ketcher-autotests/tests/Macromolecule-editor/Undo-Redo/undo-redo.spec.ts @@ -1,6 +1,6 @@ import { Locator, test } from '@playwright/test'; import { - addMonomerToCanvas, + addSingleMonomerToCanvas, clickRedo, clickUndo, dragMouseTo, @@ -28,7 +28,7 @@ test.describe('Undo Redo', () => { const MONOMER_NAME = 'Tza___3-thiazolylalanine'; const MONOMER_ALIAS = 'Tza'; - peptide1 = await addMonomerToCanvas( + peptide1 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -36,7 +36,7 @@ test.describe('Undo Redo', () => { 300, 0, ); - peptide2 = await addMonomerToCanvas( + peptide2 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, @@ -44,7 +44,7 @@ test.describe('Undo Redo', () => { 300, 1, ); - const peptide3 = await addMonomerToCanvas( + const peptide3 = await addSingleMonomerToCanvas( page, MONOMER_NAME, MONOMER_ALIAS, diff --git a/ketcher-autotests/tests/utils/canvas/helpers.ts b/ketcher-autotests/tests/utils/canvas/helpers.ts index 473ac0ad62..60fab70dcc 100644 --- a/ketcher-autotests/tests/utils/canvas/helpers.ts +++ b/ketcher-autotests/tests/utils/canvas/helpers.ts @@ -32,6 +32,7 @@ import { import { getLeftTopBarSize } from './common/getLeftTopBarSize'; import { emptyFunction } from '@utils/common/helpers'; import { hideMonomerPreview } from '@utils/macromolecules'; +import { bondTwoMonomers } from '@utils/macromolecules/polymerBond'; export async function drawBenzeneRing(page: Page) { await selectRing(RingButton.Benzene, page); @@ -295,7 +296,7 @@ export async function resetAllSettingsToDefault(page: Page) { await pressButton(page, 'Apply'); } -export async function addMonomerToCanvas( +export async function addSingleMonomerToCanvas( page: Page, monomerFullName: string, alias: string, @@ -311,6 +312,42 @@ export async function addMonomerToCanvas( .nth(index); } +export async function addBondedMonomersToCanvas( + page: Page, + monomerFullName: string, + alias: string, + initialPositionX: number, + initialPositionY: number, + deltaX: number, + deltaY: number, + amount: number, + connectTitle1?: string, + connectTitle2?: string, +) { + const monomers = []; + for (let index = 0; index < amount; index++) { + const monomer = await addSingleMonomerToCanvas( + page, + monomerFullName, + alias, + initialPositionX + deltaX * index, + initialPositionY + deltaY * index, + index, + ); + monomers.push(monomer); + if (index > 0) { + await bondTwoMonomers( + page, + monomers[index - 1], + monomer, + connectTitle1, + connectTitle2, + ); + } + } + return monomers; +} + export async function addMonomerToCenterOfCanvas( page: Page, monomerType: Sugars | Bases | Phosphates, @@ -325,6 +362,27 @@ export async function addPeptideOnCanvas(page: Page, peptideId: string) { await clickInTheMiddleOfTheScreen(page); } +export async function addRnaPresetOnCanvas( + page: Page, + presetId: string, + positionX: number, + positionY: number, + sugarIndex: number, + phosphateIndex: number, +) { + await page.getByTestId(presetId).click(); + await page.mouse.click(positionX, positionY); + await hideMonomerPreview(page); + const sugar = await page + .locator(`//\*[name() = 'g' and ./\*[name()='text' and .='R']]`) + .nth(sugarIndex); + const phosphate = await page + .locator(`//\*[name() = 'g' and ./\*[name()='text' and .='P']]`) + .nth(phosphateIndex); + + return { sugar, phosphate }; +} + export async function addChemOnCanvas(page: Page, chemId: string) { await page.getByTestId('CHEM-TAB').click(); await page.getByTestId(chemId).click(); diff --git a/ketcher-autotests/tests/utils/macromolecules/polymerBond.ts b/ketcher-autotests/tests/utils/macromolecules/polymerBond.ts index 0b7eb58506..cab3ba0e1d 100644 --- a/ketcher-autotests/tests/utils/macromolecules/polymerBond.ts +++ b/ketcher-autotests/tests/utils/macromolecules/polymerBond.ts @@ -1,14 +1,32 @@ import { Locator, Page } from '@playwright/test'; import { hideMonomerPreview } from '@utils/macromolecules/index'; +import { selectSingleBondTool } from '..'; export async function bondTwoMonomers( page: Page, firstMonomerElement: Locator, secondMonomerElement: Locator, + connectTitle1?: string, + connectTitle2?: string, + needSelectAttachmentPoint = true, + needConnect = true, ) { + await selectSingleBondTool(page); await firstMonomerElement.hover(); await page.mouse.down(); await secondMonomerElement.hover(); await page.mouse.up(); await hideMonomerPreview(page); + const dialog = await page.getByRole('dialog'); + if ((await dialog.isVisible()) && needSelectAttachmentPoint) { + if (connectTitle1) { + await page.locator(`button[title='${connectTitle1}']`).nth(0).click(); + } + if (connectTitle2) { + await page.locator(`button[title='${connectTitle2}']`).nth(1).click(); + } + if (needConnect) { + await page.locator('button[title=Connect]').click(); + } + } } diff --git a/packages/ketcher-core/src/application/editor/tools/RnaPreset.ts b/packages/ketcher-core/src/application/editor/tools/RnaPreset.ts index 4d85cb41a0..2cb6ea281c 100644 --- a/packages/ketcher-core/src/application/editor/tools/RnaPreset.ts +++ b/packages/ketcher-core/src/application/editor/tools/RnaPreset.ts @@ -25,6 +25,7 @@ import { RNABase } from 'domain/entities/RNABase'; import { Phosphate } from 'domain/entities/Phosphate'; import { Coordinates } from '../shared/coordinates'; +export const RNA_MONOMER_DISTANCE = 45; class RnaPresetTool implements Tool { rnaBase: MonomerItemType | undefined; sugar: MonomerItemType | undefined; @@ -78,7 +79,7 @@ class RnaPresetTool implements Tool { new Vec2( this.editor.lastCursorPositionOfCanvas.x + this.sugarPreviewRenderer?.width + - 45, + RNA_MONOMER_DISTANCE, this.editor.lastCursorPositionOfCanvas.y, ), ) @@ -90,7 +91,7 @@ class RnaPresetTool implements Tool { this.editor.lastCursorPositionOfCanvas.x, this.editor.lastCursorPositionOfCanvas.y + this.sugarPreviewRenderer.height + - 45, + RNA_MONOMER_DISTANCE, ), ) : undefined, diff --git a/packages/ketcher-core/src/application/render/renderers/PolymerBondRenderer.ts b/packages/ketcher-core/src/application/render/renderers/PolymerBondRenderer.ts index eab72cbb90..6c66a8e43e 100644 --- a/packages/ketcher-core/src/application/render/renderers/PolymerBondRenderer.ts +++ b/packages/ketcher-core/src/application/render/renderers/PolymerBondRenderer.ts @@ -4,7 +4,7 @@ import { DrawingEntity } from 'domain/entities/DrawingEntity'; import assert from 'assert'; import { D3SvgElementSelection } from 'application/render/types'; import { editorEvents } from 'application/editor/editorEvents'; -import { Vec2 } from 'domain/entities'; +import { Phosphate, Sugar, Vec2 } from 'domain/entities'; import { Peptide } from 'domain/entities/Peptide'; import { Chem } from 'domain/entities/Chem'; import { BaseMonomer } from 'domain/entities/BaseMonomer'; @@ -13,6 +13,7 @@ import { Coordinates } from 'application/editor/shared/coordinates'; const LINE_FROM_MONOMER_LENGTH = 15; const VERTICAL_LINE_LENGTH = 42; +const RNA_CHAIN_VERTICAL_LINE_LENGTH = 148; const CORNER_LENGTH = 8; const DOUBLE_CORNER_LENGTH = CORNER_LENGTH * 2; enum LINE_DIRECTION { @@ -36,14 +37,23 @@ export class PolymerBondRenderer extends BaseRenderer { } private isSnakeBondAvailableForMonomer(monomer?: BaseMonomer) { - return monomer instanceof Peptide || monomer instanceof Chem; + return ( + monomer instanceof Peptide || + monomer instanceof Chem || + monomer instanceof Sugar || + monomer instanceof Phosphate + ); } get isSnake() { if ( !this.isSnakeBondAvailableForMonomer(this.polymerBond.firstMonomer) || (this.polymerBond.secondMonomer && - !this.isSnakeBondAvailableForMonomer(this.polymerBond.secondMonomer)) + !this.isSnakeBondAvailableForMonomer(this.polymerBond.secondMonomer)) || + (this.polymerBond.secondMonomer && + this.polymerBond.firstMonomer.isMonomerTypeDifferentForSnakeMode( + this.polymerBond.secondMonomer, + )) ) { return false; } @@ -57,17 +67,22 @@ export class PolymerBondRenderer extends BaseRenderer { this.polymerBond.firstMonomer.getPotentialAttachmentPointByBond( this.polymerBond, ); - return ( - SnakeMode.isEnabled && - ((this.attachmentPointsForSnakeBond.includes( + const isAttachmentPointsEnabledForSnakeBond = + (this.attachmentPointsForSnakeBond.includes( firstMonomerAttachmentPoint as string, ) && this.attachmentPointsForSnakeBond.includes( secondMonomerAttachmentPoint as string, )) || - this.attachmentPointsForSnakeBond.includes( - firstMonomerPotentialAttachmentPoint as string, - )) + this.attachmentPointsForSnakeBond.includes( + firstMonomerPotentialAttachmentPoint as string, + ); + const isSameAttachmentPoints = + firstMonomerAttachmentPoint === secondMonomerAttachmentPoint; + return ( + SnakeMode.isEnabled && + isAttachmentPointsEnabledForSnakeBond && + !isSameAttachmentPoints ); } @@ -149,7 +164,7 @@ export class PolymerBondRenderer extends BaseRenderer { } private getMonomerHeight() { - return this.polymerBond.firstMonomer.renderer?.monomerSize.width ?? 0; + return this.polymerBond.firstMonomer.renderer?.monomerSize.height ?? 0; } public isMonomersOnSameHorizontalLine() { @@ -176,6 +191,15 @@ export class PolymerBondRenderer extends BaseRenderer { this.polymerBond.firstMonomer.getPotentialAttachmentPointByBond( this.polymerBond, ) === 'R1'; + + // check if there is nucleotide in current row + const isBondConnectedWithNucleotide = + this.polymerBond.firstMonomer.isMonomerInRnaChainRow; + + const verticalLineLength = isBondConnectedWithNucleotide + ? RNA_CHAIN_VERTICAL_LINE_LENGTH + : VERTICAL_LINE_LENGTH; + if (this.isSecondMonomerBottomRight(startPosition, endPosition)) { if ( isR1TheCurrentAttachmentpointOfFirstMonomer && @@ -247,7 +271,7 @@ export class PolymerBondRenderer extends BaseRenderer { startPosition, ); this.addLineFromLeftToBottom(); - this.addLine(LINE_DIRECTION.Vertical, VERTICAL_LINE_LENGTH); + this.addLine(LINE_DIRECTION.Vertical, verticalLineLength); this.addLineFromTopToLeft(); this.addLine( LINE_DIRECTION.Horizontal, @@ -264,7 +288,7 @@ export class PolymerBondRenderer extends BaseRenderer { endPosition.y - startPosition.y - CORNER_LENGTH * 4 - - VERTICAL_LINE_LENGTH, + verticalLineLength, ); this.addLineFromTopToRight(); this.addLine( @@ -364,7 +388,10 @@ export class PolymerBondRenderer extends BaseRenderer { ); } - private isSecondMonomerBottomLeft(startPosition, endPosition): boolean { + private isSecondMonomerBottomLeft( + startPosition: Vec2, + endPosition: Vec2, + ): boolean { return ( endPosition.y - startPosition.y >= 2 * (VERTICAL_LINE_LENGTH + DOUBLE_CORNER_LENGTH) && @@ -381,7 +408,7 @@ export class PolymerBondRenderer extends BaseRenderer { ); } - private isSecondMonomerLeft(startPosition, endPosition): boolean { + private isSecondMonomerLeft(startPosition: Vec2, endPosition: Vec2): boolean { return ( startPosition.y - endPosition.y < 0 && startPosition.y - endPosition.y > diff --git a/packages/ketcher-core/src/application/render/renderers/RenderersManager.ts b/packages/ketcher-core/src/application/render/renderers/RenderersManager.ts index 1d969c9928..7f79281d96 100644 --- a/packages/ketcher-core/src/application/render/renderers/RenderersManager.ts +++ b/packages/ketcher-core/src/application/render/renderers/RenderersManager.ts @@ -15,6 +15,12 @@ import { } from 'application/render'; import { notifyRenderComplete } from 'application/render/internal'; import { Peptide, Sugar, RNABase, Phosphate } from 'domain/entities'; +import { + checkIsR2R1Connection, + getNextMonomerInChain, + getRnaBaseMonomerFromSugar, + isMonomerBeginningOfChain, +} from 'domain/helpers/monomers'; export class RenderersManager { private theme; @@ -124,21 +130,6 @@ export class RenderersManager { } } - private getNextMonomerInChain(monomer?: BaseMonomer) { - if (!monomer) return undefined; - - const r2PolymerBond = monomer.attachmentPointsToBonds.R2; - - return r2PolymerBond?.getAnotherMonomer(monomer); - } - - private isR2R1Connection(monomer, nextMonomer) { - return ( - nextMonomer.attachmentPointsToBonds.R1?.getAnotherMonomer(nextMonomer) === - monomer - ); - } - private recalculatePeptideChainEnumeration( peptideRenderer: PeptideRenderer, currentEnumeration = 1, @@ -146,13 +137,13 @@ export class RenderersManager { peptideRenderer.setEnumeration(currentEnumeration); peptideRenderer.redrawEnumeration(); - const nextMonomer = this.getNextMonomerInChain(peptideRenderer.monomer); + const nextMonomer = getNextMonomerInChain(peptideRenderer.monomer); if (!(nextMonomer instanceof Peptide)) { return; } - const isR2R1Connection = this.isR2R1Connection( + const isR2R1Connection = checkIsR2R1Connection( peptideRenderer.monomer, nextMonomer, ); @@ -175,8 +166,8 @@ export class RenderersManager { ) { let currentEnumeration = _currentEnumeration; if (rnaComponentRenderer instanceof SugarRenderer) { - const rnaBaseMonomer = this.getRnaBaseMonomerFromSugar( - rnaComponentRenderer as SugarRenderer, + const rnaBaseMonomer = getRnaBaseMonomerFromSugar( + rnaComponentRenderer.monomer as Sugar, ); if (rnaBaseMonomer instanceof RNABase) { rnaBaseMonomer.renderer?.setEnumeration(currentEnumeration); @@ -185,9 +176,7 @@ export class RenderersManager { } } - const nextMonomer = this.getNextMonomerInChain( - rnaComponentRenderer.monomer, - ); + const nextMonomer = getNextMonomerInChain(rnaComponentRenderer.monomer); if ( !(nextMonomer instanceof Sugar) && @@ -196,7 +185,7 @@ export class RenderersManager { return; } - const isR2R1Connection = this.isR2R1Connection( + const isR2R1Connection = checkIsR2R1Connection( rnaComponentRenderer.monomer, nextMonomer, ); @@ -213,45 +202,20 @@ export class RenderersManager { ); } - private isMonomerBeginningOfChain( - monomer: BaseMonomer, - MonomerTypes: Array, - ) { - const r1PolymerBond = monomer.attachmentPointsToBonds.R1; - const previousMonomer = r1PolymerBond?.getAnotherMonomer(monomer); - const isPreviousMonomerPartOfChain = - previousMonomer && - !MonomerTypes.some( - (MonomerType) => previousMonomer instanceof MonomerType, - ); - const previousConnectionNotR2 = - r1PolymerBond && - previousMonomer?.getAttachmentPointByBond(r1PolymerBond) !== 'R2'; - - return ( - ((monomer.isAttachmentPointExistAndFree('R1') || - !monomer.hasAttachmentPoint('R1')) && - monomer.hasBonds) || - previousConnectionNotR2 || - isPreviousMonomerPartOfChain - ); - } - private recalculatePeptideEnumeration(peptideRenderer: PeptideRenderer) { if (!peptideRenderer.monomer.hasBonds) { peptideRenderer.setEnumeration(null); peptideRenderer.redrawEnumeration(); } - if (!this.isMonomerBeginningOfChain(peptideRenderer.monomer, [Peptide])) - return; + if (!isMonomerBeginningOfChain(peptideRenderer.monomer, [Peptide])) return; this.recalculatePeptideChainEnumeration(peptideRenderer); } private recalculateRnaEnumeration(rnaComponentRenderer: BaseMonomerRenderer) { if ( - !this.isMonomerBeginningOfChain(rnaComponentRenderer.monomer, [ + !isMonomerBeginningOfChain(rnaComponentRenderer.monomer, [ Phosphate, Sugar, ]) @@ -283,8 +247,8 @@ export class RenderersManager { } private isOnlyPartOfRnaChain(sugar: Sugar) { - const phosphate = this.getNextMonomerInChain(sugar); - const nextMonomerAfterPhospate = this.getNextMonomerInChain(phosphate); + const phosphate = getNextMonomerInChain(sugar); + const nextMonomerAfterPhospate = getNextMonomerInChain(phosphate); return !sugar.attachmentPointsToBonds.R1 && !nextMonomerAfterPhospate; } @@ -299,8 +263,8 @@ export class RenderersManager { monomerRenderer.reDrawChainBeginning(); } if (monomerRenderer instanceof SugarRenderer) { - const rnaBaseMonomer = this.getRnaBaseMonomerFromSugar( - monomerRenderer as SugarRenderer, + const rnaBaseMonomer = getRnaBaseMonomerFromSugar( + monomerRenderer.monomer as Sugar, ); if ( rnaBaseMonomer instanceof RNABase && @@ -317,14 +281,6 @@ export class RenderersManager { this.needRecalculateMonomersBeginning = false; } - private getRnaBaseMonomerFromSugar(sugarRenderer: SugarRenderer) { - const r3PolymerBond = sugarRenderer.monomer.attachmentPointsToBonds.R3; - const rnaBaseMonomer = r3PolymerBond?.getAnotherMonomer( - sugarRenderer.monomer, - ); - return rnaBaseMonomer; - } - public finishPolymerBondCreation(polymerBond: PolymerBond) { assert(polymerBond.secondMonomer); diff --git a/packages/ketcher-core/src/domain/entities/BaseMonomer.ts b/packages/ketcher-core/src/domain/entities/BaseMonomer.ts index cae38ae57e..1191b70ff9 100644 --- a/packages/ketcher-core/src/domain/entities/BaseMonomer.ts +++ b/packages/ketcher-core/src/domain/entities/BaseMonomer.ts @@ -29,6 +29,8 @@ export abstract class BaseMonomer extends DrawingEntity { public attachmentPointsVisible = false; public monomerItem: MonomerItemType; + public isMonomerInRnaChainRow = false; + constructor(monomerItem: MonomerItemType, _position?: Vec2) { super(_position); @@ -442,4 +444,8 @@ export abstract class BaseMonomer extends DrawingEntity { return this.firstFreeAttachmentPoint; } + + public isMonomerTypeDifferentForSnakeMode(_monomerToChain: BaseMonomer) { + return true; + } } diff --git a/packages/ketcher-core/src/domain/entities/Chem.ts b/packages/ketcher-core/src/domain/entities/Chem.ts index 9c7f9efe3a..b702e71d88 100644 --- a/packages/ketcher-core/src/domain/entities/Chem.ts +++ b/packages/ketcher-core/src/domain/entities/Chem.ts @@ -1,5 +1,7 @@ import { BaseMonomer } from 'domain/entities/BaseMonomer'; import { Peptide } from 'domain/entities/Peptide'; +import { Sugar } from './Sugar'; +import { Phosphate } from './Phosphate'; export class Chem extends BaseMonomer { public getValidSourcePoint(monomer?: BaseMonomer) { @@ -9,4 +11,10 @@ export class Chem extends BaseMonomer { public getValidTargetPoint(monomer: BaseMonomer) { return Peptide.prototype.getValidTargetPoint.call(this, monomer); } + + public isMonomerTypeDifferentForSnakeMode(monomerToChain: BaseMonomer) { + return ( + monomerToChain instanceof Sugar || monomerToChain instanceof Phosphate + ); + } } diff --git a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts index 870f8192d2..9f04847fa7 100644 --- a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts +++ b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts @@ -11,6 +11,9 @@ import { Pool, Chem, SGroupForest, + RNABase, + Sugar, + Phosphate, } from 'domain/entities'; import { AttachmentPointHoverOperation, @@ -36,6 +39,11 @@ import { import { monomerFactory } from 'application/editor/operations/monomer/monomerFactory'; import { Coordinates } from 'application/editor'; import { getCurrentCenterPointOfCanvas } from 'application/utils'; +import { + getNextMonomerInChain, + getRnaBaseMonomerFromSugar, +} from 'domain/helpers/monomers'; +import { RNA_MONOMER_DISTANCE } from 'application/editor/tools/RnaPreset'; const HORIZONTAL_DISTANCE_FROM_MONOMER = 50; const VERTICAL_DISTANCE_FROM_MONOMER = 60; @@ -53,6 +61,13 @@ type RnaPresetAdditionParams = { phosphatePosition: Vec2 | undefined; }; +type NucleotideOrNucleoside = { + sugar: Sugar; + phosphate?: Phosphate; + rnaBase: RNABase; + baseMonomer: Sugar | Phosphate; +}; + export class DrawingEntitiesManager { public monomers: Map = new Map(); public polymerBonds: Map = new Map(); @@ -835,48 +850,151 @@ export class DrawingEntitiesManager { return monomer; } - private rearrangeChain( + public getNucleotideSize(nucleotide: NucleotideOrNucleoside) { + const width = + (nucleotide.sugar.renderer?.monomerSize.width || 0) + + (nucleotide.phosphate?.renderer?.monomerSize.width || 0) + + (nucleotide.phosphate ? RNA_MONOMER_DISTANCE : 0); + const height = + (nucleotide.sugar.renderer?.monomerSize.height || 0) + + (nucleotide.rnaBase.renderer?.monomerSize.height || 0) + + RNA_MONOMER_DISTANCE; + return { width, height }; + } + + private reArrangeChain( monomer: BaseMonomer, - initialPosition: Vec2, + lastPosition: Vec2, canvasWidth: number, - rearrangedMonomersSet: Set = new Set(), - isNextChain = false, + rearrangedMonomersSet: Set, + monomersWithSideChain: Array, + maxVerticalDistance: number, ) { const command = new Command(); const monomerWidth = monomer.renderer?.monomerSize.width ?? 0; const monomerHeight = monomer.renderer?.monomerSize.height ?? 0; const heightMonomerWithBond = monomerHeight + VERTICAL_DISTANCE_FROM_MONOMER; + maxVerticalDistance = Math.max(maxVerticalDistance, heightMonomerWithBond); + monomer.isMonomerInRnaChainRow = + maxVerticalDistance > heightMonomerWithBond; const oldMonomerPosition = monomer.position; - const newPosition = isNextChain - ? new Vec2( - MONOMER_START_X_POSITION, - initialPosition.y + heightMonomerWithBond, - ) - : initialPosition; - const operation = new MonomerMoveOperation( this.rearrangeChainModelChange.bind( this, monomer, - Coordinates.canvasToModel(newPosition), + Coordinates.canvasToModel(lastPosition), ), this.rearrangeChainModelChange.bind(this, monomer, oldMonomerPosition), ); command.addOperation(operation); - let lastPosition = newPosition; - rearrangedMonomersSet.add(monomer.id); + + return this.reArrangeNextMonomer( + monomer, + monomerWidth, + lastPosition, + canvasWidth, + rearrangedMonomersSet, + monomersWithSideChain, + maxVerticalDistance, + command, + ); + } + + private reArrangeRnaChain( + nucleotide: NucleotideOrNucleoside, + lastPosition: Vec2, + canvasWidth: number, + rearrangedMonomersSet: Set, + monomersWithSideChain: Array, + maxVerticalDistance: number, + ) { + const command = new Command(); + const nucleotideSize = this.getNucleotideSize(nucleotide); + const { height, width } = nucleotideSize; + const heightWithBond = height + VERTICAL_DISTANCE_FROM_MONOMER; + maxVerticalDistance = Math.max(maxVerticalDistance, heightWithBond); + nucleotide.sugar.isMonomerInRnaChainRow = true; + nucleotide.rnaBase.isMonomerInRnaChainRow = true; + const oldSugarPosition = nucleotide.sugar.position; + const rnaBasePosition = new Vec2( + lastPosition.x, + lastPosition.y + + (nucleotide.sugar.renderer?.monomerSize?.height ?? 0) / 2 + + (nucleotide.rnaBase.renderer?.monomerSize?.height ?? 0) / 2 + + RNA_MONOMER_DISTANCE, + ); + this.addRnaOperations( + command, + oldSugarPosition, + lastPosition, + nucleotide.sugar, + ); + this.addRnaOperations( + command, + nucleotide.rnaBase?.position, + rnaBasePosition, + nucleotide.rnaBase, + ); + rearrangedMonomersSet.add(nucleotide.sugar.id); + rearrangedMonomersSet.add(nucleotide.rnaBase?.id); + + if (nucleotide.phosphate) { + nucleotide.phosphate.isMonomerInRnaChainRow = true; + const phosphatePosition = new Vec2( + lastPosition.x + + (nucleotide.sugar.renderer?.monomerSize?.width ?? 0) / 2 + + (nucleotide.phosphate?.renderer?.monomerSize?.width ?? 0) / 2 + + RNA_MONOMER_DISTANCE, + lastPosition.y, + ); + this.addRnaOperations( + command, + nucleotide.phosphate?.position, + phosphatePosition, + nucleotide.phosphate, + ); + rearrangedMonomersSet.add(nucleotide.phosphate?.id); + } + const nextMonomer = + nucleotide.baseMonomer === nucleotide.sugar && nucleotide.phosphate + ? nucleotide.phosphate + : nucleotide.sugar; + const nextMonomerResult = this.reArrangeNextMonomer( + nextMonomer, + width, + lastPosition, + canvasWidth, + rearrangedMonomersSet, + monomersWithSideChain, + maxVerticalDistance, + command, + ); + ({ lastPosition, maxVerticalDistance } = nextMonomerResult); + + this.setRnaBaseSideChainMonomers( + nucleotide.rnaBase, + rearrangedMonomersSet, + monomersWithSideChain, + ); + return { command, lastPosition, maxVerticalDistance }; + } + + private reArrangeNextMonomer( + monomer: BaseMonomer, + width: number, + lastPosition: Vec2, + canvasWidth: number, + rearrangedMonomersSet: Set, + monomersWithSideChain: Array, + maxVerticalDistance: number, + command: Command, + ) { for (const attachmentPointName in monomer.attachmentPointsToBonds) { const polymerBond = monomer.attachmentPointsToBonds[attachmentPointName]; - if (!polymerBond) { - continue; - } - const nextMonomer = - polymerBond.secondMonomer === monomer - ? polymerBond.firstMonomer - : polymerBond.secondMonomer; - if (rearrangedMonomersSet.has(nextMonomer.id)) { + const nextMonomer = polymerBond?.getAnotherMonomer(monomer); + if (!polymerBond || rearrangedMonomersSet.has(nextMonomer.id)) { continue; } if ( @@ -885,61 +1003,92 @@ export class DrawingEntitiesManager { (attachmentPointName === 'R1' && nextMonomer.getAttachmentPointByBond(polymerBond) === 'R2') ) { - const isMonomerFitCanvas = - newPosition.x + - monomerWidth + - DISTANCE_BETWEEN_MONOMERS + - HORIZONTAL_DISTANCE_FROM_MONOMER + - DISTANCE_FROM_RIGHT < - canvasWidth; - let rearrangeResult; - if (isMonomerFitCanvas) { - rearrangeResult = this.rearrangeChain( - nextMonomer, - new Vec2({ - x: - newPosition.x + monomerWidth + HORIZONTAL_DISTANCE_FROM_MONOMER, - y: newPosition.y, - }), - canvasWidth, - rearrangedMonomersSet, - ); - } else { - rearrangeResult = this.rearrangeChain( - nextMonomer, - new Vec2({ - x: MONOMER_START_X_POSITION, - y: newPosition.y + heightMonomerWithBond, - }), + ({ lastPosition, maxVerticalDistance } = + this.getNextPositionAndDistance( + lastPosition, + width, + maxVerticalDistance, canvasWidth, - rearrangedMonomersSet, - ); - } - lastPosition = rearrangeResult.lastPosition; - command.merge(rearrangeResult.command); - } else { - const diff = Vec2.diff(oldMonomerPosition, monomer.position); - const pos = Vec2.diff(nextMonomer.position, diff); - const rearrangeResult = this.rearrangeChain( + )); + const rearrangeResult = this.reArrangeChainInRecursive( nextMonomer, - Coordinates.modelToCanvas(pos), + lastPosition, canvasWidth, rearrangedMonomersSet, + monomersWithSideChain, + maxVerticalDistance, ); + ({ lastPosition, maxVerticalDistance } = rearrangeResult); command.merge(rearrangeResult.command); + } else { + monomersWithSideChain.push(nextMonomer); } } + return { command, lastPosition, maxVerticalDistance }; + } - return { command, lastPosition }; + private setRnaBaseSideChainMonomers( + rnaBase: RNABase, + rearrangedMonomersSet: Set, + monomersWithSideChain: Array, + ) { + for (const attachmentPointName of Object.keys( + rnaBase.attachmentPointsToBonds, + ).reverse()) { + const polymerBond = rnaBase.attachmentPointsToBonds[attachmentPointName]; + const nextMonomer = polymerBond?.getAnotherMonomer(rnaBase); + if ( + !polymerBond || + !nextMonomer || + rearrangedMonomersSet.has(nextMonomer.id) + ) { + continue; + } + monomersWithSideChain.push(nextMonomer); + } + } + + private addRnaOperations( + command: Command, + oldMonomerPosition: Vec2 | undefined, + newPosition: Vec2 | undefined, + monomer?: BaseMonomer, + ) { + if (!monomer || !oldMonomerPosition || !newPosition) { + return; + } + const operation = new MonomerMoveOperation( + this.rearrangeChainModelChange.bind( + this, + monomer, + Coordinates.canvasToModel(newPosition), + ), + this.rearrangeChainModelChange.bind(this, monomer, oldMonomerPosition), + ); + command.addOperation(operation); } public reArrangeChains(canvasWidth: number, isSnakeMode: boolean) { const command = new Command(); if (isSnakeMode) { - command.merge(this.reArrangeMonomers(canvasWidth)); + const rearrangedMonomersSet: Set = new Set(); + const lastPosition = new Vec2({ + x: MONOMER_START_X_POSITION, + y: MONOMER_START_Y_POSITION, + }); + const firstMonomers = this.getFirstMonomersInChains( + [Peptide, Chem, Sugar, Phosphate], + [...this.monomers.values()], + ); + const result = this.reArrangeMonomers( + canvasWidth, + firstMonomers, + rearrangedMonomersSet, + lastPosition, + ); + command.merge(result.command); } command.merge(this.redrawBonds()); - return command; } @@ -963,9 +1112,14 @@ export class DrawingEntitiesManager { return command; } - public reArrangeMonomers(canvasWidth: number) { - const monomersList = Array.from(this.monomers.values()).filter( - (monomer) => monomer instanceof Peptide || monomer instanceof Chem, + public getFirstMonomersInChains( + MonomerTypes: Array< + typeof Peptide | typeof Chem | typeof Phosphate | typeof Sugar + >, + currentMonomers: BaseMonomer[], + ) { + const monomersList = currentMonomers.filter((monomer) => + MonomerTypes.some((MonomerType) => monomer instanceof MonomerType), ); const firstMonomersInChains = monomersList.filter((monomer) => { @@ -974,29 +1128,20 @@ export class DrawingEntitiesManager { polymerBond?.firstMonomer === monomer ? polymerBond.secondMonomer : polymerBond?.firstMonomer; - return ( + const isFirstMonomerWithR2R1connection = !monomer.attachmentPointsToBonds.R1 && monomer.attachmentPointsToBonds.R2 && nextMonomer?.getAttachmentPointByBond( monomer.attachmentPointsToBonds.R2, - ) === 'R1' - ); - }); - - firstMonomersInChains.sort((monomer1, monomer2) => { - if ( - monomer2.position.x + monomer2.position.y < - monomer1.position.x + monomer1.position.y - ) { - return -1; - } else { - return 1; - } + ) === 'R1'; + const isSingleMonomerOrNucleoside = + !monomer.attachmentPointsToBonds.R1 && + !monomer.attachmentPointsToBonds.R2; + return isFirstMonomerWithR2R1connection || isSingleMonomerOrNucleoside; }); const filteredFirstMonomersInChains: BaseMonomer[] = []; - - firstMonomersInChains.forEach((monomer, monomerIndex) => { + firstMonomersInChains.reverse().forEach((monomer, monomerIndex) => { const currentMonomerChain: BaseMonomer[] = this.findChainByMonomer(monomer); let isFirstMonomerInChain = true; @@ -1014,25 +1159,191 @@ export class DrawingEntitiesManager { filteredFirstMonomersInChains.push(monomer); } }); + return filteredFirstMonomersInChains.sort( + (monomer1, monomer2) => + monomer1.position.x + + monomer1.position.y - + (monomer2.position.x + monomer2.position.y), + ); + } + + public reArrangeChainInRecursive( + monomer: BaseMonomer, + lastPosition: Vec2, + canvasWidth: number, + rearrangedMonomersSet: Set, + monomersWithSideChain: Array, + maxVerticalDistance: number, + ) { const command = new Command(); - let lastPosition = new Vec2({ - x: MONOMER_START_X_POSITION, - y: MONOMER_START_Y_POSITION, - }); + if (monomer instanceof Sugar || monomer instanceof Phosphate) { + const nucleotideOrNucleoside = + getNucleotideOrNucleoSideFromFirstMonomer(monomer); + if (nucleotideOrNucleoside) { + const rearrangeRnaResult = this.reArrangeRnaChain( + nucleotideOrNucleoside, + lastPosition, + canvasWidth, + rearrangedMonomersSet, + monomersWithSideChain, + maxVerticalDistance, + ); + command.merge(rearrangeRnaResult.command); + return { + command, + lastPosition: rearrangeRnaResult.lastPosition, + maxVerticalDistance: rearrangeRnaResult.maxVerticalDistance, + }; + } + } + const rearrangeResult = this.reArrangeChain( + monomer, + lastPosition, + canvasWidth, + rearrangedMonomersSet, + monomersWithSideChain, + maxVerticalDistance, + ); + command.merge(rearrangeResult.command); + return { + command, + lastPosition: rearrangeResult.lastPosition, + maxVerticalDistance: rearrangeResult.maxVerticalDistance, + }; + } + + public getNextPositionAndDistance( + lastPosition: Vec2, + width: number, + height: number, + canvasWidth: number, + ) { + const isMonomerFitCanvas = + lastPosition.x + + width + + DISTANCE_BETWEEN_MONOMERS + + HORIZONTAL_DISTANCE_FROM_MONOMER + + DISTANCE_FROM_RIGHT < + canvasWidth; + + if (!isMonomerFitCanvas) { + return { + maxVerticalDistance: 0, + lastPosition: getFirstPosition(height, lastPosition), + }; + } - filteredFirstMonomersInChains.reverse().forEach((monomer, monomerIndex) => { - const rearrangeResult = this.rearrangeChain( + return { + maxVerticalDistance: height, + lastPosition: new Vec2({ + x: lastPosition.x + width + HORIZONTAL_DISTANCE_FROM_MONOMER, + y: lastPosition.y, + }), + }; + } + + private isPartOfR2R1Chain(monomer: BaseMonomer) { + const R1Bond = monomer.attachmentPointsToBonds.R1; + const R2Bond = monomer.attachmentPointsToBonds.R2; + return ( + R1Bond?.getAnotherMonomer(monomer)?.getAttachmentPointByBond(R1Bond) === + 'R2' || + R2Bond?.getAnotherMonomer(monomer)?.getAttachmentPointByBond(R2Bond) === + 'R1' + ); + } + + private getFirstMonomerInR2R1Chain(monomer: BaseMonomer) { + const R1Bond = monomer.attachmentPointsToBonds.R1; + return R1Bond && + R1Bond.getAnotherMonomer(monomer)?.getAttachmentPointByBond(R1Bond) === + 'R2' + ? this.getFirstMonomerInR2R1Chain( + R1Bond.getAnotherMonomer(monomer) as BaseMonomer, + ) + : monomer; + } + + public reArrangeMonomers( + canvasWidth: number, + firstMonomers: BaseMonomer[], + rearrangedMonomersSet: Set, + lastPosition: Vec2, + ) { + const command = new Command(); + firstMonomers.forEach((monomer) => { + const monomersWithSideChain: Array = []; + const rearrangeResult = this.reArrangeChainInRecursive( monomer, lastPosition, canvasWidth, - undefined, - monomerIndex !== 0, + rearrangedMonomersSet, + monomersWithSideChain, + 0, ); command.merge(rearrangeResult.command); lastPosition = rearrangeResult.lastPosition; - }); + // need to calculate the vertical distance before the next row gets to display + // because the height of Rna and peptide are different + lastPosition = getFirstPosition( + rearrangeResult.maxVerticalDistance, + lastPosition, + ); + if (monomersWithSideChain.length > 0) { + monomersWithSideChain.reverse().forEach((monomerWithSideChain) => { + const currentMonomerChain: BaseMonomer[] = this.findChainByMonomer( + monomerWithSideChain, + undefined, + new Set([...rearrangedMonomersSet]), + ); + let firstMonomers = this.getFirstMonomersInChains( + [Peptide, Chem, Sugar, Phosphate], + currentMonomerChain, + ); + if (rearrangedMonomersSet.has(monomerWithSideChain.id)) { + return; + } + if (!firstMonomers.length) { + firstMonomers = [monomerWithSideChain]; + } else if (this.isPartOfR2R1Chain(monomerWithSideChain)) { + const firstMonomerInR2R1Chain = + this.getFirstMonomerInR2R1Chain(monomerWithSideChain); + firstMonomers = [firstMonomerInR2R1Chain]; + } else { + const oldMonomerPosition = monomerWithSideChain.position; + const operation = new MonomerMoveOperation( + this.rearrangeChainModelChange.bind( + this, + monomerWithSideChain, + Coordinates.canvasToModel(lastPosition), + ), + this.rearrangeChainModelChange.bind( + this, + monomerWithSideChain, + oldMonomerPosition, + ), + ); + rearrangedMonomersSet.add(monomerWithSideChain.id); + command.addOperation(operation); + const height = + (monomerWithSideChain.renderer?.monomerSize.height ?? 0) + + VERTICAL_DISTANCE_FROM_MONOMER; + lastPosition = getFirstPosition(height, lastPosition); + } - return command; + const rearrangeResult = this.reArrangeMonomers( + canvasWidth, + firstMonomers, + rearrangedMonomersSet, + lastPosition, + ); + command.merge(rearrangeResult.command); + + lastPosition = rearrangeResult.lastPosition; + }); + } + }); + return { command, lastPosition }; } public setMicromoleculesHiddenEntities(struct: Struct) { @@ -1124,3 +1435,54 @@ export class DrawingEntitiesManager { return new Vec2((xmin + xmax) / 2, (ymin + ymax) / 2); } } +function getFirstPosition(height: number, lastPosition: Vec2) { + return new Vec2(MONOMER_START_X_POSITION, lastPosition.y + height); +} + +function getNucleotideOrNucleoSideFromFirstMonomer( + monomer: BaseMonomer, +): NucleotideOrNucleoside | undefined { + if (monomer instanceof Sugar) { + const nextMonomer = getNextMonomerInChain(monomer); + const rnaBaseMonomer = getRnaBaseMonomerFromSugar(monomer); + const isNucleoside = + !(nextMonomer instanceof Phosphate) && rnaBaseMonomer instanceof RNABase; + const isNucleotide = + nextMonomer && + nextMonomer instanceof Phosphate && + rnaBaseMonomer instanceof RNABase; + if (isNucleotide) { + return { + sugar: monomer, + phosphate: nextMonomer, + rnaBase: rnaBaseMonomer, + baseMonomer: monomer, + }; + } + if (isNucleoside) { + return { + sugar: monomer, + rnaBase: rnaBaseMonomer, + baseMonomer: monomer, + }; + } + } + if (monomer instanceof Phosphate) { + const r1PolymerBond = monomer.attachmentPointsToBonds.R1; + const nextMonomer = r1PolymerBond?.getAnotherMonomer(monomer); + const rnaBaseMonomer = getRnaBaseMonomerFromSugar(nextMonomer); + const isNucleotide = + nextMonomer && + nextMonomer instanceof Sugar && + rnaBaseMonomer instanceof RNABase; + if (isNucleotide) { + return { + sugar: nextMonomer, + phosphate: monomer, + rnaBase: rnaBaseMonomer, + baseMonomer: monomer, + }; + } + } + return undefined; +} diff --git a/packages/ketcher-core/src/domain/entities/Peptide.ts b/packages/ketcher-core/src/domain/entities/Peptide.ts index b62f34adf0..af589d5607 100644 --- a/packages/ketcher-core/src/domain/entities/Peptide.ts +++ b/packages/ketcher-core/src/domain/entities/Peptide.ts @@ -1,4 +1,6 @@ import { BaseMonomer } from './BaseMonomer'; +import { Phosphate } from './Phosphate'; +import { Sugar } from './Sugar'; export class Peptide extends BaseMonomer { public getValidSourcePoint(secondMonomer?: BaseMonomer) { @@ -76,4 +78,10 @@ export class Peptide extends BaseMonomer { return undefined; } + + public isMonomerTypeDifferentForSnakeMode(monomerToChain: BaseMonomer) { + return ( + monomerToChain instanceof Sugar || monomerToChain instanceof Phosphate + ); + } } diff --git a/packages/ketcher-core/src/domain/entities/Phosphate.ts b/packages/ketcher-core/src/domain/entities/Phosphate.ts index 2e9ca827f4..f7f922294c 100644 --- a/packages/ketcher-core/src/domain/entities/Phosphate.ts +++ b/packages/ketcher-core/src/domain/entities/Phosphate.ts @@ -2,6 +2,8 @@ import { BaseMonomer } from './BaseMonomer'; import { MonomerItemType } from 'domain/types'; import { Vec2 } from './vec2'; import { Sugar } from './Sugar'; +import { Peptide } from './Peptide'; +import { Chem } from './Chem'; export class Phosphate extends BaseMonomer { constructor(monomerItem: MonomerItemType, _position?: Vec2) { @@ -79,4 +81,8 @@ export class Phosphate extends BaseMonomer { return undefined; } + + public isMonomerTypeDifferentForSnakeMode(monomerToChain: BaseMonomer) { + return monomerToChain instanceof Peptide || monomerToChain instanceof Chem; + } } diff --git a/packages/ketcher-core/src/domain/entities/Sugar.ts b/packages/ketcher-core/src/domain/entities/Sugar.ts index c497b70c20..6922f1b039 100644 --- a/packages/ketcher-core/src/domain/entities/Sugar.ts +++ b/packages/ketcher-core/src/domain/entities/Sugar.ts @@ -1,6 +1,8 @@ import { BaseMonomer } from './BaseMonomer'; import { RNABase } from './RNABase'; import { Phosphate } from './Phosphate'; +import { Peptide } from './Peptide'; +import { Chem } from './Chem'; export class Sugar extends BaseMonomer { public getValidSourcePoint(secondMonomer: BaseMonomer) { @@ -90,4 +92,8 @@ export class Sugar extends BaseMonomer { return undefined; } + + public isMonomerTypeDifferentForSnakeMode(monomerToChain: BaseMonomer) { + return monomerToChain instanceof Peptide || monomerToChain instanceof Chem; + } } diff --git a/packages/ketcher-core/src/domain/helpers/monomers.ts b/packages/ketcher-core/src/domain/helpers/monomers.ts index 60b3a5571b..f26aea3a02 100644 --- a/packages/ketcher-core/src/domain/helpers/monomers.ts +++ b/packages/ketcher-core/src/domain/helpers/monomers.ts @@ -1,5 +1,53 @@ +import { BaseMonomer, Peptide, Phosphate, Sugar } from 'domain/entities'; import { MonomerItemType } from 'domain/types'; export function getMonomerUniqueKey(monomer: MonomerItemType) { return `${monomer.props.MonomerName}___${monomer.props.Name}`; } + +export function checkIsR2R1Connection( + monomer: BaseMonomer, + nextMonomer: BaseMonomer, +) { + return ( + nextMonomer.attachmentPointsToBonds.R1?.getAnotherMonomer(nextMonomer) === + monomer + ); +} + +export function getNextMonomerInChain(monomer?: BaseMonomer) { + if (!monomer) return undefined; + + const r2PolymerBond = monomer.attachmentPointsToBonds.R2; + + return r2PolymerBond?.getAnotherMonomer(monomer); +} + +export function getRnaBaseMonomerFromSugar(monomer?: BaseMonomer) { + if (!monomer) return undefined; + const r3PolymerBond = monomer.attachmentPointsToBonds.R3; + const rnaBaseMonomer = r3PolymerBond?.getAnotherMonomer(monomer); + return rnaBaseMonomer; +} + +export function isMonomerBeginningOfChain( + monomer: BaseMonomer, + MonomerTypes: Array, +) { + const r1PolymerBond = monomer.attachmentPointsToBonds.R1; + const previousMonomer = r1PolymerBond?.getAnotherMonomer(monomer); + const isPreviousMonomerPartOfChain = + previousMonomer && + !MonomerTypes.some((MonomerType) => previousMonomer instanceof MonomerType); + const previousConnectionNotR2 = + r1PolymerBond && + previousMonomer?.getAttachmentPointByBond(r1PolymerBond) !== 'R2'; + + return ( + ((monomer.isAttachmentPointExistAndFree('R1') || + !monomer.hasAttachmentPoint('R1')) && + monomer.hasBonds) || + previousConnectionNotR2 || + isPreviousMonomerPartOfChain + ); +}