@webqit/fetch-plus 0.1.2 → 0.1.4
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/README.md +2050 -2
- package/dist/main.js +1 -1
- package/dist/main.js.map +4 -4
- package/package.json +8 -8
- package/src/FormDataPlus.js +23 -15
- package/src/HeadersPlus.js +128 -56
- package/src/LiveResponse.js +233 -155
- package/src/RequestPlus.js +9 -7
- package/src/ResponsePlus.js +6 -6
- package/src/index.js +0 -1
- package/src/messageParserMixin.js +217 -0
- package/test/1.basic.test.js +314 -0
- package/test/2.LiveResponse.test.js +261 -0
- package/test/3.LiveResponse.integration.test.js +459 -0
- package/src/URLSearchParamsPlus.js +0 -80
- package/src/core.js +0 -172
- package/test/basic.test.js +0 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import { BroadcastChannelPlus, Observer } from '@webqit/port-plus';
|
|
3
|
+
import { LiveResponse } from '../src/LiveResponse.js';
|
|
4
|
+
|
|
5
|
+
describe('LiveResponse Integration Tests (Background Ports)', function () {
|
|
6
|
+
|
|
7
|
+
describe('Simulation via direct Port Manipulation', function () {
|
|
8
|
+
async function setupWire() {
|
|
9
|
+
const portID = 'test-channel-' + Math.random().toString(36).substring(7);
|
|
10
|
+
|
|
11
|
+
// 1. Setup port
|
|
12
|
+
const serverSideClientPort = new BroadcastChannelPlus(portID, {
|
|
13
|
+
clientServerMode: 'server',
|
|
14
|
+
postAwaitsOpen: true,
|
|
15
|
+
autoStart: true // Ensure it's ready to accept connections
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// ------------
|
|
19
|
+
|
|
20
|
+
// 2. Transport layer response
|
|
21
|
+
const response = new Response('initial content', {
|
|
22
|
+
headers: { 'X-Message-Port': `channel://${portID}` }
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ------------
|
|
26
|
+
|
|
27
|
+
// 3. Client-side response
|
|
28
|
+
const liveResponseB = LiveResponse.from(response);
|
|
29
|
+
await liveResponseB.readyStateChange('live');
|
|
30
|
+
|
|
31
|
+
// 4. Take records
|
|
32
|
+
const clientSideResult = [];
|
|
33
|
+
liveResponseB.addEventListener('replace', (e) => {
|
|
34
|
+
clientSideResult.push(liveResponseB.body);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return [serverSideClientPort, liveResponseB, clientSideResult];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
it('should simulate response.replace via a background port', async function () {
|
|
41
|
+
const [serverSideClientPort, liveResponseB, clientSideResult] = await setupWire();
|
|
42
|
+
|
|
43
|
+
// ----- Server-side -----
|
|
44
|
+
serverSideClientPort.postMessage({
|
|
45
|
+
body: 'pushed content 1',
|
|
46
|
+
}, { type: 'response.replace' });
|
|
47
|
+
serverSideClientPort.postMessage({
|
|
48
|
+
body: 'pushed content 2',
|
|
49
|
+
}, { type: 'response.replace' });
|
|
50
|
+
// ----- End: Server-side
|
|
51
|
+
|
|
52
|
+
// ----- Transport latency -----
|
|
53
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
54
|
+
// ----- End: Transport latency
|
|
55
|
+
|
|
56
|
+
// ----- Client-side -----
|
|
57
|
+
expect(clientSideResult[0]).to.equal('pushed content 1');
|
|
58
|
+
expect(clientSideResult[1]).to.equal('pushed content 2');
|
|
59
|
+
|
|
60
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
61
|
+
|
|
62
|
+
// Server did not explicitly post a response.done message
|
|
63
|
+
expect(liveResponseB.readyState).to.equal('live');
|
|
64
|
+
// ----- End: Client-side
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should be done when server specifies done in last message', async function () {
|
|
68
|
+
const [serverSideClientPort, liveResponseB, clientSideResult] = await setupWire();
|
|
69
|
+
|
|
70
|
+
// ----- Server-side -----
|
|
71
|
+
serverSideClientPort.postMessage({
|
|
72
|
+
body: 'pushed content 1',
|
|
73
|
+
}, { type: 'response.replace' });
|
|
74
|
+
serverSideClientPort.postMessage({
|
|
75
|
+
body: 'pushed content 2'
|
|
76
|
+
}, { type: 'response.replace' });
|
|
77
|
+
serverSideClientPort.postMessage(null, { type: 'response.done' });
|
|
78
|
+
// ----- End: Server-side
|
|
79
|
+
|
|
80
|
+
// ----- Transport latency -----
|
|
81
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
82
|
+
// ----- End: Transport latency
|
|
83
|
+
|
|
84
|
+
// ----- Client-side -----
|
|
85
|
+
expect(clientSideResult[0]).to.equal('pushed content 1');
|
|
86
|
+
expect(clientSideResult[1]).to.equal('pushed content 2');
|
|
87
|
+
|
|
88
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
89
|
+
|
|
90
|
+
// Server did explicitly post a response.done message
|
|
91
|
+
expect(liveResponseB.readyState).to.equal('done');
|
|
92
|
+
// ----- End: Client-side
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should project Live Objects', async function () {
|
|
96
|
+
const [serverSideClientPort, liveResponseB, clientSideResult] = await setupWire();
|
|
97
|
+
|
|
98
|
+
const obj1 = { a: 1, b: 2 };
|
|
99
|
+
const obj2 = { a: 2, b: 3 };
|
|
100
|
+
const obj3 = { a: 3, b: 4 };
|
|
101
|
+
const concurrencyController = new AbortController;
|
|
102
|
+
|
|
103
|
+
// ---- First Live Object ----
|
|
104
|
+
|
|
105
|
+
// Replacement + live-projection on the server side
|
|
106
|
+
serverSideClientPort.postMessage({
|
|
107
|
+
body: obj1,
|
|
108
|
+
concurrent: true,
|
|
109
|
+
}, { type: 'response.replace', live: true, signal: concurrencyController.signal });
|
|
110
|
+
|
|
111
|
+
// Inspect on the client side
|
|
112
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
113
|
+
expect(clientSideResult[0]).to.eql(obj1);
|
|
114
|
+
expect(liveResponseB.concurrent).to.true;
|
|
115
|
+
|
|
116
|
+
// Mutate on the server side
|
|
117
|
+
Observer.set(obj1, 'c', 3);
|
|
118
|
+
|
|
119
|
+
// Inspect on the client side
|
|
120
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
121
|
+
expect(clientSideResult[0]).to.eql(obj1);
|
|
122
|
+
|
|
123
|
+
// ---- Second Live Object ----
|
|
124
|
+
|
|
125
|
+
// Replacement + live-projection on the server side
|
|
126
|
+
serverSideClientPort.postMessage({
|
|
127
|
+
body: obj2,
|
|
128
|
+
concurrent: true,
|
|
129
|
+
}, { type: 'response.replace', live: true, signal: concurrencyController.signal });
|
|
130
|
+
|
|
131
|
+
// Inspect on the client side
|
|
132
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
133
|
+
expect(clientSideResult[1]).to.eql(obj2);
|
|
134
|
+
expect(liveResponseB.concurrent).to.true;
|
|
135
|
+
|
|
136
|
+
// Mutate on the server side
|
|
137
|
+
Observer.set(obj2, 'c', 6);
|
|
138
|
+
|
|
139
|
+
// Inspect on the client side
|
|
140
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
141
|
+
expect(clientSideResult[1]).to.eql(obj2);
|
|
142
|
+
|
|
143
|
+
// ---- Concurrency ----
|
|
144
|
+
|
|
145
|
+
// Mutate on the server side
|
|
146
|
+
Observer.set(obj1, 'd', 10); // NOTE: 1
|
|
147
|
+
Observer.set(obj2, 'd', 20);
|
|
148
|
+
|
|
149
|
+
// Inspect on the client side
|
|
150
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
151
|
+
expect(clientSideResult[0]).to.eql(obj1); // NOTE: 1
|
|
152
|
+
expect(clientSideResult[1]).to.eql(obj2);
|
|
153
|
+
|
|
154
|
+
// NOTE: 1 – Second replacement+live projection being concurrent means that the previous live object projection has not been terminated by us on the server.
|
|
155
|
+
|
|
156
|
+
// Server did not explicitly post a response.done message
|
|
157
|
+
expect(liveResponseB.readyState).to.equal('live');
|
|
158
|
+
|
|
159
|
+
// ---- Third Live Object ----
|
|
160
|
+
|
|
161
|
+
concurrencyController.abort();
|
|
162
|
+
|
|
163
|
+
// Replacement + live-projection on the server side
|
|
164
|
+
serverSideClientPort.postMessage({
|
|
165
|
+
body: obj3,
|
|
166
|
+
concurrent: false,
|
|
167
|
+
}, { type: 'response.replace', live: true, signal: null });
|
|
168
|
+
serverSideClientPort.postMessage(null, { type: 'response.done' });
|
|
169
|
+
|
|
170
|
+
// Inspect on the client side
|
|
171
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
172
|
+
expect(clientSideResult[2]).to.eql(obj3);
|
|
173
|
+
expect(liveResponseB.concurrent).to.false;
|
|
174
|
+
|
|
175
|
+
// Mutate on the server side
|
|
176
|
+
Observer.set(obj2, 'c', 6);
|
|
177
|
+
|
|
178
|
+
// Inspect on the client side
|
|
179
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
180
|
+
expect(clientSideResult[1]).to.eql(obj2);
|
|
181
|
+
|
|
182
|
+
// Server did explicitly post a response.done message
|
|
183
|
+
expect(liveResponseB.readyState).to.equal('done');
|
|
184
|
+
|
|
185
|
+
// ---- Concurrency & Continuity ----
|
|
186
|
+
|
|
187
|
+
// Mutate on the server side
|
|
188
|
+
Observer.set(obj1, 'd', 11); // NOTE: 1
|
|
189
|
+
Observer.set(obj2, 'd', 21); // NOTE: 1
|
|
190
|
+
Observer.set(obj3, 'd', 22); // NOTE: 2
|
|
191
|
+
|
|
192
|
+
// Inspect on the client side
|
|
193
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
194
|
+
expect(clientSideResult[0]).to.not.eql(obj1); // NOTE: 1
|
|
195
|
+
expect(clientSideResult[1]).to.not.eql(obj2); // NOTE: 1
|
|
196
|
+
expect(clientSideResult[2]).to.eql(obj3); // NOTE: 2
|
|
197
|
+
|
|
198
|
+
// NOTE: 1 – Third replacement+live projection being non-concurrent means that the previous live object projection has been terminated by us on the server and sp should be discontinued on the client.
|
|
199
|
+
// NOTE: 2 – Third replacement+live projection remains live even tho it's last in the series of "responses" – denoted by the server's "response.done" message
|
|
200
|
+
// – until the server terminates the live projection or until port closes.
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('LiveResponse End-to-End Tests', function () {
|
|
206
|
+
async function setupWire() {
|
|
207
|
+
const portID = 'test-channel-' + Math.random().toString(36).substring(7);
|
|
208
|
+
|
|
209
|
+
// 1. Setup port
|
|
210
|
+
const serverSideClientPort = new BroadcastChannelPlus(portID, {
|
|
211
|
+
clientServerMode: 'server',
|
|
212
|
+
postAwaitsOpen: true,
|
|
213
|
+
autoStart: true // Ensure it's ready to accept connections
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// 2. Server-side LiveResponse
|
|
217
|
+
const liveResponseA = new LiveResponse('initial content', { done: false });
|
|
218
|
+
|
|
219
|
+
// ------------
|
|
220
|
+
|
|
221
|
+
// 3. Transport layer Response
|
|
222
|
+
const response = liveResponseA.toResponse({ port: serverSideClientPort, signal: undefined });
|
|
223
|
+
response.headers.set('X-Message-Port', 'channel://' + portID);
|
|
224
|
+
|
|
225
|
+
// ------------
|
|
226
|
+
|
|
227
|
+
// 4. Client-side LiveResponse
|
|
228
|
+
const liveResponseB = new LiveResponse(response);
|
|
229
|
+
await liveResponseB.readyStateChange('live');
|
|
230
|
+
|
|
231
|
+
// 5. Take records
|
|
232
|
+
const clientSideResult = [];
|
|
233
|
+
liveResponseB.addEventListener('replace', (e) => {
|
|
234
|
+
clientSideResult.push(liveResponseB.body);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
return [liveResponseA, liveResponseB, clientSideResult];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
it('should simulate response.replace via a background port', async function () {
|
|
241
|
+
const [liveResponseA, liveResponseB, clientSideResult] = await setupWire();
|
|
242
|
+
|
|
243
|
+
// ----- Server-side -----
|
|
244
|
+
await liveResponseA.replaceWith('pushed content 1', { done: false });
|
|
245
|
+
await liveResponseA.replaceWith('pushed content 2', { done: false });
|
|
246
|
+
// ----- End: Server-side -----
|
|
247
|
+
|
|
248
|
+
// ----- Transport latency -----
|
|
249
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
250
|
+
// ----- End: Transport latency -----
|
|
251
|
+
|
|
252
|
+
// ----- Client-side -----
|
|
253
|
+
expect(clientSideResult[0]).to.equal('pushed content 1');
|
|
254
|
+
expect(clientSideResult[1]).to.equal('pushed content 2');
|
|
255
|
+
|
|
256
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
257
|
+
|
|
258
|
+
// Server did specify "done: false" in last message
|
|
259
|
+
expect(liveResponseB.readyState).to.equal('live');
|
|
260
|
+
// ----- End: Client-side -----
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should be done when server specifies done in last message', async function () {
|
|
264
|
+
const [liveResponseA, liveResponseB, clientSideResult] = await setupWire();
|
|
265
|
+
|
|
266
|
+
// ----- Server-side -----
|
|
267
|
+
liveResponseA.replaceWith('pushed content 1', { done: false });
|
|
268
|
+
liveResponseA.replaceWith('pushed content 2');
|
|
269
|
+
// ----- End: Server-side -----
|
|
270
|
+
|
|
271
|
+
// ----- Transport latency -----
|
|
272
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
273
|
+
// ----- End: Transport latency -----
|
|
274
|
+
|
|
275
|
+
// ----- Client-side -----
|
|
276
|
+
expect(clientSideResult[0]).to.equal('pushed content 1');
|
|
277
|
+
expect(clientSideResult[1]).to.equal('pushed content 2');
|
|
278
|
+
|
|
279
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
280
|
+
|
|
281
|
+
// Server did not specify "done: false" in last message
|
|
282
|
+
expect(liveResponseB.readyState).to.equal('done');
|
|
283
|
+
// ----- End: Client-side -----
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should project Live Objects', async function () {
|
|
287
|
+
const [liveResponseA, liveResponseB, clientSideResult] = await setupWire();
|
|
288
|
+
|
|
289
|
+
const obj1 = { a: 1, b: 2 };
|
|
290
|
+
const obj2 = { a: 2, b: 3 };
|
|
291
|
+
const obj3 = { a: 3, b: 4 };
|
|
292
|
+
|
|
293
|
+
// ---- First Live Object ----
|
|
294
|
+
|
|
295
|
+
// Replacement + live-projection on the server side
|
|
296
|
+
await liveResponseA.replaceWith(obj1, { concurrent: true, done: false });
|
|
297
|
+
|
|
298
|
+
// Inspect on the client side
|
|
299
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
300
|
+
expect(clientSideResult[0]).to.eql(obj1);
|
|
301
|
+
expect(liveResponseB.concurrent).to.true;
|
|
302
|
+
|
|
303
|
+
// Mutate on the server side
|
|
304
|
+
Observer.set(obj1, 'c', 3);
|
|
305
|
+
|
|
306
|
+
// Inspect on the client side
|
|
307
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
308
|
+
expect(clientSideResult[0]).to.eql(obj1);
|
|
309
|
+
|
|
310
|
+
// ---- Second Live Object ----
|
|
311
|
+
|
|
312
|
+
// Replacement + live-projection on the server side
|
|
313
|
+
await liveResponseA.replaceWith(obj2, { concurrent: true, done: false });
|
|
314
|
+
|
|
315
|
+
// Inspect on the client side
|
|
316
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
317
|
+
expect(clientSideResult[1]).to.eql(obj2);
|
|
318
|
+
expect(liveResponseB.concurrent).to.true;
|
|
319
|
+
|
|
320
|
+
// Mutate on the server side
|
|
321
|
+
Observer.set(obj2, 'c', 6);
|
|
322
|
+
|
|
323
|
+
// Inspect on the client side
|
|
324
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
325
|
+
expect(clientSideResult[1]).to.eql(obj2);
|
|
326
|
+
|
|
327
|
+
// ---- Concurrency ----
|
|
328
|
+
|
|
329
|
+
// Mutate on the server side
|
|
330
|
+
Observer.set(obj1, 'd', 10); // NOTE: 1
|
|
331
|
+
Observer.set(obj2, 'd', 20);
|
|
332
|
+
|
|
333
|
+
// Inspect on the client side
|
|
334
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
335
|
+
expect(clientSideResult[0]).to.eql(obj1); // NOTE: 1
|
|
336
|
+
expect(clientSideResult[1]).to.eql(obj2);
|
|
337
|
+
|
|
338
|
+
// NOTE: 1 – Second replacement+live projection being concurrent means that the previous live object projection has not been terminated by us on the server.
|
|
339
|
+
|
|
340
|
+
// Server did specify "done: false" in last message
|
|
341
|
+
expect(liveResponseB.readyState).to.equal('live');
|
|
342
|
+
|
|
343
|
+
// ---- Third Live Object ----
|
|
344
|
+
|
|
345
|
+
// Replacement + live-projection on the server side
|
|
346
|
+
await liveResponseA.replaceWith(obj3, { concurrent: false });
|
|
347
|
+
|
|
348
|
+
// Inspect on the client side
|
|
349
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
350
|
+
expect(clientSideResult[2]).to.eql(obj3);
|
|
351
|
+
expect(liveResponseB.concurrent).to.false;
|
|
352
|
+
|
|
353
|
+
// Mutate on the server side
|
|
354
|
+
Observer.set(obj2, 'c', 6);
|
|
355
|
+
|
|
356
|
+
// Inspect on the client side
|
|
357
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
358
|
+
expect(clientSideResult[1]).to.eql(obj2);
|
|
359
|
+
|
|
360
|
+
// Server did not specify "done: false" in last message
|
|
361
|
+
expect(liveResponseB.readyState).to.equal('done');
|
|
362
|
+
|
|
363
|
+
// ---- Concurrency & Continuity ----
|
|
364
|
+
|
|
365
|
+
// Mutate on the server side
|
|
366
|
+
Observer.set(obj1, 'd', 11); // NOTE: 1
|
|
367
|
+
Observer.set(obj2, 'd', 21); // NOTE: 1
|
|
368
|
+
Observer.set(obj3, 'd', 22); // NOTE: 2
|
|
369
|
+
|
|
370
|
+
// Inspect on the client side
|
|
371
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
372
|
+
expect(clientSideResult[0]).to.not.eql(obj1); // NOTE: 1
|
|
373
|
+
expect(clientSideResult[1]).to.not.eql(obj2); // NOTE: 1
|
|
374
|
+
expect(clientSideResult[2]).to.eql(obj3); // NOTE: 2
|
|
375
|
+
|
|
376
|
+
// NOTE: 1 – Third replacement+live projection being non-concurrent means that the previous live object projection has been terminated by us on the server and sp should be discontinued on the client.
|
|
377
|
+
// NOTE: 2 – Third replacement+live projection remains live even tho it's last in the series of "responses" – denoted by the server's "response.done" message
|
|
378
|
+
// – until the server terminates the live projection or until port closes.
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('should project Live Objects – advanced pipelines', async function () {
|
|
382
|
+
const [liveResponseA, liveResponseB, clientSideResult] = await setupWire();
|
|
383
|
+
|
|
384
|
+
const obj1 = { a: 1, b: 2 };
|
|
385
|
+
const obj2 = { a: 2, b: 3 };
|
|
386
|
+
const obj3 = { a: 3, b: 4 };
|
|
387
|
+
|
|
388
|
+
// ---- First Live Object ----
|
|
389
|
+
|
|
390
|
+
// Replacement + live-projection on the server side
|
|
391
|
+
await liveResponseA.replaceWith(obj1, async ($obj1) => {
|
|
392
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
393
|
+
$obj1.c = 3;
|
|
394
|
+
}, { concurrent: true, done: false });
|
|
395
|
+
|
|
396
|
+
// Inspect on the client side
|
|
397
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
398
|
+
expect(clientSideResult[0]).to.eql(obj1);
|
|
399
|
+
expect(liveResponseB.concurrent).to.true;
|
|
400
|
+
|
|
401
|
+
// ---- Second Live Object ----
|
|
402
|
+
|
|
403
|
+
// Replacement + live-projection on the server side
|
|
404
|
+
await liveResponseA.replaceWith((async function* gen() {
|
|
405
|
+
yield obj2;
|
|
406
|
+
|
|
407
|
+
await new Promise((r) => setTimeout(r, 2));
|
|
408
|
+
// Mutate obj2 even after having returned it
|
|
409
|
+
Observer.set(obj2, 'c', 6);
|
|
410
|
+
await new Promise((r) => setTimeout(r, 2));
|
|
411
|
+
|
|
412
|
+
(new Promise((r) => setTimeout(r, 2))).then(() => {
|
|
413
|
+
// Mutate obj3 even after having returned it
|
|
414
|
+
Observer.set(obj3, 'c', 60);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
return obj3;
|
|
418
|
+
})(), { concurrent: true, done: false });
|
|
419
|
+
|
|
420
|
+
// Inspect on the client side
|
|
421
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
422
|
+
expect(clientSideResult[1]).to.eql(obj2);
|
|
423
|
+
expect(clientSideResult[2]).to.eql(obj3);
|
|
424
|
+
|
|
425
|
+
// Replace with a LiveResponse instance itself
|
|
426
|
+
|
|
427
|
+
const obj4 = { a: 2, b: 3 };
|
|
428
|
+
const obj5 = { a: 3, b: 4 };
|
|
429
|
+
|
|
430
|
+
await liveResponseA.replaceWith(LiveResponse.from((async function* gen() {
|
|
431
|
+
yield obj4;
|
|
432
|
+
|
|
433
|
+
await new Promise((r) => setTimeout(r, 2));
|
|
434
|
+
// Mutate obj2 even after having returned it
|
|
435
|
+
Observer.set(obj4, 'c', 6);
|
|
436
|
+
await new Promise((r) => setTimeout(r, 2));
|
|
437
|
+
|
|
438
|
+
(new Promise((r) => setTimeout(r, 2))).then(() => {
|
|
439
|
+
// Mutate obj3 even after having returned it
|
|
440
|
+
Observer.set(obj5, 'c', 60);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
return obj5;
|
|
444
|
+
})()), { concurrent: true });
|
|
445
|
+
|
|
446
|
+
// Inspect on the client side
|
|
447
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
448
|
+
expect(clientSideResult[3]).to.eql(obj4);
|
|
449
|
+
expect(clientSideResult[4]).to.eql(obj5);
|
|
450
|
+
|
|
451
|
+
// The nested LiveResponse is essentially flattened out
|
|
452
|
+
|
|
453
|
+
// We should be done at this point
|
|
454
|
+
expect(liveResponseB.readyState).to.equal('done');
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
});
|
|
459
|
+
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { _isString, _isNumeric, _isTypeObject } from '@webqit/util/js/index.js';
|
|
2
|
-
|
|
3
|
-
export class URLSearchParamsPlus extends URLSearchParams {
|
|
4
|
-
|
|
5
|
-
// Parse a search params string into an object
|
|
6
|
-
static eval(targetObject, str, delim = '&') {
|
|
7
|
-
str = str || '';
|
|
8
|
-
(str.startsWith('?') ? str.substr(1) : str)
|
|
9
|
-
.split(delim).filter(q => q).map(q => q.split('=').map(q => q.trim()))
|
|
10
|
-
.forEach(q => this.set(targetObject, q[0], decodeURIComponent(q[1])));
|
|
11
|
-
return targetObject;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Stringify an object into a search params string
|
|
15
|
-
static stringify(targetObject, delim = '&') {
|
|
16
|
-
const q = [];
|
|
17
|
-
Object.keys(targetObject).forEach(key => {
|
|
18
|
-
this.reduceValue(targetObject[key], key, (_value, _pathNotation, suggestedKeys = undefined) => {
|
|
19
|
-
if (suggestedKeys) return suggestedKeys;
|
|
20
|
-
q.push(`${_pathNotation}=${encodeURIComponent(_value)}`);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
return q.join(delim);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Get value by path notation
|
|
27
|
-
static get(targetObject, pathNotation) {
|
|
28
|
-
return this.reducePath(pathNotation, targetObject, (key, _targetObject) => {
|
|
29
|
-
if (!_targetObject && _targetObject !== 0) return;
|
|
30
|
-
return _targetObject[key];
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Set value by path notation
|
|
35
|
-
static set(targetObject, pathNotation, value) {
|
|
36
|
-
this.reducePath(pathNotation, targetObject, function(_key, _targetObject, suggestedBranch = undefined) {
|
|
37
|
-
let _value = value;
|
|
38
|
-
if (suggestedBranch) { _value = suggestedBranch; }
|
|
39
|
-
if (_key === '' && Array.isArray(_targetObject)) {
|
|
40
|
-
_targetObject.push(_value);
|
|
41
|
-
} else {
|
|
42
|
-
_targetObject[_key] = _value;
|
|
43
|
-
}
|
|
44
|
-
return _value;
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Resolve a value to its leaf nodes
|
|
49
|
-
static reduceValue(value, contextPath, callback) {
|
|
50
|
-
if (_isTypeObject(value)) {
|
|
51
|
-
let suggestedKeys = Object.keys(value);
|
|
52
|
-
let keys = callback(value, contextPath, suggestedKeys);
|
|
53
|
-
if (Array.isArray(keys)) {
|
|
54
|
-
return keys.forEach(key => {
|
|
55
|
-
this.reduceValue(value[key], contextPath ? `${contextPath}[${key}]` : key, callback);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
callback(value, contextPath);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Resolve a path to its leaf index
|
|
63
|
-
static reducePath(pathNotation, contextObject, callback) {
|
|
64
|
-
if (_isString(pathNotation) && pathNotation.endsWith(']') && _isTypeObject(contextObject)) {
|
|
65
|
-
let [ key, ...rest ] = pathNotation.split('[');
|
|
66
|
-
if (_isNumeric(key)) { key = parseInt(key); }
|
|
67
|
-
rest = rest.join('[').replace(']', '');
|
|
68
|
-
let branch;
|
|
69
|
-
if (key in contextObject) {
|
|
70
|
-
branch = contextObject[key];
|
|
71
|
-
} else {
|
|
72
|
-
let suggestedBranch = rest === '' || _isNumeric(rest.split('[')[0]) ? [] : {};
|
|
73
|
-
branch = callback(key, contextObject, suggestedBranch);
|
|
74
|
-
}
|
|
75
|
-
return this.reducePath(rest, branch, callback);
|
|
76
|
-
}
|
|
77
|
-
if (_isNumeric(pathNotation)) { pathNotation = parseInt(pathNotation); }
|
|
78
|
-
return callback(pathNotation, contextObject);
|
|
79
|
-
}
|
|
80
|
-
}
|