@sundaeswap/sprinkles 0.1.1 → 0.2.1

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.
@@ -1,23 +1,31 @@
1
1
  import { describe, test, expect, mock, beforeEach } from "bun:test";
2
2
  import { Type } from "@sinclair/typebox";
3
3
 
4
- // Track select calls
4
+ // Track calls and responses
5
5
  let selectCalls: any[] = [];
6
- let selectResponses: any[] = [];
6
+ let selectResponses: string[] = [];
7
+ let confirmResponses: boolean[] = [];
8
+ let inputResponses: string[] = [];
7
9
 
8
10
  // Mock @inquirer/prompts
9
11
  mock.module("@inquirer/prompts", () => ({
10
- input: mock(async () => "test"),
12
+ input: mock(async () => {
13
+ const response = inputResponses.shift();
14
+ return response ?? "";
15
+ }),
11
16
  password: mock(async () => "secret"),
12
17
  select: mock(async (opts: any) => {
13
18
  selectCalls.push(opts);
14
19
  const response = selectResponses.shift();
15
20
  if (response === undefined) {
16
- // Default: return -1 (Back/Exit)
17
- return -1;
21
+ return "cancel";
18
22
  }
19
23
  return response;
20
24
  }),
25
+ confirm: mock(async () => {
26
+ const response = confirmResponses.shift();
27
+ return response ?? false;
28
+ }),
21
29
  search: mock(async () => "result"),
22
30
  }));
23
31
 
@@ -39,255 +47,530 @@ const TestSchema = Type.Object({
39
47
  name: Type.String(),
40
48
  });
41
49
 
42
- describe("TxDialog (2.6)", () => {
50
+ // Helper to create a mock transaction
51
+ const createMockTx = (cbor = "deadbeef") => ({
52
+ toCbor: () => cbor,
53
+ body: () => ({
54
+ toCbor: () => cbor,
55
+ requiredSigners: () => null,
56
+ certs: () => null,
57
+ withdrawals: () => null,
58
+ }),
59
+ witnessSet: () => ({
60
+ vkeys: () => null,
61
+ setVkeys: () => {},
62
+ }),
63
+ setWitnessSet: () => {},
64
+ });
65
+
66
+ describe("TxDialog", () => {
43
67
  beforeEach(() => {
44
68
  selectCalls = [];
45
69
  selectResponses = [];
70
+ confirmResponses = [];
71
+ inputResponses = [];
46
72
  clipboardContent = "";
47
73
  clipboardShouldFail = false;
48
74
  });
49
75
 
50
- test("shows truncated CBOR by default", async () => {
51
- const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog");
52
- const logs: string[] = [];
53
- const origLog = console.log;
54
- console.log = (...args: any[]) => logs.push(args.join(" "));
55
-
56
- // Just exit immediately
57
- selectResponses = [-1];
58
-
59
- const mockTx = {
60
- toCbor: () => "a".repeat(100),
61
- body: () => ({
62
- requiredSigners: () => null,
63
- certs: () => null,
64
- withdrawals: () => null,
65
- }),
66
- };
67
-
68
- const mockBlaze = {
69
- wallet: { getUsedAddresses: async () => [] },
70
- };
71
-
72
- await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
73
- console.log = origLog;
74
-
75
- // Should show truncated CBOR (50 chars + ...)
76
- const cborLog = logs.find(l => l.includes("Transaction CBOR:"));
77
- expect(cborLog).toBeDefined();
78
- expect(cborLog!.length).toBeLessThan(100);
79
- expect(cborLog).toContain("...");
76
+ describe("Display", () => {
77
+ test("shows truncated CBOR by default", async () => {
78
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog");
79
+ const logs: string[] = [];
80
+ const origLog = console.log;
81
+ console.log = (...args: any[]) => logs.push(args.join(" "));
82
+
83
+ selectResponses = ["cancel"];
84
+
85
+ const mockTx = createMockTx("a".repeat(100));
86
+ const mockBlaze = {
87
+ wallet: { getUsedAddresses: async () => [] },
88
+ };
89
+
90
+ await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
91
+ console.log = origLog;
92
+
93
+ const cborLog = logs.find((l) => l.includes("Transaction CBOR:"));
94
+ expect(cborLog).toBeDefined();
95
+ expect(cborLog!.length).toBeLessThan(100);
96
+ expect(cborLog).toContain("...");
97
+ });
98
+
99
+ test("shows transaction hash", async () => {
100
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog-hash");
101
+ const logs: string[] = [];
102
+ const origLog = console.log;
103
+ console.log = (...args: any[]) => logs.push(args.join(" "));
104
+
105
+ selectResponses = ["cancel"];
106
+
107
+ const mockTx = createMockTx();
108
+ const mockBlaze = {
109
+ wallet: { getUsedAddresses: async () => [] },
110
+ };
111
+
112
+ await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
113
+ console.log = origLog;
114
+
115
+ const txLog = logs.find((l) => l.includes("Transaction:"));
116
+ expect(txLog).toBeDefined();
117
+ });
118
+
119
+ test("shows signature count", async () => {
120
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog-sigs");
121
+ const logs: string[] = [];
122
+ const origLog = console.log;
123
+ console.log = (...args: any[]) => logs.push(args.join(" "));
124
+
125
+ selectResponses = ["cancel"];
126
+
127
+ const mockTx = createMockTx();
128
+ const mockBlaze = {
129
+ wallet: { getUsedAddresses: async () => [] },
130
+ };
131
+
132
+ await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
133
+ console.log = origLog;
134
+
135
+ const sigLog = logs.find((l) => l.includes("Signatures:"));
136
+ expect(sigLog).toBeDefined();
137
+ expect(sigLog).toContain("0");
138
+ });
139
+ });
140
+
141
+ describe("Menu options", () => {
142
+ test("non-HotWallet has Expand, Copy, Import, Submit, Cancel - no Sign", async () => {
143
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog2");
144
+ const origLog = console.log;
145
+ console.log = () => {};
146
+
147
+ selectResponses = ["cancel"];
148
+
149
+ const mockTx = createMockTx();
150
+ const mockBlaze = {
151
+ wallet: { getUsedAddresses: async () => [] },
152
+ };
153
+
154
+ await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
155
+ console.log = origLog;
156
+
157
+ const menuCall = selectCalls[0];
158
+ expect(menuCall).toBeDefined();
159
+ const choiceNames = menuCall.choices.map((c: any) => c.name);
160
+ expect(choiceNames).toContain("Expand CBOR");
161
+ expect(choiceNames).toContain("Copy CBOR to clipboard");
162
+ expect(choiceNames).toContain("Import signatures from CBOR");
163
+ expect(choiceNames).toContain("Submit transaction");
164
+ expect(choiceNames).toContain("Cancel");
165
+ expect(choiceNames).not.toContain("Sign with this wallet");
166
+ });
167
+
168
+ test("HotWallet has Sign option", async () => {
169
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog3");
170
+ const origLog = console.log;
171
+ console.log = () => {};
172
+
173
+ const { HotWallet } = await import("@blaze-cardano/sdk");
174
+
175
+ selectResponses = ["cancel"];
176
+
177
+ const mockTx = createMockTx();
178
+
179
+ const hotWalletProto = HotWallet.prototype;
180
+ const mockWallet = Object.create(hotWalletProto);
181
+ mockWallet.getUsedAddresses = async () => [];
182
+
183
+ const mockBlaze = {
184
+ wallet: mockWallet,
185
+ };
186
+
187
+ await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
188
+ console.log = origLog;
189
+
190
+ const menuCall = selectCalls[0];
191
+ expect(menuCall).toBeDefined();
192
+ const choiceNames = menuCall.choices.map((c: any) => c.name);
193
+ expect(choiceNames).toContain("Sign with this wallet");
194
+ });
195
+ });
196
+
197
+ describe("Copy CBOR", () => {
198
+ test("copies CBOR to clipboard", async () => {
199
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog4");
200
+ const origLog = console.log;
201
+ const logs: string[] = [];
202
+ console.log = (...args: any[]) => logs.push(args.join(" "));
203
+
204
+ selectResponses = ["copy", "cancel"];
205
+
206
+ const txCbor = "deadbeefcafebabe";
207
+ const mockTx = createMockTx(txCbor);
208
+ const mockBlaze = {
209
+ wallet: { getUsedAddresses: async () => [] },
210
+ };
211
+
212
+ await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
213
+ console.log = origLog;
214
+
215
+ expect(clipboardContent).toBe(txCbor);
216
+ expect(logs.some((l) => l.includes("copied to clipboard"))).toBe(true);
217
+ });
218
+
219
+ test("clipboard failure falls back to expanded view", async () => {
220
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog5");
221
+ const origLog = console.log;
222
+ const logs: string[] = [];
223
+ console.log = (...args: any[]) => logs.push(args.join(" "));
224
+
225
+ clipboardShouldFail = true;
226
+
227
+ selectResponses = ["copy", "cancel"];
228
+
229
+ const txCbor = "a".repeat(100);
230
+ const mockTx = createMockTx(txCbor);
231
+ const mockBlaze = {
232
+ wallet: { getUsedAddresses: async () => [] },
233
+ };
234
+
235
+ await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
236
+ console.log = origLog;
237
+
238
+ expect(logs.some((l) => l.includes("Failed to copy"))).toBe(true);
239
+ });
240
+ });
241
+
242
+ describe("Expand CBOR", () => {
243
+ test("shows full content when expanded", async () => {
244
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog6");
245
+ const origLog = console.log;
246
+ const logs: string[] = [];
247
+ console.log = (...args: any[]) => logs.push(args.join(" "));
248
+
249
+ selectResponses = ["expand", "cancel"];
250
+
251
+ const txCbor = "b".repeat(100);
252
+ const mockTx = createMockTx(txCbor);
253
+ const mockBlaze = {
254
+ wallet: { getUsedAddresses: async () => [] },
255
+ };
256
+
257
+ await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
258
+ console.log = origLog;
259
+
260
+ // First shows truncated, second shows full
261
+ const truncatedLog = logs.find(
262
+ (l) => l.includes("...") && l.includes("Transaction CBOR:"),
263
+ );
264
+ const fullLog = logs.find(
265
+ (l) => l.includes(txCbor) && !l.includes("..."),
266
+ );
267
+ expect(truncatedLog).toBeDefined();
268
+ expect(fullLog).toBeDefined();
269
+ });
80
270
  });
81
271
 
82
- test("menu items for non-HotWallet include Expand, Copy, no Sign", async () => {
83
- const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog2");
84
- const origLog = console.log;
85
- console.log = () => {};
86
-
87
- selectResponses = [-1];
88
-
89
- const mockTx = {
90
- toCbor: () => "deadbeef",
91
- body: () => ({
92
- requiredSigners: () => null,
93
- certs: () => null,
94
- withdrawals: () => null,
95
- }),
96
- };
97
-
98
- const mockBlaze = {
99
- wallet: { getUsedAddresses: async () => [] },
100
- };
101
-
102
- await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
103
- console.log = origLog;
104
-
105
- // Check the select call had Expand and Copy but not Sign
106
- const menuCall = selectCalls[0];
107
- expect(menuCall).toBeDefined();
108
- const choiceNames = menuCall.choices.map((c: any) => c.name);
109
- expect(choiceNames).toContain("Expand CBOR");
110
- expect(choiceNames).toContain("Copy CBOR to clipboard");
111
- expect(choiceNames).not.toContain("Sign and submit transaction");
272
+ describe("Sign", () => {
273
+ test("beforeSign hook is called before signing", async () => {
274
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog7");
275
+ const origLog = console.log;
276
+ const origWarn = console.warn;
277
+ console.log = () => {};
278
+ console.warn = () => {};
279
+
280
+ const { HotWallet } = await import("@blaze-cardano/sdk");
281
+
282
+ let beforeSignCalled = false;
283
+
284
+ selectResponses = ["sign", "cancel"];
285
+
286
+ const mockTx = createMockTx();
287
+
288
+ const hotWalletProto = HotWallet.prototype;
289
+ const mockWallet = Object.create(hotWalletProto);
290
+ mockWallet.getUsedAddresses = async () => [];
291
+
292
+ const mockBlaze = {
293
+ wallet: mockWallet,
294
+ signTransaction: async (tx: any) => ({
295
+ witnessSet: () => ({
296
+ vkeys: () => ({
297
+ size: () => 1,
298
+ toCore: () => [["vkey123", "sig123"]],
299
+ }),
300
+ }),
301
+ }),
302
+ };
303
+
304
+ await sprinkle.TxDialog(mockBlaze as any, mockTx as any, {
305
+ beforeSign: async () => {
306
+ beforeSignCalled = true;
307
+ },
308
+ });
309
+ console.log = origLog;
310
+ console.warn = origWarn;
311
+
312
+ expect(beforeSignCalled).toBe(true);
313
+ });
314
+
315
+ test("Sign option hidden after signing", async () => {
316
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog-signed");
317
+ const origLog = console.log;
318
+ const origWarn = console.warn;
319
+ console.log = () => {};
320
+ console.warn = () => {};
321
+
322
+ const { HotWallet } = await import("@blaze-cardano/sdk");
323
+
324
+ selectResponses = ["sign", "cancel"];
325
+
326
+ const mockTx = createMockTx();
327
+
328
+ const hotWalletProto = HotWallet.prototype;
329
+ const mockWallet = Object.create(hotWalletProto);
330
+ mockWallet.getUsedAddresses = async () => [];
331
+
332
+ const mockBlaze = {
333
+ wallet: mockWallet,
334
+ signTransaction: async (tx: any) => ({
335
+ witnessSet: () => ({
336
+ vkeys: () => ({
337
+ size: () => 1,
338
+ toCore: () => [["vkey123", "sig123"]],
339
+ }),
340
+ }),
341
+ }),
342
+ };
343
+
344
+ await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
345
+ console.log = origLog;
346
+ console.warn = origWarn;
347
+
348
+ // First menu should have Sign, second should not
349
+ expect(selectCalls.length).toBe(2);
350
+ const firstMenuChoices = selectCalls[0].choices.map((c: any) => c.name);
351
+ const secondMenuChoices = selectCalls[1].choices.map((c: any) => c.name);
352
+ expect(firstMenuChoices).toContain("Sign with this wallet");
353
+ expect(secondMenuChoices).not.toContain("Sign with this wallet");
354
+ });
112
355
  });
113
356
 
114
- test("menu items for HotWallet include Sign", async () => {
115
- const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog3");
116
- const origLog = console.log;
117
- console.log = () => {};
118
-
119
- // Need to import HotWallet to use instanceof
120
- const { HotWallet } = await import("@blaze-cardano/sdk");
121
-
122
- selectResponses = [-1];
123
-
124
- const mockTx = {
125
- toCbor: () => "deadbeef",
126
- body: () => ({
127
- requiredSigners: () => null,
128
- certs: () => null,
129
- withdrawals: () => null,
130
- }),
131
- };
132
-
133
- // Create a mock that passes instanceof HotWallet check
134
- // We need to use Object.create to set the prototype
135
- const hotWalletProto = HotWallet.prototype;
136
- const mockWallet = Object.create(hotWalletProto);
137
- mockWallet.getUsedAddresses = async () => [];
138
-
139
- const mockBlaze = {
140
- wallet: mockWallet,
141
- };
142
-
143
- await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
144
- console.log = origLog;
145
-
146
- const menuCall = selectCalls[0];
147
- expect(menuCall).toBeDefined();
148
- const choiceNames = menuCall.choices.map((c: any) => c.name);
149
- expect(choiceNames).toContain("Sign and submit transaction");
357
+ describe("Return values", () => {
358
+ test("returns cancelled when user cancels", async () => {
359
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog-cancel");
360
+ const origLog = console.log;
361
+ console.log = () => {};
362
+
363
+ selectResponses = ["cancel"];
364
+
365
+ const mockTx = createMockTx();
366
+ const mockBlaze = {
367
+ wallet: { getUsedAddresses: async () => [] },
368
+ };
369
+
370
+ const result = await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
371
+ console.log = origLog;
372
+
373
+ expect(result.action).toBe("cancelled");
374
+ expect(result.tx).toBeDefined();
375
+ expect(result.txId).toBeUndefined();
376
+ });
377
+
378
+ test("returns signed when user signs then cancels", async () => {
379
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog-signed2");
380
+ const origLog = console.log;
381
+ const origWarn = console.warn;
382
+ console.log = () => {};
383
+ console.warn = () => {};
384
+
385
+ const { HotWallet } = await import("@blaze-cardano/sdk");
386
+
387
+ selectResponses = ["sign", "cancel"];
388
+
389
+ const mockTx = createMockTx();
390
+
391
+ const hotWalletProto = HotWallet.prototype;
392
+ const mockWallet = Object.create(hotWalletProto);
393
+ mockWallet.getUsedAddresses = async () => [];
394
+
395
+ const mockBlaze = {
396
+ wallet: mockWallet,
397
+ signTransaction: async (tx: any) => ({
398
+ witnessSet: () => ({
399
+ vkeys: () => ({
400
+ size: () => 1,
401
+ toCore: () => [["vkey123", "sig123"]],
402
+ }),
403
+ }),
404
+ }),
405
+ };
406
+
407
+ const result = await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
408
+ console.log = origLog;
409
+ console.warn = origWarn;
410
+
411
+ expect(result.action).toBe("signed");
412
+ expect(result.tx).toBeDefined();
413
+ });
414
+
415
+ test("returns submitted with txId on successful submit", async () => {
416
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog-submit");
417
+ const origLog = console.log;
418
+ console.log = () => {};
419
+
420
+ selectResponses = ["submit"];
421
+ confirmResponses = [true]; // Confirm submit with no signatures
422
+
423
+ const mockTx = createMockTx();
424
+ const mockBlaze = {
425
+ wallet: { getUsedAddresses: async () => [] },
426
+ submitTransaction: async () => "txhash123456",
427
+ };
428
+
429
+ const result = await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
430
+ console.log = origLog;
431
+
432
+ expect(result.action).toBe("submitted");
433
+ expect(result.txId).toBe("txhash123456");
434
+ expect(result.tx).toBeDefined();
435
+ });
150
436
  });
151
437
 
152
- test("copy CBOR to clipboard works", async () => {
153
- const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog4");
154
- const origLog = console.log;
155
- const logs: string[] = [];
156
- console.log = (...args: any[]) => logs.push(args.join(" "));
157
-
158
- // Select "Copy CBOR to clipboard" (index 1), then Back (-1)
159
- selectResponses = [1, -1];
160
-
161
- const txCbor = "deadbeefcafebabe";
162
- const mockTx = {
163
- toCbor: () => txCbor,
164
- body: () => ({
165
- requiredSigners: () => null,
166
- certs: () => null,
167
- withdrawals: () => null,
168
- }),
169
- };
170
-
171
- const mockBlaze = {
172
- wallet: { getUsedAddresses: async () => [] },
173
- };
174
-
175
- await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
176
- console.log = origLog;
177
-
178
- expect(clipboardContent).toBe(txCbor);
179
- expect(logs.some(l => l.includes("copied to clipboard"))).toBe(true);
438
+ describe("Submit warnings", () => {
439
+ test("warns when submitting with no signatures", async () => {
440
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog-warn");
441
+ const origLog = console.log;
442
+ console.log = () => {};
443
+
444
+ // First try to submit, decline warning, then cancel
445
+ selectResponses = ["submit", "cancel"];
446
+ confirmResponses = [false];
447
+
448
+ const mockTx = createMockTx();
449
+ const mockBlaze = {
450
+ wallet: { getUsedAddresses: async () => [] },
451
+ };
452
+
453
+ const result = await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
454
+ console.log = origLog;
455
+
456
+ // Should have returned to menu, then cancelled
457
+ expect(result.action).toBe("cancelled");
458
+ expect(selectCalls.length).toBe(2);
459
+ });
180
460
  });
181
461
 
182
- test("clipboard failure falls back to expanded view", async () => {
183
- const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog5");
184
- const origLog = console.log;
185
- const logs: string[] = [];
186
- console.log = (...args: any[]) => logs.push(args.join(" "));
187
-
188
- clipboardShouldFail = true;
189
-
190
- // Select "Copy CBOR to clipboard" (index 1),
191
- // It will fail and call showDialog again with expanded=true
192
- // Then Back (-1) from the expanded menu, then Back from outer
193
- selectResponses = [1, -1, -1];
194
-
195
- const txCbor = "a".repeat(100);
196
- const mockTx = {
197
- toCbor: () => txCbor,
198
- body: () => ({
199
- requiredSigners: () => null,
200
- certs: () => null,
201
- withdrawals: () => null,
202
- }),
203
- };
204
-
205
- const mockBlaze = {
206
- wallet: { getUsedAddresses: async () => [] },
207
- };
208
-
209
- await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
210
- console.log = origLog;
211
-
212
- expect(logs.some(l => l.includes("Failed to copy"))).toBe(true);
213
- // After failure it should show the full CBOR without truncation
214
- expect(logs.some(l => l.includes(txCbor) && !l.includes("..."))).toBe(true);
462
+ describe("Exit behavior", () => {
463
+ test("exits loop after successful submit", async () => {
464
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog-exit");
465
+ const origLog = console.log;
466
+ console.log = () => {};
467
+
468
+ selectResponses = ["submit"];
469
+ confirmResponses = [true];
470
+
471
+ const mockTx = createMockTx();
472
+ const mockBlaze = {
473
+ wallet: { getUsedAddresses: async () => [] },
474
+ submitTransaction: async () => "txhash789",
475
+ };
476
+
477
+ const result = await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
478
+ console.log = origLog;
479
+
480
+ // Should only have one menu call - no loop after submit
481
+ expect(selectCalls.length).toBe(1);
482
+ expect(result.action).toBe("submitted");
483
+ });
215
484
  });
216
485
 
217
- test("expand CBOR shows full content", async () => {
218
- const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog6");
219
- const origLog = console.log;
220
- const logs: string[] = [];
221
- console.log = (...args: any[]) => logs.push(args.join(" "));
222
-
223
- // Select "Expand CBOR" (index 0), then Back (-1) from expanded, then Back from outer
224
- selectResponses = [0, -1, -1];
225
-
226
- const txCbor = "b".repeat(100);
227
- const mockTx = {
228
- toCbor: () => txCbor,
229
- body: () => ({
230
- requiredSigners: () => null,
231
- certs: () => null,
232
- withdrawals: () => null,
233
- }),
234
- };
235
-
236
- const mockBlaze = {
237
- wallet: { getUsedAddresses: async () => [] },
238
- };
239
-
240
- await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
241
- console.log = origLog;
242
-
243
- // First call shows truncated, second shows full
244
- const truncatedLog = logs.find(l => l.includes("...") && l.includes("Transaction CBOR:"));
245
- const fullLog = logs.find(l => l.includes(txCbor) && !l.includes("..."));
246
- expect(truncatedLog).toBeDefined();
247
- expect(fullLog).toBeDefined();
486
+ describe("Required signers display", () => {
487
+ test("shows required signers when present", async () => {
488
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog-req");
489
+ const logs: string[] = [];
490
+ const origLog = console.log;
491
+ console.log = (...args: any[]) => logs.push(args.join(" "));
492
+
493
+ selectResponses = ["cancel"];
494
+
495
+ const mockTx = {
496
+ toCbor: () => "deadbeef",
497
+ body: () => ({
498
+ toCbor: () => "deadbeef",
499
+ requiredSigners: () => ({
500
+ values: () => [
501
+ { toString: () => "abc123def456" },
502
+ { toString: () => "789ghi012jkl" },
503
+ ],
504
+ }),
505
+ certs: () => null,
506
+ withdrawals: () => null,
507
+ }),
508
+ witnessSet: () => ({
509
+ vkeys: () => null,
510
+ }),
511
+ };
512
+
513
+ const mockBlaze = {
514
+ wallet: { getUsedAddresses: async () => [] },
515
+ };
516
+
517
+ await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
518
+ console.log = origLog;
519
+
520
+ // Should show "X of Y required" format
521
+ expect(logs.some((l) => l.includes("of") && l.includes("required"))).toBe(
522
+ true,
523
+ );
524
+ // Should show required signers list
525
+ expect(logs.some((l) => l.includes("Required signers:"))).toBe(true);
526
+ });
248
527
  });
249
528
 
250
- test("beforeSign hook is called before signing", async () => {
251
- const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog7");
252
- const origLog = console.log;
253
- const origWarn = console.warn;
254
- console.log = () => {};
255
- console.warn = () => {};
256
-
257
- const { HotWallet } = await import("@blaze-cardano/sdk");
258
-
259
- let beforeSignCalled = false;
260
-
261
- // Select "Sign and submit" (index 2 - after Expand and Copy), then Back
262
- selectResponses = [2, -1];
263
-
264
- const mockTx = {
265
- toCbor: () => "deadbeef",
266
- body: () => ({
267
- requiredSigners: () => null,
268
- certs: () => null,
269
- withdrawals: () => null,
270
- }),
271
- };
272
-
273
- const hotWalletProto = HotWallet.prototype;
274
- const mockWallet = Object.create(hotWalletProto);
275
- mockWallet.getUsedAddresses = async () => [];
276
-
277
- const mockBlaze = {
278
- wallet: mockWallet,
279
- signTransaction: async (tx: any) => tx,
280
- submitTransaction: async () => "txhash123",
281
- };
282
-
283
- await sprinkle.TxDialog(mockBlaze as any, mockTx as any, {
284
- beforeSign: async () => {
285
- beforeSignCalled = true;
286
- },
529
+ describe("Import signatures", () => {
530
+ test("continues on empty CBOR input", async () => {
531
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog-import1");
532
+ const origLog = console.log;
533
+ const logs: string[] = [];
534
+ console.log = (...args: any[]) => logs.push(args.join(" "));
535
+
536
+ selectResponses = ["import", "cancel"];
537
+ inputResponses = [""]; // Empty input
538
+
539
+ const mockTx = createMockTx();
540
+ const mockBlaze = {
541
+ wallet: { getUsedAddresses: async () => [] },
542
+ };
543
+
544
+ const result = await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
545
+ console.log = origLog;
546
+
547
+ expect(logs.some((l) => l.includes("No CBOR provided"))).toBe(true);
548
+ expect(result.action).toBe("cancelled");
287
549
  });
288
- console.log = origLog;
289
- console.warn = origWarn;
290
550
 
291
- expect(beforeSignCalled).toBe(true);
551
+ test("handles invalid CBOR gracefully", async () => {
552
+ const sprinkle = new Sprinkle(TestSchema, "/tmp/test-txdialog-import2");
553
+ const origLog = console.log;
554
+ const origErr = console.error;
555
+ const logs: string[] = [];
556
+ const errors: string[] = [];
557
+ console.log = (...args: any[]) => logs.push(args.join(" "));
558
+ console.error = (...args: any[]) => errors.push(args.join(" "));
559
+
560
+ selectResponses = ["import", "cancel"];
561
+ inputResponses = ["not-valid-cbor"];
562
+
563
+ const mockTx = createMockTx();
564
+ const mockBlaze = {
565
+ wallet: { getUsedAddresses: async () => [] },
566
+ };
567
+
568
+ const result = await sprinkle.TxDialog(mockBlaze as any, mockTx as any);
569
+ console.log = origLog;
570
+ console.error = origErr;
571
+
572
+ expect(errors.some((l) => l.includes("Failed to import"))).toBe(true);
573
+ expect(result.action).toBe("cancelled");
574
+ });
292
575
  });
293
576
  });