diff --git a/ketcher-autotests/tests/Structure-Creating-&-Editing/SMARTS-attributes/Atom-properties-attributes/atom-properties-attributes.spec.ts b/ketcher-autotests/tests/Structure-Creating-&-Editing/SMARTS-attributes/Atom-properties-attributes/atom-properties-attributes.spec.ts index 681af557e5..3e3ec3a41d 100644 --- a/ketcher-autotests/tests/Structure-Creating-&-Editing/SMARTS-attributes/Atom-properties-attributes/atom-properties-attributes.spec.ts +++ b/ketcher-autotests/tests/Structure-Creating-&-Editing/SMARTS-attributes/Atom-properties-attributes/atom-properties-attributes.spec.ts @@ -140,11 +140,9 @@ test.describe('Checking atom properties attributes in SMARTS format', () => { }); test('Check that cannot add Charge more than -15', async ({ page }) => { - test.fail(); /** * Test case: https://github.com/epam/ketcher/issues/3943 * Description: Validation should be added +-15 range allowed only - * This test will fail until https://github.com/epam/ketcher/issues/3943 is fixed */ await setCharge(page, '-16'); const applyButton = await page.getByText('Apply'); @@ -153,11 +151,9 @@ test.describe('Checking atom properties attributes in SMARTS format', () => { }); test('Check that cannot add Charge more than 15', async ({ page }) => { - test.fail(); /** * Test case: https://github.com/epam/ketcher/issues/3943 * Description: Validation should be added +-15 range allowed only - * This test will fail until https://github.com/epam/ketcher/issues/3943 is fixed */ await setCharge(page, '16'); const applyButton = await page.getByText('Apply'); diff --git a/packages/ketcher-react/src/script/ui/data/convert/structconv.js b/packages/ketcher-react/src/script/ui/data/convert/structconv.js index 88b5fec0d3..649e68eb1e 100644 --- a/packages/ketcher-react/src/script/ui/data/convert/structconv.js +++ b/packages/ketcher-react/src/script/ui/data/convert/structconv.js @@ -21,14 +21,13 @@ import { StereoLabel, getAtomType, } from 'ketcher-core'; - -import { atom as atomSchema } from '../schema/struct-schema'; import { capitalize } from 'lodash/fp'; import { sdataSchema, getSdataDefault, sdataCustomSchema, } from '../schema/sdata-schema'; +import { matchCharge } from '../utils'; const DefaultStereoGroupNumber = 1; @@ -183,13 +182,14 @@ export function toAtom(atom) { exactChangeFlag: 0, }); } - const chargeRegexp = new RegExp(atomSchema.properties.charge.pattern); - const pch = chargeRegexp.exec(restAtom.charge); + const pch = matchCharge(restAtom.charge); const charge = pch ? parseInt(pch[1] + pch[3] + pch[2]) : restAtom.charge; const conv = Object.assign({}, restAtom, { isotope: restAtom.isotope ? Number(restAtom.isotope) : null, - charge: restAtom.charge ? Number(charge) : null, + // empty charge value by default treated as zero, + // no need to pass and display zero values(0, -0) explicitly + charge: restAtom.charge && charge !== 0 ? Number(charge) : null, alias: restAtom.alias || null, exactChangeFlag: +(restAtom.exactChangeFlag ?? false), unsaturatedAtom: +(restAtom.unsaturatedAtom ?? false), diff --git a/packages/ketcher-react/src/script/ui/data/schema/struct-schema.js b/packages/ketcher-react/src/script/ui/data/schema/struct-schema.js index f9c1e4857d..cf4775e90d 100644 --- a/packages/ketcher-react/src/script/ui/data/schema/struct-schema.js +++ b/packages/ketcher-react/src/script/ui/data/schema/struct-schema.js @@ -58,7 +58,7 @@ export const atom = { charge: { title: 'Charge', type: 'string', - pattern: '^([+-]?)([0-9]{1,3})([+-]?)$', + pattern: '^([+-]?)(1[0-5]|0|[0-9])([+-]?)$', maxLength: 4, default: '', invalidMessage: 'Invalid charge value', diff --git a/packages/ketcher-react/src/script/ui/data/utils.test.ts b/packages/ketcher-react/src/script/ui/data/utils.test.ts new file mode 100644 index 0000000000..8f3e10224e --- /dev/null +++ b/packages/ketcher-react/src/script/ui/data/utils.test.ts @@ -0,0 +1,50 @@ +import { matchCharge } from 'src/script/ui/data/utils'; + +describe('data utils', () => { + describe('matchCharge', () => { + it('should return null for empty string', () => { + const result = matchCharge(''); + expect(result).toBeNull(); + }); + it('should return null for string with positive out of range charge', () => { + const result = matchCharge('16'); + expect(result).toBeNull(); + }); + it('should return null for string with negative out of range charge', () => { + const result = matchCharge('-16'); + expect(result).toBeNull(); + }); + it('should return expected matched array for string with zero charge', () => { + const expectedMatchGroups = ['', '0', '']; + const [, signBefore, absoluteValue, signAfter] = matchCharge('0') || []; + expect([signBefore, absoluteValue, signAfter]).toEqual( + expectedMatchGroups, + ); + }); + it('should return expected matched array for string with negative zero charge', () => { + const expectedMatchGroups = ['-', '0', '']; + const [, signBefore, absoluteValue, signAfter] = matchCharge('-0') || []; + expect([signBefore, absoluteValue, signAfter]).toEqual( + expectedMatchGroups, + ); + }); + it('should return expected matched array for string with negative zero charge after', () => { + const expectedMatchGroups = ['', '0', '-']; + const [, signBefore, absoluteValue, signAfter] = matchCharge('0-') || []; + expect([signBefore, absoluteValue, signAfter]).toEqual( + expectedMatchGroups, + ); + }); + it('should return expected matched array for string with two signs number', () => { + const expectedMatchGroups = ['-', '0', '-']; + const [, signBefore, absoluteValue, signAfter] = matchCharge('-0-') || []; + expect([signBefore, absoluteValue, signAfter]).toEqual( + expectedMatchGroups, + ); + }); + it('should return null for string with invalid number format', () => { + const result = matchCharge('--0'); + expect(result).toBeNull(); + }); + }); +}); diff --git a/packages/ketcher-react/src/script/ui/data/utils.ts b/packages/ketcher-react/src/script/ui/data/utils.ts new file mode 100644 index 0000000000..d44ec5b40a --- /dev/null +++ b/packages/ketcher-react/src/script/ui/data/utils.ts @@ -0,0 +1,16 @@ +import { atom } from './schema/struct-schema'; + +/** + * Get match groups from string representation of charge. It returns RegExpExecArray for charges with values +-[0..15] + * overwise returns null + * + * @example + * matchCharge("16") === null + * matchCharge("0") === [] + * matchCharge("-1") === ["-1", "-", "1", ""] + * matchCharge("15+") === ["15+", "", "15", "+"] + */ +export function matchCharge(charge: string) { + const regex = new RegExp(atom.properties.charge.pattern); + return regex.exec(charge); +} diff --git a/packages/ketcher-react/src/script/ui/views/modal/components/toolbox/Atom/helper.ts b/packages/ketcher-react/src/script/ui/views/modal/components/toolbox/Atom/helper.ts index 1180dcf189..8bcf58789f 100644 --- a/packages/ketcher-react/src/script/ui/views/modal/components/toolbox/Atom/helper.ts +++ b/packages/ketcher-react/src/script/ui/views/modal/components/toolbox/Atom/helper.ts @@ -1,6 +1,7 @@ import { AtomType, Elements, genericsList } from 'ketcher-core'; import { capitalize } from 'lodash'; import { atom as atomSchema } from '../../../../../data/schema/struct-schema'; +import { matchCharge } from 'src/script/ui/data/utils'; export function atomValid( label: string, @@ -47,8 +48,7 @@ export function chargeValid( isMultipleAtoms: boolean, isCustomQuery: boolean, ) { - const regex = new RegExp(atomSchema.properties.charge.pattern); - const result = regex.exec(charge); + const result = matchCharge(charge); const isValidCharge = result && (result[1] === '' || result[3] === ''); if (isCustomQuery || charge === '') { return true;