Skip to content

Commit

Permalink
Add accountsPage test for empty seed PDAs in js-experimental (#158)
Browse files Browse the repository at this point in the history
* Refactor code normalization in js tests

* Add accountsPage test for empty seed PDAs
  • Loading branch information
lorisleiva authored Jan 31, 2024
1 parent b974da0 commit 24cb013
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 94 deletions.
100 changes: 37 additions & 63 deletions test/renderers/js-experimental/_setup.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { ExecutionContext } from 'ava';
import chalk from 'chalk';
import { format, type Options as PrettierOptions } from 'prettier';
import type { RenderMap } from '../../../src';

const PRETTIER_OPTIONS: PrettierOptions = {
semi: true,
singleQuote: true,
trailingComma: 'es5',
trailingComma: 'none',
useTabs: false,
tabWidth: 2,
arrowParens: 'always',
Expand All @@ -17,20 +18,33 @@ export function renderMapContains(
t: ExecutionContext,
renderMap: RenderMap,
key: string,
expected: string[] | string | RegExp[] | RegExp
expected: (string | RegExp)[] | string | RegExp
) {
t.true(renderMap.has(key), `RenderMap is missing key "${key}".`);
return codeContains(t, renderMap.get(key), expected);
}

export function renderMapContainsAny(
export function codeContains(
t: ExecutionContext,
renderMap: RenderMap,
key: string,
expected: string[] | RegExp[]
actual: string,
expected: (string | RegExp)[] | string | RegExp
) {
t.true(renderMap.has(key), `RenderMap is missing key "${key}".`);
return codeContainsAny(t, renderMap.get(key), expected);
const expectedArray = Array.isArray(expected) ? expected : [expected];
const normalizedActual = normalizeCode(actual);
expectedArray.forEach((e) => {
if (typeof e === 'string') {
const normalizeExpected = normalizeCode(e);
t.true(
normalizedActual.includes(normalizeExpected),
`The following expected code is missing from the actual content:\n\n` +
`${chalk.blue(normalizeExpected)}\n\n` +
`Actual content:\n\n` +
`${chalk.blue(normalizedActual)}`
);
} else {
t.regex(normalizedActual, e);
}
});
}

export function renderMapContainsImports(
Expand All @@ -43,73 +57,33 @@ export function renderMapContainsImports(
return codeContainsImports(t, renderMap.get(key), expectedImports);
}

export function codeContains(
t: ExecutionContext,
actual: string,
expected: string[] | string | RegExp[] | RegExp
) {
const expectedArray = Array.isArray(expected) ? expected : [expected];
const formattedActual = format(actual, PRETTIER_OPTIONS);
const formattedExpected = expectedArray.map((e) =>
typeof e === 'string' ? format(e, PRETTIER_OPTIONS) : e
);
formattedExpected.forEach((e) => {
t.true(
typeof e === 'string'
? formattedActual.includes(e)
: e.test(formattedActual),
`The following expected code is missing from the actual content:\n` +
`${e}\n\n` +
`Actual content:\n` +
`${actual}`
);
});
}

export function codeContainsAny(
t: ExecutionContext,
actual: string,
expected: string[] | RegExp[]
) {
const formattedActual = format(actual, PRETTIER_OPTIONS);
const formattedExpected = expected.map((e) =>
typeof e === 'string' ? format(e, PRETTIER_OPTIONS) : e
);
const found = formattedExpected.some((e) =>
typeof e === 'string'
? formattedActual.includes(e)
: e.test(formattedActual)
);
t.true(
found,
`None of the following expected code pieces are present in the actual content:\n` +
`${formattedExpected.join('\n')}\n\n` +
`Actual content:\n` +
`${actual}`
);
}

export function codeContainsImports(
t: ExecutionContext,
actual: string,
expectedImports: Record<string, string[]>
) {
const formattedActual = format(actual, PRETTIER_OPTIONS);
const normalizedActual = normalizeCode(actual);
const importPairs = Object.entries(expectedImports).flatMap(
([key, value]) => {
return value.map((v) => [key, v] as const);
}
);

importPairs.forEach(([importFrom, importValue]) => {
t.true(
new RegExp(
`import { [^}]*\\b${importValue}\\b[^}]* } from '${importFrom}'`
).test(formattedActual),
`The following expected import is missing from the actual content:\n` +
`${importValue} from ${importFrom}\n\n` +
`Actual content:\n` +
`${actual}`
t.regex(
normalizedActual,
new RegExp(`import{[^}]*\\b${importValue}\\b[^}]*}from'${importFrom}'`)
);
});
}

function normalizeCode(code: string) {
try {
code = format(code, PRETTIER_OPTIONS);
} catch (e) {}

return code
.replace(/\s+/g, ' ')
.replace(/\s*(\W)\s*/g, '$1')
.trim();
}
30 changes: 30 additions & 0 deletions test/renderers/js-experimental/accountsPage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import test from 'ava';
import {
accountNode,
pdaLinkNode,
pdaNode,
programNode,
visit,
} from '../../../src';
import { getRenderMapVisitor } from '../../../src/renderers/js-experimental/getRenderMapVisitor';
import { renderMapContains } from './_setup';

test('it renders PDA helpers for PDA with no seeds', (t) => {
// Given the following program with 1 account and 1 pda with empty seeds.
const node = programNode({
name: 'myProgram',
publicKey: '1111',
accounts: [accountNode({ name: 'foo', pda: pdaLinkNode('bar') })],
pdas: [pdaNode('bar', [])],
});

// When we render it.
const renderMap = visit(node, getRenderMapVisitor());

// Then we expect the following fetch helper functions delegating to findBarPda.
renderMapContains(t, renderMap, 'accounts/foo.ts', [
'export async function fetchFooFromSeeds',
'export async function fetchMaybeFooFromSeeds',
'await findBarPda({ programAddress })',
]);
});
33 changes: 8 additions & 25 deletions test/renderers/js-experimental/pdasPage.test.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,22 @@
import test from 'ava';
import {
accountNode,
pdaLinkNode,
pdaNode,
programNode,
visit,
} from '../../../src';
import { pdaNode, programNode, visit } from '../../../src';
import { getRenderMapVisitor } from '../../../src/renderers/js-experimental/getRenderMapVisitor';
import { renderMapContains } from './_setup';

test('it renders an empty array seed used on a pda', (t) => {
// Given the following program with 1 account and 1 pda with empty seeds.
const node = programNode({
name: 'splToken',
publicKey: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
accounts: [
accountNode({
name: 'testAccount',
discriminators: [],
pda: pdaLinkNode('testPda'),
}),
],
pdas: [
// Empty array seeds.
pdaNode('testPda', []),
],
name: 'myProgram',
publicKey: '1111',
pdas: [pdaNode('foo', [])],
});

// When we render it.
const renderMap = visit(node, getRenderMapVisitor());

// Then we expect the following function and and empty seeds
// array used on program derived address function.
renderMapContains(t, renderMap, 'pdas/testPda.ts', [
/export async function findTestPdaPda/,
/getProgramDerivedAddress\({ programAddress, seeds: \[\] }\)/,
// Then we expect the following PDA function using an empty seeds array to derive the address.
renderMapContains(t, renderMap, 'pdas/foo.ts', [
'export async function findFooPda',
'getProgramDerivedAddress({ programAddress, seeds: [] })',
]);
});
8 changes: 2 additions & 6 deletions test/renderers/js-experimental/programsPage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,7 @@ test('it checks the discriminator of sub-instructions before their parents.', (t

// Then we expect the sub-instruction condition to be rendered before the parent instruction condition.
renderMapContains(t, renderMap, 'programs/splToken.ts', [
`export function identifySplTokenInstruction(instruction: { data: Uint8Array } | Uint8Array): SplTokenInstruction {\n` +
`const data = instruction instanceof Uint8Array ? instruction : instruction.data;\n` +
`if (memcmp(data, getU8Encoder().encode(1), 0) && memcmp(data, getU32Encoder().encode(1), 1)) { return SplTokenInstruction.MintTokensV1; }\n` +
`if (memcmp(data, getU8Encoder().encode(1), 0)) { return SplTokenInstruction.MintTokens; }\n` +
`throw new Error('The provided instruction could not be identified as a splToken instruction.')\n` +
`}`,
`if (memcmp(data, getU8Encoder().encode(1), 0) && memcmp(data, getU32Encoder().encode(1), 1)) { return SplTokenInstruction.MintTokensV1; }\n` +
`if (memcmp(data, getU8Encoder().encode(1), 0)) { return SplTokenInstruction.MintTokens; }`,
]);
});

0 comments on commit 24cb013

Please sign in to comment.