@sundaeswap/sprinkles 0.3.0 → 0.5.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/cjs/Sprinkle/__tests__/encryption.test.js +20 -8
- package/dist/cjs/Sprinkle/__tests__/encryption.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/enhancements.test.js +41 -16
- package/dist/cjs/Sprinkle/__tests__/enhancements.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js +85 -38
- package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/settings-persistence.test.js +120 -0
- package/dist/cjs/Sprinkle/__tests__/settings-persistence.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/show-menu.test.js +93 -7
- package/dist/cjs/Sprinkle/__tests__/show-menu.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js +21 -0
- package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js.map +1 -1
- package/dist/cjs/Sprinkle/encryption.js +131 -0
- package/dist/cjs/Sprinkle/encryption.js.map +1 -0
- package/dist/cjs/Sprinkle/index.js +318 -352
- package/dist/cjs/Sprinkle/index.js.map +1 -1
- package/dist/cjs/Sprinkle/prompts.js +393 -0
- package/dist/cjs/Sprinkle/prompts.js.map +1 -0
- package/dist/cjs/Sprinkle/schemas.js +97 -0
- package/dist/cjs/Sprinkle/schemas.js.map +1 -0
- package/dist/cjs/Sprinkle/tx-dialog.js +101 -0
- package/dist/cjs/Sprinkle/tx-dialog.js.map +1 -0
- package/dist/cjs/Sprinkle/type-guards.js +42 -0
- package/dist/cjs/Sprinkle/type-guards.js.map +1 -0
- package/dist/cjs/Sprinkle/types.js +49 -0
- package/dist/cjs/Sprinkle/types.js.map +1 -0
- package/dist/cjs/Sprinkle/wallet.js +98 -0
- package/dist/cjs/Sprinkle/wallet.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/encryption.test.js +20 -8
- package/dist/esm/Sprinkle/__tests__/encryption.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/enhancements.test.js +41 -16
- package/dist/esm/Sprinkle/__tests__/enhancements.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js +85 -38
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/settings-persistence.test.js +120 -0
- package/dist/esm/Sprinkle/__tests__/settings-persistence.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/show-menu.test.js +94 -8
- package/dist/esm/Sprinkle/__tests__/show-menu.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js +21 -0
- package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js.map +1 -1
- package/dist/esm/Sprinkle/encryption.js +117 -0
- package/dist/esm/Sprinkle/encryption.js.map +1 -0
- package/dist/esm/Sprinkle/index.js +172 -337
- package/dist/esm/Sprinkle/index.js.map +1 -1
- package/dist/esm/Sprinkle/prompts.js +385 -0
- package/dist/esm/Sprinkle/prompts.js.map +1 -0
- package/dist/esm/Sprinkle/schemas.js +91 -0
- package/dist/esm/Sprinkle/schemas.js.map +1 -0
- package/dist/esm/Sprinkle/tx-dialog.js +90 -0
- package/dist/esm/Sprinkle/tx-dialog.js.map +1 -0
- package/dist/esm/Sprinkle/type-guards.js +24 -0
- package/dist/esm/Sprinkle/type-guards.js.map +1 -0
- package/dist/esm/Sprinkle/types.js +42 -0
- package/dist/esm/Sprinkle/types.js.map +1 -0
- package/dist/esm/Sprinkle/wallet.js +90 -0
- package/dist/esm/Sprinkle/wallet.js.map +1 -0
- package/dist/types/Sprinkle/encryption.d.ts +43 -0
- package/dist/types/Sprinkle/encryption.d.ts.map +1 -0
- package/dist/types/Sprinkle/index.d.ts +13 -174
- package/dist/types/Sprinkle/index.d.ts.map +1 -1
- package/dist/types/Sprinkle/prompts.d.ts +94 -0
- package/dist/types/Sprinkle/prompts.d.ts.map +1 -0
- package/dist/types/Sprinkle/schemas.d.ts +125 -0
- package/dist/types/Sprinkle/schemas.d.ts.map +1 -0
- package/dist/types/Sprinkle/tx-dialog.d.ts +37 -0
- package/dist/types/Sprinkle/tx-dialog.d.ts.map +1 -0
- package/dist/types/Sprinkle/type-guards.d.ts +22 -0
- package/dist/types/Sprinkle/type-guards.d.ts.map +1 -0
- package/dist/types/Sprinkle/types.d.ts +62 -0
- package/dist/types/Sprinkle/types.d.ts.map +1 -0
- package/dist/types/Sprinkle/wallet.d.ts +27 -0
- package/dist/types/Sprinkle/wallet.d.ts.map +1 -0
- package/dist/types/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/Sprinkle/__tests__/encryption.test.ts +21 -8
- package/src/Sprinkle/__tests__/enhancements.test.ts +41 -15
- package/src/Sprinkle/__tests__/fill-in-struct.test.ts +104 -38
- package/src/Sprinkle/__tests__/settings-persistence.test.ts +108 -0
- package/src/Sprinkle/__tests__/show-menu.test.ts +96 -8
- package/src/Sprinkle/__tests__/tx-dialog.test.ts +21 -0
- package/src/Sprinkle/encryption.ts +130 -0
- package/src/Sprinkle/index.ts +265 -478
- package/src/Sprinkle/prompts.ts +481 -0
- package/src/Sprinkle/schemas.ts +111 -0
- package/src/Sprinkle/tx-dialog.ts +100 -0
- package/src/Sprinkle/type-guards.ts +51 -0
- package/src/Sprinkle/types.ts +73 -0
- package/src/Sprinkle/wallet.ts +133 -0
package/package.json
CHANGED
|
@@ -8,6 +8,9 @@ import { withProfile } from "./test-helpers.js";
|
|
|
8
8
|
const mockSelect = mock();
|
|
9
9
|
const mockInput = mock();
|
|
10
10
|
const mockPassword = mock();
|
|
11
|
+
const mockSelectCancellable = mock();
|
|
12
|
+
const mockInputCancellable = mock();
|
|
13
|
+
const mockPasswordCancellable = mock();
|
|
11
14
|
|
|
12
15
|
mock.module("@inquirer/prompts", () => ({
|
|
13
16
|
select: mockSelect,
|
|
@@ -15,6 +18,13 @@ mock.module("@inquirer/prompts", () => ({
|
|
|
15
18
|
password: mockPassword,
|
|
16
19
|
}));
|
|
17
20
|
|
|
21
|
+
mock.module("../prompts.js", () => ({
|
|
22
|
+
selectCancellable: mockSelectCancellable,
|
|
23
|
+
inputCancellable: mockInputCancellable,
|
|
24
|
+
passwordCancellable: mockPasswordCancellable,
|
|
25
|
+
confirmCancellable: mock(),
|
|
26
|
+
}));
|
|
27
|
+
|
|
18
28
|
describe("Encryption & Sensitive Fields", () => {
|
|
19
29
|
let tmpDir: string;
|
|
20
30
|
|
|
@@ -23,6 +33,9 @@ describe("Encryption & Sensitive Fields", () => {
|
|
|
23
33
|
mockSelect.mockClear();
|
|
24
34
|
mockInput.mockClear();
|
|
25
35
|
mockPassword.mockClear();
|
|
36
|
+
mockSelectCancellable.mockClear();
|
|
37
|
+
mockInputCancellable.mockClear();
|
|
38
|
+
mockPasswordCancellable.mockClear();
|
|
26
39
|
});
|
|
27
40
|
|
|
28
41
|
afterEach(() => {
|
|
@@ -36,13 +49,13 @@ describe("Encryption & Sensitive Fields", () => {
|
|
|
36
49
|
});
|
|
37
50
|
const sprinkle = new Sprinkle(schema, tmpDir);
|
|
38
51
|
|
|
39
|
-
|
|
52
|
+
mockPasswordCancellable.mockResolvedValueOnce("my-secret");
|
|
40
53
|
|
|
41
54
|
const result = await sprinkle.FillInStruct(schema);
|
|
42
55
|
expect(result).toEqual({ secret: "my-secret" });
|
|
43
|
-
expect(
|
|
44
|
-
expect(
|
|
45
|
-
expect(
|
|
56
|
+
expect(mockPasswordCancellable).toHaveBeenCalledTimes(1);
|
|
57
|
+
expect(mockPasswordCancellable.mock.calls[0][0].message).toBe("Enter secret");
|
|
58
|
+
expect(mockInputCancellable).not.toHaveBeenCalled();
|
|
46
59
|
});
|
|
47
60
|
|
|
48
61
|
test("uses input() for non-sensitive string fields", async () => {
|
|
@@ -51,19 +64,19 @@ describe("Encryption & Sensitive Fields", () => {
|
|
|
51
64
|
});
|
|
52
65
|
const sprinkle = new Sprinkle(schema, tmpDir);
|
|
53
66
|
|
|
54
|
-
|
|
67
|
+
mockInputCancellable.mockResolvedValueOnce("visible");
|
|
55
68
|
|
|
56
69
|
const result = await sprinkle.FillInStruct(schema);
|
|
57
70
|
expect(result).toEqual({ name: "visible" });
|
|
58
|
-
expect(
|
|
59
|
-
expect(
|
|
71
|
+
expect(mockInputCancellable).toHaveBeenCalledTimes(1);
|
|
72
|
+
expect(mockPasswordCancellable).not.toHaveBeenCalled();
|
|
60
73
|
});
|
|
61
74
|
|
|
62
75
|
test("does not remember sensitive field as default", async () => {
|
|
63
76
|
const schema = Type.String({ sensitive: true });
|
|
64
77
|
const sprinkle = new Sprinkle(Type.Object({ p: Type.String() }), tmpDir);
|
|
65
78
|
|
|
66
|
-
|
|
79
|
+
mockPasswordCancellable.mockResolvedValueOnce("secret-val");
|
|
67
80
|
|
|
68
81
|
await sprinkle.FillInStruct(schema);
|
|
69
82
|
expect(sprinkle.defaults["string"]).toBeUndefined();
|
|
@@ -4,13 +4,25 @@ import { Sprinkle, Type, type IMenu } from "../index.js";
|
|
|
4
4
|
const mockSelect = mock();
|
|
5
5
|
const mockInput = mock();
|
|
6
6
|
const mockPassword = mock();
|
|
7
|
-
const
|
|
7
|
+
const mockSelectCancellable = mock();
|
|
8
|
+
const mockInputCancellable = mock();
|
|
9
|
+
const mockPasswordCancellable = mock();
|
|
10
|
+
const mockConfirmCancellable = mock();
|
|
11
|
+
const mockSearchCancellable = mock();
|
|
8
12
|
|
|
9
13
|
mock.module("@inquirer/prompts", () => ({
|
|
10
14
|
select: mockSelect,
|
|
11
15
|
input: mockInput,
|
|
12
16
|
password: mockPassword,
|
|
13
|
-
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
mock.module("../prompts.js", () => ({
|
|
20
|
+
selectCancellable: mockSelectCancellable,
|
|
21
|
+
inputCancellable: mockInputCancellable,
|
|
22
|
+
passwordCancellable: mockPasswordCancellable,
|
|
23
|
+
confirmCancellable: mockConfirmCancellable,
|
|
24
|
+
searchCancellable: mockSearchCancellable,
|
|
25
|
+
select: mockSelectCancellable,
|
|
14
26
|
}));
|
|
15
27
|
|
|
16
28
|
describe("beforeShow hook (2.2)", () => {
|
|
@@ -21,6 +33,7 @@ describe("beforeShow hook (2.2)", () => {
|
|
|
21
33
|
sprinkle = new Sprinkle(schema, "/tmp/test");
|
|
22
34
|
sprinkle.settings = { name: "test" } as any;
|
|
23
35
|
mockSelect.mockClear();
|
|
36
|
+
mockSelectCancellable.mockClear();
|
|
24
37
|
});
|
|
25
38
|
|
|
26
39
|
test("calls beforeShow before rendering menu", async () => {
|
|
@@ -30,7 +43,7 @@ describe("beforeShow hook (2.2)", () => {
|
|
|
30
43
|
callOrder.push("beforeShow");
|
|
31
44
|
});
|
|
32
45
|
|
|
33
|
-
|
|
46
|
+
mockSelectCancellable.mockImplementation(async () => {
|
|
34
47
|
callOrder.push("select");
|
|
35
48
|
return -1; // exit
|
|
36
49
|
});
|
|
@@ -51,7 +64,7 @@ describe("beforeShow hook (2.2)", () => {
|
|
|
51
64
|
test("passes sprinkle instance to beforeShow", async () => {
|
|
52
65
|
let receivedSprinkle: any;
|
|
53
66
|
|
|
54
|
-
|
|
67
|
+
mockSelectCancellable.mockResolvedValueOnce(-1);
|
|
55
68
|
|
|
56
69
|
const menu: IMenu<any> = {
|
|
57
70
|
title: "Test",
|
|
@@ -66,7 +79,7 @@ describe("beforeShow hook (2.2)", () => {
|
|
|
66
79
|
});
|
|
67
80
|
|
|
68
81
|
test("menu works without beforeShow", async () => {
|
|
69
|
-
|
|
82
|
+
mockSelectCancellable.mockResolvedValueOnce(-1);
|
|
70
83
|
|
|
71
84
|
const menu: IMenu<any> = {
|
|
72
85
|
title: "Test",
|
|
@@ -80,11 +93,11 @@ describe("beforeShow hook (2.2)", () => {
|
|
|
80
93
|
|
|
81
94
|
describe("SearchSelect (2.3)", () => {
|
|
82
95
|
beforeEach(() => {
|
|
83
|
-
|
|
96
|
+
mockSearchCancellable.mockClear();
|
|
84
97
|
});
|
|
85
98
|
|
|
86
99
|
test("delegates to search prompt", async () => {
|
|
87
|
-
|
|
100
|
+
mockSearchCancellable.mockResolvedValueOnce("selected-value");
|
|
88
101
|
|
|
89
102
|
const result = await Sprinkle.SearchSelect({
|
|
90
103
|
message: "Pick one",
|
|
@@ -95,20 +108,31 @@ describe("SearchSelect (2.3)", () => {
|
|
|
95
108
|
});
|
|
96
109
|
|
|
97
110
|
expect(result).toBe("selected-value");
|
|
98
|
-
expect(
|
|
99
|
-
expect(
|
|
111
|
+
expect(mockSearchCancellable).toHaveBeenCalledTimes(1);
|
|
112
|
+
expect(mockSearchCancellable.mock.calls[0][0].message).toBe("Pick one");
|
|
100
113
|
});
|
|
101
114
|
|
|
102
115
|
test("passes source function through", async () => {
|
|
103
116
|
const sourceFn = mock(() => [{ name: "X", value: "x" }]);
|
|
104
|
-
|
|
117
|
+
mockSearchCancellable.mockResolvedValueOnce("x");
|
|
105
118
|
|
|
106
119
|
await Sprinkle.SearchSelect({
|
|
107
120
|
message: "Search",
|
|
108
121
|
source: sourceFn,
|
|
109
122
|
});
|
|
110
123
|
|
|
111
|
-
expect(
|
|
124
|
+
expect(mockSearchCancellable.mock.calls[0][0].source).toBe(sourceFn);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("returns null when cancelled", async () => {
|
|
128
|
+
mockSearchCancellable.mockResolvedValueOnce(null);
|
|
129
|
+
|
|
130
|
+
const result = await Sprinkle.SearchSelect({
|
|
131
|
+
message: "Pick one",
|
|
132
|
+
source: () => [{ name: "A", value: "a" }],
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(result).toBeNull();
|
|
112
136
|
});
|
|
113
137
|
});
|
|
114
138
|
|
|
@@ -120,6 +144,8 @@ describe("Optional type support (2.4)", () => {
|
|
|
120
144
|
sprinkle = new Sprinkle(schema, "/tmp/test");
|
|
121
145
|
mockSelect.mockClear();
|
|
122
146
|
mockInput.mockClear();
|
|
147
|
+
mockSelectCancellable.mockClear();
|
|
148
|
+
mockInputCancellable.mockClear();
|
|
123
149
|
});
|
|
124
150
|
|
|
125
151
|
test("skips optional field when user selects Skip", async () => {
|
|
@@ -128,8 +154,8 @@ describe("Optional type support (2.4)", () => {
|
|
|
128
154
|
nickname: Type.Optional(Type.String()),
|
|
129
155
|
});
|
|
130
156
|
|
|
131
|
-
|
|
132
|
-
|
|
157
|
+
mockInputCancellable.mockResolvedValueOnce("Alice"); // name
|
|
158
|
+
mockSelectCancellable.mockResolvedValueOnce(false); // skip nickname
|
|
133
159
|
|
|
134
160
|
const result = await sprinkle.FillInStruct(schema);
|
|
135
161
|
expect(result.name).toBe("Alice");
|
|
@@ -142,10 +168,10 @@ describe("Optional type support (2.4)", () => {
|
|
|
142
168
|
nickname: Type.Optional(Type.String()),
|
|
143
169
|
});
|
|
144
170
|
|
|
145
|
-
|
|
171
|
+
mockInputCancellable
|
|
146
172
|
.mockResolvedValueOnce("Alice") // name
|
|
147
173
|
.mockResolvedValueOnce("Ali"); // nickname
|
|
148
|
-
|
|
174
|
+
mockSelectCancellable.mockResolvedValueOnce(true); // fill nickname
|
|
149
175
|
|
|
150
176
|
const result = await sprinkle.FillInStruct(schema);
|
|
151
177
|
expect(result.name).toBe("Alice");
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { describe, expect, test, mock, beforeEach, spyOn } from "bun:test";
|
|
2
2
|
import { Sprinkle, Type, WalletSettingsSchema } from "../index.js";
|
|
3
|
+
import { UserCancelledError } from "../types.js";
|
|
3
4
|
|
|
4
5
|
// Mock @inquirer/prompts
|
|
5
6
|
const mockSelect = mock();
|
|
6
7
|
const mockInput = mock();
|
|
7
8
|
const mockPassword = mock();
|
|
8
9
|
const mockConfirm = mock();
|
|
10
|
+
const mockSelectCancellable = mock();
|
|
11
|
+
const mockInputCancellable = mock();
|
|
12
|
+
const mockPasswordCancellable = mock();
|
|
13
|
+
const mockConfirmCancellable = mock();
|
|
9
14
|
|
|
10
15
|
mock.module("@inquirer/prompts", () => ({
|
|
11
16
|
select: mockSelect,
|
|
@@ -14,6 +19,13 @@ mock.module("@inquirer/prompts", () => ({
|
|
|
14
19
|
confirm: mockConfirm,
|
|
15
20
|
}));
|
|
16
21
|
|
|
22
|
+
mock.module("../prompts.js", () => ({
|
|
23
|
+
selectCancellable: mockSelectCancellable,
|
|
24
|
+
inputCancellable: mockInputCancellable,
|
|
25
|
+
passwordCancellable: mockPasswordCancellable,
|
|
26
|
+
confirmCancellable: mockConfirmCancellable,
|
|
27
|
+
}));
|
|
28
|
+
|
|
17
29
|
describe("FillInStruct", () => {
|
|
18
30
|
let sprinkle: Sprinkle<any>;
|
|
19
31
|
|
|
@@ -22,24 +34,30 @@ describe("FillInStruct", () => {
|
|
|
22
34
|
sprinkle = new Sprinkle(schema, "/tmp/test");
|
|
23
35
|
mockSelect.mockClear();
|
|
24
36
|
mockInput.mockClear();
|
|
37
|
+
mockPassword.mockClear();
|
|
38
|
+
mockConfirm.mockClear();
|
|
39
|
+
mockSelectCancellable.mockClear();
|
|
40
|
+
mockInputCancellable.mockClear();
|
|
41
|
+
mockPasswordCancellable.mockClear();
|
|
42
|
+
mockConfirmCancellable.mockClear();
|
|
25
43
|
});
|
|
26
44
|
|
|
27
45
|
test("fills a simple string field", async () => {
|
|
28
|
-
|
|
46
|
+
mockInputCancellable.mockResolvedValueOnce("hello");
|
|
29
47
|
|
|
30
48
|
const result = await sprinkle.FillInStruct(Type.String());
|
|
31
49
|
expect(result).toBe("hello");
|
|
32
50
|
});
|
|
33
51
|
|
|
34
52
|
test("fills a string with title as prompt", async () => {
|
|
35
|
-
|
|
53
|
+
mockInputCancellable.mockResolvedValueOnce("world");
|
|
36
54
|
|
|
37
55
|
await sprinkle.FillInStruct(Type.String({ title: "Enter name" }));
|
|
38
|
-
expect(
|
|
56
|
+
expect(mockInputCancellable.mock.calls[0][0].message).toBe("Enter name");
|
|
39
57
|
});
|
|
40
58
|
|
|
41
59
|
test("fills a bigint field", async () => {
|
|
42
|
-
|
|
60
|
+
mockInputCancellable.mockResolvedValueOnce("42");
|
|
43
61
|
|
|
44
62
|
const result = await sprinkle.FillInStruct(Type.BigInt());
|
|
45
63
|
expect(result).toBe(42n);
|
|
@@ -48,8 +66,8 @@ describe("FillInStruct", () => {
|
|
|
48
66
|
test("fills a literal field without prompting", async () => {
|
|
49
67
|
const result = await sprinkle.FillInStruct(Type.Literal("fixed"));
|
|
50
68
|
expect(result).toBe("fixed");
|
|
51
|
-
expect(
|
|
52
|
-
expect(
|
|
69
|
+
expect(mockInputCancellable).not.toHaveBeenCalled();
|
|
70
|
+
expect(mockSelectCancellable).not.toHaveBeenCalled();
|
|
53
71
|
});
|
|
54
72
|
|
|
55
73
|
test("fills an object with multiple fields", async () => {
|
|
@@ -58,7 +76,7 @@ describe("FillInStruct", () => {
|
|
|
58
76
|
age: Type.BigInt(),
|
|
59
77
|
});
|
|
60
78
|
|
|
61
|
-
|
|
79
|
+
mockInputCancellable
|
|
62
80
|
.mockResolvedValueOnce("Alice")
|
|
63
81
|
.mockResolvedValueOnce("30");
|
|
64
82
|
|
|
@@ -79,10 +97,10 @@ describe("FillInStruct", () => {
|
|
|
79
97
|
]);
|
|
80
98
|
|
|
81
99
|
// Select first variant (the Object with type "a")
|
|
82
|
-
|
|
100
|
+
mockSelectCancellable.mockImplementationOnce(async (opts: any) => {
|
|
83
101
|
return opts.choices[0].value;
|
|
84
102
|
});
|
|
85
|
-
|
|
103
|
+
mockInputCancellable.mockResolvedValueOnce("test-value");
|
|
86
104
|
|
|
87
105
|
const result = await sprinkle.FillInStruct(schema);
|
|
88
106
|
expect(result).toEqual({ type: "a", value: "test-value" });
|
|
@@ -91,10 +109,10 @@ describe("FillInStruct", () => {
|
|
|
91
109
|
test("fills an array with items", async () => {
|
|
92
110
|
const schema = Type.Array(Type.String());
|
|
93
111
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
112
|
+
mockInputCancellable.mockResolvedValueOnce("first");
|
|
113
|
+
mockSelectCancellable.mockResolvedValueOnce(true); // add another
|
|
114
|
+
mockInputCancellable.mockResolvedValueOnce("second");
|
|
115
|
+
mockSelectCancellable.mockResolvedValueOnce(false); // stop
|
|
98
116
|
|
|
99
117
|
const result = await sprinkle.FillInStruct(schema);
|
|
100
118
|
expect(result).toEqual(["first", "second"]);
|
|
@@ -103,30 +121,30 @@ describe("FillInStruct", () => {
|
|
|
103
121
|
test("fills an array with single item", async () => {
|
|
104
122
|
const schema = Type.Array(Type.String());
|
|
105
123
|
|
|
106
|
-
|
|
107
|
-
|
|
124
|
+
mockInputCancellable.mockResolvedValueOnce("only");
|
|
125
|
+
mockSelectCancellable.mockResolvedValueOnce(false); // stop
|
|
108
126
|
|
|
109
127
|
const result = await sprinkle.FillInStruct(schema);
|
|
110
128
|
expect(result).toEqual(["only"]);
|
|
111
129
|
});
|
|
112
130
|
|
|
113
131
|
test("uses default value for string", async () => {
|
|
114
|
-
|
|
132
|
+
mockInputCancellable.mockResolvedValueOnce("used-default");
|
|
115
133
|
|
|
116
134
|
await sprinkle.FillInStruct(
|
|
117
135
|
Type.String(),
|
|
118
136
|
"my-default" as any,
|
|
119
137
|
);
|
|
120
138
|
|
|
121
|
-
expect(
|
|
139
|
+
expect(mockInputCancellable.mock.calls[0][0].default).toBe("my-default");
|
|
122
140
|
});
|
|
123
141
|
|
|
124
142
|
test("uses default value for bigint", async () => {
|
|
125
|
-
|
|
143
|
+
mockInputCancellable.mockResolvedValueOnce("99");
|
|
126
144
|
|
|
127
145
|
await sprinkle.FillInStruct(Type.BigInt(), 99n as any);
|
|
128
146
|
|
|
129
|
-
expect(
|
|
147
|
+
expect(mockInputCancellable.mock.calls[0][0].default).toBe("99");
|
|
130
148
|
});
|
|
131
149
|
|
|
132
150
|
test("throws for unsupported types", async () => {
|
|
@@ -142,14 +160,14 @@ describe("FillInStruct", () => {
|
|
|
142
160
|
}),
|
|
143
161
|
});
|
|
144
162
|
|
|
145
|
-
|
|
163
|
+
mockInputCancellable.mockResolvedValueOnce("deep-value");
|
|
146
164
|
|
|
147
165
|
const result = await sprinkle.FillInStruct(schema);
|
|
148
166
|
expect(result).toEqual({ outer: { inner: "deep-value" } });
|
|
149
167
|
});
|
|
150
168
|
|
|
151
169
|
test("remembers last string input as default", async () => {
|
|
152
|
-
|
|
170
|
+
mockInputCancellable
|
|
153
171
|
.mockResolvedValueOnce("first-input")
|
|
154
172
|
.mockResolvedValueOnce("second-input");
|
|
155
173
|
|
|
@@ -158,13 +176,13 @@ describe("FillInStruct", () => {
|
|
|
158
176
|
|
|
159
177
|
await sprinkle.FillInStruct(Type.String());
|
|
160
178
|
// Second call should have the first input as default
|
|
161
|
-
expect(
|
|
179
|
+
expect(mockInputCancellable.mock.calls[1][0].default).toBe("first-input");
|
|
162
180
|
});
|
|
163
181
|
|
|
164
182
|
test("fills a tuple with same-type elements", async () => {
|
|
165
183
|
const schema = Type.Tuple([Type.String(), Type.String()]);
|
|
166
184
|
|
|
167
|
-
|
|
185
|
+
mockInputCancellable
|
|
168
186
|
.mockResolvedValueOnce("policyId")
|
|
169
187
|
.mockResolvedValueOnce("assetName");
|
|
170
188
|
|
|
@@ -175,7 +193,7 @@ describe("FillInStruct", () => {
|
|
|
175
193
|
test("fills a tuple with mixed types", async () => {
|
|
176
194
|
const schema = Type.Tuple([Type.String(), Type.BigInt()]);
|
|
177
195
|
|
|
178
|
-
|
|
196
|
+
mockInputCancellable
|
|
179
197
|
.mockResolvedValueOnce("label")
|
|
180
198
|
.mockResolvedValueOnce("42");
|
|
181
199
|
|
|
@@ -186,14 +204,14 @@ describe("FillInStruct", () => {
|
|
|
186
204
|
test("fills a tuple with default values", async () => {
|
|
187
205
|
const schema = Type.Tuple([Type.String(), Type.BigInt()]);
|
|
188
206
|
|
|
189
|
-
|
|
207
|
+
mockInputCancellable
|
|
190
208
|
.mockResolvedValueOnce("new-label")
|
|
191
209
|
.mockResolvedValueOnce("99");
|
|
192
210
|
|
|
193
211
|
await sprinkle.FillInStruct(schema, ["default-label", 50n] as any);
|
|
194
212
|
|
|
195
|
-
expect(
|
|
196
|
-
expect(
|
|
213
|
+
expect(mockInputCancellable.mock.calls[0][0].default).toBe("default-label");
|
|
214
|
+
expect(mockInputCancellable.mock.calls[1][0].default).toBe("50");
|
|
197
215
|
});
|
|
198
216
|
|
|
199
217
|
test("fills a tuple nested in an object", async () => {
|
|
@@ -201,7 +219,7 @@ describe("FillInStruct", () => {
|
|
|
201
219
|
asset: Type.Tuple([Type.String(), Type.String()]),
|
|
202
220
|
});
|
|
203
221
|
|
|
204
|
-
|
|
222
|
+
mockInputCancellable
|
|
205
223
|
.mockResolvedValueOnce("policy123")
|
|
206
224
|
.mockResolvedValueOnce("token456");
|
|
207
225
|
|
|
@@ -213,14 +231,14 @@ describe("FillInStruct", () => {
|
|
|
213
231
|
const schema = Type.String({ title: "Hot Wallet Private Key" });
|
|
214
232
|
|
|
215
233
|
// Select "existing" option
|
|
216
|
-
|
|
217
|
-
|
|
234
|
+
mockSelectCancellable.mockResolvedValueOnce("existing");
|
|
235
|
+
mockPasswordCancellable.mockResolvedValueOnce("my-private-key");
|
|
218
236
|
|
|
219
237
|
const result = await sprinkle.FillInStruct(schema);
|
|
220
238
|
|
|
221
239
|
// Verify select was called with correct options
|
|
222
|
-
expect(
|
|
223
|
-
expect(
|
|
240
|
+
expect(mockSelectCancellable.mock.calls[0][0].message).toBe("Hot wallet setup:");
|
|
241
|
+
expect(mockSelectCancellable.mock.calls[0][0].choices).toEqual([
|
|
224
242
|
{ name: "Enter existing private key", value: "existing" },
|
|
225
243
|
{ name: "Generate new wallet", value: "generate" },
|
|
226
244
|
]);
|
|
@@ -230,12 +248,12 @@ describe("FillInStruct", () => {
|
|
|
230
248
|
test("hot wallet existing key prompts for password", async () => {
|
|
231
249
|
const schema = Type.String({ title: "Hot Wallet Private Key" });
|
|
232
250
|
|
|
233
|
-
|
|
234
|
-
|
|
251
|
+
mockSelectCancellable.mockResolvedValueOnce("existing");
|
|
252
|
+
mockPasswordCancellable.mockResolvedValueOnce("deadbeef1234");
|
|
235
253
|
|
|
236
254
|
const result = await sprinkle.FillInStruct(schema);
|
|
237
255
|
|
|
238
|
-
expect(
|
|
256
|
+
expect(mockPasswordCancellable).toHaveBeenCalledWith({
|
|
239
257
|
message: "Enter your private key:",
|
|
240
258
|
});
|
|
241
259
|
expect(result).toBe("deadbeef1234");
|
|
@@ -243,12 +261,12 @@ describe("FillInStruct", () => {
|
|
|
243
261
|
|
|
244
262
|
test("full wallet settings schema with existing key", async () => {
|
|
245
263
|
// Select "hot" variant
|
|
246
|
-
|
|
264
|
+
mockSelectCancellable.mockImplementationOnce(async (opts: any) => {
|
|
247
265
|
return opts.choices[0].value; // hot wallet object
|
|
248
266
|
});
|
|
249
267
|
// Select "existing" key option
|
|
250
|
-
|
|
251
|
-
|
|
268
|
+
mockSelectCancellable.mockResolvedValueOnce("existing");
|
|
269
|
+
mockPasswordCancellable.mockResolvedValueOnce("abc123privatekey");
|
|
252
270
|
|
|
253
271
|
const result = await sprinkle.FillInStruct(WalletSettingsSchema);
|
|
254
272
|
|
|
@@ -257,4 +275,52 @@ describe("FillInStruct", () => {
|
|
|
257
275
|
privateKey: "abc123privatekey",
|
|
258
276
|
});
|
|
259
277
|
});
|
|
278
|
+
|
|
279
|
+
test("throws UserCancelledError when input prompt is cancelled", async () => {
|
|
280
|
+
mockInputCancellable.mockResolvedValueOnce(null);
|
|
281
|
+
|
|
282
|
+
await expect(sprinkle.FillInStruct(Type.String())).rejects.toThrow(
|
|
283
|
+
UserCancelledError,
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("throws UserCancelledError when select prompt is cancelled", async () => {
|
|
288
|
+
const schema = Type.Union([
|
|
289
|
+
Type.Object({ type: Type.Literal("a") }),
|
|
290
|
+
Type.Object({ type: Type.Literal("b") }),
|
|
291
|
+
]);
|
|
292
|
+
|
|
293
|
+
mockSelectCancellable.mockResolvedValueOnce(null);
|
|
294
|
+
|
|
295
|
+
await expect(sprinkle.FillInStruct(schema)).rejects.toThrow(
|
|
296
|
+
UserCancelledError,
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("throws UserCancelledError when nested prompt is cancelled", async () => {
|
|
301
|
+
const schema = Type.Object({
|
|
302
|
+
name: Type.String(),
|
|
303
|
+
age: Type.BigInt(),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// First field succeeds, second is cancelled
|
|
307
|
+
mockInputCancellable
|
|
308
|
+
.mockResolvedValueOnce("Alice")
|
|
309
|
+
.mockResolvedValueOnce(null);
|
|
310
|
+
|
|
311
|
+
await expect(sprinkle.FillInStruct(schema)).rejects.toThrow(
|
|
312
|
+
UserCancelledError,
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("throws UserCancelledError when array add-another prompt is cancelled", async () => {
|
|
317
|
+
const schema = Type.Array(Type.String());
|
|
318
|
+
|
|
319
|
+
mockInputCancellable.mockResolvedValueOnce("first");
|
|
320
|
+
mockSelectCancellable.mockResolvedValueOnce(null); // cancel on "add another?"
|
|
321
|
+
|
|
322
|
+
await expect(sprinkle.FillInStruct(schema)).rejects.toThrow(
|
|
323
|
+
UserCancelledError,
|
|
324
|
+
);
|
|
325
|
+
});
|
|
260
326
|
});
|
|
@@ -178,4 +178,112 @@ describe("Settings Persistence", () => {
|
|
|
178
178
|
expect(sprinkle.defaults).toEqual({ string: "old" });
|
|
179
179
|
expect(sprinkle.profileMeta.name).toBe("Default");
|
|
180
180
|
});
|
|
181
|
+
|
|
182
|
+
describe("currentProfile", () => {
|
|
183
|
+
test("returns null when no profile loaded", () => {
|
|
184
|
+
const schema = Type.Object({ name: Type.String() });
|
|
185
|
+
const sprinkle = new Sprinkle(schema, tmpDir);
|
|
186
|
+
|
|
187
|
+
expect(sprinkle.currentProfile).toBeNull();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("returns profile info when loaded", () => {
|
|
191
|
+
const schema = Type.Object({ name: Type.String() });
|
|
192
|
+
const sprinkle = withProfile(new Sprinkle(schema, tmpDir));
|
|
193
|
+
|
|
194
|
+
const profile = sprinkle.currentProfile;
|
|
195
|
+
expect(profile).not.toBeNull();
|
|
196
|
+
expect(profile!.id).toBe("test");
|
|
197
|
+
expect(profile!.name).toBe("Test");
|
|
198
|
+
expect(profile!.createdAt).toBeDefined();
|
|
199
|
+
expect(profile!.updatedAt).toBeDefined();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("includes description when present", () => {
|
|
203
|
+
const schema = Type.Object({ name: Type.String() });
|
|
204
|
+
const sprinkle = withProfile(new Sprinkle(schema, tmpDir));
|
|
205
|
+
sprinkle.profileMeta.description = "Test profile description";
|
|
206
|
+
|
|
207
|
+
const profile = sprinkle.currentProfile;
|
|
208
|
+
expect(profile!.description).toBe("Test profile description");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("has undefined description when not set", () => {
|
|
212
|
+
const schema = Type.Object({ name: Type.String() });
|
|
213
|
+
const sprinkle = withProfile(new Sprinkle(schema, tmpDir));
|
|
214
|
+
delete sprinkle.profileMeta.description;
|
|
215
|
+
|
|
216
|
+
const profile = sprinkle.currentProfile;
|
|
217
|
+
expect(profile!.description).toBeUndefined();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe("getDisplaySettings", () => {
|
|
222
|
+
test("masks sensitive fields", () => {
|
|
223
|
+
const schema = Type.Object({
|
|
224
|
+
name: Type.String(),
|
|
225
|
+
secret: Type.String({ sensitive: true }),
|
|
226
|
+
});
|
|
227
|
+
const sprinkle = withProfile(new Sprinkle(schema, tmpDir));
|
|
228
|
+
sprinkle.settings = { name: "visible", secret: "hidden" } as any;
|
|
229
|
+
|
|
230
|
+
const display = sprinkle.getDisplaySettings();
|
|
231
|
+
expect(display.name).toBe("visible");
|
|
232
|
+
expect(display.secret).toBe("********");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("preserves non-sensitive fields", () => {
|
|
236
|
+
const schema = Type.Object({
|
|
237
|
+
name: Type.String(),
|
|
238
|
+
count: Type.Number(),
|
|
239
|
+
});
|
|
240
|
+
const sprinkle = withProfile(new Sprinkle(schema, tmpDir));
|
|
241
|
+
sprinkle.settings = { name: "test", count: 42 } as any;
|
|
242
|
+
|
|
243
|
+
const display = sprinkle.getDisplaySettings();
|
|
244
|
+
expect(display.name).toBe("test");
|
|
245
|
+
expect(display.count).toBe(42);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("preserves BigInt values", () => {
|
|
249
|
+
const schema = Type.Object({
|
|
250
|
+
amount: Type.BigInt(),
|
|
251
|
+
secret: Type.String({ sensitive: true }),
|
|
252
|
+
});
|
|
253
|
+
const sprinkle = withProfile(new Sprinkle(schema, tmpDir));
|
|
254
|
+
sprinkle.settings = { amount: 42n, secret: "hidden" } as any;
|
|
255
|
+
|
|
256
|
+
const display = sprinkle.getDisplaySettings();
|
|
257
|
+
expect(display.amount).toBe(42n);
|
|
258
|
+
expect(display.secret).toBe("********");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("masks nested sensitive fields", () => {
|
|
262
|
+
const schema = Type.Object({
|
|
263
|
+
wallet: Type.Object({
|
|
264
|
+
key: Type.String({ sensitive: true }),
|
|
265
|
+
address: Type.String(),
|
|
266
|
+
}),
|
|
267
|
+
});
|
|
268
|
+
const sprinkle = withProfile(new Sprinkle(schema, tmpDir));
|
|
269
|
+
sprinkle.settings = {
|
|
270
|
+
wallet: { key: "secret", address: "addr1..." },
|
|
271
|
+
} as any;
|
|
272
|
+
|
|
273
|
+
const display = sprinkle.getDisplaySettings();
|
|
274
|
+
expect(display.wallet.key).toBe("********");
|
|
275
|
+
expect(display.wallet.address).toBe("addr1...");
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("does not mask empty sensitive fields", () => {
|
|
279
|
+
const schema = Type.Object({
|
|
280
|
+
secret: Type.String({ sensitive: true }),
|
|
281
|
+
});
|
|
282
|
+
const sprinkle = withProfile(new Sprinkle(schema, tmpDir));
|
|
283
|
+
sprinkle.settings = { secret: "" } as any;
|
|
284
|
+
|
|
285
|
+
const display = sprinkle.getDisplaySettings();
|
|
286
|
+
expect(display.secret).toBe("");
|
|
287
|
+
});
|
|
288
|
+
});
|
|
181
289
|
});
|