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
48 changes: 48 additions & 0 deletions test/e2e/specs/editor/collaboration/collaboration-presence.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Internal dependencies
*/
import { test, expect } from './fixtures';

test.describe( 'Collaboration - Presence', () => {
test( 'Collaborator avatars appear when two users are editing', async ( {
collaborationUtils,
requestUtils,
page,
} ) => {
const post = await requestUtils.createPost( {
title: 'Presence Test - Avatars',
status: 'draft',
date_gmt: new Date().toISOString(),
} );
await collaborationUtils.openCollaborativeSession( post.id );

// The collaborator presence button renders when other
// collaborators are present.
await expect(
page.getByRole( 'button', { name: /Collaborators list/ } )
).toBeVisible( { timeout: 10000 } );
} );

test( 'Collaborator name shows in the popover list', async ( {
collaborationUtils,
requestUtils,
page,
} ) => {
const post = await requestUtils.createPost( {
title: 'Presence Test - Name',
status: 'draft',
date_gmt: new Date().toISOString(),
} );
await collaborationUtils.openCollaborativeSession( post.id );

// Wait for the presence button to appear and click to open popover.
const presenceButton = page.getByRole( 'button', {
name: /Collaborators list/,
} );
await expect( presenceButton ).toBeVisible( { timeout: 10000 } );
await presenceButton.click();

// The popover should list the second collaborator by name.
await expect( page.getByText( 'Test Collaborator' ) ).toBeVisible();
} );
} );
149 changes: 149 additions & 0 deletions test/e2e/specs/editor/collaboration/collaboration-sync.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/**
* Internal dependencies
*/
import { test, expect } from './fixtures';

test.describe( 'Collaboration - Sync', () => {
test( 'User A adds a paragraph block, User B sees it', async ( {
collaborationUtils,
requestUtils,
editor,
} ) => {
const post = await requestUtils.createPost( {
title: 'Sync Test - A to B',
status: 'draft',
date_gmt: new Date().toISOString(),
} );
await collaborationUtils.openCollaborativeSession( post.id );

const { editor2 } = collaborationUtils;

// User A inserts a paragraph block.
await editor.insertBlock( {
name: 'core/paragraph',
attributes: { content: 'Hello from User A' },
} );

// User B should see the paragraph after sync propagation.
await expect
.poll( () => editor2.getBlocks(), { timeout: 5000 } )
.toMatchObject( [
{
name: 'core/paragraph',
attributes: { content: 'Hello from User A' },
},
] );
} );

test( 'User B adds a paragraph block, User A sees it', async ( {
collaborationUtils,
requestUtils,
editor,
} ) => {
const post = await requestUtils.createPost( {
title: 'Sync Test - B to A',
status: 'draft',
date_gmt: new Date().toISOString(),
} );
await collaborationUtils.openCollaborativeSession( post.id );

const { page2 } = collaborationUtils;

// User B inserts a paragraph block via the data API.
await page2.evaluate( () => {
const block = window.wp.blocks.createBlock( 'core/paragraph', {
content: 'Hello from User B',
} );
window.wp.data.dispatch( 'core/block-editor' ).insertBlock( block );
} );

// User A should see the paragraph after sync propagation.
await expect
.poll( () => editor.getBlocks(), { timeout: 5000 } )
.toMatchObject( [
{
name: 'core/paragraph',
attributes: { content: 'Hello from User B' },
},
] );
} );

test( 'Both users add blocks simultaneously, both changes appear', async ( {
collaborationUtils,
requestUtils,
editor,
} ) => {
const post = await requestUtils.createPost( {
title: 'Sync Test - Simultaneous',
status: 'draft',
date_gmt: new Date().toISOString(),
} );
await collaborationUtils.openCollaborativeSession( post.id );

const { editor2, page2 } = collaborationUtils;

// Both users insert blocks concurrently.
await Promise.all( [
editor.insertBlock( {
name: 'core/paragraph',
attributes: { content: 'From User A' },
} ),
page2.evaluate( () => {
const block = window.wp.blocks.createBlock( 'core/paragraph', {
content: 'From User B',
} );
window.wp.data
.dispatch( 'core/block-editor' )
.insertBlock( block );
} ),
] );

// Both users should eventually see both blocks.
for ( const ed of [ editor, editor2 ] ) {
await expect( async () => {
const blocks = await ed.getBlocks();
const contents = blocks.map(
( b: { attributes: Record< string, unknown > } ) =>
b.attributes.content
);
expect( contents ).toContain( 'From User A' );
expect( contents ).toContain( 'From User B' );
} ).toPass( { timeout: 5000 } );
}
} );

test( 'Title changes sync between users', async ( {
collaborationUtils,
requestUtils,
page,
} ) => {
const post = await requestUtils.createPost( {
title: 'Sync Test - Title',
status: 'draft',
date_gmt: new Date().toISOString(),
} );
await collaborationUtils.openCollaborativeSession( post.id );

const { page2 } = collaborationUtils;

// User A changes the title.
await page.evaluate( () => {
window.wp.data
.dispatch( 'core/editor' )
.editPost( { title: 'New Title from User A' } );
} );

// User B should see the updated title after sync propagation.
await expect
.poll(
() =>
page2.evaluate( () =>
window.wp.data
.select( 'core/editor' )
.getEditedPostAttribute( 'title' )
),
{ timeout: 5000 }
)
.toBe( 'New Title from User A' );
} );
} );
132 changes: 132 additions & 0 deletions test/e2e/specs/editor/collaboration/collaboration-undo-redo.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* Internal dependencies
*/
import { test, expect } from './fixtures';

test.describe( 'Collaboration - Undo/Redo', () => {
test( 'User A undo only affects their own changes, not User B changes', async ( {
collaborationUtils,
requestUtils,
editor,
page,
} ) => {
const post = await requestUtils.createPost( {
title: 'Undo Test',
status: 'draft',
date_gmt: new Date().toISOString(),
} );
await collaborationUtils.openCollaborativeSession( post.id );

const { editor2, page2 } = collaborationUtils;

// User B adds a block.
await page2.evaluate( () => {
const block = window.wp.blocks.createBlock( 'core/paragraph', {
content: 'From User B',
} );
window.wp.data.dispatch( 'core/block-editor' ).insertBlock( block );
} );

// Wait for User B's block to appear on User A.
await expect
.poll( () => editor.getBlocks(), { timeout: 5000 } )
.toMatchObject( [
{
name: 'core/paragraph',
attributes: { content: 'From User B' },
},
] );

// User A adds their own block.
await editor.insertBlock( {
name: 'core/paragraph',
attributes: { content: 'From User A' },
} );

// Wait for both blocks to appear on User B.
await expect( async () => {
const blocks = await editor2.getBlocks();
const contents = blocks.map(
( b: { attributes: Record< string, unknown > } ) =>
b.attributes.content
);
expect( contents ).toContain( 'From User A' );
expect( contents ).toContain( 'From User B' );
} ).toPass( { timeout: 5000 } );

// User A performs undo via the data API.
await page.evaluate( () => {
window.wp.data.dispatch( 'core/editor' ).undo();
} );

// User A should see only User B's block (their own block is undone).
await expect( async () => {
const blocks = await editor.getBlocks();
const contents = blocks.map(
( b: { attributes: Record< string, unknown > } ) =>
b.attributes.content
);
expect( contents ).not.toContain( 'From User A' );
expect( contents ).toContain( 'From User B' );
} ).toPass( { timeout: 5000 } );

// User B should also see the undo result.
await expect( async () => {
const blocks = await editor2.getBlocks();
const contents = blocks.map(
( b: { attributes: Record< string, unknown > } ) =>
b.attributes.content
);
expect( contents ).not.toContain( 'From User A' );
expect( contents ).toContain( 'From User B' );
} ).toPass( { timeout: 5000 } );
} );

test( 'Redo restores the undone change', async ( {
collaborationUtils,
requestUtils,
editor,
page,
} ) => {
const post = await requestUtils.createPost( {
title: 'Redo Test',
status: 'draft',
date_gmt: new Date().toISOString(),
} );
await collaborationUtils.openCollaborativeSession( post.id );

// User A adds a block.
await editor.insertBlock( {
name: 'core/paragraph',
attributes: { content: 'Undoable content' },
} );

// Verify the block exists.
await expect
.poll( () => editor.getBlocks(), { timeout: 3000 } )
.toHaveLength( 1 );

// Undo via data API.
await page.evaluate( () => {
window.wp.data.dispatch( 'core/editor' ).undo();
} );

await expect
.poll( () => editor.getBlocks(), { timeout: 5000 } )
.toHaveLength( 0 );

// Redo via data API.
await page.evaluate( () => {
window.wp.data.dispatch( 'core/editor' ).redo();
} );

await expect
.poll( () => editor.getBlocks(), { timeout: 5000 } )
.toMatchObject( [
{
name: 'core/paragraph',
attributes: { content: 'Undoable content' },
},
] );
} );
} );
Loading
Loading