Skip to content

Commit 4fb5d95

Browse files
committed
Add tests and fix for JS renderer
1 parent d4736da commit 4fb5d95

File tree

3 files changed

+108
-3
lines changed

3 files changed

+108
-3
lines changed

packages/renderers-js/public/templates/fragments/instructionFunction.njk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{{ inputTypeFragment }}
22

3-
export {{ 'async' if useAsync }} function {{ functionName }}{{ typeParamsFragment }}(input: {{ inputTypeCallFragment }}, config?: { programAddress?: TProgramAddress } ): {{ getReturnType(instructionTypeFragment) }} {
3+
export {{ 'async' if useAsync }} function {{ functionName }}{{ typeParamsFragment }}({% if hasInput %}input: {{ inputTypeCallFragment }}, {% endif %}config?: { programAddress?: TProgramAddress } ): {{ getReturnType(instructionTypeFragment) }} {
44
// Program address.
55
const programAddress = config?.programAddress ?? {{ programAddressConstant }};
66

packages/renderers-js/src/fragments/instructionFunction.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export function getInstructionFunctionFragment(
7272
const hasRemainingAccountArgs =
7373
(instructionNode.remainingAccounts ?? []).filter(({ value }) => isNode(value, 'argumentValueNode')).length > 0;
7474
const hasAnyArgs = hasDataArgs || hasExtraArgs || hasRemainingAccountArgs;
75+
const hasInput = hasAccounts || hasAnyArgs;
7576
const instructionDataName = nameApi.instructionDataType(instructionNode.name);
7677
const programAddressConstant = nameApi.programAddressConstant(programNode.name);
7778
const encoderFunction = customData
@@ -125,6 +126,7 @@ export function getInstructionFunctionFragment(
125126
hasData,
126127
hasDataArgs,
127128
hasExtraArgs,
129+
hasInput,
128130
hasLegacyOptionalAccounts,
129131
hasRemainingAccounts,
130132
hasResolver,

packages/renderers-js/test/instructionsPage.test.ts

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ import { visit } from '@codama/visitors-core';
2222
import { test } from 'vitest';
2323

2424
import { getRenderMapVisitor } from '../src';
25-
import { codeContains, codeDoesNotContain, renderMapContains, renderMapContainsImports } from './_setup';
25+
import {
26+
codeContains,
27+
codeDoesNotContain,
28+
renderMapContains,
29+
renderMapContainsImports,
30+
renderMapDoesNotContain,
31+
} from './_setup';
2632

2733
test('it renders instruction accounts that can either be signer or non-signer', async () => {
2834
// Given the following instruction with a signer or non-signer account.
@@ -417,7 +423,104 @@ test('it renders optional config that can override the program address', async (
417423
// Then we expect an optional config parameter with an optional programAddress field
418424
// And we expect this to be used to override programAddress if it is set
419425
await renderMapContains(renderMap, 'instructions/myInstruction.ts', [
420-
'config?: { programAddress?: TProgramAddress }',
426+
'config?: { programAddress?: TProgramAddress; }',
421427
'programAddress = config?.programAddress ?? MY_PROGRAM_PROGRAM_ADDRESS',
422428
]);
423429
});
430+
431+
test('it renders instructions with no accounts and no data', async () => {
432+
// Given the following instruction with no accounts and no arguments.
433+
const node = programNode({
434+
instructions: [instructionNode({ name: 'myInstruction' })],
435+
name: 'myProgram',
436+
publicKey: '1111',
437+
});
438+
439+
// When we render it.
440+
const renderMap = visit(node, getRenderMapVisitor());
441+
442+
// Then the instruction input type is generated as an empty object.
443+
await renderMapContains(renderMap, 'instructions/myInstruction.ts', ['export type MyInstructionInput = {};']);
444+
445+
// But the instruction function does not use it as an argument.
446+
await renderMapDoesNotContain(renderMap, 'instructions/myInstruction.ts', ['input: MyInstructionInput']);
447+
});
448+
449+
test('it renders instructions with no accounts but with some omitted data', async () => {
450+
// Given the following instruction with no accounts but with a discriminator argument.
451+
const node = programNode({
452+
instructions: [
453+
instructionNode({
454+
arguments: [
455+
instructionArgumentNode({
456+
defaultValue: numberValueNode(42),
457+
defaultValueStrategy: 'omitted',
458+
name: 'myDiscriminator',
459+
type: numberTypeNode('u32'),
460+
}),
461+
],
462+
discriminators: [fieldDiscriminatorNode('myDiscriminator')],
463+
name: 'myInstruction',
464+
}),
465+
],
466+
name: 'myProgram',
467+
publicKey: '1111',
468+
});
469+
470+
// When we render it.
471+
const renderMap = visit(node, getRenderMapVisitor());
472+
473+
// Then the instruction input type is generated as an empty object.
474+
await renderMapContains(renderMap, 'instructions/myInstruction.ts', ['export type MyInstructionInput = {};']);
475+
476+
// But the instruction function does not use it as an argument.
477+
await renderMapDoesNotContain(renderMap, 'instructions/myInstruction.ts', ['input: MyInstructionInput']);
478+
});
479+
480+
test('it renders instructions with no accounts but with some arguments', async () => {
481+
// Given the following instruction with no accounts but with a non-omitted argument.
482+
const node = programNode({
483+
instructions: [
484+
instructionNode({
485+
arguments: [instructionArgumentNode({ name: 'myArgument', type: numberTypeNode('u32') })],
486+
name: 'myInstruction',
487+
}),
488+
],
489+
name: 'myProgram',
490+
publicKey: '1111',
491+
});
492+
493+
// When we render it.
494+
const renderMap = visit(node, getRenderMapVisitor());
495+
496+
// Then we expect the following input type to be rendered
497+
// and used as an argument of the instruction function.
498+
await renderMapContains(renderMap, 'instructions/myInstruction.ts', [
499+
"export type MyInstructionInput = { myArgument: MyInstructionInstructionDataArgs['myArgument']; };",
500+
'input: MyInstructionInput',
501+
]);
502+
});
503+
504+
test('it renders instructions with no arguments but with some accounts', async () => {
505+
// Given the following instruction with no arguments but with an account.
506+
const node = programNode({
507+
instructions: [
508+
instructionNode({
509+
accounts: [instructionAccountNode({ isSigner: false, isWritable: false, name: 'myAccount' })],
510+
name: 'myInstruction',
511+
}),
512+
],
513+
name: 'myProgram',
514+
publicKey: '1111',
515+
});
516+
517+
// When we render it.
518+
const renderMap = visit(node, getRenderMapVisitor());
519+
520+
// Then we expect the following input type to be rendered
521+
// and used as an argument of the instruction function.
522+
await renderMapContains(renderMap, 'instructions/myInstruction.ts', [
523+
'export type MyInstructionInput <TAccountMyAccount extends string = string> = { myAccount: Address<TAccountMyAccount>; };',
524+
'input: MyInstructionInput<TAccountMyAccount>',
525+
]);
526+
});

0 commit comments

Comments
 (0)