-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from algorandfoundation/feat-utils
feat: implement stubs for urange, assertMatch and match functions
- Loading branch information
Showing
7 changed files
with
280 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { assert, assertMatch, internal, match } from '@algorandfoundation/algorand-typescript' | ||
import { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4' | ||
import { DeliberateAny } from '../typescript-helpers' | ||
import { asBytes, asMaybeBigUintCls } from '../util' | ||
import { BytesBackedCls, Uint64BackedCls } from './base' | ||
|
||
export const matchImpl: typeof match = (subject, test): boolean => { | ||
const bigIntSubjectValue = getBigIntValue(subject) | ||
if (bigIntSubjectValue !== undefined) { | ||
const bigIntTestValue = getBigIntValue(test) | ||
if (bigIntTestValue !== undefined) { | ||
return bigIntSubjectValue === bigIntTestValue | ||
} else if (Object.hasOwn(test, 'lessThan')) { | ||
return bigIntSubjectValue < getBigIntValue((test as DeliberateAny).lessThan)! | ||
} else if (Object.hasOwn(test, 'greaterThan')) { | ||
return bigIntSubjectValue > getBigIntValue((test as DeliberateAny).greaterThan)! | ||
} else if (Object.hasOwn(test, 'lessThanEq')) { | ||
return bigIntSubjectValue <= getBigIntValue((test as DeliberateAny).lessThanEq)! | ||
} else if (Object.hasOwn(test, 'greaterThanEq')) { | ||
return bigIntSubjectValue >= getBigIntValue((test as DeliberateAny).greaterThanEq)! | ||
} else if (Object.hasOwn(test, 'between')) { | ||
const [start, end] = (test as DeliberateAny).between | ||
return bigIntSubjectValue >= getBigIntValue(start)! && bigIntSubjectValue <= getBigIntValue(end)! | ||
} | ||
} else if (subject instanceof internal.primitives.BytesCls) { | ||
return subject.equals(asBytes(test as unknown as internal.primitives.StubBytesCompat)) | ||
} else if (typeof subject === 'string') { | ||
return subject === test | ||
} else if (subject instanceof BytesBackedCls) { | ||
return subject.bytes.equals((test as unknown as BytesBackedCls).bytes) | ||
} else if (subject instanceof Uint64BackedCls) { | ||
return ( | ||
getBigIntValue(subject.uint64 as unknown as internal.primitives.Uint64Cls) === | ||
getBigIntValue((test as unknown as Uint64BackedCls).uint64 as unknown as internal.primitives.Uint64Cls) | ||
) | ||
} else if (subject instanceof ARC4Encoded) { | ||
return subject.bytes.equals((test as unknown as ARC4Encoded).bytes) | ||
} else if (Array.isArray(subject)) { | ||
return (test as []).map((x, i) => matchImpl((subject as DeliberateAny)[i], x as DeliberateAny)).every((x) => x) | ||
} else if (typeof subject === 'object') { | ||
return Object.entries(test!) | ||
.map(([k, v]) => matchImpl((subject as DeliberateAny)[k], v as DeliberateAny)) | ||
.every((x) => x) | ||
} | ||
return false | ||
} | ||
|
||
export const assertMatchImpl: typeof assertMatch = (subject, test, message): boolean => { | ||
const isMatching = matchImpl(subject, test) | ||
assert(isMatching, message) | ||
return isMatching | ||
} | ||
|
||
const getBigIntValue = (x: unknown) => { | ||
return asMaybeBigUintCls(x)?.asBigInt() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { internal } from '@algorandfoundation/algorand-typescript' | ||
import { asBigInt, asUint64 } from '../util' | ||
|
||
export function* urangeImpl( | ||
a: internal.primitives.StubUint64Compat, | ||
b?: internal.primitives.StubUint64Compat, | ||
c?: internal.primitives.StubUint64Compat, | ||
) { | ||
const start = b ? asBigInt(a) : BigInt(0) | ||
const end = b ? asBigInt(b) : asBigInt(a) | ||
const step = c ? asBigInt(c) : BigInt(1) | ||
let iterationCount = 0 | ||
for (let i = start; i < end; i += step) { | ||
iterationCount++ | ||
yield asUint64(i) | ||
} | ||
return iterationCount | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { assertMatch, biguint, BigUint, Bytes, match, Uint64 } from '@algorandfoundation/algorand-typescript' | ||
import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' | ||
import { afterEach, describe, expect, test } from 'vitest' | ||
import { MAX_UINT512, MAX_UINT64 } from '../src/constants' | ||
import { StrImpl } from '../src/impl/encoded-types' | ||
describe('match', () => { | ||
const ctx = new TestExecutionContext() | ||
|
||
afterEach(async () => { | ||
ctx.reset() | ||
}) | ||
|
||
const numericTestData = [ | ||
{ subject: 1, test: 1, expected: true }, | ||
{ subject: 0, test: 1, expected: false }, | ||
{ subject: 1, test: 0, expected: false }, | ||
{ subject: 1, test: Uint64(1), expected: true }, | ||
{ subject: Uint64(1), test: Uint64(1), expected: true }, | ||
{ subject: Uint64(1), test: 1, expected: true }, | ||
{ subject: 42, test: MAX_UINT64, expected: false }, | ||
{ subject: Uint64(MAX_UINT64), test: Uint64(42), expected: false }, | ||
{ subject: BigUint(1), test: 1n, expected: true }, | ||
{ subject: 1n, test: BigUint(1), expected: true }, | ||
{ subject: BigUint(1), test: BigUint(1), expected: true }, | ||
{ subject: 42n, test: MAX_UINT512, expected: false }, | ||
{ subject: BigUint(MAX_UINT512), test: BigUint(42n), expected: false }, | ||
{ subject: { a: BigUint(MAX_UINT512) }, test: { a: { lessThan: MAX_UINT512 } }, expected: false }, | ||
{ subject: { a: BigUint(MAX_UINT512) }, test: { a: { lessThanEq: MAX_UINT64 } }, expected: false }, | ||
{ subject: { a: MAX_UINT64 }, test: { a: { lessThan: BigUint(MAX_UINT512) } }, expected: true }, | ||
{ subject: { a: MAX_UINT512 }, test: { a: { lessThanEq: BigUint(MAX_UINT512) } }, expected: true }, | ||
{ subject: { a: BigUint(MAX_UINT512) }, test: { a: { greaterThan: MAX_UINT512 } }, expected: false }, | ||
{ subject: { a: BigUint(MAX_UINT64) }, test: { a: { greaterThanEq: MAX_UINT512 } }, expected: false }, | ||
{ subject: { a: MAX_UINT512 }, test: { a: { greaterThan: BigUint(MAX_UINT64) } }, expected: true }, | ||
{ subject: { a: MAX_UINT512 }, test: { a: { greaterThanEq: BigUint(MAX_UINT512) } }, expected: true }, | ||
{ | ||
subject: { a: MAX_UINT512 }, | ||
test: { a: { between: [BigUint(MAX_UINT64), BigUint(MAX_UINT512)] as [biguint, biguint] } }, | ||
expected: true, | ||
}, | ||
{ | ||
subject: { a: MAX_UINT64 }, | ||
test: { a: { between: [BigUint(MAX_UINT64), BigUint(MAX_UINT512)] as [biguint, biguint] } }, | ||
expected: true, | ||
}, | ||
{ subject: { a: 42 }, test: { a: { between: [BigUint(MAX_UINT64), BigUint(MAX_UINT512)] as [biguint, biguint] } }, expected: false }, | ||
] | ||
|
||
const account1 = ctx.any.account() | ||
const sameAccount = ctx.ledger.getAccount(account1) | ||
const differentAccount = ctx.any.account() | ||
|
||
const app1 = ctx.any.application() | ||
const sameApp = ctx.ledger.getApplication(app1.id) | ||
const differentApp = ctx.any.application() | ||
|
||
const asset1 = ctx.any.asset() | ||
const sameAsset = ctx.ledger.getAsset(asset1.id) | ||
const differentAsset = ctx.any.application() | ||
|
||
const arc4Str1 = ctx.any.arc4.str(10) | ||
const sameArc4Str = new StrImpl((arc4Str1 as StrImpl).typeInfo, arc4Str1.native) | ||
const differentArc4Str = ctx.any.arc4.str(10) | ||
|
||
const testData = [ | ||
{ subject: '', test: '', expected: true }, | ||
{ subject: 'hello', test: 'hello', expected: true }, | ||
{ subject: 'hello', test: 'world', expected: false }, | ||
{ subject: '', test: 'world', expected: false }, | ||
{ subject: Bytes(), test: Bytes(), expected: true }, | ||
{ subject: Bytes('hello'), test: Bytes('hello'), expected: true }, | ||
{ subject: Bytes('hello'), test: Bytes('world'), expected: false }, | ||
{ subject: Bytes(''), test: Bytes('world'), expected: false }, | ||
{ subject: account1, test: account1, expected: true }, | ||
{ subject: account1, test: sameAccount, expected: true }, | ||
{ subject: account1, test: differentAccount, expected: false }, | ||
{ subject: app1, test: app1, expected: true }, | ||
{ subject: app1, test: sameApp, expected: true }, | ||
{ subject: app1, test: differentApp, expected: false }, | ||
{ subject: asset1, test: asset1, expected: true }, | ||
{ subject: asset1, test: sameAsset, expected: true }, | ||
{ subject: asset1, test: differentAsset, expected: false }, | ||
{ subject: arc4Str1, test: arc4Str1, expected: true }, | ||
{ subject: arc4Str1, test: sameArc4Str, expected: true }, | ||
{ subject: arc4Str1, test: differentArc4Str, expected: false }, | ||
{ subject: { a: 'hello', b: 42, c: arc4Str1 }, test: { a: 'hello', b: { lessThanEq: 42 }, c: sameArc4Str }, expected: true }, | ||
{ subject: { a: 'hello', b: 42, c: arc4Str1 }, test: { c: sameArc4Str }, expected: true }, | ||
{ subject: { a: 'hello', b: 42, c: arc4Str1 }, test: { c: differentArc4Str }, expected: false }, | ||
{ subject: ['hello', 42, arc4Str1], test: ['hello', { lessThanEq: 42 }, sameArc4Str], expected: true }, | ||
{ subject: ['hello', 42, arc4Str1], test: ['hello'], expected: true }, | ||
{ subject: ['hello', 42, arc4Str1], test: ['world'], expected: false }, | ||
] | ||
|
||
test.each(numericTestData)('should be able to match numeric data %s', (data) => { | ||
const { subject, test, expected } = data | ||
expect(match(subject, test)).toBe(expected) | ||
}) | ||
|
||
test.each(testData)('should be able to match %s', (data) => { | ||
const { subject, test, expected } = data | ||
expect(match(subject, test)).toBe(expected) | ||
}) | ||
|
||
test.each(numericTestData.filter((x) => x.expected))('should be able to assert match numeric data %s', (data) => { | ||
const { subject, test, expected } = data | ||
expect(assertMatch(subject, test)).toBe(expected) | ||
}) | ||
|
||
test.each(testData.filter((x) => x.expected))('should be able to assert match %s', (data) => { | ||
const { subject, test, expected } = data | ||
expect(match(subject, test)).toBe(expected) | ||
}) | ||
|
||
test.each(numericTestData.filter((x) => !x.expected))('should throw exception when assert match fails for numeric data %s', (data) => { | ||
const { subject, test } = data | ||
expect(() => assertMatch(subject, test)).toThrow('Assertion failed') | ||
}) | ||
|
||
test.each(testData.filter((x) => !x.expected))('should throw exception when assert match fails %s', (data) => { | ||
const { subject, test } = data | ||
expect(() => assertMatch(subject, test)).toThrow('Assertion failed') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { Uint64, urange } from '@algorandfoundation/algorand-typescript' | ||
import { describe, expect, it } from 'vitest' | ||
|
||
describe('urange', () => { | ||
it('should iterate from 0 to a-1 when only a is provided', () => { | ||
const a = Uint64(5) | ||
const iterator = urange(a) | ||
const result = [] | ||
|
||
for (let item = iterator.next(); !item.done; item = iterator.next()) { | ||
result.push(item.value) | ||
} | ||
|
||
expect(result).toEqual([BigInt(0), BigInt(1), BigInt(2), BigInt(3), BigInt(4)]) | ||
}) | ||
|
||
it('should iterate from a to b-1 when a and b are provided', () => { | ||
const a = Uint64(2) | ||
const b = Uint64(5) | ||
const iterator = urange(a, b) | ||
const result = [] | ||
|
||
for (let item = iterator.next(); !item.done; item = iterator.next()) { | ||
result.push(item.value) | ||
} | ||
|
||
expect(result).toEqual([BigInt(2), BigInt(3), BigInt(4)]) | ||
}) | ||
|
||
it('should iterate from a to b-1 with step c when a, b, and c are provided', () => { | ||
const a = Uint64(2) | ||
const b = Uint64(10) | ||
const c = Uint64(2) | ||
const iterator = urange(a, b, c) | ||
const result = [] | ||
|
||
for (let item = iterator.next(); !item.done; item = iterator.next()) { | ||
result.push(item.value) | ||
} | ||
|
||
expect(result).toEqual([BigInt(2), BigInt(4), BigInt(6), BigInt(8)]) | ||
}) | ||
|
||
it('should return iteration count when done', () => { | ||
const a = Uint64(3) | ||
const iterator = urange(a) | ||
let item = iterator.next() | ||
let count = 0 | ||
|
||
while (!item.done) { | ||
count++ | ||
item = iterator.next() | ||
} | ||
|
||
expect(item.value).toBe(count) | ||
}) | ||
}) |