@uploadista/client-browser 0.0.20 → 0.1.0-beta.5

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.
@@ -0,0 +1,245 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { createBrowserWebSocketFactory } from "./websocket-factory";
3
+
4
+ // Mock WebSocket
5
+ class MockWebSocket {
6
+ static CONNECTING = 0;
7
+ static OPEN = 1;
8
+ static CLOSING = 2;
9
+ static CLOSED = 3;
10
+
11
+ readyState = MockWebSocket.CONNECTING;
12
+ url: string;
13
+ onopen: (() => void) | null = null;
14
+ onclose: ((event: { code: number; reason: string }) => void) | null = null;
15
+ onerror: ((event: { message: string }) => void) | null = null;
16
+ onmessage: ((event: { data: string }) => void) | null = null;
17
+
18
+ private sentMessages: (string | Uint8Array)[] = [];
19
+
20
+ constructor(url: string) {
21
+ this.url = url;
22
+ // Simulate connection opening
23
+ setTimeout(() => {
24
+ this.readyState = MockWebSocket.OPEN;
25
+ this.onopen?.();
26
+ }, 0);
27
+ }
28
+
29
+ send(data: string | Uint8Array): void {
30
+ this.sentMessages.push(data);
31
+ }
32
+
33
+ close(code?: number, reason?: string): void {
34
+ this.readyState = MockWebSocket.CLOSING;
35
+ setTimeout(() => {
36
+ this.readyState = MockWebSocket.CLOSED;
37
+ this.onclose?.({ code: code ?? 1000, reason: reason ?? "" });
38
+ }, 0);
39
+ }
40
+
41
+ // Test helpers
42
+ simulateMessage(data: string): void {
43
+ this.onmessage?.({ data });
44
+ }
45
+
46
+ simulateError(message: string): void {
47
+ this.onerror?.({ message });
48
+ }
49
+
50
+ getSentMessages(): (string | Uint8Array)[] {
51
+ return this.sentMessages;
52
+ }
53
+ }
54
+
55
+ describe("createBrowserWebSocketFactory", () => {
56
+ let originalWebSocket: typeof WebSocket;
57
+
58
+ beforeEach(() => {
59
+ originalWebSocket = globalThis.WebSocket;
60
+ (globalThis as unknown as { WebSocket: typeof MockWebSocket }).WebSocket =
61
+ MockWebSocket;
62
+ });
63
+
64
+ afterEach(() => {
65
+ globalThis.WebSocket = originalWebSocket;
66
+ });
67
+
68
+ it("should create a WebSocket factory", () => {
69
+ const factory = createBrowserWebSocketFactory();
70
+ expect(factory).toBeDefined();
71
+ expect(factory.create).toBeDefined();
72
+ expect(typeof factory.create).toBe("function");
73
+ });
74
+
75
+ describe("create", () => {
76
+ it("should create a WebSocket connection", () => {
77
+ const factory = createBrowserWebSocketFactory();
78
+ const ws = factory.create("wss://api.example.com/ws");
79
+
80
+ expect(ws).toBeDefined();
81
+ expect(ws.readyState).toBe(0); // CONNECTING
82
+ });
83
+
84
+ it("should have CONNECTING, OPEN, CLOSING, CLOSED constants", () => {
85
+ const factory = createBrowserWebSocketFactory();
86
+ const ws = factory.create("wss://api.example.com/ws");
87
+
88
+ expect(ws.CONNECTING).toBe(0);
89
+ expect(ws.OPEN).toBe(1);
90
+ expect(ws.CLOSING).toBe(2);
91
+ expect(ws.CLOSED).toBe(3);
92
+ });
93
+ });
94
+
95
+ describe("WebSocket events", () => {
96
+ it("should call onopen when connection opens", async () => {
97
+ const factory = createBrowserWebSocketFactory();
98
+ const ws = factory.create("wss://api.example.com/ws");
99
+
100
+ const onopen = vi.fn();
101
+ ws.onopen = onopen;
102
+
103
+ // Wait for connection to open
104
+ await new Promise((resolve) => setTimeout(resolve, 10));
105
+
106
+ expect(onopen).toHaveBeenCalled();
107
+ expect(ws.readyState).toBe(1); // OPEN
108
+ });
109
+
110
+ it("should call onclose when connection closes", async () => {
111
+ const factory = createBrowserWebSocketFactory();
112
+ const ws = factory.create("wss://api.example.com/ws");
113
+
114
+ const onclose = vi.fn();
115
+ ws.onclose = onclose;
116
+
117
+ // Wait for connection to open
118
+ await new Promise((resolve) => setTimeout(resolve, 10));
119
+
120
+ // Close the connection
121
+ ws.close(1000, "Normal closure");
122
+
123
+ // Wait for close event
124
+ await new Promise((resolve) => setTimeout(resolve, 10));
125
+
126
+ expect(onclose).toHaveBeenCalledWith({
127
+ code: 1000,
128
+ reason: "Normal closure",
129
+ });
130
+ });
131
+
132
+ it("should call onerror when error occurs", async () => {
133
+ const factory = createBrowserWebSocketFactory();
134
+ const ws = factory.create("wss://api.example.com/ws");
135
+
136
+ const onerror = vi.fn();
137
+ ws.onerror = onerror;
138
+
139
+ // Wait for connection to open
140
+ await new Promise((resolve) => setTimeout(resolve, 10));
141
+
142
+ // Simulate error using the mock helper
143
+ const nativeWs = (ws as unknown as { native: MockWebSocket }).native;
144
+ if (nativeWs && "simulateError" in nativeWs) {
145
+ nativeWs.simulateError("Test error");
146
+ } else {
147
+ // Fallback: call onerror directly
148
+ ws.onerror?.({ message: "WebSocket error" });
149
+ }
150
+
151
+ expect(onerror).toHaveBeenCalled();
152
+ });
153
+
154
+ it("should call onmessage when message received", async () => {
155
+ const factory = createBrowserWebSocketFactory();
156
+ const ws = factory.create("wss://api.example.com/ws");
157
+
158
+ const onmessage = vi.fn();
159
+ ws.onmessage = onmessage;
160
+
161
+ // Wait for connection to open
162
+ await new Promise((resolve) => setTimeout(resolve, 10));
163
+
164
+ // Simulate message
165
+ const nativeWs = (ws as unknown as { native: MockWebSocket }).native;
166
+ if (nativeWs && "simulateMessage" in nativeWs) {
167
+ nativeWs.simulateMessage('{"type": "test"}');
168
+ } else {
169
+ // Fallback
170
+ ws.onmessage?.({ data: '{"type": "test"}' });
171
+ }
172
+
173
+ expect(onmessage).toHaveBeenCalledWith({ data: '{"type": "test"}' });
174
+ });
175
+ });
176
+
177
+ describe("send", () => {
178
+ it("should send string data", async () => {
179
+ const factory = createBrowserWebSocketFactory();
180
+ const ws = factory.create("wss://api.example.com/ws");
181
+
182
+ // Wait for connection to open
183
+ await new Promise((resolve) => setTimeout(resolve, 10));
184
+
185
+ ws.send('{"type": "subscribe"}');
186
+
187
+ // Verify message was sent (would check mock in real scenario)
188
+ expect(ws.readyState).toBe(1); // OPEN
189
+ });
190
+
191
+ it("should send binary data", async () => {
192
+ const factory = createBrowserWebSocketFactory();
193
+ const ws = factory.create("wss://api.example.com/ws");
194
+
195
+ // Wait for connection to open
196
+ await new Promise((resolve) => setTimeout(resolve, 10));
197
+
198
+ const binaryData = new Uint8Array([1, 2, 3, 4]);
199
+ ws.send(binaryData);
200
+
201
+ expect(ws.readyState).toBe(1); // OPEN
202
+ });
203
+ });
204
+
205
+ describe("close", () => {
206
+ it("should close with default code", async () => {
207
+ const factory = createBrowserWebSocketFactory();
208
+ const ws = factory.create("wss://api.example.com/ws");
209
+
210
+ const onclose = vi.fn();
211
+ ws.onclose = onclose;
212
+
213
+ // Wait for connection to open
214
+ await new Promise((resolve) => setTimeout(resolve, 10));
215
+
216
+ ws.close();
217
+
218
+ // Wait for close event
219
+ await new Promise((resolve) => setTimeout(resolve, 10));
220
+
221
+ expect(ws.readyState).toBe(3); // CLOSED
222
+ });
223
+
224
+ it("should close with custom code and reason", async () => {
225
+ const factory = createBrowserWebSocketFactory();
226
+ const ws = factory.create("wss://api.example.com/ws");
227
+
228
+ const onclose = vi.fn();
229
+ ws.onclose = onclose;
230
+
231
+ // Wait for connection to open
232
+ await new Promise((resolve) => setTimeout(resolve, 10));
233
+
234
+ ws.close(1001, "Going away");
235
+
236
+ // Wait for close event
237
+ await new Promise((resolve) => setTimeout(resolve, 10));
238
+
239
+ expect(onclose).toHaveBeenCalledWith({
240
+ code: 1001,
241
+ reason: "Going away",
242
+ });
243
+ });
244
+ });
245
+ });
@@ -0,0 +1,84 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { computeblobSha256 } from "./hash-util";
3
+
4
+ describe("computeblobSha256", () => {
5
+ it("should compute SHA-256 hash of a blob", async () => {
6
+ const blob = new Blob(["Hello, World!"], { type: "text/plain" });
7
+ const hash = await computeblobSha256(blob);
8
+
9
+ // Hash should be a 64-character hex string
10
+ expect(hash).toHaveLength(64);
11
+ expect(hash).toMatch(/^[a-f0-9]{64}$/);
12
+ });
13
+
14
+ it("should compute SHA-256 hash of a File", async () => {
15
+ const file = new File(["Test content"], "test.txt", { type: "text/plain" });
16
+ const hash = await computeblobSha256(file);
17
+
18
+ expect(hash).toHaveLength(64);
19
+ expect(hash).toMatch(/^[a-f0-9]{64}$/);
20
+ });
21
+
22
+ it("should return same hash for identical content", async () => {
23
+ const content = "Same content for both";
24
+ const blob1 = new Blob([content], { type: "text/plain" });
25
+ const blob2 = new Blob([content], { type: "text/plain" });
26
+
27
+ const hash1 = await computeblobSha256(blob1);
28
+ const hash2 = await computeblobSha256(blob2);
29
+
30
+ expect(hash1).toBe(hash2);
31
+ });
32
+
33
+ it("should return different hash for different content", async () => {
34
+ const blob1 = new Blob(["Content A"], { type: "text/plain" });
35
+ const blob2 = new Blob(["Content B"], { type: "text/plain" });
36
+
37
+ const hash1 = await computeblobSha256(blob1);
38
+ const hash2 = await computeblobSha256(blob2);
39
+
40
+ expect(hash1).not.toBe(hash2);
41
+ });
42
+
43
+ it("should handle empty blob", async () => {
44
+ const blob = new Blob([], { type: "text/plain" });
45
+ const hash = await computeblobSha256(blob);
46
+
47
+ expect(hash).toHaveLength(64);
48
+ expect(hash).toMatch(/^[a-f0-9]{64}$/);
49
+ });
50
+
51
+ it("should handle binary content", async () => {
52
+ const binaryData = new Uint8Array([0, 1, 2, 255, 254, 253]);
53
+ const blob = new Blob([binaryData], { type: "application/octet-stream" });
54
+ const hash = await computeblobSha256(blob);
55
+
56
+ expect(hash).toHaveLength(64);
57
+ expect(hash).toMatch(/^[a-f0-9]{64}$/);
58
+ });
59
+
60
+ it("should throw error when crypto API fails", async () => {
61
+ // Save original
62
+ const originalSubtle = crypto.subtle;
63
+
64
+ // Mock crypto.subtle.digest to throw
65
+ Object.defineProperty(crypto, "subtle", {
66
+ value: {
67
+ digest: vi.fn().mockRejectedValue(new Error("Crypto error")),
68
+ },
69
+ writable: true,
70
+ });
71
+
72
+ const blob = new Blob(["test"], { type: "text/plain" });
73
+
74
+ await expect(computeblobSha256(blob)).rejects.toThrow(
75
+ "Failed to compute file checksum"
76
+ );
77
+
78
+ // Restore original
79
+ Object.defineProperty(crypto, "subtle", {
80
+ value: originalSubtle,
81
+ writable: true,
82
+ });
83
+ });
84
+ });
package/vitest.config.ts CHANGED
@@ -3,9 +3,10 @@ import { defineConfig } from "vitest/config";
3
3
  export default defineConfig({
4
4
  test: {
5
5
  globals: true,
6
- environment: "node",
6
+ environment: "happy-dom",
7
7
  include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
8
8
  exclude: ["node_modules", "dist"],
9
+ setupFiles: ["./src/__tests__/setup.ts"],
9
10
  coverage: {
10
11
  provider: "v8",
11
12
  reporter: ["text", "json", "html"],
@@ -15,6 +16,7 @@ export default defineConfig({
15
16
  "**/*.d.ts",
16
17
  "**/*.test.ts",
17
18
  "**/*.spec.ts",
19
+ "**/__tests__/**",
18
20
  ],
19
21
  },
20
22
  },