Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
"eslint-plugin-storybook": "0.9.0",
"eslint-plugin-testing-library": "6.0.2",
"execa": "4.0.2",
"fake-indexeddb": "6.2.3",

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is necessary to mock indexdb properly in a node environment.

"fast-glob": "3.2.7",
"filenamify": "4.2.0",
"glob": "7.1.2",
Expand Down
76 changes: 40 additions & 36 deletions packages/sync/src/test/connect-indexdb.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,64 @@
/**
* External dependencies
*/
import { describe, expect, it, jest, beforeEach } from '@jest/globals';

const mockIndexeddbPersistence = {
destroy: jest.fn(),
};

jest.mock( 'y-indexeddb', () => {
return {
IndexeddbPersistence: jest
.fn()
.mockImplementation( () => mockIndexeddbPersistence ),
};
} );

const mockYDoc = {
clientID: 12345,
meta: new Map(),
getMap: jest.fn(),
transact: jest.fn( ( fn ) => fn() ),
destroy: jest.fn(),
};

jest.mock( 'yjs', () => ( {
Doc: jest.fn().mockImplementation( () => mockYDoc ),
} ) );
import { describe, expect, it, beforeEach, afterEach } from '@jest/globals';
import * as Y from 'yjs';
// Polyfill structuredClone for jsdom environment (required by fake-indexeddb).
// Jest uses jsdom which doesn't include the structuredClone API yet.
// See: https://github.com/dumbmatter/fakeIndexedDB#jsdom-often-used-with-jest
import 'core-js/stable/structured-clone';

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted, this is due to a jsdom limitation.

import 'fake-indexeddb/auto';

/**
* Internal dependencies
*/
import { connectIndexDb } from '../connect-indexdb';

describe( 'connectIndexDb', () => {
let doc;
let provider;

beforeEach( () => {
jest.clearAllMocks();
doc = new Y.Doc();
} );

afterEach( () => {
provider?.destroy();
doc?.destroy();
} );

it( 'creates an IndexeddbPersistence provider correctly', async () => {
const { IndexeddbPersistence } = jest.requireMock( 'y-indexeddb' );
const objectId = '123';
const objectType = 'post';
const doc = mockYDoc;
const result = await connectIndexDb( '123', 'post', doc );
provider = result;

expect( result ).toBeDefined();
expect( typeof result.destroy ).toBe( 'function' );
} );

it( 'destroy method cleans up the provider', async () => {
const result = await connectIndexDb( '789', 'post', doc );
provider = result;

expect( result ).toBeDefined();
expect( typeof result.destroy ).toBe( 'function' );
expect( () => result.destroy() ).not.toThrow();
} );

const result = await connectIndexDb( objectId, objectType, doc );
it( 'handles different object types and IDs correctly', async () => {
const result = await connectIndexDb( '456', 'page', doc );
provider = result;

expect( result ).toBeDefined();
expect( typeof result.destroy ).toBe( 'function' );
expect( IndexeddbPersistence ).toHaveBeenCalledWith( 'post-123', doc );
} );

it( 'destroy method calls provider.destroy', async () => {
const result = await connectIndexDb( '789', 'post', mockYDoc );
it( 'persists data to IndexedDB', async () => {
const result = await connectIndexDb( '123', 'post', doc );
provider = result;

result.destroy();
const ymap = doc.getMap( 'test' );
ymap.set( 'key', 'value' );

expect( mockIndexeddbPersistence.destroy ).toHaveBeenCalled();
expect( ymap.get( 'key' ) ).toBe( 'value' );
} );
} );
204 changes: 75 additions & 129 deletions packages/sync/src/test/create-webrtc-connection.ts
Original file line number Diff line number Diff line change
@@ -1,161 +1,107 @@
/**
* External dependencies
*/
import { describe, expect, it, jest, beforeEach } from '@jest/globals';
import type * as Y from 'yjs';

const mockWebrtcProvider = {
destroy: jest.fn(),
};

jest.mock( '../webrtc-http-stream-signaling', () => {
return {
WebrtcProviderWithHttpSignaling: jest
.fn()
.mockImplementation( () => mockWebrtcProvider ),
};
} );

const mockYDoc = {
clientID: 12345,
meta: new Map(),
getMap: jest.fn(),
transact: jest.fn( ( fn: () => void ) => fn() ),
destroy: jest.fn(),
};

jest.mock( 'yjs', () => ( {
Doc: jest.fn().mockImplementation( () => mockYDoc ),
} ) );
import {
describe,
expect,
it,
jest,
beforeEach,
afterEach,
} from '@jest/globals';
import * as Y from 'yjs';

/**
* Internal dependencies
*/
import {
createWebRTCConnection,
type WebRTCConnectionConfig,
} from '../create-webrtc-connection';
import { createWebRTCConnection } from '../create-webrtc-connection';
import { WebrtcProviderWithHttpSignaling } from '../webrtc-http-stream-signaling';

// Mock the WebRTC provider to avoid network connections in tests
jest.mock( '../webrtc-http-stream-signaling', () => ( {
WebrtcProviderWithHttpSignaling: jest.fn(),
} ) );

describe( 'createWebRTCConnection', () => {
let doc: Y.Doc;
const mockProvider = WebrtcProviderWithHttpSignaling as jest.Mock< any >;

beforeEach( () => {
doc = new Y.Doc();
jest.clearAllMocks();
} );

describe( 'configuration', () => {
it( 'creates a connection function with signaling servers', () => {
const config: WebRTCConnectionConfig = {
signaling: [ 'ws://localhost:4444' ],
};

const connectDoc = createWebRTCConnection( config );

expect( typeof connectDoc ).toBe( 'function' );
} );

it( 'accepts password in configuration', () => {
const config: WebRTCConnectionConfig = {
signaling: [ 'ws://localhost:4444' ],
password: 'test-password',
};

const connectDoc = createWebRTCConnection( config );
afterEach( () => {
doc?.destroy();
} );

expect( typeof connectDoc ).toBe( 'function' );
it( 'creates a connection function', () => {
const connectDoc = createWebRTCConnection( {
signaling: [ 'ws://localhost:4444' ],
} );

it( 'accepts multiple signaling servers', () => {
const config: WebRTCConnectionConfig = {
signaling: [
'ws://localhost:4444',
'ws://localhost:5555',
'wss://example.com/signaling',
],
};

const connectDoc = createWebRTCConnection( config );
expect( typeof connectDoc ).toBe( 'function' );
} );

expect( typeof connectDoc ).toBe( 'function' );
it( 'creates WebrtcProvider with room name in format "objectType-objectId"', async () => {
const connectDoc = createWebRTCConnection( {
signaling: [ 'ws://localhost:4444' ],
} );
} );

describe( 'connection function', () => {
it( 'creates WebrtcProvider with correct room name', async () => {
const { WebrtcProviderWithHttpSignaling } = jest.requireMock(
'../webrtc-http-stream-signaling'
) as {
WebrtcProviderWithHttpSignaling: jest.Mock;
};
await connectDoc( '789', 'post', doc );

const config: WebRTCConnectionConfig = {
expect( mockProvider ).toHaveBeenCalledWith(
'post-789',
doc,
expect.objectContaining( {
signaling: [ 'ws://localhost:4444' ],
};

const connectDoc = createWebRTCConnection( config );
await connectDoc( '123', 'post', mockYDoc as unknown as Y.Doc );

expect( WebrtcProviderWithHttpSignaling ).toHaveBeenCalledWith(
'post-123',
mockYDoc,
expect.objectContaining( {
signaling: [ 'ws://localhost:4444' ],
} )
);
} );
} )
);
} );

it( 'passes password to WebrtcProvider', async () => {
const { WebrtcProviderWithHttpSignaling } = jest.requireMock(
'../webrtc-http-stream-signaling'
) as {
WebrtcProviderWithHttpSignaling: jest.Mock;
};
it( 'passes signaling servers to WebrtcProvider', async () => {
const signaling = [
'ws://localhost:4444',
'ws://localhost:5555',
'wss://example.com/signaling',
];
const connectDoc = createWebRTCConnection( { signaling } );

await connectDoc( '100', 'page', doc );

expect( mockProvider ).toHaveBeenCalledWith(
'page-100',
doc,
expect.objectContaining( { signaling } )
);
} );

const config: WebRTCConnectionConfig = {
signaling: [ 'ws://localhost:4444' ],
password: 'secret-password',
};

const connectDoc = createWebRTCConnection( config );
await connectDoc( '456', 'page', mockYDoc as unknown as Y.Doc );

expect( WebrtcProviderWithHttpSignaling ).toHaveBeenCalledWith(
'page-456',
mockYDoc,
expect.objectContaining( {
signaling: [ 'ws://localhost:4444' ],
password: 'secret-password',
} )
);
it( 'passes password to WebrtcProvider when provided', async () => {
const connectDoc = createWebRTCConnection( {
signaling: [ 'ws://localhost:4444' ],
password: 'test-password',
} );

it( 'returns promise with destroy method', async () => {
const config: WebRTCConnectionConfig = {
signaling: [ 'ws://localhost:4444' ],
};
await connectDoc( '456', 'post', doc );

const connectDoc = createWebRTCConnection( config );
const result = await connectDoc(
'789',
'post',
mockYDoc as unknown as Y.Doc
);
expect( mockProvider ).toHaveBeenCalledWith(
'post-456',
doc,
expect.objectContaining( {
password: 'test-password',
} )
);
} );

expect( result ).toBeDefined();
expect( typeof result.destroy ).toBe( 'function' );
it( 'returns promise with no-op destroy method', async () => {
const connectDoc = createWebRTCConnection( {
signaling: [ 'ws://localhost:4444' ],
} );

it( 'destroy method is a no-op', async () => {
const config: WebRTCConnectionConfig = {
signaling: [ 'ws://localhost:4444' ],
};

const connectDoc = createWebRTCConnection( config );
const result = await connectDoc(
'100',
'post',
mockYDoc as unknown as Y.Doc
);
const result = await connectDoc( '789', 'post', doc );

expect( () => result.destroy() ).not.toThrow();
} );
expect( result ).toBeDefined();
expect( typeof result.destroy ).toBe( 'function' );
expect( () => result.destroy() ).not.toThrow();
} );
} );
Loading
Loading