@knocklabs/client 0.14.10-canary.2 โ 0.14.10
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 +326 -0
- package/package.json +3 -3
- package/test/README.md +590 -0
- package/dist/cjs/api.js +0 -2
- package/dist/cjs/api.js.map +0 -1
- package/dist/cjs/clients/feed/feed.js +0 -2
- package/dist/cjs/clients/feed/feed.js.map +0 -1
- package/dist/cjs/clients/feed/index.js +0 -2
- package/dist/cjs/clients/feed/index.js.map +0 -1
- package/dist/cjs/clients/feed/socket-manager.js +0 -2
- package/dist/cjs/clients/feed/socket-manager.js.map +0 -1
- package/dist/cjs/clients/feed/store.js +0 -2
- package/dist/cjs/clients/feed/store.js.map +0 -1
- package/dist/cjs/clients/feed/utils.js +0 -2
- package/dist/cjs/clients/feed/utils.js.map +0 -1
- package/dist/cjs/clients/guide/client.js +0 -2
- package/dist/cjs/clients/guide/client.js.map +0 -1
- package/dist/cjs/clients/messages/index.js +0 -2
- package/dist/cjs/clients/messages/index.js.map +0 -1
- package/dist/cjs/clients/ms-teams/index.js +0 -2
- package/dist/cjs/clients/ms-teams/index.js.map +0 -1
- package/dist/cjs/clients/objects/constants.js +0 -2
- package/dist/cjs/clients/objects/constants.js.map +0 -1
- package/dist/cjs/clients/objects/index.js +0 -2
- package/dist/cjs/clients/objects/index.js.map +0 -1
- package/dist/cjs/clients/preferences/index.js +0 -2
- package/dist/cjs/clients/preferences/index.js.map +0 -1
- package/dist/cjs/clients/slack/index.js +0 -2
- package/dist/cjs/clients/slack/index.js.map +0 -1
- package/dist/cjs/clients/users/index.js +0 -2
- package/dist/cjs/clients/users/index.js.map +0 -1
- package/dist/cjs/helpers.js +0 -2
- package/dist/cjs/helpers.js.map +0 -1
- package/dist/cjs/index.js +0 -2
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/knock.js +0 -2
- package/dist/cjs/knock.js.map +0 -1
- package/dist/cjs/networkStatus.js +0 -2
- package/dist/cjs/networkStatus.js.map +0 -1
- package/dist/esm/api.mjs +0 -58
- package/dist/esm/api.mjs.map +0 -1
- package/dist/esm/clients/feed/feed.mjs +0 -422
- package/dist/esm/clients/feed/feed.mjs.map +0 -1
- package/dist/esm/clients/feed/index.mjs +0 -47
- package/dist/esm/clients/feed/index.mjs.map +0 -1
- package/dist/esm/clients/feed/socket-manager.mjs +0 -81
- package/dist/esm/clients/feed/socket-manager.mjs.map +0 -1
- package/dist/esm/clients/feed/store.mjs +0 -104
- package/dist/esm/clients/feed/store.mjs.map +0 -1
- package/dist/esm/clients/feed/utils.mjs +0 -35
- package/dist/esm/clients/feed/utils.mjs.map +0 -1
- package/dist/esm/clients/guide/client.mjs +0 -284
- package/dist/esm/clients/guide/client.mjs.map +0 -1
- package/dist/esm/clients/messages/index.mjs +0 -64
- package/dist/esm/clients/messages/index.mjs.map +0 -1
- package/dist/esm/clients/ms-teams/index.mjs +0 -91
- package/dist/esm/clients/ms-teams/index.mjs.map +0 -1
- package/dist/esm/clients/objects/constants.mjs +0 -5
- package/dist/esm/clients/objects/constants.mjs.map +0 -1
- package/dist/esm/clients/objects/index.mjs +0 -42
- package/dist/esm/clients/objects/index.mjs.map +0 -1
- package/dist/esm/clients/preferences/index.mjs +0 -128
- package/dist/esm/clients/preferences/index.mjs.map +0 -1
- package/dist/esm/clients/slack/index.mjs +0 -72
- package/dist/esm/clients/slack/index.mjs.map +0 -1
- package/dist/esm/clients/users/index.mjs +0 -99
- package/dist/esm/clients/users/index.mjs.map +0 -1
- package/dist/esm/helpers.mjs +0 -8
- package/dist/esm/helpers.mjs.map +0 -1
- package/dist/esm/index.mjs +0 -16
- package/dist/esm/index.mjs.map +0 -1
- package/dist/esm/knock.mjs +0 -95
- package/dist/esm/knock.mjs.map +0 -1
- package/dist/esm/networkStatus.mjs +0 -15
- package/dist/esm/networkStatus.mjs.map +0 -1
- package/dist/types/api.d.ts +0 -25
- package/dist/types/api.d.ts.map +0 -1
- package/dist/types/clients/feed/feed.d.ts +0 -75
- package/dist/types/clients/feed/feed.d.ts.map +0 -1
- package/dist/types/clients/feed/index.d.ts +0 -17
- package/dist/types/clients/feed/index.d.ts.map +0 -1
- package/dist/types/clients/feed/interfaces.d.ts +0 -99
- package/dist/types/clients/feed/interfaces.d.ts.map +0 -1
- package/dist/types/clients/feed/socket-manager.d.ts +0 -31
- package/dist/types/clients/feed/socket-manager.d.ts.map +0 -1
- package/dist/types/clients/feed/store.d.ts +0 -20
- package/dist/types/clients/feed/store.d.ts.map +0 -1
- package/dist/types/clients/feed/types.d.ts +0 -35
- package/dist/types/clients/feed/types.d.ts.map +0 -1
- package/dist/types/clients/feed/utils.d.ts +0 -20
- package/dist/types/clients/feed/utils.d.ts.map +0 -1
- package/dist/types/clients/guide/client.d.ts +0 -124
- package/dist/types/clients/guide/client.d.ts.map +0 -1
- package/dist/types/clients/guide/index.d.ts +0 -3
- package/dist/types/clients/guide/index.d.ts.map +0 -1
- package/dist/types/clients/messages/index.d.ts +0 -15
- package/dist/types/clients/messages/index.d.ts.map +0 -1
- package/dist/types/clients/messages/interfaces.d.ts +0 -46
- package/dist/types/clients/messages/interfaces.d.ts.map +0 -1
- package/dist/types/clients/ms-teams/index.d.ts +0 -14
- package/dist/types/clients/ms-teams/index.d.ts.map +0 -1
- package/dist/types/clients/ms-teams/interfaces.d.ts +0 -49
- package/dist/types/clients/ms-teams/interfaces.d.ts.map +0 -1
- package/dist/types/clients/objects/constants.d.ts +0 -2
- package/dist/types/clients/objects/constants.d.ts.map +0 -1
- package/dist/types/clients/objects/index.d.ts +0 -23
- package/dist/types/clients/objects/index.d.ts.map +0 -1
- package/dist/types/clients/preferences/index.d.ts +0 -46
- package/dist/types/clients/preferences/index.d.ts.map +0 -1
- package/dist/types/clients/preferences/interfaces.d.ts +0 -29
- package/dist/types/clients/preferences/interfaces.d.ts.map +0 -1
- package/dist/types/clients/slack/index.d.ts +0 -13
- package/dist/types/clients/slack/index.d.ts.map +0 -1
- package/dist/types/clients/slack/interfaces.d.ts +0 -29
- package/dist/types/clients/slack/interfaces.d.ts.map +0 -1
- package/dist/types/clients/users/index.d.ts +0 -22
- package/dist/types/clients/users/index.d.ts.map +0 -1
- package/dist/types/clients/users/interfaces.d.ts +0 -9
- package/dist/types/clients/users/interfaces.d.ts.map +0 -1
- package/dist/types/helpers.d.ts +0 -2
- package/dist/types/helpers.d.ts.map +0 -1
- package/dist/types/index.d.ts +0 -21
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/interfaces.d.ts +0 -66
- package/dist/types/interfaces.d.ts.map +0 -1
- package/dist/types/knock.d.ts +0 -39
- package/dist/types/knock.d.ts.map +0 -1
- package/dist/types/networkStatus.d.ts +0 -8
- package/dist/types/networkStatus.d.ts.map +0 -1
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! ๐งช
|
package/dist/cjs/api.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
"use strict";var i=Object.defineProperty;var n=(t,e,s)=>e in t?i(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s;var r=(t,e,s)=>n(t,typeof e!="symbol"?e+"":e,s);Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const u=require("axios"),l=require("axios-retry"),c=require("phoenix"),a=t=>t&&typeof t=="object"&&"default"in t?t:{default:t},p=a(u),o=a(l);class d{constructor(e){r(this,"host");r(this,"apiKey");r(this,"userToken");r(this,"axiosClient");r(this,"socket");this.host=e.host,this.apiKey=e.apiKey,this.userToken=e.userToken||null,this.axiosClient=p.default.create({baseURL:this.host,headers:{Accept:"application/json","Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-Knock-User-Token":this.userToken}}),typeof window<"u"&&(this.socket=new c.Socket(`${this.host.replace("http","ws")}/ws/v1`,{params:{user_token:this.userToken,api_key:this.apiKey}})),o.default(this.axiosClient,{retries:3,retryCondition:this.canRetryRequest,retryDelay:o.default.exponentialDelay})}async makeRequest(e){try{const s=await this.axiosClient(e);return{statusCode:s.status<300?"ok":"error",body:s.data,error:void 0,status:s.status}}catch(s){return console.error(s),{statusCode:"error",status:500,body:void 0,error:s}}}canRetryRequest(e){return o.default.isNetworkError(e)?!0:e.response?e.response.status>=500&&e.response.status<=599||e.response.status===429:!1}}exports.default=d;
|
|
2
|
-
//# sourceMappingURL=api.js.map
|
package/dist/cjs/api.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sources":["../../src/api.ts"],"sourcesContent":["import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from \"axios\";\nimport axiosRetry from \"axios-retry\";\nimport { Socket } from \"phoenix\";\n\ntype ApiClientOptions = {\n host: string;\n apiKey: string;\n userToken: string | undefined;\n};\n\nexport interface ApiResponse {\n // eslint-disable-next-line\n error?: any;\n // eslint-disable-next-line\n body?: any;\n statusCode: \"ok\" | \"error\";\n status: number;\n}\n\nclass ApiClient {\n private host: string;\n private apiKey: string;\n private userToken: string | null;\n private axiosClient: AxiosInstance;\n\n public socket: Socket | undefined;\n\n constructor(options: ApiClientOptions) {\n this.host = options.host;\n this.apiKey = options.apiKey;\n this.userToken = options.userToken || null;\n\n // Create a retryable axios client\n this.axiosClient = axios.create({\n baseURL: this.host,\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n \"X-Knock-User-Token\": this.userToken,\n },\n });\n\n if (typeof window !== \"undefined\") {\n this.socket = new Socket(`${this.host.replace(\"http\", \"ws\")}/ws/v1`, {\n params: {\n user_token: this.userToken,\n api_key: this.apiKey,\n },\n });\n }\n\n axiosRetry(this.axiosClient, {\n retries: 3,\n retryCondition: this.canRetryRequest,\n retryDelay: axiosRetry.exponentialDelay,\n });\n }\n\n async makeRequest(req: AxiosRequestConfig): Promise<ApiResponse> {\n try {\n const result = await this.axiosClient(req);\n\n return {\n statusCode: result.status < 300 ? \"ok\" : \"error\",\n body: result.data,\n error: undefined,\n status: result.status,\n };\n\n // eslint:disable-next-line\n } catch (e: unknown) {\n console.error(e);\n\n return {\n statusCode: \"error\",\n status: 500,\n body: undefined,\n error: e,\n };\n }\n }\n\n private canRetryRequest(error: AxiosError) {\n // Retry Network Errors.\n if (axiosRetry.isNetworkError(error)) {\n return true;\n }\n\n if (!error.response) {\n // Cannot determine if the request can be retried\n return false;\n }\n\n // Retry Server Errors (5xx).\n if (error.response.status >= 500 && error.response.status <= 599) {\n return true;\n }\n\n // Retry if rate limited.\n if (error.response.status === 429) {\n return true;\n }\n\n return false;\n }\n}\n\nexport default ApiClient;\n"],"names":["ApiClient","options","__publicField","axios","Socket","axiosRetry","req","result","e","error"],"mappings":"6ZAmBA,MAAMA,CAAU,CAQd,YAAYC,EAA2B,CAP/BC,EAAA,aACAA,EAAA,eACAA,EAAA,kBACAA,EAAA,oBAEDA,EAAA,eAGL,KAAK,KAAOD,EAAQ,KACpB,KAAK,OAASA,EAAQ,OACjB,KAAA,UAAYA,EAAQ,WAAa,KAGjC,KAAA,YAAcE,UAAM,OAAO,CAC9B,QAAS,KAAK,KACd,QAAS,CACP,OAAQ,mBACR,eAAgB,mBAChB,cAAe,UAAU,KAAK,MAAM,GACpC,qBAAsB,KAAK,SAAA,CAC7B,CACD,EAEG,OAAO,OAAW,MACf,KAAA,OAAS,IAAIC,EAAA,OAAO,GAAG,KAAK,KAAK,QAAQ,OAAQ,IAAI,CAAC,SAAU,CACnE,OAAQ,CACN,WAAY,KAAK,UACjB,QAAS,KAAK,MAAA,CAChB,CACD,GAGHC,EAAA,QAAW,KAAK,YAAa,CAC3B,QAAS,EACT,eAAgB,KAAK,gBACrB,WAAYA,EAAAA,QAAW,gBAAA,CACxB,CAAA,CAGH,MAAM,YAAYC,EAA+C,CAC3D,GAAA,CACF,MAAMC,EAAS,MAAM,KAAK,YAAYD,CAAG,EAElC,MAAA,CACL,WAAYC,EAAO,OAAS,IAAM,KAAO,QACzC,KAAMA,EAAO,KACb,MAAO,OACP,OAAQA,EAAO,MACjB,QAGOC,EAAY,CACnB,eAAQ,MAAMA,CAAC,EAER,CACL,WAAY,QACZ,OAAQ,IACR,KAAM,OACN,MAAOA,CACT,CAAA,CACF,CAGM,gBAAgBC,EAAmB,CAErC,OAAAJ,EAAA,QAAW,eAAeI,CAAK,EAC1B,GAGJA,EAAM,SAMPA,EAAM,SAAS,QAAU,KAAOA,EAAM,SAAS,QAAU,KAKzDA,EAAM,SAAS,SAAW,IATrB,EAaF,CAEX"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
"use strict";var g=Object.defineProperty;var p=(c,e,t)=>e in c?g(c,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):c[e]=t;var d=(c,e,t)=>p(c,typeof e!="symbol"?e+"":e,t);Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const k=require("eventemitter2"),_=require("nanoid"),v=require("../../helpers.js"),m=require("../../networkStatus.js"),S=require("./socket-manager.js"),y=require("./store.js"),f=require("./utils.js"),b=c=>c&&typeof c=="object"&&"default"in c?c:{default:c},w=b(k),I={archived:"exclude"},C=2e3,U="client_";class A{constructor(e,t,s,a){d(this,"defaultOptions");d(this,"referenceId");d(this,"unsubscribeFromSocketEvents");d(this,"socketManager");d(this,"userFeedId");d(this,"broadcaster");d(this,"broadcastChannel");d(this,"disconnectTimer",null);d(this,"hasSubscribedToRealTimeUpdates",!1);d(this,"visibilityChangeHandler",()=>{});d(this,"visibilityChangeListenerConnected",!1);d(this,"store");this.knock=e,this.feedId=t,(!t||!v.isValidUuid(t))&&this.knock.log("[Feed] Invalid or missing feedId provided to the Feed constructor. The feed should be a UUID of an in-app feed channel (`in_app_feed`) found in the Knock dashboard. Please provide a valid feedId to the Feed constructor.",!0),this.feedId=t,this.userFeedId=this.buildUserFeedId(),this.referenceId=U+_.nanoid(),this.socketManager=a,this.store=y.default(),this.broadcaster=new w.default({wildcard:!0,delimiter:"."}),this.defaultOptions={...I,...f.mergeDateRangeParams(s)},this.knock.log(`[Feed] Initialized a feed on channel ${t}`),this.initializeRealtimeConnection(),this.setupBroadcastChannel()}reinitialize(e){this.socketManager=e,this.userFeedId=this.buildUserFeedId(),this.initializeRealtimeConnection(),this.setupBroadcastChannel()}teardown(){var e;this.knock.log("[Feed] Tearing down feed instance"),(e=this.socketManager)==null||e.leave(this),this.tearDownVisibilityListeners(),this.disconnectTimer&&(clearTimeout(this.disconnectTimer),this.disconnectTimer=null),this.broadcastChannel&&this.broadcastChannel.close()}dispose(){this.knock.log("[Feed] Disposing of feed instance"),this.teardown(),this.broadcaster.removeAllListeners(),this.knock.feeds.removeInstance(this)}listenForUpdates(){var e;if(this.knock.log("[Feed] Connecting to real-time service"),this.hasSubscribedToRealTimeUpdates=!0,!this.knock.isAuthenticated()){this.knock.log("[Feed] User is not authenticated, skipping listening for updates");return}this.unsubscribeFromSocketEvents=(e=this.socketManager)==null?void 0:e.join(this)}on(e,t){this.broadcaster.on(e,t)}off(e,t){this.broadcaster.off(e,t)}getState(){return this.store.getState()}async markAsSeen(e){const t=new Date().toISOString();return this.optimisticallyPerformStatusUpdate(e,"seen",{seen_at:t},"unseen_count"),this.makeStatusUpdate(e,"seen")}async markAllAsSeen(){const{metadata:e,items:t,...s}=this.store.getState();if(this.defaultOptions.status==="unseen")s.resetStore({...e,total_count:0,unseen_count:0});else{s.setMetadata({...e,unseen_count:0});const n={seen_at:new Date().toISOString()},i=t.map(o=>o.id);s.setItemAttrs(i,n)}const r=await this.makeBulkStatusUpdate("seen");return this.emitEvent("all_seen",t),r}async markAsUnseen(e){return this.optimisticallyPerformStatusUpdate(e,"unseen",{seen_at:null},"unseen_count"),this.makeStatusUpdate(e,"unseen")}async markAsRead(e){const t=new Date().toISOString();return this.optimisticallyPerformStatusUpdate(e,"read",{read_at:t},"unread_count"),this.makeStatusUpdate(e,"read")}async markAllAsRead(){const{metadata:e,items:t,...s}=this.store.getState();if(this.defaultOptions.status==="unread")s.resetStore({...e,total_count:0,unread_count:0});else{s.setMetadata({...e,unread_count:0});const n={read_at:new Date().toISOString()},i=t.map(o=>o.id);s.setItemAttrs(i,n)}const r=await this.makeBulkStatusUpdate("read");return this.emitEvent("all_read",t),r}async markAsUnread(e){return this.optimisticallyPerformStatusUpdate(e,"unread",{read_at:null},"unread_count"),this.makeStatusUpdate(e,"unread")}async markAsInteracted(e,t){const s=new Date().toISOString();return this.optimisticallyPerformStatusUpdate(e,"interacted",{read_at:s,interacted_at:s},"unread_count"),this.makeStatusUpdate(e,"interacted",t)}async markAsArchived(e){const t=this.store.getState(),s=this.defaultOptions.archived==="exclude",a=Array.isArray(e)?e:[e],r=a.map(n=>n.id);if(s){const n=a.filter(u=>!u.seen_at).length,i=a.filter(u=>!u.read_at).length,o={...t.metadata,total_count:Math.max(0,t.metadata.total_count-a.length),unseen_count:Math.max(0,t.metadata.unseen_count-n),unread_count:Math.max(0,t.metadata.unread_count-i)},l=t.items.filter(u=>!r.includes(u.id));t.setResult({entries:l,meta:o,page_info:t.pageInfo})}else t.setItemAttrs(r,{archived_at:new Date().toISOString()});return this.makeStatusUpdate(e,"archived")}async markAllAsArchived(){const{items:e,...t}=this.store.getState();if(this.defaultOptions.archived==="exclude")t.resetStore();else{const r=e.map(n=>n.id);t.setItemAttrs(r,{archived_at:new Date().toISOString()})}const a=await this.makeBulkStatusUpdate("archive");return this.emitEvent("all_archived",e),a}async markAllReadAsArchived(){const{items:e,...t}=this.store.getState(),a=e.filter(i=>i.read_at===null).map(i=>i.id);if(t.setItemAttrs(a,{archived_at:new Date().toISOString()}),this.defaultOptions.archived==="exclude"){const i=e.filter(l=>!a.includes(l.id)),o={...t.metadata,total_count:i.length,unread_count:0};t.setResult({entries:i,meta:o,page_info:t.pageInfo})}return await this.makeBulkStatusUpdate("archive")}async markAsUnarchived(e){return this.optimisticallyPerformStatusUpdate(e,"unarchived",{archived_at:null}),this.makeStatusUpdate(e,"unarchived")}async fetch(e={}){const{networkStatus:t,...s}=this.store.getState();if(!this.knock.isAuthenticated()){this.knock.log("[Feed] User is not authenticated, skipping fetch");return}if(m.isRequestInFlight(t)){this.knock.log("[Feed] Request is in flight, skipping fetch");return}s.setNetworkStatus(e.__loadingType??m.NetworkStatus.loading);const a=f.getFormattedTriggerData({...this.defaultOptions,...e}),r={...this.defaultOptions,...f.mergeDateRangeParams(e),trigger_data:a,__loadingType:void 0,__fetchSource:void 0,__experimentalCrossBrowserUpdates:void 0,auto_manage_socket_connection:void 0,auto_manage_socket_connection_delay:void 0},n=await this.knock.client().makeRequest({method:"GET",url:`/v1/users/${this.knock.userId}/feeds/${this.feedId}`,params:r});if(n.statusCode==="error"||!n.body)return s.setNetworkStatus(m.NetworkStatus.error),{status:n.statusCode,data:n.error||n.body};const i={entries:n.body.entries,meta:n.body.meta,page_info:n.body.page_info};if(e.before){const u={shouldSetPage:!1,shouldAppend:!0};s.setResult(i,u)}else if(e.after){const u={shouldSetPage:!0,shouldAppend:!0};s.setResult(i,u)}else s.setResult(i);this.broadcast("messages.new",i);const o=e.__fetchSource==="socket"?"items.received.realtime":"items.received.page",l={items:i.entries,metadata:i.meta,event:o};return this.broadcast(l.event,l),{data:i,status:n.statusCode}}async fetchNextPage(e={}){const{pageInfo:t}=this.store.getState();t.after&&this.fetch({...e,after:t.after,__loadingType:m.NetworkStatus.fetchMore})}get socketChannelTopic(){return`feeds:${this.userFeedId}`}broadcast(e,t){this.broadcaster.emit(e,t)}async onNewMessageReceived({data:e}){var n;this.knock.log("[Feed] Received new real-time message");const{items:t,...s}=this.store.getState(),a=t[0],r=(n=e[this.referenceId])==null?void 0:n.metadata;r&&s.setMetadata(r),this.fetch({before:a==null?void 0:a.__cursor,__fetchSource:"socket"})}buildUserFeedId(){return`${this.feedId}:${this.knock.userId}`}optimisticallyPerformStatusUpdate(e,t,s,a){const r=this.store.getState(),n=Array.isArray(e)?e:[e],i=n.map(o=>o.id);if(a){const{metadata:o}=r,l=n.filter(h=>{switch(t){case"seen":return h.seen_at===null;case"unseen":return h.seen_at!==null;case"read":case"interacted":return h.read_at===null;case"unread":return h.read_at!==null;default:return!0}}),u=t.startsWith("un")?l.length:-l.length;r.setMetadata({...o,[a]:Math.max(0,o[a]+u)})}r.setItemAttrs(i,s)}async makeStatusUpdate(e,t,s){const a=Array.isArray(e)?e:[e],r=a.map(i=>i.id),n=await this.knock.messages.batchUpdateStatuses(r,t,{metadata:s});return this.emitEvent(t,a),n}async makeBulkStatusUpdate(e){const t={user_ids:[this.knock.userId],engagement_status:this.defaultOptions.status!=="all"?this.defaultOptions.status:void 0,archived:this.defaultOptions.archived,has_tenant:this.defaultOptions.has_tenant,tenants:this.defaultOptions.tenant?[this.defaultOptions.tenant]:void 0};return await this.knock.messages.bulkUpdateAllStatusesInChannel({channelId:this.feedId,status:e,options:t})}setupBroadcastChannel(){this.broadcastChannel=typeof self<"u"&&"BroadcastChannel"in self?new BroadcastChannel(`knock:feed:${this.userFeedId}`):null,this.broadcastChannel&&this.defaultOptions.__experimentalCrossBrowserUpdates===!0&&(this.broadcastChannel.onmessage=e=>{switch(e.data.type){case"items:archived":case"items:unarchived":case"items:seen":case"items:unseen":case"items:read":case"items:unread":case"items:all_read":case"items:all_seen":case"items:all_archived":return this.fetch();default:return null}})}broadcastOverChannel(e,t){if(this.broadcastChannel)try{const s=JSON.parse(JSON.stringify(t));this.broadcastChannel.postMessage({type:e,payload:s})}catch(s){console.warn(`Could not broadcast ${e}, got error: ${s}`)}}initializeRealtimeConnection(){var e;this.socketManager&&(this.defaultOptions.auto_manage_socket_connection&&this.setUpVisibilityListeners(),this.hasSubscribedToRealTimeUpdates&&this.knock.isAuthenticated()&&(this.unsubscribeFromSocketEvents=(e=this.socketManager)==null?void 0:e.join(this)))}async handleSocketEvent(e){switch(e.event){case S.SocketEventType.NewMessage:this.onNewMessageReceived(e);return;default:{e.event;return}}}setUpVisibilityListeners(){typeof document>"u"||this.visibilityChangeListenerConnected||(this.visibilityChangeHandler=this.handleVisibilityChange.bind(this),this.visibilityChangeListenerConnected=!0,document.addEventListener("visibilitychange",this.visibilityChangeHandler))}tearDownVisibilityListeners(){typeof document>"u"||(document.removeEventListener("visibilitychange",this.visibilityChangeHandler),this.visibilityChangeListenerConnected=!1)}emitEvent(e,t){this.broadcaster.emit(`items.${e}`,{items:t}),this.broadcaster.emit(`items:${e}`,{items:t}),this.broadcastOverChannel(`items:${e}`,{items:t})}handleVisibilityChange(){var s,a;const e=this.defaultOptions.auto_manage_socket_connection_delay??C,t=this.knock.client();document.visibilityState==="hidden"?this.disconnectTimer=setTimeout(()=>{var r;(r=t.socket)==null||r.disconnect(),this.disconnectTimer=null},e):document.visibilityState==="visible"&&(this.disconnectTimer&&(clearTimeout(this.disconnectTimer),this.disconnectTimer=null),(s=t.socket)!=null&&s.isConnected()||(a=t.socket)==null||a.connect())}}exports.default=A;
|
|
2
|
-
//# sourceMappingURL=feed.js.map
|