@ic-reactor/candid 3.0.12-beta.0 → 3.0.14-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/README.md +28 -0
- 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 +19 -6
- package/dist/visitor/arguments/index.d.ts.map +1 -1
- package/dist/visitor/arguments/index.js +343 -24
- package/dist/visitor/arguments/index.js.map +1 -1
- package/dist/visitor/arguments/types.d.ts +183 -178
- 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/dist/visitor/returns/types.d.ts +3 -4
- package/dist/visitor/returns/types.d.ts.map +1 -1
- package/dist/visitor/types.d.ts +14 -0
- package/dist/visitor/types.d.ts.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 +443 -23
- package/src/visitor/arguments/index.ts +410 -41
- package/src/visitor/arguments/schema.test.ts +117 -7
- package/src/visitor/arguments/types.ts +284 -284
- package/src/visitor/returns/types.ts +4 -27
- package/src/visitor/types.ts +45 -0
- package/src/visitor/arguments/README.md +0 -230
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest"
|
|
2
2
|
import { IDL } from "@icp-sdk/core/candid"
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
FieldVisitor,
|
|
5
|
+
OptionalField,
|
|
6
|
+
RecordField,
|
|
7
|
+
VariantField,
|
|
8
|
+
VectorField,
|
|
9
|
+
} from "./index"
|
|
4
10
|
|
|
5
11
|
describe("ArgumentFieldVisitor", () => {
|
|
6
|
-
const visitor = new
|
|
12
|
+
const visitor = new FieldVisitor()
|
|
7
13
|
|
|
8
14
|
// ════════════════════════════════════════════════════════════════════════
|
|
9
15
|
// Primitive Types
|
|
@@ -186,7 +192,9 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
186
192
|
expect(field.type).toBe("record")
|
|
187
193
|
expect(field.fields).toHaveLength(2)
|
|
188
194
|
|
|
189
|
-
const addressField = field.fields.find(
|
|
195
|
+
const addressField = field.fields.find(
|
|
196
|
+
(f) => f.label === "address"
|
|
197
|
+
) as RecordField
|
|
190
198
|
if (!addressField || addressField.type !== "record") {
|
|
191
199
|
throw new Error("Address field not found or not record")
|
|
192
200
|
}
|
|
@@ -235,7 +243,7 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
235
243
|
expect(field.fields).toHaveLength(5)
|
|
236
244
|
|
|
237
245
|
// Check 'to' field
|
|
238
|
-
const toField = field.fields.find((f) => f.label === "to")
|
|
246
|
+
const toField = field.fields.find((f) => f.label === "to") as RecordField
|
|
239
247
|
if (!toField || toField.type !== "record") {
|
|
240
248
|
throw new Error("To field not found or not record")
|
|
241
249
|
}
|
|
@@ -251,7 +259,9 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
251
259
|
expect(amountField.candidType).toBe("nat")
|
|
252
260
|
|
|
253
261
|
// Check optional 'fee' field
|
|
254
|
-
const feeField = field.fields.find(
|
|
262
|
+
const feeField = field.fields.find(
|
|
263
|
+
(f) => f.label === "fee"
|
|
264
|
+
) as OptionalField
|
|
255
265
|
if (!feeField || feeField.type !== "optional") {
|
|
256
266
|
throw new Error("Fee field not found or not optional")
|
|
257
267
|
}
|
|
@@ -289,8 +299,8 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
289
299
|
})
|
|
290
300
|
|
|
291
301
|
// Test getOptionDefault helper
|
|
292
|
-
expect(field.getOptionDefault("Active")).toEqual({
|
|
293
|
-
expect(field.getOptionDefault("Pending")).toEqual({
|
|
302
|
+
expect(field.getOptionDefault("Active")).toEqual({ _type: "Active" })
|
|
303
|
+
expect(field.getOptionDefault("Pending")).toEqual({ _type: "Pending" })
|
|
294
304
|
})
|
|
295
305
|
|
|
296
306
|
it("should handle variant with payloads", () => {
|
|
@@ -330,7 +340,9 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
330
340
|
expect(field.type).toBe("variant")
|
|
331
341
|
expect(field.options).toEqual(["Approve", "Burn", "Transfer"]) // Sorted order
|
|
332
342
|
|
|
333
|
-
const transferField = field.fields.find(
|
|
343
|
+
const transferField = field.fields.find(
|
|
344
|
+
(f) => f.label === "Transfer"
|
|
345
|
+
) as RecordField
|
|
334
346
|
if (!transferField || transferField.type !== "record") {
|
|
335
347
|
throw new Error("Transfer field not found or not record")
|
|
336
348
|
}
|
|
@@ -429,7 +441,7 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
429
441
|
expect(field.type).toBe("optional")
|
|
430
442
|
expect(field.innerField.type).toBe("record")
|
|
431
443
|
expect(field.innerField.type).toBe("record")
|
|
432
|
-
const inner = field.innerField
|
|
444
|
+
const inner = field.innerField as RecordField
|
|
433
445
|
if (inner.type === "record") {
|
|
434
446
|
expect(inner.fields).toHaveLength(2)
|
|
435
447
|
} else {
|
|
@@ -448,7 +460,7 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
448
460
|
expect(field.type).toBe("optional")
|
|
449
461
|
expect(field.innerField.type).toBe("optional")
|
|
450
462
|
expect(field.innerField.type).toBe("optional")
|
|
451
|
-
const inner = field.innerField
|
|
463
|
+
const inner = field.innerField as OptionalField
|
|
452
464
|
if (inner.type === "optional") {
|
|
453
465
|
expect(inner.innerField.type).toBe("text")
|
|
454
466
|
} else {
|
|
@@ -479,7 +491,7 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
479
491
|
|
|
480
492
|
expect(field.type).toBe("vector")
|
|
481
493
|
expect(field.itemField.type).toBe("record")
|
|
482
|
-
const item = field.itemField
|
|
494
|
+
const item = field.itemField as RecordField
|
|
483
495
|
if (item.type === "record") {
|
|
484
496
|
expect(item.fields).toHaveLength(2)
|
|
485
497
|
} else {
|
|
@@ -509,7 +521,7 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
509
521
|
|
|
510
522
|
expect(field.type).toBe("vector")
|
|
511
523
|
expect(field.itemField.type).toBe("vector")
|
|
512
|
-
const item = field.itemField
|
|
524
|
+
const item = field.itemField as VectorField
|
|
513
525
|
if (item.type === "vector") {
|
|
514
526
|
expect(item.itemField.type).toBe("text")
|
|
515
527
|
} else {
|
|
@@ -550,7 +562,7 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
550
562
|
expect(typeof field.getInnerDefault).toBe("function")
|
|
551
563
|
|
|
552
564
|
// Extract should return a variant
|
|
553
|
-
const extracted = field.extract()
|
|
565
|
+
const extracted = field.extract() as VariantField
|
|
554
566
|
if (extracted.type !== "variant") {
|
|
555
567
|
throw new Error("Extracted field is not variant")
|
|
556
568
|
}
|
|
@@ -585,14 +597,16 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
585
597
|
|
|
586
598
|
expect(field.type).toBe("recursive")
|
|
587
599
|
|
|
588
|
-
const extracted = field.extract()
|
|
600
|
+
const extracted = field.extract() as VariantField
|
|
589
601
|
if (extracted.type !== "variant") {
|
|
590
602
|
throw new Error("Extracted field is not variant")
|
|
591
603
|
}
|
|
592
604
|
expect(extracted.type).toBe("variant")
|
|
593
605
|
expect(extracted.options).toEqual(["Nil", "Cons"])
|
|
594
606
|
|
|
595
|
-
const consField = extracted.fields.find(
|
|
607
|
+
const consField = extracted.fields.find(
|
|
608
|
+
(f) => f.label === "Cons"
|
|
609
|
+
) as RecordField
|
|
596
610
|
if (!consField || consField.type !== "record") {
|
|
597
611
|
throw new Error("Cons field not found or not record")
|
|
598
612
|
}
|
|
@@ -642,7 +656,7 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
642
656
|
expect(meta.fields).toHaveLength(1)
|
|
643
657
|
expect(meta.fields[0].type).toBe("record")
|
|
644
658
|
|
|
645
|
-
const recordField = meta.fields[0]
|
|
659
|
+
const recordField = meta.fields[0] as RecordField
|
|
646
660
|
if (recordField.type !== "record") {
|
|
647
661
|
throw new Error("Expected record field")
|
|
648
662
|
}
|
|
@@ -754,13 +768,15 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
754
768
|
)
|
|
755
769
|
const meta = visitor.visitFunc(funcType, "updateUser")
|
|
756
770
|
|
|
757
|
-
const argRecord = meta.fields[0]
|
|
771
|
+
const argRecord = meta.fields[0] as RecordField
|
|
758
772
|
if (argRecord.type !== "record") {
|
|
759
773
|
throw new Error("Expected record field")
|
|
760
774
|
}
|
|
761
775
|
expect(argRecord.name).toBe("[0]")
|
|
762
776
|
|
|
763
|
-
const userRecord = argRecord.fields.find(
|
|
777
|
+
const userRecord = argRecord.fields.find(
|
|
778
|
+
(f) => f.label === "user"
|
|
779
|
+
) as RecordField
|
|
764
780
|
if (!userRecord || userRecord.type !== "record") {
|
|
765
781
|
throw new Error("User record not found or not record")
|
|
766
782
|
}
|
|
@@ -783,7 +799,7 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
783
799
|
const funcType = IDL.Func([IDL.Vec(IDL.Text)], [], [])
|
|
784
800
|
const meta = visitor.visitFunc(funcType, "addTags")
|
|
785
801
|
|
|
786
|
-
const vecField = meta.fields[0]
|
|
802
|
+
const vecField = meta.fields[0] as VectorField
|
|
787
803
|
if (vecField.type !== "vector") {
|
|
788
804
|
throw new Error("Expected vector field")
|
|
789
805
|
}
|
|
@@ -837,7 +853,9 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
837
853
|
expect(field.fields.length).toBeGreaterThan(5)
|
|
838
854
|
|
|
839
855
|
// Check spender field
|
|
840
|
-
const spenderField = field.fields.find(
|
|
856
|
+
const spenderField = field.fields.find(
|
|
857
|
+
(f) => f.label === "spender"
|
|
858
|
+
) as RecordField
|
|
841
859
|
if (!spenderField || spenderField.type !== "record") {
|
|
842
860
|
throw new Error("Spender field not found or not record")
|
|
843
861
|
}
|
|
@@ -906,7 +924,9 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
906
924
|
expect(field.options).toContain("UpgradeSnsControlledCanister")
|
|
907
925
|
|
|
908
926
|
// Check Motion variant
|
|
909
|
-
const motionField = field.fields.find(
|
|
927
|
+
const motionField = field.fields.find(
|
|
928
|
+
(f) => f.label === "Motion"
|
|
929
|
+
) as RecordField
|
|
910
930
|
if (!motionField || motionField.type !== "record") {
|
|
911
931
|
throw new Error("Motion field not found or not record")
|
|
912
932
|
}
|
|
@@ -916,7 +936,7 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
916
936
|
// Check TransferSnsTreasuryFunds variant
|
|
917
937
|
const transferField = field.fields.find(
|
|
918
938
|
(f) => f.label === "TransferSnsTreasuryFunds"
|
|
919
|
-
)
|
|
939
|
+
) as RecordField
|
|
920
940
|
if (!transferField || transferField.type !== "record") {
|
|
921
941
|
throw new Error("Transfer field not found or not record")
|
|
922
942
|
}
|
|
@@ -944,8 +964,9 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
944
964
|
"status"
|
|
945
965
|
)
|
|
946
966
|
|
|
947
|
-
expect(field.getOptionDefault("Active")).toEqual({
|
|
967
|
+
expect(field.getOptionDefault("Active")).toEqual({ _type: "Active" })
|
|
948
968
|
expect(field.getOptionDefault("Pending")).toEqual({
|
|
969
|
+
_type: "Pending",
|
|
949
970
|
Pending: { reason: "" },
|
|
950
971
|
})
|
|
951
972
|
})
|
|
@@ -972,4 +993,403 @@ describe("ArgumentFieldVisitor", () => {
|
|
|
972
993
|
expect(field.getInnerDefault()).toEqual({ value: "" })
|
|
973
994
|
})
|
|
974
995
|
})
|
|
996
|
+
|
|
997
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
998
|
+
// New Features - displayLabel, component, renderHint
|
|
999
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1000
|
+
|
|
1001
|
+
describe("displayLabel formatting", () => {
|
|
1002
|
+
it("should format __arg labels correctly", () => {
|
|
1003
|
+
const funcType = IDL.Func([IDL.Text, IDL.Nat], [], [])
|
|
1004
|
+
const meta = visitor.visitFunc(funcType, "test")
|
|
1005
|
+
|
|
1006
|
+
expect(meta.fields[0].displayLabel).toBe("Arg 0")
|
|
1007
|
+
expect(meta.fields[1].displayLabel).toBe("Arg 1")
|
|
1008
|
+
})
|
|
1009
|
+
|
|
1010
|
+
it("should format tuple index labels correctly", () => {
|
|
1011
|
+
const tupleType = IDL.Tuple(IDL.Text, IDL.Nat, IDL.Bool)
|
|
1012
|
+
const field = visitor.visitTuple(
|
|
1013
|
+
tupleType,
|
|
1014
|
+
[IDL.Text, IDL.Nat, IDL.Bool],
|
|
1015
|
+
"triple"
|
|
1016
|
+
)
|
|
1017
|
+
|
|
1018
|
+
expect(field.fields[0].displayLabel).toBe("Item 0")
|
|
1019
|
+
expect(field.fields[1].displayLabel).toBe("Item 1")
|
|
1020
|
+
expect(field.fields[2].displayLabel).toBe("Item 2")
|
|
1021
|
+
})
|
|
1022
|
+
|
|
1023
|
+
it("should format snake_case labels correctly", () => {
|
|
1024
|
+
const recordType = IDL.Record({
|
|
1025
|
+
created_at_time: IDL.Nat,
|
|
1026
|
+
user_address: IDL.Text,
|
|
1027
|
+
})
|
|
1028
|
+
const field = visitor.visitRecord(
|
|
1029
|
+
recordType,
|
|
1030
|
+
[
|
|
1031
|
+
["created_at_time", IDL.Nat],
|
|
1032
|
+
["user_address", IDL.Text],
|
|
1033
|
+
],
|
|
1034
|
+
"record"
|
|
1035
|
+
)
|
|
1036
|
+
|
|
1037
|
+
const createdField = field.fields.find(
|
|
1038
|
+
(f) => f.label === "created_at_time"
|
|
1039
|
+
)
|
|
1040
|
+
const userField = field.fields.find((f) => f.label === "user_address")
|
|
1041
|
+
|
|
1042
|
+
expect(createdField?.displayLabel).toBe("Created At Time")
|
|
1043
|
+
expect(userField?.displayLabel).toBe("User Address")
|
|
1044
|
+
})
|
|
1045
|
+
})
|
|
1046
|
+
|
|
1047
|
+
describe("component hints", () => {
|
|
1048
|
+
it("should have correct component for record", () => {
|
|
1049
|
+
const recordType = IDL.Record({ name: IDL.Text })
|
|
1050
|
+
const field = visitor.visitRecord(
|
|
1051
|
+
recordType,
|
|
1052
|
+
[["name", IDL.Text]],
|
|
1053
|
+
"person"
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
expect(field.component).toBe("record-container")
|
|
1057
|
+
})
|
|
1058
|
+
|
|
1059
|
+
it("should have correct component for variant", () => {
|
|
1060
|
+
const variantType = IDL.Variant({ A: IDL.Null, B: IDL.Text })
|
|
1061
|
+
const field = visitor.visitVariant(
|
|
1062
|
+
variantType,
|
|
1063
|
+
[
|
|
1064
|
+
["A", IDL.Null],
|
|
1065
|
+
["B", IDL.Text],
|
|
1066
|
+
],
|
|
1067
|
+
"choice"
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
expect(field.component).toBe("variant-select")
|
|
1071
|
+
})
|
|
1072
|
+
|
|
1073
|
+
it("should have correct component for optional", () => {
|
|
1074
|
+
const optType = IDL.Opt(IDL.Text)
|
|
1075
|
+
const field = visitor.visitOpt(optType, IDL.Text, "optional")
|
|
1076
|
+
|
|
1077
|
+
expect(field.component).toBe("optional-toggle")
|
|
1078
|
+
})
|
|
1079
|
+
|
|
1080
|
+
it("should have correct component for vector", () => {
|
|
1081
|
+
const vecType = IDL.Vec(IDL.Text)
|
|
1082
|
+
const field = visitor.visitVec(vecType, IDL.Text, "vec") as VectorField
|
|
1083
|
+
|
|
1084
|
+
expect(field.component).toBe("vector-list")
|
|
1085
|
+
})
|
|
1086
|
+
|
|
1087
|
+
it("should have correct component for blob", () => {
|
|
1088
|
+
const blobType = IDL.Vec(IDL.Nat8)
|
|
1089
|
+
const field = visitor.visitVec(blobType, IDL.Nat8, "blob")
|
|
1090
|
+
|
|
1091
|
+
expect(field.component).toBe("blob-upload")
|
|
1092
|
+
})
|
|
1093
|
+
|
|
1094
|
+
it("should have correct component for text", () => {
|
|
1095
|
+
const field = visitor.visitText(IDL.Text, "text")
|
|
1096
|
+
|
|
1097
|
+
expect(field.component).toBe("text-input")
|
|
1098
|
+
})
|
|
1099
|
+
|
|
1100
|
+
it("should have correct component for number", () => {
|
|
1101
|
+
const field = visitor.visitFloat(IDL.Float64 as IDL.FloatClass, "num")
|
|
1102
|
+
|
|
1103
|
+
expect(field.component).toBe("number-input")
|
|
1104
|
+
})
|
|
1105
|
+
|
|
1106
|
+
it("should have correct component for boolean", () => {
|
|
1107
|
+
const field = visitor.visitBool(IDL.Bool, "bool")
|
|
1108
|
+
|
|
1109
|
+
expect(field.component).toBe("boolean-checkbox")
|
|
1110
|
+
})
|
|
1111
|
+
|
|
1112
|
+
it("should have correct component for principal", () => {
|
|
1113
|
+
const field = visitor.visitPrincipal(IDL.Principal, "principal")
|
|
1114
|
+
|
|
1115
|
+
expect(field.component).toBe("principal-input")
|
|
1116
|
+
})
|
|
1117
|
+
|
|
1118
|
+
it("should have correct component for null", () => {
|
|
1119
|
+
const field = visitor.visitNull(IDL.Null, "null")
|
|
1120
|
+
|
|
1121
|
+
expect(field.component).toBe("null-hidden")
|
|
1122
|
+
})
|
|
1123
|
+
})
|
|
1124
|
+
|
|
1125
|
+
describe("renderHint properties", () => {
|
|
1126
|
+
it("compound types should have isCompound: true", () => {
|
|
1127
|
+
const recordField = visitor.visitRecord(
|
|
1128
|
+
IDL.Record({ x: IDL.Text }),
|
|
1129
|
+
[["x", IDL.Text]],
|
|
1130
|
+
"rec"
|
|
1131
|
+
)
|
|
1132
|
+
const variantField = visitor.visitVariant(
|
|
1133
|
+
IDL.Variant({ A: IDL.Null }),
|
|
1134
|
+
[["A", IDL.Null]],
|
|
1135
|
+
"var"
|
|
1136
|
+
)
|
|
1137
|
+
const optionalField = visitor.visitOpt(IDL.Opt(IDL.Text), IDL.Text, "opt")
|
|
1138
|
+
const vectorField = visitor.visitVec(IDL.Vec(IDL.Text), IDL.Text, "vec")
|
|
1139
|
+
|
|
1140
|
+
expect(recordField.renderHint.isCompound).toBe(true)
|
|
1141
|
+
expect(recordField.renderHint.isPrimitive).toBe(false)
|
|
1142
|
+
|
|
1143
|
+
expect(variantField.renderHint.isCompound).toBe(true)
|
|
1144
|
+
expect(optionalField.renderHint.isCompound).toBe(true)
|
|
1145
|
+
expect((vectorField as VectorField).renderHint.isCompound).toBe(true)
|
|
1146
|
+
})
|
|
1147
|
+
|
|
1148
|
+
it("primitive types should have isPrimitive: true", () => {
|
|
1149
|
+
const textField = visitor.visitText(IDL.Text, "text")
|
|
1150
|
+
const boolField = visitor.visitBool(IDL.Bool, "bool")
|
|
1151
|
+
const principalField = visitor.visitPrincipal(IDL.Principal, "principal")
|
|
1152
|
+
|
|
1153
|
+
expect(textField.renderHint.isPrimitive).toBe(true)
|
|
1154
|
+
expect(textField.renderHint.isCompound).toBe(false)
|
|
1155
|
+
|
|
1156
|
+
expect(boolField.renderHint.isPrimitive).toBe(true)
|
|
1157
|
+
expect(principalField.renderHint.isPrimitive).toBe(true)
|
|
1158
|
+
})
|
|
1159
|
+
|
|
1160
|
+
it("should have correct inputType hints", () => {
|
|
1161
|
+
const textField = visitor.visitText(IDL.Text, "text")
|
|
1162
|
+
const boolField = visitor.visitBool(IDL.Bool, "bool")
|
|
1163
|
+
const numField = visitor.visitFloat(IDL.Float64 as IDL.FloatClass, "num")
|
|
1164
|
+
|
|
1165
|
+
expect(textField.renderHint.inputType).toBe("text")
|
|
1166
|
+
expect(boolField.renderHint.inputType).toBe("checkbox")
|
|
1167
|
+
expect(numField.renderHint.inputType).toBe("number")
|
|
1168
|
+
})
|
|
1169
|
+
})
|
|
1170
|
+
|
|
1171
|
+
describe("inputProps for primitive types", () => {
|
|
1172
|
+
it("text field should have inputProps", () => {
|
|
1173
|
+
const field = visitor.visitText(IDL.Text, "text")
|
|
1174
|
+
|
|
1175
|
+
expect(field.inputProps).toBeDefined()
|
|
1176
|
+
expect(field.inputProps.type).toBe("text")
|
|
1177
|
+
expect(field.inputProps.placeholder).toBeDefined()
|
|
1178
|
+
})
|
|
1179
|
+
|
|
1180
|
+
it("boolean field should have checkbox inputProps", () => {
|
|
1181
|
+
const field = visitor.visitBool(IDL.Bool, "bool")
|
|
1182
|
+
|
|
1183
|
+
expect(field.inputProps).toBeDefined()
|
|
1184
|
+
expect(field.inputProps.type).toBe("checkbox")
|
|
1185
|
+
})
|
|
1186
|
+
|
|
1187
|
+
it("number field should have number inputProps with min/max", () => {
|
|
1188
|
+
const field = visitor.visitFixedNat(IDL.Nat8 as IDL.FixedNatClass, "byte")
|
|
1189
|
+
|
|
1190
|
+
if (field.type === "number") {
|
|
1191
|
+
expect(field.inputProps).toBeDefined()
|
|
1192
|
+
expect(field.inputProps.type).toBe("number")
|
|
1193
|
+
expect(field.inputProps.min).toBe("0")
|
|
1194
|
+
expect(field.inputProps.max).toBe("255")
|
|
1195
|
+
}
|
|
1196
|
+
})
|
|
1197
|
+
|
|
1198
|
+
it("principal field should have inputProps with spellCheck disabled", () => {
|
|
1199
|
+
const field = visitor.visitPrincipal(IDL.Principal, "principal")
|
|
1200
|
+
|
|
1201
|
+
expect(field.inputProps).toBeDefined()
|
|
1202
|
+
expect(field.inputProps.spellCheck).toBe(false)
|
|
1203
|
+
expect(field.inputProps.autoComplete).toBe("off")
|
|
1204
|
+
})
|
|
1205
|
+
})
|
|
1206
|
+
|
|
1207
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1208
|
+
// Enhanced Variant Helpers
|
|
1209
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1210
|
+
|
|
1211
|
+
describe("Variant helper methods", () => {
|
|
1212
|
+
it("getField should return the correct field for an option", () => {
|
|
1213
|
+
const variantType = IDL.Variant({
|
|
1214
|
+
Transfer: IDL.Record({ to: IDL.Principal, amount: IDL.Nat }),
|
|
1215
|
+
Burn: IDL.Nat,
|
|
1216
|
+
})
|
|
1217
|
+
const field = visitor.visitVariant(
|
|
1218
|
+
variantType,
|
|
1219
|
+
[
|
|
1220
|
+
["Transfer", IDL.Record({ to: IDL.Principal, amount: IDL.Nat })],
|
|
1221
|
+
["Burn", IDL.Nat],
|
|
1222
|
+
],
|
|
1223
|
+
"action"
|
|
1224
|
+
)
|
|
1225
|
+
|
|
1226
|
+
const transferField = field.getField("Transfer")
|
|
1227
|
+
expect(transferField.type).toBe("record")
|
|
1228
|
+
|
|
1229
|
+
const burnField = field.getField("Burn")
|
|
1230
|
+
expect(burnField.type).toBe("text") // nat is rendered as text for large numbers
|
|
1231
|
+
})
|
|
1232
|
+
|
|
1233
|
+
it("getSelectedOption should return the selected option from a value", () => {
|
|
1234
|
+
const variantType = IDL.Variant({ A: IDL.Null, B: IDL.Text, C: IDL.Nat })
|
|
1235
|
+
const field = visitor.visitVariant(
|
|
1236
|
+
variantType,
|
|
1237
|
+
[
|
|
1238
|
+
["A", IDL.Null],
|
|
1239
|
+
["B", IDL.Text],
|
|
1240
|
+
["C", IDL.Nat],
|
|
1241
|
+
],
|
|
1242
|
+
"choice"
|
|
1243
|
+
)
|
|
1244
|
+
|
|
1245
|
+
expect(field.getSelectedOption({ A: null })).toBe("A")
|
|
1246
|
+
expect(field.getSelectedOption({ B: "hello" })).toBe("B")
|
|
1247
|
+
expect(field.getSelectedOption({ C: "100" })).toBe("C")
|
|
1248
|
+
// Falls back to default option for unknown values
|
|
1249
|
+
expect(field.getSelectedOption({})).toBe("A")
|
|
1250
|
+
})
|
|
1251
|
+
|
|
1252
|
+
it("getSelectedField should return the field for the selected option", () => {
|
|
1253
|
+
const variantType = IDL.Variant({
|
|
1254
|
+
Ok: IDL.Nat,
|
|
1255
|
+
Err: IDL.Text,
|
|
1256
|
+
})
|
|
1257
|
+
const field = visitor.visitVariant(
|
|
1258
|
+
variantType,
|
|
1259
|
+
[
|
|
1260
|
+
["Ok", IDL.Nat],
|
|
1261
|
+
["Err", IDL.Text],
|
|
1262
|
+
],
|
|
1263
|
+
"result"
|
|
1264
|
+
)
|
|
1265
|
+
|
|
1266
|
+
const okField = field.getSelectedField({ Ok: "100" })
|
|
1267
|
+
expect(okField.label).toBe("Ok")
|
|
1268
|
+
|
|
1269
|
+
const errField = field.getSelectedField({ Err: "error" })
|
|
1270
|
+
expect(errField.label).toBe("Err")
|
|
1271
|
+
})
|
|
1272
|
+
})
|
|
1273
|
+
|
|
1274
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1275
|
+
// Optional isEnabled Helper
|
|
1276
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1277
|
+
|
|
1278
|
+
describe("Optional isEnabled helper", () => {
|
|
1279
|
+
it("should return true for non-null values", () => {
|
|
1280
|
+
const optType = IDL.Opt(IDL.Text)
|
|
1281
|
+
const field = visitor.visitOpt(optType, IDL.Text, "optional")
|
|
1282
|
+
|
|
1283
|
+
expect(field.isEnabled("hello")).toBe(true)
|
|
1284
|
+
expect(field.isEnabled("")).toBe(true)
|
|
1285
|
+
expect(field.isEnabled(0)).toBe(true)
|
|
1286
|
+
expect(field.isEnabled(false)).toBe(true)
|
|
1287
|
+
expect(field.isEnabled({})).toBe(true)
|
|
1288
|
+
})
|
|
1289
|
+
|
|
1290
|
+
it("should return false for null and undefined", () => {
|
|
1291
|
+
const optType = IDL.Opt(IDL.Text)
|
|
1292
|
+
const field = visitor.visitOpt(optType, IDL.Text, "optional")
|
|
1293
|
+
|
|
1294
|
+
expect(field.isEnabled(null)).toBe(false)
|
|
1295
|
+
expect(field.isEnabled(undefined)).toBe(false)
|
|
1296
|
+
})
|
|
1297
|
+
})
|
|
1298
|
+
|
|
1299
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1300
|
+
// Vector createItemField Helper
|
|
1301
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1302
|
+
|
|
1303
|
+
describe("Vector createItemField helper", () => {
|
|
1304
|
+
it("should create item field with correct index in name path", () => {
|
|
1305
|
+
const funcType = IDL.Func([IDL.Vec(IDL.Text)], [], [])
|
|
1306
|
+
const meta = visitor.visitFunc(funcType, "addItems")
|
|
1307
|
+
const vecField = meta.fields[0] as VectorField
|
|
1308
|
+
|
|
1309
|
+
const item0 = vecField.createItemField(0)
|
|
1310
|
+
expect(item0.name).toBe("[0][0]")
|
|
1311
|
+
|
|
1312
|
+
const item5 = vecField.createItemField(5)
|
|
1313
|
+
expect(item5.name).toBe("[0][5]")
|
|
1314
|
+
})
|
|
1315
|
+
|
|
1316
|
+
it("should use custom label when provided", () => {
|
|
1317
|
+
const funcType = IDL.Func(
|
|
1318
|
+
[IDL.Vec(IDL.Record({ name: IDL.Text }))],
|
|
1319
|
+
[],
|
|
1320
|
+
[]
|
|
1321
|
+
)
|
|
1322
|
+
const meta = visitor.visitFunc(funcType, "addItems")
|
|
1323
|
+
const vecField = meta.fields[0] as VectorField
|
|
1324
|
+
|
|
1325
|
+
const item = vecField.createItemField(3, { label: "Person 3" })
|
|
1326
|
+
expect(item.label).toBe("Person 3")
|
|
1327
|
+
expect(item.displayLabel).toBe("Person 3")
|
|
1328
|
+
})
|
|
1329
|
+
|
|
1330
|
+
it("should use default label when not provided", () => {
|
|
1331
|
+
const funcType = IDL.Func([IDL.Vec(IDL.Text)], [], [])
|
|
1332
|
+
const meta = visitor.visitFunc(funcType, "addTags")
|
|
1333
|
+
const vecField = meta.fields[0] as VectorField
|
|
1334
|
+
|
|
1335
|
+
const item = vecField.createItemField(2)
|
|
1336
|
+
expect(item.label).toBe("Item 2")
|
|
1337
|
+
expect(item.displayLabel).toBe("Item 2")
|
|
1338
|
+
})
|
|
1339
|
+
})
|
|
1340
|
+
|
|
1341
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1342
|
+
// Blob Field Utilities
|
|
1343
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1344
|
+
|
|
1345
|
+
describe("Blob field utilities", () => {
|
|
1346
|
+
it("should have limits defined", () => {
|
|
1347
|
+
const blobType = IDL.Vec(IDL.Nat8)
|
|
1348
|
+
const field = visitor.visitVec(blobType, IDL.Nat8, "data")
|
|
1349
|
+
|
|
1350
|
+
if (field.type === "blob") {
|
|
1351
|
+
expect(field.limits).toBeDefined()
|
|
1352
|
+
expect(field.limits.maxHexBytes).toBeGreaterThan(0)
|
|
1353
|
+
expect(field.limits.maxFileBytes).toBeGreaterThan(0)
|
|
1354
|
+
expect(field.limits.maxHexDisplayLength).toBeGreaterThan(0)
|
|
1355
|
+
}
|
|
1356
|
+
})
|
|
1357
|
+
|
|
1358
|
+
it("normalizeHex should remove 0x prefix and lowercase", () => {
|
|
1359
|
+
const blobType = IDL.Vec(IDL.Nat8)
|
|
1360
|
+
const field = visitor.visitVec(blobType, IDL.Nat8, "data")
|
|
1361
|
+
|
|
1362
|
+
if (field.type === "blob") {
|
|
1363
|
+
expect(field.normalizeHex("0xDEADBEEF")).toBe("deadbeef")
|
|
1364
|
+
expect(field.normalizeHex("DEADBEEF")).toBe("deadbeef")
|
|
1365
|
+
expect(field.normalizeHex("0x")).toBe("")
|
|
1366
|
+
expect(field.normalizeHex("abc123")).toBe("abc123")
|
|
1367
|
+
}
|
|
1368
|
+
})
|
|
1369
|
+
|
|
1370
|
+
it("validateInput should validate hex strings", () => {
|
|
1371
|
+
const blobType = IDL.Vec(IDL.Nat8)
|
|
1372
|
+
const field = visitor.visitVec(blobType, IDL.Nat8, "data")
|
|
1373
|
+
|
|
1374
|
+
if (field.type === "blob") {
|
|
1375
|
+
// Valid inputs
|
|
1376
|
+
expect(field.validateInput("").valid).toBe(true)
|
|
1377
|
+
expect(field.validateInput("deadbeef").valid).toBe(true)
|
|
1378
|
+
expect(field.validateInput("0x1234").valid).toBe(true)
|
|
1379
|
+
|
|
1380
|
+
// Invalid inputs
|
|
1381
|
+
expect(field.validateInput("xyz").valid).toBe(false)
|
|
1382
|
+
expect(field.validateInput("abc").valid).toBe(false) // odd length
|
|
1383
|
+
}
|
|
1384
|
+
})
|
|
1385
|
+
|
|
1386
|
+
it("validateInput should validate Uint8Array", () => {
|
|
1387
|
+
const blobType = IDL.Vec(IDL.Nat8)
|
|
1388
|
+
const field = visitor.visitVec(blobType, IDL.Nat8, "data")
|
|
1389
|
+
|
|
1390
|
+
if (field.type === "blob") {
|
|
1391
|
+
expect(field.validateInput(new Uint8Array([1, 2, 3])).valid).toBe(true)
|
|
1392
|
+
}
|
|
1393
|
+
})
|
|
1394
|
+
})
|
|
975
1395
|
})
|