@abloatai/ablo 0.3.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/CHANGELOG.md +208 -0
- package/LICENSE +201 -0
- package/NOTICE +12 -0
- package/README.md +230 -0
- package/dist/BaseSyncedStore.d.ts +709 -0
- package/dist/BaseSyncedStore.js +1843 -0
- package/dist/Database.d.ts +344 -0
- package/dist/Database.js +1259 -0
- package/dist/LazyReferenceCollection.d.ts +181 -0
- package/dist/LazyReferenceCollection.js +460 -0
- package/dist/Model.d.ts +339 -0
- package/dist/Model.js +715 -0
- package/dist/ModelRegistry.d.ts +200 -0
- package/dist/ModelRegistry.js +535 -0
- package/dist/NetworkMonitor.d.ts +27 -0
- package/dist/NetworkMonitor.js +73 -0
- package/dist/ObjectPool.d.ts +202 -0
- package/dist/ObjectPool.js +1106 -0
- package/dist/SyncClient.d.ts +489 -0
- package/dist/SyncClient.js +1555 -0
- package/dist/SyncEngineContext.d.ts +46 -0
- package/dist/SyncEngineContext.js +74 -0
- package/dist/adapters/alwaysOnline.d.ts +16 -0
- package/dist/adapters/alwaysOnline.js +19 -0
- package/dist/adapters/inMemoryStorage.d.ts +30 -0
- package/dist/adapters/inMemoryStorage.js +94 -0
- package/dist/agent/Agent.d.ts +358 -0
- package/dist/agent/Agent.js +500 -0
- package/dist/agent/index.d.ts +115 -0
- package/dist/agent/index.js +128 -0
- package/dist/agent/session.d.ts +90 -0
- package/dist/agent/session.js +156 -0
- package/dist/agent/types.d.ts +73 -0
- package/dist/agent/types.js +10 -0
- package/dist/ai-sdk/coordination-context.d.ts +51 -0
- package/dist/ai-sdk/coordination-context.js +107 -0
- package/dist/ai-sdk/index.d.ts +68 -0
- package/dist/ai-sdk/index.js +68 -0
- package/dist/ai-sdk/intent-broadcast.d.ts +77 -0
- package/dist/ai-sdk/intent-broadcast.js +72 -0
- package/dist/ai-sdk/wrap.d.ts +67 -0
- package/dist/ai-sdk/wrap.js +45 -0
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.js +9 -0
- package/dist/auth/index.d.ts +137 -0
- package/dist/auth/index.js +246 -0
- package/dist/client/Ablo.d.ts +835 -0
- package/dist/client/Ablo.js +1440 -0
- package/dist/client/ApiClient.d.ts +200 -0
- package/dist/client/ApiClient.js +659 -0
- package/dist/client/auth.d.ts +79 -0
- package/dist/client/auth.js +81 -0
- package/dist/client/createInternalComponents.d.ts +44 -0
- package/dist/client/createInternalComponents.js +88 -0
- package/dist/client/createModelProxy.d.ts +152 -0
- package/dist/client/createModelProxy.js +199 -0
- package/dist/client/identity.d.ts +63 -0
- package/dist/client/identity.js +156 -0
- package/dist/client/index.d.ts +36 -0
- package/dist/client/index.js +33 -0
- package/dist/client/persistence.d.ts +7 -0
- package/dist/client/persistence.js +11 -0
- package/dist/client/validateAbloOptions.d.ts +42 -0
- package/dist/client/validateAbloOptions.js +43 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.js +12 -0
- package/dist/context.d.ts +27 -0
- package/dist/context.js +58 -0
- package/dist/core/DatabaseManager.d.ts +108 -0
- package/dist/core/DatabaseManager.js +361 -0
- package/dist/core/QueryProcessor.d.ts +77 -0
- package/dist/core/QueryProcessor.js +262 -0
- package/dist/core/QueryView.d.ts +64 -0
- package/dist/core/QueryView.js +219 -0
- package/dist/core/StoreManager.d.ts +131 -0
- package/dist/core/StoreManager.js +334 -0
- package/dist/core/ViewRegistry.d.ts +20 -0
- package/dist/core/ViewRegistry.js +55 -0
- package/dist/core/index.d.ts +34 -0
- package/dist/core/index.js +59 -0
- package/dist/core/openIDBWithTimeout.d.ts +27 -0
- package/dist/core/openIDBWithTimeout.js +63 -0
- package/dist/core/query-utils.d.ts +37 -0
- package/dist/core/query-utils.js +60 -0
- package/dist/errors.d.ts +235 -0
- package/dist/errors.js +243 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +82 -0
- package/dist/interfaces/headless.d.ts +95 -0
- package/dist/interfaces/headless.js +41 -0
- package/dist/interfaces/index.d.ts +321 -0
- package/dist/interfaces/index.js +8 -0
- package/dist/mutators/RecordingTransaction.d.ts +36 -0
- package/dist/mutators/RecordingTransaction.js +216 -0
- package/dist/mutators/Transaction.d.ts +48 -0
- package/dist/mutators/Transaction.js +64 -0
- package/dist/mutators/UndoManager.d.ts +114 -0
- package/dist/mutators/UndoManager.js +143 -0
- package/dist/mutators/defineMutators.d.ts +55 -0
- package/dist/mutators/defineMutators.js +28 -0
- package/dist/policy/index.d.ts +19 -0
- package/dist/policy/index.js +18 -0
- package/dist/policy/types.d.ts +74 -0
- package/dist/policy/types.js +17 -0
- package/dist/principal.d.ts +44 -0
- package/dist/principal.js +49 -0
- package/dist/query/client.d.ts +43 -0
- package/dist/query/client.js +84 -0
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.js +5 -0
- package/dist/query/types.d.ts +143 -0
- package/dist/query/types.js +36 -0
- package/dist/react/AbloProvider.d.ts +205 -0
- package/dist/react/AbloProvider.js +398 -0
- package/dist/react/ClientSideSuspense.d.ts +36 -0
- package/dist/react/ClientSideSuspense.js +17 -0
- package/dist/react/DefaultFallback.d.ts +24 -0
- package/dist/react/DefaultFallback.js +43 -0
- package/dist/react/SyncGroupProvider.d.ts +19 -0
- package/dist/react/SyncGroupProvider.js +44 -0
- package/dist/react/context.d.ts +161 -0
- package/dist/react/context.js +35 -0
- package/dist/react/index.d.ts +64 -0
- package/dist/react/index.js +73 -0
- package/dist/react/internalContext.d.ts +35 -0
- package/dist/react/internalContext.js +3 -0
- package/dist/react/useAblo.d.ts +72 -0
- package/dist/react/useAblo.js +63 -0
- package/dist/react/useCurrentUserId.d.ts +21 -0
- package/dist/react/useCurrentUserId.js +33 -0
- package/dist/react/useErrorListener.d.ts +20 -0
- package/dist/react/useErrorListener.js +39 -0
- package/dist/react/useIntent.d.ts +29 -0
- package/dist/react/useIntent.js +42 -0
- package/dist/react/useMutate.d.ts +83 -0
- package/dist/react/useMutate.js +122 -0
- package/dist/react/useMutationFailureListener.d.ts +26 -0
- package/dist/react/useMutationFailureListener.js +38 -0
- package/dist/react/useMutators.d.ts +56 -0
- package/dist/react/useMutators.js +66 -0
- package/dist/react/usePresence.d.ts +32 -0
- package/dist/react/usePresence.js +41 -0
- package/dist/react/useQuery.d.ts +123 -0
- package/dist/react/useQuery.js +145 -0
- package/dist/react/useReactive.d.ts +35 -0
- package/dist/react/useReactive.js +111 -0
- package/dist/react/useReader.d.ts +69 -0
- package/dist/react/useReader.js +73 -0
- package/dist/react/useSyncStatus.d.ts +61 -0
- package/dist/react/useSyncStatus.js +76 -0
- package/dist/react/useUndoScope.d.ts +36 -0
- package/dist/react/useUndoScope.js +73 -0
- package/dist/realtime/index.d.ts +10 -0
- package/dist/realtime/index.js +9 -0
- package/dist/schema/field.d.ts +134 -0
- package/dist/schema/field.js +264 -0
- package/dist/schema/index.d.ts +29 -0
- package/dist/schema/index.js +38 -0
- package/dist/schema/model.d.ts +326 -0
- package/dist/schema/model.js +89 -0
- package/dist/schema/queries.d.ts +203 -0
- package/dist/schema/queries.js +145 -0
- package/dist/schema/relation.d.ts +172 -0
- package/dist/schema/relation.js +104 -0
- package/dist/schema/schema.d.ts +259 -0
- package/dist/schema/schema.js +188 -0
- package/dist/schema/sugar.d.ts +129 -0
- package/dist/schema/sugar.js +94 -0
- package/dist/source/index.d.ts +423 -0
- package/dist/source/index.js +320 -0
- package/dist/source/pushQueue.d.ts +112 -0
- package/dist/source/pushQueue.js +249 -0
- package/dist/stores/ObjectStore.d.ts +103 -0
- package/dist/stores/ObjectStore.js +371 -0
- package/dist/stores/ObjectStoreContract.d.ts +39 -0
- package/dist/stores/ObjectStoreContract.js +1 -0
- package/dist/stores/SyncActionStore.d.ts +101 -0
- package/dist/stores/SyncActionStore.js +481 -0
- package/dist/sync/BootstrapHelper.d.ts +127 -0
- package/dist/sync/BootstrapHelper.js +434 -0
- package/dist/sync/ConnectionManager.d.ts +136 -0
- package/dist/sync/ConnectionManager.js +465 -0
- package/dist/sync/HydrationCoordinator.d.ts +137 -0
- package/dist/sync/HydrationCoordinator.js +468 -0
- package/dist/sync/NetworkProbe.d.ts +43 -0
- package/dist/sync/NetworkProbe.js +113 -0
- package/dist/sync/OfflineFlush.d.ts +9 -0
- package/dist/sync/OfflineFlush.js +22 -0
- package/dist/sync/OfflineTransactionStore.d.ts +37 -0
- package/dist/sync/OfflineTransactionStore.js +263 -0
- package/dist/sync/SyncWebSocket.d.ts +663 -0
- package/dist/sync/SyncWebSocket.js +1336 -0
- package/dist/sync/createIntentStream.d.ts +33 -0
- package/dist/sync/createIntentStream.js +243 -0
- package/dist/sync/createPresenceStream.d.ts +46 -0
- package/dist/sync/createPresenceStream.js +192 -0
- package/dist/sync/createSnapshot.d.ts +33 -0
- package/dist/sync/createSnapshot.js +124 -0
- package/dist/sync/participants.d.ts +114 -0
- package/dist/sync/participants.js +336 -0
- package/dist/sync/schemas.d.ts +79 -0
- package/dist/sync/schemas.js +78 -0
- package/dist/testing/fixtures/bootstrap.d.ts +45 -0
- package/dist/testing/fixtures/bootstrap.js +53 -0
- package/dist/testing/fixtures/deltas.d.ts +86 -0
- package/dist/testing/fixtures/deltas.js +139 -0
- package/dist/testing/fixtures/models.d.ts +82 -0
- package/dist/testing/fixtures/models.js +270 -0
- package/dist/testing/helpers/react-wrapper.d.ts +66 -0
- package/dist/testing/helpers/react-wrapper.js +64 -0
- package/dist/testing/helpers/sync-engine-harness.d.ts +55 -0
- package/dist/testing/helpers/sync-engine-harness.js +70 -0
- package/dist/testing/helpers/wait.d.ts +25 -0
- package/dist/testing/helpers/wait.js +44 -0
- package/dist/testing/index.d.ts +21 -0
- package/dist/testing/index.js +32 -0
- package/dist/testing/mocks/MockMutationExecutor.d.ts +65 -0
- package/dist/testing/mocks/MockMutationExecutor.js +139 -0
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +20 -0
- package/dist/testing/mocks/MockNetworkMonitor.js +46 -0
- package/dist/testing/mocks/MockSyncContext.d.ts +64 -0
- package/dist/testing/mocks/MockSyncContext.js +100 -0
- package/dist/testing/mocks/MockSyncStore.d.ts +88 -0
- package/dist/testing/mocks/MockSyncStore.js +171 -0
- package/dist/testing/mocks/MockWebSocket.d.ts +66 -0
- package/dist/testing/mocks/MockWebSocket.js +117 -0
- package/dist/transactions/OptimisticEchoTracker.d.ts +82 -0
- package/dist/transactions/OptimisticEchoTracker.js +104 -0
- package/dist/transactions/TransactionQueue.d.ts +499 -0
- package/dist/transactions/TransactionQueue.js +1895 -0
- package/dist/transactions/index.d.ts +16 -0
- package/dist/transactions/index.js +7 -0
- package/dist/transactions/mutation-error-handler.d.ts +5 -0
- package/dist/transactions/mutation-error-handler.js +39 -0
- package/dist/types/global.d.ts +107 -0
- package/dist/types/global.js +38 -0
- package/dist/types/index.d.ts +241 -0
- package/dist/types/index.js +70 -0
- package/dist/types/streams.d.ts +495 -0
- package/dist/types/streams.js +11 -0
- package/dist/utils/asyncIterator.d.ts +41 -0
- package/dist/utils/asyncIterator.js +142 -0
- package/dist/utils/duration.d.ts +28 -0
- package/dist/utils/duration.js +47 -0
- package/dist/utils/mobx-setup.d.ts +42 -0
- package/dist/utils/mobx-setup.js +381 -0
- package/docs/api-keys.md +24 -0
- package/docs/api.md +230 -0
- package/docs/audit.md +81 -0
- package/docs/capabilities.md +163 -0
- package/docs/client-behavior.md +202 -0
- package/docs/data-sources.md +214 -0
- package/docs/examples/agent-human.md +84 -0
- package/docs/examples/ai-sdk-tool.md +92 -0
- package/docs/examples/existing-python-backend.md +249 -0
- package/docs/examples/nextjs.md +88 -0
- package/docs/examples/server-agent.md +86 -0
- package/docs/guarantees.md +148 -0
- package/docs/index.md +97 -0
- package/docs/integration-guide.md +493 -0
- package/docs/interaction-model.md +140 -0
- package/docs/mcp/claude-code.md +43 -0
- package/docs/mcp/cursor.md +53 -0
- package/docs/mcp/windsurf.md +46 -0
- package/docs/mcp.md +59 -0
- package/docs/quickstart.md +152 -0
- package/docs/react.md +115 -0
- package/docs/roadmap.md +45 -0
- package/examples/README.md +54 -0
- package/examples/data-source/README.md +102 -0
- package/examples/data-source/ablo-driver.ts +89 -0
- package/examples/data-source/customer-server.ts +208 -0
- package/examples/data-source/run.ts +101 -0
- package/examples/data-source/schema.ts +25 -0
- package/examples/quickstart.ts +54 -0
- package/examples/tsconfig.json +16 -0
- package/llms.txt +143 -0
- package/package.json +147 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React test helpers for the sync engine SDK.
|
|
3
|
+
*
|
|
4
|
+
* These helpers wire the SDK's SyncProvider into @testing-library/react
|
|
5
|
+
* so consumers can test components and hooks that use useModel/useModels/useMutations.
|
|
6
|
+
*/
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import { type SyncStoreContract } from '../../react/context.js';
|
|
9
|
+
import { MockSyncStore, createMockSyncStore } from '../mocks/MockSyncStore.js';
|
|
10
|
+
export interface TestWrapperOptions {
|
|
11
|
+
/** Mock sync store. If omitted, a new MockSyncStore is created. */
|
|
12
|
+
store?: SyncStoreContract;
|
|
13
|
+
/** Organization ID. Default: "test-org-id". */
|
|
14
|
+
organizationId?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create a wrapper component for @testing-library/react's renderHook/render.
|
|
18
|
+
* Wraps children in the SDK's SyncProvider with a mock store.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* import { renderHook } from '@testing-library/react';
|
|
22
|
+
* import { createReactTestWrapper, createMockSyncStore } from '@ablo/sync-engine/testing';
|
|
23
|
+
*
|
|
24
|
+
* const mockStore = createMockSyncStore();
|
|
25
|
+
* mockStore.setModels(Task, [task1, task2]);
|
|
26
|
+
*
|
|
27
|
+
* const { result } = renderHook(
|
|
28
|
+
* () => useModels(Task),
|
|
29
|
+
* { wrapper: createReactTestWrapper({ store: mockStore }) }
|
|
30
|
+
* );
|
|
31
|
+
*/
|
|
32
|
+
export declare function createReactTestWrapper(options?: TestWrapperOptions): React.FC<{
|
|
33
|
+
children: React.ReactNode;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Drop-in replacement for @testing-library/react's `renderHook` that
|
|
37
|
+
* automatically provides the SDK's SyncProvider with a mock store.
|
|
38
|
+
*
|
|
39
|
+
* Note: This helper lazy-loads @testing-library/react to avoid forcing
|
|
40
|
+
* consumers without React tests to install it.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* import { renderSyncHook, createMockSyncStore } from '@ablo/sync-engine/testing';
|
|
44
|
+
*
|
|
45
|
+
* const mockStore = createMockSyncStore();
|
|
46
|
+
* mockStore.addModel(Task, myTask);
|
|
47
|
+
*
|
|
48
|
+
* const { result } = renderSyncHook(
|
|
49
|
+
* () => useModel(Task, myTask.id),
|
|
50
|
+
* { store: mockStore }
|
|
51
|
+
* );
|
|
52
|
+
* expect(result.current?.id).toBe(myTask.id);
|
|
53
|
+
*/
|
|
54
|
+
export declare function renderSyncHook<TProps, TResult>(callback: (props: TProps) => TResult, options?: TestWrapperOptions & {
|
|
55
|
+
initialProps?: TProps;
|
|
56
|
+
}): {
|
|
57
|
+
result: {
|
|
58
|
+
current: TResult;
|
|
59
|
+
};
|
|
60
|
+
rerender: (props?: TProps) => void;
|
|
61
|
+
unmount: () => void;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Re-export MockSyncStore for convenience.
|
|
65
|
+
*/
|
|
66
|
+
export { MockSyncStore, createMockSyncStore };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React test helpers for the sync engine SDK.
|
|
3
|
+
*
|
|
4
|
+
* These helpers wire the SDK's SyncProvider into @testing-library/react
|
|
5
|
+
* so consumers can test components and hooks that use useModel/useModels/useMutations.
|
|
6
|
+
*/
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import { SyncProvider } from '../../react/context.js';
|
|
9
|
+
import { MockSyncStore, createMockSyncStore } from '../mocks/MockSyncStore.js';
|
|
10
|
+
/**
|
|
11
|
+
* Create a wrapper component for @testing-library/react's renderHook/render.
|
|
12
|
+
* Wraps children in the SDK's SyncProvider with a mock store.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* import { renderHook } from '@testing-library/react';
|
|
16
|
+
* import { createReactTestWrapper, createMockSyncStore } from '@ablo/sync-engine/testing';
|
|
17
|
+
*
|
|
18
|
+
* const mockStore = createMockSyncStore();
|
|
19
|
+
* mockStore.setModels(Task, [task1, task2]);
|
|
20
|
+
*
|
|
21
|
+
* const { result } = renderHook(
|
|
22
|
+
* () => useModels(Task),
|
|
23
|
+
* { wrapper: createReactTestWrapper({ store: mockStore }) }
|
|
24
|
+
* );
|
|
25
|
+
*/
|
|
26
|
+
export function createReactTestWrapper(options = {}) {
|
|
27
|
+
const store = options.store ?? createMockSyncStore();
|
|
28
|
+
const organizationId = options.organizationId ?? 'test-org-id';
|
|
29
|
+
const Wrapper = ({ children }) => React.createElement(SyncProvider, { store, organizationId }, children);
|
|
30
|
+
return Wrapper;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Drop-in replacement for @testing-library/react's `renderHook` that
|
|
34
|
+
* automatically provides the SDK's SyncProvider with a mock store.
|
|
35
|
+
*
|
|
36
|
+
* Note: This helper lazy-loads @testing-library/react to avoid forcing
|
|
37
|
+
* consumers without React tests to install it.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* import { renderSyncHook, createMockSyncStore } from '@ablo/sync-engine/testing';
|
|
41
|
+
*
|
|
42
|
+
* const mockStore = createMockSyncStore();
|
|
43
|
+
* mockStore.addModel(Task, myTask);
|
|
44
|
+
*
|
|
45
|
+
* const { result } = renderSyncHook(
|
|
46
|
+
* () => useModel(Task, myTask.id),
|
|
47
|
+
* { store: mockStore }
|
|
48
|
+
* );
|
|
49
|
+
* expect(result.current?.id).toBe(myTask.id);
|
|
50
|
+
*/
|
|
51
|
+
export function renderSyncHook(callback, options = {}) {
|
|
52
|
+
// Lazy-load @testing-library/react so the SDK doesn't force consumers
|
|
53
|
+
// to install it unless they actually use these helpers.
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
55
|
+
const rtl = require('@testing-library/react');
|
|
56
|
+
return rtl.renderHook(callback, {
|
|
57
|
+
wrapper: createReactTestWrapper(options),
|
|
58
|
+
initialProps: options.initialProps,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Re-export MockSyncStore for convenience.
|
|
63
|
+
*/
|
|
64
|
+
export { MockSyncStore, createMockSyncStore };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full sync engine test harness.
|
|
3
|
+
*
|
|
4
|
+
* Creates a complete stack (ModelRegistry, ObjectPool, TransactionQueue, etc.)
|
|
5
|
+
* with real implementations backed by mocked I/O for integration tests.
|
|
6
|
+
*/
|
|
7
|
+
import { ModelRegistry } from '../../ModelRegistry.js';
|
|
8
|
+
import { ObjectPool } from '../../ObjectPool.js';
|
|
9
|
+
import { MockMutationExecutor } from '../mocks/MockMutationExecutor.js';
|
|
10
|
+
import { MockNetworkMonitor } from '../mocks/MockNetworkMonitor.js';
|
|
11
|
+
import { MockWebSocket } from '../mocks/MockWebSocket.js';
|
|
12
|
+
import { MockMutationDispatcher } from '../mocks/MockSyncContext.js';
|
|
13
|
+
import type { TestContextResult } from '../mocks/MockSyncContext.js';
|
|
14
|
+
export interface TestHarness {
|
|
15
|
+
/** Pre-registered ModelRegistry with test models */
|
|
16
|
+
registry: ModelRegistry;
|
|
17
|
+
/** Real ObjectPool with FK indexes configured */
|
|
18
|
+
pool: ObjectPool;
|
|
19
|
+
/** Mock WebSocket for delta injection */
|
|
20
|
+
webSocket: MockWebSocket;
|
|
21
|
+
/** DI context with all mocks */
|
|
22
|
+
context: TestContextResult;
|
|
23
|
+
/** Shorthand: mock mutation executor */
|
|
24
|
+
mutationExecutor: MockMutationExecutor;
|
|
25
|
+
/** Shorthand: mock network monitor */
|
|
26
|
+
networkMonitor: MockNetworkMonitor;
|
|
27
|
+
/** Shorthand: mock mutation dispatcher */
|
|
28
|
+
mutationDispatcher: MockMutationDispatcher;
|
|
29
|
+
/** Cleanup everything */
|
|
30
|
+
cleanup: () => void;
|
|
31
|
+
}
|
|
32
|
+
export interface TestHarnessOptions {
|
|
33
|
+
/** Start offline (default: false) */
|
|
34
|
+
startOffline?: boolean;
|
|
35
|
+
/** Initial sync ID for mutation executor */
|
|
36
|
+
initialSyncId?: number;
|
|
37
|
+
/** ObjectPool config overrides */
|
|
38
|
+
poolConfig?: {
|
|
39
|
+
maxSize?: number;
|
|
40
|
+
maxAge?: number;
|
|
41
|
+
gcInterval?: number;
|
|
42
|
+
useWeakRefs?: boolean;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a full test harness with real sync engine components + mocked I/O.
|
|
47
|
+
*
|
|
48
|
+
* Usage:
|
|
49
|
+
* ```ts
|
|
50
|
+
* let harness: TestHarness;
|
|
51
|
+
* beforeEach(() => { harness = createTestHarness(); });
|
|
52
|
+
* afterEach(() => { harness.cleanup(); });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function createTestHarness(options?: TestHarnessOptions): TestHarness;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full sync engine test harness.
|
|
3
|
+
*
|
|
4
|
+
* Creates a complete stack (ModelRegistry, ObjectPool, TransactionQueue, etc.)
|
|
5
|
+
* with real implementations backed by mocked I/O for integration tests.
|
|
6
|
+
*/
|
|
7
|
+
import { ModelRegistry, setActiveRegistry } from '../../ModelRegistry.js';
|
|
8
|
+
import { ObjectPool } from '../../ObjectPool.js';
|
|
9
|
+
import { MockWebSocket } from '../mocks/MockWebSocket.js';
|
|
10
|
+
import { createTestContext } from '../mocks/MockSyncContext.js';
|
|
11
|
+
import { registerTestModels, createTestConfig, resetFixtureCounter, } from '../fixtures/models.js';
|
|
12
|
+
import { resetDeltaCounter } from '../fixtures/deltas.js';
|
|
13
|
+
/**
|
|
14
|
+
* Create a full test harness with real sync engine components + mocked I/O.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* ```ts
|
|
18
|
+
* let harness: TestHarness;
|
|
19
|
+
* beforeEach(() => { harness = createTestHarness(); });
|
|
20
|
+
* afterEach(() => { harness.cleanup(); });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function createTestHarness(options = {}) {
|
|
24
|
+
// Reset counters for deterministic tests
|
|
25
|
+
resetFixtureCounter();
|
|
26
|
+
resetDeltaCounter();
|
|
27
|
+
// Create and register test models
|
|
28
|
+
const registry = new ModelRegistry();
|
|
29
|
+
setActiveRegistry(registry);
|
|
30
|
+
registerTestModels(registry);
|
|
31
|
+
// Create DI context with test config
|
|
32
|
+
const testConfig = createTestConfig();
|
|
33
|
+
const context = createTestContext({
|
|
34
|
+
config: testConfig,
|
|
35
|
+
startOffline: options.startOffline,
|
|
36
|
+
mutationExecutorOptions: {
|
|
37
|
+
initialSyncId: options.initialSyncId ?? 1,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
// Create real ObjectPool with FK indexes
|
|
41
|
+
const pool = new ObjectPool({
|
|
42
|
+
maxSize: options.poolConfig?.maxSize ?? 10000,
|
|
43
|
+
maxAge: options.poolConfig?.maxAge ?? 5 * 60 * 1000,
|
|
44
|
+
gcInterval: options.poolConfig?.gcInterval ?? 0, // Disable auto-GC in tests
|
|
45
|
+
useWeakRefs: options.poolConfig?.useWeakRefs ?? false, // Disable WeakRefs for predictable tests
|
|
46
|
+
}, registry);
|
|
47
|
+
// Register FK indexes for test models
|
|
48
|
+
pool.registerForeignKey('Task', 'projectId');
|
|
49
|
+
pool.registerForeignKey('Comment', 'taskId');
|
|
50
|
+
pool.registerForeignKey('Slide', 'deckId');
|
|
51
|
+
pool.registerForeignKey('SlideLayer', 'slideId');
|
|
52
|
+
// Create mock WebSocket
|
|
53
|
+
const webSocket = new MockWebSocket();
|
|
54
|
+
return {
|
|
55
|
+
registry,
|
|
56
|
+
pool,
|
|
57
|
+
webSocket,
|
|
58
|
+
context,
|
|
59
|
+
mutationExecutor: context.mocks.mutationExecutor,
|
|
60
|
+
networkMonitor: context.mocks.networkMonitor,
|
|
61
|
+
mutationDispatcher: context.mocks.mutationDispatcher,
|
|
62
|
+
cleanup: () => {
|
|
63
|
+
pool.clear();
|
|
64
|
+
webSocket.reset();
|
|
65
|
+
context.cleanup();
|
|
66
|
+
resetFixtureCounter();
|
|
67
|
+
resetDeltaCounter();
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async test helpers for timing-sensitive sync engine tests.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Flush all pending microtasks (Promise.resolve, queueMicrotask).
|
|
6
|
+
* Critical for testing TransactionQueue's microtask batching.
|
|
7
|
+
*/
|
|
8
|
+
export declare function flushMicrotasks(): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Wait for a condition to become true, polling at intervals.
|
|
11
|
+
* Times out after maxWait ms.
|
|
12
|
+
*/
|
|
13
|
+
export declare function waitFor(condition: () => boolean, options?: {
|
|
14
|
+
maxWait?: number;
|
|
15
|
+
interval?: number;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Wait for N milliseconds. Use sparingly — prefer flushMicrotasks() or waitFor().
|
|
19
|
+
*/
|
|
20
|
+
export declare function delay(ms: number): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Run a callback after flushing microtasks.
|
|
23
|
+
* Useful for asserting state after TransactionQueue batch processing.
|
|
24
|
+
*/
|
|
25
|
+
export declare function afterMicrotasks<T>(fn: () => T): Promise<T>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async test helpers for timing-sensitive sync engine tests.
|
|
3
|
+
*/
|
|
4
|
+
import { AbloConnectionError } from '../../errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Flush all pending microtasks (Promise.resolve, queueMicrotask).
|
|
7
|
+
* Critical for testing TransactionQueue's microtask batching.
|
|
8
|
+
*/
|
|
9
|
+
export function flushMicrotasks() {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
// Use setTimeout(0) to yield to the microtask queue
|
|
12
|
+
setTimeout(resolve, 0);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Wait for a condition to become true, polling at intervals.
|
|
17
|
+
* Times out after maxWait ms.
|
|
18
|
+
*/
|
|
19
|
+
export async function waitFor(condition, options = {}) {
|
|
20
|
+
const { maxWait = 5000, interval = 10 } = options;
|
|
21
|
+
const start = Date.now();
|
|
22
|
+
while (!condition()) {
|
|
23
|
+
if (Date.now() - start > maxWait) {
|
|
24
|
+
throw new AbloConnectionError(`waitFor timed out after ${maxWait}ms`, {
|
|
25
|
+
code: 'wait_for_timeout',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Wait for N milliseconds. Use sparingly — prefer flushMicrotasks() or waitFor().
|
|
33
|
+
*/
|
|
34
|
+
export function delay(ms) {
|
|
35
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Run a callback after flushing microtasks.
|
|
39
|
+
* Useful for asserting state after TransactionQueue batch processing.
|
|
40
|
+
*/
|
|
41
|
+
export async function afterMicrotasks(fn) {
|
|
42
|
+
await flushMicrotasks();
|
|
43
|
+
return fn();
|
|
44
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ablo/sync-engine/testing — Public test utilities for SDK consumers.
|
|
3
|
+
*
|
|
4
|
+
* Provides mock implementations, fixture factories, and test harnesses
|
|
5
|
+
* for writing integration tests against the sync engine.
|
|
6
|
+
*/
|
|
7
|
+
export { MockMutationExecutor } from './mocks/MockMutationExecutor.js';
|
|
8
|
+
export type { CapturedMutation, MockMutationExecutorOptions } from './mocks/MockMutationExecutor.js';
|
|
9
|
+
export { MockNetworkMonitor } from './mocks/MockNetworkMonitor.js';
|
|
10
|
+
export { MockWebSocket } from './mocks/MockWebSocket.js';
|
|
11
|
+
export type { MockDelta, MockBootstrapHint } from './mocks/MockWebSocket.js';
|
|
12
|
+
export { createTestContext, MockMutationDispatcher, } from './mocks/MockSyncContext.js';
|
|
13
|
+
export type { TestContextOptions, TestContextResult } from './mocks/MockSyncContext.js';
|
|
14
|
+
export { TestProject, TestTask, TestComment, TestSlideDeck, TestSlide, TestSlideLayer, TEST_MODEL_PRIORITIES, registerTestModels, createTestConfig, resetFixtureCounter, createProjectFixture, createTaskFixture, createCommentFixture, createSlideDeckFixture, createSlideFixture, createSlideLayerFixture, } from './fixtures/models.js';
|
|
15
|
+
export { createDelta, createInsertDelta, createUpdateDelta, createDeleteDelta, createArchiveDelta, createUnarchiveDelta, createCoveringDelta, createGroupAddedDelta, createLegacyGroupChangeDelta, createGroupRemovedDelta, createDeltaBatch, createConfirmationDelta, resetDeltaCounter, } from './fixtures/deltas.js';
|
|
16
|
+
export { createFullBootstrapResponse, createPartialBootstrapResponse, createTestBootstrapResponse, } from './fixtures/bootstrap.js';
|
|
17
|
+
export type { BootstrapModelData, BootstrapResponse } from './fixtures/bootstrap.js';
|
|
18
|
+
export { createTestHarness, } from './helpers/sync-engine-harness.js';
|
|
19
|
+
export type { TestHarness, TestHarnessOptions } from './helpers/sync-engine-harness.js';
|
|
20
|
+
export { flushMicrotasks, waitFor, delay, afterMicrotasks, } from './helpers/wait.js';
|
|
21
|
+
export { createReactTestWrapper, renderSyncHook, MockSyncStore, createMockSyncStore, type TestWrapperOptions, } from './helpers/react-wrapper.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ablo/sync-engine/testing — Public test utilities for SDK consumers.
|
|
3
|
+
*
|
|
4
|
+
* Provides mock implementations, fixture factories, and test harnesses
|
|
5
|
+
* for writing integration tests against the sync engine.
|
|
6
|
+
*/
|
|
7
|
+
// ─────────────────────────────────────────────
|
|
8
|
+
// Mocks
|
|
9
|
+
// ─────────────────────────────────────────────
|
|
10
|
+
export { MockMutationExecutor } from './mocks/MockMutationExecutor.js';
|
|
11
|
+
export { MockNetworkMonitor } from './mocks/MockNetworkMonitor.js';
|
|
12
|
+
export { MockWebSocket } from './mocks/MockWebSocket.js';
|
|
13
|
+
export { createTestContext, MockMutationDispatcher, } from './mocks/MockSyncContext.js';
|
|
14
|
+
// ─────────────────────────────────────────────
|
|
15
|
+
// Fixtures: Models
|
|
16
|
+
// ─────────────────────────────────────────────
|
|
17
|
+
export { TestProject, TestTask, TestComment, TestSlideDeck, TestSlide, TestSlideLayer, TEST_MODEL_PRIORITIES, registerTestModels, createTestConfig, resetFixtureCounter, createProjectFixture, createTaskFixture, createCommentFixture, createSlideDeckFixture, createSlideFixture, createSlideLayerFixture, } from './fixtures/models.js';
|
|
18
|
+
// ─────────────────────────────────────────────
|
|
19
|
+
// Fixtures: Deltas
|
|
20
|
+
// ─────────────────────────────────────────────
|
|
21
|
+
export { createDelta, createInsertDelta, createUpdateDelta, createDeleteDelta, createArchiveDelta, createUnarchiveDelta, createCoveringDelta, createGroupAddedDelta, createLegacyGroupChangeDelta, createGroupRemovedDelta, createDeltaBatch, createConfirmationDelta, resetDeltaCounter, } from './fixtures/deltas.js';
|
|
22
|
+
// ─────────────────────────────────────────────
|
|
23
|
+
// Fixtures: Bootstrap
|
|
24
|
+
// ─────────────────────────────────────────────
|
|
25
|
+
export { createFullBootstrapResponse, createPartialBootstrapResponse, createTestBootstrapResponse, } from './fixtures/bootstrap.js';
|
|
26
|
+
// ─────────────────────────────────────────────
|
|
27
|
+
// Helpers
|
|
28
|
+
// ─────────────────────────────────────────────
|
|
29
|
+
export { createTestHarness, } from './helpers/sync-engine-harness.js';
|
|
30
|
+
export { flushMicrotasks, waitFor, delay, afterMicrotasks, } from './helpers/wait.js';
|
|
31
|
+
// React testing helpers
|
|
32
|
+
export { createReactTestWrapper, renderSyncHook, MockSyncStore, createMockSyncStore, } from './helpers/react-wrapper.js';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MockMutationExecutor — Test double for the MutationExecutor interface.
|
|
3
|
+
*
|
|
4
|
+
* Captures all mutation calls, allows controlled responses (success/failure/latency),
|
|
5
|
+
* and returns configurable lastSyncId for delta confirmation testing.
|
|
6
|
+
*/
|
|
7
|
+
import type { MutationExecutor, MutationOperation, MutationOptions, CommitResult } from '../../interfaces/index.js';
|
|
8
|
+
export interface CapturedMutation {
|
|
9
|
+
method: string;
|
|
10
|
+
modelName?: string;
|
|
11
|
+
modelId?: string;
|
|
12
|
+
input?: Record<string, unknown>;
|
|
13
|
+
operations?: MutationOperation[];
|
|
14
|
+
options?: MutationOptions;
|
|
15
|
+
clientMutationId?: string;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
}
|
|
18
|
+
export interface MockMutationExecutorOptions {
|
|
19
|
+
/** Starting lastSyncId — increments by 1 per commit call */
|
|
20
|
+
initialSyncId?: number;
|
|
21
|
+
/** Whether mutations should succeed by default */
|
|
22
|
+
shouldSucceed?: boolean;
|
|
23
|
+
/** Simulated network latency in ms */
|
|
24
|
+
latencyMs?: number;
|
|
25
|
+
}
|
|
26
|
+
export declare class MockMutationExecutor implements MutationExecutor {
|
|
27
|
+
/** All captured mutation calls in order */
|
|
28
|
+
readonly calls: CapturedMutation[];
|
|
29
|
+
/** Current sync ID — incremented on each successful commit */
|
|
30
|
+
private _syncId;
|
|
31
|
+
private _shouldSucceed;
|
|
32
|
+
private _latencyMs;
|
|
33
|
+
/** Per-method failure overrides: method name → error */
|
|
34
|
+
private _failureOverrides;
|
|
35
|
+
/** Per-method response overrides */
|
|
36
|
+
private _responseOverrides;
|
|
37
|
+
constructor(options?: MockMutationExecutorOptions);
|
|
38
|
+
/** Get current sync ID without incrementing */
|
|
39
|
+
get currentSyncId(): number;
|
|
40
|
+
/** Set the next sync ID to return */
|
|
41
|
+
setSyncId(id: number): void;
|
|
42
|
+
/** Make all mutations fail with given error */
|
|
43
|
+
failAll(error?: Error): void;
|
|
44
|
+
/** Make all mutations succeed again */
|
|
45
|
+
succeedAll(): void;
|
|
46
|
+
/** Make a specific method fail */
|
|
47
|
+
failMethod(method: string, error?: Error): void;
|
|
48
|
+
/** Clear failure override for a method */
|
|
49
|
+
clearFailure(method: string): void;
|
|
50
|
+
/** Get calls filtered by method */
|
|
51
|
+
getCallsByMethod(method: string): CapturedMutation[];
|
|
52
|
+
/** Get the last call made */
|
|
53
|
+
get lastCall(): CapturedMutation | undefined;
|
|
54
|
+
/** Reset all state */
|
|
55
|
+
reset(options?: MockMutationExecutorOptions): void;
|
|
56
|
+
commit(operations: MutationOperation[], options?: MutationOptions): Promise<CommitResult>;
|
|
57
|
+
executeCreate(modelName: string, id: string, input: Record<string, unknown>, clientMutationId?: string): Promise<void>;
|
|
58
|
+
executeUpdate(modelName: string, modelId: string, data: Record<string, unknown>, clientMutationId?: string): Promise<CommitResult | null>;
|
|
59
|
+
executeDelete(modelName: string, modelId: string, clientMutationId?: string): Promise<void>;
|
|
60
|
+
executeArchive(modelName: string, modelId: string, clientMutationId?: string): Promise<void>;
|
|
61
|
+
executeUnarchive(modelName: string, modelId: string, clientMutationId?: string): Promise<void>;
|
|
62
|
+
private _capture;
|
|
63
|
+
private _maybeDelay;
|
|
64
|
+
private _maybeThrow;
|
|
65
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MockMutationExecutor — Test double for the MutationExecutor interface.
|
|
3
|
+
*
|
|
4
|
+
* Captures all mutation calls, allows controlled responses (success/failure/latency),
|
|
5
|
+
* and returns configurable lastSyncId for delta confirmation testing.
|
|
6
|
+
*/
|
|
7
|
+
import { AbloError } from '../../errors.js';
|
|
8
|
+
export class MockMutationExecutor {
|
|
9
|
+
/** All captured mutation calls in order */
|
|
10
|
+
calls = [];
|
|
11
|
+
/** Current sync ID — incremented on each successful commit */
|
|
12
|
+
_syncId;
|
|
13
|
+
_shouldSucceed;
|
|
14
|
+
_latencyMs;
|
|
15
|
+
/** Per-method failure overrides: method name → error */
|
|
16
|
+
_failureOverrides = new Map();
|
|
17
|
+
/** Per-method response overrides */
|
|
18
|
+
_responseOverrides = new Map();
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this._syncId = options.initialSyncId ?? 1;
|
|
21
|
+
this._shouldSucceed = options.shouldSucceed ?? true;
|
|
22
|
+
this._latencyMs = options.latencyMs ?? 0;
|
|
23
|
+
}
|
|
24
|
+
// ─────────────────────────────────────────────
|
|
25
|
+
// Test control API
|
|
26
|
+
// ─────────────────────────────────────────────
|
|
27
|
+
/** Get current sync ID without incrementing */
|
|
28
|
+
get currentSyncId() {
|
|
29
|
+
return this._syncId;
|
|
30
|
+
}
|
|
31
|
+
/** Set the next sync ID to return */
|
|
32
|
+
setSyncId(id) {
|
|
33
|
+
this._syncId = id;
|
|
34
|
+
}
|
|
35
|
+
/** Make all mutations fail with given error */
|
|
36
|
+
failAll(error) {
|
|
37
|
+
this._shouldSucceed = false;
|
|
38
|
+
if (error) {
|
|
39
|
+
this._failureOverrides.set('*', error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Make all mutations succeed again */
|
|
43
|
+
succeedAll() {
|
|
44
|
+
this._shouldSucceed = true;
|
|
45
|
+
this._failureOverrides.clear();
|
|
46
|
+
}
|
|
47
|
+
/** Make a specific method fail */
|
|
48
|
+
failMethod(method, error) {
|
|
49
|
+
this._failureOverrides.set(method, error ?? new Error(`Mock ${method} failed`));
|
|
50
|
+
}
|
|
51
|
+
/** Clear failure override for a method */
|
|
52
|
+
clearFailure(method) {
|
|
53
|
+
this._failureOverrides.delete(method);
|
|
54
|
+
}
|
|
55
|
+
/** Get calls filtered by method */
|
|
56
|
+
getCallsByMethod(method) {
|
|
57
|
+
return this.calls.filter((c) => c.method === method);
|
|
58
|
+
}
|
|
59
|
+
/** Get the last call made */
|
|
60
|
+
get lastCall() {
|
|
61
|
+
return this.calls[this.calls.length - 1];
|
|
62
|
+
}
|
|
63
|
+
/** Reset all state */
|
|
64
|
+
reset(options) {
|
|
65
|
+
this.calls.length = 0;
|
|
66
|
+
this._syncId = options?.initialSyncId ?? 1;
|
|
67
|
+
this._shouldSucceed = options?.shouldSucceed ?? true;
|
|
68
|
+
this._latencyMs = options?.latencyMs ?? 0;
|
|
69
|
+
this._failureOverrides.clear();
|
|
70
|
+
this._responseOverrides.clear();
|
|
71
|
+
}
|
|
72
|
+
// ─────────────────────────────────────────────
|
|
73
|
+
// MutationExecutor interface implementation
|
|
74
|
+
// ─────────────────────────────────────────────
|
|
75
|
+
async commit(operations, options) {
|
|
76
|
+
this._capture('commit', { operations, options });
|
|
77
|
+
await this._maybeDelay();
|
|
78
|
+
this._maybeThrow('commit');
|
|
79
|
+
const syncId = this._syncId++;
|
|
80
|
+
return { lastSyncId: syncId };
|
|
81
|
+
}
|
|
82
|
+
async executeCreate(modelName, id, input, clientMutationId) {
|
|
83
|
+
this._capture('executeCreate', { modelName, modelId: id, input, clientMutationId });
|
|
84
|
+
await this._maybeDelay();
|
|
85
|
+
this._maybeThrow('executeCreate');
|
|
86
|
+
}
|
|
87
|
+
async executeUpdate(modelName, modelId, data, clientMutationId) {
|
|
88
|
+
this._capture('executeUpdate', { modelName, modelId, input: data, clientMutationId });
|
|
89
|
+
await this._maybeDelay();
|
|
90
|
+
this._maybeThrow('executeUpdate');
|
|
91
|
+
return { lastSyncId: this._syncId++ };
|
|
92
|
+
}
|
|
93
|
+
async executeDelete(modelName, modelId, clientMutationId) {
|
|
94
|
+
this._capture('executeDelete', { modelName, modelId, clientMutationId });
|
|
95
|
+
await this._maybeDelay();
|
|
96
|
+
this._maybeThrow('executeDelete');
|
|
97
|
+
}
|
|
98
|
+
async executeArchive(modelName, modelId, clientMutationId) {
|
|
99
|
+
this._capture('executeArchive', { modelName, modelId, clientMutationId });
|
|
100
|
+
await this._maybeDelay();
|
|
101
|
+
this._maybeThrow('executeArchive');
|
|
102
|
+
}
|
|
103
|
+
async executeUnarchive(modelName, modelId, clientMutationId) {
|
|
104
|
+
this._capture('executeUnarchive', { modelName, modelId, clientMutationId });
|
|
105
|
+
await this._maybeDelay();
|
|
106
|
+
this._maybeThrow('executeUnarchive');
|
|
107
|
+
}
|
|
108
|
+
// ─────────────────────────────────────────────
|
|
109
|
+
// Internal helpers
|
|
110
|
+
// ─────────────────────────────────────────────
|
|
111
|
+
_capture(method, data) {
|
|
112
|
+
this.calls.push({
|
|
113
|
+
method,
|
|
114
|
+
timestamp: Date.now(),
|
|
115
|
+
...data,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async _maybeDelay() {
|
|
119
|
+
if (this._latencyMs > 0) {
|
|
120
|
+
await new Promise((r) => setTimeout(r, this._latencyMs));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
_maybeThrow(method) {
|
|
124
|
+
// Check specific method override first
|
|
125
|
+
const methodError = this._failureOverrides.get(method);
|
|
126
|
+
if (methodError)
|
|
127
|
+
throw methodError;
|
|
128
|
+
// Check global override
|
|
129
|
+
const globalError = this._failureOverrides.get('*');
|
|
130
|
+
if (globalError)
|
|
131
|
+
throw globalError;
|
|
132
|
+
// Check global flag
|
|
133
|
+
if (!this._shouldSucceed) {
|
|
134
|
+
throw new AbloError(`Mock mutation failed: ${method}`, {
|
|
135
|
+
code: 'mock_mutation_failed',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MockNetworkMonitor — Test double for OnlineStatusProvider.
|
|
3
|
+
*
|
|
4
|
+
* Allows tests to programmatically toggle online/offline state
|
|
5
|
+
* and trigger visibility change events.
|
|
6
|
+
*/
|
|
7
|
+
import type { OnlineStatusProvider } from '../../interfaces/index.js';
|
|
8
|
+
export declare class MockNetworkMonitor implements OnlineStatusProvider {
|
|
9
|
+
private _online;
|
|
10
|
+
constructor(initialOnline?: boolean);
|
|
11
|
+
isOnline(): boolean;
|
|
12
|
+
/** Simulate going online */
|
|
13
|
+
goOnline(): void;
|
|
14
|
+
/** Simulate going offline */
|
|
15
|
+
goOffline(): void;
|
|
16
|
+
/** Toggle online state and return new value */
|
|
17
|
+
toggle(): boolean;
|
|
18
|
+
/** Reset to initial state (online) */
|
|
19
|
+
reset(): void;
|
|
20
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MockNetworkMonitor — Test double for OnlineStatusProvider.
|
|
3
|
+
*
|
|
4
|
+
* Allows tests to programmatically toggle online/offline state
|
|
5
|
+
* and trigger visibility change events.
|
|
6
|
+
*/
|
|
7
|
+
export class MockNetworkMonitor {
|
|
8
|
+
_online;
|
|
9
|
+
constructor(initialOnline = true) {
|
|
10
|
+
this._online = initialOnline;
|
|
11
|
+
}
|
|
12
|
+
isOnline() {
|
|
13
|
+
return this._online;
|
|
14
|
+
}
|
|
15
|
+
/** Simulate going online */
|
|
16
|
+
goOnline() {
|
|
17
|
+
this._online = true;
|
|
18
|
+
// Also update navigator.onLine for code that reads it directly
|
|
19
|
+
Object.defineProperty(navigator, 'onLine', {
|
|
20
|
+
writable: true,
|
|
21
|
+
value: true,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/** Simulate going offline */
|
|
25
|
+
goOffline() {
|
|
26
|
+
this._online = false;
|
|
27
|
+
Object.defineProperty(navigator, 'onLine', {
|
|
28
|
+
writable: true,
|
|
29
|
+
value: false,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Toggle online state and return new value */
|
|
33
|
+
toggle() {
|
|
34
|
+
if (this._online) {
|
|
35
|
+
this.goOffline();
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
this.goOnline();
|
|
39
|
+
}
|
|
40
|
+
return this._online;
|
|
41
|
+
}
|
|
42
|
+
/** Reset to initial state (online) */
|
|
43
|
+
reset() {
|
|
44
|
+
this.goOnline();
|
|
45
|
+
}
|
|
46
|
+
}
|