From 52718d76f7cce2dfbe52fa552458aeaacd91e229 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 23 Jan 2019 13:29:03 +0100 Subject: [PATCH] Add .eq to Codec (#622) * Add .eq to Codec * Tests for Codec .eq * Extra ()'s around checks * Update AbstractInt.ts * Use .size in Map compare * Equivalency comment * Update Compact.eq to user wrapper eq --- packages/types/src/AccountId.spec.ts | 4 + packages/types/src/AccountId.ts | 7 ++ packages/types/src/AccountIndex.spec.ts | 8 ++ packages/types/src/AccountIndex.ts | 13 +++ packages/types/src/Address.spec.ts | 13 +++ packages/types/src/Address.ts | 7 ++ packages/types/src/Bool.spec.ts | 10 +++ packages/types/src/Bool.ts | 11 +++ packages/types/src/ChainProperties.spec.ts | 9 +++ packages/types/src/ChainProperties.ts | 8 ++ packages/types/src/Moment.spec.ts | 10 +++ packages/types/src/Moment.ts | 7 ++ packages/types/src/Null.spec.ts | 19 +++++ packages/types/src/Null.ts | 8 ++ packages/types/src/Text.spec.ts | 14 ++++ packages/types/src/Text.ts | 9 +++ packages/types/src/codec/AbstractInt.ts | 15 ++++ packages/types/src/codec/Compact.spec.ts | 10 +++ packages/types/src/codec/Compact.ts | 11 +++ packages/types/src/codec/Enum.spec.js | 26 ++++++ packages/types/src/codec/Enum.ts | 13 +++ packages/types/src/codec/EnumType.spec.ts | 19 +++++ packages/types/src/codec/EnumType.ts | 13 +++ .../codec/{Option.spec.js => Option.spec.ts} | 20 ++++- packages/types/src/codec/Option.ts | 11 +++ packages/types/src/codec/Set.spec.ts | 28 +++++++ packages/types/src/codec/Set.ts | 19 ++++- packages/types/src/codec/Struct.spec.ts | 18 +++++ packages/types/src/codec/Struct.ts | 9 ++- packages/types/src/codec/Tuple.spec.ts | 9 +++ packages/types/src/codec/Tuple.ts | 9 ++- packages/types/src/codec/U8a.spec.ts | 18 +++++ packages/types/src/codec/U8a.ts | 13 +++ packages/types/src/codec/UInt.spec.ts | 18 +++++ packages/types/src/codec/Vector.spec.ts | 12 +++ packages/types/src/codec/Vector.ts | 11 ++- .../src/codec/utils/compareArray.spec.ts | 48 +++++++++++ .../types/src/codec/utils/compareArray.ts | 21 +++++ .../types/src/codec/utils/compareMap.spec.ts | 81 +++++++++++++++++++ packages/types/src/codec/utils/compareMap.ts | 34 ++++++++ packages/types/src/codec/utils/index.ts | 7 ++ packages/types/src/types.ts | 5 ++ 42 files changed, 646 insertions(+), 9 deletions(-) create mode 100644 packages/types/src/Null.spec.ts rename packages/types/src/codec/{Option.spec.js => Option.spec.ts} (77%) create mode 100644 packages/types/src/codec/utils/compareArray.spec.ts create mode 100644 packages/types/src/codec/utils/compareArray.ts create mode 100644 packages/types/src/codec/utils/compareMap.spec.ts create mode 100644 packages/types/src/codec/utils/compareMap.ts create mode 100644 packages/types/src/codec/utils/index.ts diff --git a/packages/types/src/AccountId.spec.ts b/packages/types/src/AccountId.spec.ts index 3a5359eb98..73040734e0 100644 --- a/packages/types/src/AccountId.spec.ts +++ b/packages/types/src/AccountId.spec.ts @@ -20,6 +20,10 @@ describe('AccountId', () => { it('is empty by default', () => { expect(id.isEmpty).toBe(true); }); + + it('equals the empty address', () => { + expect(id.eq('5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUppTZ')).toBe(true); + }); }); describe('decoding', () => { diff --git a/packages/types/src/AccountId.ts b/packages/types/src/AccountId.ts index 58e1606b17..ff8e059c4c 100644 --- a/packages/types/src/AccountId.ts +++ b/packages/types/src/AccountId.ts @@ -40,6 +40,13 @@ export default class AccountId extends U8aFixed { return value; } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + return super.eq(AccountId.decodeAccountId(other)); + } + /** * @description Converts the Object to JSON, typically used for RPC transfers */ diff --git a/packages/types/src/AccountIndex.spec.ts b/packages/types/src/AccountIndex.spec.ts index e21bdda6f6..95d894586d 100644 --- a/packages/types/src/AccountIndex.spec.ts +++ b/packages/types/src/AccountIndex.spec.ts @@ -30,6 +30,14 @@ describe('AccountIndex', () => { ).toEqual('Mwz15xN8'); }); + it('compares ss-58 values', () => { + expect(new AccountIndex(256 * 1).eq('25GUyk')).toBe(true); + }); + + it('compares numbers', () => { + expect(new AccountIndex('25GUyk').eq(256 * 1)).toBe(true); + }); + describe('calcLength', () => { const testLength = (value: number, length: number) => expect(AccountIndex.calcLength(value)).toEqual(length); diff --git a/packages/types/src/AccountIndex.ts b/packages/types/src/AccountIndex.ts index 2ca0202c35..0cbaad118c 100644 --- a/packages/types/src/AccountIndex.ts +++ b/packages/types/src/AccountIndex.ts @@ -86,6 +86,19 @@ export default class AccountIndex extends U32 { } } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + // shortcut for BN or Number, don't create an object + if (isBn(other) || isNumber(other)) { + return super.eq(other); + } + + // convert and compare + return super.eq(new AccountIndex(other)); + } + /** * @description Returns a hex string representation of the value */ diff --git a/packages/types/src/Address.spec.ts b/packages/types/src/Address.spec.ts index fd1e4cbc1d..ece7c880e4 100644 --- a/packages/types/src/Address.spec.ts +++ b/packages/types/src/Address.spec.ts @@ -13,6 +13,19 @@ describe('Address', () => { expect(a.toString()).toBe(expected); }); + describe('utility', () => { + it('equals on AccountId', () => { + const addr = '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'; + + expect(new Address(addr).eq(addr)).toBe(true); + }); + + it('equals on AccountIndex', () => { + // see the test below - these are equivalent (with different prefix encoding) + expect(new Address('PrF3').eq('F7ih')).toBe(true); + }); + }); + describe('decoding', () => { testDecode( 'Address', diff --git a/packages/types/src/Address.ts b/packages/types/src/Address.ts index 720145a43a..025a16d989 100644 --- a/packages/types/src/Address.ts +++ b/packages/types/src/Address.ts @@ -84,6 +84,13 @@ export default class Address extends Base { : this.raw.encodedLength; } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + return this.raw.eq(other); + } + /** * @description Returns a hex string representation of the value */ diff --git a/packages/types/src/Bool.spec.ts b/packages/types/src/Bool.spec.ts index b213dd0bef..b1e5608a25 100644 --- a/packages/types/src/Bool.spec.ts +++ b/packages/types/src/Bool.spec.ts @@ -34,4 +34,14 @@ describe('Bool', () => { it('correctly encodes length', () => { expect(new Bool(true).encodedLength).toEqual(1); }); + + describe('utils', () => { + it('compares agains a boolean', () => { + expect(new Bool(true).eq(true)).toBe(true); + }); + + it('compares agains a Bool', () => { + expect(new Bool(false).eq(new Bool(false))).toBe(true); + }); + }); }); diff --git a/packages/types/src/Bool.ts b/packages/types/src/Bool.ts index cf653f043c..c3447508a9 100644 --- a/packages/types/src/Bool.ts +++ b/packages/types/src/Bool.ts @@ -36,6 +36,17 @@ export default class Bool extends Boolean implements Codec { return 1; } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + return this.valueOf() === ( + other instanceof Boolean + ? other.valueOf() + : other + ); + } + /** * @description Returns a hex string representation of the value */ diff --git a/packages/types/src/ChainProperties.spec.ts b/packages/types/src/ChainProperties.spec.ts index 8c913e933c..71670362f3 100644 --- a/packages/types/src/ChainProperties.spec.ts +++ b/packages/types/src/ChainProperties.spec.ts @@ -19,4 +19,13 @@ describe('ChainProperties', () => { }).entries()] ).toEqual([['decimals', 15], ['tokenSymbol', 'BBQ']]); }); + + it('checks equality', () => { + expect( + new ChainProperties({ + decimals: 15, + tokenSymbol: 'BBQ' + }).eq([['tokenSymbol', 'BBQ'], ['decimals', 15]]) + ).toBe(true); + }); }); diff --git a/packages/types/src/ChainProperties.ts b/packages/types/src/ChainProperties.ts index 5717783bed..439c1a3ff0 100644 --- a/packages/types/src/ChainProperties.ts +++ b/packages/types/src/ChainProperties.ts @@ -3,6 +3,7 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { Codec } from './types'; +import { compareMap } from './codec/utils'; // TODO Expose known properties as they become known, currently we have `tokenSymbol` (e.g. BBQ) @@ -38,6 +39,13 @@ export default class ChainProperties extends Map implements Codec { return super.get(name); } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + return compareMap(this, other); + } + /** * @description Unimplemented, will throw */ diff --git a/packages/types/src/Moment.spec.ts b/packages/types/src/Moment.spec.ts index d8599c208f..6120de08ec 100644 --- a/packages/types/src/Moment.spec.ts +++ b/packages/types/src/Moment.spec.ts @@ -42,4 +42,14 @@ describe('Moment', () => { ).toBe(true); }); }); + + describe('utils', () => { + it('compares values', () => { + expect(new Moment(123).eq(123)).toBe(true); + }); + + it('compares values (non-match)', () => { + expect(new Moment(123).eq(456)).toBe(false); + }); + }); }); diff --git a/packages/types/src/Moment.ts b/packages/types/src/Moment.ts index 08e655def6..625c0fadbf 100644 --- a/packages/types/src/Moment.ts +++ b/packages/types/src/Moment.ts @@ -52,6 +52,13 @@ export default class Moment extends Date implements Codec { return BITLENGTH / 8; } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + return Moment.decodeMoment(other).getTime() === this.getTime(); + } + /** * @description Returns the number of bits in the value */ diff --git a/packages/types/src/Null.spec.ts b/packages/types/src/Null.spec.ts new file mode 100644 index 0000000000..5310bbabb9 --- /dev/null +++ b/packages/types/src/Null.spec.ts @@ -0,0 +1,19 @@ +// Copyright 2017-2019 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import Null from './Null'; + +describe('Null', () => { + it('compares against null', () => { + expect(new Null().eq(null)).toBe(true); + }); + + it('compares against Null', () => { + expect(new Null().eq(new Null())).toBe(true); + }); + + it('compares against other (failed)', () => { + expect(new Null().eq()).toBe(false); + }); +}); diff --git a/packages/types/src/Null.ts b/packages/types/src/Null.ts index bfdd6512c0..4983f9e49d 100644 --- a/packages/types/src/Null.ts +++ b/packages/types/src/Null.ts @@ -3,6 +3,7 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { Codec } from './types'; +import { isNull } from '@polkadot/util'; /** * @name Null @@ -17,6 +18,13 @@ export default class Null implements Codec { return 0; } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + return other instanceof Null || isNull(other); + } + /** * @description Returns a hex string representation of the value */ diff --git a/packages/types/src/Text.spec.ts b/packages/types/src/Text.spec.ts index 58620d60c9..9654d16ce0 100644 --- a/packages/types/src/Text.spec.ts +++ b/packages/types/src/Text.spec.ts @@ -30,4 +30,18 @@ describe('Text', () => { testEncode('toString', 'foo'); testEncode('toU8a', Uint8Array.from([12, 102, 111, 111])); }); + + describe('utils', () => { + it('compares actual string values', () => { + expect(new Text('123').eq('123')).toBe(true); + }); + + it('compares actual String values', () => { + expect(new Text('XYX').eq(new String('XYX'))).toBe(true); + }); + + it('compares actual non-string values (fails)', () => { + expect(new Text('123').eq(123)).toBe(false); + }); + }); }); diff --git a/packages/types/src/Text.ts b/packages/types/src/Text.ts index 19f8548b19..a9bd90982b 100644 --- a/packages/types/src/Text.ts +++ b/packages/types/src/Text.ts @@ -56,6 +56,15 @@ export default class Text extends String implements Codec { return super.length; } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + return isString(other) + ? this.toString() === other.toString() + : false; + } + /** * @description Returns a hex string representation of the value */ diff --git a/packages/types/src/codec/AbstractInt.ts b/packages/types/src/codec/AbstractInt.ts index c3cad8ea17..d71866d2e7 100644 --- a/packages/types/src/codec/AbstractInt.ts +++ b/packages/types/src/codec/AbstractInt.ts @@ -21,6 +21,7 @@ export const DEFAULT_UINT_BITS = 64; export default abstract class AbstractInt extends BN implements Codec { protected _bitLength: UIntBitLength; private _isHexJson: boolean; + private _isNegative: boolean; constructor ( isNegative: boolean, @@ -32,6 +33,7 @@ export default abstract class AbstractInt extends BN implements Codec { this._bitLength = bitLength; this._isHexJson = isHexJson; + this._isNegative = isNegative; } static decodeAbstracInt (value: AnyNumber, bitLength: UIntBitLength, isNegative: boolean): string { @@ -70,6 +72,19 @@ export default abstract class AbstractInt extends BN implements Codec { return this._bitLength; } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + // Here we are actually overriding the built-in .eq to take care of both + // number and BN inputs (no `.eqn` needed) - numbers will be converted + return super.eq( + isHex(other) + ? hexToBn(other.toString(), { isLe: false, isNegative: this._isNegative }) + : bnToBn(other) + ); + } + /** * @description Returns the BN representation of the number. (Compatibility) */ diff --git a/packages/types/src/codec/Compact.spec.ts b/packages/types/src/codec/Compact.spec.ts index f27364dd7d..77a098b293 100644 --- a/packages/types/src/codec/Compact.spec.ts +++ b/packages/types/src/codec/Compact.spec.ts @@ -137,6 +137,16 @@ describe('Compact', () => { }); }); + describe('utils', () => { + it('compares against another Compact', () => { + expect(new Compact(U32, 12345).eq(new Compact(U32, 12345))).toBe(true); + }); + + it('compares against a primitive', () => { + expect(new Compact(U32, 12345).eq(12345)).toBe(true); + }); + }); + describe('helpers', () => { it('correctly adds the length prefix', () => { expect( diff --git a/packages/types/src/codec/Compact.ts b/packages/types/src/codec/Compact.ts index f8db36a115..3a203ded96 100644 --- a/packages/types/src/codec/Compact.ts +++ b/packages/types/src/codec/Compact.ts @@ -80,6 +80,17 @@ export default class Compact extends Base implements Codec { return this.raw.bitLength(); } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + return this.raw.eq( + other instanceof Compact + ? other.raw + : other + ); + } + /** * @description Returns the BN representation of the number */ diff --git a/packages/types/src/codec/Enum.spec.js b/packages/types/src/codec/Enum.spec.js index 2cb4717c53..1035bb6c87 100644 --- a/packages/types/src/codec/Enum.spec.js +++ b/packages/types/src/codec/Enum.spec.js @@ -53,4 +53,30 @@ describe('Enum', () => { new Enum(['foo', 'bar'], 5).toString() ).toEqual('5'); }); + + describe('utils', () => { + it('compares agains the index value', () => { + expect( + new Enum(['foo', 'bar'], 1).eq(1) + ).toBe(true); + }); + + it('compares agains the index value (false)', () => { + expect( + new Enum(['foo', 'bar'], 1).eq(0) + ).toBe(false); + }); + + it('compares agains the string value', () => { + expect( + new Enum(['foo', 'bar'], 1).eq('bar') + ).toBe(true); + }); + + it('compares agains the string value (false)', () => { + expect( + new Enum(['foo', 'bar'], 1).eq('foo') + ).toBe(false); + }); + }); }); diff --git a/packages/types/src/codec/Enum.ts b/packages/types/src/codec/Enum.ts index 17ca9a8b4f..225ced8e0e 100644 --- a/packages/types/src/codec/Enum.ts +++ b/packages/types/src/codec/Enum.ts @@ -65,6 +65,19 @@ export default class Enum extends Base implements Codec { return 1; } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + if (other instanceof Enum) { + return this.raw === other.raw; + } + + return isString(other) + ? this.toString() === other + : this.raw === other; + } + /** * @description Returns a hex string representation of the value */ diff --git a/packages/types/src/codec/EnumType.spec.ts b/packages/types/src/codec/EnumType.spec.ts index c9243c7c23..2eee1b018a 100644 --- a/packages/types/src/codec/EnumType.spec.ts +++ b/packages/types/src/codec/EnumType.spec.ts @@ -84,4 +84,23 @@ describe('Struct', () => { expect(enumType.type).toBe('Text'); expect(enumType.value).toEqual(text); }); + + describe('utils', () => { + const eqtest = new EnumType( + { U32, Text }, + new Uint8Array([1, 3 << 2, 88, 89, 90]) + ); + + it('compares against index', () => { + expect( + eqtest.eq(1) + ).toBe(true); + }); + + it('compares against values', () => { + expect( + eqtest.eq('XYZ') + ).toBe(true); + }); + }); }); diff --git a/packages/types/src/codec/EnumType.ts b/packages/types/src/codec/EnumType.ts index 3eb4034dfe..02f56c9750 100644 --- a/packages/types/src/codec/EnumType.ts +++ b/packages/types/src/codec/EnumType.ts @@ -130,6 +130,19 @@ export default class EnumType extends Base implements Codec { return this.raw; } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + // cater for the case where we only pass the enum index + if (isNumber(other)) { + return this.toNumber() === other; + } + + // compare the actual wrapper value + return this.value.eq(other); + } + /** * @description Returns a hex string representation of the value */ diff --git a/packages/types/src/codec/Option.spec.js b/packages/types/src/codec/Option.spec.ts similarity index 77% rename from packages/types/src/codec/Option.spec.js rename to packages/types/src/codec/Option.spec.ts index 17fe551422..e3bb65ddbd 100644 --- a/packages/types/src/codec/Option.spec.js +++ b/packages/types/src/codec/Option.spec.ts @@ -5,7 +5,7 @@ import Option from './Option'; import Text from '../Text'; -const testDecode = (type, input, expected) => +const testDecode = (type: string, input: any, expected: any) => it(`can decode from ${type}`, () => { const o = new Option(Text, input); @@ -13,11 +13,11 @@ const testDecode = (type, input, expected) => expect(o.isNone).toBe(!expected.length); }); -const testEncode = (to, expected) => +const testEncode = (to: string, expected: any) => it(`can encode ${to}`, () => { const e = new Option(Text, 'foo'); - expect(e[to]()).toEqual(expected); + expect((e as any)[to]()).toEqual(expected); }); describe('Option', () => { @@ -36,7 +36,7 @@ describe('Option', () => { testDecode('string (with)', 'foo', 'foo'); testDecode('string (without)', undefined, ''); testDecode('Uint8Array (with)', Uint8Array.from([1, 12, 102, 111, 111]), 'foo'); - testDecode('Uint8Array (without)', Uint8Array.from([0]), '', false); + testDecode('Uint8Array (without)', Uint8Array.from([0]), ''); testEncode('toHex', '0x010c666f6f'); testEncode('toString', 'foo'); @@ -65,4 +65,16 @@ describe('Option', () => { new Option(Text).toU8a() ).toEqual(new Uint8Array([0])); }); + + describe('utils', () => { + const testeq = new Option(Text, '1234'); + + it('compares against other option', () => { + expect(testeq.eq(new Option(Text, '1234'))).toBe(true); + }); + + it('compares against raw value', () => { + expect(testeq.eq('1234')).toBe(true); + }); + }); }); diff --git a/packages/types/src/codec/Option.ts b/packages/types/src/codec/Option.ts index d94c1f83f9..4fabb39cc3 100644 --- a/packages/types/src/codec/Option.ts +++ b/packages/types/src/codec/Option.ts @@ -75,6 +75,17 @@ export default class Option extends Base implements Codec { return this.raw; } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + if (other instanceof Option) { + return (this.isSome === other.isSome) && this.value.eq(other.value); + } + + return this.value.eq(other); + } + /** * @description Returns a hex string representation of the value */ diff --git a/packages/types/src/codec/Set.spec.ts b/packages/types/src/codec/Set.spec.ts index 0ed3521b89..fbda18c291 100644 --- a/packages/types/src/codec/Set.spec.ts +++ b/packages/types/src/codec/Set.spec.ts @@ -42,4 +42,32 @@ describe('Set', () => { expect(set.toU8a()).toEqual(new Uint8Array([19])); }); + + describe('utils', () => { + const set = new Set(SET_ROLES, ['full', 'authority']); + + it('compares against string array', () => { + expect( + set.eq(['authority', 'full']) + ).toBe(true); + }); + + it('compares against number (encoded)', () => { + expect( + set.eq(SET_ROLES.full | SET_ROLES.authority) + ).toBe(true); + }); + + it('compares against other sets', () => { + expect( + set.eq(new Set(SET_ROLES, ['authority', 'full'])) + ).toBe(true); + }); + + it('returns false on other values', () => { + expect( + set.eq('full') + ).toBe(false); + }); + }); }); diff --git a/packages/types/src/codec/Set.ts b/packages/types/src/codec/Set.ts index c222559862..00d2271458 100644 --- a/packages/types/src/codec/Set.ts +++ b/packages/types/src/codec/Set.ts @@ -2,10 +2,11 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { isU8a, isUndefined, u8aToHex } from '@polkadot/util'; +import { isU8a, isNumber, isUndefined, u8aToHex } from '@polkadot/util'; import Base from './Base'; import { Codec } from '../types'; +import { compareArray } from './utils'; type SetValues = { [index: string]: number @@ -94,6 +95,22 @@ export default class Set extends Base> implements Codec { return Set.encodeSet(this._setValues, this.raw); } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + if (Array.isArray(other)) { + // we don't actually care about the order, sort the values + return compareArray(this.values.sort(), other.sort()); + } else if (other instanceof Set) { + return this.eq(other.values); + } else if (isNumber(other)) { + return this.valueEncoded === other; + } + + return false; + } + /** * @description Returns a hex string representation of the value */ diff --git a/packages/types/src/codec/Struct.spec.ts b/packages/types/src/codec/Struct.spec.ts index c308f5b622..f541315cf2 100644 --- a/packages/types/src/codec/Struct.spec.ts +++ b/packages/types/src/codec/Struct.spec.ts @@ -154,4 +154,22 @@ describe('Struct', () => { .toString() ).toEqual('1234'); }); + + describe('utils', () => { + it('compares against other objects', () => { + const test = { + foo: 'foo', + bar: 'bar', + baz: 3 + }; + + expect( + new Struct({ + foo: Text, + bar: Text, + baz: U32 + }, test).eq(test) + ).toBe(true); + }); + }); }); diff --git a/packages/types/src/codec/Struct.ts b/packages/types/src/codec/Struct.ts index 4b37f09478..d4db048215 100644 --- a/packages/types/src/codec/Struct.ts +++ b/packages/types/src/codec/Struct.ts @@ -5,7 +5,7 @@ import { hexToU8a, isHex, isObject, isU8a, u8aConcat, u8aToHex } from '@polkadot/util'; import { Codec, Constructor, ConstructorDef } from '../types'; -import decodeU8a from './utils/decodeU8a'; +import { compareMap, decodeU8a } from './utils'; /** * @name Struct @@ -152,6 +152,13 @@ export default class Struct< }, 0); } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + return compareMap(this, other); + } + /** * @description Returns a specific names entry in the structure * @param name The name of the entry to retrieve diff --git a/packages/types/src/codec/Tuple.spec.ts b/packages/types/src/codec/Tuple.spec.ts index 212a384943..afaf7d1362 100644 --- a/packages/types/src/codec/Tuple.spec.ts +++ b/packages/types/src/codec/Tuple.spec.ts @@ -85,4 +85,13 @@ describe('Tuple', () => { expect(tuple.map(v => v.toString())).toEqual(['bazzing', '69']); }); + describe('utils', () => { + it('compares against inputs', () => { + expect(tuple.eq(['bazzing', 69])).toBe(true); + }); + + it('compares against inputs (mismatch)', () => { + expect(tuple.eq(['bazzing', 72])).toBe(false); + }); + }); }); diff --git a/packages/types/src/codec/Tuple.ts b/packages/types/src/codec/Tuple.ts index bf1f84731c..6577d92e79 100644 --- a/packages/types/src/codec/Tuple.ts +++ b/packages/types/src/codec/Tuple.ts @@ -5,7 +5,7 @@ import { isU8a, u8aConcat, u8aToHex, isHex, hexToU8a } from '@polkadot/util'; import { AnyNumber, AnyU8a, AnyString, Codec, Constructor } from '../types'; -import decodeU8a from './utils/decodeU8a'; +import { compareArray, decodeU8a } from './utils'; type TupleConstructors = Array | { [index: string]: Constructor @@ -71,6 +71,13 @@ export default class Tuple extends Array implements Codec { : Object.keys(this._Types); } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + return compareArray(this, other); + } + /** * @description Converts the Object to an standard JavaScript Array */ diff --git a/packages/types/src/codec/U8a.spec.ts b/packages/types/src/codec/U8a.spec.ts index ec5347eac9..4eec497ab6 100644 --- a/packages/types/src/codec/U8a.spec.ts +++ b/packages/types/src/codec/U8a.spec.ts @@ -51,4 +51,22 @@ describe('U8a', () => { it('implements subarray correctly', () => { expect(u8a.subarray(1, 3)).toEqual(Uint8Array.from([2, 3])); }); + + describe('utils', () => { + it('compares against other U8a', () => { + expect(u8a.eq(new Uint8Array([1, 2, 3, 4, 5]))).toBe(true); + }); + + it('compares against other U8a (non-length)', () => { + expect(u8a.eq(new Uint8Array([1, 2, 3, 4]))).toBe(false); + }); + + it('compares against other U8a (mismatch)', () => { + expect(u8a.eq(new Uint8Array([1, 2, 3, 4, 6]))).toBe(false); + }); + + it('compares against hex inputs', () => { + expect(u8a.eq('0x0102030405')).toBe(true); + }); + }); }); diff --git a/packages/types/src/codec/U8a.ts b/packages/types/src/codec/U8a.ts index 7a7160c8ae..cc62588138 100644 --- a/packages/types/src/codec/U8a.ts +++ b/packages/types/src/codec/U8a.ts @@ -52,6 +52,19 @@ export default class U8a extends Uint8Array implements Codec { return super.length; } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + if (other instanceof Uint8Array) { + return (this.length === other.length) && isUndefined( + this.find((value, index) => value !== other[index]) + ); + } + + return this.eq(U8a.decodeU8a(other)); + } + /** * @description Create a new subarray from the actual buffer. This is needed for compat reasons since a new Uint8Array gets returned here * @param begin The position to start at diff --git a/packages/types/src/codec/UInt.spec.ts b/packages/types/src/codec/UInt.spec.ts index 019accd1d5..869eb3138a 100644 --- a/packages/types/src/codec/UInt.spec.ts +++ b/packages/types/src/codec/UInt.spec.ts @@ -2,6 +2,8 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import BN from 'bn.js'; + import UInt from './UInt'; describe('UInt', () => { @@ -48,4 +50,20 @@ describe('UInt', () => { expect(new UInt(0x12, 16).toJSON()).toEqual('0x0012'); expect(new UInt(0x12, 16, false).toJSON()).toEqual(0x12); }); + + describe('eq', () => { + const test = new UInt(12345); + + it('compares against other BN values', () => { + expect(test.eq(new BN(12345))).toBe(true); + }); + + it('compares against other number values', () => { + expect(test.eq(12345)).toBe(true); + }); + + it('compares against hex values', () => { + expect(test.eq('0x3039')).toBe(true); + }); + }); }); diff --git a/packages/types/src/codec/Vector.spec.ts b/packages/types/src/codec/Vector.spec.ts index fc173e388c..66ac4ed114 100644 --- a/packages/types/src/codec/Vector.spec.ts +++ b/packages/types/src/codec/Vector.spec.ts @@ -116,4 +116,16 @@ describe('Vector', () => { testEncode('toString', '[1, 23, 345, 4567, 56789]'); testEncode('toU8a', Uint8Array.from([20, 4, 49, 8, 50, 51, 12, 51, 52, 53, 16, 52, 53, 54, 55, 20, 53, 54, 55, 56, 57])); }); + + describe('utils', () => { + const vec = new Vector(Text, ['123', '456']); + + it('compares against codec types', () => { + expect(vec.eq([new Text('123'), new Text('456')])).toBe(true); + }); + + it('compares against codec + primitive types', () => { + expect(vec.eq(['123', new Text('456')])).toBe(true); + }); + }); }); diff --git a/packages/types/src/codec/Vector.ts b/packages/types/src/codec/Vector.ts index 6af117936c..b78d6cccdc 100644 --- a/packages/types/src/codec/Vector.ts +++ b/packages/types/src/codec/Vector.ts @@ -6,12 +6,12 @@ import { u8aConcat, u8aToU8a, u8aToHex } from '@polkadot/util'; import Compact from './Compact'; import { Codec, Constructor } from '../types'; -import decodeU8a from './utils/decodeU8a'; +import { compareArray, decodeU8a } from './utils'; /** * @name Vector * @description - * This manages codec arrays. Intrernally it keeps track of the length (as decoded) and allows + * This manages codec arrays. Internally it keeps track of the length (as decoded) and allows * construction with the passed `Type` in the constructor. It is an extension to Array, providing * specific encoding/decoding on top of the base type. * @noInheritDoc @@ -75,6 +75,13 @@ export default class Vector< return super.length; } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + return compareArray(this, other); + } + /** * @description Converts the Object to an standard JavaScript Array */ diff --git a/packages/types/src/codec/utils/compareArray.spec.ts b/packages/types/src/codec/utils/compareArray.spec.ts new file mode 100644 index 0000000000..da32c9441d --- /dev/null +++ b/packages/types/src/codec/utils/compareArray.spec.ts @@ -0,0 +1,48 @@ +// Copyright 2017-2019 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import U32 from '../../U32'; +import compareArray from './compareArray'; + +describe('compareArray', () => { + const a = [new U32(123), new U32(456), new U32(789)]; + + it('returns false when second param is a non-array', () => { + expect( + compareArray(a, 123) + ).toBe(false); + }); + + it('compares array of codec agains primitive', () => { + expect( + compareArray(a, [123, 456, 789]) + ).toBe(true); + }); + + it('compares array of codec agains codec', () => { + expect( + compareArray(a, [new U32(123), new U32(456), new U32(789)]) + ).toBe(true); + }); + + it('compares primitive against primitive', () => { + expect( + compareArray( + [123, 456], [123, 456] + ) + ).toBe(true); + }); + + it('returns false when lengths are not matching', () => { + expect( + compareArray(a, [123]) + ).toBe(false); + }); + + it('returns false when value mismatches', () => { + expect( + compareArray(a, [123, 456, 999]) + ).toBe(false); + }); +}); diff --git a/packages/types/src/codec/utils/compareArray.ts b/packages/types/src/codec/utils/compareArray.ts new file mode 100644 index 0000000000..1ffb8d9974 --- /dev/null +++ b/packages/types/src/codec/utils/compareArray.ts @@ -0,0 +1,21 @@ +// Copyright 2017-2019 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { isFunction, isUndefined } from '@polkadot/util'; + +// NOTE These are used internally and when comparing objects, expects that +// when the second is an Array that the first has to be as well +export default function compareArray (a: Array, b?: any): boolean { + if (Array.isArray(b)) { + return (a.length === b.length) && isUndefined( + a.find((value, index) => + isFunction(value.eq) + ? !value.eq(b[index]) + : value !== b[index] + ) + ); + } + + return false; +} diff --git a/packages/types/src/codec/utils/compareMap.spec.ts b/packages/types/src/codec/utils/compareMap.spec.ts new file mode 100644 index 0000000000..e5d8e5f92e --- /dev/null +++ b/packages/types/src/codec/utils/compareMap.spec.ts @@ -0,0 +1,81 @@ +// Copyright 2017-2019 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { Codec } from '../../types'; +import U32 from '../../U32'; +import compareMap from './compareMap'; + +describe('compareMap', () => { + const a = new Map([ + ['decimals', new U32(15)], + ['missing', new U32(10)], + ['foobar', new U32(5)] + ]); + const b = new Map([ + ['decimals', 15], + ['missing', 10], + ['foobar', 5] + ]); + const c = new Map([ + ['decimals', new U32(15)], + ['missing', new U32(10)], + ['foobar', new U32(5)] + ]); + + it('compares Map against Object', () => { + expect( + compareMap(a, { + decimals: 15, + foobar: 5, + missing: 10 + }) + ).toBe(true); + }); + + it('compares Map against entries array', () => { + expect( + compareMap(b, [ + ['missing', 10], ['decimals', 15], ['foobar', 5] + ]) + ).toBe(true); + }); + + it('compares between 2 maps', () => { + expect(compareMap(a, b)).toBe(true); + }); + + it('compares between 2 maps (both codec)', () => { + expect(compareMap(a, c)).toBe(true); + }); + + it('returns false when second param is a non-map, non-array, non-object', () => { + expect( + compareMap(a, 123) + ).toBe(false); + }); + + it('returns false when second param is a array with non-entries', () => { + expect( + compareMap(a, [123, 456, 789]) + ).toBe(false); + }); + + it('returns false when second param is a array with non-entries (only key)', () => { + expect( + compareMap(a, [[123], [456], [789]]) + ).toBe(false); + }); + + it('returns false when properties are missing', () => { + expect( + compareMap(a, [['decimals', 15], ['wrong', 10], ['foobar', 5]]) + ).toBe(false); + }); + + it('returns false when lengths do not match', () => { + expect( + compareMap(a, [['decimals', 15]]) + ).toBe(false); + }); +}); diff --git a/packages/types/src/codec/utils/compareMap.ts b/packages/types/src/codec/utils/compareMap.ts new file mode 100644 index 0000000000..02de2876e8 --- /dev/null +++ b/packages/types/src/codec/utils/compareMap.ts @@ -0,0 +1,34 @@ +// Copyright 2017-2019 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { isFunction, isObject, isUndefined } from '@polkadot/util'; + +// NOTE These are used internally and when comparing objects, expects that +// when the second is an Map that the first has to be as well +export default function compareMap (a: Map, b?: any): boolean { + if (Array.isArray(b)) { + return (a.size === b.length) && isUndefined( + b.find((entry) => { + if (!Array.isArray(entry) || entry.length !== 2) { + return true; + } + + const value = a.get(entry[0]); + + return isUndefined(value) || ( + // Codec has .eq, use it here + isFunction(value.eq) + ? !value.eq(entry[1]) + : value !== entry[1] + ); + }) + ); + } else if (b instanceof Map) { + return compareMap(a, [...b.entries()]); + } else if (isObject(b)) { + return compareMap(a, Object.entries(b)); + } + + return false; +} diff --git a/packages/types/src/codec/utils/index.ts b/packages/types/src/codec/utils/index.ts new file mode 100644 index 0000000000..94fe457179 --- /dev/null +++ b/packages/types/src/codec/utils/index.ts @@ -0,0 +1,7 @@ +// Copyright 2017-2019 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +export { default as compareArray } from './compareArray'; +export { default as compareMap } from './compareMap'; +export { default as decodeU8a } from './decodeU8a'; diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index 0119af4bcb..40365c502f 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -23,6 +23,11 @@ export interface Codec { */ encodedLength: number; + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean; + /** * @description Returns a hex string representation of the value */