@frak-labs/core-sdk 0.1.0-beta.b0bd1f8a → 0.1.0-beta.b77c23c7
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/cdn/bundle.iife.js +2 -2
- package/dist/actions-B5j-i1p0.cjs +1 -0
- package/dist/actions-q090Z0oR.js +1 -0
- package/dist/actions.cjs +1 -1
- package/dist/actions.js +1 -1
- package/dist/bundle.cjs +1 -1
- package/dist/bundle.d.cts +1 -1
- package/dist/bundle.d.ts +1 -1
- package/dist/bundle.js +1 -1
- package/dist/{index-p4FqSp8z.d.cts → index-CRsQWnTs.d.cts} +1 -1
- package/dist/{index-UFX7xCg3.d.ts → index-Ck1hudEi.d.ts} +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/actions/referral/processReferral.test.ts +357 -0
- package/src/actions/referral/processReferral.ts +42 -16
- package/src/actions/referral/referralInteraction.test.ts +153 -0
- package/src/actions/wrapper/modalBuilder.test.ts +253 -0
- package/src/actions/wrapper/sendTransaction.test.ts +164 -0
- package/src/actions/wrapper/siweAuthenticate.test.ts +290 -0
- package/src/clients/DebugInfo.test.ts +418 -0
- package/src/constants/interactionTypes.test.ts +128 -0
- package/src/constants/productTypes.test.ts +130 -0
- package/src/utils/ssoUrlListener.test.ts +252 -0
- package/src/utils/trackEvent.ts +3 -1
- package/dist/actions-CEEObPYc.js +0 -1
- package/dist/actions-DbQhWYx8.cjs +0 -1
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for DebugInfoGatherer class
|
|
3
|
+
* Tests debug information gathering and formatting for error reporting
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { FrakRpcError, RpcErrorCodes } from "@frak-labs/frame-connector";
|
|
7
|
+
import {
|
|
8
|
+
afterEach,
|
|
9
|
+
beforeEach,
|
|
10
|
+
describe,
|
|
11
|
+
expect,
|
|
12
|
+
it,
|
|
13
|
+
vi,
|
|
14
|
+
} from "../../tests/vitest-fixtures";
|
|
15
|
+
import type { FrakWalletSdkConfig } from "../types";
|
|
16
|
+
import { DebugInfoGatherer } from "./DebugInfo";
|
|
17
|
+
|
|
18
|
+
describe("DebugInfoGatherer", () => {
|
|
19
|
+
let mockConfig: FrakWalletSdkConfig;
|
|
20
|
+
let mockIframe: HTMLIFrameElement;
|
|
21
|
+
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
// Create mock config
|
|
25
|
+
mockConfig = {
|
|
26
|
+
metadata: {
|
|
27
|
+
name: "Test App",
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Create mock iframe
|
|
32
|
+
mockIframe = document.createElement("iframe");
|
|
33
|
+
mockIframe.src = "https://wallet.frak.id";
|
|
34
|
+
mockIframe.setAttribute("loading", "lazy");
|
|
35
|
+
document.body.appendChild(mockIframe);
|
|
36
|
+
|
|
37
|
+
// Mock iframe contentDocument
|
|
38
|
+
Object.defineProperty(mockIframe, "contentDocument", {
|
|
39
|
+
value: {
|
|
40
|
+
readyState: "complete",
|
|
41
|
+
},
|
|
42
|
+
writable: true,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Mock iframe contentWindow
|
|
46
|
+
Object.defineProperty(mockIframe, "contentWindow", {
|
|
47
|
+
value: {},
|
|
48
|
+
writable: true,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Spy on console.warn
|
|
52
|
+
consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
consoleWarnSpy.mockRestore();
|
|
57
|
+
document.body.removeChild(mockIframe);
|
|
58
|
+
vi.clearAllMocks();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("constructor", () => {
|
|
62
|
+
it("should create instance with config and iframe", () => {
|
|
63
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
64
|
+
|
|
65
|
+
expect(gatherer).toBeInstanceOf(DebugInfoGatherer);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should create instance without config and iframe", () => {
|
|
69
|
+
const gatherer = new DebugInfoGatherer();
|
|
70
|
+
|
|
71
|
+
expect(gatherer).toBeInstanceOf(DebugInfoGatherer);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should initialize with null lastRequest and lastResponse", () => {
|
|
75
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
76
|
+
|
|
77
|
+
// Access private properties through formatDebugInfo output
|
|
78
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
79
|
+
expect(debugInfo).toContain("No Frak request logged");
|
|
80
|
+
expect(debugInfo).toContain("No Frak response logged");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("static empty()", () => {
|
|
85
|
+
it("should create empty instance", () => {
|
|
86
|
+
const gatherer = DebugInfoGatherer.empty();
|
|
87
|
+
|
|
88
|
+
expect(gatherer).toBeInstanceOf(DebugInfoGatherer);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should create instance without config or iframe", () => {
|
|
92
|
+
const gatherer = DebugInfoGatherer.empty();
|
|
93
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
94
|
+
|
|
95
|
+
expect(debugInfo).toContain("no-config");
|
|
96
|
+
expect(debugInfo).toContain("not-iframe");
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("setLastRequest", () => {
|
|
101
|
+
it("should set last request with timestamp", () => {
|
|
102
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
103
|
+
const mockMessage = {
|
|
104
|
+
id: 1,
|
|
105
|
+
method: "test_method",
|
|
106
|
+
params: [],
|
|
107
|
+
} as any;
|
|
108
|
+
|
|
109
|
+
gatherer.setLastRequest(mockMessage);
|
|
110
|
+
|
|
111
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
112
|
+
expect(debugInfo).toContain("Last Request:");
|
|
113
|
+
expect(debugInfo).not.toContain("No Frak request logged");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("setLastResponse", () => {
|
|
118
|
+
it("should set last response with timestamp", () => {
|
|
119
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
120
|
+
const mockMessage = {
|
|
121
|
+
id: 1,
|
|
122
|
+
method: "test_method",
|
|
123
|
+
} as any;
|
|
124
|
+
const mockResponse = {
|
|
125
|
+
id: 1,
|
|
126
|
+
result: "success",
|
|
127
|
+
} as any;
|
|
128
|
+
|
|
129
|
+
gatherer.setLastResponse(mockMessage, mockResponse);
|
|
130
|
+
|
|
131
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
132
|
+
expect(debugInfo).toContain("Last Response:");
|
|
133
|
+
expect(debugInfo).not.toContain("No Frak response logged");
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("updateSetupStatus", () => {
|
|
138
|
+
it("should update setup status to true", () => {
|
|
139
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
140
|
+
|
|
141
|
+
gatherer.updateSetupStatus(true);
|
|
142
|
+
|
|
143
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
144
|
+
expect(debugInfo).toContain("Client Status: setup");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should update setup status to false", () => {
|
|
148
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
149
|
+
|
|
150
|
+
gatherer.updateSetupStatus(false);
|
|
151
|
+
|
|
152
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
153
|
+
expect(debugInfo).toContain("Client Status: not-setup");
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("formatDebugInfo", () => {
|
|
158
|
+
it("should format debug info with all fields", () => {
|
|
159
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
160
|
+
|
|
161
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
162
|
+
|
|
163
|
+
expect(debugInfo).toContain("Debug Information:");
|
|
164
|
+
expect(debugInfo).toContain("Timestamp:");
|
|
165
|
+
expect(debugInfo).toContain("URL:");
|
|
166
|
+
expect(debugInfo).toContain("Config:");
|
|
167
|
+
expect(debugInfo).toContain("Navigator Info:");
|
|
168
|
+
expect(debugInfo).toContain("IFrame Status:");
|
|
169
|
+
expect(debugInfo).toContain("Last Request:");
|
|
170
|
+
expect(debugInfo).toContain("Last Response:");
|
|
171
|
+
expect(debugInfo).toContain("Client Status:");
|
|
172
|
+
expect(debugInfo).toContain("Error:");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("should format FrakRpcError correctly", () => {
|
|
176
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
177
|
+
const error = new FrakRpcError(
|
|
178
|
+
RpcErrorCodes.walletNotConnected,
|
|
179
|
+
"Wallet not connected"
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const debugInfo = gatherer.formatDebugInfo(error);
|
|
183
|
+
|
|
184
|
+
expect(debugInfo).toContain(
|
|
185
|
+
`FrakRpcError: ${RpcErrorCodes.walletNotConnected} 'Wallet not connected'`
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("should format regular Error correctly", () => {
|
|
190
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
191
|
+
const error = new Error("Network timeout");
|
|
192
|
+
|
|
193
|
+
const debugInfo = gatherer.formatDebugInfo(error);
|
|
194
|
+
|
|
195
|
+
expect(debugInfo).toContain("Network timeout");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should format string error correctly", () => {
|
|
199
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
200
|
+
|
|
201
|
+
const debugInfo = gatherer.formatDebugInfo("String error message");
|
|
202
|
+
|
|
203
|
+
expect(debugInfo).toContain("String error message");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should format unknown error as 'Unknown'", () => {
|
|
207
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
208
|
+
|
|
209
|
+
const debugInfo = gatherer.formatDebugInfo(null);
|
|
210
|
+
|
|
211
|
+
expect(debugInfo).toContain("Error: Unknown");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("should include encoded URL", () => {
|
|
215
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
216
|
+
|
|
217
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
218
|
+
|
|
219
|
+
// URL should be base64 encoded
|
|
220
|
+
expect(debugInfo).toContain("URL:");
|
|
221
|
+
const urlMatch = debugInfo.match(/URL: (.+)/);
|
|
222
|
+
expect(urlMatch).toBeTruthy();
|
|
223
|
+
if (urlMatch) {
|
|
224
|
+
// Should be base64 encoded (alphanumeric + =)
|
|
225
|
+
expect(urlMatch[1]).toMatch(/^[A-Za-z0-9+/=]+$/);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("should include encoded config when config is provided", () => {
|
|
230
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
231
|
+
|
|
232
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
233
|
+
|
|
234
|
+
expect(debugInfo).toContain("Config:");
|
|
235
|
+
expect(debugInfo).not.toContain("no-config");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("should show 'no-config' when config is not provided", () => {
|
|
239
|
+
const gatherer = new DebugInfoGatherer(undefined, mockIframe);
|
|
240
|
+
|
|
241
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
242
|
+
|
|
243
|
+
expect(debugInfo).toContain("Config: no-config");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("should include iframe status when iframe is provided", () => {
|
|
247
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
248
|
+
|
|
249
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
250
|
+
|
|
251
|
+
expect(debugInfo).toContain("IFrame Status:");
|
|
252
|
+
expect(debugInfo).not.toContain("not-iframe");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("should show 'not-iframe' when iframe is not provided", () => {
|
|
256
|
+
const gatherer = new DebugInfoGatherer(mockConfig, undefined);
|
|
257
|
+
|
|
258
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
259
|
+
|
|
260
|
+
expect(debugInfo).toContain("IFrame Status: not-iframe");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("should include navigator info", () => {
|
|
264
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
265
|
+
|
|
266
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
267
|
+
|
|
268
|
+
expect(debugInfo).toContain("Navigator Info:");
|
|
269
|
+
expect(debugInfo).not.toContain("no-navigator");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("should handle iframe without contentDocument", () => {
|
|
273
|
+
const iframeWithoutDoc = document.createElement("iframe");
|
|
274
|
+
Object.defineProperty(iframeWithoutDoc, "contentDocument", {
|
|
275
|
+
value: null,
|
|
276
|
+
writable: true,
|
|
277
|
+
});
|
|
278
|
+
Object.defineProperty(iframeWithoutDoc, "contentWindow", {
|
|
279
|
+
value: {},
|
|
280
|
+
writable: true,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const gatherer = new DebugInfoGatherer(
|
|
284
|
+
mockConfig,
|
|
285
|
+
iframeWithoutDoc
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
289
|
+
|
|
290
|
+
expect(debugInfo).toContain("IFrame Status:");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should handle iframe with incomplete readyState", () => {
|
|
294
|
+
const iframeIncomplete = document.createElement("iframe");
|
|
295
|
+
Object.defineProperty(iframeIncomplete, "contentDocument", {
|
|
296
|
+
value: {
|
|
297
|
+
readyState: "loading",
|
|
298
|
+
},
|
|
299
|
+
writable: true,
|
|
300
|
+
});
|
|
301
|
+
Object.defineProperty(iframeIncomplete, "contentWindow", {
|
|
302
|
+
value: {},
|
|
303
|
+
writable: true,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const gatherer = new DebugInfoGatherer(
|
|
307
|
+
mockConfig,
|
|
308
|
+
iframeIncomplete
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
312
|
+
|
|
313
|
+
expect(debugInfo).toContain("IFrame Status:");
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("should include last request when set", () => {
|
|
317
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
318
|
+
const mockMessage = {
|
|
319
|
+
id: 1,
|
|
320
|
+
method: "test_method",
|
|
321
|
+
params: [],
|
|
322
|
+
} as any;
|
|
323
|
+
|
|
324
|
+
gatherer.setLastRequest(mockMessage);
|
|
325
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
326
|
+
|
|
327
|
+
expect(debugInfo).toContain("Last Request:");
|
|
328
|
+
expect(debugInfo).not.toContain("No Frak request logged");
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("should include last response when set", () => {
|
|
332
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
333
|
+
const mockMessage = {
|
|
334
|
+
id: 1,
|
|
335
|
+
method: "test_method",
|
|
336
|
+
} as any;
|
|
337
|
+
const mockResponse = {
|
|
338
|
+
id: 1,
|
|
339
|
+
result: "success",
|
|
340
|
+
} as any;
|
|
341
|
+
|
|
342
|
+
gatherer.setLastResponse(mockMessage, mockResponse);
|
|
343
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
344
|
+
|
|
345
|
+
expect(debugInfo).toContain("Last Response:");
|
|
346
|
+
expect(debugInfo).not.toContain("No Frak response logged");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("should show 'No Frak request logged' when no request is set", () => {
|
|
350
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
351
|
+
|
|
352
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
353
|
+
|
|
354
|
+
expect(debugInfo).toContain("No Frak request logged");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("should show 'No Frak response logged' when no response is set", () => {
|
|
358
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
359
|
+
|
|
360
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
361
|
+
|
|
362
|
+
expect(debugInfo).toContain("No Frak response logged");
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe("base64 encoding error handling", () => {
|
|
367
|
+
it("should handle encoding errors gracefully", () => {
|
|
368
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
369
|
+
|
|
370
|
+
// Create a circular reference that can't be JSON stringified
|
|
371
|
+
const circularObj: any = { prop: "value" };
|
|
372
|
+
circularObj.self = circularObj;
|
|
373
|
+
|
|
374
|
+
// Mock base64Encode to throw an error
|
|
375
|
+
// We can't directly test the private method, but we can test
|
|
376
|
+
// that formatDebugInfo doesn't throw when encoding fails
|
|
377
|
+
// by checking that it still produces output
|
|
378
|
+
|
|
379
|
+
const debugInfo = gatherer.formatDebugInfo("test error");
|
|
380
|
+
|
|
381
|
+
// Should still produce debug info even if encoding fails
|
|
382
|
+
expect(debugInfo).toContain("Debug Information:");
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
describe("integration", () => {
|
|
387
|
+
it("should format complete debug info with all data set", () => {
|
|
388
|
+
const gatherer = new DebugInfoGatherer(mockConfig, mockIframe);
|
|
389
|
+
const mockRequest = {
|
|
390
|
+
id: 1,
|
|
391
|
+
method: "frak_sendInteraction",
|
|
392
|
+
params: [],
|
|
393
|
+
} as any;
|
|
394
|
+
const mockResponse = {
|
|
395
|
+
id: 1,
|
|
396
|
+
result: { delegationId: "123" },
|
|
397
|
+
} as any;
|
|
398
|
+
const error = new FrakRpcError(
|
|
399
|
+
RpcErrorCodes.serverError,
|
|
400
|
+
"Server error"
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
gatherer.setLastRequest(mockRequest);
|
|
404
|
+
gatherer.setLastResponse(mockRequest, mockResponse);
|
|
405
|
+
gatherer.updateSetupStatus(true);
|
|
406
|
+
|
|
407
|
+
const debugInfo = gatherer.formatDebugInfo(error);
|
|
408
|
+
|
|
409
|
+
expect(debugInfo).toContain("Debug Information:");
|
|
410
|
+
expect(debugInfo).toContain("Client Status: setup");
|
|
411
|
+
expect(debugInfo).toContain(
|
|
412
|
+
`FrakRpcError: ${RpcErrorCodes.serverError} 'Server error'`
|
|
413
|
+
);
|
|
414
|
+
expect(debugInfo).not.toContain("No Frak request logged");
|
|
415
|
+
expect(debugInfo).not.toContain("No Frak response logged");
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for interactionTypes constants
|
|
3
|
+
* Tests interaction type definitions and type utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { interactionTypes } from "./interactionTypes";
|
|
8
|
+
|
|
9
|
+
describe("interactionTypes", () => {
|
|
10
|
+
describe("structure", () => {
|
|
11
|
+
it("should have all expected categories", () => {
|
|
12
|
+
expect(interactionTypes).toHaveProperty("press");
|
|
13
|
+
expect(interactionTypes).toHaveProperty("dapp");
|
|
14
|
+
expect(interactionTypes).toHaveProperty("webshop");
|
|
15
|
+
expect(interactionTypes).toHaveProperty("referral");
|
|
16
|
+
expect(interactionTypes).toHaveProperty("purchase");
|
|
17
|
+
expect(interactionTypes).toHaveProperty("retail");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should have press interactions", () => {
|
|
21
|
+
expect(interactionTypes.press).toEqual({
|
|
22
|
+
openArticle: "0xc0a24ffb",
|
|
23
|
+
readArticle: "0xd5bd0fbe",
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should have dapp interactions", () => {
|
|
28
|
+
expect(interactionTypes.dapp).toEqual({
|
|
29
|
+
proofVerifiableStorageUpdate: "0x2ab2aeef",
|
|
30
|
+
callableVerifiableStorageUpdate: "0xa07da986",
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should have webshop interactions", () => {
|
|
35
|
+
expect(interactionTypes.webshop).toEqual({
|
|
36
|
+
open: "0xb311798f",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should have referral interactions", () => {
|
|
41
|
+
expect(interactionTypes.referral).toEqual({
|
|
42
|
+
referred: "0x010cc3b9",
|
|
43
|
+
createLink: "0xb2c0f17c",
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should have purchase interactions", () => {
|
|
48
|
+
expect(interactionTypes.purchase).toEqual({
|
|
49
|
+
started: "0xd87e90c3",
|
|
50
|
+
completed: "0x8403aeb4",
|
|
51
|
+
unsafeCompleted: "0x4d5b14e0",
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should have retail interactions", () => {
|
|
56
|
+
expect(interactionTypes.retail).toEqual({
|
|
57
|
+
customerMeeting: "0x74489004",
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("interaction values", () => {
|
|
63
|
+
it("should have all interaction values as hex strings", () => {
|
|
64
|
+
Object.values(interactionTypes).forEach((category) => {
|
|
65
|
+
Object.values(category).forEach((value) => {
|
|
66
|
+
expect(value).toMatch(/^0x[a-f0-9]{8}$/);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should have unique interaction values across all categories", () => {
|
|
72
|
+
const allValues = Object.values(interactionTypes).flatMap(
|
|
73
|
+
(category) => Object.values(category)
|
|
74
|
+
);
|
|
75
|
+
const uniqueValues = new Set(allValues);
|
|
76
|
+
expect(allValues.length).toBe(uniqueValues.size);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("specific interactions", () => {
|
|
81
|
+
it("should have correct press.openArticle value", () => {
|
|
82
|
+
expect(interactionTypes.press.openArticle).toBe("0xc0a24ffb");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should have correct press.readArticle value", () => {
|
|
86
|
+
expect(interactionTypes.press.readArticle).toBe("0xd5bd0fbe");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should have correct webshop.open value", () => {
|
|
90
|
+
expect(interactionTypes.webshop.open).toBe("0xb311798f");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should have correct referral.referred value", () => {
|
|
94
|
+
expect(interactionTypes.referral.referred).toBe("0x010cc3b9");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should have correct purchase.completed value", () => {
|
|
98
|
+
expect(interactionTypes.purchase.completed).toBe("0x8403aeb4");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should have correct purchase.unsafeCompleted value", () => {
|
|
102
|
+
expect(interactionTypes.purchase.unsafeCompleted).toBe(
|
|
103
|
+
"0x4d5b14e0"
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should have correct retail.customerMeeting value", () => {
|
|
108
|
+
expect(interactionTypes.retail.customerMeeting).toBe("0x74489004");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("type safety", () => {
|
|
113
|
+
it("should be readonly (as const)", () => {
|
|
114
|
+
// TypeScript ensures this is readonly, but we can verify structure
|
|
115
|
+
expect(Object.isFrozen(interactionTypes)).toBe(false);
|
|
116
|
+
// The values should be consistent
|
|
117
|
+
expect(typeof interactionTypes.press.openArticle).toBe("string");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should have consistent structure across categories", () => {
|
|
121
|
+
Object.values(interactionTypes).forEach((category) => {
|
|
122
|
+
expect(typeof category).toBe("object");
|
|
123
|
+
expect(category).not.toBeNull();
|
|
124
|
+
expect(Array.isArray(category)).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for productTypes constants
|
|
3
|
+
* Tests product type definitions and bitmask calculations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { productTypes, productTypesMask } from "./productTypes";
|
|
8
|
+
|
|
9
|
+
describe("productTypes", () => {
|
|
10
|
+
describe("structure", () => {
|
|
11
|
+
it("should have all expected product types", () => {
|
|
12
|
+
expect(productTypes).toHaveProperty("dapp");
|
|
13
|
+
expect(productTypes).toHaveProperty("press");
|
|
14
|
+
expect(productTypes).toHaveProperty("webshop");
|
|
15
|
+
expect(productTypes).toHaveProperty("retail");
|
|
16
|
+
expect(productTypes).toHaveProperty("referral");
|
|
17
|
+
expect(productTypes).toHaveProperty("purchase");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should have correct numeric values", () => {
|
|
21
|
+
expect(productTypes.dapp).toBe(1);
|
|
22
|
+
expect(productTypes.press).toBe(2);
|
|
23
|
+
expect(productTypes.webshop).toBe(3);
|
|
24
|
+
expect(productTypes.retail).toBe(4);
|
|
25
|
+
expect(productTypes.referral).toBe(30);
|
|
26
|
+
expect(productTypes.purchase).toBe(31);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("productTypesMask", () => {
|
|
31
|
+
it("should have masks for all product types", () => {
|
|
32
|
+
expect(productTypesMask).toHaveProperty("dapp");
|
|
33
|
+
expect(productTypesMask).toHaveProperty("press");
|
|
34
|
+
expect(productTypesMask).toHaveProperty("webshop");
|
|
35
|
+
expect(productTypesMask).toHaveProperty("retail");
|
|
36
|
+
expect(productTypesMask).toHaveProperty("referral");
|
|
37
|
+
expect(productTypesMask).toHaveProperty("purchase");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should calculate correct bitmask for dapp (value 1)", () => {
|
|
41
|
+
expect(productTypesMask.dapp).toBe(BigInt(1) << BigInt(1));
|
|
42
|
+
expect(productTypesMask.dapp).toBe(BigInt(2));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should calculate correct bitmask for press (value 2)", () => {
|
|
46
|
+
expect(productTypesMask.press).toBe(BigInt(1) << BigInt(2));
|
|
47
|
+
expect(productTypesMask.press).toBe(BigInt(4));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should calculate correct bitmask for webshop (value 3)", () => {
|
|
51
|
+
expect(productTypesMask.webshop).toBe(BigInt(1) << BigInt(3));
|
|
52
|
+
expect(productTypesMask.webshop).toBe(BigInt(8));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should calculate correct bitmask for retail (value 4)", () => {
|
|
56
|
+
expect(productTypesMask.retail).toBe(BigInt(1) << BigInt(4));
|
|
57
|
+
expect(productTypesMask.retail).toBe(BigInt(16));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should calculate correct bitmask for referral (value 30)", () => {
|
|
61
|
+
expect(productTypesMask.referral).toBe(BigInt(1) << BigInt(30));
|
|
62
|
+
expect(productTypesMask.referral).toBe(BigInt(1073741824));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should calculate correct bitmask for purchase (value 31)", () => {
|
|
66
|
+
expect(productTypesMask.purchase).toBe(BigInt(1) << BigInt(31));
|
|
67
|
+
expect(productTypesMask.purchase).toBe(BigInt(2147483648));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should have all masks as BigInt values", () => {
|
|
71
|
+
Object.values(productTypesMask).forEach((mask) => {
|
|
72
|
+
expect(typeof mask).toBe("bigint");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should have unique mask values", () => {
|
|
77
|
+
const maskValues = Object.values(productTypesMask);
|
|
78
|
+
const uniqueValues = new Set(maskValues);
|
|
79
|
+
expect(maskValues.length).toBe(uniqueValues.size);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("bitmask calculation", () => {
|
|
84
|
+
it("should correctly calculate bitmask from product type value", () => {
|
|
85
|
+
// Verify the formula: 1 << value
|
|
86
|
+
expect(productTypesMask.dapp).toBe(
|
|
87
|
+
BigInt(1) << BigInt(productTypes.dapp)
|
|
88
|
+
);
|
|
89
|
+
expect(productTypesMask.press).toBe(
|
|
90
|
+
BigInt(1) << BigInt(productTypes.press)
|
|
91
|
+
);
|
|
92
|
+
expect(productTypesMask.webshop).toBe(
|
|
93
|
+
BigInt(1) << BigInt(productTypes.webshop)
|
|
94
|
+
);
|
|
95
|
+
expect(productTypesMask.retail).toBe(
|
|
96
|
+
BigInt(1) << BigInt(productTypes.retail)
|
|
97
|
+
);
|
|
98
|
+
expect(productTypesMask.referral).toBe(
|
|
99
|
+
BigInt(1) << BigInt(productTypes.referral)
|
|
100
|
+
);
|
|
101
|
+
expect(productTypesMask.purchase).toBe(
|
|
102
|
+
BigInt(1) << BigInt(productTypes.purchase)
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should have masks that are powers of 2", () => {
|
|
107
|
+
Object.values(productTypesMask).forEach((mask) => {
|
|
108
|
+
// A power of 2 has exactly one bit set
|
|
109
|
+
// Check: mask & (mask - 1n) should be 0n
|
|
110
|
+
const isPowerOfTwo = mask > 0n && (mask & (mask - 1n)) === 0n;
|
|
111
|
+
expect(isPowerOfTwo).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("type safety", () => {
|
|
117
|
+
it("should have consistent structure", () => {
|
|
118
|
+
const productKeys = Object.keys(productTypes);
|
|
119
|
+
const maskKeys = Object.keys(productTypesMask);
|
|
120
|
+
expect(productKeys.sort()).toEqual(maskKeys.sort());
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should have numeric values for product types", () => {
|
|
124
|
+
Object.values(productTypes).forEach((value) => {
|
|
125
|
+
expect(typeof value).toBe("number");
|
|
126
|
+
expect(Number.isInteger(value)).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|