From 8ca495f880ebc3e119378b674234f13239378021 Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Tue, 22 Sep 2026 12:26:43 +0100 Subject: [PATCH] Adapt nodes to Codama v2 --- packages/nodes/README.md | 4 +- packages/nodes/src/EnumTypeNode.ts | 2 +- packages/nodes/src/InstructionArgumentNode.ts | 21 ---- packages/nodes/src/InstructionNode.ts | 12 +-- packages/nodes/src/NestedTypeNode.ts | 65 ------------- packages/nodes/src/NumberTypeNode.ts | 17 ---- packages/nodes/src/TypeNode.ts | 34 +++++++ packages/nodes/src/index.ts | 5 +- packages/nodes/test/AccountNode.test.ts | 22 ++++- packages/nodes/test/ConstantNode.test.ts | 43 ++++++--- packages/nodes/test/DefinedTypeNode.test.ts | 9 +- packages/nodes/test/ErrorNode.test.ts | 18 +++- packages/nodes/test/EventNode.test.ts | 12 ++- .../nodes/test/InstructionAccountNode.test.ts | 23 ++++- .../test/InstructionArgumentNode.test.ts | 13 --- .../test/InstructionByteDeltaNode.test.ts | 22 ++++- packages/nodes/test/InstructionNode.test.ts | 64 ++++++++++--- .../InstructionRemainingAccountsNode.test.ts | 21 +++- .../nodes/test/InstructionStatusNode.test.ts | 10 +- packages/nodes/test/Node.test.ts | 18 ++-- packages/nodes/test/PdaNode.test.ts | 22 ++++- packages/nodes/test/ProgramNode.test.ts | 22 +++-- packages/nodes/test/RootNode.test.ts | 19 +++- packages/nodes/test/TextNode.test.ts | 29 ++++++ .../AccountDataValueNode.test.ts | 49 ++++++++++ .../ArgumentValueNode.test.ts | 13 --- .../ConditionalValueNode.test.ts | 18 +++- .../DataValueNode.test.ts | 33 +++++++ .../contextualValueNodes/PdaValueNode.test.ts | 29 +++++- .../ResolverValueNode.test.ts | 13 --- .../test/countNodes/PrefixedCountNode.test.ts | 6 +- .../test/countNodes/SentinelCountNode.test.ts | 25 +++++ .../test/displayNodes/DisplayNode.typetest.ts | 12 +++ .../UnitNumberDisplayNode.test.ts | 24 +++++ .../InstructionArgumentLinkNode.test.ts | 13 --- .../pdaSeedNodes/ConstantPdaSeedNode.test.ts | 34 ++++++- .../pdaSeedNodes/VariablePdaSeedNode.test.ts | 11 ++- .../FixedSizeTransformNode.test.ts | 23 +++++ .../HiddenPrefixTransformNode.test.ts | 35 +++++++ .../HiddenSuffixTransformNode.test.ts | 35 +++++++ .../PostOffsetTransformNode.test.ts | 30 ++++++ .../PreOffsetTransformNode.test.ts | 30 ++++++ .../SentinelTransformNode.test.ts | 25 +++++ .../SizePrefixTransformNode.test.ts | 23 +++++ .../transformNodes/TransformNode.typetest.ts | 12 +++ .../test/typeNodes/AmountTypeNode.test.ts | 13 --- .../test/typeNodes/ArrayTypeNode.test.ts | 17 +++- .../test/typeNodes/DateTimeTypeNode.test.ts | 16 +++- .../test/typeNodes/DurationTypeNode.test.ts | 28 ++++++ .../EnumEmptyVariantTypeNode.test.ts | 13 --- .../EnumStructVariantTypeNode.test.ts | 13 --- .../EnumTupleVariantTypeNode.test.ts | 13 --- .../nodes/test/typeNodes/EnumTypeNode.test.ts | 46 ++++++++- .../typeNodes/EnumVariantTypeNode.test.ts | 72 ++++++++++++++ .../test/typeNodes/FixedPointTypeNode.test.ts | 37 ++++++++ .../test/typeNodes/FixedSizeTypeNode.test.ts | 13 --- .../test/typeNodes/FloatTypeNode.test.ts | 39 ++++++++ .../typeNodes/HiddenPrefixTypeNode.test.ts | 13 --- .../typeNodes/HiddenSuffixTypeNode.test.ts | 13 --- .../test/typeNodes/IntegerTypeNode.test.ts | 39 ++++++++ .../nodes/test/typeNodes/MapTypeNode.test.ts | 6 +- .../test/typeNodes/NestedTypeNode.test.ts | 59 ------------ .../test/typeNodes/NestedTypeNode.typetest.ts | 95 ------------------- .../test/typeNodes/NumberTypeNode.test.ts | 13 --- .../test/typeNodes/OptionTypeNode.test.ts | 18 +++- .../test/typeNodes/PostOffsetTypeNode.test.ts | 13 --- .../test/typeNodes/PreOffsetTypeNode.test.ts | 13 --- .../typeNodes/RemainderOptionTypeNode.test.ts | 6 +- .../test/typeNodes/SentinelTypeNode.test.ts | 13 --- .../nodes/test/typeNodes/SetTypeNode.test.ts | 6 +- .../test/typeNodes/SizePrefixTypeNode.test.ts | 13 --- .../test/typeNodes/SolAmountTypeNode.test.ts | 13 --- .../typeNodes/StructFieldTypeNode.test.ts | 27 +++++- .../nodes/test/typeNodes/TypeNode.test.ts | 44 +++++++++ .../nodes/test/typeNodes/TypeNode.typetest.ts | 2 +- .../test/valueNodes/ConstantValueNode.test.ts | 26 ++++- .../test/valueNodes/FloatValueNode.test.ts | 40 ++++++++ .../test/valueNodes/IntegerValueNode.test.ts | 36 +++++++ .../test/valueNodes/MapEntryValueNode.test.ts | 6 +- .../test/valueNodes/NumberValueNode.test.ts | 13 --- .../test/valueNodes/SomeValueNode.test.ts | 6 +- 81 files changed, 1244 insertions(+), 629 deletions(-) delete mode 100644 packages/nodes/src/InstructionArgumentNode.ts delete mode 100644 packages/nodes/src/NestedTypeNode.ts delete mode 100644 packages/nodes/src/NumberTypeNode.ts create mode 100644 packages/nodes/src/TypeNode.ts delete mode 100644 packages/nodes/test/InstructionArgumentNode.test.ts create mode 100644 packages/nodes/test/TextNode.test.ts create mode 100644 packages/nodes/test/contextualValueNodes/AccountDataValueNode.test.ts delete mode 100644 packages/nodes/test/contextualValueNodes/ArgumentValueNode.test.ts create mode 100644 packages/nodes/test/contextualValueNodes/DataValueNode.test.ts delete mode 100644 packages/nodes/test/contextualValueNodes/ResolverValueNode.test.ts create mode 100644 packages/nodes/test/countNodes/SentinelCountNode.test.ts create mode 100644 packages/nodes/test/displayNodes/DisplayNode.typetest.ts create mode 100644 packages/nodes/test/displayNodes/UnitNumberDisplayNode.test.ts delete mode 100644 packages/nodes/test/linkNodes/InstructionArgumentLinkNode.test.ts create mode 100644 packages/nodes/test/transformNodes/FixedSizeTransformNode.test.ts create mode 100644 packages/nodes/test/transformNodes/HiddenPrefixTransformNode.test.ts create mode 100644 packages/nodes/test/transformNodes/HiddenSuffixTransformNode.test.ts create mode 100644 packages/nodes/test/transformNodes/PostOffsetTransformNode.test.ts create mode 100644 packages/nodes/test/transformNodes/PreOffsetTransformNode.test.ts create mode 100644 packages/nodes/test/transformNodes/SentinelTransformNode.test.ts create mode 100644 packages/nodes/test/transformNodes/SizePrefixTransformNode.test.ts create mode 100644 packages/nodes/test/transformNodes/TransformNode.typetest.ts delete mode 100644 packages/nodes/test/typeNodes/AmountTypeNode.test.ts create mode 100644 packages/nodes/test/typeNodes/DurationTypeNode.test.ts delete mode 100644 packages/nodes/test/typeNodes/EnumEmptyVariantTypeNode.test.ts delete mode 100644 packages/nodes/test/typeNodes/EnumStructVariantTypeNode.test.ts delete mode 100644 packages/nodes/test/typeNodes/EnumTupleVariantTypeNode.test.ts create mode 100644 packages/nodes/test/typeNodes/EnumVariantTypeNode.test.ts create mode 100644 packages/nodes/test/typeNodes/FixedPointTypeNode.test.ts delete mode 100644 packages/nodes/test/typeNodes/FixedSizeTypeNode.test.ts create mode 100644 packages/nodes/test/typeNodes/FloatTypeNode.test.ts delete mode 100644 packages/nodes/test/typeNodes/HiddenPrefixTypeNode.test.ts delete mode 100644 packages/nodes/test/typeNodes/HiddenSuffixTypeNode.test.ts create mode 100644 packages/nodes/test/typeNodes/IntegerTypeNode.test.ts delete mode 100644 packages/nodes/test/typeNodes/NestedTypeNode.test.ts delete mode 100644 packages/nodes/test/typeNodes/NestedTypeNode.typetest.ts delete mode 100644 packages/nodes/test/typeNodes/NumberTypeNode.test.ts delete mode 100644 packages/nodes/test/typeNodes/PostOffsetTypeNode.test.ts delete mode 100644 packages/nodes/test/typeNodes/PreOffsetTypeNode.test.ts delete mode 100644 packages/nodes/test/typeNodes/SentinelTypeNode.test.ts delete mode 100644 packages/nodes/test/typeNodes/SizePrefixTypeNode.test.ts delete mode 100644 packages/nodes/test/typeNodes/SolAmountTypeNode.test.ts create mode 100644 packages/nodes/test/typeNodes/TypeNode.test.ts create mode 100644 packages/nodes/test/valueNodes/FloatValueNode.test.ts create mode 100644 packages/nodes/test/valueNodes/IntegerValueNode.test.ts delete mode 100644 packages/nodes/test/valueNodes/NumberValueNode.test.ts diff --git a/packages/nodes/README.md b/packages/nodes/README.md index be4f65f0f..3fb474dd4 100644 --- a/packages/nodes/README.md +++ b/packages/nodes/README.md @@ -26,8 +26,8 @@ pnpm install @codama/nodes The Codama IDL is composed of various nodes that describe different aspects of a Solana program. Some nodes are categorised together as they share a similar purpose. For instance, all the nodes that describe a data structure that can be encoded and decoded into buffers are grouped under the `TypeNode` category. -The nodes themselves are defined by the [Codama specification](https://github.com/codama-idl/spec), which is the canonical reference for this package. Every node has its own generated documentation page describing its attributes and providing worked TypeScript examples. Head over to the [spec documentation](https://github.com/codama-idl/spec/blob/main/v1/docs/README.md) to explore all available nodes and their categories. +The nodes themselves are defined by the [Codama specification](https://github.com/codama-idl/spec), which is the canonical reference for this package. Every node has its own generated documentation page describing its attributes and providing worked TypeScript examples. Head over to the [spec documentation](https://github.com/codama-idl/spec/blob/main/docs/README.md) to explore all available nodes and their categories. ## Helpers -For every concrete node in the spec, this package exports a factory function named after it — e.g. `accountNode(input)` creates an `AccountNode` and `numberTypeNode('u64')` creates a `NumberTypeNode`. The worked examples on each documentation page use these helpers directly. The package also exports the matching TypeScript types, including the `Node` union type representing all available nodes and category unions such as `TypeNode` and `ValueNode`. +For every concrete node in the spec, this package exports a factory function named after it — e.g. `accountNode(input)` creates an `AccountNode` and `integerTypeNode('u64')` creates an `IntegerTypeNode`. The worked examples on each documentation page use these helpers directly. The package also exports the matching TypeScript types, including the `Node` union type representing all available nodes and category unions such as `TypeNode` and `ValueNode`. diff --git a/packages/nodes/src/EnumTypeNode.ts b/packages/nodes/src/EnumTypeNode.ts index 53320bfe3..0f32a979b 100644 --- a/packages/nodes/src/EnumTypeNode.ts +++ b/packages/nodes/src/EnumTypeNode.ts @@ -1,7 +1,7 @@ import type { EnumTypeNode } from '@codama/node-types'; export function isScalarEnum(node: EnumTypeNode): boolean { - return (node.variants ?? []).every(variant => variant.kind === 'enumEmptyVariantTypeNode'); + return (node.variants ?? []).every(variant => variant.data === undefined); } export function isDataEnum(node: EnumTypeNode): boolean { diff --git a/packages/nodes/src/InstructionArgumentNode.ts b/packages/nodes/src/InstructionArgumentNode.ts deleted file mode 100644 index e8a8b13cb..000000000 --- a/packages/nodes/src/InstructionArgumentNode.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { InstructionArgumentNode } from '@codama/node-types'; - -import { structFieldTypeNode } from './generated/typeNodes/StructFieldTypeNode'; -import { structTypeNode } from './generated/typeNodes/StructTypeNode'; -import { VALUE_NODE_KINDS } from './generated/valueNodes/ValueNode'; -import { isNode } from './Node'; - -export function structTypeNodeFromInstructionArgumentNodes(nodes: InstructionArgumentNode[]) { - return structTypeNode(nodes.map(structFieldTypeNodeFromInstructionArgumentNode)); -} - -export function structFieldTypeNodeFromInstructionArgumentNode(node: InstructionArgumentNode) { - if (isNode(node.defaultValue, VALUE_NODE_KINDS)) { - return structFieldTypeNode({ ...node, defaultValue: node.defaultValue }); - } - return structFieldTypeNode({ - ...node, - defaultValue: undefined, - defaultValueStrategy: undefined, - }); -} diff --git a/packages/nodes/src/InstructionNode.ts b/packages/nodes/src/InstructionNode.ts index aa3867593..1d34dc7a3 100644 --- a/packages/nodes/src/InstructionNode.ts +++ b/packages/nodes/src/InstructionNode.ts @@ -1,10 +1,4 @@ -import type { - InstructionArgumentNode, - InstructionNode, - OptionalAccountStrategy, - ProgramNode, - RootNode, -} from '@codama/node-types'; +import type { InstructionNode, OptionalAccountStrategy, ProgramNode, RootNode } from '@codama/node-types'; import { isNode } from './Node'; import { getAllInstructions } from './ProgramNode'; @@ -15,10 +9,6 @@ export function parseOptionalAccountStrategy( return optionalAccountStrategy ?? 'programId'; } -export function getAllInstructionArguments(node: InstructionNode): InstructionArgumentNode[] { - return [...(node.arguments ?? []), ...(node.extraArguments ?? [])]; -} - export function getAllInstructionsWithSubs( node: InstructionNode | ProgramNode | RootNode, config: { leavesOnly?: boolean; subInstructionsFirst?: boolean } = {}, diff --git a/packages/nodes/src/NestedTypeNode.ts b/packages/nodes/src/NestedTypeNode.ts deleted file mode 100644 index 76da4ac40..000000000 --- a/packages/nodes/src/NestedTypeNode.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { CODAMA_ERROR__UNEXPECTED_NESTED_NODE_KIND, CodamaError } from '@codama/errors'; -import type { NestedTypeNode, Node, TypeNode } from '@codama/node-types'; - -import { TYPE_NODE_KINDS } from './generated/typeNodes/TypeNode'; -import { isNode } from './Node'; - -export function resolveNestedTypeNode(typeNode: NestedTypeNode): TType { - switch (typeNode.kind) { - case 'fixedSizeTypeNode': - case 'hiddenPrefixTypeNode': - case 'hiddenSuffixTypeNode': - case 'postOffsetTypeNode': - case 'preOffsetTypeNode': - case 'sentinelTypeNode': - case 'sizePrefixTypeNode': - return resolveNestedTypeNode(typeNode.type as NestedTypeNode); - default: - return typeNode; - } -} - -export function transformNestedTypeNode( - typeNode: NestedTypeNode, - map: (type: TFrom) => TTo, -): NestedTypeNode { - switch (typeNode.kind) { - case 'fixedSizeTypeNode': - case 'hiddenPrefixTypeNode': - case 'hiddenSuffixTypeNode': - case 'postOffsetTypeNode': - case 'preOffsetTypeNode': - case 'sentinelTypeNode': - case 'sizePrefixTypeNode': - return Object.freeze({ - ...typeNode, - type: transformNestedTypeNode(typeNode.type as NestedTypeNode, map), - } as NestedTypeNode); - default: - return map(typeNode); - } -} - -export function isNestedTypeNode( - node: Node | null | undefined, - kind: TKind | TKind[], -): node is NestedTypeNode> { - if (!isNode(node, TYPE_NODE_KINDS)) return false; - const kinds = Array.isArray(kind) ? kind : [kind]; - const resolved = resolveNestedTypeNode(node); - return !!node && kinds.includes(resolved.kind as TKind); -} - -export function assertIsNestedTypeNode( - node: Node | null | undefined, - kind: TKind | TKind[], -): asserts node is NestedTypeNode> { - const kinds = Array.isArray(kind) ? kind : [kind]; - if (!isNestedTypeNode(node, kinds)) { - throw new CodamaError(CODAMA_ERROR__UNEXPECTED_NESTED_NODE_KIND, { - expectedKinds: kinds, - kind: node?.kind ?? null, - node, - }); - } -} diff --git a/packages/nodes/src/NumberTypeNode.ts b/packages/nodes/src/NumberTypeNode.ts deleted file mode 100644 index 5bf0166fc..000000000 --- a/packages/nodes/src/NumberTypeNode.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { NumberTypeNode } from '@codama/node-types'; - -export function isSignedInteger(node: NumberTypeNode): boolean { - return node.format.startsWith('i'); -} - -export function isUnsignedInteger(node: NumberTypeNode): boolean { - return node.format.startsWith('u') || node.format === 'shortU16'; -} - -export function isInteger(node: NumberTypeNode): boolean { - return !node.format.startsWith('f'); -} - -export function isDecimal(node: NumberTypeNode): boolean { - return node.format.startsWith('f'); -} diff --git a/packages/nodes/src/TypeNode.ts b/packages/nodes/src/TypeNode.ts new file mode 100644 index 000000000..9bbe73071 --- /dev/null +++ b/packages/nodes/src/TypeNode.ts @@ -0,0 +1,34 @@ +import type { TransformNode, TypeNode } from '@codama/node-types'; + +/** + * Return a copy of `typeNode` with the given `transforms` appended after + * any it already carries. + * + * Transforms apply in array order, the first being the innermost, so + * appending keeps the node's existing transforms innermost and layers the + * new ones on the outside. This is the v2 counterpart of re-wrapping a + * type in its original wrapper nodes: a consumer that replaces a type node + * (e.g. rebuilds a `structTypeNode`) can carry the original's transforms + * across with `addTypeNodeTransforms(newNode, oldNode.transforms ?? [])` + * instead of a double spread. + * + * Only pass transforms the target node does not already carry. A node + * derived by spreading the original (`{ ...oldNode, fields }`) already has + * `oldNode.transforms`, so calling `addTypeNodeTransforms(rebuilt, + * oldNode.transforms ?? [])` on it would duplicate them. + * + * When `transforms` is empty the node is returned unchanged. The result + * omits the `transforms` attribute entirely when there is nothing to + * carry, matching the generated constructors. + * + * The return type keeps `T` for ergonomics; the node's `TTransforms` type + * parameter is not re-derived, as with the generated constructors' own + * `as TTransforms` casts. + */ +export function addTypeNodeTransforms(typeNode: T, transforms: readonly TransformNode[]): T { + if (transforms.length === 0) return typeNode; + const merged = [...(typeNode.transforms ?? []), ...transforms]; + // Spreading a generic `T` widens to an index-signature object, so the + // narrowed node type has to be reasserted through `unknown`. + return Object.freeze({ ...typeNode, transforms: merged }) as unknown as T; +} diff --git a/packages/nodes/src/index.ts b/packages/nodes/src/index.ts index 9d5bf0170..cf543ee9b 100644 --- a/packages/nodes/src/index.ts +++ b/packages/nodes/src/index.ts @@ -11,19 +11,16 @@ export * from './shared'; export * from './ConstantPdaSeedNode'; export * from './ConstantValueNode'; export * from './EnumTypeNode'; -export * from './InstructionArgumentNode'; export * from './InstructionNode'; -export * from './NestedTypeNode'; export * from './Node'; -export * from './NumberTypeNode'; export * from './ProgramNode'; +export * from './TypeNode'; // Legacy plural-noun aliases preserved for API stability. Each maps to // the canonical `*_NODE_KINDS` name generated from the matching spec // union. export { CONTEXTUAL_VALUE_NODE_KINDS as CONTEXTUAL_VALUE_NODES, - ENUM_VARIANT_TYPE_NODE_KINDS as ENUM_VARIANT_TYPE_NODES, INSTRUCTION_INPUT_VALUE_NODE_KINDS as INSTRUCTION_INPUT_VALUE_NODES, REGISTERED_COUNT_NODE_KINDS as COUNT_NODES, REGISTERED_DISCRIMINATOR_NODE_KINDS as DISCRIMINATOR_NODES, diff --git a/packages/nodes/test/AccountNode.test.ts b/packages/nodes/test/AccountNode.test.ts index 528be9f0a..e8770082a 100644 --- a/packages/nodes/test/AccountNode.test.ts +++ b/packages/nodes/test/AccountNode.test.ts @@ -1,13 +1,29 @@ import { expect, test } from 'vitest'; -import { accountNode } from '../src'; +import { accountNode, integerTypeNode, structFieldTypeNode, structTypeNode } from '../src'; test('it returns the right node kind', () => { - const node = accountNode({ name: 'foo' }); + const node = accountNode({ identifier: 'foo' }); expect(node.kind).toBe('accountNode'); }); test('it returns a frozen object', () => { - const node = accountNode({ name: 'foo' }); + const node = accountNode({ identifier: 'foo' }); expect(Object.isFrozen(node)).toBe(true); }); + +test('it defaults the data to an empty struct', () => { + const node = accountNode({ identifier: 'foo' }); + expect(node.data).toEqual(structTypeNode([])); +}); + +test('it keeps the provided data', () => { + const data = structTypeNode([structFieldTypeNode({ identifier: 'amount', type: integerTypeNode('u64') })]); + const node = accountNode({ data, identifier: 'foo' }); + expect(node.data).toBe(data); +}); + +test('it preserves the identifier casing', () => { + const node = accountNode({ identifier: 'MyAccount' }); + expect(node.identifier).toBe('MyAccount'); +}); diff --git a/packages/nodes/test/ConstantNode.test.ts b/packages/nodes/test/ConstantNode.test.ts index 8bbfd1c70..f02309796 100644 --- a/packages/nodes/test/ConstantNode.test.ts +++ b/packages/nodes/test/ConstantNode.test.ts @@ -1,37 +1,52 @@ +import { CODAMA_ERROR__INVALID_BRANDED_STRING, CodamaError } from '@codama/errors'; import { expect, test } from 'vitest'; -import { constantNode, numberTypeNode, numberValueNode, stringTypeNode, stringValueNode } from '../src'; +import { constantNode, integerTypeNode, integerValueNode, stringTypeNode, stringValueNode } from '../src'; test('it returns the right node kind', () => { - const node = constantNode('myConstant', numberTypeNode('u32'), numberValueNode(42)); + const node = constantNode('myConstant', integerTypeNode('u32'), integerValueNode('42')); expect(node.kind).toBe('constantNode'); }); test('it returns a frozen object', () => { - const node = constantNode('myConstant', numberTypeNode('u32'), numberValueNode(42)); + const node = constantNode('myConstant', integerTypeNode('u32'), integerValueNode('42')); expect(Object.isFrozen(node)).toBe(true); }); -test('it creates a constant with a number type and value', () => { - const node = constantNode('maxItems', numberTypeNode('u64'), numberValueNode(100)); - expect(node.name).toBe('maxItems'); - expect(node.type.kind).toBe('numberTypeNode'); - expect(node.value.kind).toBe('numberValueNode'); +test('it creates a constant with an integer type and value', () => { + const node = constantNode('maxItems', integerTypeNode('u64'), integerValueNode('100')); + expect(node.identifier).toBe('maxItems'); + expect(node.type.kind).toBe('integerTypeNode'); + expect(node.value.kind).toBe('integerValueNode'); }); test('it creates a constant with a string type and value', () => { const node = constantNode('appName', stringTypeNode('utf8'), stringValueNode('MyApp')); - expect(node.name).toBe('appName'); + expect(node.identifier).toBe('appName'); expect(node.type.kind).toBe('stringTypeNode'); expect(node.value.kind).toBe('stringValueNode'); }); -test('it converts name to camelCase', () => { - const node = constantNode('my_constant', numberTypeNode('u8'), numberValueNode(1)); - expect(node.name).toBe('myConstant'); +test('it preserves the identifier casing', () => { + const node = constantNode('my_constant', integerTypeNode('u8'), integerValueNode('1')); + expect(node.identifier).toBe('my_constant'); +}); + +test('it rejects invalid identifiers', () => { + expect(() => constantNode('my-constant', integerTypeNode('u8'), integerValueNode('1'))).toThrow( + new CodamaError(CODAMA_ERROR__INVALID_BRANDED_STRING, { + actual: 'my-constant', + expected: 'identifier (letters, digits and underscores; no leading digit)', + }), + ); }); test('it can have documentation', () => { - const node = constantNode('myConstant', numberTypeNode('u32'), numberValueNode(42), ['My docs']); - expect(node.docs).toEqual(['My docs']); + const node = constantNode('myConstant', integerTypeNode('u32'), integerValueNode('42'), { docs: 'My docs' }); + expect(node.docs).toBe('My docs'); +}); + +test('it omits documentation when not provided', () => { + const node = constantNode('myConstant', integerTypeNode('u32'), integerValueNode('42')); + expect('docs' in node).toBe(false); }); diff --git a/packages/nodes/test/DefinedTypeNode.test.ts b/packages/nodes/test/DefinedTypeNode.test.ts index 89ef60a0f..f8ad40d6c 100644 --- a/packages/nodes/test/DefinedTypeNode.test.ts +++ b/packages/nodes/test/DefinedTypeNode.test.ts @@ -3,11 +3,16 @@ import { expect, test } from 'vitest'; import { definedTypeNode, structTypeNode } from '../src'; test('it returns the right node kind', () => { - const node = definedTypeNode({ name: 'foo', type: structTypeNode([]) }); + const node = definedTypeNode({ identifier: 'foo', type: structTypeNode([]) }); expect(node.kind).toBe('definedTypeNode'); }); test('it returns a frozen object', () => { - const node = definedTypeNode({ name: 'foo', type: structTypeNode([]) }); + const node = definedTypeNode({ identifier: 'foo', type: structTypeNode([]) }); expect(Object.isFrozen(node)).toBe(true); }); + +test('it can have documentation', () => { + const node = definedTypeNode({ docs: 'line one\nline two', identifier: 'foo', type: structTypeNode([]) }); + expect(node.docs).toBe('line one\nline two'); +}); diff --git a/packages/nodes/test/ErrorNode.test.ts b/packages/nodes/test/ErrorNode.test.ts index 4489ce231..4e652e3bf 100644 --- a/packages/nodes/test/ErrorNode.test.ts +++ b/packages/nodes/test/ErrorNode.test.ts @@ -1,13 +1,25 @@ import { expect, test } from 'vitest'; -import { errorNode } from '../src'; +import { errorNode, textNode } from '../src'; test('it returns the right node kind', () => { - const node = errorNode({ name: 'foo', code: 42, message: 'error message' }); + const node = errorNode({ code: 42, identifier: 'foo', message: 'error message' }); expect(node.kind).toBe('errorNode'); }); test('it returns a frozen object', () => { - const node = errorNode({ name: 'foo', code: 42, message: 'error message' }); + const node = errorNode({ code: 42, identifier: 'foo', message: 'error message' }); expect(Object.isFrozen(node)).toBe(true); }); + +test('it keeps the code and message', () => { + const node = errorNode({ code: 42, identifier: 'foo', message: 'error message' }); + expect(node.code).toBe(42); + expect(node.message).toBe('error message'); +}); + +test('it accepts a text node as message', () => { + const message = textNode({ content: 'error message' }); + const node = errorNode({ code: 42, identifier: 'foo', message }); + expect(node.message).toBe(message); +}); diff --git a/packages/nodes/test/EventNode.test.ts b/packages/nodes/test/EventNode.test.ts index 532861285..4bb22778d 100644 --- a/packages/nodes/test/EventNode.test.ts +++ b/packages/nodes/test/EventNode.test.ts @@ -1,14 +1,18 @@ import { expect, test } from 'vitest'; -import { structTypeNode } from '../src'; -import { eventNode } from '../src'; +import { eventNode, structTypeNode } from '../src'; test('it returns the right node kind', () => { - const node = eventNode({ data: structTypeNode([]), name: 'foo' }); + const node = eventNode({ data: structTypeNode([]), identifier: 'foo' }); expect(node.kind).toBe('eventNode'); }); test('it returns a frozen object', () => { - const node = eventNode({ data: structTypeNode([]), name: 'foo' }); + const node = eventNode({ data: structTypeNode([]), identifier: 'foo' }); expect(Object.isFrozen(node)).toBe(true); }); + +test('it omits discriminators when the array is empty', () => { + const node = eventNode({ data: structTypeNode([]), discriminators: [], identifier: 'foo' }); + expect('discriminators' in node).toBe(false); +}); diff --git a/packages/nodes/test/InstructionAccountNode.test.ts b/packages/nodes/test/InstructionAccountNode.test.ts index d76ff4bd2..ee55fecc4 100644 --- a/packages/nodes/test/InstructionAccountNode.test.ts +++ b/packages/nodes/test/InstructionAccountNode.test.ts @@ -1,13 +1,30 @@ import { expect, test } from 'vitest'; -import { instructionAccountNode } from '../src'; +import { accountLinkNode, instructionAccountNode, payerValueNode } from '../src'; test('it returns the right node kind', () => { - const node = instructionAccountNode({ name: 'foo', isSigner: false, isWritable: false }); + const node = instructionAccountNode({ identifier: 'foo', isSigner: false, isWritable: false }); expect(node.kind).toBe('instructionAccountNode'); }); test('it returns a frozen object', () => { - const node = instructionAccountNode({ name: 'foo', isSigner: false, isWritable: false }); + const node = instructionAccountNode({ identifier: 'foo', isSigner: false, isWritable: false }); expect(Object.isFrozen(node)).toBe(true); }); + +test('it defaults isOptional to false', () => { + const node = instructionAccountNode({ identifier: 'foo', isSigner: false, isWritable: false }); + expect(node.isOptional).toBe(false); +}); + +test('it can have a default value and an account link', () => { + const node = instructionAccountNode({ + accountLink: accountLinkNode('token'), + defaultValue: payerValueNode(), + identifier: 'foo', + isSigner: true, + isWritable: true, + }); + expect(node.defaultValue).toEqual(payerValueNode()); + expect(node.accountLink).toEqual(accountLinkNode('token')); +}); diff --git a/packages/nodes/test/InstructionArgumentNode.test.ts b/packages/nodes/test/InstructionArgumentNode.test.ts deleted file mode 100644 index 32bcf6ad0..000000000 --- a/packages/nodes/test/InstructionArgumentNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { instructionArgumentNode, structTypeNode } from '../src'; - -test('it returns the right node kind', () => { - const node = instructionArgumentNode({ name: 'foo', type: structTypeNode([]) }); - expect(node.kind).toBe('instructionArgumentNode'); -}); - -test('it returns a frozen object', () => { - const node = instructionArgumentNode({ name: 'foo', type: structTypeNode([]) }); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/InstructionByteDeltaNode.test.ts b/packages/nodes/test/InstructionByteDeltaNode.test.ts index 4279afdd9..c7d1cc3a5 100644 --- a/packages/nodes/test/InstructionByteDeltaNode.test.ts +++ b/packages/nodes/test/InstructionByteDeltaNode.test.ts @@ -1,13 +1,29 @@ import { expect, test } from 'vitest'; -import { instructionByteDeltaNode, numberValueNode } from '../src'; +import { accountLinkNode, dataValueNode, instructionByteDeltaNode, integerValueNode } from '../src'; test('it returns the right node kind', () => { - const node = instructionByteDeltaNode(numberValueNode(42)); + const node = instructionByteDeltaNode(integerValueNode('42')); expect(node.kind).toBe('instructionByteDeltaNode'); }); test('it returns a frozen object', () => { - const node = instructionByteDeltaNode(numberValueNode(42)); + const node = instructionByteDeltaNode(integerValueNode('42')); expect(Object.isFrozen(node)).toBe(true); }); + +test('it defaults withHeader to true and omits subtract', () => { + const node = instructionByteDeltaNode(integerValueNode('42')); + expect(node.withHeader).toBe(true); + expect('subtract' in node).toBe(false); +}); + +test('it accepts a data value or an account link as value', () => { + expect(instructionByteDeltaNode(dataValueNode('space')).value).toEqual(dataValueNode('space')); + expect(instructionByteDeltaNode(accountLinkNode('token'), { subtract: true, withHeader: false })).toEqual({ + kind: 'instructionByteDeltaNode', + subtract: true, + value: accountLinkNode('token'), + withHeader: false, + }); +}); diff --git a/packages/nodes/test/InstructionNode.test.ts b/packages/nodes/test/InstructionNode.test.ts index a603069d1..2254718c6 100644 --- a/packages/nodes/test/InstructionNode.test.ts +++ b/packages/nodes/test/InstructionNode.test.ts @@ -1,48 +1,84 @@ import { expect, test } from 'vitest'; -import { instructionNode, instructionStatusNode } from '../src'; +import { + instructionAccountNode, + instructionNode, + instructionStatusNode, + integerTypeNode, + structFieldTypeNode, + structTypeNode, +} from '../src'; test('it returns the right node kind', () => { - const node = instructionNode({ name: 'foo' }); + const node = instructionNode({ identifier: 'foo' }); expect(node.kind).toBe('instructionNode'); }); test('it returns a frozen object', () => { - const node = instructionNode({ name: 'foo' }); + const node = instructionNode({ identifier: 'foo' }); expect(Object.isFrozen(node)).toBe(true); }); +test('it defaults the optional account strategy to programId', () => { + const node = instructionNode({ identifier: 'foo' }); + expect(node.optionalAccountStrategy).toBe('programId'); +}); + +test('it defaults to no data', () => { + const node = instructionNode({ identifier: 'foo' }); + expect(node.data).toBeUndefined(); +}); + +test('it can have a data type', () => { + const data = structTypeNode([structFieldTypeNode({ identifier: 'amount', type: integerTypeNode('u64') })]); + const node = instructionNode({ data, identifier: 'foo' }); + expect(node.data).toBe(data); +}); + +test('it omits array attributes when they are empty', () => { + const node = instructionNode({ accounts: [], byteDeltas: [], identifier: 'foo', remainingAccounts: [] }); + expect('accounts' in node).toBe(false); + expect('byteDeltas' in node).toBe(false); + expect('remainingAccounts' in node).toBe(false); +}); + +test('it keeps array attributes when they are non-empty', () => { + const account = instructionAccountNode({ identifier: 'authority', isSigner: true, isWritable: false }); + const node = instructionNode({ accounts: [account], identifier: 'foo' }); + expect(node.accounts).toEqual([account]); +}); + test('it defaults to no status', () => { - const node = instructionNode({ name: 'foo' }); + const node = instructionNode({ identifier: 'foo' }); expect(node.status).toBeUndefined(); }); test('it can have a live status', () => { const statusMode = instructionStatusNode('live'); - const node = instructionNode({ name: 'foo', status: statusMode }); + const node = instructionNode({ identifier: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); expect(node.status?.lifecycle).toBe('live'); }); test('it can have a deprecated status with message', () => { - const statusMode = instructionStatusNode('deprecated', 'Use the newFoo instruction instead.'); - const node = instructionNode({ name: 'foo', status: statusMode }); + const statusMode = instructionStatusNode('deprecated', { message: 'Use the newFoo instruction instead.' }); + const node = instructionNode({ identifier: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); expect(node.status?.lifecycle).toBe('deprecated'); expect(node.status?.message).toBe('Use the newFoo instruction instead.'); }); test('it can have an archived status with message', () => { - const statusMode = instructionStatusNode('archived', 'This instruction was removed in v2.0.0.'); - const node = instructionNode({ name: 'foo', status: statusMode }); + const statusMode = instructionStatusNode('archived', { message: 'This instruction was removed in v2.0.0.' }); + const node = instructionNode({ identifier: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); expect(node.status?.lifecycle).toBe('archived'); expect(node.status?.message).toBe('This instruction was removed in v2.0.0.'); }); test('it can have a draft status with message', () => { - const statusMode = instructionStatusNode('draft', 'This instruction is under development.'); - const node = instructionNode({ name: 'foo', status: statusMode }); + const statusMode = instructionStatusNode('draft', { message: 'This instruction is under development.' }); + const node = instructionNode({ identifier: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); expect(node.status?.lifecycle).toBe('draft'); expect(node.status?.message).toBe('This instruction is under development.'); @@ -50,15 +86,15 @@ test('it can have a draft status with message', () => { test('it can have a status without a message', () => { const statusMode = instructionStatusNode('deprecated'); - const node = instructionNode({ name: 'foo', status: statusMode }); + const node = instructionNode({ identifier: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); expect(node.status?.lifecycle).toBe('deprecated'); expect(node.status?.message).toBeUndefined(); }); test('it can have an empty message', () => { - const statusMode = instructionStatusNode('deprecated', ''); - const node = instructionNode({ name: 'foo', status: statusMode }); + const statusMode = instructionStatusNode('deprecated', { message: '' }); + const node = instructionNode({ identifier: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); expect(node.status?.lifecycle).toBe('deprecated'); expect(node.status?.message).toBe(''); diff --git a/packages/nodes/test/InstructionRemainingAccountsNode.test.ts b/packages/nodes/test/InstructionRemainingAccountsNode.test.ts index c03ff1ef4..a4c9f455d 100644 --- a/packages/nodes/test/InstructionRemainingAccountsNode.test.ts +++ b/packages/nodes/test/InstructionRemainingAccountsNode.test.ts @@ -1,13 +1,28 @@ import { expect, test } from 'vitest'; -import { argumentValueNode, instructionRemainingAccountsNode } from '../src'; +import { instructionRemainingAccountsNode } from '../src'; test('it returns the right node kind', () => { - const node = instructionRemainingAccountsNode(argumentValueNode('foo')); + const node = instructionRemainingAccountsNode('foo'); expect(node.kind).toBe('instructionRemainingAccountsNode'); }); test('it returns a frozen object', () => { - const node = instructionRemainingAccountsNode(argumentValueNode('foo')); + const node = instructionRemainingAccountsNode('foo'); expect(Object.isFrozen(node)).toBe(true); }); + +test('it omits the flags when they are not provided', () => { + const node = instructionRemainingAccountsNode('foo'); + expect(node.identifier).toBe('foo'); + expect('isOptional' in node).toBe(false); + expect('isSigner' in node).toBe(false); + expect('isWritable' in node).toBe(false); +}); + +test('it keeps the provided flags', () => { + const node = instructionRemainingAccountsNode('foo', { isOptional: true, isSigner: 'either', isWritable: true }); + expect(node.isOptional).toBe(true); + expect(node.isSigner).toBe('either'); + expect(node.isWritable).toBe(true); +}); diff --git a/packages/nodes/test/InstructionStatusNode.test.ts b/packages/nodes/test/InstructionStatusNode.test.ts index e4bf80757..bbfba927a 100644 --- a/packages/nodes/test/InstructionStatusNode.test.ts +++ b/packages/nodes/test/InstructionStatusNode.test.ts @@ -1,6 +1,6 @@ import { expect, test } from 'vitest'; -import { instructionStatusNode } from '../src'; +import { instructionStatusNode, textNode } from '../src'; test('it returns the right node kind', () => { const node = instructionStatusNode('live'); @@ -13,11 +13,17 @@ test('it returns a frozen object', () => { }); test('it can have a status with message', () => { - const node = instructionStatusNode('deprecated', 'Use newInstruction'); + const node = instructionStatusNode('deprecated', { message: 'Use newInstruction' }); expect(node.lifecycle).toBe('deprecated'); expect(node.message).toBe('Use newInstruction'); }); +test('it can have a status with a text node message', () => { + const message = textNode({ content: 'Use newInstruction' }); + const node = instructionStatusNode('deprecated', { message }); + expect(node.message).toBe(message); +}); + test('it can have a status without message', () => { const node = instructionStatusNode('archived'); expect(node.lifecycle).toBe('archived'); diff --git a/packages/nodes/test/Node.test.ts b/packages/nodes/test/Node.test.ts index 28f3fbf77..7264a520b 100644 --- a/packages/nodes/test/Node.test.ts +++ b/packages/nodes/test/Node.test.ts @@ -3,9 +3,9 @@ import { describe, expect, test } from 'vitest'; import { assertIsNode, assertIsNodeFilter, + integerTypeNode, isNode, isNodeFilter, - numberTypeNode, publicKeyTypeNode, removeNullAndAssertIsNodeFilter, tupleTypeNode, @@ -21,7 +21,7 @@ describe('isNode', () => { test('it checks if a given node is part of the given kinds', () => { expect(isNode(tupleTypeNode([]), ['tupleTypeNode', 'publicKeyTypeNode'])).toBe(true); expect(isNode(publicKeyTypeNode(), ['tupleTypeNode', 'publicKeyTypeNode'])).toBe(true); - expect(isNode(numberTypeNode('u8'), ['tupleTypeNode', 'publicKeyTypeNode'])).toBe(false); + expect(isNode(integerTypeNode('u8'), ['tupleTypeNode', 'publicKeyTypeNode'])).toBe(false); expect(isNode(null, ['tupleTypeNode', 'publicKeyTypeNode'])).toBe(false); }); }); @@ -40,8 +40,8 @@ describe('assertIsNode', () => { test('it asserts that a given node is part of the given kinds', () => { expect(() => assertIsNode(tupleTypeNode([]), ['tupleTypeNode', 'publicKeyTypeNode'])).not.toThrow(); expect(() => assertIsNode(publicKeyTypeNode(), ['tupleTypeNode', 'publicKeyTypeNode'])).not.toThrow(); - expect(() => assertIsNode(numberTypeNode('u8'), ['tupleTypeNode', 'publicKeyTypeNode'])).toThrowError( - 'Expected node of kind [tupleTypeNode,publicKeyTypeNode], got [numberTypeNode].', + expect(() => assertIsNode(integerTypeNode('u8'), ['tupleTypeNode', 'publicKeyTypeNode'])).toThrowError( + 'Expected node of kind [tupleTypeNode,publicKeyTypeNode], got [integerTypeNode].', ); expect(() => assertIsNode(null, ['tupleTypeNode', 'publicKeyTypeNode'])).toThrowError( 'Expected node of kind [tupleTypeNode,publicKeyTypeNode], got [null].', @@ -61,7 +61,7 @@ describe('isNodeFilter', () => { const filter = isNodeFilter(['tupleTypeNode', 'publicKeyTypeNode']); expect(filter(tupleTypeNode([]))).toBe(true); expect(filter(publicKeyTypeNode())).toBe(true); - expect(filter(numberTypeNode('u8'))).toBe(false); + expect(filter(integerTypeNode('u8'))).toBe(false); expect(filter(null)).toBe(false); }); }); @@ -80,8 +80,8 @@ describe('assertIsNodeFilter', () => { const filter = assertIsNodeFilter(['tupleTypeNode', 'publicKeyTypeNode']); expect(() => filter(tupleTypeNode([]))).not.toThrow(); expect(() => filter(publicKeyTypeNode())).not.toThrow(); - expect(() => filter(numberTypeNode('u8'))).toThrowError( - 'Expected node of kind [tupleTypeNode,publicKeyTypeNode], got [numberTypeNode].', + expect(() => filter(integerTypeNode('u8'))).toThrowError( + 'Expected node of kind [tupleTypeNode,publicKeyTypeNode], got [integerTypeNode].', ); expect(() => filter(null)).toThrowError('Expected node of kind [tupleTypeNode,publicKeyTypeNode], got [null].'); }); @@ -102,8 +102,8 @@ describe('removeNullAndAssertIsNodeFilter', () => { tupleTypeNode([]), publicKeyTypeNode(), ]); - expect(() => [tupleTypeNode([]), numberTypeNode('u8'), null].filter(filter)).toThrowError( - 'Expected node of kind [tupleTypeNode,publicKeyTypeNode], got [numberTypeNode].', + expect(() => [tupleTypeNode([]), integerTypeNode('u8'), null].filter(filter)).toThrowError( + 'Expected node of kind [tupleTypeNode,publicKeyTypeNode], got [integerTypeNode].', ); }); }); diff --git a/packages/nodes/test/PdaNode.test.ts b/packages/nodes/test/PdaNode.test.ts index e8d255759..e8c6d7d14 100644 --- a/packages/nodes/test/PdaNode.test.ts +++ b/packages/nodes/test/PdaNode.test.ts @@ -1,13 +1,29 @@ import { expect, test } from 'vitest'; -import { pdaNode } from '../src'; +import { constantPdaSeedNodeFromString, pdaNode, publicKeyTypeNode, variablePdaSeedNode } from '../src'; test('it returns the right node kind', () => { - const node = pdaNode({ name: 'foo', seeds: [] }); + const node = pdaNode({ identifier: 'foo', seeds: [] }); expect(node.kind).toBe('pdaNode'); }); test('it returns a frozen object', () => { - const node = pdaNode({ name: 'foo', seeds: [] }); + const node = pdaNode({ identifier: 'foo', seeds: [] }); expect(Object.isFrozen(node)).toBe(true); }); + +test('it omits seeds when the array is empty', () => { + const node = pdaNode({ identifier: 'foo', seeds: [] }); + expect('seeds' in node).toBe(false); +}); + +test('it keeps seeds when they are non-empty', () => { + const seeds = [constantPdaSeedNodeFromString('utf8', 'metadata'), variablePdaSeedNode('mint', publicKeyTypeNode())]; + const node = pdaNode({ identifier: 'foo', seeds }); + expect(node.seeds).toEqual(seeds); +}); + +test('it can override the program ID', () => { + const node = pdaNode({ identifier: 'foo', programId: '1111', seeds: [] }); + expect(node.programId).toBe('1111'); +}); diff --git a/packages/nodes/test/ProgramNode.test.ts b/packages/nodes/test/ProgramNode.test.ts index 2d0061563..d66e7d7cd 100644 --- a/packages/nodes/test/ProgramNode.test.ts +++ b/packages/nodes/test/ProgramNode.test.ts @@ -3,20 +3,30 @@ import { expect, test } from 'vitest'; import { accountNode, programNode, structTypeNode } from '../src'; test('it returns the right node kind', () => { - const node = programNode({ name: 'foo', publicKey: '1111' }); + const node = programNode({ identifier: 'foo', publicKey: '1111' }); expect(node.kind).toBe('programNode'); }); test('it returns a frozen object', () => { - const node = programNode({ name: 'foo', publicKey: '1111' }); + const node = programNode({ identifier: 'foo', publicKey: '1111' }); expect(Object.isFrozen(node)).toBe(true); }); +test('it defaults the version to 0.0.0', () => { + const node = programNode({ identifier: 'foo', publicKey: '1111' }); + expect(node.version).toBe('0.0.0'); +}); + +test('it does not carry an origin', () => { + const node = programNode({ identifier: 'foo', publicKey: '1111' }); + expect('origin' in node).toBe(false); +}); + test('it omits array attributes entirely when they are empty (skip-when-empty)', () => { // Arrays are omitted when empty on write. An absent array and an empty // array are semantically identical (see the "Array attributes are omitted // when empty" convention in the `@codama/spec` README). - const node = programNode({ name: 'foo', publicKey: '1111' }); + const node = programNode({ identifier: 'foo', publicKey: '1111' }); expect('accounts' in node).toBe(false); expect('instructions' in node).toBe(false); expect('pdas' in node).toBe(false); @@ -27,12 +37,12 @@ test('it omits array attributes entirely when they are empty (skip-when-empty)', }); test('it omits an array attribute even when an explicit empty array is passed', () => { - const node = programNode({ accounts: [], name: 'foo', publicKey: '1111' }); + const node = programNode({ accounts: [], identifier: 'foo', publicKey: '1111' }); expect('accounts' in node).toBe(false); }); test('it keeps array attributes when they are non-empty', () => { - const account = accountNode({ data: structTypeNode([]), name: 'myAccount' }); - const node = programNode({ accounts: [account], name: 'foo', publicKey: '1111' }); + const account = accountNode({ data: structTypeNode([]), identifier: 'myAccount' }); + const node = programNode({ accounts: [account], identifier: 'foo', publicKey: '1111' }); expect(node.accounts).toEqual([account]); }); diff --git a/packages/nodes/test/RootNode.test.ts b/packages/nodes/test/RootNode.test.ts index c1fd78b0f..60b8c3209 100644 --- a/packages/nodes/test/RootNode.test.ts +++ b/packages/nodes/test/RootNode.test.ts @@ -4,22 +4,33 @@ import { expect, expectTypeOf, test } from 'vitest'; import { CODAMA_VERSION, programNode, rootNode } from '../src'; test('it returns the right node kind', () => { - const root = rootNode(programNode({ name: 'foo', publicKey: '1111' })); + const root = rootNode(programNode({ identifier: 'foo', publicKey: '1111' })); expect(root.kind).toBe('rootNode'); }); test('it returns the right Codama standard', () => { - const root = rootNode(programNode({ name: 'foo', publicKey: '1111' })); + const root = rootNode(programNode({ identifier: 'foo', publicKey: '1111' })); expect(root.standard).toBe('codama'); }); test('it tags the root with the spec version it was generated against', () => { - const root = rootNode(programNode({ name: 'foo', publicKey: '1111' })); + const root = rootNode(programNode({ identifier: 'foo', publicKey: '1111' })); expect(root.version).toBe(CODAMA_VERSION); expectTypeOf(root.version).toMatchTypeOf(); }); test('it returns a frozen object', () => { - const root = rootNode(programNode({ name: 'foo', publicKey: '1111' })); + const root = rootNode(programNode({ identifier: 'foo', publicKey: '1111' })); expect(Object.isFrozen(root)).toBe(true); }); + +test('it omits additional programs when the array is empty', () => { + const root = rootNode(programNode({ identifier: 'foo', publicKey: '1111' }), { additionalPrograms: [] }); + expect('additionalPrograms' in root).toBe(false); +}); + +test('it keeps additional programs when they are non-empty', () => { + const additional = programNode({ identifier: 'bar', publicKey: '2222' }); + const root = rootNode(programNode({ identifier: 'foo', publicKey: '1111' }), { additionalPrograms: [additional] }); + expect(root.additionalPrograms).toEqual([additional]); +}); diff --git a/packages/nodes/test/TextNode.test.ts b/packages/nodes/test/TextNode.test.ts new file mode 100644 index 000000000..9b0a2875b --- /dev/null +++ b/packages/nodes/test/TextNode.test.ts @@ -0,0 +1,29 @@ +import { expect, test } from 'vitest'; + +import { pluginNode, textNode } from '../src'; + +test('it returns the right node kind', () => { + const node = textNode({ content: 'Hello' }); + expect(node.kind).toBe('textNode'); +}); + +test('it returns a frozen object', () => { + const node = textNode({ content: 'Hello' }); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it keeps the content, including multi-line content', () => { + const node = textNode({ content: 'line one\nline two' }); + expect(node.content).toBe('line one\nline two'); +}); + +test('it omits plugins when the array is empty', () => { + const node = textNode({ content: 'Hello', plugins: [] }); + expect('plugins' in node).toBe(false); +}); + +test('it keeps the provided plugins', () => { + const plugins = [pluginNode('i18n.fr', 'Bonjour')]; + const node = textNode({ content: 'Hello', plugins }); + expect(node.plugins).toEqual(plugins); +}); diff --git a/packages/nodes/test/contextualValueNodes/AccountDataValueNode.test.ts b/packages/nodes/test/contextualValueNodes/AccountDataValueNode.test.ts new file mode 100644 index 000000000..e1f0468af --- /dev/null +++ b/packages/nodes/test/contextualValueNodes/AccountDataValueNode.test.ts @@ -0,0 +1,49 @@ +import { CODAMA_ERROR__INVALID_BRANDED_STRING, CodamaError } from '@codama/errors'; +import { expect, test } from 'vitest'; + +import { accountDataValueNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = accountDataValueNode('mint'); + expect(node.kind).toBe('accountDataValueNode'); +}); + +test('it returns a frozen object', () => { + const node = accountDataValueNode('mint'); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it omits the path when not provided', () => { + const node = accountDataValueNode('mint'); + expect(node.account).toBe('mint'); + expect('path' in node).toBe(false); +}); + +test('it keeps the provided path', () => { + const node = accountDataValueNode('mint', { path: 'decimals' }); + expect(node.account).toBe('mint'); + expect(node.path).toBe('decimals'); +}); + +test('it accepts nested paths', () => { + const node = accountDataValueNode('config', { path: 'fees[0].amount' }); + expect(node.path).toBe('fees[0].amount'); +}); + +test('it rejects an invalid account identifier', () => { + expect(() => accountDataValueNode('my-mint')).toThrow( + new CodamaError(CODAMA_ERROR__INVALID_BRANDED_STRING, { + actual: 'my-mint', + expected: 'identifier (letters, digits and underscores; no leading digit)', + }), + ); +}); + +test('it rejects an invalid path', () => { + expect(() => accountDataValueNode('mint', { path: 'decimals.' })).toThrow( + new CodamaError(CODAMA_ERROR__INVALID_BRANDED_STRING, { + actual: 'decimals.', + expected: 'path (e.g. "data.amount" or "[0].field")', + }), + ); +}); diff --git a/packages/nodes/test/contextualValueNodes/ArgumentValueNode.test.ts b/packages/nodes/test/contextualValueNodes/ArgumentValueNode.test.ts deleted file mode 100644 index a01029a60..000000000 --- a/packages/nodes/test/contextualValueNodes/ArgumentValueNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { argumentValueNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = argumentValueNode('space'); - expect(node.kind).toBe('argumentValueNode'); -}); - -test('it returns a frozen object', () => { - const node = argumentValueNode('space'); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/contextualValueNodes/ConditionalValueNode.test.ts b/packages/nodes/test/contextualValueNodes/ConditionalValueNode.test.ts index 796a5e345..23d346da2 100644 --- a/packages/nodes/test/contextualValueNodes/ConditionalValueNode.test.ts +++ b/packages/nodes/test/contextualValueNodes/ConditionalValueNode.test.ts @@ -1,13 +1,25 @@ import { expect, test } from 'vitest'; -import { accountValueNode, argumentValueNode, conditionalValueNode } from '../../src'; +import { accountValueNode, conditionalValueNode, dataValueNode, integerValueNode, payerValueNode } from '../../src'; test('it returns the right node kind', () => { - const node = conditionalValueNode({ condition: accountValueNode('token'), ifTrue: argumentValueNode('space') }); + const node = conditionalValueNode({ condition: accountValueNode('token'), ifTrue: dataValueNode('space') }); expect(node.kind).toBe('conditionalValueNode'); }); test('it returns a frozen object', () => { - const node = conditionalValueNode({ condition: accountValueNode('token'), ifTrue: argumentValueNode('space') }); + const node = conditionalValueNode({ condition: accountValueNode('token'), ifTrue: dataValueNode('space') }); expect(Object.isFrozen(node)).toBe(true); }); + +test('it accepts a data value as condition with an explicit value to compare against', () => { + const node = conditionalValueNode({ + condition: dataValueNode('amount'), + ifFalse: payerValueNode(), + value: integerValueNode('0'), + }); + expect(node.condition).toEqual(dataValueNode('amount')); + expect(node.value).toEqual(integerValueNode('0')); + expect(node.ifFalse).toEqual(payerValueNode()); + expect('ifTrue' in node).toBe(false); +}); diff --git a/packages/nodes/test/contextualValueNodes/DataValueNode.test.ts b/packages/nodes/test/contextualValueNodes/DataValueNode.test.ts new file mode 100644 index 000000000..f1ac1c4aa --- /dev/null +++ b/packages/nodes/test/contextualValueNodes/DataValueNode.test.ts @@ -0,0 +1,33 @@ +import { CODAMA_ERROR__INVALID_BRANDED_STRING, CodamaError } from '@codama/errors'; +import { expect, test } from 'vitest'; + +import { dataValueNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = dataValueNode('amount'); + expect(node.kind).toBe('dataValueNode'); +}); + +test('it returns a frozen object', () => { + const node = dataValueNode('amount'); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it keeps the provided path', () => { + expect(dataValueNode('amount').path).toBe('amount'); + expect(dataValueNode('config.amount').path).toBe('config.amount'); + expect(dataValueNode('[0].amount').path).toBe('[0].amount'); + expect(dataValueNode('items[2]').path).toBe('items[2]'); +}); + +test.each(['', '1amount', 'config.', '.amount', 'items[a]', 'items[01]', 'config-amount'])( + 'it rejects the invalid path %j', + path => { + expect(() => dataValueNode(path)).toThrow( + new CodamaError(CODAMA_ERROR__INVALID_BRANDED_STRING, { + actual: path, + expected: 'path (e.g. "data.amount" or "[0].field")', + }), + ); + }, +); diff --git a/packages/nodes/test/contextualValueNodes/PdaValueNode.test.ts b/packages/nodes/test/contextualValueNodes/PdaValueNode.test.ts index e08d8e6f2..5d8573146 100644 --- a/packages/nodes/test/contextualValueNodes/PdaValueNode.test.ts +++ b/packages/nodes/test/contextualValueNodes/PdaValueNode.test.ts @@ -1,13 +1,36 @@ import { expect, test } from 'vitest'; -import { pdaValueNode } from '../../src'; +import { accountValueNode, pdaLinkNode, pdaNode, pdaSeedValueNode, pdaValueNode } from '../../src'; test('it returns the right node kind', () => { - const node = pdaValueNode('associatedToken', []); + const node = pdaValueNode('associatedToken'); expect(node.kind).toBe('pdaValueNode'); }); test('it returns a frozen object', () => { - const node = pdaValueNode('associatedToken', []); + const node = pdaValueNode('associatedToken'); expect(Object.isFrozen(node)).toBe(true); }); + +test('it wraps a string PDA into a PDA link node', () => { + const node = pdaValueNode('associatedToken'); + expect(node.pda).toEqual(pdaLinkNode('associatedToken')); +}); + +test('it accepts an inline PDA node', () => { + const pda = pdaNode({ identifier: 'associatedToken', seeds: [] }); + const node = pdaValueNode(pda); + expect(node.pda).toBe(pda); +}); + +test('it omits seeds when the array is empty', () => { + const node = pdaValueNode('associatedToken', { seeds: [] }); + expect('seeds' in node).toBe(false); +}); + +test('it keeps seeds and program ID when provided', () => { + const seeds = [pdaSeedValueNode('mint', accountValueNode('mint'))]; + const node = pdaValueNode('associatedToken', { programId: accountValueNode('tokenProgram'), seeds }); + expect(node.seeds).toEqual(seeds); + expect(node.programId).toEqual(accountValueNode('tokenProgram')); +}); diff --git a/packages/nodes/test/contextualValueNodes/ResolverValueNode.test.ts b/packages/nodes/test/contextualValueNodes/ResolverValueNode.test.ts deleted file mode 100644 index a9b30b00d..000000000 --- a/packages/nodes/test/contextualValueNodes/ResolverValueNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { resolverValueNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = resolverValueNode('foo'); - expect(node.kind).toBe('resolverValueNode'); -}); - -test('it returns a frozen object', () => { - const node = resolverValueNode('foo'); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/countNodes/PrefixedCountNode.test.ts b/packages/nodes/test/countNodes/PrefixedCountNode.test.ts index 52e9d3e00..61a8a16f2 100644 --- a/packages/nodes/test/countNodes/PrefixedCountNode.test.ts +++ b/packages/nodes/test/countNodes/PrefixedCountNode.test.ts @@ -1,13 +1,13 @@ import { expect, test } from 'vitest'; -import { numberTypeNode, prefixedCountNode } from '../../src'; +import { integerTypeNode, prefixedCountNode } from '../../src'; test('it returns the right node kind', () => { - const node = prefixedCountNode(numberTypeNode('u32')); + const node = prefixedCountNode(integerTypeNode('u32')); expect(node.kind).toBe('prefixedCountNode'); }); test('it returns a frozen object', () => { - const node = prefixedCountNode(numberTypeNode('u32')); + const node = prefixedCountNode(integerTypeNode('u32')); expect(Object.isFrozen(node)).toBe(true); }); diff --git a/packages/nodes/test/countNodes/SentinelCountNode.test.ts b/packages/nodes/test/countNodes/SentinelCountNode.test.ts new file mode 100644 index 000000000..da1f406de --- /dev/null +++ b/packages/nodes/test/countNodes/SentinelCountNode.test.ts @@ -0,0 +1,25 @@ +import { expect, test } from 'vitest'; + +import { constantValueNodeFromBytes, sentinelCountNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = sentinelCountNode(constantValueNodeFromBytes('base16', 'ff')); + expect(node.kind).toBe('sentinelCountNode'); +}); + +test('it returns a frozen object', () => { + const node = sentinelCountNode(constantValueNodeFromBytes('base16', 'ff')); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it keeps the sentinel and omits the strategy when not provided', () => { + const sentinel = constantValueNodeFromBytes('base16', 'ff'); + const node = sentinelCountNode(sentinel); + expect(node.sentinel).toBe(sentinel); + expect('strategy' in node).toBe(false); +}); + +test.each(['required', 'optional', 'omitted'] as const)('it keeps the %s strategy', strategy => { + const node = sentinelCountNode(constantValueNodeFromBytes('base16', 'ff'), { strategy }); + expect(node.strategy).toBe(strategy); +}); diff --git a/packages/nodes/test/displayNodes/DisplayNode.typetest.ts b/packages/nodes/test/displayNodes/DisplayNode.typetest.ts new file mode 100644 index 000000000..941b625c6 --- /dev/null +++ b/packages/nodes/test/displayNodes/DisplayNode.typetest.ts @@ -0,0 +1,12 @@ +import type { RegisteredDisplayNode } from '@codama/node-types'; + +import { REGISTERED_DISPLAY_NODE_KINDS } from '../../src'; + +// [DESCRIBE] Registered display node kinds. +{ + // It matches exactly with RegisteredDisplayNode['kind']. + { + REGISTERED_DISPLAY_NODE_KINDS satisfies readonly RegisteredDisplayNode['kind'][]; + null as unknown as RegisteredDisplayNode['kind'] satisfies (typeof REGISTERED_DISPLAY_NODE_KINDS)[number]; + } +} diff --git a/packages/nodes/test/displayNodes/UnitNumberDisplayNode.test.ts b/packages/nodes/test/displayNodes/UnitNumberDisplayNode.test.ts new file mode 100644 index 000000000..3b035b776 --- /dev/null +++ b/packages/nodes/test/displayNodes/UnitNumberDisplayNode.test.ts @@ -0,0 +1,24 @@ +import { expect, test } from 'vitest'; + +import { injectedValueNode, stringValueNode, unitNumberDisplayNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = unitNumberDisplayNode({ unit: stringValueNode('SOL') }); + expect(node.kind).toBe('unitNumberDisplayNode'); +}); + +test('it returns a frozen object', () => { + const node = unitNumberDisplayNode({ unit: stringValueNode('SOL') }); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it keeps a concrete string unit', () => { + const node = unitNumberDisplayNode({ unit: stringValueNode('SOL') }); + expect(node.unit).toEqual(stringValueNode('SOL')); +}); + +test('it keeps an injected unit', () => { + const unit = injectedValueNode({ fallback: stringValueNode('tokens'), key: 'symbol' }); + const node = unitNumberDisplayNode({ unit }); + expect(node.unit).toBe(unit); +}); diff --git a/packages/nodes/test/linkNodes/InstructionArgumentLinkNode.test.ts b/packages/nodes/test/linkNodes/InstructionArgumentLinkNode.test.ts deleted file mode 100644 index 09683ae44..000000000 --- a/packages/nodes/test/linkNodes/InstructionArgumentLinkNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { instructionArgumentLinkNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = instructionArgumentLinkNode('amount'); - expect(node.kind).toBe('instructionArgumentLinkNode'); -}); - -test('it returns a frozen object', () => { - const node = instructionArgumentLinkNode('amount'); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/pdaSeedNodes/ConstantPdaSeedNode.test.ts b/packages/nodes/test/pdaSeedNodes/ConstantPdaSeedNode.test.ts index 8793d589b..0c339a810 100644 --- a/packages/nodes/test/pdaSeedNodes/ConstantPdaSeedNode.test.ts +++ b/packages/nodes/test/pdaSeedNodes/ConstantPdaSeedNode.test.ts @@ -1,13 +1,41 @@ import { expect, test } from 'vitest'; -import { constantPdaSeedNode, numberTypeNode, numberValueNode } from '../../src'; +import { + bytesTypeNode, + bytesValueNode, + constantPdaSeedNode, + constantPdaSeedNodeFromBytes, + constantPdaSeedNodeFromProgramId, + constantPdaSeedNodeFromString, + integerTypeNode, + integerValueNode, + programIdValueNode, + publicKeyTypeNode, + stringTypeNode, + stringValueNode, +} from '../../src'; test('it returns the right node kind', () => { - const node = constantPdaSeedNode(numberTypeNode('u64'), numberValueNode(42)); + const node = constantPdaSeedNode(integerTypeNode('u64'), integerValueNode('42')); expect(node.kind).toBe('constantPdaSeedNode'); }); test('it returns a frozen object', () => { - const node = constantPdaSeedNode(numberTypeNode('u64'), numberValueNode(42)); + const node = constantPdaSeedNode(integerTypeNode('u64'), integerValueNode('42')); expect(Object.isFrozen(node)).toBe(true); }); + +test('it can be created from a string', () => { + const node = constantPdaSeedNodeFromString('utf8', 'metadata'); + expect(node).toEqual(constantPdaSeedNode(stringTypeNode('utf8'), stringValueNode('metadata'))); +}); + +test('it can be created from bytes', () => { + const node = constantPdaSeedNodeFromBytes('base16', 'aabbccdd'); + expect(node).toEqual(constantPdaSeedNode(bytesTypeNode(), bytesValueNode('base16', 'aabbccdd'))); +}); + +test('it can be created from the program ID', () => { + const node = constantPdaSeedNodeFromProgramId(); + expect(node).toEqual(constantPdaSeedNode(publicKeyTypeNode(), programIdValueNode())); +}); diff --git a/packages/nodes/test/pdaSeedNodes/VariablePdaSeedNode.test.ts b/packages/nodes/test/pdaSeedNodes/VariablePdaSeedNode.test.ts index ae1f5160d..3ec900c3e 100644 --- a/packages/nodes/test/pdaSeedNodes/VariablePdaSeedNode.test.ts +++ b/packages/nodes/test/pdaSeedNodes/VariablePdaSeedNode.test.ts @@ -1,13 +1,18 @@ import { expect, test } from 'vitest'; -import { numberTypeNode, variablePdaSeedNode } from '../../src'; +import { integerTypeNode, variablePdaSeedNode } from '../../src'; test('it returns the right node kind', () => { - const node = variablePdaSeedNode('edition', numberTypeNode('u64')); + const node = variablePdaSeedNode('edition', integerTypeNode('u64')); expect(node.kind).toBe('variablePdaSeedNode'); }); test('it returns a frozen object', () => { - const node = variablePdaSeedNode('edition', numberTypeNode('u64')); + const node = variablePdaSeedNode('edition', integerTypeNode('u64')); expect(Object.isFrozen(node)).toBe(true); }); + +test('it can have documentation', () => { + const node = variablePdaSeedNode('edition', integerTypeNode('u64'), { docs: 'The edition number.' }); + expect(node.docs).toBe('The edition number.'); +}); diff --git a/packages/nodes/test/transformNodes/FixedSizeTransformNode.test.ts b/packages/nodes/test/transformNodes/FixedSizeTransformNode.test.ts new file mode 100644 index 000000000..590836abf --- /dev/null +++ b/packages/nodes/test/transformNodes/FixedSizeTransformNode.test.ts @@ -0,0 +1,23 @@ +import { expect, test } from 'vitest'; + +import { fixedSizeTransformNode, stringTypeNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = fixedSizeTransformNode(32); + expect(node.kind).toBe('fixedSizeTransformNode'); +}); + +test('it returns a frozen object', () => { + const node = fixedSizeTransformNode(32); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it keeps the provided size', () => { + const node = fixedSizeTransformNode(32); + expect(node.size).toBe(32); +}); + +test('it can be attached to a type node', () => { + const node = stringTypeNode('utf8', { transforms: [fixedSizeTransformNode(32)] }); + expect(node.transforms).toEqual([fixedSizeTransformNode(32)]); +}); diff --git a/packages/nodes/test/transformNodes/HiddenPrefixTransformNode.test.ts b/packages/nodes/test/transformNodes/HiddenPrefixTransformNode.test.ts new file mode 100644 index 000000000..0d59b5716 --- /dev/null +++ b/packages/nodes/test/transformNodes/HiddenPrefixTransformNode.test.ts @@ -0,0 +1,35 @@ +import { expect, test } from 'vitest'; + +import { + constantValueNodeFromBytes, + constantValueNodeFromString, + hiddenPrefixTransformNode, + stringTypeNode, +} from '../../src'; + +test('it returns the right node kind', () => { + const node = hiddenPrefixTransformNode([constantValueNodeFromBytes('base16', 'ff')]); + expect(node.kind).toBe('hiddenPrefixTransformNode'); +}); + +test('it returns a frozen object', () => { + const node = hiddenPrefixTransformNode([constantValueNodeFromBytes('base16', 'ff')]); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it omits the prefix when the array is empty', () => { + const node = hiddenPrefixTransformNode([]); + expect('prefix' in node).toBe(false); +}); + +test('it keeps the provided prefix constants in order', () => { + const prefix = [constantValueNodeFromBytes('base16', 'ff'), constantValueNodeFromString('utf8', 'hello')]; + const node = hiddenPrefixTransformNode(prefix); + expect(node.prefix).toEqual(prefix); +}); + +test('it can be attached to a type node', () => { + const prefix = [constantValueNodeFromBytes('base16', 'ff')]; + const node = stringTypeNode('utf8', { transforms: [hiddenPrefixTransformNode(prefix)] }); + expect(node.transforms).toEqual([hiddenPrefixTransformNode(prefix)]); +}); diff --git a/packages/nodes/test/transformNodes/HiddenSuffixTransformNode.test.ts b/packages/nodes/test/transformNodes/HiddenSuffixTransformNode.test.ts new file mode 100644 index 000000000..8fd8204da --- /dev/null +++ b/packages/nodes/test/transformNodes/HiddenSuffixTransformNode.test.ts @@ -0,0 +1,35 @@ +import { expect, test } from 'vitest'; + +import { + constantValueNodeFromBytes, + constantValueNodeFromString, + hiddenSuffixTransformNode, + stringTypeNode, +} from '../../src'; + +test('it returns the right node kind', () => { + const node = hiddenSuffixTransformNode([constantValueNodeFromBytes('base16', 'ff')]); + expect(node.kind).toBe('hiddenSuffixTransformNode'); +}); + +test('it returns a frozen object', () => { + const node = hiddenSuffixTransformNode([constantValueNodeFromBytes('base16', 'ff')]); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it omits the suffix when the array is empty', () => { + const node = hiddenSuffixTransformNode([]); + expect('suffix' in node).toBe(false); +}); + +test('it keeps the provided suffix constants in order', () => { + const suffix = [constantValueNodeFromBytes('base16', 'ff'), constantValueNodeFromString('utf8', 'hello')]; + const node = hiddenSuffixTransformNode(suffix); + expect(node.suffix).toEqual(suffix); +}); + +test('it can be attached to a type node', () => { + const suffix = [constantValueNodeFromBytes('base16', 'ff')]; + const node = stringTypeNode('utf8', { transforms: [hiddenSuffixTransformNode(suffix)] }); + expect(node.transforms).toEqual([hiddenSuffixTransformNode(suffix)]); +}); diff --git a/packages/nodes/test/transformNodes/PostOffsetTransformNode.test.ts b/packages/nodes/test/transformNodes/PostOffsetTransformNode.test.ts new file mode 100644 index 000000000..5bc290f3a --- /dev/null +++ b/packages/nodes/test/transformNodes/PostOffsetTransformNode.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from 'vitest'; + +import { integerTypeNode, postOffsetTransformNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = postOffsetTransformNode(4); + expect(node.kind).toBe('postOffsetTransformNode'); +}); + +test('it returns a frozen object', () => { + const node = postOffsetTransformNode(4); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it defaults the strategy to relative', () => { + const node = postOffsetTransformNode(4); + expect(node.offset).toBe(4); + expect(node.strategy).toBe('relative'); +}); + +test.each(['relative', 'absolute', 'padded', 'preOffset'] as const)('it keeps the %s strategy', strategy => { + const node = postOffsetTransformNode(-2, { strategy }); + expect(node.offset).toBe(-2); + expect(node.strategy).toBe(strategy); +}); + +test('it can be attached to a type node', () => { + const node = integerTypeNode('u8', { transforms: [postOffsetTransformNode(4, { strategy: 'padded' })] }); + expect(node.transforms).toEqual([postOffsetTransformNode(4, { strategy: 'padded' })]); +}); diff --git a/packages/nodes/test/transformNodes/PreOffsetTransformNode.test.ts b/packages/nodes/test/transformNodes/PreOffsetTransformNode.test.ts new file mode 100644 index 000000000..e14c0dfd2 --- /dev/null +++ b/packages/nodes/test/transformNodes/PreOffsetTransformNode.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from 'vitest'; + +import { integerTypeNode, preOffsetTransformNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = preOffsetTransformNode(4); + expect(node.kind).toBe('preOffsetTransformNode'); +}); + +test('it returns a frozen object', () => { + const node = preOffsetTransformNode(4); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it defaults the strategy to relative', () => { + const node = preOffsetTransformNode(4); + expect(node.offset).toBe(4); + expect(node.strategy).toBe('relative'); +}); + +test.each(['relative', 'absolute', 'padded'] as const)('it keeps the %s strategy', strategy => { + const node = preOffsetTransformNode(-2, { strategy }); + expect(node.offset).toBe(-2); + expect(node.strategy).toBe(strategy); +}); + +test('it can be attached to a type node', () => { + const node = integerTypeNode('u8', { transforms: [preOffsetTransformNode(4, { strategy: 'absolute' })] }); + expect(node.transforms).toEqual([preOffsetTransformNode(4, { strategy: 'absolute' })]); +}); diff --git a/packages/nodes/test/transformNodes/SentinelTransformNode.test.ts b/packages/nodes/test/transformNodes/SentinelTransformNode.test.ts new file mode 100644 index 000000000..3b2a53ae7 --- /dev/null +++ b/packages/nodes/test/transformNodes/SentinelTransformNode.test.ts @@ -0,0 +1,25 @@ +import { expect, test } from 'vitest'; + +import { constantValueNodeFromBytes, sentinelTransformNode, stringTypeNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = sentinelTransformNode(constantValueNodeFromBytes('base16', '00')); + expect(node.kind).toBe('sentinelTransformNode'); +}); + +test('it returns a frozen object', () => { + const node = sentinelTransformNode(constantValueNodeFromBytes('base16', '00')); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it keeps the provided sentinel', () => { + const sentinel = constantValueNodeFromBytes('base16', '00'); + const node = sentinelTransformNode(sentinel); + expect(node.sentinel).toBe(sentinel); +}); + +test('it can be attached to a type node', () => { + const sentinel = constantValueNodeFromBytes('base16', '00'); + const node = stringTypeNode('utf8', { transforms: [sentinelTransformNode(sentinel)] }); + expect(node.transforms).toEqual([sentinelTransformNode(sentinel)]); +}); diff --git a/packages/nodes/test/transformNodes/SizePrefixTransformNode.test.ts b/packages/nodes/test/transformNodes/SizePrefixTransformNode.test.ts new file mode 100644 index 000000000..46b9237a8 --- /dev/null +++ b/packages/nodes/test/transformNodes/SizePrefixTransformNode.test.ts @@ -0,0 +1,23 @@ +import { expect, test } from 'vitest'; + +import { integerTypeNode, sizePrefixTransformNode, stringTypeNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = sizePrefixTransformNode(integerTypeNode('u32')); + expect(node.kind).toBe('sizePrefixTransformNode'); +}); + +test('it returns a frozen object', () => { + const node = sizePrefixTransformNode(integerTypeNode('u32')); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it keeps the provided prefix', () => { + const node = sizePrefixTransformNode(integerTypeNode('u32', { endian: 'be' })); + expect(node.prefix).toEqual(integerTypeNode('u32', { endian: 'be' })); +}); + +test('it can be attached to a type node', () => { + const node = stringTypeNode('utf8', { transforms: [sizePrefixTransformNode(integerTypeNode('u32'))] }); + expect(node.transforms).toEqual([sizePrefixTransformNode(integerTypeNode('u32'))]); +}); diff --git a/packages/nodes/test/transformNodes/TransformNode.typetest.ts b/packages/nodes/test/transformNodes/TransformNode.typetest.ts new file mode 100644 index 000000000..dfa99bb3b --- /dev/null +++ b/packages/nodes/test/transformNodes/TransformNode.typetest.ts @@ -0,0 +1,12 @@ +import type { TransformNode } from '@codama/node-types'; + +import { TRANSFORM_NODE_KINDS } from '../../src'; + +// [DESCRIBE] Transform node kinds. +{ + // It matches exactly with TransformNode['kind']. + { + TRANSFORM_NODE_KINDS satisfies readonly TransformNode['kind'][]; + null as unknown as TransformNode['kind'] satisfies (typeof TRANSFORM_NODE_KINDS)[number]; + } +} diff --git a/packages/nodes/test/typeNodes/AmountTypeNode.test.ts b/packages/nodes/test/typeNodes/AmountTypeNode.test.ts deleted file mode 100644 index 838edd677..000000000 --- a/packages/nodes/test/typeNodes/AmountTypeNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { amountTypeNode, numberTypeNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = amountTypeNode(numberTypeNode('u64'), 9); - expect(node.kind).toBe('amountTypeNode'); -}); - -test('it returns a frozen object', () => { - const node = amountTypeNode(numberTypeNode('u64'), 9); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/typeNodes/ArrayTypeNode.test.ts b/packages/nodes/test/typeNodes/ArrayTypeNode.test.ts index be879d58c..9c71b1736 100644 --- a/packages/nodes/test/typeNodes/ArrayTypeNode.test.ts +++ b/packages/nodes/test/typeNodes/ArrayTypeNode.test.ts @@ -1,13 +1,24 @@ import { expect, test } from 'vitest'; -import { arrayTypeNode, numberTypeNode, remainderCountNode } from '../../src'; +import { arrayTypeNode, fixedSizeTransformNode, integerTypeNode, remainderCountNode } from '../../src'; test('it returns the right node kind', () => { - const node = arrayTypeNode(numberTypeNode('u64'), remainderCountNode()); + const node = arrayTypeNode(integerTypeNode('u64'), remainderCountNode()); expect(node.kind).toBe('arrayTypeNode'); }); test('it returns a frozen object', () => { - const node = arrayTypeNode(numberTypeNode('u64'), remainderCountNode()); + const node = arrayTypeNode(integerTypeNode('u64'), remainderCountNode()); expect(Object.isFrozen(node)).toBe(true); }); + +test('it omits transforms when none are provided', () => { + const node = arrayTypeNode(integerTypeNode('u64'), remainderCountNode()); + expect('transforms' in node).toBe(false); +}); + +test('it keeps the provided transforms', () => { + const transforms = [fixedSizeTransformNode(32)]; + const node = arrayTypeNode(integerTypeNode('u64'), remainderCountNode(), { transforms }); + expect(node.transforms).toEqual(transforms); +}); diff --git a/packages/nodes/test/typeNodes/DateTimeTypeNode.test.ts b/packages/nodes/test/typeNodes/DateTimeTypeNode.test.ts index f9441a6d9..4188092cb 100644 --- a/packages/nodes/test/typeNodes/DateTimeTypeNode.test.ts +++ b/packages/nodes/test/typeNodes/DateTimeTypeNode.test.ts @@ -1,13 +1,23 @@ import { expect, test } from 'vitest'; -import { dateTimeTypeNode, numberTypeNode } from '../../src'; +import { dateTimeTypeNode, integerTypeNode } from '../../src'; test('it returns the right node kind', () => { - const node = dateTimeTypeNode(numberTypeNode('u64')); + const node = dateTimeTypeNode(integerTypeNode('u64')); expect(node.kind).toBe('dateTimeTypeNode'); }); test('it returns a frozen object', () => { - const node = dateTimeTypeNode(numberTypeNode('u64')); + const node = dateTimeTypeNode(integerTypeNode('u64')); expect(Object.isFrozen(node)).toBe(true); }); + +test('it omits ticksPerSecond when not provided', () => { + const node = dateTimeTypeNode(integerTypeNode('u64')); + expect('ticksPerSecond' in node).toBe(false); +}); + +test('it keeps the provided ticksPerSecond', () => { + const node = dateTimeTypeNode(integerTypeNode('i64'), { ticksPerSecond: 1000 }); + expect(node.ticksPerSecond).toBe(1000); +}); diff --git a/packages/nodes/test/typeNodes/DurationTypeNode.test.ts b/packages/nodes/test/typeNodes/DurationTypeNode.test.ts new file mode 100644 index 000000000..ef6797349 --- /dev/null +++ b/packages/nodes/test/typeNodes/DurationTypeNode.test.ts @@ -0,0 +1,28 @@ +import { expect, test } from 'vitest'; + +import { durationTypeNode, integerTypeNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = durationTypeNode(integerTypeNode('u64')); + expect(node.kind).toBe('durationTypeNode'); +}); + +test('it returns a frozen object', () => { + const node = durationTypeNode(integerTypeNode('u64')); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it keeps the underlying integer', () => { + const node = durationTypeNode(integerTypeNode('i64')); + expect(node.number).toEqual(integerTypeNode('i64')); +}); + +test('it omits ticksPerSecond when not provided', () => { + const node = durationTypeNode(integerTypeNode('u64')); + expect('ticksPerSecond' in node).toBe(false); +}); + +test('it keeps the provided ticksPerSecond', () => { + const node = durationTypeNode(integerTypeNode('u64'), { ticksPerSecond: 1000 }); + expect(node.ticksPerSecond).toBe(1000); +}); diff --git a/packages/nodes/test/typeNodes/EnumEmptyVariantTypeNode.test.ts b/packages/nodes/test/typeNodes/EnumEmptyVariantTypeNode.test.ts deleted file mode 100644 index c4dd9816c..000000000 --- a/packages/nodes/test/typeNodes/EnumEmptyVariantTypeNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { enumEmptyVariantTypeNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = enumEmptyVariantTypeNode('apple'); - expect(node.kind).toBe('enumEmptyVariantTypeNode'); -}); - -test('it returns a frozen object', () => { - const node = enumEmptyVariantTypeNode('apple'); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/typeNodes/EnumStructVariantTypeNode.test.ts b/packages/nodes/test/typeNodes/EnumStructVariantTypeNode.test.ts deleted file mode 100644 index 0f7bed31d..000000000 --- a/packages/nodes/test/typeNodes/EnumStructVariantTypeNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { enumStructVariantTypeNode, structTypeNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = enumStructVariantTypeNode('apple', structTypeNode([])); - expect(node.kind).toBe('enumStructVariantTypeNode'); -}); - -test('it returns a frozen object', () => { - const node = enumStructVariantTypeNode('apple', structTypeNode([])); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/typeNodes/EnumTupleVariantTypeNode.test.ts b/packages/nodes/test/typeNodes/EnumTupleVariantTypeNode.test.ts deleted file mode 100644 index 83c97dcfe..000000000 --- a/packages/nodes/test/typeNodes/EnumTupleVariantTypeNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { enumTupleVariantTypeNode, tupleTypeNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = enumTupleVariantTypeNode('apple', tupleTypeNode([])); - expect(node.kind).toBe('enumTupleVariantTypeNode'); -}); - -test('it returns a frozen object', () => { - const node = enumTupleVariantTypeNode('apple', tupleTypeNode([])); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/typeNodes/EnumTypeNode.test.ts b/packages/nodes/test/typeNodes/EnumTypeNode.test.ts index e9eb117cd..4b053b757 100644 --- a/packages/nodes/test/typeNodes/EnumTypeNode.test.ts +++ b/packages/nodes/test/typeNodes/EnumTypeNode.test.ts @@ -1,6 +1,15 @@ import { expect, test } from 'vitest'; -import { enumTypeNode } from '../../src'; +import { + enumTypeNode, + enumVariantTypeNode, + integerTypeNode, + isDataEnum, + isScalarEnum, + structFieldTypeNode, + structTypeNode, + tupleTypeNode, +} from '../../src'; test('it returns the right node kind', () => { const node = enumTypeNode([]); @@ -11,3 +20,38 @@ test('it returns a frozen object', () => { const node = enumTypeNode([]); expect(Object.isFrozen(node)).toBe(true); }); + +test('it defaults the size to a u8', () => { + const node = enumTypeNode([]); + expect(node.size).toEqual(integerTypeNode('u8')); +}); + +test('it omits variants when the array is empty', () => { + const node = enumTypeNode([]); + expect('variants' in node).toBe(false); +}); + +test('it keeps the provided variants and size', () => { + const variants = [enumVariantTypeNode('apple'), enumVariantTypeNode('banana')]; + const node = enumTypeNode(variants, { size: integerTypeNode('u32') }); + expect(node.variants).toEqual(variants); + expect(node.size).toEqual(integerTypeNode('u32')); +}); + +test('it identifies scalar enums as those whose variants carry no data', () => { + const scalar = enumTypeNode([enumVariantTypeNode('apple'), enumVariantTypeNode('banana')]); + expect(isScalarEnum(scalar)).toBe(true); + expect(isDataEnum(scalar)).toBe(false); +}); + +test('it identifies data enums as those with at least one variant carrying data', () => { + const data = enumTypeNode([ + enumVariantTypeNode('unit'), + enumVariantTypeNode('tuple', { data: tupleTypeNode([integerTypeNode('u8')]) }), + enumVariantTypeNode('struct', { + data: structTypeNode([structFieldTypeNode({ identifier: 'value', type: integerTypeNode('u8') })]), + }), + ]); + expect(isScalarEnum(data)).toBe(false); + expect(isDataEnum(data)).toBe(true); +}); diff --git a/packages/nodes/test/typeNodes/EnumVariantTypeNode.test.ts b/packages/nodes/test/typeNodes/EnumVariantTypeNode.test.ts new file mode 100644 index 000000000..4206fcc0f --- /dev/null +++ b/packages/nodes/test/typeNodes/EnumVariantTypeNode.test.ts @@ -0,0 +1,72 @@ +import { CODAMA_ERROR__INVALID_BRANDED_STRING, CodamaError } from '@codama/errors'; +import { expect, test } from 'vitest'; + +import { + enumVariantDisplayNode, + enumVariantTypeNode, + integerTypeNode, + publicKeyTypeNode, + structFieldTypeNode, + structTypeNode, + tupleTypeNode, +} from '../../src'; + +test('it returns the right node kind', () => { + const node = enumVariantTypeNode('apple'); + expect(node.kind).toBe('enumVariantTypeNode'); +}); + +test('it returns a frozen object', () => { + const node = enumVariantTypeNode('apple'); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it creates a unit variant when no data is provided', () => { + const node = enumVariantTypeNode('apple'); + expect(node.identifier).toBe('apple'); + expect('data' in node).toBe(false); +}); + +test('it creates a struct variant when given a struct type', () => { + const data = structTypeNode([structFieldTypeNode({ identifier: 'weight', type: integerTypeNode('u32') })]); + const node = enumVariantTypeNode('apple', { data }); + expect(node.data).toBe(data); +}); + +test('it creates a tuple variant when given a tuple type', () => { + const data = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); + const node = enumVariantTypeNode('apple', { data }); + expect(node.data).toBe(data); +}); + +test('it carries any other type node as-is', () => { + const node = enumVariantTypeNode('apple', { data: publicKeyTypeNode() }); + expect(node.data).toEqual(publicKeyTypeNode()); +}); + +test('it omits the discriminator when not provided', () => { + const node = enumVariantTypeNode('apple'); + expect('discriminator' in node).toBe(false); +}); + +test('it keeps the provided discriminator, docs and display', () => { + const display = enumVariantDisplayNode({ label: 'Apple' }); + const node = enumVariantTypeNode('apple', { discriminator: 42, display, docs: 'A red fruit.' }); + expect(node.discriminator).toBe(42); + expect(node.docs).toBe('A red fruit.'); + expect(node.display).toBe(display); +}); + +test('it preserves the identifier casing', () => { + const node = enumVariantTypeNode('RedApple'); + expect(node.identifier).toBe('RedApple'); +}); + +test('it rejects invalid identifiers', () => { + expect(() => enumVariantTypeNode('1apple')).toThrow( + new CodamaError(CODAMA_ERROR__INVALID_BRANDED_STRING, { + actual: '1apple', + expected: 'identifier (letters, digits and underscores; no leading digit)', + }), + ); +}); diff --git a/packages/nodes/test/typeNodes/FixedPointTypeNode.test.ts b/packages/nodes/test/typeNodes/FixedPointTypeNode.test.ts new file mode 100644 index 000000000..28ec531c5 --- /dev/null +++ b/packages/nodes/test/typeNodes/FixedPointTypeNode.test.ts @@ -0,0 +1,37 @@ +import { expect, test } from 'vitest'; + +import { fixedPointTypeNode, integerTypeNode, stringValueNode, unitNumberDisplayNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = fixedPointTypeNode(integerTypeNode('u64'), 9); + expect(node.kind).toBe('fixedPointTypeNode'); +}); + +test('it returns a frozen object', () => { + const node = fixedPointTypeNode(integerTypeNode('u64'), 9); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it keeps the underlying integer and scale', () => { + const node = fixedPointTypeNode(integerTypeNode('u64'), 9); + expect(node.number).toEqual(integerTypeNode('u64')); + expect(node.scale).toBe(9); +}); + +test('it omits base and unit when not provided', () => { + const node = fixedPointTypeNode(integerTypeNode('u64'), 9); + expect('base' in node).toBe(false); + expect('unit' in node).toBe(false); +}); + +test('it keeps the provided base and unit', () => { + const node = fixedPointTypeNode(integerTypeNode('u128'), 64, { base: 2, unit: 'SOL' }); + expect(node.base).toBe(2); + expect(node.unit).toBe('SOL'); +}); + +test('it can have a unit display node', () => { + const display = unitNumberDisplayNode({ unit: stringValueNode('SOL') }); + const node = fixedPointTypeNode(integerTypeNode('u64'), 9, { display }); + expect(node.display).toBe(display); +}); diff --git a/packages/nodes/test/typeNodes/FixedSizeTypeNode.test.ts b/packages/nodes/test/typeNodes/FixedSizeTypeNode.test.ts deleted file mode 100644 index 1f19c066e..000000000 --- a/packages/nodes/test/typeNodes/FixedSizeTypeNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { fixedSizeTypeNode, stringTypeNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = fixedSizeTypeNode(stringTypeNode('utf8'), 42); - expect(node.kind).toBe('fixedSizeTypeNode'); -}); - -test('it returns a frozen object', () => { - const node = fixedSizeTypeNode(stringTypeNode('utf8'), 42); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/typeNodes/FloatTypeNode.test.ts b/packages/nodes/test/typeNodes/FloatTypeNode.test.ts new file mode 100644 index 000000000..8adabe558 --- /dev/null +++ b/packages/nodes/test/typeNodes/FloatTypeNode.test.ts @@ -0,0 +1,39 @@ +import { expect, test } from 'vitest'; + +import { floatTypeNode, preOffsetTransformNode, stringValueNode, unitNumberDisplayNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = floatTypeNode('f32'); + expect(node.kind).toBe('floatTypeNode'); +}); + +test('it returns a frozen object', () => { + const node = floatTypeNode('f32'); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it defaults the endianness to little-endian', () => { + const node = floatTypeNode('f64'); + expect(node.format).toBe('f64'); + expect(node.endian).toBe('le'); +}); + +test('it keeps the provided endianness', () => { + const node = floatTypeNode('f32', { endian: 'be' }); + expect(node.endian).toBe('be'); +}); + +test('it omits unit, display and transforms when not provided', () => { + const node = floatTypeNode('f32'); + expect('unit' in node).toBe(false); + expect('display' in node).toBe(false); + expect('transforms' in node).toBe(false); +}); + +test('it keeps the provided unit, display and transforms', () => { + const display = unitNumberDisplayNode({ unit: stringValueNode('%') }); + const node = floatTypeNode('f64', { display, transforms: [preOffsetTransformNode(4)], unit: '%' }); + expect(node.unit).toBe('%'); + expect(node.display).toBe(display); + expect(node.transforms).toEqual([preOffsetTransformNode(4)]); +}); diff --git a/packages/nodes/test/typeNodes/HiddenPrefixTypeNode.test.ts b/packages/nodes/test/typeNodes/HiddenPrefixTypeNode.test.ts deleted file mode 100644 index f9980cd5c..000000000 --- a/packages/nodes/test/typeNodes/HiddenPrefixTypeNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { hiddenPrefixTypeNode, numberTypeNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = hiddenPrefixTypeNode(numberTypeNode('u8'), []); - expect(node.kind).toBe('hiddenPrefixTypeNode'); -}); - -test('it returns a frozen object', () => { - const node = hiddenPrefixTypeNode(numberTypeNode('u8'), []); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/typeNodes/HiddenSuffixTypeNode.test.ts b/packages/nodes/test/typeNodes/HiddenSuffixTypeNode.test.ts deleted file mode 100644 index 62296878a..000000000 --- a/packages/nodes/test/typeNodes/HiddenSuffixTypeNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { hiddenSuffixTypeNode, numberTypeNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = hiddenSuffixTypeNode(numberTypeNode('u8'), []); - expect(node.kind).toBe('hiddenSuffixTypeNode'); -}); - -test('it returns a frozen object', () => { - const node = hiddenSuffixTypeNode(numberTypeNode('u8'), []); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/typeNodes/IntegerTypeNode.test.ts b/packages/nodes/test/typeNodes/IntegerTypeNode.test.ts new file mode 100644 index 000000000..7f7bb7dfd --- /dev/null +++ b/packages/nodes/test/typeNodes/IntegerTypeNode.test.ts @@ -0,0 +1,39 @@ +import { expect, test } from 'vitest'; + +import { fixedSizeTransformNode, integerTypeNode, stringValueNode, unitNumberDisplayNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = integerTypeNode('u8'); + expect(node.kind).toBe('integerTypeNode'); +}); + +test('it returns a frozen object', () => { + const node = integerTypeNode('u8'); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it defaults the endianness to little-endian', () => { + const node = integerTypeNode('u64'); + expect(node.format).toBe('u64'); + expect(node.endian).toBe('le'); +}); + +test('it keeps the provided endianness', () => { + const node = integerTypeNode('i128', { endian: 'be' }); + expect(node.endian).toBe('be'); +}); + +test('it omits unit, display and transforms when not provided', () => { + const node = integerTypeNode('u8'); + expect('unit' in node).toBe(false); + expect('display' in node).toBe(false); + expect('transforms' in node).toBe(false); +}); + +test('it keeps the provided unit, display and transforms', () => { + const display = unitNumberDisplayNode({ unit: stringValueNode('lamports') }); + const node = integerTypeNode('u64', { display, transforms: [fixedSizeTransformNode(16)], unit: 'lamports' }); + expect(node.unit).toBe('lamports'); + expect(node.display).toBe(display); + expect(node.transforms).toEqual([fixedSizeTransformNode(16)]); +}); diff --git a/packages/nodes/test/typeNodes/MapTypeNode.test.ts b/packages/nodes/test/typeNodes/MapTypeNode.test.ts index 998f816d6..97328c38a 100644 --- a/packages/nodes/test/typeNodes/MapTypeNode.test.ts +++ b/packages/nodes/test/typeNodes/MapTypeNode.test.ts @@ -1,13 +1,13 @@ import { expect, test } from 'vitest'; -import { mapTypeNode, numberTypeNode, remainderCountNode } from '../../src'; +import { integerTypeNode, mapTypeNode, remainderCountNode } from '../../src'; test('it returns the right node kind', () => { - const node = mapTypeNode(numberTypeNode('u8'), numberTypeNode('u64'), remainderCountNode()); + const node = mapTypeNode(integerTypeNode('u8'), integerTypeNode('u64'), remainderCountNode()); expect(node.kind).toBe('mapTypeNode'); }); test('it returns a frozen object', () => { - const node = mapTypeNode(numberTypeNode('u8'), numberTypeNode('u64'), remainderCountNode()); + const node = mapTypeNode(integerTypeNode('u8'), integerTypeNode('u64'), remainderCountNode()); expect(Object.isFrozen(node)).toBe(true); }); diff --git a/packages/nodes/test/typeNodes/NestedTypeNode.test.ts b/packages/nodes/test/typeNodes/NestedTypeNode.test.ts deleted file mode 100644 index a42d17df6..000000000 --- a/packages/nodes/test/typeNodes/NestedTypeNode.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe, expect, test } from 'vitest'; - -import { - assertIsNestedTypeNode, - fixedSizeTypeNode, - isNestedTypeNode, - numberTypeNode, - publicKeyTypeNode, - resolveNestedTypeNode, - sizePrefixTypeNode, - stringTypeNode, - transformNestedTypeNode, -} from '../../src'; - -describe('resolveNestedTypeNode', () => { - test('it resolved nested type nodes', () => { - const node = sizePrefixTypeNode(fixedSizeTypeNode(stringTypeNode('utf8'), 32), numberTypeNode('u8')); - expect(resolveNestedTypeNode(node)).toEqual(stringTypeNode('utf8')); - }); - - test('it returns the same instance when resolving nested types nodes', () => { - const node = numberTypeNode('u8'); - expect(resolveNestedTypeNode(node)).toBe(node); - }); -}); - -describe('transformNestedTypeNode', () => { - test('it transforms nested type nodes', () => { - const node = sizePrefixTypeNode(fixedSizeTypeNode(stringTypeNode('utf8'), 32), numberTypeNode('u8')); - const transformedNode = transformNestedTypeNode(node, () => publicKeyTypeNode()); - expect(transformedNode).toEqual( - sizePrefixTypeNode(fixedSizeTypeNode(publicKeyTypeNode(), 32), numberTypeNode('u8')), - ); - }); -}); - -describe('isNestedTypeNode', () => { - test('it checks if a node is a nested type', () => { - const flatNode = numberTypeNode('u64'); - expect(isNestedTypeNode(flatNode, 'numberTypeNode')).toBe(true); - expect(isNestedTypeNode(flatNode, 'stringTypeNode')).toBe(false); - - const nestedNode = sizePrefixTypeNode(fixedSizeTypeNode(numberTypeNode('u64'), 32), numberTypeNode('u8')); - expect(isNestedTypeNode(nestedNode, 'numberTypeNode')).toBe(true); - expect(isNestedTypeNode(nestedNode, 'stringTypeNode')).toBe(false); - }); -}); - -describe('assertIsNestedTypeNode', () => { - test('it asserts that a node is a nested type', () => { - const flatNode = numberTypeNode('u64'); - expect(() => assertIsNestedTypeNode(flatNode, 'numberTypeNode')).not.toThrow(); - expect(() => assertIsNestedTypeNode(flatNode, 'stringTypeNode')).toThrow(); - - const nestedNode = sizePrefixTypeNode(fixedSizeTypeNode(numberTypeNode('u64'), 32), numberTypeNode('u8')); - expect(() => assertIsNestedTypeNode(nestedNode, 'numberTypeNode')).not.toThrow(); - expect(() => assertIsNestedTypeNode(nestedNode, 'stringTypeNode')).toThrow(); - }); -}); diff --git a/packages/nodes/test/typeNodes/NestedTypeNode.typetest.ts b/packages/nodes/test/typeNodes/NestedTypeNode.typetest.ts deleted file mode 100644 index 010a052d3..000000000 --- a/packages/nodes/test/typeNodes/NestedTypeNode.typetest.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { - assertIsNestedTypeNode, - fixedSizeTypeNode, - isNestedTypeNode, - NestedTypeNode, - Node, - NumberTypeNode, - numberTypeNode, - resolveNestedTypeNode, - StringTypeNode, - stringTypeNode, - transformNestedTypeNode, -} from '../../src'; - -// [DESCRIBE] NestedTypeNode. -{ - // It constraints the nested type of a node. - { - const stringNestedNode = fixedSizeTypeNode(stringTypeNode('utf8'), 32); - const numberNestedNode = fixedSizeTypeNode(numberTypeNode('u32'), 32); - stringNestedNode satisfies NestedTypeNode; - numberNestedNode satisfies NestedTypeNode; - // @ts-expect-error The nested type is not a number. - stringNestedNode satisfies NestedTypeNode; - // @ts-expect-error The nested type is not a string. - numberNestedNode satisfies NestedTypeNode; - } -} - -// [DESCRIBE] resolveNestedTypeNode. -{ - // It unwraps the nested type of a node. - { - const stringNestedNode = fixedSizeTypeNode(stringTypeNode('utf8'), 32); - const numberNestedNode = fixedSizeTypeNode(numberTypeNode('u32'), 32); - resolveNestedTypeNode(stringNestedNode) satisfies StringTypeNode; - resolveNestedTypeNode(numberNestedNode) satisfies NumberTypeNode; - // @ts-expect-error The nested type is not a number. - resolveNestedTypeNode(stringNestedNode) satisfies NumberTypeNode; - // @ts-expect-error The nested type is not a string. - resolveNestedTypeNode(numberNestedNode) satisfies StringTypeNode; - } -} - -// [DESCRIBE] transformNestedTypeNode. -{ - // It transforms the nested type of a nested node. - { - const transformedNode = transformNestedTypeNode(fixedSizeTypeNode(stringTypeNode('utf8'), 32), () => - numberTypeNode('u32'), - ); - transformedNode satisfies NestedTypeNode; - // @ts-expect-error The nested type is not a number. - transformedNode satisfies NestedTypeNode; - } -} - -// [DESCRIBE] isNestedTypeNode. -{ - // It narrows the type of a node to a nested type node. - { - const node = {} as Node; - if (isNestedTypeNode(node, 'numberTypeNode')) { - node satisfies NestedTypeNode; - // @ts-expect-error The nested type is not a string. - node satisfies NestedTypeNode; - } - if (isNestedTypeNode(node, 'stringTypeNode')) { - node satisfies NestedTypeNode; - // @ts-expect-error The nested type is not a number. - node satisfies NestedTypeNode; - } - } -} - -// [DESCRIBE] assertIsNestedTypeNode. -{ - // It narrows the type of a node to a nested type node. - { - { - const node = {} as Node; - assertIsNestedTypeNode(node, 'numberTypeNode'); - node satisfies NestedTypeNode; - // @ts-expect-error The nested type is not a string. - node satisfies NestedTypeNode; - } - { - const node = {} as Node; - assertIsNestedTypeNode(node, 'stringTypeNode'); - node satisfies NestedTypeNode; - // @ts-expect-error The nested type is not a number. - node satisfies NestedTypeNode; - } - } -} diff --git a/packages/nodes/test/typeNodes/NumberTypeNode.test.ts b/packages/nodes/test/typeNodes/NumberTypeNode.test.ts deleted file mode 100644 index d846a06db..000000000 --- a/packages/nodes/test/typeNodes/NumberTypeNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { numberTypeNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = numberTypeNode('u8'); - expect(node.kind).toBe('numberTypeNode'); -}); - -test('it returns a frozen object', () => { - const node = numberTypeNode('u8'); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/typeNodes/OptionTypeNode.test.ts b/packages/nodes/test/typeNodes/OptionTypeNode.test.ts index bc5953ca7..8db6441b1 100644 --- a/packages/nodes/test/typeNodes/OptionTypeNode.test.ts +++ b/packages/nodes/test/typeNodes/OptionTypeNode.test.ts @@ -1,13 +1,25 @@ import { expect, test } from 'vitest'; -import { numberTypeNode, optionTypeNode } from '../../src'; +import { integerTypeNode, optionTypeNode } from '../../src'; test('it returns the right node kind', () => { - const node = optionTypeNode(numberTypeNode('u8')); + const node = optionTypeNode(integerTypeNode('u8')); expect(node.kind).toBe('optionTypeNode'); }); test('it returns a frozen object', () => { - const node = optionTypeNode(numberTypeNode('u8')); + const node = optionTypeNode(integerTypeNode('u8')); expect(Object.isFrozen(node)).toBe(true); }); + +test('it defaults to a u8 prefix and a non-fixed layout', () => { + const node = optionTypeNode(integerTypeNode('u8')); + expect(node.prefix).toEqual(integerTypeNode('u8')); + expect(node.fixed).toBe(false); +}); + +test('it keeps the provided prefix and fixed flag', () => { + const node = optionTypeNode(integerTypeNode('u8'), { fixed: true, prefix: integerTypeNode('u32') }); + expect(node.prefix).toEqual(integerTypeNode('u32')); + expect(node.fixed).toBe(true); +}); diff --git a/packages/nodes/test/typeNodes/PostOffsetTypeNode.test.ts b/packages/nodes/test/typeNodes/PostOffsetTypeNode.test.ts deleted file mode 100644 index 072679764..000000000 --- a/packages/nodes/test/typeNodes/PostOffsetTypeNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { numberTypeNode, postOffsetTypeNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = postOffsetTypeNode(numberTypeNode('u8'), 42); - expect(node.kind).toBe('postOffsetTypeNode'); -}); - -test('it returns a frozen object', () => { - const node = postOffsetTypeNode(numberTypeNode('u8'), 42); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/typeNodes/PreOffsetTypeNode.test.ts b/packages/nodes/test/typeNodes/PreOffsetTypeNode.test.ts deleted file mode 100644 index 4b499793d..000000000 --- a/packages/nodes/test/typeNodes/PreOffsetTypeNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { numberTypeNode, preOffsetTypeNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = preOffsetTypeNode(numberTypeNode('u8'), 42); - expect(node.kind).toBe('preOffsetTypeNode'); -}); - -test('it returns a frozen object', () => { - const node = preOffsetTypeNode(numberTypeNode('u8'), 42); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/typeNodes/RemainderOptionTypeNode.test.ts b/packages/nodes/test/typeNodes/RemainderOptionTypeNode.test.ts index eba164229..76efb1b34 100644 --- a/packages/nodes/test/typeNodes/RemainderOptionTypeNode.test.ts +++ b/packages/nodes/test/typeNodes/RemainderOptionTypeNode.test.ts @@ -1,13 +1,13 @@ import { expect, test } from 'vitest'; -import { numberTypeNode, remainderOptionTypeNode } from '../../src'; +import { integerTypeNode, remainderOptionTypeNode } from '../../src'; test('it returns the right node kind', () => { - const node = remainderOptionTypeNode(numberTypeNode('u8')); + const node = remainderOptionTypeNode(integerTypeNode('u8')); expect(node.kind).toBe('remainderOptionTypeNode'); }); test('it returns a frozen object', () => { - const node = remainderOptionTypeNode(numberTypeNode('u8')); + const node = remainderOptionTypeNode(integerTypeNode('u8')); expect(Object.isFrozen(node)).toBe(true); }); diff --git a/packages/nodes/test/typeNodes/SentinelTypeNode.test.ts b/packages/nodes/test/typeNodes/SentinelTypeNode.test.ts deleted file mode 100644 index e302dc4f4..000000000 --- a/packages/nodes/test/typeNodes/SentinelTypeNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { constantValueNodeFromBytes, sentinelTypeNode, stringTypeNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = sentinelTypeNode(stringTypeNode('utf8'), constantValueNodeFromBytes('base16', 'ff')); - expect(node.kind).toBe('sentinelTypeNode'); -}); - -test('it returns a frozen object', () => { - const node = sentinelTypeNode(stringTypeNode('utf8'), constantValueNodeFromBytes('base16', 'ff')); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/typeNodes/SetTypeNode.test.ts b/packages/nodes/test/typeNodes/SetTypeNode.test.ts index 0f590ac8e..c5c84e29d 100644 --- a/packages/nodes/test/typeNodes/SetTypeNode.test.ts +++ b/packages/nodes/test/typeNodes/SetTypeNode.test.ts @@ -1,13 +1,13 @@ import { expect, test } from 'vitest'; -import { numberTypeNode, remainderCountNode, setTypeNode } from '../../src'; +import { integerTypeNode, remainderCountNode, setTypeNode } from '../../src'; test('it returns the right node kind', () => { - const node = setTypeNode(numberTypeNode('u64'), remainderCountNode()); + const node = setTypeNode(integerTypeNode('u64'), remainderCountNode()); expect(node.kind).toBe('setTypeNode'); }); test('it returns a frozen object', () => { - const node = setTypeNode(numberTypeNode('u64'), remainderCountNode()); + const node = setTypeNode(integerTypeNode('u64'), remainderCountNode()); expect(Object.isFrozen(node)).toBe(true); }); diff --git a/packages/nodes/test/typeNodes/SizePrefixTypeNode.test.ts b/packages/nodes/test/typeNodes/SizePrefixTypeNode.test.ts deleted file mode 100644 index e81823cb6..000000000 --- a/packages/nodes/test/typeNodes/SizePrefixTypeNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { numberTypeNode, sizePrefixTypeNode, stringTypeNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = sizePrefixTypeNode(stringTypeNode('utf8'), numberTypeNode('u32')); - expect(node.kind).toBe('sizePrefixTypeNode'); -}); - -test('it returns a frozen object', () => { - const node = sizePrefixTypeNode(stringTypeNode('utf8'), numberTypeNode('u32')); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/typeNodes/SolAmountTypeNode.test.ts b/packages/nodes/test/typeNodes/SolAmountTypeNode.test.ts deleted file mode 100644 index b0a92051d..000000000 --- a/packages/nodes/test/typeNodes/SolAmountTypeNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { numberTypeNode, solAmountTypeNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = solAmountTypeNode(numberTypeNode('u32')); - expect(node.kind).toBe('solAmountTypeNode'); -}); - -test('it returns a frozen object', () => { - const node = solAmountTypeNode(numberTypeNode('u32')); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/typeNodes/StructFieldTypeNode.test.ts b/packages/nodes/test/typeNodes/StructFieldTypeNode.test.ts index 58091b748..4fd9b695d 100644 --- a/packages/nodes/test/typeNodes/StructFieldTypeNode.test.ts +++ b/packages/nodes/test/typeNodes/StructFieldTypeNode.test.ts @@ -1,13 +1,34 @@ import { expect, test } from 'vitest'; -import { numberTypeNode, structFieldTypeNode } from '../../src'; +import { integerTypeNode, integerValueNode, structFieldTypeNode } from '../../src'; test('it returns the right node kind', () => { - const node = structFieldTypeNode({ name: 'age', type: numberTypeNode('u8') }); + const node = structFieldTypeNode({ identifier: 'age', type: integerTypeNode('u8') }); expect(node.kind).toBe('structFieldTypeNode'); }); test('it returns a frozen object', () => { - const node = structFieldTypeNode({ name: 'age', type: numberTypeNode('u8') }); + const node = structFieldTypeNode({ identifier: 'age', type: integerTypeNode('u8') }); expect(Object.isFrozen(node)).toBe(true); }); + +test('it preserves the identifier casing', () => { + const node = structFieldTypeNode({ identifier: 'my_field', type: integerTypeNode('u8') }); + expect(node.identifier).toBe('my_field'); +}); + +test('it can have a default value and strategy', () => { + const node = structFieldTypeNode({ + defaultValue: integerValueNode('42'), + defaultValueStrategy: 'omitted', + identifier: 'age', + type: integerTypeNode('u8'), + }); + expect(node.defaultValue).toEqual(integerValueNode('42')); + expect(node.defaultValueStrategy).toBe('omitted'); +}); + +test('it can have single-string documentation', () => { + const node = structFieldTypeNode({ docs: 'The age.', identifier: 'age', type: integerTypeNode('u8') }); + expect(node.docs).toBe('The age.'); +}); diff --git a/packages/nodes/test/typeNodes/TypeNode.test.ts b/packages/nodes/test/typeNodes/TypeNode.test.ts new file mode 100644 index 000000000..cacdb245c --- /dev/null +++ b/packages/nodes/test/typeNodes/TypeNode.test.ts @@ -0,0 +1,44 @@ +import { expect, test } from 'vitest'; + +import { + addTypeNodeTransforms, + fixedSizeTransformNode, + integerTypeNode, + sizePrefixTransformNode, + stringTypeNode, +} from '../../src'; + +test('it appends transforms to a type node with none', () => { + const node = addTypeNodeTransforms(stringTypeNode('utf8'), [fixedSizeTransformNode(32)]); + expect(node.transforms).toEqual([fixedSizeTransformNode(32)]); +}); + +test('it appends transforms after the existing ones', () => { + const original = stringTypeNode('utf8', { transforms: [sizePrefixTransformNode(integerTypeNode('u32'))] }); + const node = addTypeNodeTransforms(original, [fixedSizeTransformNode(32)]); + expect(node.transforms).toEqual([sizePrefixTransformNode(integerTypeNode('u32')), fixedSizeTransformNode(32)]); +}); + +test('it keeps the rest of the type node intact', () => { + const node = addTypeNodeTransforms(stringTypeNode('base58'), [fixedSizeTransformNode(32)]); + expect(node.kind).toBe('stringTypeNode'); + expect(node.encoding).toBe('base58'); +}); + +test('it returns the same node when given no transforms', () => { + const original = stringTypeNode('utf8'); + const node = addTypeNodeTransforms(original, []); + expect(node).toBe(original); + expect(node.transforms).toBeUndefined(); +}); + +test('it does not mutate the original node', () => { + const original = stringTypeNode('utf8', { transforms: [sizePrefixTransformNode(integerTypeNode('u32'))] }); + addTypeNodeTransforms(original, [fixedSizeTransformNode(32)]); + expect(original.transforms).toEqual([sizePrefixTransformNode(integerTypeNode('u32'))]); +}); + +test('it returns a frozen object', () => { + const node = addTypeNodeTransforms(stringTypeNode('utf8'), [fixedSizeTransformNode(32)]); + expect(Object.isFrozen(node)).toBe(true); +}); diff --git a/packages/nodes/test/typeNodes/TypeNode.typetest.ts b/packages/nodes/test/typeNodes/TypeNode.typetest.ts index 1e4fc4bf1..e84a00790 100644 --- a/packages/nodes/test/typeNodes/TypeNode.typetest.ts +++ b/packages/nodes/test/typeNodes/TypeNode.typetest.ts @@ -2,7 +2,7 @@ import type { RegisteredTypeNode } from '@codama/node-types'; import { REGISTERED_TYPE_NODE_KINDS } from '../../src'; -// [DESCRIBE] Registered value node kinds. +// [DESCRIBE] Registered type node kinds. { // It matches exactly with RegisteredTypeNode['kind']. { diff --git a/packages/nodes/test/valueNodes/ConstantValueNode.test.ts b/packages/nodes/test/valueNodes/ConstantValueNode.test.ts index f7c2e79da..ac7f4d2f5 100644 --- a/packages/nodes/test/valueNodes/ConstantValueNode.test.ts +++ b/packages/nodes/test/valueNodes/ConstantValueNode.test.ts @@ -1,13 +1,33 @@ import { expect, test } from 'vitest'; -import { constantValueNode, numberTypeNode, numberValueNode } from '../../src'; +import { + bytesTypeNode, + bytesValueNode, + constantValueNode, + constantValueNodeFromBytes, + constantValueNodeFromString, + integerTypeNode, + integerValueNode, + stringTypeNode, + stringValueNode, +} from '../../src'; test('it returns the right node kind', () => { - const node = constantValueNode(numberTypeNode('u8'), numberValueNode(42)); + const node = constantValueNode(integerTypeNode('u8'), integerValueNode('42')); expect(node.kind).toBe('constantValueNode'); }); test('it returns a frozen object', () => { - const node = constantValueNode(numberTypeNode('u8'), numberValueNode(42)); + const node = constantValueNode(integerTypeNode('u8'), integerValueNode('42')); expect(Object.isFrozen(node)).toBe(true); }); + +test('it can be created from a string', () => { + const node = constantValueNodeFromString('utf8', 'hello'); + expect(node).toEqual(constantValueNode(stringTypeNode('utf8'), stringValueNode('hello'))); +}); + +test('it can be created from bytes', () => { + const node = constantValueNodeFromBytes('base16', 'aabbccdd'); + expect(node).toEqual(constantValueNode(bytesTypeNode(), bytesValueNode('base16', 'aabbccdd'))); +}); diff --git a/packages/nodes/test/valueNodes/FloatValueNode.test.ts b/packages/nodes/test/valueNodes/FloatValueNode.test.ts new file mode 100644 index 000000000..bba84e789 --- /dev/null +++ b/packages/nodes/test/valueNodes/FloatValueNode.test.ts @@ -0,0 +1,40 @@ +import { CODAMA_ERROR__INVALID_BRANDED_STRING, CodamaError } from '@codama/errors'; +import { expect, test } from 'vitest'; + +import { floatValueNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = floatValueNode('1.5'); + expect(node.kind).toBe('floatValueNode'); +}); + +test('it returns a frozen object', () => { + const node = floatValueNode('1.5'); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it stores the value as a string', () => { + const node = floatValueNode('1.5'); + expect(node.value).toBe('1.5'); +}); + +test('it accepts integers, negative decimals and the special values', () => { + expect(floatValueNode('0').value).toBe('0'); + expect(floatValueNode('42').value).toBe('42'); + expect(floatValueNode('-0.25').value).toBe('-0.25'); + expect(floatValueNode('NaN').value).toBe('NaN'); + expect(floatValueNode('Infinity').value).toBe('Infinity'); + expect(floatValueNode('-Infinity').value).toBe('-Infinity'); +}); + +test.each(['', '1.50', '.5', '1.', '01.5', '1e3', '+1.5', 'abc', 'nan'])( + 'it rejects the non-canonical value %j', + value => { + expect(() => floatValueNode(value)).toThrow( + new CodamaError(CODAMA_ERROR__INVALID_BRANDED_STRING, { + actual: value, + expected: 'decimal (or "NaN"/"Infinity"/"-Infinity")', + }), + ); + }, +); diff --git a/packages/nodes/test/valueNodes/IntegerValueNode.test.ts b/packages/nodes/test/valueNodes/IntegerValueNode.test.ts new file mode 100644 index 000000000..b71077f8a --- /dev/null +++ b/packages/nodes/test/valueNodes/IntegerValueNode.test.ts @@ -0,0 +1,36 @@ +import { CODAMA_ERROR__INVALID_BRANDED_STRING, CodamaError } from '@codama/errors'; +import { expect, test } from 'vitest'; + +import { integerValueNode } from '../../src'; + +test('it returns the right node kind', () => { + const node = integerValueNode('42'); + expect(node.kind).toBe('integerValueNode'); +}); + +test('it returns a frozen object', () => { + const node = integerValueNode('42'); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it stores the value as a string', () => { + const node = integerValueNode('42'); + expect(node.value).toBe('42'); +}); + +test('it accepts zero, negative and very large values', () => { + expect(integerValueNode('0').value).toBe('0'); + expect(integerValueNode('-42').value).toBe('-42'); + expect(integerValueNode('340282366920938463463374607431768211455').value).toBe( + '340282366920938463463374607431768211455', + ); +}); + +test.each(['', '007', '-0', '1.5', '1e3', '+1', 'abc', ' 42'])('it rejects the invalid value %j', value => { + expect(() => integerValueNode(value)).toThrow( + new CodamaError(CODAMA_ERROR__INVALID_BRANDED_STRING, { + actual: value, + expected: 'integer (base-10, no leading zeros)', + }), + ); +}); diff --git a/packages/nodes/test/valueNodes/MapEntryValueNode.test.ts b/packages/nodes/test/valueNodes/MapEntryValueNode.test.ts index efc198943..e9f6e1577 100644 --- a/packages/nodes/test/valueNodes/MapEntryValueNode.test.ts +++ b/packages/nodes/test/valueNodes/MapEntryValueNode.test.ts @@ -1,13 +1,13 @@ import { expect, test } from 'vitest'; -import { mapEntryValueNode, numberValueNode, stringValueNode } from '../../src'; +import { integerValueNode, mapEntryValueNode, stringValueNode } from '../../src'; test('it returns the right node kind', () => { - const node = mapEntryValueNode(stringValueNode('age'), numberValueNode(42)); + const node = mapEntryValueNode(stringValueNode('age'), integerValueNode('42')); expect(node.kind).toBe('mapEntryValueNode'); }); test('it returns a frozen object', () => { - const node = mapEntryValueNode(stringValueNode('age'), numberValueNode(42)); + const node = mapEntryValueNode(stringValueNode('age'), integerValueNode('42')); expect(Object.isFrozen(node)).toBe(true); }); diff --git a/packages/nodes/test/valueNodes/NumberValueNode.test.ts b/packages/nodes/test/valueNodes/NumberValueNode.test.ts deleted file mode 100644 index f44e45ced..000000000 --- a/packages/nodes/test/valueNodes/NumberValueNode.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect, test } from 'vitest'; - -import { numberValueNode } from '../../src'; - -test('it returns the right node kind', () => { - const node = numberValueNode(42); - expect(node.kind).toBe('numberValueNode'); -}); - -test('it returns a frozen object', () => { - const node = numberValueNode(42); - expect(Object.isFrozen(node)).toBe(true); -}); diff --git a/packages/nodes/test/valueNodes/SomeValueNode.test.ts b/packages/nodes/test/valueNodes/SomeValueNode.test.ts index bcb606b5c..7040ad71c 100644 --- a/packages/nodes/test/valueNodes/SomeValueNode.test.ts +++ b/packages/nodes/test/valueNodes/SomeValueNode.test.ts @@ -1,13 +1,13 @@ import { expect, test } from 'vitest'; -import { numberValueNode, someValueNode } from '../../src'; +import { integerValueNode, someValueNode } from '../../src'; test('it returns the right node kind', () => { - const node = someValueNode(numberValueNode(42)); + const node = someValueNode(integerValueNode('42')); expect(node.kind).toBe('someValueNode'); }); test('it returns a frozen object', () => { - const node = someValueNode(numberValueNode(42)); + const node = someValueNode(integerValueNode('42')); expect(Object.isFrozen(node)).toBe(true); });