@ic-reactor/candid 3.0.12-beta.0 → 3.0.13-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/metadata-display-reactor.d.ts.map +1 -1
- package/dist/metadata-display-reactor.js +2 -2
- package/dist/metadata-display-reactor.js.map +1 -1
- package/dist/visitor/arguments/helpers.d.ts +40 -0
- package/dist/visitor/arguments/helpers.d.ts.map +1 -0
- package/dist/visitor/arguments/helpers.js +81 -0
- package/dist/visitor/arguments/helpers.js.map +1 -0
- package/dist/visitor/arguments/index.d.ts +8 -5
- package/dist/visitor/arguments/index.d.ts.map +1 -1
- package/dist/visitor/arguments/index.js +212 -17
- package/dist/visitor/arguments/index.js.map +1 -1
- package/dist/visitor/arguments/types.d.ts +282 -30
- package/dist/visitor/arguments/types.d.ts.map +1 -1
- package/dist/visitor/arguments/types.js +1 -40
- package/dist/visitor/arguments/types.js.map +1 -1
- package/package.json +2 -2
- package/src/metadata-display-reactor.ts +2 -2
- package/src/visitor/arguments/helpers.ts +104 -0
- package/src/visitor/arguments/index.test.ts +401 -2
- package/src/visitor/arguments/index.ts +251 -17
- package/src/visitor/arguments/schema.test.ts +3 -3
- package/src/visitor/arguments/types.ts +318 -52
- package/src/visitor/arguments/README.md +0 -230
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArgumentFieldType,
|
|
3
|
+
CompoundField,
|
|
4
|
+
Field,
|
|
5
|
+
FieldByType,
|
|
6
|
+
PrimitiveField,
|
|
7
|
+
RecordField,
|
|
8
|
+
TupleField,
|
|
9
|
+
VariantField,
|
|
10
|
+
} from "./types"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Type guard for checking specific field types.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* function FieldInput({ field }: { field: Field }) {
|
|
18
|
+
* if (isFieldType(field, 'record')) {
|
|
19
|
+
* // field is now typed as RecordField
|
|
20
|
+
* return <RecordInput field={field} />
|
|
21
|
+
* }
|
|
22
|
+
* if (isFieldType(field, 'text')) {
|
|
23
|
+
* // field is now typed as TextField
|
|
24
|
+
* return <TextInput field={field} />
|
|
25
|
+
* }
|
|
26
|
+
* // ...
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function isFieldType<T extends ArgumentFieldType>(
|
|
31
|
+
field: Field,
|
|
32
|
+
type: T
|
|
33
|
+
): field is FieldByType<T> {
|
|
34
|
+
return field.type === type
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Check if a field is a compound type (contains other fields) */
|
|
38
|
+
export function isCompoundField(field: Field): field is CompoundField {
|
|
39
|
+
return [
|
|
40
|
+
"record",
|
|
41
|
+
"variant",
|
|
42
|
+
"tuple",
|
|
43
|
+
"optional",
|
|
44
|
+
"vector",
|
|
45
|
+
"recursive",
|
|
46
|
+
].includes(field.type)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Check if a field is a primitive type */
|
|
50
|
+
export function isPrimitiveField(field: Field): field is PrimitiveField {
|
|
51
|
+
return ["principal", "number", "text", "boolean", "null"].includes(field.type)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Check if a field has children (for iteration) */
|
|
55
|
+
export function hasChildFields(
|
|
56
|
+
field: Field
|
|
57
|
+
): field is RecordField | VariantField | TupleField {
|
|
58
|
+
return "fields" in field && Array.isArray((field as RecordField).fields)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
62
|
+
// Label Formatting Utilities
|
|
63
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Format a raw Candid label into a human-readable display label.
|
|
67
|
+
* Handles common patterns like "__arg0", "_0_", "snake_case", etc.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* formatLabel("__arg0") // "Arg 0"
|
|
72
|
+
* formatLabel("_0_") // "Item 0"
|
|
73
|
+
* formatLabel("created_at") // "Created At"
|
|
74
|
+
* formatLabel("userAddress") // "User Address"
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function formatLabel(label: string): string {
|
|
78
|
+
// Handle argument labels: __arg0 -> Arg 0
|
|
79
|
+
if (label.startsWith("__arg")) {
|
|
80
|
+
const num = label.slice(5)
|
|
81
|
+
return `Arg ${num}`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Handle tuple index labels: _0_ -> Item 0
|
|
85
|
+
if (/^_\d+_$/.test(label)) {
|
|
86
|
+
const num = label.slice(1, -1)
|
|
87
|
+
return `Item ${num}`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Handle item labels for vectors: label_item -> Item
|
|
91
|
+
if (label.endsWith("_item")) {
|
|
92
|
+
return "Item"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Convert snake_case or just clean up underscores
|
|
96
|
+
// and capitalize each word
|
|
97
|
+
return label
|
|
98
|
+
.replace(/^_+|_+$/g, "") // Remove leading/trailing underscores
|
|
99
|
+
.replace(/_/g, " ") // Replace underscores with spaces
|
|
100
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2") // Add space before capitals (camelCase)
|
|
101
|
+
.split(" ")
|
|
102
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
103
|
+
.join(" ")
|
|
104
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest"
|
|
2
2
|
import { IDL } from "@icp-sdk/core/candid"
|
|
3
|
-
import {
|
|
3
|
+
import { FieldVisitor, VectorField } from "./index"
|
|
4
4
|
|
|
5
5
|
describe("ArgumentFieldVisitor", () => {
|
|
6
|
-
const visitor = new
|
|
6
|
+
const visitor = new FieldVisitor()
|
|
7
7
|
|
|
8
8
|
// ════════════════════════════════════════════════════════════════════════
|
|
9
9
|
// Primitive Types
|
|
@@ -972,4 +972,403 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
972
972
|
expect(field.getInnerDefault()).toEqual({ value: "" })
|
|
973
973
|
})
|
|
974
974
|
})
|
|
975
|
+
|
|
976
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
977
|
+
// New Features - displayLabel, component, renderHint
|
|
978
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
979
|
+
|
|
980
|
+
describe("displayLabel formatting", () => {
|
|
981
|
+
it("should format __arg labels correctly", () => {
|
|
982
|
+
const funcType = IDL.Func([IDL.Text, IDL.Nat], [], [])
|
|
983
|
+
const meta = visitor.visitFunc(funcType, "test")
|
|
984
|
+
|
|
985
|
+
expect(meta.fields[0].displayLabel).toBe("Arg 0")
|
|
986
|
+
expect(meta.fields[1].displayLabel).toBe("Arg 1")
|
|
987
|
+
})
|
|
988
|
+
|
|
989
|
+
it("should format tuple index labels correctly", () => {
|
|
990
|
+
const tupleType = IDL.Tuple(IDL.Text, IDL.Nat, IDL.Bool)
|
|
991
|
+
const field = visitor.visitTuple(
|
|
992
|
+
tupleType,
|
|
993
|
+
[IDL.Text, IDL.Nat, IDL.Bool],
|
|
994
|
+
"triple"
|
|
995
|
+
)
|
|
996
|
+
|
|
997
|
+
expect(field.fields[0].displayLabel).toBe("Item 0")
|
|
998
|
+
expect(field.fields[1].displayLabel).toBe("Item 1")
|
|
999
|
+
expect(field.fields[2].displayLabel).toBe("Item 2")
|
|
1000
|
+
})
|
|
1001
|
+
|
|
1002
|
+
it("should format snake_case labels correctly", () => {
|
|
1003
|
+
const recordType = IDL.Record({
|
|
1004
|
+
created_at_time: IDL.Nat,
|
|
1005
|
+
user_address: IDL.Text,
|
|
1006
|
+
})
|
|
1007
|
+
const field = visitor.visitRecord(
|
|
1008
|
+
recordType,
|
|
1009
|
+
[
|
|
1010
|
+
["created_at_time", IDL.Nat],
|
|
1011
|
+
["user_address", IDL.Text],
|
|
1012
|
+
],
|
|
1013
|
+
"record"
|
|
1014
|
+
)
|
|
1015
|
+
|
|
1016
|
+
const createdField = field.fields.find(
|
|
1017
|
+
(f) => f.label === "created_at_time"
|
|
1018
|
+
)
|
|
1019
|
+
const userField = field.fields.find((f) => f.label === "user_address")
|
|
1020
|
+
|
|
1021
|
+
expect(createdField?.displayLabel).toBe("Created At Time")
|
|
1022
|
+
expect(userField?.displayLabel).toBe("User Address")
|
|
1023
|
+
})
|
|
1024
|
+
})
|
|
1025
|
+
|
|
1026
|
+
describe("component hints", () => {
|
|
1027
|
+
it("should have correct component for record", () => {
|
|
1028
|
+
const recordType = IDL.Record({ name: IDL.Text })
|
|
1029
|
+
const field = visitor.visitRecord(
|
|
1030
|
+
recordType,
|
|
1031
|
+
[["name", IDL.Text]],
|
|
1032
|
+
"person"
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
expect(field.component).toBe("record-container")
|
|
1036
|
+
})
|
|
1037
|
+
|
|
1038
|
+
it("should have correct component for variant", () => {
|
|
1039
|
+
const variantType = IDL.Variant({ A: IDL.Null, B: IDL.Text })
|
|
1040
|
+
const field = visitor.visitVariant(
|
|
1041
|
+
variantType,
|
|
1042
|
+
[
|
|
1043
|
+
["A", IDL.Null],
|
|
1044
|
+
["B", IDL.Text],
|
|
1045
|
+
],
|
|
1046
|
+
"choice"
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
expect(field.component).toBe("variant-select")
|
|
1050
|
+
})
|
|
1051
|
+
|
|
1052
|
+
it("should have correct component for optional", () => {
|
|
1053
|
+
const optType = IDL.Opt(IDL.Text)
|
|
1054
|
+
const field = visitor.visitOpt(optType, IDL.Text, "optional")
|
|
1055
|
+
|
|
1056
|
+
expect(field.component).toBe("optional-toggle")
|
|
1057
|
+
})
|
|
1058
|
+
|
|
1059
|
+
it("should have correct component for vector", () => {
|
|
1060
|
+
const vecType = IDL.Vec(IDL.Text)
|
|
1061
|
+
const field = visitor.visitVec(vecType, IDL.Text, "vec") as VectorField
|
|
1062
|
+
|
|
1063
|
+
expect(field.component).toBe("vector-list")
|
|
1064
|
+
})
|
|
1065
|
+
|
|
1066
|
+
it("should have correct component for blob", () => {
|
|
1067
|
+
const blobType = IDL.Vec(IDL.Nat8)
|
|
1068
|
+
const field = visitor.visitVec(blobType, IDL.Nat8, "blob")
|
|
1069
|
+
|
|
1070
|
+
expect(field.component).toBe("blob-upload")
|
|
1071
|
+
})
|
|
1072
|
+
|
|
1073
|
+
it("should have correct component for text", () => {
|
|
1074
|
+
const field = visitor.visitText(IDL.Text, "text")
|
|
1075
|
+
|
|
1076
|
+
expect(field.component).toBe("text-input")
|
|
1077
|
+
})
|
|
1078
|
+
|
|
1079
|
+
it("should have correct component for number", () => {
|
|
1080
|
+
const field = visitor.visitFloat(IDL.Float64 as IDL.FloatClass, "num")
|
|
1081
|
+
|
|
1082
|
+
expect(field.component).toBe("number-input")
|
|
1083
|
+
})
|
|
1084
|
+
|
|
1085
|
+
it("should have correct component for boolean", () => {
|
|
1086
|
+
const field = visitor.visitBool(IDL.Bool, "bool")
|
|
1087
|
+
|
|
1088
|
+
expect(field.component).toBe("boolean-checkbox")
|
|
1089
|
+
})
|
|
1090
|
+
|
|
1091
|
+
it("should have correct component for principal", () => {
|
|
1092
|
+
const field = visitor.visitPrincipal(IDL.Principal, "principal")
|
|
1093
|
+
|
|
1094
|
+
expect(field.component).toBe("principal-input")
|
|
1095
|
+
})
|
|
1096
|
+
|
|
1097
|
+
it("should have correct component for null", () => {
|
|
1098
|
+
const field = visitor.visitNull(IDL.Null, "null")
|
|
1099
|
+
|
|
1100
|
+
expect(field.component).toBe("null-hidden")
|
|
1101
|
+
})
|
|
1102
|
+
})
|
|
1103
|
+
|
|
1104
|
+
describe("renderHint properties", () => {
|
|
1105
|
+
it("compound types should have isCompound: true", () => {
|
|
1106
|
+
const recordField = visitor.visitRecord(
|
|
1107
|
+
IDL.Record({ x: IDL.Text }),
|
|
1108
|
+
[["x", IDL.Text]],
|
|
1109
|
+
"rec"
|
|
1110
|
+
)
|
|
1111
|
+
const variantField = visitor.visitVariant(
|
|
1112
|
+
IDL.Variant({ A: IDL.Null }),
|
|
1113
|
+
[["A", IDL.Null]],
|
|
1114
|
+
"var"
|
|
1115
|
+
)
|
|
1116
|
+
const optionalField = visitor.visitOpt(IDL.Opt(IDL.Text), IDL.Text, "opt")
|
|
1117
|
+
const vectorField = visitor.visitVec(IDL.Vec(IDL.Text), IDL.Text, "vec")
|
|
1118
|
+
|
|
1119
|
+
expect(recordField.renderHint.isCompound).toBe(true)
|
|
1120
|
+
expect(recordField.renderHint.isPrimitive).toBe(false)
|
|
1121
|
+
|
|
1122
|
+
expect(variantField.renderHint.isCompound).toBe(true)
|
|
1123
|
+
expect(optionalField.renderHint.isCompound).toBe(true)
|
|
1124
|
+
expect((vectorField as VectorField).renderHint.isCompound).toBe(true)
|
|
1125
|
+
})
|
|
1126
|
+
|
|
1127
|
+
it("primitive types should have isPrimitive: true", () => {
|
|
1128
|
+
const textField = visitor.visitText(IDL.Text, "text")
|
|
1129
|
+
const boolField = visitor.visitBool(IDL.Bool, "bool")
|
|
1130
|
+
const principalField = visitor.visitPrincipal(IDL.Principal, "principal")
|
|
1131
|
+
|
|
1132
|
+
expect(textField.renderHint.isPrimitive).toBe(true)
|
|
1133
|
+
expect(textField.renderHint.isCompound).toBe(false)
|
|
1134
|
+
|
|
1135
|
+
expect(boolField.renderHint.isPrimitive).toBe(true)
|
|
1136
|
+
expect(principalField.renderHint.isPrimitive).toBe(true)
|
|
1137
|
+
})
|
|
1138
|
+
|
|
1139
|
+
it("should have correct inputType hints", () => {
|
|
1140
|
+
const textField = visitor.visitText(IDL.Text, "text")
|
|
1141
|
+
const boolField = visitor.visitBool(IDL.Bool, "bool")
|
|
1142
|
+
const numField = visitor.visitFloat(IDL.Float64 as IDL.FloatClass, "num")
|
|
1143
|
+
|
|
1144
|
+
expect(textField.renderHint.inputType).toBe("text")
|
|
1145
|
+
expect(boolField.renderHint.inputType).toBe("checkbox")
|
|
1146
|
+
expect(numField.renderHint.inputType).toBe("number")
|
|
1147
|
+
})
|
|
1148
|
+
})
|
|
1149
|
+
|
|
1150
|
+
describe("inputProps for primitive types", () => {
|
|
1151
|
+
it("text field should have inputProps", () => {
|
|
1152
|
+
const field = visitor.visitText(IDL.Text, "text")
|
|
1153
|
+
|
|
1154
|
+
expect(field.inputProps).toBeDefined()
|
|
1155
|
+
expect(field.inputProps.type).toBe("text")
|
|
1156
|
+
expect(field.inputProps.placeholder).toBeDefined()
|
|
1157
|
+
})
|
|
1158
|
+
|
|
1159
|
+
it("boolean field should have checkbox inputProps", () => {
|
|
1160
|
+
const field = visitor.visitBool(IDL.Bool, "bool")
|
|
1161
|
+
|
|
1162
|
+
expect(field.inputProps).toBeDefined()
|
|
1163
|
+
expect(field.inputProps.type).toBe("checkbox")
|
|
1164
|
+
})
|
|
1165
|
+
|
|
1166
|
+
it("number field should have number inputProps with min/max", () => {
|
|
1167
|
+
const field = visitor.visitFixedNat(IDL.Nat8 as IDL.FixedNatClass, "byte")
|
|
1168
|
+
|
|
1169
|
+
if (field.type === "number") {
|
|
1170
|
+
expect(field.inputProps).toBeDefined()
|
|
1171
|
+
expect(field.inputProps.type).toBe("number")
|
|
1172
|
+
expect(field.inputProps.min).toBe("0")
|
|
1173
|
+
expect(field.inputProps.max).toBe("255")
|
|
1174
|
+
}
|
|
1175
|
+
})
|
|
1176
|
+
|
|
1177
|
+
it("principal field should have inputProps with spellCheck disabled", () => {
|
|
1178
|
+
const field = visitor.visitPrincipal(IDL.Principal, "principal")
|
|
1179
|
+
|
|
1180
|
+
expect(field.inputProps).toBeDefined()
|
|
1181
|
+
expect(field.inputProps.spellCheck).toBe(false)
|
|
1182
|
+
expect(field.inputProps.autoComplete).toBe("off")
|
|
1183
|
+
})
|
|
1184
|
+
})
|
|
1185
|
+
|
|
1186
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1187
|
+
// Enhanced Variant Helpers
|
|
1188
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1189
|
+
|
|
1190
|
+
describe("Variant helper methods", () => {
|
|
1191
|
+
it("getField should return the correct field for an option", () => {
|
|
1192
|
+
const variantType = IDL.Variant({
|
|
1193
|
+
Transfer: IDL.Record({ to: IDL.Principal, amount: IDL.Nat }),
|
|
1194
|
+
Burn: IDL.Nat,
|
|
1195
|
+
})
|
|
1196
|
+
const field = visitor.visitVariant(
|
|
1197
|
+
variantType,
|
|
1198
|
+
[
|
|
1199
|
+
["Transfer", IDL.Record({ to: IDL.Principal, amount: IDL.Nat })],
|
|
1200
|
+
["Burn", IDL.Nat],
|
|
1201
|
+
],
|
|
1202
|
+
"action"
|
|
1203
|
+
)
|
|
1204
|
+
|
|
1205
|
+
const transferField = field.getField("Transfer")
|
|
1206
|
+
expect(transferField.type).toBe("record")
|
|
1207
|
+
|
|
1208
|
+
const burnField = field.getField("Burn")
|
|
1209
|
+
expect(burnField.type).toBe("text") // nat is rendered as text for large numbers
|
|
1210
|
+
})
|
|
1211
|
+
|
|
1212
|
+
it("getSelectedOption should return the selected option from a value", () => {
|
|
1213
|
+
const variantType = IDL.Variant({ A: IDL.Null, B: IDL.Text, C: IDL.Nat })
|
|
1214
|
+
const field = visitor.visitVariant(
|
|
1215
|
+
variantType,
|
|
1216
|
+
[
|
|
1217
|
+
["A", IDL.Null],
|
|
1218
|
+
["B", IDL.Text],
|
|
1219
|
+
["C", IDL.Nat],
|
|
1220
|
+
],
|
|
1221
|
+
"choice"
|
|
1222
|
+
)
|
|
1223
|
+
|
|
1224
|
+
expect(field.getSelectedOption({ A: null })).toBe("A")
|
|
1225
|
+
expect(field.getSelectedOption({ B: "hello" })).toBe("B")
|
|
1226
|
+
expect(field.getSelectedOption({ C: "100" })).toBe("C")
|
|
1227
|
+
// Falls back to default option for unknown values
|
|
1228
|
+
expect(field.getSelectedOption({})).toBe("A")
|
|
1229
|
+
})
|
|
1230
|
+
|
|
1231
|
+
it("getSelectedField should return the field for the selected option", () => {
|
|
1232
|
+
const variantType = IDL.Variant({
|
|
1233
|
+
Ok: IDL.Nat,
|
|
1234
|
+
Err: IDL.Text,
|
|
1235
|
+
})
|
|
1236
|
+
const field = visitor.visitVariant(
|
|
1237
|
+
variantType,
|
|
1238
|
+
[
|
|
1239
|
+
["Ok", IDL.Nat],
|
|
1240
|
+
["Err", IDL.Text],
|
|
1241
|
+
],
|
|
1242
|
+
"result"
|
|
1243
|
+
)
|
|
1244
|
+
|
|
1245
|
+
const okField = field.getSelectedField({ Ok: "100" })
|
|
1246
|
+
expect(okField.label).toBe("Ok")
|
|
1247
|
+
|
|
1248
|
+
const errField = field.getSelectedField({ Err: "error" })
|
|
1249
|
+
expect(errField.label).toBe("Err")
|
|
1250
|
+
})
|
|
1251
|
+
})
|
|
1252
|
+
|
|
1253
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1254
|
+
// Optional isEnabled Helper
|
|
1255
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1256
|
+
|
|
1257
|
+
describe("Optional isEnabled helper", () => {
|
|
1258
|
+
it("should return true for non-null values", () => {
|
|
1259
|
+
const optType = IDL.Opt(IDL.Text)
|
|
1260
|
+
const field = visitor.visitOpt(optType, IDL.Text, "optional")
|
|
1261
|
+
|
|
1262
|
+
expect(field.isEnabled("hello")).toBe(true)
|
|
1263
|
+
expect(field.isEnabled("")).toBe(true)
|
|
1264
|
+
expect(field.isEnabled(0)).toBe(true)
|
|
1265
|
+
expect(field.isEnabled(false)).toBe(true)
|
|
1266
|
+
expect(field.isEnabled({})).toBe(true)
|
|
1267
|
+
})
|
|
1268
|
+
|
|
1269
|
+
it("should return false for null and undefined", () => {
|
|
1270
|
+
const optType = IDL.Opt(IDL.Text)
|
|
1271
|
+
const field = visitor.visitOpt(optType, IDL.Text, "optional")
|
|
1272
|
+
|
|
1273
|
+
expect(field.isEnabled(null)).toBe(false)
|
|
1274
|
+
expect(field.isEnabled(undefined)).toBe(false)
|
|
1275
|
+
})
|
|
1276
|
+
})
|
|
1277
|
+
|
|
1278
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1279
|
+
// Vector createItemField Helper
|
|
1280
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1281
|
+
|
|
1282
|
+
describe("Vector createItemField helper", () => {
|
|
1283
|
+
it("should create item field with correct index in name path", () => {
|
|
1284
|
+
const funcType = IDL.Func([IDL.Vec(IDL.Text)], [], [])
|
|
1285
|
+
const meta = visitor.visitFunc(funcType, "addItems")
|
|
1286
|
+
const vecField = meta.fields[0] as VectorField
|
|
1287
|
+
|
|
1288
|
+
const item0 = vecField.createItemField(0)
|
|
1289
|
+
expect(item0.name).toBe("[0][0]")
|
|
1290
|
+
|
|
1291
|
+
const item5 = vecField.createItemField(5)
|
|
1292
|
+
expect(item5.name).toBe("[0][5]")
|
|
1293
|
+
})
|
|
1294
|
+
|
|
1295
|
+
it("should use custom label when provided", () => {
|
|
1296
|
+
const funcType = IDL.Func(
|
|
1297
|
+
[IDL.Vec(IDL.Record({ name: IDL.Text }))],
|
|
1298
|
+
[],
|
|
1299
|
+
[]
|
|
1300
|
+
)
|
|
1301
|
+
const meta = visitor.visitFunc(funcType, "addItems")
|
|
1302
|
+
const vecField = meta.fields[0] as VectorField
|
|
1303
|
+
|
|
1304
|
+
const item = vecField.createItemField(3, { label: "Person 3" })
|
|
1305
|
+
expect(item.label).toBe("Person 3")
|
|
1306
|
+
expect(item.displayLabel).toBe("Person 3")
|
|
1307
|
+
})
|
|
1308
|
+
|
|
1309
|
+
it("should use default label when not provided", () => {
|
|
1310
|
+
const funcType = IDL.Func([IDL.Vec(IDL.Text)], [], [])
|
|
1311
|
+
const meta = visitor.visitFunc(funcType, "addTags")
|
|
1312
|
+
const vecField = meta.fields[0] as VectorField
|
|
1313
|
+
|
|
1314
|
+
const item = vecField.createItemField(2)
|
|
1315
|
+
expect(item.label).toBe("Item 2")
|
|
1316
|
+
expect(item.displayLabel).toBe("Item 2")
|
|
1317
|
+
})
|
|
1318
|
+
})
|
|
1319
|
+
|
|
1320
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1321
|
+
// Blob Field Utilities
|
|
1322
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1323
|
+
|
|
1324
|
+
describe("Blob field utilities", () => {
|
|
1325
|
+
it("should have limits defined", () => {
|
|
1326
|
+
const blobType = IDL.Vec(IDL.Nat8)
|
|
1327
|
+
const field = visitor.visitVec(blobType, IDL.Nat8, "data")
|
|
1328
|
+
|
|
1329
|
+
if (field.type === "blob") {
|
|
1330
|
+
expect(field.limits).toBeDefined()
|
|
1331
|
+
expect(field.limits.maxHexBytes).toBeGreaterThan(0)
|
|
1332
|
+
expect(field.limits.maxFileBytes).toBeGreaterThan(0)
|
|
1333
|
+
expect(field.limits.maxHexDisplayLength).toBeGreaterThan(0)
|
|
1334
|
+
}
|
|
1335
|
+
})
|
|
1336
|
+
|
|
1337
|
+
it("normalizeHex should remove 0x prefix and lowercase", () => {
|
|
1338
|
+
const blobType = IDL.Vec(IDL.Nat8)
|
|
1339
|
+
const field = visitor.visitVec(blobType, IDL.Nat8, "data")
|
|
1340
|
+
|
|
1341
|
+
if (field.type === "blob") {
|
|
1342
|
+
expect(field.normalizeHex("0xDEADBEEF")).toBe("deadbeef")
|
|
1343
|
+
expect(field.normalizeHex("DEADBEEF")).toBe("deadbeef")
|
|
1344
|
+
expect(field.normalizeHex("0x")).toBe("")
|
|
1345
|
+
expect(field.normalizeHex("abc123")).toBe("abc123")
|
|
1346
|
+
}
|
|
1347
|
+
})
|
|
1348
|
+
|
|
1349
|
+
it("validateInput should validate hex strings", () => {
|
|
1350
|
+
const blobType = IDL.Vec(IDL.Nat8)
|
|
1351
|
+
const field = visitor.visitVec(blobType, IDL.Nat8, "data")
|
|
1352
|
+
|
|
1353
|
+
if (field.type === "blob") {
|
|
1354
|
+
// Valid inputs
|
|
1355
|
+
expect(field.validateInput("").valid).toBe(true)
|
|
1356
|
+
expect(field.validateInput("deadbeef").valid).toBe(true)
|
|
1357
|
+
expect(field.validateInput("0x1234").valid).toBe(true)
|
|
1358
|
+
|
|
1359
|
+
// Invalid inputs
|
|
1360
|
+
expect(field.validateInput("xyz").valid).toBe(false)
|
|
1361
|
+
expect(field.validateInput("abc").valid).toBe(false) // odd length
|
|
1362
|
+
}
|
|
1363
|
+
})
|
|
1364
|
+
|
|
1365
|
+
it("validateInput should validate Uint8Array", () => {
|
|
1366
|
+
const blobType = IDL.Vec(IDL.Nat8)
|
|
1367
|
+
const field = visitor.visitVec(blobType, IDL.Nat8, "data")
|
|
1368
|
+
|
|
1369
|
+
if (field.type === "blob") {
|
|
1370
|
+
expect(field.validateInput(new Uint8Array([1, 2, 3])).valid).toBe(true)
|
|
1371
|
+
}
|
|
1372
|
+
})
|
|
1373
|
+
})
|
|
975
1374
|
})
|