@knocklabs/client 0.14.10-canary.2 โ†’ 0.14.11

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/CHANGELOG.md ADDED
@@ -0,0 +1,332 @@
1
+ # Changelog
2
+
3
+ ## 0.14.11
4
+
5
+ ### Patch Changes
6
+
7
+ - 6539c97: patch fix package contents being built properly
8
+
9
+ ## 0.14.10
10
+
11
+ ### Patch Changes
12
+
13
+ - e05ad61: feat: Migrates the internal store library from zustand to @tanstack/store. This is a non-breaking change that maintains backwards compatibility with the @knocklabs/client and @knocklabs/react-core packages.
14
+ - e05ad61: feat: Migrates the internal store library from zustand to @tanstack/store. This is a non-breaking change that maintains backwards compatibility with the @knocklabs/client and @knocklabs/react-core packages.
15
+ - e05ad61: feat: Migrates the internal store library from zustand to @tanstack/store. This is a non-breaking change that maintains backwards compatibility with the @knocklabs/client and @knocklabs/react-core packages.
16
+
17
+ ## 0.14.10-canary.2
18
+
19
+ ### Patch Changes
20
+
21
+ - e69da7b: feat: Migrates the internal store library from zustand to @tanstack/store. This is a non-breaking change that maintains backwards compatibility with the @knocklabs/client and @knocklabs/react-core packages.
22
+
23
+ ## 0.14.10-canary.1
24
+
25
+ ### Patch Changes
26
+
27
+ - c76b2d9: feat: Migrates the internal store library from zustand to @tanstack/store. This is a non-breaking change that maintains backwards compatibility with the @knocklabs/client and @knocklabs/react-core packages.
28
+
29
+ ## 0.14.10-canary.0
30
+
31
+ ### Patch Changes
32
+
33
+ - 86a72cc: feat: Migrates the internal store library from zustand to @tanstack/store. This is a non-breaking change that maintains backwards compatibility with the @knocklabs/client and @knocklabs/react-core packages.
34
+
35
+ ## 0.14.9
36
+
37
+ ### Patch Changes
38
+
39
+ - 4e73f12: fix: ensure this is bound to guide client instance in handleLocationChange
40
+
41
+ ## 0.14.8
42
+
43
+ ### Patch Changes
44
+
45
+ - 329ee05: downgrade tanstack store deps to 0.6.x to work in older TS version
46
+
47
+ ## 0.14.7
48
+
49
+ ### Patch Changes
50
+
51
+ - efd1005: Fix CJS builds
52
+
53
+ v0.14.6 of our client SDK did not support CJS. This version fixes CJS support.
54
+
55
+ ## 0.14.6
56
+
57
+ ### Patch Changes
58
+
59
+ - a5c615e: Allow multiple instances of `Feed` to listen for real-time updates to the same notification feed
60
+
61
+ Previously, using two or more instances of `Feed` with the same in-app feed channel would result in
62
+ only the most recently connected `Feed` receiving real-time updates. Now, all instances of `Feed`
63
+ configured with the same in-app channel will receive real-time updates.
64
+
65
+ ## 0.14.5
66
+
67
+ ### Patch Changes
68
+
69
+ - 8f00623: activation location rules support for guides
70
+
71
+ ## 0.14.4
72
+
73
+ ### Patch Changes
74
+
75
+ - e800896: feat: typescript fixes + quality of life improvements
76
+
77
+ ## 0.14.3
78
+
79
+ ### Patch Changes
80
+
81
+ - c97a1d9: Update TanStack Store
82
+
83
+ ## 0.14.2
84
+
85
+ ### Patch Changes
86
+
87
+ - 00439a2: fix(KNO-7843): Fix types and stringify trigger_data for feed API requests
88
+
89
+ ## 0.14.1
90
+
91
+ ### Patch Changes
92
+
93
+ - 4c41841: feat: accept options in the fetchNextPage method
94
+
95
+ ## 0.14.0
96
+
97
+ ### Minor Changes
98
+
99
+ - 711948c: feat: add guide client, hooks, provider, and components
100
+
101
+ ## 0.13.1
102
+
103
+ ### Patch Changes
104
+
105
+ - 187abc1: Allows the feed to accept date range options upon initialization.
106
+
107
+ ## 0.13.0
108
+
109
+ ### Minor Changes
110
+
111
+ - 4cd1b1e: add support for filtering by date in the feed client
112
+
113
+ ## 0.12.0
114
+
115
+ ### Minor Changes
116
+
117
+ - 8ba5dcb: [JS] Support React 19 in React SDKs
118
+
119
+ ## 0.11.4
120
+
121
+ ### Patch Changes
122
+
123
+ - 8ea25f4: fix: include missing timestamp fields on FeedItem and Message
124
+
125
+ ## 0.11.3
126
+
127
+ ### Patch Changes
128
+
129
+ - 4f76cd6: fix: raise when calling user methods before auth
130
+
131
+ ## 0.11.2
132
+
133
+ ### Patch Changes
134
+
135
+ - 2161d3f: Make id and displayName required in MsTeamsTeam and MsTeamsChannel types
136
+ - 2161d3f: Add `ms_teams_team_id` to MsTeamsChannelConnection type
137
+ - 1ba1393: add TeamsKit hooks for teams and channels
138
+ - b4b5c02: add getTeams and getChannels to MsTeamsClient
139
+
140
+ ## 0.11.1
141
+
142
+ ### Patch Changes
143
+
144
+ - b9f6712: fix: types for userId should handle undefined and null
145
+
146
+ ## 0.11.0
147
+
148
+ ### Minor Changes
149
+
150
+ - 013ad8d: feat: add MsTeamsAuthButton
151
+
152
+ ## 0.10.17
153
+
154
+ ### Patch Changes
155
+
156
+ - 26db496: fix: ensure feed can render with empty/missing userId values
157
+ - 988aaf9: fix: engagement_status in BulkUpdateMessagesInChannelProperties type
158
+
159
+ ## 0.10.16
160
+
161
+ ### Patch Changes
162
+
163
+ - bc99374: fix: bundle client package using "compat" interop
164
+
165
+ ## 0.10.15
166
+
167
+ ### Patch Changes
168
+
169
+ - 26166e3: fix: update preference set types
170
+ - Updated dependencies [26166e3]
171
+ - @knocklabs/types@0.1.5
172
+
173
+ ## 0.10.14
174
+
175
+ ### Patch Changes
176
+
177
+ - 7510909: fix: ensure axios is always imported correctly
178
+
179
+ ## 0.10.13
180
+
181
+ ### Patch Changes
182
+
183
+ - 1d440f7: feat: add prebuilt In App Feed Components for React Native
184
+
185
+ ## 0.10.12
186
+
187
+ ### Patch Changes
188
+
189
+ - 5545f9e: feat: support passing metadata for interactions
190
+
191
+ ## 0.10.11
192
+
193
+ ### Patch Changes
194
+
195
+ - 395f0ca: fix: check type of zustand default import and fix cjs build
196
+
197
+ ## 0.10.10
198
+
199
+ ### Patch Changes
200
+
201
+ - a4d520c: chore: update generic types
202
+ - Updated dependencies [a4d520c]
203
+ - @knocklabs/types@0.1.4
204
+
205
+ ## 0.10.9
206
+
207
+ ### Patch Changes
208
+
209
+ - d0adb14: fix: don't destroy the store, ever
210
+
211
+ ## 0.10.8
212
+
213
+ ### Patch Changes
214
+
215
+ - 29e3942: fix: introduce new useNotificationStore hook to prevent issues that prevent state updates
216
+
217
+ ## 0.10.7
218
+
219
+ ### Patch Changes
220
+
221
+ - f25b112: fix: ensure feed store reference re-renders after changes to user
222
+
223
+ ## 0.10.6
224
+
225
+ ### Patch Changes
226
+
227
+ - b29a47a: Add KnockExpoPushNotificationProvider to react-native sdk
228
+
229
+ ## 0.10.5
230
+
231
+ ### Patch Changes
232
+
233
+ - 044eb0f: fix: check if document is defined before setting up auto socket manager
234
+
235
+ ## 0.10.4
236
+
237
+ ### Patch Changes
238
+
239
+ - 5a7c56e: fix: avoid adding duplicate visibiliy change event listeners
240
+
241
+ ## 0.10.3
242
+
243
+ ### Patch Changes
244
+
245
+ - a71ce51: fix: event types to use items. and fix types
246
+
247
+ ## 0.10.2
248
+
249
+ ### Patch Changes
250
+
251
+ - 42ba22c: fix: improve typing for react < 18
252
+
253
+ ## 0.10.1
254
+
255
+ ### Patch Changes
256
+
257
+ - 3c277cb: fix: remove react-query and replace with swr for react 16+ support
258
+ - 567e24f: fix: clean up visibility change event listener on feed teardown
259
+
260
+ ## 0.10.0
261
+
262
+ ### Minor Changes
263
+
264
+ - 8bdc75b: Added a new MessageClient to access message api's independently from the Feed.
265
+
266
+ ## 0.9.4
267
+
268
+ ### Patch Changes
269
+
270
+ - f58371c: Added user get and identify methods
271
+
272
+ ## 0.9.3
273
+
274
+ ### Patch Changes
275
+
276
+ - bc69618: Add react-native to package.json files to fix a bug in our React Native SDK
277
+
278
+ ## 0.9.2
279
+
280
+ ### Patch Changes
281
+
282
+ - fed0f8c: add support for actions in notification feed cells
283
+
284
+ ## 0.9.1
285
+
286
+ ### Patch Changes
287
+
288
+ - f37d680: Update event format for client status updates
289
+
290
+ ## 0.9.0
291
+
292
+ ### Minor Changes
293
+
294
+ - 627e643: Add SlackKit components, hooks, client JS functions, and example apps.
295
+
296
+ ## 0.8.21
297
+
298
+ ### Patch Changes
299
+
300
+ - c9faba5: fix esm build issues with mjs files
301
+
302
+ ## 0.8.20
303
+
304
+ ### Patch Changes
305
+
306
+ - Re-releasing packages
307
+
308
+ ## 0.8.19
309
+
310
+ ### Patch Changes
311
+
312
+ - 7786ec5: chore: upgrade to yarn modern and update local package references
313
+ - 9dd0d15: feat: add onUserTokenExpiring callback option to client
314
+
315
+ ## 0.8.18
316
+
317
+ ### Patch Changes
318
+
319
+ - e53c200: fix: strip socket manager options from http requests
320
+ - d4ba1f2: chore: add shared types package
321
+
322
+ ## 0.8.17
323
+
324
+ ### Patch Changes
325
+
326
+ - 345ebc1: feat: add option to automatically manage open socket connections
327
+
328
+ ## 0.8.16
329
+
330
+ ### Patch Changes
331
+
332
+ - 7bc5e4a: fix: add @babel/runtime dependency
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knocklabs/client",
3
- "version": "0.14.10-canary.2",
3
+ "version": "0.14.11",
4
4
  "description": "The clientside library for interacting with Knock",
5
5
  "homepage": "https://github.com/knocklabs/javascript/tree/main/packages/client",
6
6
  "author": "@knocklabs",
@@ -68,7 +68,7 @@
68
68
  },
69
69
  "dependencies": {
70
70
  "@babel/runtime": "^7.27.1",
71
- "@knocklabs/types": "workspace:^",
71
+ "@knocklabs/types": "^0.1.5",
72
72
  "@tanstack/store": "^0.7.1",
73
73
  "@types/phoenix": "^1.6.6",
74
74
  "axios": "^1.9.0",
@@ -79,4 +79,4 @@
79
79
  "phoenix": "1.7.19",
80
80
  "urlpattern-polyfill": "^10.0.0"
81
81
  }
82
- }
82
+ }
package/test/README.md ADDED
@@ -0,0 +1,590 @@
1
+ # Testing Guide for Knock Client
2
+
3
+ This directory contains all tests for the Knock JavaScript client. This guide will help you understand our testing patterns, utilities, and how to write effective tests.
4
+
5
+ ## ๐Ÿ“ Test Structure
6
+
7
+ ```
8
+ test/
9
+ โ”œโ”€โ”€ README.md # This guide
10
+ โ”œโ”€โ”€ setup.ts # Global test configuration
11
+ โ”œโ”€โ”€ test-utils/ # Shared testing utilities
12
+ โ”‚ โ”œโ”€โ”€ fixtures.ts # Test data generators
13
+ โ”‚ โ”œโ”€โ”€ mocks.ts # Mock factories
14
+ โ”‚ โ””โ”€โ”€ property-testing.ts # Property-based testing tools
15
+ โ”œโ”€โ”€ clients/ # Client-specific tests
16
+ โ”‚ โ”œโ”€โ”€ feed/ # Feed client tests
17
+ โ”‚ โ”œโ”€โ”€ messages/ # Messages client tests
18
+ โ”‚ โ”œโ”€โ”€ users/ # Users client tests
19
+ โ”‚ โ””โ”€โ”€ ... # Other client tests
20
+ โ”œโ”€โ”€ knock.test.ts # Main Knock class tests
21
+ โ”œโ”€โ”€ api.test.ts # API client tests
22
+ โ”œโ”€โ”€ helpers.test.ts # Utility functions tests
23
+ โ””โ”€โ”€ ... # Other core tests
24
+ ```
25
+
26
+ ## ๐Ÿš€ Quick Start: Writing Your First Test
27
+
28
+ Here's a simple test to get you started:
29
+
30
+ ```typescript
31
+ import { describe, expect, test } from "vitest";
32
+
33
+ import { createMockKnock } from "./test-utils/mocks";
34
+
35
+ describe("My Feature", () => {
36
+ test("should do something", () => {
37
+ const { knock } = createMockKnock();
38
+
39
+ // Your test logic here
40
+ expect(knock).toBeDefined();
41
+ });
42
+ });
43
+ ```
44
+
45
+ **Important:** Always add `` at the top of test files.
46
+
47
+ ## ๐Ÿ›  Test Utilities
48
+
49
+ ### 1. Fixtures (`test-utils/fixtures.ts`)
50
+
51
+ Fixtures create realistic test data. Use them instead of manually creating objects.
52
+
53
+ **Feed Items:**
54
+
55
+ ```typescript
56
+ import {
57
+ createArchivedFeedItem,
58
+ createMockFeedItem,
59
+ createReadFeedItem,
60
+ createUnreadFeedItem,
61
+ } from "./test-utils/fixtures";
62
+
63
+ // Create a basic feed item
64
+ const item = createMockFeedItem();
65
+
66
+ // Create specific states
67
+ const unreadItem = createUnreadFeedItem();
68
+ const readItem = createReadFeedItem({
69
+ read_at: "2024-01-01T00:00:00Z",
70
+ });
71
+
72
+ // Create multiple items
73
+ const items = createMockFeedItems(5);
74
+ ```
75
+
76
+ **Messages:**
77
+
78
+ ```typescript
79
+ import {
80
+ createMockMessage,
81
+ createReadMessage,
82
+ createUnreadMessage,
83
+ } from "./test-utils/fixtures";
84
+
85
+ const message = createMockMessage({
86
+ id: "custom-id",
87
+ });
88
+ ```
89
+
90
+ **Users:**
91
+
92
+ ```typescript
93
+ import { createMockUser, createMockUsers } from "./test-utils/fixtures";
94
+
95
+ const user = createMockUser({ name: "John Doe" });
96
+ const users = createMockUsers(10);
97
+ ```
98
+
99
+ **Complex Scenarios:**
100
+
101
+ ```typescript
102
+ import {
103
+ createBulkOperationScenario,
104
+ createErrorRecoveryScenario,
105
+ createUserJourneyScenario,
106
+ } from "./test-utils/fixtures";
107
+
108
+ // Pre-built realistic test scenarios
109
+ const scenario = createUserJourneyScenario();
110
+ ```
111
+
112
+ ### 2. Mocks (`test-utils/mocks.ts`)
113
+
114
+ Mocks handle external dependencies and API calls.
115
+
116
+ **Basic Setup:**
117
+
118
+ ```typescript
119
+ import { authenticateKnock, createMockKnock } from "./test-utils/mocks";
120
+
121
+ test("my test", () => {
122
+ const { knock, mockApiClient } = createMockKnock();
123
+
124
+ // Authenticate if needed
125
+ authenticateKnock(knock);
126
+
127
+ // Your test logic
128
+ });
129
+ ```
130
+
131
+ **API Mocking:**
132
+
133
+ ```typescript
134
+ import {
135
+ mockNetworkError,
136
+ mockNetworkFailure,
137
+ mockNetworkSuccess,
138
+ } from "./test-utils/mocks";
139
+
140
+ test("handles successful API call", async () => {
141
+ const { knock, mockApiClient } = createMockKnock();
142
+
143
+ // Mock successful response
144
+ mockNetworkSuccess(mockApiClient, { data: "success" });
145
+
146
+ // Test your code
147
+ });
148
+
149
+ test("handles API error", async () => {
150
+ const { knock, mockApiClient } = createMockKnock();
151
+
152
+ // Mock error response
153
+ mockNetworkError(mockApiClient, 400, "Bad Request");
154
+
155
+ // Test error handling
156
+ });
157
+
158
+ test("handles network failure", async () => {
159
+ const { knock, mockApiClient } = createMockKnock();
160
+
161
+ // Mock network failure
162
+ mockNetworkFailure(mockApiClient, new Error("Network down"));
163
+
164
+ // Test failure handling
165
+ });
166
+ ```
167
+
168
+ **Feed Mocking:**
169
+
170
+ ```typescript
171
+ import { createMockFeed } from "./test-utils/mocks";
172
+
173
+ test("feed operations", () => {
174
+ const { feed, mockApiClient, mockSocketManager } = createMockFeed(
175
+ "test-feed-id",
176
+ { page_size: 25 },
177
+ );
178
+
179
+ // Test feed operations
180
+ });
181
+ ```
182
+
183
+ ### 3. Property Testing (`test-utils/property-testing.ts`)
184
+
185
+ Property testing helps find edge cases by testing with generated data.
186
+
187
+ ```typescript
188
+ import {
189
+ feedItemArbitrary,
190
+ generators,
191
+ property,
192
+ } from "./test-utils/property-testing";
193
+
194
+ test("property: all feed items should have valid IDs", async () => {
195
+ const result = await property.forAll(
196
+ feedItemArbitrary(),
197
+ (item) => item.id.length > 0,
198
+ );
199
+
200
+ expect(result.success).toBe(true);
201
+ });
202
+
203
+ test("property: numbers are always positive", async () => {
204
+ const result = await property.forAll(
205
+ generators.number(1, 1000),
206
+ (num) => num > 0,
207
+ );
208
+
209
+ expect(result.success).toBe(true);
210
+ });
211
+ ```
212
+
213
+ ## ๐Ÿ“ Testing Patterns
214
+
215
+ ### 1. Test Organization
216
+
217
+ **Use descriptive describe blocks:**
218
+
219
+ ```typescript
220
+ describe("Feed Client", () => {
221
+ describe("Initialization", () => {
222
+ test("creates feed with valid options", () => {
223
+ // Test initialization
224
+ });
225
+ });
226
+
227
+ describe("Data Operations", () => {
228
+ test("fetches feed items successfully", () => {
229
+ // Test data fetching
230
+ });
231
+ });
232
+
233
+ describe("Error Handling", () => {
234
+ test("handles network errors gracefully", () => {
235
+ // Test error scenarios
236
+ });
237
+ });
238
+ });
239
+ ```
240
+
241
+ ### 2. Setup and Cleanup
242
+
243
+ **Use consistent setup:**
244
+
245
+ ```typescript
246
+ import { afterEach, beforeEach, describe, test, vi } from "vitest";
247
+
248
+ describe("My Feature", () => {
249
+ const getTestSetup = () => {
250
+ const { knock, mockApiClient } = createMockKnock();
251
+ authenticateKnock(knock);
252
+
253
+ return {
254
+ knock,
255
+ mockApiClient,
256
+ cleanup: () => vi.clearAllMocks(),
257
+ };
258
+ };
259
+
260
+ afterEach(() => {
261
+ vi.clearAllMocks();
262
+ });
263
+
264
+ test("my test", () => {
265
+ const { knock, mockApiClient, cleanup } = getTestSetup();
266
+
267
+ try {
268
+ // Your test logic
269
+ } finally {
270
+ cleanup();
271
+ }
272
+ });
273
+ });
274
+ ```
275
+
276
+ ### 3. Async Testing
277
+
278
+ **Handle promises correctly:**
279
+
280
+ ```typescript
281
+ test("async operation succeeds", async () => {
282
+ const { knock, mockApiClient } = createMockKnock();
283
+
284
+ mockNetworkSuccess(mockApiClient, { success: true });
285
+
286
+ const result = await knock.someAsyncOperation();
287
+
288
+ expect(result).toEqual({ success: true });
289
+ });
290
+
291
+ test("async operation fails", async () => {
292
+ const { knock, mockApiClient } = createMockKnock();
293
+
294
+ mockNetworkFailure(mockApiClient, new Error("Failed"));
295
+
296
+ await expect(knock.someAsyncOperation()).rejects.toThrow("Failed");
297
+ });
298
+ ```
299
+
300
+ ### 4. State Testing
301
+
302
+ **Test different states:**
303
+
304
+ ```typescript
305
+ test("handles unread items", () => {
306
+ const items = [
307
+ createUnreadFeedItem(),
308
+ createReadFeedItem(),
309
+ createUnreadFeedItem(),
310
+ ];
311
+
312
+ const unreadCount = items.filter((item) => !item.read_at).length;
313
+ expect(unreadCount).toBe(2);
314
+ });
315
+ ```
316
+
317
+ ## ๐ŸŽฏ Testing Specific Clients
318
+
319
+ ### Feed Client Tests
320
+
321
+ ```typescript
322
+ import { createMockFeedItems } from "./test-utils/fixtures";
323
+ import { createMockFeed } from "./test-utils/mocks";
324
+
325
+ test("feed fetches items", async () => {
326
+ const { feed, mockApiClient } = createMockFeed();
327
+ const items = createMockFeedItems(5);
328
+
329
+ mockNetworkSuccess(mockApiClient, {
330
+ entries: items,
331
+ page_info: { page_size: 50 },
332
+ });
333
+
334
+ await feed.fetch();
335
+
336
+ expect(feed.store.items).toHaveLength(5);
337
+ });
338
+ ```
339
+
340
+ ### Messages Client Tests
341
+
342
+ ```typescript
343
+ import { createMockMessage } from "./test-utils/fixtures";
344
+
345
+ test("messages client gets message", async () => {
346
+ const { knock, mockApiClient } = createMockKnock();
347
+ const message = createMockMessage();
348
+
349
+ mockNetworkSuccess(mockApiClient, message);
350
+
351
+ const result = await knock.messages.get(message.id);
352
+
353
+ expect(result).toEqual(message);
354
+ });
355
+ ```
356
+
357
+ ### User Client Tests
358
+
359
+ ```typescript
360
+ test("user client identifies user", async () => {
361
+ const { knock, mockApiClient } = createMockKnock();
362
+
363
+ mockNetworkSuccess(mockApiClient, { success: true });
364
+
365
+ await knock.user.identify("user_123", { name: "John" });
366
+
367
+ expect(mockApiClient.makeRequest).toHaveBeenCalledWith({
368
+ method: "PUT",
369
+ url: "/v1/users/user_123",
370
+ data: { name: "John" },
371
+ });
372
+ });
373
+ ```
374
+
375
+ ## ๐Ÿงช Advanced Testing
376
+
377
+ ### Error Scenarios
378
+
379
+ ```typescript
380
+ test("handles rate limiting", async () => {
381
+ const { knock, mockApiClient } = createMockKnock();
382
+
383
+ mockNetworkError(mockApiClient, 429, "Rate limited");
384
+
385
+ await expect(knock.someOperation()).rejects.toThrow();
386
+ });
387
+
388
+ test("retries on network failure", async () => {
389
+ const { knock, mockApiClient } = createMockKnock();
390
+
391
+ // First call fails, second succeeds
392
+ mockApiClient.makeRequest
393
+ .mockRejectedValueOnce(new Error("Network error"))
394
+ .mockResolvedValueOnce({ statusCode: "ok", body: { success: true } });
395
+
396
+ const result = await knock.someRetryableOperation();
397
+
398
+ expect(result).toEqual({ success: true });
399
+ expect(mockApiClient.makeRequest).toHaveBeenCalledTimes(2);
400
+ });
401
+ ```
402
+
403
+ ### Performance Testing
404
+
405
+ ```typescript
406
+ import { createLargeFeedDataset } from "./test-utils/fixtures";
407
+
408
+ test("handles large datasets efficiently", () => {
409
+ const { items, metadata } = createLargeFeedDataset(10000);
410
+
411
+ const startTime = performance.now();
412
+
413
+ // Test operation
414
+ const result = processLargeDataset(items);
415
+
416
+ const endTime = performance.now();
417
+
418
+ expect(result).toBeDefined();
419
+ expect(endTime - startTime).toBeLessThan(1000); // Should complete in < 1s
420
+ });
421
+ ```
422
+
423
+ ## ๐Ÿ”ง Configuration
424
+
425
+ ### Global Setup (`setup.ts`)
426
+
427
+ The setup file handles:
428
+
429
+ - Environment polyfills
430
+ - Console output suppression during tests
431
+ - Global error handling
432
+ - Browser API mocks (localStorage, sessionStorage)
433
+
434
+ You usually don't need to modify this file.
435
+
436
+ ### Environment
437
+
438
+ All tests should run in Node environment:
439
+
440
+ ```typescript
441
+
442
+ ```
443
+
444
+ ## ๐Ÿšจ Common Issues & Solutions
445
+
446
+ ### 1. Unhandled Promise Rejections
447
+
448
+ **Problem:** Tests fail with unhandled promise rejections.
449
+
450
+ **Solution:** Always handle promises properly:
451
+
452
+ ```typescript
453
+ // โŒ Bad
454
+ test("test", () => {
455
+ someAsyncFunction(); // Promise not handled
456
+ });
457
+
458
+ // โœ… Good
459
+ test("test", async () => {
460
+ await someAsyncFunction();
461
+ });
462
+
463
+ // โœ… Also good
464
+ test("test", () => {
465
+ return someAsyncFunction();
466
+ });
467
+ ```
468
+
469
+ ### 2. Mock Cleanup
470
+
471
+ **Problem:** Mocks from one test affect another.
472
+
473
+ **Solution:** Always clean up:
474
+
475
+ ```typescript
476
+ afterEach(() => {
477
+ vi.clearAllMocks();
478
+ vi.restoreAllMocks();
479
+ });
480
+ ```
481
+
482
+ ### 3. Authentication Required
483
+
484
+ **Problem:** Tests fail because client isn't authenticated.
485
+
486
+ **Solution:** Use `authenticateKnock`:
487
+
488
+ ```typescript
489
+ test("authenticated operation", () => {
490
+ const { knock } = createMockKnock();
491
+ authenticateKnock(knock); // Add this line
492
+
493
+ // Now test authenticated operations
494
+ });
495
+ ```
496
+
497
+ ### 4. Network Mocking
498
+
499
+ **Problem:** Real network calls in tests.
500
+
501
+ **Solution:** Always mock network calls:
502
+
503
+ ```typescript
504
+ test("API operation", async () => {
505
+ const { knock, mockApiClient } = createMockKnock();
506
+
507
+ // Mock the expected response
508
+ mockNetworkSuccess(mockApiClient, expectedData);
509
+
510
+ const result = await knock.apiOperation();
511
+
512
+ expect(result).toEqual(expectedData);
513
+ });
514
+ ```
515
+
516
+ ## ๐Ÿ“š Examples
517
+
518
+ ### Complete Test File Example
519
+
520
+ ```typescript
521
+ import { afterEach, describe, expect, test, vi } from "vitest";
522
+
523
+ import { createMockFeedItem } from "./test-utils/fixtures";
524
+ import {
525
+ authenticateKnock,
526
+ createMockKnock,
527
+ mockNetworkSuccess,
528
+ } from "./test-utils/mocks";
529
+
530
+ describe("My Feature", () => {
531
+ afterEach(() => {
532
+ vi.clearAllMocks();
533
+ });
534
+
535
+ describe("Basic Operations", () => {
536
+ test("performs basic operation", () => {
537
+ const { knock } = createMockKnock();
538
+
539
+ expect(knock).toBeDefined();
540
+ });
541
+ });
542
+
543
+ describe("Authenticated Operations", () => {
544
+ test("performs authenticated operation", async () => {
545
+ const { knock, mockApiClient } = createMockKnock();
546
+ authenticateKnock(knock);
547
+
548
+ const expectedData = { success: true };
549
+ mockNetworkSuccess(mockApiClient, expectedData);
550
+
551
+ const result = await knock.authenticatedOperation();
552
+
553
+ expect(result).toEqual(expectedData);
554
+ });
555
+ });
556
+
557
+ describe("Data Operations", () => {
558
+ test("processes feed item", () => {
559
+ const item = createMockFeedItem({
560
+ read_at: null, // Unread item
561
+ });
562
+
563
+ const result = processItem(item);
564
+
565
+ expect(result.isUnread).toBe(true);
566
+ });
567
+ });
568
+
569
+ describe("Error Handling", () => {
570
+ test("handles errors gracefully", async () => {
571
+ const { knock, mockApiClient } = createMockKnock();
572
+
573
+ mockApiClient.makeRequest.mockRejectedValue(new Error("API Error"));
574
+
575
+ await expect(knock.faultyOperation()).rejects.toThrow("API Error");
576
+ });
577
+ });
578
+ });
579
+ ```
580
+
581
+ ## ๐ŸŽ‰ You're Ready!
582
+
583
+ With this guide and the provided utilities, you should be able to write comprehensive tests for any part of the Knock client. Remember:
584
+
585
+ 1. **Use the test utilities** - they handle the complex setup for you
586
+ 2. **Follow the patterns** - consistent structure makes tests easier to understand
587
+ 3. **Test both success and failure cases** - robust testing catches more bugs
588
+ 4. **Clean up after yourself** - prevent test pollution
589
+
590
+ Happy testing! ๐Ÿงช