@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 +332 -0
- package/package.json +3 -3
- package/test/README.md +590 -0
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.
|
|
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": "
|
|
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! ๐งช
|