Skip to content

Commit

Permalink
Add base visitor tests for each node (#137)
Browse files Browse the repository at this point in the history
* Create AccountNode.test.ts

* Add test macros

* Create DefinedTypeNode.test.ts

* Add ErrorNode tests

* Prepare all tests by copy pasting the account node tests

* Update InstructionAccountNode.test.ts

* Update instructionArgumentNode test

* Add InstructionByteDeltaNode tests

* Add instructionRemainingAccountsNode tests

* Update PdaNode.test.ts

* Add InstructionNode tests

* Update ProgramNode.test.ts

* Update RootNode.test.ts

* Add tests for contextualValueNodes

* Add discriminatorNodes tests

* Add linkNodes tests

* Add pdaSeedNodes tests

* Add sizeNodes tests

* Add typeNodes tests

* Add valueNodes tests

* Add changeset
  • Loading branch information
lorisleiva authored Jan 4, 2024
1 parent 6d02b0e commit d1a2149
Show file tree
Hide file tree
Showing 74 changed files with 2,158 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/few-meals-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@metaplex-foundation/kinobi': patch
---

Add base visitor tests for each node
7 changes: 6 additions & 1 deletion src/nodes/contextualValueNodes/ResolverValueNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,10 @@ export function resolverValueNode(
dependsOn?: ResolverValueNode['dependsOn'];
} = {}
): ResolverValueNode {
return { kind: 'resolverValueNode', name: mainCase(name), ...options };
return {
kind: 'resolverValueNode',
name: mainCase(name),
importFrom: options.importFrom,
dependsOn: options.dependsOn,
};
}
1 change: 1 addition & 0 deletions src/nodes/valueNodes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './BooleanValueNode';
export * from './EnumValueNode';
export * from './MapEntryValueNode';
export * from './MapValueNode';
export * from './NoneValueNode';
export * from './NumberValueNode';
export * from './PublicKeyValueNode';
export * from './SetValueNode';
Expand Down
47 changes: 45 additions & 2 deletions src/visitors/getDebugStringVisitor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { pipe } from '../shared';
import { Node } from '../nodes';
import { pipe } from '../shared';
import { interceptVisitor } from './interceptVisitor';
import { mergeVisitor } from './mergeVisitor';
import { Visitor } from './visitor';
Expand Down Expand Up @@ -59,6 +59,17 @@ function getNodeDetails(node: Node): string[] {
...(node.isSigner === 'either' ? ['optionalSigner'] : []),
...(node.isOptional ? ['optional'] : []),
];
case 'instructionRemainingAccountsNode':
return [
...(node.isWritable ? ['writable'] : []),
...(node.isSigner === true ? ['signer'] : []),
...(node.isSigner === 'either' ? ['optionalSigner'] : []),
];
case 'instructionByteDeltaNode':
return [
...(node.subtract ? ['subtract'] : []),
...(node.withHeader ? ['withHeader'] : []),
];
case 'errorNode':
return [node.code.toString(), node.name];
case 'programLinkNode':
Expand All @@ -70,13 +81,45 @@ function getNodeDetails(node: Node): string[] {
...(node.importFrom ? [`from:${node.importFrom}`] : []),
];
case 'numberTypeNode':
return [node.format, ...(node.endian === 'be' ? ['be'] : [])];
return [node.format, ...(node.endian === 'be' ? ['bigEndian'] : [])];
case 'amountTypeNode':
return [node.decimals.toString(), ...(node.unit ? [node.unit] : [])];
case 'stringTypeNode':
return [node.encoding];
case 'optionTypeNode':
return node.fixed ? ['fixed'] : [];
case 'fixedSizeNode':
return [node.size.toString()];
case 'numberValueNode':
return [node.number.toString()];
case 'stringValueNode':
return [node.string];
case 'booleanValueNode':
return [node.boolean ? 'true' : 'false'];
case 'publicKeyValueNode':
return [node.publicKey];
case 'enumValueNode':
return [node.variant];
case 'resolverValueNode':
return [
node.name,
...(node.importFrom ? [`from:${node.importFrom}`] : []),
];
case 'byteDiscriminatorNode':
return [
...(node.bytes.length > 0
? [
`0x${node.bytes
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('')}`,
]
: []),
...(node.offset > 0 ? [`offset:${node.offset}`] : []),
];
case 'fieldDiscriminatorNode':
return [node.name, ...(node.offset > 0 ? [`offset:${node.offset}`] : [])];
case 'sizeDiscriminatorNode':
return [node.size.toString()];
default:
return 'name' in node ? [node.name] : [];
}
Expand Down
34 changes: 22 additions & 12 deletions src/visitors/identityVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
constantPdaSeedNode,
dateTimeTypeNode,
definedTypeNode,
enumEmptyVariantTypeNode,
enumStructVariantTypeNode,
enumTupleVariantTypeNode,
enumTypeNode,
Expand Down Expand Up @@ -261,8 +262,13 @@ export function identityVisitor<TNodeKind extends NodeKind = NodeKind>(
node
) {
const newStruct = visit(this)(node.struct);
if (!newStruct) return null;
if (!newStruct) {
return enumEmptyVariantTypeNode(node.name);
}
assertIsNode(newStruct, 'structTypeNode');
if (newStruct.fields.length === 0) {
return enumEmptyVariantTypeNode(node.name);
}
return enumStructVariantTypeNode(node.name, newStruct);
};
}
Expand All @@ -272,8 +278,13 @@ export function identityVisitor<TNodeKind extends NodeKind = NodeKind>(
node
) {
const newTuple = visit(this)(node.tuple);
if (!newTuple) return null;
if (!newTuple) {
return enumEmptyVariantTypeNode(node.name);
}
assertIsNode(newTuple, 'tupleTypeNode');
if (newTuple.items.length === 0) {
return enumEmptyVariantTypeNode(node.name);
}
return enumTupleVariantTypeNode(node.name, newTuple);
};
}
Expand Down Expand Up @@ -328,11 +339,10 @@ export function identityVisitor<TNodeKind extends NodeKind = NodeKind>(

if (castedNodeKeys.includes('structTypeNode')) {
visitor.visitStructType = function visitStructType(node) {
return structTypeNode(
node.fields
.map(visit(this))
.filter(removeNullAndAssertIsNodeFilter('structFieldTypeNode'))
);
const fields = node.fields
.map(visit(this))
.filter(removeNullAndAssertIsNodeFilter('structFieldTypeNode'));
return structTypeNode(fields);
};
}

Expand All @@ -351,11 +361,10 @@ export function identityVisitor<TNodeKind extends NodeKind = NodeKind>(

if (castedNodeKeys.includes('tupleTypeNode')) {
visitor.visitTupleType = function visitTupleType(node) {
return tupleTypeNode(
node.items
.map(visit(this))
.filter(removeNullAndAssertIsNodeFilter(TYPE_NODES))
);
const items = node.items
.map(visit(this))
.filter(removeNullAndAssertIsNodeFilter(TYPE_NODES));
return tupleTypeNode(items);
};
}

Expand Down Expand Up @@ -566,6 +575,7 @@ export function identityVisitor<TNodeKind extends NodeKind = NodeKind>(
? visit(this)(node.ifFalse) ?? undefined
: undefined;
if (ifFalse) assertIsNode(ifFalse, CONDITIONAL_VALUE_BRANCH_NODES);
if (!ifTrue && !ifFalse) return null;
return conditionalValueNode({ condition, value, ifTrue, ifFalse });
};
}
Expand Down
8 changes: 8 additions & 0 deletions src/visitors/mergeVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ export function mergeVisitor<TReturn, TNodeKind extends NodeKind = NodeKind>(
};
}

if (castedNodeKeys.includes('instructionByteDeltaNode')) {
visitor.visitInstructionByteDelta = function visitInstructionByteDelta(
node
) {
return merge(node, visit(this)(node.value));
};
}

if (castedNodeKeys.includes('definedTypeNode')) {
visitor.visitDefinedType = function visitDefinedType(node) {
return merge(node, visit(this)(node.type));
Expand Down
51 changes: 51 additions & 0 deletions test/visitors/nodes/AccountNode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import test from 'ava';
import {
accountNode,
numberTypeNode,
pdaLinkNode,
publicKeyTypeNode,
sizeDiscriminatorNode,
structFieldTypeNode,
structTypeNode,
} from '../../../src';
import {
deleteNodesVisitorMacro,
getDebugStringVisitorMacro,
identityVisitorMacro,
mergeVisitorMacro,
} from './_setup';

const node = accountNode({
name: 'token',
data: structTypeNode([
structFieldTypeNode({ name: 'mint', type: publicKeyTypeNode() }),
structFieldTypeNode({ name: 'owner', type: publicKeyTypeNode() }),
structFieldTypeNode({ name: 'amount', type: numberTypeNode('u64') }),
]),
pda: pdaLinkNode('associatedToken'),
discriminators: [sizeDiscriminatorNode(72)],
size: 72,
});

test(mergeVisitorMacro, node, 10);
test(identityVisitorMacro, node);
test(deleteNodesVisitorMacro, node, '[accountNode]', null);
test(deleteNodesVisitorMacro, node, '[pdaLinkNode]', {
...node,
pda: undefined,
});
test(
getDebugStringVisitorMacro,
node,
`
accountNode [token]
| structTypeNode
| | structFieldTypeNode [mint]
| | | publicKeyTypeNode
| | structFieldTypeNode [owner]
| | | publicKeyTypeNode
| | structFieldTypeNode [amount]
| | | numberTypeNode [u64]
| pdaLinkNode [associatedToken]
| sizeDiscriminatorNode [72]`
);
43 changes: 43 additions & 0 deletions test/visitors/nodes/DefinedTypeNode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import test from 'ava';
import {
definedTypeNode,
fixedSizeNode,
numberTypeNode,
stringTypeNode,
structFieldTypeNode,
structTypeNode,
} from '../../../src';
import {
deleteNodesVisitorMacro,
getDebugStringVisitorMacro,
identityVisitorMacro,
mergeVisitorMacro,
} from './_setup';

const node = definedTypeNode({
name: 'person',
type: structTypeNode([
structFieldTypeNode({
name: 'name',
type: stringTypeNode({ size: fixedSizeNode(32) }),
}),
structFieldTypeNode({ name: 'age', type: numberTypeNode('u64') }),
]),
});

test(mergeVisitorMacro, node, 7);
test(identityVisitorMacro, node);
test(deleteNodesVisitorMacro, node, '[definedTypeNode]', null);
test(deleteNodesVisitorMacro, node, '[structTypeNode]', null);
test(
getDebugStringVisitorMacro,
node,
`
definedTypeNode [person]
| structTypeNode
| | structFieldTypeNode [name]
| | | stringTypeNode [utf8]
| | | | fixedSizeNode [32]
| | structFieldTypeNode [age]
| | | numberTypeNode [u64]`
);
20 changes: 20 additions & 0 deletions test/visitors/nodes/ErrorNode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import test from 'ava';
import { errorNode } from '../../../src';
import {
deleteNodesVisitorMacro,
getDebugStringVisitorMacro,
identityVisitorMacro,
mergeVisitorMacro,
} from './_setup';

const node = errorNode({
name: 'InvalidTokenOwner',
code: 42,
message:
'The provided account does not match the owner of the token account.',
});

test(mergeVisitorMacro, node, 1);
test(identityVisitorMacro, node);
test(deleteNodesVisitorMacro, node, '[errorNode]', null);
test(getDebugStringVisitorMacro, node, `errorNode [42.invalidTokenOwner]`);
31 changes: 31 additions & 0 deletions test/visitors/nodes/InstructionAccountNode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import test from 'ava';
import { accountValueNode, instructionAccountNode } from '../../../src';
import {
deleteNodesVisitorMacro,
getDebugStringVisitorMacro,
identityVisitorMacro,
mergeVisitorMacro,
} from './_setup';

const node = instructionAccountNode({
name: 'owner',
isWritable: true,
isSigner: 'either',
isOptional: false,
defaultValue: accountValueNode('authority'),
});

test(mergeVisitorMacro, node, 2);
test(identityVisitorMacro, node);
test(deleteNodesVisitorMacro, node, '[instructionAccountNode]', null);
test(deleteNodesVisitorMacro, node, '[accountValueNode]', {
...node,
defaultValue: undefined,
});
test(
getDebugStringVisitorMacro,
node,
`
instructionAccountNode [owner.writable.optionalSigner]
| accountValueNode [authority]`
);
35 changes: 35 additions & 0 deletions test/visitors/nodes/InstructionArgumentNode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import test from 'ava';
import {
instructionArgumentNode,
numberTypeNode,
numberValueNode,
} from '../../../src';
import {
deleteNodesVisitorMacro,
getDebugStringVisitorMacro,
identityVisitorMacro,
mergeVisitorMacro,
} from './_setup';

const node = instructionArgumentNode({
name: 'amount',
type: numberTypeNode('u64'),
defaultValue: numberValueNode(1),
});

test(mergeVisitorMacro, node, 3);
test(identityVisitorMacro, node);
test(deleteNodesVisitorMacro, node, '[instructionArgumentNode]', null);
test(deleteNodesVisitorMacro, node, '[numberTypeNode]', null);
test(deleteNodesVisitorMacro, node, '[numberValueNode]', {
...node,
defaultValue: undefined,
});
test(
getDebugStringVisitorMacro,
node,
`
instructionArgumentNode [amount]
| numberTypeNode [u64]
| numberValueNode [1]`
);
Loading

0 comments on commit d1a2149

Please sign in to comment.