Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/zoom out mode hook conflicts controlled #67040

Closed
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
35 changes: 31 additions & 4 deletions packages/block-editor/src/hooks/use-zoom-out.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { useEffect } from '@wordpress/element';
import { useEffect, useRef } from '@wordpress/element';

/**
* Internal dependencies
Expand All @@ -21,16 +21,43 @@ export function useZoomOut( enabled = true ) {
);
const { isZoomOut } = unlock( useSelect( blockEditorStore ) );

const isZoomedOut = isZoomOut();
// If starting from zoom out engaged, do not control zoom level for the user.
const controlZoomLevel = useRef( ! isZoomedOut );

useEffect( () => {
if ( ! enabled ) {
if ( ! enabled || ! controlZoomLevel.current ) {
return;
}

const isAlreadyInZoomOut = isZoomOut();
if ( ! isAlreadyInZoomOut ) {
setZoomLevel( 'auto-scaled' );
}

return () => ! isAlreadyInZoomOut && resetZoomLevel();
return () => {
return (
controlZoomLevel.current &&
! isAlreadyInZoomOut &&
resetZoomLevel()
);
};
}, [ enabled, isZoomOut, resetZoomLevel, setZoomLevel ] );

/**
* This hook tracks if the zoom state was changed manually by the user via clicking
* the zoom out button.
*/
useEffect( () => {
// If the zoom state changed (isZoomOut) and it does not match the requested zoom
// state (zoomOut), then it means the user manually changed the zoom state while
// this hook was mounted, and we should no longer control the zoom state.
if ( isZoomedOut !== enabled ) {
// Turn off all automatic zooming control.
controlZoomLevel.current = false;
}

// Intentionally excluding `enabled` from the dependency array. We want to catch instances where
// the zoom out state changes due to user interaction and not due to the hook.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ isZoomedOut ] );
}
232 changes: 214 additions & 18 deletions test/e2e/specs/site-editor/site-editor-inserter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,39 +21,40 @@ test.describe( 'Site Editor Inserter', () => {
await editor.canvas.locator( 'body' ).click();
} );

test.use( {
InserterUtils: async ( { editor, page }, use ) => {
await use( new InserterUtils( { editor, page } ) );
},
} );

test( 'inserter toggle button should toggle global inserter', async ( {
page,
InserterUtils,
} ) => {
await page.click( 'role=button[name="Block Inserter"i]' );
const inserterButton = InserterUtils.getInserterButton();

await inserterButton.click();

const blockLibrary = InserterUtils.getBlockLibrary();

// Visibility check
await expect(
page.locator( 'role=searchbox[name="Search"i]' )
).toBeVisible();
await page.click( 'role=button[name="Block Inserter"i]' );
await expect( blockLibrary ).toBeVisible();
await inserterButton.click();
//Hidden State check
await expect(
page.locator( 'role=searchbox[name="Search"i]' )
).toBeHidden();
await expect( blockLibrary ).toBeHidden();
} );

// A test for https://github.com/WordPress/gutenberg/issues/43090.
test( 'should close the inserter when clicking on the toggle button', async ( {
page,
editor,
InserterUtils,
} ) => {
const inserterButton = page.getByRole( 'button', {
name: 'Block Inserter',
exact: true,
} );
const blockLibrary = page.getByRole( 'region', {
name: 'Block Library',
} );
const inserterButton = InserterUtils.getInserterButton();
const blockLibrary = InserterUtils.getBlockLibrary();

const beforeBlocks = await editor.getBlocks();

await inserterButton.click();
await blockLibrary.getByRole( 'tab', { name: 'Blocks' } ).click();
await InserterUtils.getBlockLibraryTab( 'Blocks' ).click();
await blockLibrary.getByRole( 'option', { name: 'Buttons' } ).click();

await expect
Expand All @@ -64,4 +65,199 @@ test.describe( 'Site Editor Inserter', () => {

await expect( blockLibrary ).toBeHidden();
} );

test( 'should open the inserter to patterns tab if using zoom out', async ( {
InserterUtils,
} ) => {
const zoomOutButton = InserterUtils.getZoomOutButton();
const inserterButton = InserterUtils.getInserterButton();
const blockLibrary = InserterUtils.getBlockLibrary();

await zoomOutButton.click();
await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();

await inserterButton.click();
const patternsTab = InserterUtils.getBlockLibraryTab( 'Patterns' );
await expect( patternsTab ).toHaveAttribute(
'data-active-item',
'true'
);
Comment on lines +81 to +84
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we use aria-selected? It's a more user perceivable attribute whereas data-* is purely for developers.

I checked to see if there was an isSelected() but there isn't.

You can do this I believe but you'd have to modify your helper utils to accept options.

getByRole('button', {
selected: true,
});

await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: this feels a bit cumbersome. Moreover, this is only ever used for asserting on the active state of Zoom Out rather than retrieving a reference to the Zoom Out canvas itself.

Therefore it might be a clearer to have an API something like this (maybe via a dedicated ZoomOut helper?):

Suggested change
await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();
await expect( await ZoomOutUtils.isActive() );

Not a deal breaker but I think it would improve the readability of the tests.


await inserterButton.click();

await expect( blockLibrary ).toBeHidden();

// We should still be in Zoom Out
await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();
Comment on lines +91 to +92
Copy link
Contributor

Choose a reason for hiding this comment

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

The fact we need this comment is a clue that the API isn't quite right (see other comment).

} );

test( 'should enter zoom out from patterns tab and exit zoom out when closing the inserter', async ( {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
test( 'should enter zoom out from patterns tab and exit zoom out when closing the inserter', async ( {
test( 'should enter zoom out when activating the patterns tab and exit zoom out when closing the inserter', async ( {

InserterUtils,
} ) => {
const inserterButton = InserterUtils.getInserterButton();
const blockLibrary = InserterUtils.getBlockLibrary();

await inserterButton.click();
await expect( await InserterUtils.getZoomCanvas() ).toBeHidden();

const blocksTab = InserterUtils.getBlockLibraryTab( 'Blocks' );
await expect( blocksTab ).toHaveAttribute( 'data-active-item', 'true' );

const patternsTab = InserterUtils.getBlockLibraryTab( 'Patterns' );
await patternsTab.click();
await expect( patternsTab ).toHaveAttribute(
'data-active-item',
'true'
);
await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();

await inserterButton.click();

await expect( blockLibrary ).toBeHidden();

await expect( await InserterUtils.getZoomCanvas() ).toBeHidden();
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
await expect( await InserterUtils.getZoomCanvas() ).toBeHidden();
await expect( await ZoomOut.isInactive() );

Maybe something like this?

} );

test( 'if starting from zoom out, blocks tab should not reset zoom level', async ( {
InserterUtils,
} ) => {
const zoomOutButton = InserterUtils.getZoomOutButton();
const inserterButton = InserterUtils.getInserterButton();
const blockLibrary = InserterUtils.getBlockLibrary();

// Manually enter zoom out
await zoomOutButton.click();
await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();

// Open inserter
await inserterButton.click();

// Patterns tab should be active
const patternsTab = InserterUtils.getBlockLibraryTab( 'Patterns' );
await expect( patternsTab ).toHaveAttribute(
'data-active-item',
'true'
);
// Canvas should be zoomed
await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();

// // Select blocks tab
const blocksTab = InserterUtils.getBlockLibraryTab( 'Blocks' );
await blocksTab.click();
await expect( blocksTab ).toHaveAttribute( 'data-active-item', 'true' );

// Zoom out should still be engaged
await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();

// Close the inserter
await inserterButton.click();
await expect( blockLibrary ).toBeHidden();

// We should stayin zoom out since the inserter was opened with
// zoom out engaged
Comment on lines +157 to +158
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// We should stayin zoom out since the inserter was opened with
// zoom out engaged
// Zoom Out should remain active since the inserter was opened with
// Zoom Out already engaged.

await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();
} );

// Test for https://github.com/WordPress/gutenberg/issues/66328
test( 'should not return you to zoom out if manually disengaged', async ( {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
test( 'should not return you to zoom out if manually disengaged', async ( {
test( 'should not re-activate Zoom Out if manually disengaged', async ( {

InserterUtils,
} ) => {
const zoomOutButton = InserterUtils.getZoomOutButton();
const inserterButton = InserterUtils.getInserterButton();
const blockLibrary = InserterUtils.getBlockLibrary();

await zoomOutButton.click();
await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();

await inserterButton.click();
const patternsTab = InserterUtils.getBlockLibraryTab( 'Patterns' );
await expect( patternsTab ).toHaveAttribute(
'data-active-item',
'true'
);
await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();

await zoomOutButton.click();
await expect( await InserterUtils.getZoomCanvas() ).toBeHidden();

// Close the inserter
await inserterButton.click();

await expect( blockLibrary ).toBeHidden();

// We should not return to zoom out since it was manually disengaged
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// We should not return to zoom out since it was manually disengaged
// Zoom Out should remain inactive since it was manually disengaged

await expect( await InserterUtils.getZoomCanvas() ).toBeHidden();
} );

// Similar test to the above but starting from not zoomed in
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Similar test to the above but starting from not zoomed in
// Similar test to the above but starting with Zoom Out inactive

test( 'should not toggle zoom state when closing the inserter if the user manually changed zoom state', async ( {
InserterUtils,
} ) => {
const zoomOutButton = InserterUtils.getZoomOutButton();
const inserterButton = InserterUtils.getInserterButton();
const blockLibrary = InserterUtils.getBlockLibrary();

await inserterButton.click();

// Go to patterns tab which should enter zoom out
const patternsTab = InserterUtils.getBlockLibraryTab( 'Patterns' );
await patternsTab.click();
await expect( patternsTab ).toHaveAttribute(
'data-active-item',
'true'
);
await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();

// Manually toggle zoom out off
await zoomOutButton.click();
await expect( await InserterUtils.getZoomCanvas() ).toBeHidden();

// Manually toggle zoom out again to return to zoomed-in state set by the patterns tab.
await zoomOutButton.click();
await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();

// Close the inserter
await inserterButton.click();

await expect( blockLibrary ).toBeHidden();

// We should stay in zoomed out state since it was manually engaged
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// We should stay in zoomed out state since it was manually engaged
// Zoom Out should remain active since it was manually engaged

await expect( await InserterUtils.getZoomCanvas() ).toBeVisible();
} );
} );

class InserterUtils {
constructor( { editor, page } ) {
this.editor = editor;
this.page = page;
}

getInserterButton() {
return this.page.getByRole( 'button', {
name: 'Block Inserter',
exact: true,
} );
}

getBlockLibrary() {
return this.page.getByRole( 'region', {
name: 'Block Library',
} );
}

getBlockLibraryTab( name ) {
return this.page.getByRole( 'tab', { name } );
}

getZoomOutButton() {
return this.page.getByRole( 'button', {
name: 'Zoom Out',
exact: true,
} );
}

getZoomCanvas() {
return this.page.locator( '.is-zoomed-out' );
}
}
Loading