Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -1731,7 +1731,7 @@ describe( 'Selecting links', () => {
);
};

const { container } = render( <LinkControlConsumer /> );
render( <LinkControlConsumer /> );

// Search Input UI.
const searchInput = screen.getByRole( 'combobox', {
Expand Down Expand Up @@ -1804,8 +1804,7 @@ describe( 'Selecting links', () => {
} );

// Make sure focus is retained after submission.
// eslint-disable-next-line testing-library/no-node-access
expect( container.firstChild ).toHaveFocus();
expect( currentLinkAnchor ).toHaveFocus();

expect( currentLink ).toBeVisible();
expect(
Expand Down
53 changes: 51 additions & 2 deletions packages/block-library/src/cover/test/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,50 @@ const disabledColorSettings = {
disableCustomGradients: true,
};

/**
* Runs `callback` with `requestAnimationFrame` callbacks flushed on a microtask
* ahead of any pending `setTimeout( 0 )` timers, then restores the original.
*
* In a browser, Ariakit normalizes the matrix's roving tabindex in a
* `requestAnimationFrame` before `useFocusOnMount`'s `setTimeout( 0 )` focuses
* the active (center) cell, so opening the dropdown leaves the content position
* untouched. JSDOM implements `requestAnimationFrame` as a ~16ms timer, which
* inverts the ordering and would otherwise focus and select the first DOM cell.
*
* @param {() => ( Promise< unknown > | unknown )} callback
* @return {Promise< unknown >} Resolves with the callback's return value.
*/
async function withAnimationFramesBeforeTimers( callback ) {
const originalRequestAnimationFrame = global.requestAnimationFrame;

let flushing = false;
global.requestAnimationFrame = ( frameCallback ) => {
// Frames scheduled while flushing fall back to the real implementation
// so self-rescheduling loops (e.g. Popover's Floating UI `autoUpdate`)
// don't spin forever on the microtask queue.
if ( flushing ) {
return originalRequestAnimationFrame( frameCallback );
}

queueMicrotask( () => {
flushing = true;
try {
frameCallback( performance.now() );
} finally {
flushing = false;
}
} );

return 0;
};

try {
return await callback();
} finally {
global.requestAnimationFrame = originalRequestAnimationFrame;
}
}

async function setup( attributes, useCoreBlocks, customSettings ) {
const testBlock = { name: 'core/cover', attributes };
const settings = customSettings || defaultSettings;
Expand Down Expand Up @@ -149,8 +193,13 @@ describe( 'Cover block', () => {
await setup();
await createAndSelectBlock();

await userEvent.click(
screen.getByLabelText( 'Change content position' )
// Open the matrix dropdown with browser-like frame ordering so
// focus-on-mount lands on the active cell instead of dirtying the
// content position.
await withAnimationFramesBeforeTimers( () =>
userEvent.click(
screen.getByLabelText( 'Change content position' )
)
);

expect( screen.getByLabelText( 'Block: Cover' ) ).not.toHaveClass(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ describe.each( [
const customSelect = screen.getByRole( 'listbox', {
name: props.label,
} );
expect( customSelect ).toHaveFocus();
await waitFor( () => expect( customSelect ).toHaveFocus() );
await press.Enter();

expect( onKeyDown ).toHaveBeenCalledTimes( 0 );
Expand Down
16 changes: 0 additions & 16 deletions packages/components/src/menu/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,6 @@ const resetTypeahead = () => {

describe( 'Menu', () => {
let user: ReturnType< typeof userEvent.setup >;
let originalGetClientRects: () => DOMRectList;

beforeAll( () => {
// Code that considers focusability considers rects in whether the
// element is visible, but since jsdom does not simulate layout, we
// need to fake it. This should ideally be a global test mock to
// faithfully emulate a true browser environment.
originalGetClientRects = window.Element.prototype.getClientRects;
window.Element.prototype.getClientRects = jest.fn(
() => [ { width: 1, height: 1 } ] as unknown as DOMRectList
);
} );

afterAll( () => {
window.Element.prototype.getClientRects = originalGetClientRects;
} );

beforeEach( () => {
user = userEvent.setup();
Expand Down
24 changes: 0 additions & 24 deletions packages/components/src/modal/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,6 @@ describe( 'Modal', () => {
} );

describe( 'Focus handling', () => {
const originalGetClientRects =
window.HTMLElement.prototype.getClientRects;

const FocusMountDemo = ( {
focusOnMount,
}: Pick< ModalProps, 'focusOnMount' > ) => {
Expand Down Expand Up @@ -300,27 +297,6 @@ describe( 'Modal', () => {
);
};

beforeEach( () => {
/**
* The test environment does not have a layout engine, so we need to mock
* the getClientRects method. This ensures that the focusable elements can be
* found by the `focusOnMount` logic which depends on layout information
* to determine if the element is visible or not.
* See https://github.com/WordPress/gutenberg/blob/trunk/packages/dom/src/focusable.js#L55-L61.
*/
// @ts-expect-error We're not trying to comply to the DOM spec, only mocking
window.HTMLElement.prototype.getClientRects = function () {
return [ 'trick-jsdom-into-having-size-for-element-rect' ];
};
} );

afterEach( () => {
// Restore original HTMLElement prototype.
// See beforeEach for details.
window.HTMLElement.prototype.getClientRects =
originalGetClientRects;
} );

it( 'should focus the Modal dialog by default when `focusOnMount` prop is not provided', async () => {
const user = userEvent.setup();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,7 @@ const getNavigableMenuFocusables = () => [
screen.getByRole( 'link', { name: 'Item 4' } ),
];

const originalGetClientRects = window.HTMLElement.prototype.getClientRects;

describe( 'NavigableMenu', () => {
beforeAll( () => {
// Mocking `getClientRects()` is necessary to pass a check performed by
// the `focus.tabbable.find()` and by the `focus.focusable.find()` functions
// from the `@wordpress/dom` package.
// @ts-expect-error We're not trying to comply to the DOM spec, only mocking
window.HTMLElement.prototype.getClientRects = function () {
return [ 'trick-jsdom-into-having-size-for-element-rect' ];
};
} );

afterAll( () => {
window.HTMLElement.prototype.getClientRects = originalGetClientRects;
} );

it( 'moves focus on its focusable children by using the up/down arrow keys', async () => {
const user = userEvent.setup();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,7 @@ const getTabbableContainerTabbables = () => [
screen.getByRole( 'link', { name: 'Item 4' } ),
];

const originalGetClientRects = window.HTMLElement.prototype.getClientRects;

describe( 'TabbableContainer', () => {
beforeAll( () => {
// Mocking `getClientRects()` is necessary to pass a check performed by
// the `focus.tabbable.find()` and by the `focus.focusable.find()` functions
// from the `@wordpress/dom` package.
// @ts-expect-error We're not trying to comply to the DOM spec, only mocking
window.HTMLElement.prototype.getClientRects = function () {
return [ 'trick-jsdom-into-having-size-for-element-rect' ];
};
} );

afterAll( () => {
window.HTMLElement.prototype.getClientRects = originalGetClientRects;
} );

it( 'moves focus on its tabbable children by using the tab key', async () => {
const user = userEvent.setup();

Expand Down
24 changes: 0 additions & 24 deletions packages/components/src/navigator/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -469,30 +469,6 @@ const getNavigationButton = ( buttonKey: keyof typeof BUTTON_TEXT ) =>
screen.getByRole( 'button', { name: BUTTON_TEXT[ buttonKey ] } );

describe( 'Navigator', () => {
const originalGetClientRects = window.Element.prototype.getClientRects;

// `getClientRects` needs to be mocked so that `isVisible` from the `@wordpress/dom`
// `focusable` module can pass, in a JSDOM env where the DOM elements have no width/height.
const mockedGetClientRects = jest.fn( () => [
{
x: 0,
y: 0,
width: 100,
height: 100,
},
] );

beforeAll( () => {
// @ts-expect-error There's no need for an exact mock, this is just needed
// for the tests to pass (see `mockedGetClientRects` inline comments).
window.Element.prototype.getClientRects =
jest.fn( mockedGetClientRects );
} );

afterAll( () => {
window.Element.prototype.getClientRects = originalGetClientRects;
} );

it( 'should render', () => {
render( <MyNavigation /> );

Expand Down
17 changes: 0 additions & 17 deletions packages/components/src/tab-panel/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ const TABS = [
const getSelectedTab = async () =>
await screen.findByRole( 'tab', { selected: true } );

let originalGetClientRects: () => DOMRectList;

describe.each( [
[ 'uncontrolled', TabPanel ],
// The controlled component tests will be added once we certify the
Expand All @@ -45,21 +43,6 @@ describe.each( [
] )( 'TabPanel %s', ( ...modeAndComponent ) => {
const [ , Component ] = modeAndComponent;

beforeAll( () => {
originalGetClientRects = window.HTMLElement.prototype.getClientRects;
// Mocking `getClientRects()` is necessary to pass a check performed by
// the `focus.tabbable.find()` and by the `focus.focusable.find()` functions
// from the `@wordpress/dom` package.
// @ts-expect-error We're not trying to comply to the DOM spec, only mocking
window.HTMLElement.prototype.getClientRects = function () {
return [ 'trick-jsdom-into-having-size-for-element-rect' ];
};
} );

afterAll( () => {
window.HTMLElement.prototype.getClientRects = originalGetClientRects;
} );

describe( 'Accessibility and semantics', () => {
it( 'should use the correct aria attributes', async () => {
const panelRenderFunction = jest.fn();
Expand Down
17 changes: 0 additions & 17 deletions packages/components/src/tabs/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,6 @@ const ControlledTabs = ( {
);
};

let originalGetClientRects: () => DOMRectList;

async function waitForComponentToBeInitializedWithSelectedTab(
selectedTabName: string | undefined
) {
Expand Down Expand Up @@ -218,21 +216,6 @@ async function waitForComponentToBeInitializedWithSelectedTab(
}

describe( 'Tabs', () => {
beforeAll( () => {
originalGetClientRects = window.HTMLElement.prototype.getClientRects;
// Mocking `getClientRects()` is necessary to pass a check performed by
// the `focus.tabbable.find()` and by the `focus.focusable.find()` functions
// from the `@wordpress/dom` package.
// @ts-expect-error We're not trying to comply to the DOM spec, only mocking
window.HTMLElement.prototype.getClientRects = function () {
return [ 'trick-jsdom-into-having-size-for-element-rect' ];
};
} );

afterAll( () => {
window.HTMLElement.prototype.getClientRects = originalGetClientRects;
} );

describe( 'Adherence to spec and basic behavior', () => {
it( 'should apply the correct roles, semantics and attributes', async () => {
await render( <UncontrolledTabs tabs={ TABS } /> );
Expand Down
23 changes: 0 additions & 23 deletions packages/components/src/tree-grid/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,6 @@ const TestButton = forwardRef(
);

describe( 'TreeGrid', () => {
const originalGetClientRects = window.Element.prototype.getClientRects;

// `getClientRects` needs to be mocked so that `isVisible` from the `@wordpress/dom`
// `focusable` module can pass, in a JSDOM env where the DOM elements have no width/height.
const mockedGetClientRects = jest.fn( () => [
{
x: 0,
y: 0,
width: 100,
height: 100,
},
] );

beforeAll( () => {
// @ts-expect-error - This is just a mock
window.Element.prototype.getClientRects =
jest.fn( mockedGetClientRects );
} );

afterAll( () => {
window.Element.prototype.getClientRects = originalGetClientRects;
} );

describe( 'simple rendering', () => {
it( 'renders a table, tbody and any child elements', () => {
const { container } = render(
Expand Down
18 changes: 10 additions & 8 deletions packages/widget-dashboard/src/test/actions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ function Harness( {
}

describe( 'WidgetDashboard.Actions', () => {
let user: ReturnType< typeof userEvent.setup >;

beforeEach( () => {
user = userEvent.setup();
} );

it( 'renders the Customize button when editMode is false', () => {
render( <Harness /> );

Expand All @@ -91,9 +97,7 @@ describe( 'WidgetDashboard.Actions', () => {
const onEditChange = jest.fn();
render( <Harness onEditChange={ onEditChange } /> );

await userEvent.click(
screen.getByRole( 'button', { name: 'Customize' } )
);
await user.click( screen.getByRole( 'button', { name: 'Customize' } ) );

expect( onEditChange ).toHaveBeenLastCalledWith( true );
} );
Expand All @@ -117,9 +121,7 @@ describe( 'WidgetDashboard.Actions', () => {
/>
);

await userEvent.click(
screen.getByRole( 'button', { name: 'Cancel' } )
);
await user.click( screen.getByRole( 'button', { name: 'Cancel' } ) );

expect( onEditChange ).toHaveBeenLastCalledWith( false );
expect( onLayoutChange ).not.toHaveBeenCalled();
Expand Down Expand Up @@ -176,7 +178,7 @@ describe( 'WidgetDashboard.Actions', () => {
<Harness initialEditMode onGridSettingsChange={ () => {} } />
);

await userEvent.click(
await user.click(
screen.getByRole( 'button', { name: 'Layout settings' } )
);

Expand All @@ -202,7 +204,7 @@ describe( 'WidgetDashboard.Actions', () => {
</WidgetDashboard>
);

await userEvent.click(
await user.click(
screen.getByRole( 'button', { name: 'More options' } )
);

Expand Down
Loading
Loading