@luvio/adapter-test-library 0.104.0 → 0.106.0
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/babel.config.js +1 -0
- package/dist/es/es2018/{durableStore.d.ts → MockDurableStore.d.ts} +5 -3
- package/dist/es/es2018/durableStorePersistence.d.ts +2 -0
- package/dist/es/es2018/main.d.ts +2 -2
- package/dist/es/es2018/network.d.ts +7 -0
- package/dist/es/es2018/test-library.js +82 -34
- package/dist/es/es2018/utils.d.ts +1 -0
- package/dist/umd/es2018/{durableStore.d.ts → MockDurableStore.d.ts} +5 -3
- package/dist/umd/es2018/durableStorePersistence.d.ts +2 -0
- package/dist/umd/es2018/main.d.ts +2 -2
- package/dist/umd/es2018/network.d.ts +7 -0
- package/dist/umd/es2018/test-library.js +86 -36
- package/dist/umd/es2018/utils.d.ts +1 -0
- package/dist/umd/es5/{durableStore.d.ts → MockDurableStore.d.ts} +5 -3
- package/dist/umd/es5/durableStorePersistence.d.ts +2 -0
- package/dist/umd/es5/main.d.ts +2 -2
- package/dist/umd/es5/network.d.ts +7 -0
- package/dist/umd/es5/test-library.js +234 -101
- package/dist/umd/es5/utils.d.ts +1 -0
- package/jest.config.js +16 -0
- package/package.json +4 -4
- package/rollup.config.js +3 -0
- package/src/MockDurableStore.ts +142 -0
- package/src/__tests__/MockDurableStore.spec.ts +53 -0
- package/src/durableStorePersistence.ts +13 -0
- package/src/main.ts +2 -1
- package/src/network.ts +16 -1
- package/src/utils.ts +4 -0
- package/src/durableStore.ts +0 -108
package/babel.config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('../../../babel.config');
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import type { DurableStore, DurableStoreEntries, OnDurableStoreChangedListener, DurableStoreOperation
|
|
1
|
+
import type { DurableStore, DurableStoreEntries, OnDurableStoreChangedListener, DurableStoreOperation } from '@luvio/environments';
|
|
2
2
|
import type { DurableStorePersistence } from './durableStorePersistence';
|
|
3
3
|
export declare class MockDurableStore implements DurableStore {
|
|
4
|
+
private writePromises;
|
|
4
5
|
listeners: Set<OnDurableStoreChangedListener>;
|
|
5
6
|
persistence: DurableStorePersistence;
|
|
6
7
|
constructor(persistence?: DurableStorePersistence);
|
|
7
8
|
getEntries<T>(entryIds: string[], segment: string): Promise<DurableStoreEntries<T> | undefined>;
|
|
8
|
-
getAllEntries<T>(segment: string): Promise<DurableStoreEntries<T
|
|
9
|
+
getAllEntries<T>(segment: string): Promise<DurableStoreEntries<T> | undefined>;
|
|
9
10
|
setEntries<T>(entries: DurableStoreEntries<T>, segment: string): Promise<void>;
|
|
10
11
|
evictEntries(ids: string[], segment: string): Promise<void>;
|
|
11
12
|
registerOnChangedListener(listener: OnDurableStoreChangedListener): () => Promise<void>;
|
|
12
13
|
batchOperations<T>(operations: DurableStoreOperation<T>[]): Promise<void>;
|
|
13
|
-
performOperation
|
|
14
|
+
private performOperation;
|
|
15
|
+
flushPendingOperations(): Promise<void>;
|
|
14
16
|
}
|
|
@@ -2,10 +2,12 @@ export interface DurableStorePersistence {
|
|
|
2
2
|
get<T>(key: string): Promise<T | undefined>;
|
|
3
3
|
set<T>(key: string, value: T): Promise<void>;
|
|
4
4
|
delete(key: string): Promise<void>;
|
|
5
|
+
flushPendingWork(): Promise<void>;
|
|
5
6
|
}
|
|
6
7
|
export declare class MemoryDurableStorePersistence implements DurableStorePersistence {
|
|
7
8
|
private store;
|
|
8
9
|
get<T>(key: string): Promise<T | undefined>;
|
|
9
10
|
set<T>(key: string, value: T): Promise<void>;
|
|
10
11
|
delete(key: string): Promise<void>;
|
|
12
|
+
flushPendingWork(): Promise<void>;
|
|
11
13
|
}
|
package/dist/es/es2018/main.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { MockPayload, ConnectivityState, buildMockNetworkAdapter, resetMockNetworkAdapter, getMockNetworkAdapterCallCount, buildSuccessMockPayload, buildErrorMockPayload, setMockNetworkPayloads, setNetworkConnectivity, buildFetchResponse, overrideMockNetworkResponses, } from './network';
|
|
1
|
+
export { MockPayload, ConnectivityState, buildMockNetworkAdapter, resetMockNetworkAdapter, getMockNetworkAdapterCallCount, buildSuccessMockPayload, buildErrorMockPayload, setMockNetworkPayloads, setNetworkConnectivity, buildFetchResponse, overrideMockNetworkResponses, flushPendingNetworkRequests, } from './network';
|
|
2
2
|
export { verifyImmutable, isImmutable } from './verification';
|
|
3
3
|
export { getMockLuvioWithFulfilledSnapshot, getMockFulfilledSnapshot } from './mocks';
|
|
4
4
|
export { stripProperties } from './utils';
|
|
5
|
-
export { MockDurableStore } from './
|
|
5
|
+
export { MockDurableStore } from './MockDurableStore';
|
|
6
6
|
export { MemoryDurableStorePersistence, DurableStorePersistence } from './durableStorePersistence';
|
|
@@ -7,6 +7,13 @@ export interface MockPayload {
|
|
|
7
7
|
networkArgs: Partial<ResourceRequest>;
|
|
8
8
|
response: FetchResponse<any>;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Flushes any pending network requests. Useful for tests that need to ensure all
|
|
12
|
+
* un-awaited background refreshes are complete
|
|
13
|
+
*
|
|
14
|
+
* @param _mockNetworkAdapter {NetworkAdapter} The network adapter instance to flush
|
|
15
|
+
*/
|
|
16
|
+
export declare function flushPendingNetworkRequests(_mockNetworkAdapter: NetworkAdapter): Promise<void>;
|
|
10
17
|
export declare function buildMockNetworkAdapter(mockPayloads: MockPayload[]): NetworkAdapter;
|
|
11
18
|
export declare function setNetworkConnectivity(mockNetworkAdapter: NetworkAdapter, connectivityState: ConnectivityState): void;
|
|
12
19
|
export declare function setMockNetworkPayloads(mockNetworkAdapter: NetworkAdapter, mockPayloads: MockPayload[]): void;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import sinon from 'sinon';
|
|
2
|
-
import { DurableStoreOperationType } from '@luvio/environments';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Clone an object
|
|
@@ -49,6 +48,9 @@ function doesThrow(predicate) {
|
|
|
49
48
|
return true;
|
|
50
49
|
}
|
|
51
50
|
return false;
|
|
51
|
+
}
|
|
52
|
+
function flushPromises() {
|
|
53
|
+
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
const networkConnectivityStateMap = new WeakMap();
|
|
@@ -94,6 +96,18 @@ function sortPayloads(a, b) {
|
|
|
94
96
|
function isOkResponse(status) {
|
|
95
97
|
return status >= 200 && status <= 299;
|
|
96
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Flushes any pending network requests. Useful for tests that need to ensure all
|
|
101
|
+
* un-awaited background refreshes are complete
|
|
102
|
+
*
|
|
103
|
+
* @param _mockNetworkAdapter {NetworkAdapter} The network adapter instance to flush
|
|
104
|
+
*/
|
|
105
|
+
async function flushPendingNetworkRequests(_mockNetworkAdapter) {
|
|
106
|
+
// since the network mock is actually synchronous (just returns things wrapped
|
|
107
|
+
// in Promise.resolve/reject) the only thing necessary to flush any pending
|
|
108
|
+
// network activity is to flush the pending microtask queue
|
|
109
|
+
await flushPromises();
|
|
110
|
+
}
|
|
97
111
|
function buildMockNetworkAdapter(mockPayloads) {
|
|
98
112
|
// any endpoints not setup with a fake will return a rejected promise
|
|
99
113
|
const networkAdapter = sinon.stub().rejects(buildMockSetupError());
|
|
@@ -281,53 +295,66 @@ class MemoryDurableStorePersistence {
|
|
|
281
295
|
return this.store[key];
|
|
282
296
|
}
|
|
283
297
|
async set(key, value) {
|
|
298
|
+
// simulate a more realistic durable store by making the write wait a
|
|
299
|
+
// tick before actually setting the value
|
|
300
|
+
await flushPromises();
|
|
284
301
|
this.store[key] = value;
|
|
285
302
|
}
|
|
286
303
|
async delete(key) {
|
|
287
304
|
delete this.store[key];
|
|
288
305
|
}
|
|
306
|
+
async flushPendingWork() {
|
|
307
|
+
// since this implementation does actual "IO" synchronously the only
|
|
308
|
+
// thing necessary to await any pending IO is to flush the current
|
|
309
|
+
// microtask queue
|
|
310
|
+
await flushPromises();
|
|
311
|
+
}
|
|
289
312
|
}
|
|
290
313
|
|
|
314
|
+
function waitForPromiseSet(set) {
|
|
315
|
+
// NOTE: we are building an array from the Set at this point in time. If
|
|
316
|
+
// more Promises are added to the Set while this is awaiting it won't
|
|
317
|
+
// await the newly-added Promise. That's what we want.
|
|
318
|
+
return Promise.all(Array.from(set)).then();
|
|
319
|
+
}
|
|
291
320
|
class MockDurableStore {
|
|
292
321
|
constructor(persistence) {
|
|
293
|
-
//
|
|
322
|
+
// for read/write synchronization
|
|
323
|
+
this.writePromises = new Set();
|
|
294
324
|
this.listeners = new Set();
|
|
295
325
|
this.persistence = persistence || new MemoryDurableStorePersistence();
|
|
296
326
|
}
|
|
297
|
-
getEntries(entryIds, segment) {
|
|
327
|
+
async getEntries(entryIds, segment) {
|
|
328
|
+
// await any current write operations
|
|
329
|
+
if (this.writePromises.size > 0) {
|
|
330
|
+
await waitForPromiseSet(this.writePromises);
|
|
331
|
+
}
|
|
332
|
+
const entries = await this.persistence.get(segment);
|
|
333
|
+
if (entries === undefined) {
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
298
336
|
const returnSource = Object.create(null);
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
337
|
+
for (const entryId of entryIds) {
|
|
338
|
+
const entry = entries[entryId];
|
|
339
|
+
if (entry !== undefined) {
|
|
340
|
+
returnSource[entryId] = clone(entry);
|
|
302
341
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (entry !== undefined) {
|
|
306
|
-
returnSource[entryId] = clone(entry);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return returnSource;
|
|
310
|
-
});
|
|
342
|
+
}
|
|
343
|
+
return returnSource;
|
|
311
344
|
}
|
|
312
|
-
getAllEntries(segment) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return returnSource;
|
|
320
|
-
});
|
|
345
|
+
async getAllEntries(segment) {
|
|
346
|
+
// await any current write operations
|
|
347
|
+
if (this.writePromises.size > 0) {
|
|
348
|
+
await waitForPromiseSet(this.writePromises);
|
|
349
|
+
}
|
|
350
|
+
const entries = await this.persistence.get(segment);
|
|
351
|
+
return entries;
|
|
321
352
|
}
|
|
322
353
|
setEntries(entries, segment) {
|
|
323
|
-
return this.batchOperations([
|
|
324
|
-
{ entries, segment, type: DurableStoreOperationType.SetEntries },
|
|
325
|
-
]);
|
|
354
|
+
return this.batchOperations([{ entries, segment, type: 'setEntries' }]);
|
|
326
355
|
}
|
|
327
356
|
evictEntries(ids, segment) {
|
|
328
|
-
return this.batchOperations([
|
|
329
|
-
{ ids, segment, type: DurableStoreOperationType.EvictEntries },
|
|
330
|
-
]);
|
|
357
|
+
return this.batchOperations([{ ids, segment, type: 'evictEntries' }]);
|
|
331
358
|
}
|
|
332
359
|
registerOnChangedListener(listener) {
|
|
333
360
|
this.listeners.add(listener);
|
|
@@ -337,9 +364,22 @@ class MockDurableStore {
|
|
|
337
364
|
};
|
|
338
365
|
}
|
|
339
366
|
async batchOperations(operations) {
|
|
367
|
+
// await any current write operations
|
|
368
|
+
if (this.writePromises.size > 0) {
|
|
369
|
+
await waitForPromiseSet(this.writePromises);
|
|
370
|
+
}
|
|
340
371
|
const changes = [];
|
|
341
|
-
|
|
342
|
-
|
|
372
|
+
const writePromise = (async () => {
|
|
373
|
+
for (const operation of operations) {
|
|
374
|
+
changes.push(await this.performOperation(operation));
|
|
375
|
+
}
|
|
376
|
+
})();
|
|
377
|
+
this.writePromises.add(writePromise);
|
|
378
|
+
try {
|
|
379
|
+
await writePromise;
|
|
380
|
+
}
|
|
381
|
+
finally {
|
|
382
|
+
this.writePromises.delete(writePromise);
|
|
343
383
|
}
|
|
344
384
|
this.listeners.forEach((listener) => {
|
|
345
385
|
listener(changes);
|
|
@@ -351,13 +391,13 @@ class MockDurableStore {
|
|
|
351
391
|
const entries = rawEntries === undefined ? {} : rawEntries;
|
|
352
392
|
let ids = [];
|
|
353
393
|
switch (operation.type) {
|
|
354
|
-
case
|
|
394
|
+
case 'setEntries':
|
|
355
395
|
ids = Object.keys(operation.entries);
|
|
356
396
|
ids.forEach((id) => {
|
|
357
397
|
entries[id] = clone(operation.entries[id]);
|
|
358
398
|
});
|
|
359
399
|
break;
|
|
360
|
-
case
|
|
400
|
+
case 'evictEntries':
|
|
361
401
|
ids = operation.ids;
|
|
362
402
|
ids.forEach((id) => {
|
|
363
403
|
delete entries[id];
|
|
@@ -366,6 +406,14 @@ class MockDurableStore {
|
|
|
366
406
|
await this.persistence.set(operation.segment, entries);
|
|
367
407
|
return { ids, segment, type: operation.type };
|
|
368
408
|
}
|
|
409
|
+
async flushPendingOperations() {
|
|
410
|
+
// flush any pending read operations
|
|
411
|
+
await this.persistence.flushPendingWork();
|
|
412
|
+
// wait for any pending writes to finish
|
|
413
|
+
while (this.writePromises.size > 0) {
|
|
414
|
+
await waitForPromiseSet(this.writePromises);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
369
417
|
}
|
|
370
418
|
|
|
371
|
-
export { ConnectivityState, MemoryDurableStorePersistence, MockDurableStore, buildErrorMockPayload, buildFetchResponse, buildMockNetworkAdapter, buildSuccessMockPayload, getMockFulfilledSnapshot, getMockLuvioWithFulfilledSnapshot, getMockNetworkAdapterCallCount, isImmutable, overrideMockNetworkResponses, resetMockNetworkAdapter, setMockNetworkPayloads, setNetworkConnectivity, stripProperties, verifyImmutable };
|
|
419
|
+
export { ConnectivityState, MemoryDurableStorePersistence, MockDurableStore, buildErrorMockPayload, buildFetchResponse, buildMockNetworkAdapter, buildSuccessMockPayload, flushPendingNetworkRequests, getMockFulfilledSnapshot, getMockLuvioWithFulfilledSnapshot, getMockNetworkAdapterCallCount, isImmutable, overrideMockNetworkResponses, resetMockNetworkAdapter, setMockNetworkPayloads, setNetworkConnectivity, stripProperties, verifyImmutable };
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import type { DurableStore, DurableStoreEntries, OnDurableStoreChangedListener, DurableStoreOperation
|
|
1
|
+
import type { DurableStore, DurableStoreEntries, OnDurableStoreChangedListener, DurableStoreOperation } from '@luvio/environments';
|
|
2
2
|
import type { DurableStorePersistence } from './durableStorePersistence';
|
|
3
3
|
export declare class MockDurableStore implements DurableStore {
|
|
4
|
+
private writePromises;
|
|
4
5
|
listeners: Set<OnDurableStoreChangedListener>;
|
|
5
6
|
persistence: DurableStorePersistence;
|
|
6
7
|
constructor(persistence?: DurableStorePersistence);
|
|
7
8
|
getEntries<T>(entryIds: string[], segment: string): Promise<DurableStoreEntries<T> | undefined>;
|
|
8
|
-
getAllEntries<T>(segment: string): Promise<DurableStoreEntries<T
|
|
9
|
+
getAllEntries<T>(segment: string): Promise<DurableStoreEntries<T> | undefined>;
|
|
9
10
|
setEntries<T>(entries: DurableStoreEntries<T>, segment: string): Promise<void>;
|
|
10
11
|
evictEntries(ids: string[], segment: string): Promise<void>;
|
|
11
12
|
registerOnChangedListener(listener: OnDurableStoreChangedListener): () => Promise<void>;
|
|
12
13
|
batchOperations<T>(operations: DurableStoreOperation<T>[]): Promise<void>;
|
|
13
|
-
performOperation
|
|
14
|
+
private performOperation;
|
|
15
|
+
flushPendingOperations(): Promise<void>;
|
|
14
16
|
}
|
|
@@ -2,10 +2,12 @@ export interface DurableStorePersistence {
|
|
|
2
2
|
get<T>(key: string): Promise<T | undefined>;
|
|
3
3
|
set<T>(key: string, value: T): Promise<void>;
|
|
4
4
|
delete(key: string): Promise<void>;
|
|
5
|
+
flushPendingWork(): Promise<void>;
|
|
5
6
|
}
|
|
6
7
|
export declare class MemoryDurableStorePersistence implements DurableStorePersistence {
|
|
7
8
|
private store;
|
|
8
9
|
get<T>(key: string): Promise<T | undefined>;
|
|
9
10
|
set<T>(key: string, value: T): Promise<void>;
|
|
10
11
|
delete(key: string): Promise<void>;
|
|
12
|
+
flushPendingWork(): Promise<void>;
|
|
11
13
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { MockPayload, ConnectivityState, buildMockNetworkAdapter, resetMockNetworkAdapter, getMockNetworkAdapterCallCount, buildSuccessMockPayload, buildErrorMockPayload, setMockNetworkPayloads, setNetworkConnectivity, buildFetchResponse, overrideMockNetworkResponses, } from './network';
|
|
1
|
+
export { MockPayload, ConnectivityState, buildMockNetworkAdapter, resetMockNetworkAdapter, getMockNetworkAdapterCallCount, buildSuccessMockPayload, buildErrorMockPayload, setMockNetworkPayloads, setNetworkConnectivity, buildFetchResponse, overrideMockNetworkResponses, flushPendingNetworkRequests, } from './network';
|
|
2
2
|
export { verifyImmutable, isImmutable } from './verification';
|
|
3
3
|
export { getMockLuvioWithFulfilledSnapshot, getMockFulfilledSnapshot } from './mocks';
|
|
4
4
|
export { stripProperties } from './utils';
|
|
5
|
-
export { MockDurableStore } from './
|
|
5
|
+
export { MockDurableStore } from './MockDurableStore';
|
|
6
6
|
export { MemoryDurableStorePersistence, DurableStorePersistence } from './durableStorePersistence';
|
|
@@ -7,6 +7,13 @@ export interface MockPayload {
|
|
|
7
7
|
networkArgs: Partial<ResourceRequest>;
|
|
8
8
|
response: FetchResponse<any>;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Flushes any pending network requests. Useful for tests that need to ensure all
|
|
12
|
+
* un-awaited background refreshes are complete
|
|
13
|
+
*
|
|
14
|
+
* @param _mockNetworkAdapter {NetworkAdapter} The network adapter instance to flush
|
|
15
|
+
*/
|
|
16
|
+
export declare function flushPendingNetworkRequests(_mockNetworkAdapter: NetworkAdapter): Promise<void>;
|
|
10
17
|
export declare function buildMockNetworkAdapter(mockPayloads: MockPayload[]): NetworkAdapter;
|
|
11
18
|
export declare function setNetworkConnectivity(mockNetworkAdapter: NetworkAdapter, connectivityState: ConnectivityState): void;
|
|
12
19
|
export declare function setMockNetworkPayloads(mockNetworkAdapter: NetworkAdapter, mockPayloads: MockPayload[]): void;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('sinon')
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports', 'sinon'
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.luvioAdapterTestLibrary = {}, global.sinon
|
|
5
|
-
})(this, (function (exports, sinon
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('sinon')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', 'sinon'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.luvioAdapterTestLibrary = {}, global.sinon));
|
|
5
|
+
})(this, (function (exports, sinon) { 'use strict';
|
|
6
6
|
|
|
7
7
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
8
8
|
|
|
@@ -56,6 +56,9 @@
|
|
|
56
56
|
return true;
|
|
57
57
|
}
|
|
58
58
|
return false;
|
|
59
|
+
}
|
|
60
|
+
function flushPromises() {
|
|
61
|
+
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
const networkConnectivityStateMap = new WeakMap();
|
|
@@ -101,6 +104,18 @@
|
|
|
101
104
|
function isOkResponse(status) {
|
|
102
105
|
return status >= 200 && status <= 299;
|
|
103
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Flushes any pending network requests. Useful for tests that need to ensure all
|
|
109
|
+
* un-awaited background refreshes are complete
|
|
110
|
+
*
|
|
111
|
+
* @param _mockNetworkAdapter {NetworkAdapter} The network adapter instance to flush
|
|
112
|
+
*/
|
|
113
|
+
async function flushPendingNetworkRequests(_mockNetworkAdapter) {
|
|
114
|
+
// since the network mock is actually synchronous (just returns things wrapped
|
|
115
|
+
// in Promise.resolve/reject) the only thing necessary to flush any pending
|
|
116
|
+
// network activity is to flush the pending microtask queue
|
|
117
|
+
await flushPromises();
|
|
118
|
+
}
|
|
104
119
|
function buildMockNetworkAdapter(mockPayloads) {
|
|
105
120
|
// any endpoints not setup with a fake will return a rejected promise
|
|
106
121
|
const networkAdapter = sinon__default["default"].stub().rejects(buildMockSetupError());
|
|
@@ -288,53 +303,66 @@
|
|
|
288
303
|
return this.store[key];
|
|
289
304
|
}
|
|
290
305
|
async set(key, value) {
|
|
306
|
+
// simulate a more realistic durable store by making the write wait a
|
|
307
|
+
// tick before actually setting the value
|
|
308
|
+
await flushPromises();
|
|
291
309
|
this.store[key] = value;
|
|
292
310
|
}
|
|
293
311
|
async delete(key) {
|
|
294
312
|
delete this.store[key];
|
|
295
313
|
}
|
|
314
|
+
async flushPendingWork() {
|
|
315
|
+
// since this implementation does actual "IO" synchronously the only
|
|
316
|
+
// thing necessary to await any pending IO is to flush the current
|
|
317
|
+
// microtask queue
|
|
318
|
+
await flushPromises();
|
|
319
|
+
}
|
|
296
320
|
}
|
|
297
321
|
|
|
322
|
+
function waitForPromiseSet(set) {
|
|
323
|
+
// NOTE: we are building an array from the Set at this point in time. If
|
|
324
|
+
// more Promises are added to the Set while this is awaiting it won't
|
|
325
|
+
// await the newly-added Promise. That's what we want.
|
|
326
|
+
return Promise.all(Array.from(set)).then();
|
|
327
|
+
}
|
|
298
328
|
class MockDurableStore {
|
|
299
329
|
constructor(persistence) {
|
|
300
|
-
//
|
|
330
|
+
// for read/write synchronization
|
|
331
|
+
this.writePromises = new Set();
|
|
301
332
|
this.listeners = new Set();
|
|
302
333
|
this.persistence = persistence || new MemoryDurableStorePersistence();
|
|
303
334
|
}
|
|
304
|
-
getEntries(entryIds, segment) {
|
|
335
|
+
async getEntries(entryIds, segment) {
|
|
336
|
+
// await any current write operations
|
|
337
|
+
if (this.writePromises.size > 0) {
|
|
338
|
+
await waitForPromiseSet(this.writePromises);
|
|
339
|
+
}
|
|
340
|
+
const entries = await this.persistence.get(segment);
|
|
341
|
+
if (entries === undefined) {
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
305
344
|
const returnSource = Object.create(null);
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
345
|
+
for (const entryId of entryIds) {
|
|
346
|
+
const entry = entries[entryId];
|
|
347
|
+
if (entry !== undefined) {
|
|
348
|
+
returnSource[entryId] = clone(entry);
|
|
309
349
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (entry !== undefined) {
|
|
313
|
-
returnSource[entryId] = clone(entry);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return returnSource;
|
|
317
|
-
});
|
|
350
|
+
}
|
|
351
|
+
return returnSource;
|
|
318
352
|
}
|
|
319
|
-
getAllEntries(segment) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return returnSource;
|
|
327
|
-
});
|
|
353
|
+
async getAllEntries(segment) {
|
|
354
|
+
// await any current write operations
|
|
355
|
+
if (this.writePromises.size > 0) {
|
|
356
|
+
await waitForPromiseSet(this.writePromises);
|
|
357
|
+
}
|
|
358
|
+
const entries = await this.persistence.get(segment);
|
|
359
|
+
return entries;
|
|
328
360
|
}
|
|
329
361
|
setEntries(entries, segment) {
|
|
330
|
-
return this.batchOperations([
|
|
331
|
-
{ entries, segment, type: environments.DurableStoreOperationType.SetEntries },
|
|
332
|
-
]);
|
|
362
|
+
return this.batchOperations([{ entries, segment, type: 'setEntries' }]);
|
|
333
363
|
}
|
|
334
364
|
evictEntries(ids, segment) {
|
|
335
|
-
return this.batchOperations([
|
|
336
|
-
{ ids, segment, type: environments.DurableStoreOperationType.EvictEntries },
|
|
337
|
-
]);
|
|
365
|
+
return this.batchOperations([{ ids, segment, type: 'evictEntries' }]);
|
|
338
366
|
}
|
|
339
367
|
registerOnChangedListener(listener) {
|
|
340
368
|
this.listeners.add(listener);
|
|
@@ -344,9 +372,22 @@
|
|
|
344
372
|
};
|
|
345
373
|
}
|
|
346
374
|
async batchOperations(operations) {
|
|
375
|
+
// await any current write operations
|
|
376
|
+
if (this.writePromises.size > 0) {
|
|
377
|
+
await waitForPromiseSet(this.writePromises);
|
|
378
|
+
}
|
|
347
379
|
const changes = [];
|
|
348
|
-
|
|
349
|
-
|
|
380
|
+
const writePromise = (async () => {
|
|
381
|
+
for (const operation of operations) {
|
|
382
|
+
changes.push(await this.performOperation(operation));
|
|
383
|
+
}
|
|
384
|
+
})();
|
|
385
|
+
this.writePromises.add(writePromise);
|
|
386
|
+
try {
|
|
387
|
+
await writePromise;
|
|
388
|
+
}
|
|
389
|
+
finally {
|
|
390
|
+
this.writePromises.delete(writePromise);
|
|
350
391
|
}
|
|
351
392
|
this.listeners.forEach((listener) => {
|
|
352
393
|
listener(changes);
|
|
@@ -358,13 +399,13 @@
|
|
|
358
399
|
const entries = rawEntries === undefined ? {} : rawEntries;
|
|
359
400
|
let ids = [];
|
|
360
401
|
switch (operation.type) {
|
|
361
|
-
case
|
|
402
|
+
case 'setEntries':
|
|
362
403
|
ids = Object.keys(operation.entries);
|
|
363
404
|
ids.forEach((id) => {
|
|
364
405
|
entries[id] = clone(operation.entries[id]);
|
|
365
406
|
});
|
|
366
407
|
break;
|
|
367
|
-
case
|
|
408
|
+
case 'evictEntries':
|
|
368
409
|
ids = operation.ids;
|
|
369
410
|
ids.forEach((id) => {
|
|
370
411
|
delete entries[id];
|
|
@@ -373,6 +414,14 @@
|
|
|
373
414
|
await this.persistence.set(operation.segment, entries);
|
|
374
415
|
return { ids, segment, type: operation.type };
|
|
375
416
|
}
|
|
417
|
+
async flushPendingOperations() {
|
|
418
|
+
// flush any pending read operations
|
|
419
|
+
await this.persistence.flushPendingWork();
|
|
420
|
+
// wait for any pending writes to finish
|
|
421
|
+
while (this.writePromises.size > 0) {
|
|
422
|
+
await waitForPromiseSet(this.writePromises);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
376
425
|
}
|
|
377
426
|
|
|
378
427
|
exports.MemoryDurableStorePersistence = MemoryDurableStorePersistence;
|
|
@@ -381,6 +430,7 @@
|
|
|
381
430
|
exports.buildFetchResponse = buildFetchResponse;
|
|
382
431
|
exports.buildMockNetworkAdapter = buildMockNetworkAdapter;
|
|
383
432
|
exports.buildSuccessMockPayload = buildSuccessMockPayload;
|
|
433
|
+
exports.flushPendingNetworkRequests = flushPendingNetworkRequests;
|
|
384
434
|
exports.getMockFulfilledSnapshot = getMockFulfilledSnapshot;
|
|
385
435
|
exports.getMockLuvioWithFulfilledSnapshot = getMockLuvioWithFulfilledSnapshot;
|
|
386
436
|
exports.getMockNetworkAdapterCallCount = getMockNetworkAdapterCallCount;
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import type { DurableStore, DurableStoreEntries, OnDurableStoreChangedListener, DurableStoreOperation
|
|
1
|
+
import type { DurableStore, DurableStoreEntries, OnDurableStoreChangedListener, DurableStoreOperation } from '@luvio/environments';
|
|
2
2
|
import type { DurableStorePersistence } from './durableStorePersistence';
|
|
3
3
|
export declare class MockDurableStore implements DurableStore {
|
|
4
|
+
private writePromises;
|
|
4
5
|
listeners: Set<OnDurableStoreChangedListener>;
|
|
5
6
|
persistence: DurableStorePersistence;
|
|
6
7
|
constructor(persistence?: DurableStorePersistence);
|
|
7
8
|
getEntries<T>(entryIds: string[], segment: string): Promise<DurableStoreEntries<T> | undefined>;
|
|
8
|
-
getAllEntries<T>(segment: string): Promise<DurableStoreEntries<T
|
|
9
|
+
getAllEntries<T>(segment: string): Promise<DurableStoreEntries<T> | undefined>;
|
|
9
10
|
setEntries<T>(entries: DurableStoreEntries<T>, segment: string): Promise<void>;
|
|
10
11
|
evictEntries(ids: string[], segment: string): Promise<void>;
|
|
11
12
|
registerOnChangedListener(listener: OnDurableStoreChangedListener): () => Promise<void>;
|
|
12
13
|
batchOperations<T>(operations: DurableStoreOperation<T>[]): Promise<void>;
|
|
13
|
-
performOperation
|
|
14
|
+
private performOperation;
|
|
15
|
+
flushPendingOperations(): Promise<void>;
|
|
14
16
|
}
|
|
@@ -2,10 +2,12 @@ export interface DurableStorePersistence {
|
|
|
2
2
|
get<T>(key: string): Promise<T | undefined>;
|
|
3
3
|
set<T>(key: string, value: T): Promise<void>;
|
|
4
4
|
delete(key: string): Promise<void>;
|
|
5
|
+
flushPendingWork(): Promise<void>;
|
|
5
6
|
}
|
|
6
7
|
export declare class MemoryDurableStorePersistence implements DurableStorePersistence {
|
|
7
8
|
private store;
|
|
8
9
|
get<T>(key: string): Promise<T | undefined>;
|
|
9
10
|
set<T>(key: string, value: T): Promise<void>;
|
|
10
11
|
delete(key: string): Promise<void>;
|
|
12
|
+
flushPendingWork(): Promise<void>;
|
|
11
13
|
}
|
package/dist/umd/es5/main.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { MockPayload, ConnectivityState, buildMockNetworkAdapter, resetMockNetworkAdapter, getMockNetworkAdapterCallCount, buildSuccessMockPayload, buildErrorMockPayload, setMockNetworkPayloads, setNetworkConnectivity, buildFetchResponse, overrideMockNetworkResponses, } from './network';
|
|
1
|
+
export { MockPayload, ConnectivityState, buildMockNetworkAdapter, resetMockNetworkAdapter, getMockNetworkAdapterCallCount, buildSuccessMockPayload, buildErrorMockPayload, setMockNetworkPayloads, setNetworkConnectivity, buildFetchResponse, overrideMockNetworkResponses, flushPendingNetworkRequests, } from './network';
|
|
2
2
|
export { verifyImmutable, isImmutable } from './verification';
|
|
3
3
|
export { getMockLuvioWithFulfilledSnapshot, getMockFulfilledSnapshot } from './mocks';
|
|
4
4
|
export { stripProperties } from './utils';
|
|
5
|
-
export { MockDurableStore } from './
|
|
5
|
+
export { MockDurableStore } from './MockDurableStore';
|
|
6
6
|
export { MemoryDurableStorePersistence, DurableStorePersistence } from './durableStorePersistence';
|
|
@@ -7,6 +7,13 @@ export interface MockPayload {
|
|
|
7
7
|
networkArgs: Partial<ResourceRequest>;
|
|
8
8
|
response: FetchResponse<any>;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Flushes any pending network requests. Useful for tests that need to ensure all
|
|
12
|
+
* un-awaited background refreshes are complete
|
|
13
|
+
*
|
|
14
|
+
* @param _mockNetworkAdapter {NetworkAdapter} The network adapter instance to flush
|
|
15
|
+
*/
|
|
16
|
+
export declare function flushPendingNetworkRequests(_mockNetworkAdapter: NetworkAdapter): Promise<void>;
|
|
10
17
|
export declare function buildMockNetworkAdapter(mockPayloads: MockPayload[]): NetworkAdapter;
|
|
11
18
|
export declare function setNetworkConnectivity(mockNetworkAdapter: NetworkAdapter, connectivityState: ConnectivityState): void;
|
|
12
19
|
export declare function setMockNetworkPayloads(mockNetworkAdapter: NetworkAdapter, mockPayloads: MockPayload[]): void;
|