From cc0f8afb06c6375e74eb8f12cbf6b432a22d9c3d Mon Sep 17 00:00:00 2001 From: Razvan Tomegea Date: Tue, 19 Nov 2024 17:15:14 +0200 Subject: [PATCH 1/2] Updated transaction data decode functions (#1307) * Updated transaction data decode functions * Fix * Fix * Refactor --- CHANGELOG.md | 2 + .../decodeForDisplay/decodeForDisplay.ts | 20 +- .../helpers/decodeByMethod.ts | 45 ++-- .../getDisplayValueAndValidationWarnings.ts | 17 +- .../helpers/getHexValidationWarnings.ts | 9 +- .../helpers/getSmartDecodedParts.ts | 9 +- .../tests/decodeByMethod.spec.ts | 122 +++++++++++ .../tests/decodeForDisplay.spec.ts | 149 +++++++++++++ ...tDisplayValueAndValidationWarnings.spec.ts | 199 ++++++++++++++++++ .../tests/getHexValidationWarnings.spec.ts | 59 ++++++ .../tests/getSmartDecodedParts.spec.ts | 93 ++++++++ 11 files changed, 677 insertions(+), 47 deletions(-) create mode 100644 src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/decodeByMethod.spec.ts create mode 100644 src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/decodeForDisplay.spec.ts create mode 100644 src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/getDisplayValueAndValidationWarnings.spec.ts create mode 100644 src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/getHexValidationWarnings.spec.ts create mode 100644 src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/getSmartDecodedParts.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e841bbca7..1544cadb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [Updated transaction data decode functions](https://github.com/multiversx/mx-sdk-dapp/pull/1307) + ## [[v3.0.10](https://github.com/multiversx/mx-sdk-dapp/pull/1305)] - 2024-11-11 - [Added ability to show transaction toast on demand](https://github.com/multiversx/mx-sdk-dapp/pull/1304) diff --git a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/decodeForDisplay.ts b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/decodeForDisplay.ts index 4015fcbea..b2d2d1315 100644 --- a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/decodeForDisplay.ts +++ b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/decodeForDisplay.ts @@ -20,6 +20,12 @@ export const decodeForDisplay = ({ validationWarnings: [] }; + if (!input.includes('@') && !input.includes('\n')) { + display.displayValue = decodeByMethod(input, decodeMethod); + + return display; + } + if (input.includes('@')) { const parts = input.split('@'); const decodedParts = getDisplayValueAndValidationWarnings({ @@ -28,20 +34,20 @@ export const decodeForDisplay = ({ decodeMethod, display }); - display.displayValue = decodedParts.join('@'); - return display; + display.displayValue = decodedParts.join('@'); } if (input.includes('\n')) { const parts = input.split('\n'); const initialDecodedParts = parts.map((part) => { - const base64Buffer = Buffer.from(String(part), 'base64'); + const base64Buffer = Buffer.from(part, 'base64'); + if (decodeMethod === DecodeMethodEnum.raw) { return part; - } else { - return decodeByMethod(base64Buffer.toString('hex'), decodeMethod); } + + return decodeByMethod(base64Buffer.toString('hex'), decodeMethod); }); const decodedParts = @@ -54,11 +60,7 @@ export const decodeForDisplay = ({ : initialDecodedParts; display.displayValue = decodedParts.join('\n'); - - return display; } - display.displayValue = decodeByMethod(input, decodeMethod); - return display; }; diff --git a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/decodeByMethod.ts b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/decodeByMethod.ts index 7befdf982..a8d0b71ab 100644 --- a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/decodeByMethod.ts +++ b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/decodeByMethod.ts @@ -5,32 +5,32 @@ import { TransactionTokensType } from 'types/serverTransactions.types'; import { addressIsValid } from 'utils/account/addressIsValid'; -import { isUtf8, stringContainsNumbers } from 'utils/decoders'; +import { isUtf8 } from 'utils/decoders'; export const decodeByMethod = ( part: string, decodeMethod: DecodeMethodEnum | string, transactionTokens?: TransactionTokensType ) => { - try { - switch (decodeMethod) { - case DecodeMethodEnum.text: - if (!stringContainsNumbers(part)) { - return part; - } - + switch (decodeMethod) { + case DecodeMethodEnum.text: + try { return Buffer.from(part, 'hex').toString('utf8'); - case DecodeMethodEnum.decimal: - const bn = new BigNumber(part, 16); + } catch {} - return bn.toString(10); - case DecodeMethodEnum.smart: + return part; + case DecodeMethodEnum.decimal: + return part !== '' ? new BigNumber(part, 16).toString(10) : ''; + case DecodeMethodEnum.smart: + try { const bech32Encoded = Address.fromHex(part).toString(); if (addressIsValid(bech32Encoded)) { return bech32Encoded; } + } catch {} + try { const decoded = Buffer.from(part, 'hex').toString('utf8'); if (!isUtf8(decoded)) { @@ -47,20 +47,15 @@ export const decodeByMethod = ( const bn = new BigNumber(part, 16); - return bn.toString(10); + return bn.isFinite() ? bn.toString(10) : part; + } else { + return decoded; } + } catch {} - return decoded; - case DecodeMethodEnum.raw: - default: - return part; - } - } catch (err) { - console.error( - `Error during data decoding of "${part}" as "${decodeMethod}"`, - err - ); - - return part; + return part; + case DecodeMethodEnum.raw: + default: + return part; } }; diff --git a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/getDisplayValueAndValidationWarnings.ts b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/getDisplayValueAndValidationWarnings.ts index 57da1d03a..b4f031e78 100644 --- a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/getDisplayValueAndValidationWarnings.ts +++ b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/getDisplayValueAndValidationWarnings.ts @@ -39,11 +39,14 @@ export const getDisplayValueAndValidationWarnings = ({ } }); - return decodeMethod === DecodeMethodEnum.smart - ? getSmartDecodedParts({ - parts, - decodedParts: initialDecodedParts, - identifier - }) - : initialDecodedParts; + const decodedParts = + decodeMethod === DecodeMethodEnum.smart + ? getSmartDecodedParts({ + parts, + decodedParts: initialDecodedParts, + identifier + }) + : initialDecodedParts; + + return decodedParts; }; diff --git a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/getHexValidationWarnings.ts b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/getHexValidationWarnings.ts index e76c7e8ad..a57892697 100644 --- a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/getHexValidationWarnings.ts +++ b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/getHexValidationWarnings.ts @@ -1,15 +1,18 @@ -const isHexValidCharacters = (str: string) => { - return str.toLowerCase().match(/[0-9a-f]/g); +export const isHexValidCharacters = (str: string) => { + return str.toLowerCase().match(/^[0-9a-f]+$/i); }; -const isHexValidLength = (str: string) => { + +export const isHexValidLength = (str: string) => { return str.length % 2 === 0; }; export const getHexValidationWarnings = (str: string) => { const warnings = []; + if (str && !isHexValidCharacters(str)) { warnings.push(`Invalid Hex characters on argument @${str}`); } + if (str && !isHexValidLength(str)) { warnings.push(`Odd number of Hex characters on argument @${str}`); } diff --git a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/getSmartDecodedParts.ts b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/getSmartDecodedParts.ts index dc515e1a7..ac7834969 100644 --- a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/getSmartDecodedParts.ts +++ b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/helpers/getSmartDecodedParts.ts @@ -1,4 +1,4 @@ -import { TransactionTypesEnum } from 'types'; +import { DecodeMethodEnum, TransactionTypesEnum } from 'types'; import { decodeByMethod } from './decodeByMethod'; interface SmartDecodedPartsType { @@ -15,12 +15,15 @@ export const getSmartDecodedParts = ({ const updatedParts = [...decodedParts]; if (parts[0] === TransactionTypesEnum.ESDTNFTTransfer && parts[2]) { - updatedParts[2] = decodeByMethod(parts[2], 'decimal'); + updatedParts[2] = decodeByMethod(parts[2], DecodeMethodEnum.decimal); } if (identifier === TransactionTypesEnum.ESDTNFTTransfer && parts[1]) { const base64Buffer = Buffer.from(String(parts[1]), 'base64'); - updatedParts[1] = decodeByMethod(base64Buffer.toString('hex'), 'decimal'); + updatedParts[1] = decodeByMethod( + base64Buffer.toString('hex'), + DecodeMethodEnum.decimal + ); } return updatedParts; diff --git a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/decodeByMethod.spec.ts b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/decodeByMethod.spec.ts new file mode 100644 index 000000000..581370c8a --- /dev/null +++ b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/decodeByMethod.spec.ts @@ -0,0 +1,122 @@ +import { Address } from '@multiversx/sdk-core/out'; +import { DecodeMethodEnum } from 'types'; +import { addressIsValid } from 'utils/account/addressIsValid'; +import { isUtf8 } from 'utils/decoders'; +import { decodeByMethod } from '../helpers'; + +jest.mock('@multiversx/sdk-core/out', () => ({ + Address: { + fromHex: jest.fn() + } +})); + +jest.mock('utils/account/addressIsValid'); +jest.mock('utils/decoders'); + +describe('decodeByMethod', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('text decode method', () => { + it('should decode hex to utf8 text', () => { + const hexString = Buffer.from('Hello').toString('hex'); + const result = decodeByMethod(hexString, DecodeMethodEnum.text); + expect(result).toBe('Hello'); + }); + + it('should return empty string if the hex is invalid', () => { + const invalidHex = '{test: test}'; + const result = decodeByMethod(invalidHex, DecodeMethodEnum.text); + expect(result).toBe(''); + }); + }); + + describe('decimal decode method', () => { + it('should convert hex to decimal', () => { + const result = decodeByMethod('a', DecodeMethodEnum.decimal); + expect(result).toBe('10'); + }); + + it('should return empty string for empty input', () => { + const result = decodeByMethod('', DecodeMethodEnum.decimal); + expect(result).toBe(''); + }); + }); + + describe('smart decode method', () => { + it('should return bech32 address when valid', () => { + const mockAddress = + 'erd1zwq3qaa3vk5suenlkj4cf0ullwefa6h3n2394k25pxv4sz0pwhhsj9u9vk'; + + (Address.fromHex as jest.Mock).mockReturnValue({ + toString: () => mockAddress + }); + + (addressIsValid as jest.Mock).mockReturnValue(true); + + const result = decodeByMethod('validHex', DecodeMethodEnum.smart); + expect(result).toBe(mockAddress); + }); + + it('should decode to utf8 when possible and valid', () => { + (Address.fromHex as jest.Mock).mockImplementation(() => { + throw new Error(); + }); + (isUtf8 as jest.Mock).mockReturnValue(true); + + const hexString = Buffer.from('ValidText').toString('hex'); + const result = decodeByMethod(hexString, DecodeMethodEnum.smart); + expect(result).toBe('ValidText'); + }); + + it('should check for tokens when utf8 decoded but invalid', () => { + const mockTokens = { + esdts: ['token1'], + nfts: ['nft1'] + }; + + (Address.fromHex as jest.Mock).mockImplementation(() => { + throw new Error(); + }); + + (isUtf8 as jest.Mock).mockReturnValue(false); + + const hexString = Buffer.from('token1').toString('hex'); + const result = decodeByMethod( + hexString, + DecodeMethodEnum.smart, + mockTokens + ); + expect(result).toBe('token1'); + }); + + it('should convert to decimal when no other conditions met', () => { + (Address.fromHex as jest.Mock).mockImplementation(() => { + throw new Error(); + }); + (isUtf8 as jest.Mock).mockReturnValue(false); + + const result = decodeByMethod('a', DecodeMethodEnum.smart); + expect(result).toBe('10'); + }); + + it('should return original part when all decoding fails', () => { + (Address.fromHex as jest.Mock).mockImplementation(() => { + throw new Error(); + }); + + const invalidInput = 'invalid'; + const result = decodeByMethod(invalidInput, DecodeMethodEnum.smart); + expect(result).toBe(invalidInput); + }); + }); + + describe('raw decode method', () => { + it('should return original part', () => { + const part = 'rawData'; + const result = decodeByMethod(part, DecodeMethodEnum.raw); + expect(result).toBe(part); + }); + }); +}); diff --git a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/decodeForDisplay.spec.ts b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/decodeForDisplay.spec.ts new file mode 100644 index 000000000..96722d503 --- /dev/null +++ b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/decodeForDisplay.spec.ts @@ -0,0 +1,149 @@ +import { + DecodeMethodEnum, + DecodeForDisplayPropsType +} from 'types/serverTransactions.types'; +import { decodeForDisplay } from '../decodeForDisplay'; +import { + decodeByMethod, + getDisplayValueAndValidationWarnings, + getSmartDecodedParts +} from '../helpers'; + +jest.mock('../helpers/decodeByMethod'); +jest.mock('../helpers/getDisplayValueAndValidationWarnings'); +jest.mock('../helpers/getSmartDecodedParts'); + +describe('decodeForDisplay', () => { + beforeEach(() => { + jest.clearAllMocks(); + (decodeByMethod as jest.Mock).mockReturnValue('decoded'); + (getDisplayValueAndValidationWarnings as jest.Mock).mockReturnValue([ + 'decodedPart1', + 'decodedPart2' + ]); + (getSmartDecodedParts as jest.Mock).mockReturnValue([ + 'smartDecoded1', + 'smartDecoded2' + ]); + }); + + it('should handle input with @ symbol', () => { + const input = 'part1@part2'; + const props: DecodeForDisplayPropsType = { + input, + decodeMethod: DecodeMethodEnum.text, + identifier: 'test' + }; + + const result = decodeForDisplay(props); + + expect(getDisplayValueAndValidationWarnings).toHaveBeenCalledWith({ + parts: ['part1', 'part2'], + identifier: 'test', + decodeMethod: DecodeMethodEnum.text, + display: expect.any(Object) + }); + expect(result.displayValue).toBe('decodedPart1@decodedPart2'); + expect(result.validationWarnings).toEqual([]); + }); + + it('should handle input with newline character using raw decode method', () => { + const input = 'part1\npart2'; + + const props: DecodeForDisplayPropsType = { + input, + decodeMethod: DecodeMethodEnum.raw, + identifier: 'test' + }; + + const result = decodeForDisplay(props); + + expect(result.displayValue).toBe(input); + expect(result.validationWarnings).toEqual([]); + }); + + it('should handle input with newline character using smart decode method', () => { + const input = 'part1\npart2'; + const props: DecodeForDisplayPropsType = { + input, + decodeMethod: DecodeMethodEnum.smart, + identifier: 'test' + }; + + const result = decodeForDisplay(props); + + expect(getSmartDecodedParts).toHaveBeenCalledWith({ + parts: ['part1', 'part2'], + decodedParts: ['decoded', 'decoded'], + identifier: 'test' + }); + + expect(result.displayValue).toBe('smartDecoded1\nsmartDecoded2'); + expect(result.validationWarnings).toEqual([]); + }); + + it('should handle input with both @ and newline characters', () => { + const input = 'part1@part2\npart3'; + const props: DecodeForDisplayPropsType = { + input, + decodeMethod: DecodeMethodEnum.text, + identifier: 'test' + }; + + const result = decodeForDisplay(props); + + expect(getDisplayValueAndValidationWarnings).toHaveBeenCalled(); + expect(decodeByMethod).toHaveBeenCalled(); + expect(result.validationWarnings).toEqual([]); + }); + + it('should handle simple input without @ or newline', () => { + const input = 'simpleInput'; + const props: DecodeForDisplayPropsType = { + input, + decodeMethod: DecodeMethodEnum.text, + identifier: 'test' + }; + + const result = decodeForDisplay(props); + + expect(decodeByMethod).toHaveBeenCalledWith(input, DecodeMethodEnum.text); + expect(result.displayValue).toBe('decoded'); + expect(result.validationWarnings).toEqual([]); + }); + + it('should preserve validation warnings from getDisplayValueAndValidationWarnings', () => { + const input = 'part1@part2'; + const mockWarnings = ['warning1']; + (getDisplayValueAndValidationWarnings as jest.Mock).mockImplementation( + ({ display }) => { + display.validationWarnings = mockWarnings; + return ['decodedPart1', 'decodedPart2']; + } + ); + + const props: DecodeForDisplayPropsType = { + input, + decodeMethod: DecodeMethodEnum.text, + identifier: 'test' + }; + + const result = decodeForDisplay(props); + + expect(result.validationWarnings).toEqual(mockWarnings); + }); + + it('should handle empty input', () => { + const props: DecodeForDisplayPropsType = { + input: '', + decodeMethod: DecodeMethodEnum.text, + identifier: 'test' + }; + + const result = decodeForDisplay(props); + + expect(decodeByMethod).toHaveBeenCalledWith('', DecodeMethodEnum.text); + expect(result.displayValue).toBe('decoded'); + expect(result.validationWarnings).toEqual([]); + }); +}); diff --git a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/getDisplayValueAndValidationWarnings.spec.ts b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/getDisplayValueAndValidationWarnings.spec.ts new file mode 100644 index 000000000..2617bf417 --- /dev/null +++ b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/getDisplayValueAndValidationWarnings.spec.ts @@ -0,0 +1,199 @@ +import { DecodeMethodEnum, DecodedDisplayType } from 'types'; +import { + decodeByMethod, + getHexValidationWarnings, + getSmartDecodedParts, + getDisplayValueAndValidationWarnings +} from '../helpers'; + +jest.mock('../helpers/decodeByMethod'); +jest.mock('../helpers/getHexValidationWarnings'); +jest.mock('../helpers/getSmartDecodedParts'); + +describe('getDisplayValueAndValidationWarnings', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const createMockDisplay = (): DecodedDisplayType => ({ + displayValue: '', + validationWarnings: [] + }); + + it('should handle encoded display value in first two parts', () => { + const mockParts = ['short', 'test@123']; + const mockDisplay = createMockDisplay(); + (decodeByMethod as jest.Mock).mockReturnValue('decoded'); + + const result = getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(decodeByMethod).toHaveBeenCalledWith( + 'test@123', + DecodeMethodEnum.text + ); + + expect(result).toEqual(['short', 'decoded']); + }); + + it('should pass through non-encoded values in first two parts', () => { + const mockParts = ['short', 'test123']; + const mockDisplay = createMockDisplay(); + + const result = getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(decodeByMethod).toHaveBeenCalledWith( + 'test123', + DecodeMethodEnum.text + ); + + expect(result).toEqual(['short', 'decoded']); + }); + + it('should handle hex validation warnings', () => { + const mockParts = ['a'.repeat(64), 'value2']; + const mockWarnings = ['warning1']; + const mockDisplay = createMockDisplay(); + mockDisplay.validationWarnings = ['existing']; + + (getHexValidationWarnings as jest.Mock).mockReturnValue(mockWarnings); + (decodeByMethod as jest.Mock).mockReturnValue('decoded'); + + const result = getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(getHexValidationWarnings).toHaveBeenCalledWith('a'.repeat(64)); + expect(mockDisplay.validationWarnings).toEqual(['existing', 'warning1']); + expect(result).toEqual(['decoded', 'decoded']); + }); + + it('should use smart decoding when decodeMethod is smart', () => { + const mockParts = ['value1', 'value2']; + const mockInitialDecoded = ['value1', 'decoded1']; + const mockSmartDecoded = ['smart1', 'smart2']; + const mockDisplay = createMockDisplay(); + + (decodeByMethod as jest.Mock) + .mockReturnValueOnce('decoded1') + .mockReturnValueOnce('decoded2'); + (getSmartDecodedParts as jest.Mock).mockReturnValue(mockSmartDecoded); + + const result = getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.smart, + identifier: 'test', + display: mockDisplay + }); + + expect(getSmartDecodedParts).toHaveBeenCalledWith({ + parts: mockParts, + decodedParts: mockInitialDecoded, + identifier: 'test' + }); + + expect(result).toEqual(mockSmartDecoded); + }); + + it('should handle empty parts array', () => { + const mockDisplay = createMockDisplay(); + + const result = getDisplayValueAndValidationWarnings({ + parts: [], + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(result).toEqual([]); + expect(decodeByMethod).not.toHaveBeenCalled(); + }); + + it('should deduplicate validation warnings', () => { + const mockParts = ['value1', 'value2']; + const mockWarnings = ['warning1', 'warning1', 'warning2']; + const mockDisplay = createMockDisplay(); + mockDisplay.validationWarnings = ['warning1']; + + (getHexValidationWarnings as jest.Mock).mockReturnValue(mockWarnings); + (decodeByMethod as jest.Mock).mockReturnValue('decoded'); + + getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(mockDisplay.validationWarnings).toEqual(['warning1', 'warning2']); + }); + + it('should preserve displayValue in display object', () => { + const mockParts = ['value1', 'value2']; + const mockDisplay = createMockDisplay(); + mockDisplay.displayValue = 'initial value'; + + getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(mockDisplay.displayValue).toBe('initial value'); + }); + + it('should handle hex validation and decoding for non-special parts', () => { + const mockParts = ['short', 'xyz123']; + const mockDisplay = createMockDisplay(); + (decodeByMethod as jest.Mock).mockReturnValue('decoded'); + (getHexValidationWarnings as jest.Mock).mockReturnValue([ + 'Invalid Hex characters on argument @xyz123' + ]); + + const result = getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(getHexValidationWarnings).toHaveBeenCalledWith('xyz123'); + expect(decodeByMethod).toHaveBeenCalledWith( + 'xyz123', + DecodeMethodEnum.text + ); + expect(mockDisplay.validationWarnings).toContain( + 'Invalid Hex characters on argument @xyz123' + ); + expect(result).toEqual(['short', 'decoded']); + }); + + it('should not add duplicate validation warnings', () => { + const mockParts = ['short', 'xyz123']; + const mockDisplay = createMockDisplay(); + mockDisplay.validationWarnings = [ + 'Invalid Hex characters on argument @xyz123' + ]; + (decodeByMethod as jest.Mock).mockReturnValue('decoded'); + (getHexValidationWarnings as jest.Mock).mockReturnValue([ + 'Invalid Hex characters on argument @xyz123' + ]); + + getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(mockDisplay.validationWarnings).toHaveLength(1); + expect(mockDisplay.validationWarnings).toEqual([ + 'Invalid Hex characters on argument @xyz123' + ]); + }); +}); diff --git a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/getHexValidationWarnings.spec.ts b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/getHexValidationWarnings.spec.ts new file mode 100644 index 000000000..ca73785bd --- /dev/null +++ b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/getHexValidationWarnings.spec.ts @@ -0,0 +1,59 @@ +import { + getHexValidationWarnings, + isHexValidCharacters, + isHexValidLength +} from '../helpers'; + +describe('Hex Validation Functions', () => { + describe('isHexValidCharacters', () => { + it('should return match for valid hex characters', () => { + expect(isHexValidCharacters('123abc')).toBeTruthy(); + expect(isHexValidCharacters('ABCDEF')).toBeTruthy(); + }); + + it('should return null for invalid hex characters', () => { + expect(isHexValidCharacters('xyz')).toBeNull(); + expect(isHexValidCharacters('123g')).toBeNull(); + }); + }); + + describe('isHexValidLength', () => { + it('should return true for even length strings', () => { + expect(isHexValidLength('1234')).toBeTruthy(); + expect(isHexValidLength('ab')).toBeTruthy(); + }); + + it('should return false for odd length strings', () => { + expect(isHexValidLength('123')).toBeFalsy(); + expect(isHexValidLength('a')).toBeFalsy(); + }); + }); + + describe('getHexValidationWarnings', () => { + it('should return empty array for valid hex string', () => { + expect(getHexValidationWarnings('1234ab')).toEqual([]); + }); + + it('should return warning for invalid hex characters', () => { + const result = getHexValidationWarnings('xyz123'); + + expect(result).toContain('Invalid Hex characters on argument @xyz123'); + }); + + it('should return warning for odd length', () => { + const result = getHexValidationWarnings('123'); + expect(result).toContain('Odd number of Hex characters on argument @123'); + }); + + it('should return both warnings when both conditions fail', () => { + const result = getHexValidationWarnings('xyz'); + expect(result).toHaveLength(2); + expect(result).toContain('Invalid Hex characters on argument @xyz'); + expect(result).toContain('Odd number of Hex characters on argument @xyz'); + }); + + it('should return empty array for empty string', () => { + expect(getHexValidationWarnings('')).toEqual([]); + }); + }); +}); diff --git a/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/getSmartDecodedParts.spec.ts b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/getSmartDecodedParts.spec.ts new file mode 100644 index 000000000..d4ad1d1c4 --- /dev/null +++ b/src/utils/transactions/transactionInfoHelpers/decodeForDisplay/tests/getSmartDecodedParts.spec.ts @@ -0,0 +1,93 @@ +import { DecodeMethodEnum, TransactionTypesEnum } from 'types'; +import { decodeByMethod, getSmartDecodedParts } from '../helpers'; + +jest.mock('../helpers/decodeByMethod'); + +describe('getSmartDecodedParts', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return unchanged decodedParts when no conditions are met', () => { + const input = { + parts: ['someType'], + decodedParts: ['decodedValue'] + }; + + const result = getSmartDecodedParts(input); + expect(result).toEqual(['decodedValue']); + expect(decodeByMethod).not.toHaveBeenCalled(); + }); + + it('should decode parts[2] when first part is ESDTNFTTransfer', () => { + const mockDecodedValue = '123'; + (decodeByMethod as jest.Mock).mockReturnValue(mockDecodedValue); + + const input = { + parts: [TransactionTypesEnum.ESDTNFTTransfer, 'value1', 'hexValue'], + decodedParts: ['decoded1', 'decoded2', 'decoded3'] + }; + + const result = getSmartDecodedParts(input); + + expect(decodeByMethod).toHaveBeenCalledWith( + 'hexValue', + DecodeMethodEnum.decimal + ); + expect(result).toEqual(['decoded1', 'decoded2', mockDecodedValue]); + }); + + it('should decode parts[1] when identifier is ESDTNFTTransfer', () => { + const mockDecodedValue = '456'; + (decodeByMethod as jest.Mock).mockReturnValue(mockDecodedValue); + + const base64Value = Buffer.from('test').toString('base64'); + const expectedHexValue = Buffer.from('test').toString('hex'); + + const input = { + parts: ['someType', base64Value], + decodedParts: ['decoded1', 'decoded2'], + identifier: TransactionTypesEnum.ESDTNFTTransfer + }; + + const result = getSmartDecodedParts(input); + + expect(decodeByMethod).toHaveBeenCalledWith( + expectedHexValue, + DecodeMethodEnum.decimal + ); + expect(result).toEqual(['decoded1', mockDecodedValue]); + }); + + it('should handle both conditions simultaneously', () => { + const mockDecodedValues = ['123', '456']; + (decodeByMethod as jest.Mock) + .mockReturnValueOnce(mockDecodedValues[0]) + .mockReturnValueOnce(mockDecodedValues[1]); + + const base64Value = Buffer.from('test').toString('base64'); + const expectedHexValue = Buffer.from('test').toString('hex'); + + const input = { + parts: [TransactionTypesEnum.ESDTNFTTransfer, base64Value, 'hexValue'], + decodedParts: ['decoded1', 'decoded2', 'decoded3'], + identifier: TransactionTypesEnum.ESDTNFTTransfer + }; + + const result = getSmartDecodedParts(input); + + expect(decodeByMethod).toHaveBeenCalledWith( + 'hexValue', + DecodeMethodEnum.decimal + ); + expect(decodeByMethod).toHaveBeenCalledWith( + expectedHexValue, + DecodeMethodEnum.decimal + ); + expect(result).toEqual([ + 'decoded1', + mockDecodedValues[1], + mockDecodedValues[0] + ]); + }); +}); From fed20a73b52ffa2d16e06430aed50eb0b1ca9c0c Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Tue, 19 Nov 2024 17:17:29 +0200 Subject: [PATCH 2/2] 3.0.11 --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1544cadb8..4cc85e735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [[v3.0.11](https://github.com/multiversx/mx-sdk-dapp/pull/1308)] - 2024-11-19 + - [Updated transaction data decode functions](https://github.com/multiversx/mx-sdk-dapp/pull/1307) ## [[v3.0.10](https://github.com/multiversx/mx-sdk-dapp/pull/1305)] - 2024-11-11 diff --git a/package.json b/package.json index 1acc4a8b0..78b2a2acf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-dapp", - "version": "3.0.10", + "version": "3.0.11", "description": "A library to hold the main logic for a dapp on the MultiversX blockchain", "author": "MultiversX", "license": "GPL-3.0-or-later",