@luvio/adapter-test-library 0.137.4 → 0.138.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.
@@ -0,0 +1,35 @@
1
+ import type { AdapterFactory, NetworkAdapter } from '@luvio/engine';
2
+ import { Luvio } from '@luvio/engine';
3
+ import type { MockPayload } from './network';
4
+ export declare function buildLuvioInstance(mockNetworkAdapter: NetworkAdapter): Luvio;
5
+ declare type AdapterRoundtripTestParams<C, D> = AdapterNetworkRoundtripTestParams<C, D> | AdapterNetworkRoundtripWithLuvioTestParams<C, D> | AdapterCacheHitRoundtripTestParams<C, D>;
6
+ declare type AdapterTest<C, D> = {
7
+ adapterFactory: AdapterFactory<C, D>;
8
+ adapterConfig: C;
9
+ privateProperties?: string[];
10
+ };
11
+ declare type AdapterTestWithInjectedLuvio<C, D> = AdapterTest<C, D> & {
12
+ luvio: Luvio;
13
+ expectedData: D;
14
+ };
15
+ declare type NetworkConfig = {
16
+ request: MockPayload['networkArgs'];
17
+ response: any;
18
+ };
19
+ declare type AdapterNetworkRoundtripTestParams<C, D> = AdapterTest<C, D> & {
20
+ type: 'network';
21
+ network: NetworkConfig;
22
+ expectedData?: D;
23
+ };
24
+ declare type AdapterNetworkRoundtripWithLuvioTestParams<C, D> = AdapterTestWithInjectedLuvio<C, D> & {
25
+ type: 'injectedLuvioNetwork';
26
+ };
27
+ declare type AdapterCacheHitRoundtripTestParams<C, D> = AdapterTestWithInjectedLuvio<C, D> & {
28
+ type: 'cache';
29
+ };
30
+ export declare function testAdapterCompletesRoundtrip<C = any, D = any>(params: AdapterRoundtripTestParams<C, D>): Promise<void>;
31
+ export declare function buildLuvioWithNetwork(networkConfig: NetworkConfig): {
32
+ luvio: Luvio;
33
+ networkAdapter: NetworkAdapter;
34
+ };
35
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { FetchResponse } from '@luvio/engine';
2
+ export interface LuvioCustomMatchers<R> {
3
+ toEqualFulfilledSnapshotWithData: (expected: any, privateProperties?: string[]) => R;
4
+ toEqualStaleSnapshotWithData: (expected: any, privateProperties?: string[]) => R;
5
+ toEqualErrorSnapshot: (expectedStatus?: number) => R;
6
+ toEqualOfflineErrorSnapshot: () => R;
7
+ toHaveBeenHitTimes: (expected: Number) => R;
8
+ toHaveBeenHitOnce: () => R;
9
+ toEqualFetchResponse: (expected: FetchResponse<unknown>) => R;
10
+ }
@@ -0,0 +1 @@
1
+ export declare function setupCustomLuvioMatchers(): void;
@@ -1,6 +1,9 @@
1
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
- export { stripProperties } from './utils';
4
+ export { stripProperties, customMatchers } from './utils';
5
+ export { setupCustomLuvioMatchers } from './jest.setup';
5
6
  export { MockDurableStore } from './MockDurableStore';
6
7
  export { MemoryDurableStorePersistence, DurableStorePersistence } from './durableStorePersistence';
8
+ export { testAdapterCompletesRoundtrip, buildLuvioWithNetwork, buildLuvioInstance, } from './adapterRoundtrip';
9
+ export { LuvioCustomMatchers } from './customMatchers';
@@ -1,4 +1,52 @@
1
1
  import sinon from 'sinon';
2
+ import { InMemoryStore, Environment, Luvio } from '@luvio/engine';
3
+
4
+ function isImmutable(value) {
5
+ return !doesThrow(() => {
6
+ // if verifyImmutable does not throw then the object is immutable
7
+ verifyImmutable(value, '$');
8
+ });
9
+ }
10
+ function verifyImmutable(value, path) {
11
+ if (typeof value !== 'object' || value === null) {
12
+ return;
13
+ }
14
+ if (Array.isArray(value)) {
15
+ if (doesThrow(() => {
16
+ const len = value.length;
17
+ value.push('__test');
18
+ if (len === value.length) {
19
+ throw new Error('IE11 does not throw when mutating a frozen object');
20
+ }
21
+ }) === false) {
22
+ throw new Error(`Unexpected mutable property found at ${path}: Array is extensible!`);
23
+ }
24
+ value.forEach((item, index) => {
25
+ verifyImmutable(item, `${path}.${index}`);
26
+ });
27
+ return;
28
+ }
29
+ if (doesThrow(() => {
30
+ value['__test_____'] = true;
31
+ if (value['__test_____'] !== true) {
32
+ throw new Error('IE11 does not throw when mutating a frozen object');
33
+ }
34
+ }) === false) {
35
+ throw new Error(`Unexpected mutable property found at ${path}: Object is extensible!`);
36
+ }
37
+ Object.keys(value).forEach((key) => {
38
+ if (doesThrow(() => {
39
+ const old = value[key];
40
+ value[key] = '_______foo';
41
+ if (value[key] === old) {
42
+ throw new Error('IE11 does not throw when mutating a frozen object');
43
+ }
44
+ }) === false) {
45
+ throw new Error(`Unexpected mutable property found at ${path}: "${path}.${key}" is mutable!`);
46
+ }
47
+ verifyImmutable(value[key], `${path}.${key}`);
48
+ });
49
+ }
2
50
 
3
51
  /**
4
52
  * Clone an object
@@ -51,7 +99,108 @@ function doesThrow(predicate) {
51
99
  }
52
100
  function flushPromises() {
53
101
  return new Promise((resolve) => setTimeout(resolve, 0));
54
- }
102
+ }
103
+ // Copied from engine to avoid build time dependency
104
+ // BEGIN OF COPY BLOCK
105
+ var SnapshotState;
106
+ (function (SnapshotState) {
107
+ SnapshotState["Fulfilled"] = "Fulfilled";
108
+ SnapshotState["Unfulfilled"] = "Unfulfilled";
109
+ SnapshotState["Error"] = "Error";
110
+ SnapshotState["Pending"] = "Pending";
111
+ SnapshotState["Stale"] = "Stale";
112
+ })(SnapshotState || (SnapshotState = {}));
113
+ function isErrorSnapshot(snapshot) {
114
+ return snapshot.state === SnapshotState.Error;
115
+ }
116
+ function isFulfilledSnapshot(snapshot) {
117
+ return snapshot.state === SnapshotState.Fulfilled;
118
+ }
119
+ function isStaleSnapshot(snapshot) {
120
+ return snapshot.state === SnapshotState.Stale;
121
+ }
122
+ // END OF COPY BLOCK
123
+ const customMatchers = {
124
+ toEqualFulfilledSnapshotWithData: (snapshot, expected, privateProperties) => {
125
+ if (isFulfilledSnapshot(snapshot)) {
126
+ const expectedWithoutPrivateProperties = stripProperties(expected, privateProperties || []);
127
+ expect(snapshot.data).toEqual(expectedWithoutPrivateProperties);
128
+ expect(isImmutable(snapshot.data)).toBe(true);
129
+ return { pass: true, message: () => 'Snapshot is a FulfilledSnapshot' };
130
+ }
131
+ return {
132
+ pass: false,
133
+ message: () => 'Actual Snapshot is not a FulfilledSnapshot.',
134
+ };
135
+ },
136
+ toEqualStaleSnapshotWithData: (snapshot, expected, privateProperties) => {
137
+ if (isStaleSnapshot(snapshot)) {
138
+ const expectedWithoutPrivateProperties = stripProperties(expected, privateProperties || []);
139
+ expect(snapshot.data).toEqual(expectedWithoutPrivateProperties);
140
+ expect(isImmutable(snapshot.data)).toBe(true);
141
+ return { pass: true, message: () => 'Snapshot is a StaleSnapshot' };
142
+ }
143
+ return {
144
+ pass: false,
145
+ message: () => 'Actual Snapshot is not a StaleSnapshot.',
146
+ };
147
+ },
148
+ toEqualErrorSnapshot: (actual, expectedStatus) => {
149
+ if (isErrorSnapshot(actual)) {
150
+ expect(isImmutable(actual.error)).toBe(true);
151
+ expect(actual.data).toBeUndefined();
152
+ if (expectedStatus !== undefined) {
153
+ expect(actual.error.status).toBe(expectedStatus);
154
+ }
155
+ return { pass: true, message: () => 'Snapshot is an ErrorSnapshot' };
156
+ }
157
+ return {
158
+ pass: false,
159
+ message: () => 'Actual Snapshot is not a ErrorSnapshot.',
160
+ };
161
+ },
162
+ toEqualOfflineErrorSnapshot: (actual) => {
163
+ if (isErrorSnapshot(actual)) {
164
+ expect(isImmutable(actual.error)).toBe(true);
165
+ expect(actual.data).toBeUndefined();
166
+ expect(actual.error).toBeInstanceOf(Error);
167
+ expect(actual.error.errorType).toBe('networkAdapterError');
168
+ return {
169
+ pass: true,
170
+ message: () => 'Snapshot is an ErrorSnapshot',
171
+ };
172
+ }
173
+ return {
174
+ pass: false,
175
+ message: () => 'Actual Snapshot is not a ErrorSnapshot.',
176
+ };
177
+ },
178
+ toHaveBeenHitTimes: (mockNetworkAdapter, expected) => {
179
+ const callCount = getMockNetworkAdapterCallCount(mockNetworkAdapter);
180
+ if (callCount !== expected) {
181
+ return {
182
+ pass: false,
183
+ message: () => `Number of network calls made are different. Actual ${callCount}, Expected ${expected}.`,
184
+ };
185
+ }
186
+ return { pass: true, message: () => `Network calls were made ${expected} times.` };
187
+ },
188
+ toHaveBeenHitOnce: (mockNetworkAdapter) => {
189
+ const callCount = getMockNetworkAdapterCallCount(mockNetworkAdapter);
190
+ if (callCount !== 1) {
191
+ return {
192
+ pass: false,
193
+ message: () => `Number of network calls was not 1. Actual ${callCount}.`,
194
+ };
195
+ }
196
+ return { pass: true, message: () => 'Number of network calls was 1' };
197
+ },
198
+ toEqualFetchResponse: (actual, expected) => {
199
+ expect(actual).toEqual({ ...expected, errorType: 'fetchResponse' });
200
+ expect(isImmutable(actual)).toBe(true);
201
+ return { pass: true, message: () => 'Actual response equals expected response' };
202
+ },
203
+ };
55
204
 
56
205
  const networkConnectivityStateMap = new WeakMap();
57
206
  var ConnectivityState;
@@ -213,53 +362,6 @@ function buildErrorMockPayload(networkArgs, body, errorStatusCode, errorStatusTe
213
362
  return mockPayload;
214
363
  }
215
364
 
216
- function isImmutable(value) {
217
- return !doesThrow(() => {
218
- // if verifyImmutable does not throw then the object is immutable
219
- verifyImmutable(value, '$');
220
- });
221
- }
222
- function verifyImmutable(value, path) {
223
- if (typeof value !== 'object' || value === null) {
224
- return;
225
- }
226
- if (Array.isArray(value)) {
227
- if (doesThrow(() => {
228
- const len = value.length;
229
- value.push('__test');
230
- if (len === value.length) {
231
- throw new Error('IE11 does not throw when mutating a frozen object');
232
- }
233
- }) === false) {
234
- throw new Error(`Unexpected mutable property found at ${path}: Array is extensible!`);
235
- }
236
- value.forEach((item, index) => {
237
- verifyImmutable(item, `${path}.${index}`);
238
- });
239
- return;
240
- }
241
- if (doesThrow(() => {
242
- value['__test_____'] = true;
243
- if (value['__test_____'] !== true) {
244
- throw new Error('IE11 does not throw when mutating a frozen object');
245
- }
246
- }) === false) {
247
- throw new Error(`Unexpected mutable property found at ${path}: Object is extensible!`);
248
- }
249
- Object.keys(value).forEach((key) => {
250
- if (doesThrow(() => {
251
- const old = value[key];
252
- value[key] = '_______foo';
253
- if (value[key] === old) {
254
- throw new Error('IE11 does not throw when mutating a frozen object');
255
- }
256
- }) === false) {
257
- throw new Error(`Unexpected mutable property found at ${path}: "${path}.${key}" is mutable!`);
258
- }
259
- verifyImmutable(value[key], `${path}.${key}`);
260
- });
261
- }
262
-
263
365
  const mockFulfilledSnapshot = { state: 'Fulfilled' };
264
366
  const mockFulfilledSnapshotPromise = {
265
367
  then() {
@@ -287,6 +389,10 @@ function getMockFulfilledSnapshot() {
287
389
  return mockFulfilledSnapshot;
288
390
  }
289
391
 
392
+ function setupCustomLuvioMatchers() {
393
+ expect.extend(customMatchers);
394
+ }
395
+
290
396
  class MemoryDurableStorePersistence {
291
397
  constructor() {
292
398
  this.store = {};
@@ -418,4 +524,49 @@ class MockDurableStore {
418
524
  }
419
525
  }
420
526
 
421
- export { ConnectivityState, MemoryDurableStorePersistence, MockDurableStore, buildErrorMockPayload, buildFetchResponse, buildMockNetworkAdapter, buildSuccessMockPayload, flushPendingNetworkRequests, getMockFulfilledSnapshot, getMockLuvioWithFulfilledSnapshot, getMockNetworkAdapterCallCount, isImmutable, overrideMockNetworkResponses, resetMockNetworkAdapter, setMockNetworkPayloads, setNetworkConnectivity, stripProperties, verifyImmutable };
527
+ // Use this util if you want to configure multiple network mocks in your test code.
528
+ // If you're only mocking one, we suggest you use buildLuvioWithNetwork instead.
529
+ function buildLuvioInstance(mockNetworkAdapter) {
530
+ const store = new InMemoryStore();
531
+ const env = new Environment(store, mockNetworkAdapter);
532
+ return new Luvio(env);
533
+ }
534
+ async function testAdapterCompletesRoundtrip(params) {
535
+ const adapterSetup = setupAdapter(params);
536
+ const result = await callAdapter(adapterSetup, params);
537
+ expect(result.data).toBeDefined();
538
+ expect(result).toEqualFulfilledSnapshotWithData(params.type === 'network'
539
+ ? params.expectedData || params.network.response
540
+ : params.expectedData, params.privateProperties);
541
+ }
542
+ function setupAdapter(params) {
543
+ const luvio = params.type === 'network' ? buildLuvioWithNetwork(params.network).luvio : params.luvio;
544
+ return params.adapterFactory(luvio);
545
+ }
546
+ // Returns a tuple of the Luvio instance and network adapter so the test author may expect a number expected calls to the network.
547
+ function buildLuvioWithNetwork(networkConfig) {
548
+ // TODO: handle alternative expected network calls, errors and etc
549
+ const expectedNetwork = [
550
+ buildSuccessMockPayload(networkConfig.request, networkConfig.response),
551
+ ];
552
+ const mockNetworkAdapter = buildMockNetworkAdapter(expectedNetwork);
553
+ return { luvio: buildLuvioInstance(mockNetworkAdapter), networkAdapter: mockNetworkAdapter };
554
+ }
555
+ async function callAdapter(adapter, params) {
556
+ const snapshotOrPromise = adapter(params.adapterConfig);
557
+ if (snapshotOrPromise === null) {
558
+ throw new Error('Result of calling the adapter was null. Is the adapter config valid?');
559
+ }
560
+ // Cache Miss - Check that the network call was right.
561
+ if (params.type === 'injectedLuvioNetwork' || params.type === 'network') {
562
+ expect(snapshotOrPromise).toBeInstanceOf(Promise);
563
+ return snapshotOrPromise;
564
+ }
565
+ else {
566
+ // Cache hit
567
+ expect(snapshotOrPromise).not.toBeInstanceOf(Promise); // Expect cache hit to be synchronous
568
+ return Promise.resolve(snapshotOrPromise);
569
+ }
570
+ }
571
+
572
+ export { ConnectivityState, MemoryDurableStorePersistence, MockDurableStore, buildErrorMockPayload, buildFetchResponse, buildLuvioInstance, buildLuvioWithNetwork, buildMockNetworkAdapter, buildSuccessMockPayload, customMatchers, flushPendingNetworkRequests, getMockFulfilledSnapshot, getMockLuvioWithFulfilledSnapshot, getMockNetworkAdapterCallCount, isImmutable, overrideMockNetworkResponses, resetMockNetworkAdapter, setMockNetworkPayloads, setNetworkConnectivity, setupCustomLuvioMatchers, stripProperties, testAdapterCompletesRoundtrip, verifyImmutable };
@@ -1,3 +1,4 @@
1
+ import type { FetchResponse, NetworkAdapter, Snapshot } from '@luvio/engine';
1
2
  /**
2
3
  * Clone an object
3
4
  *
@@ -20,3 +21,17 @@ export declare function stripProperties(obj: {
20
21
  }, props: string[]): any;
21
22
  export declare function doesThrow(predicate: () => void): boolean;
22
23
  export declare function flushPromises(): Promise<unknown>;
24
+ declare type MatcherResult = {
25
+ pass: boolean;
26
+ message: () => string;
27
+ };
28
+ export declare const customMatchers: {
29
+ toEqualFulfilledSnapshotWithData: (snapshot: Snapshot<unknown, unknown>, expected: any, privateProperties?: string[] | undefined) => MatcherResult;
30
+ toEqualStaleSnapshotWithData: (snapshot: Snapshot<unknown, unknown>, expected: any, privateProperties?: string[] | undefined) => MatcherResult;
31
+ toEqualErrorSnapshot: (actual: Snapshot<unknown, unknown>, expectedStatus?: number | undefined) => MatcherResult;
32
+ toEqualOfflineErrorSnapshot: (actual: Snapshot<unknown, unknown>) => MatcherResult;
33
+ toHaveBeenHitTimes: (mockNetworkAdapter: NetworkAdapter, expected: Number) => MatcherResult;
34
+ toHaveBeenHitOnce: (mockNetworkAdapter: NetworkAdapter) => MatcherResult;
35
+ toEqualFetchResponse: (actual: FetchResponse<unknown>, expected: FetchResponse<unknown>) => MatcherResult;
36
+ };
37
+ export {};
@@ -0,0 +1,35 @@
1
+ import type { AdapterFactory, NetworkAdapter } from '@luvio/engine';
2
+ import { Luvio } from '@luvio/engine';
3
+ import type { MockPayload } from './network';
4
+ export declare function buildLuvioInstance(mockNetworkAdapter: NetworkAdapter): Luvio;
5
+ declare type AdapterRoundtripTestParams<C, D> = AdapterNetworkRoundtripTestParams<C, D> | AdapterNetworkRoundtripWithLuvioTestParams<C, D> | AdapterCacheHitRoundtripTestParams<C, D>;
6
+ declare type AdapterTest<C, D> = {
7
+ adapterFactory: AdapterFactory<C, D>;
8
+ adapterConfig: C;
9
+ privateProperties?: string[];
10
+ };
11
+ declare type AdapterTestWithInjectedLuvio<C, D> = AdapterTest<C, D> & {
12
+ luvio: Luvio;
13
+ expectedData: D;
14
+ };
15
+ declare type NetworkConfig = {
16
+ request: MockPayload['networkArgs'];
17
+ response: any;
18
+ };
19
+ declare type AdapterNetworkRoundtripTestParams<C, D> = AdapterTest<C, D> & {
20
+ type: 'network';
21
+ network: NetworkConfig;
22
+ expectedData?: D;
23
+ };
24
+ declare type AdapterNetworkRoundtripWithLuvioTestParams<C, D> = AdapterTestWithInjectedLuvio<C, D> & {
25
+ type: 'injectedLuvioNetwork';
26
+ };
27
+ declare type AdapterCacheHitRoundtripTestParams<C, D> = AdapterTestWithInjectedLuvio<C, D> & {
28
+ type: 'cache';
29
+ };
30
+ export declare function testAdapterCompletesRoundtrip<C = any, D = any>(params: AdapterRoundtripTestParams<C, D>): Promise<void>;
31
+ export declare function buildLuvioWithNetwork(networkConfig: NetworkConfig): {
32
+ luvio: Luvio;
33
+ networkAdapter: NetworkAdapter;
34
+ };
35
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { FetchResponse } from '@luvio/engine';
2
+ export interface LuvioCustomMatchers<R> {
3
+ toEqualFulfilledSnapshotWithData: (expected: any, privateProperties?: string[]) => R;
4
+ toEqualStaleSnapshotWithData: (expected: any, privateProperties?: string[]) => R;
5
+ toEqualErrorSnapshot: (expectedStatus?: number) => R;
6
+ toEqualOfflineErrorSnapshot: () => R;
7
+ toHaveBeenHitTimes: (expected: Number) => R;
8
+ toHaveBeenHitOnce: () => R;
9
+ toEqualFetchResponse: (expected: FetchResponse<unknown>) => R;
10
+ }
@@ -0,0 +1 @@
1
+ export declare function setupCustomLuvioMatchers(): void;
@@ -1,6 +1,9 @@
1
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
- export { stripProperties } from './utils';
4
+ export { stripProperties, customMatchers } from './utils';
5
+ export { setupCustomLuvioMatchers } from './jest.setup';
5
6
  export { MockDurableStore } from './MockDurableStore';
6
7
  export { MemoryDurableStorePersistence, DurableStorePersistence } from './durableStorePersistence';
8
+ export { testAdapterCompletesRoundtrip, buildLuvioWithNetwork, buildLuvioInstance, } from './adapterRoundtrip';
9
+ export { LuvioCustomMatchers } from './customMatchers';