import { render } from '@testing-library/react'
import React, { createElement, useEffect } from 'react'
import {
MenuState,
assertActiveElement,
assertMenu,
assertMenuButton,
assertMenuButtonLinkedWithMenu,
assertMenuItem,
assertMenuLinkedWithMenuItem,
assertNoActiveMenuItem,
getByText,
getMenu,
getMenuButton,
getMenuButtons,
getMenuItems,
getMenus,
} from '../../test-utils/accessibility-assertions'
import {
Keys,
MouseButton,
click,
focus,
mouseLeave,
mouseMove,
press,
rawClick,
shift,
type,
word,
} from '../../test-utils/interactions'
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
import { Transition } from '../transition/transition'
import { Menu } from './menu'
jest.mock('../../hooks/use-id')
// @ts-expect-error
global.ResizeObserver = class FakeResizeObserver {
observe() {}
disconnect() {}
}
beforeAll(() => {
jest.spyOn(window, 'requestAnimationFrame').mockImplementation(setImmediate as any)
jest.spyOn(window, 'cancelAnimationFrame').mockImplementation(clearImmediate as any)
})
afterAll(() => jest.restoreAllMocks())
describe('Safe guards', () => {
it.each([
['Menu.Button', Menu.Button],
['Menu.Items', Menu.Items],
['Menu.Item', Menu.Item],
])(
'should error when we are using a <%s /> without a parent
',
suppressConsoleLogs((name, Component) => {
expect(() => render(createElement(Component as any))).toThrow(
`<${name} /> is missing a parent component.`
)
})
)
it(
'should be possible to render a Menu without crashing',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
})
)
})
describe('Rendering', () => {
describe('Menu', () => {
it(
'should be possible to render a Menu using a render prop',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({
state: MenuState.InvisibleUnmounted,
attributes: { id: 'headlessui-menu-button-1' },
})
assertMenu({ state: MenuState.InvisibleUnmounted })
await click(getMenuButton())
assertMenuButton({
state: MenuState.Visible,
attributes: { id: 'headlessui-menu-button-1' },
})
assertMenu({ state: MenuState.Visible })
})
)
it(
'should be possible to manually close the Menu using the exposed close function',
suppressConsoleLogs(async () => {
render(
)
assertMenu({ state: MenuState.InvisibleUnmounted })
await click(getMenuButton())
assertMenu({ state: MenuState.Visible })
await click(getByText('Close'))
assertMenu({ state: MenuState.InvisibleUnmounted })
})
)
})
describe('Menu.Button', () => {
it(
'should be possible to render a Menu.Button using a render prop',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({
state: MenuState.InvisibleUnmounted,
attributes: { id: 'headlessui-menu-button-1' },
textContent: JSON.stringify({
open: false,
active: false,
disabled: false,
hover: false,
focus: false,
autofocus: false,
}),
})
assertMenu({ state: MenuState.InvisibleUnmounted })
await click(getMenuButton())
assertMenuButton({
state: MenuState.Visible,
attributes: { id: 'headlessui-menu-button-1' },
textContent: JSON.stringify({
open: true,
active: true,
disabled: false,
hover: false,
focus: false,
autofocus: false,
}),
})
assertMenu({ state: MenuState.Visible })
})
)
it(
'should be possible to render a Menu.Button using a render prop and an `as` prop',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({
state: MenuState.InvisibleUnmounted,
attributes: { id: 'headlessui-menu-button-1' },
textContent: JSON.stringify({
open: false,
active: false,
disabled: false,
hover: false,
focus: false,
autofocus: false,
}),
})
assertMenu({ state: MenuState.InvisibleUnmounted })
await click(getMenuButton())
assertMenuButton({
state: MenuState.Visible,
attributes: { id: 'headlessui-menu-button-1' },
textContent: JSON.stringify({
open: true,
active: true,
disabled: false,
hover: false,
focus: false,
autofocus: false,
}),
})
assertMenu({ state: MenuState.Visible })
})
)
describe('`type` attribute', () => {
it('should set the `type` to "button" by default', async () => {
render(
)
expect(getMenuButton()).toHaveAttribute('type', 'button')
})
it('should not set the `type` to "button" if it already contains a `type`', async () => {
render(
)
expect(getMenuButton()).toHaveAttribute('type', 'submit')
})
it('should set the `type` to "button" when using the `as` prop which resolves to a "button"', async () => {
let CustomButton = React.forwardRef((props, ref) => (
))
render(
)
expect(getMenuButton()).toHaveAttribute('type', 'button')
})
it('should not set the type if the "as" prop is not a "button"', async () => {
render(
)
expect(getMenuButton()).not.toHaveAttribute('type')
})
it('should not set the `type` to "button" when using the `as` prop which resolves to a "div"', async () => {
let CustomButton = React.forwardRef((props, ref) => (
))
render(
)
expect(getMenuButton()).not.toHaveAttribute('type')
})
})
})
describe('Menu.Items', () => {
it(
'should be possible to render Menu.Items using a render prop',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({
state: MenuState.InvisibleUnmounted,
attributes: { id: 'headlessui-menu-button-1' },
})
assertMenu({ state: MenuState.InvisibleUnmounted })
await click(getMenuButton())
assertMenuButton({
state: MenuState.Visible,
attributes: { id: 'headlessui-menu-button-1' },
})
assertMenu({
state: MenuState.Visible,
textContent: JSON.stringify({ open: true }),
})
})
)
it('should be possible to always render the Menu.Items if we provide it a `static` prop', () => {
render(
)
// Let's verify that the Menu is already there
expect(getMenu()).not.toBe(null)
})
it('should be possible to use a different render strategy for the Menu.Items', async () => {
render(
)
assertMenu({ state: MenuState.InvisibleHidden })
// Let's open the Menu, to see if it is not hidden anymore
await click(getMenuButton())
assertMenu({ state: MenuState.Visible })
})
})
describe('Menu.Item', () => {
it(
'should be possible to render a Menu.Item using a render prop',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({
state: MenuState.InvisibleUnmounted,
attributes: { id: 'headlessui-menu-button-1' },
})
assertMenu({ state: MenuState.InvisibleUnmounted })
await click(getMenuButton())
assertMenuButton({
state: MenuState.Visible,
attributes: { id: 'headlessui-menu-button-1' },
})
assertMenu({
state: MenuState.Visible,
textContent: JSON.stringify({ active: false, focus: false, disabled: false }),
})
})
)
it(
'should be possible to manually close the Menu using the exposed close function',
suppressConsoleLogs(async () => {
render(
)
assertMenu({ state: MenuState.InvisibleUnmounted })
await click(getMenuButton())
assertMenu({ state: MenuState.Visible })
await click(getByText('Close'))
assertMenu({ state: MenuState.InvisibleUnmounted })
})
)
it('should not override an explicit disabled prop on MenuItems child', async () => {
render(
)
assertMenuButton({
state: MenuState.InvisibleUnmounted,
})
assertMenu({ state: MenuState.InvisibleUnmounted })
getMenuButton()?.focus()
await press(Keys.Enter)
assertMenuButton({
state: MenuState.Visible,
})
assertMenu({ state: MenuState.Visible })
assertMenuItem(getMenuItems()[0], {
tag: 'button',
attributes: { 'data-focus': '' },
})
assertMenuItem(getMenuItems()[1], {
tag: 'button',
attributes: {},
})
assertMenuItem(getMenuItems()[2], {
tag: 'button',
attributes: { disabled: '' },
})
})
})
it('should guarantee the order of DOM nodes when performing actions', async () => {
function Example({ hide = false }) {
return (
<>
>
)
}
let { rerender } = render()
// Open the Menu
await click(getByText('Trigger'))
rerender() // Remove Menu.Item 2
rerender() // Re-add Menu.Item 2
assertMenu({ state: MenuState.Visible })
let items = getMenuItems()
// Focus the first item
await press(Keys.ArrowDown)
// Verify that the first menu item is active
assertMenuLinkedWithMenuItem(items[0])
await press(Keys.ArrowDown)
// Verify that the second menu item is active
assertMenuLinkedWithMenuItem(items[1])
await press(Keys.ArrowDown)
// Verify that the third menu item is active
assertMenuLinkedWithMenuItem(items[2])
})
})
describe('Rendering composition', () => {
it(
'should be possible to conditionally render classNames (aka className can be a function?!)',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({
state: MenuState.InvisibleUnmounted,
attributes: { id: 'headlessui-menu-button-1' },
})
assertMenu({ state: MenuState.InvisibleUnmounted })
// Open menu
await click(getMenuButton())
let items = getMenuItems()
// Verify correct classNames
expect('' + items[0].classList).toEqual(
JSON.stringify({ active: false, focus: false, disabled: false })
)
expect('' + items[1].classList).toEqual(
JSON.stringify({ active: false, focus: false, disabled: true })
)
expect('' + items[2].classList).toEqual('no-special-treatment')
// Double check that nothing is active
assertNoActiveMenuItem()
// Make the first item active
await press(Keys.ArrowDown)
// Verify the classNames
expect('' + items[0].classList).toEqual(
JSON.stringify({ active: true, focus: true, disabled: false })
)
expect('' + items[1].classList).toEqual(
JSON.stringify({ active: false, focus: false, disabled: true })
)
expect('' + items[2].classList).toEqual('no-special-treatment')
// Double check that the first item is the active one
assertMenuLinkedWithMenuItem(items[0])
// Let's go down, this should go to the third item since the second item is disabled!
await press(Keys.ArrowDown)
// Verify the classNames
expect('' + items[0].classList).toEqual(
JSON.stringify({ active: false, focus: false, disabled: false })
)
expect('' + items[1].classList).toEqual(
JSON.stringify({ active: false, focus: false, disabled: true })
)
expect('' + items[2].classList).toEqual('no-special-treatment')
// Double check that the last item is the active one
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should be possible to swap the menu item with a button for example',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({
state: MenuState.InvisibleUnmounted,
attributes: { id: 'headlessui-menu-button-1' },
})
assertMenu({ state: MenuState.InvisibleUnmounted })
// Open menu
await click(getMenuButton())
// Verify items are buttons now
let items = getMenuItems()
items.forEach((item) => assertMenuItem(item, { tag: 'button' }))
})
)
it(
'should mark all the elements between Menu.Items and Menu.Item with role none',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
expect.hasAssertions()
document.querySelectorAll('.outer').forEach((element) => {
expect(element).not.toHaveAttribute('role', 'none')
})
document.querySelectorAll('.inner').forEach((element) => {
expect(element).toHaveAttribute('role', 'none')
})
})
)
})
describe('Composition', () => {
function Debug({ fn, name }: { fn: (text: string) => void; name: string }) {
useEffect(() => {
fn(`Mounting - ${name}`)
return () => {
fn(`Unmounting - ${name}`)
}
}, [fn, name])
return null
}
it(
'should be possible to wrap the Menu.Items with a Transition component',
suppressConsoleLogs(async () => {
let orderFn = jest.fn()
render(
)
assertMenuButton({
state: MenuState.InvisibleUnmounted,
attributes: { id: 'headlessui-menu-button-1' },
})
assertMenu({ state: MenuState.InvisibleUnmounted })
await rawClick(getMenuButton())
assertMenuButton({
state: MenuState.Visible,
attributes: { id: 'headlessui-menu-button-1' },
})
assertMenu({
state: MenuState.Visible,
textContent: JSON.stringify({ active: false, focus: false, disabled: false }),
})
await rawClick(getMenuButton())
// Verify that we tracked the `mounts` and `unmounts` in the correct order
expect(orderFn.mock.calls).toEqual([
['Mounting - Menu'],
['Mounting - Transition'],
['Mounting - Menu.Item'],
['Unmounting - Transition'],
['Unmounting - Menu.Item'],
])
})
)
it(
'should be possible to wrap the Menu.Items with a Transition.Child component',
suppressConsoleLogs(async () => {
let orderFn = jest.fn()
render(
)
assertMenuButton({
state: MenuState.InvisibleUnmounted,
attributes: { id: 'headlessui-menu-button-1' },
})
assertMenu({ state: MenuState.InvisibleUnmounted })
await rawClick(getMenuButton())
assertMenuButton({
state: MenuState.Visible,
attributes: { id: 'headlessui-menu-button-1' },
})
assertMenu({
state: MenuState.Visible,
textContent: JSON.stringify({ active: false, focus: false, disabled: false }),
})
await rawClick(getMenuButton())
// Verify that we tracked the `mounts` and `unmounts` in the correct order
expect(orderFn.mock.calls).toEqual([
['Mounting - Menu'],
['Mounting - Transition'],
['Mounting - Menu.Item'],
['Unmounting - Transition'],
['Unmounting - Menu.Item'],
])
})
)
})
describe('Keyboard interactions', () => {
describe('`Enter` key', () => {
it(
'should be possible to open the menu with Enter',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
assertMenu({ state: MenuState.Visible })
assertMenuButtonLinkedWithMenu()
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach((item) => assertMenuItem(item))
// Verify that the first menu item is active
assertMenuLinkedWithMenuItem(items[0])
})
)
it(
'should not be possible to open the menu with Enter when the button is disabled',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Try to open the menu
await press(Keys.Enter)
// Verify it is still closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
})
)
it(
'should have no active menu item when there are no menu items at all',
suppressConsoleLogs(async () => {
render(
)
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
assertMenu({ state: MenuState.Visible })
assertNoActiveMenuItem()
})
)
it(
'should focus the first non disabled menu item when opening with Enter',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
let items = getMenuItems()
// Verify that the first non-disabled menu item is active
assertMenuLinkedWithMenuItem(items[1])
})
)
it(
'should focus the first non disabled menu item when opening with Enter (jump over multiple disabled ones)',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
let items = getMenuItems()
// Verify that the first non-disabled menu item is active
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should have no active menu item upon Enter key press, when there are no non-disabled menu items',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
assertNoActiveMenuItem()
})
)
it(
'should be possible to close the menu with Enter when there is no active menuitem',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Open menu
await click(getMenuButton())
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
// Close menu
await press(Keys.Enter)
// Verify it is closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Verify the button is focused again
assertActiveElement(getMenuButton())
})
)
it(
'should be possible to close the menu with Enter and invoke the active menu item',
suppressConsoleLogs(async () => {
let clickHandler = jest.fn()
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Open menu
await click(getMenuButton())
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
// Activate the first menu item
let items = getMenuItems()
await mouseMove(items[0])
// Close menu, and invoke the item
await press(Keys.Enter)
// Verify it is closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Verify the button is focused again
assertActiveElement(getMenuButton())
// Verify the "click" went through on the `a` tag
expect(clickHandler).toHaveBeenCalled()
})
)
})
it(
'should be possible to use a button as a menu item and invoke it upon Enter',
suppressConsoleLogs(async () => {
let clickHandler = jest.fn()
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Open menu
await click(getMenuButton())
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
// Activate the second menu item
let items = getMenuItems()
await mouseMove(items[1])
// Close menu, and invoke the item
await press(Keys.Enter)
// Verify it is closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Verify the button got "clicked"
expect(clickHandler).toHaveBeenCalledTimes(1)
// Verify the button is focused again
assertActiveElement(getMenuButton())
// Click the menu button again
await click(getMenuButton())
// Activate the last menu item
await mouseMove(getMenuItems()[2])
// Close menu, and invoke the item
await press(Keys.Enter)
// Verify the button got "clicked"
expect(clickHandler).toHaveBeenCalledTimes(2)
// Verify the button is focused again
assertActiveElement(getMenuButton())
})
)
describe('`Space` key', () => {
it(
'should be possible to open the menu with Space',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Space)
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
assertMenu({ state: MenuState.Visible })
assertMenuButtonLinkedWithMenu()
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
})
)
it(
'should not be possible to open the menu with Space when the button is disabled',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Try to open the menu
await press(Keys.Space)
// Verify it is still closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
})
)
it(
'should have no active menu item when there are no menu items at all',
suppressConsoleLogs(async () => {
render(
)
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Space)
assertMenu({ state: MenuState.Visible })
assertNoActiveMenuItem()
})
)
it(
'should focus the first non disabled menu item when opening with Space',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Space)
let items = getMenuItems()
// Verify that the first non-disabled menu item is active
assertMenuLinkedWithMenuItem(items[1])
})
)
it(
'should focus the first non disabled menu item when opening with Space (jump over multiple disabled ones)',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Space)
let items = getMenuItems()
// Verify that the first non-disabled menu item is active
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should have no active menu item upon Space key press, when there are no non-disabled menu items',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Space)
assertNoActiveMenuItem()
})
)
it(
'should be possible to close the menu with Space when there is no active menuitem',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Open menu
await click(getMenuButton())
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
// Close menu
await press(Keys.Space)
// Verify it is closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Verify the button is focused again
assertActiveElement(getMenuButton())
})
)
it(
'should be possible to close the menu with Space and invoke the active menu item',
suppressConsoleLogs(async () => {
let clickHandler = jest.fn()
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Open menu
await click(getMenuButton())
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
// Activate the first menu item
let items = getMenuItems()
await mouseMove(items[0])
// Close menu, and invoke the item
await press(Keys.Space)
// Verify it is closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Verify the "click" went through on the `a` tag
expect(clickHandler).toHaveBeenCalled()
// Verify the button is focused again
assertActiveElement(getMenuButton())
})
)
})
describe('`Escape` key', () => {
it(
'should be possible to close an open menu with Escape',
suppressConsoleLogs(async () => {
render(
)
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Space)
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
assertMenu({ state: MenuState.Visible })
assertMenuButtonLinkedWithMenu()
// Close menu
await press(Keys.Escape)
// Verify it is closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Verify the button is focused again
assertActiveElement(getMenuButton())
})
)
})
describe('`Tab` key', () => {
it(
'should close when we use Tab',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
assertMenu({ state: MenuState.Visible })
assertMenuButtonLinkedWithMenu()
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
// Try to tab
await press(Keys.Tab)
// Verify it is closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
})
)
it(
'should focus trap when we use Shift+Tab',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
assertMenu({ state: MenuState.Visible })
assertMenuButtonLinkedWithMenu()
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
// Try to Shift+Tab
await press(shift(Keys.Tab))
// Verify it is closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
})
)
})
describe('`ArrowDown` key', () => {
it(
'should be possible to open the menu with ArrowDown',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.ArrowDown)
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
assertMenu({ state: MenuState.Visible })
assertMenuButtonLinkedWithMenu()
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach((item) => assertMenuItem(item))
// Verify that the first menu item is active
assertMenuLinkedWithMenuItem(items[0])
})
)
it(
'should not be possible to open the menu with ArrowDown when the button is disabled',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Try to open the menu
await press(Keys.ArrowDown)
// Verify it is still closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
})
)
it(
'should have no active menu item when there are no menu items at all',
suppressConsoleLogs(async () => {
render(
)
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.ArrowDown)
assertMenu({ state: MenuState.Visible })
assertNoActiveMenuItem()
})
)
it(
'should be possible to use ArrowDown to navigate the menu items',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
// We should be able to go down once
await press(Keys.ArrowDown)
assertMenuLinkedWithMenuItem(items[1])
// We should be able to go down again
await press(Keys.ArrowDown)
assertMenuLinkedWithMenuItem(items[2])
// We should NOT be able to go down again (because last item). Current implementation won't go around.
await press(Keys.ArrowDown)
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should be possible to use ArrowDown to navigate the menu items and skip the first disabled one',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[1])
// We should be able to go down once
await press(Keys.ArrowDown)
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should be possible to use ArrowDown to navigate the menu items and jump to the first non-disabled one',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[2])
})
)
})
describe('`ArrowUp` key', () => {
it(
'should be possible to open the menu with ArrowUp and the last item should be active',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.ArrowUp)
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
assertMenu({ state: MenuState.Visible })
assertMenuButtonLinkedWithMenu()
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach((item) => assertMenuItem(item))
// ! ALERT: The LAST item should now be active
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should not be possible to open the menu with ArrowUp and the last item should be active when the button is disabled',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Try to open the menu
await press(Keys.ArrowUp)
// Verify it is still closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
})
)
it(
'should have no active menu item when there are no menu items at all',
suppressConsoleLogs(async () => {
render(
)
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.ArrowUp)
assertMenu({ state: MenuState.Visible })
assertNoActiveMenuItem()
})
)
it(
'should be possible to use ArrowUp to navigate the menu items and jump to the first non-disabled one',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.ArrowUp)
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[0])
})
)
it(
'should not be possible to navigate up or down if there is only a single non-disabled item',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[2])
// We should not be able to go up (because those are disabled)
await press(Keys.ArrowUp)
assertMenuLinkedWithMenuItem(items[2])
// We should not be able to go down (because this is the last item)
await press(Keys.ArrowDown)
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should be possible to use ArrowUp to navigate the menu items',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.ArrowUp)
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
assertMenu({ state: MenuState.Visible })
assertMenuButtonLinkedWithMenu()
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach((item) => assertMenuItem(item))
assertMenuLinkedWithMenuItem(items[2])
// We should be able to go down once
await press(Keys.ArrowUp)
assertMenuLinkedWithMenuItem(items[1])
// We should be able to go down again
await press(Keys.ArrowUp)
assertMenuLinkedWithMenuItem(items[0])
// We should NOT be able to go up again (because first item). Current implementation won't go around.
await press(Keys.ArrowUp)
assertMenuLinkedWithMenuItem(items[0])
})
)
})
describe('`End` key', () => {
it(
'should be possible to use the End key to go to the last menu item',
suppressConsoleLogs(async () => {
render(
)
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
let items = getMenuItems()
// We should be on the first item
assertMenuLinkedWithMenuItem(items[0])
// We should be able to go to the last item
await press(Keys.End)
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should be possible to use the End key to go to the last non disabled menu item',
suppressConsoleLogs(async () => {
render(
)
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
let items = getMenuItems()
// We should be on the first item
assertMenuLinkedWithMenuItem(items[0])
// We should be able to go to the last non-disabled item
await press(Keys.End)
assertMenuLinkedWithMenuItem(items[1])
})
)
it(
'should be possible to use the End key to go to the first menu item if that is the only non-disabled menu item',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
// We opened via click, we don't have an active item
assertNoActiveMenuItem()
// We should not be able to go to the end
await press(Keys.End)
let items = getMenuItems()
assertMenuLinkedWithMenuItem(items[0])
})
)
it(
'should have no active menu item upon End key press, when there are no non-disabled menu items',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
// We opened via click, we don't have an active item
assertNoActiveMenuItem()
// We should not be able to go to the end
await press(Keys.End)
assertNoActiveMenuItem()
})
)
})
describe('`PageDown` key', () => {
it(
'should be possible to use the PageDown key to go to the last menu item',
suppressConsoleLogs(async () => {
render(
)
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
let items = getMenuItems()
// We should be on the first item
assertMenuLinkedWithMenuItem(items[0])
// We should be able to go to the last item
await press(Keys.PageDown)
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should be possible to use the PageDown key to go to the last non disabled menu item',
suppressConsoleLogs(async () => {
render(
)
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.Enter)
let items = getMenuItems()
// We should be on the first item
assertMenuLinkedWithMenuItem(items[0])
// We should be able to go to the last non-disabled item
await press(Keys.PageDown)
assertMenuLinkedWithMenuItem(items[1])
})
)
it(
'should be possible to use the PageDown key to go to the first menu item if that is the only non-disabled menu item',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
// We opened via click, we don't have an active item
assertNoActiveMenuItem()
// We should not be able to go to the end
await press(Keys.PageDown)
let items = getMenuItems()
assertMenuLinkedWithMenuItem(items[0])
})
)
it(
'should have no active menu item upon PageDown key press, when there are no non-disabled menu items',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
// We opened via click, we don't have an active item
assertNoActiveMenuItem()
// We should not be able to go to the end
await press(Keys.PageDown)
assertNoActiveMenuItem()
})
)
})
describe('`Home` key', () => {
it(
'should be possible to use the Home key to go to the first menu item',
suppressConsoleLogs(async () => {
render(
)
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.ArrowUp)
let items = getMenuItems()
// We should be on the last item
assertMenuLinkedWithMenuItem(items[2])
// We should be able to go to the first item
await press(Keys.Home)
assertMenuLinkedWithMenuItem(items[0])
})
)
it(
'should be possible to use the Home key to go to the first non disabled menu item',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
// We opened via click, we don't have an active item
assertNoActiveMenuItem()
// We should not be able to go to the end
await press(Keys.Home)
let items = getMenuItems()
// We should be on the first non-disabled item
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should be possible to use the Home key to go to the last menu item if that is the only non-disabled menu item',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
// We opened via click, we don't have an active item
assertNoActiveMenuItem()
// We should not be able to go to the end
await press(Keys.Home)
let items = getMenuItems()
assertMenuLinkedWithMenuItem(items[3])
})
)
it(
'should have no active menu item upon Home key press, when there are no non-disabled menu items',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
// We opened via click, we don't have an active item
assertNoActiveMenuItem()
// We should not be able to go to the end
await press(Keys.Home)
assertNoActiveMenuItem()
})
)
})
describe('`PageUp` key', () => {
it(
'should be possible to use the PageUp key to go to the first menu item',
suppressConsoleLogs(async () => {
render(
)
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.ArrowUp)
let items = getMenuItems()
// We should be on the last item
assertMenuLinkedWithMenuItem(items[2])
// We should be able to go to the first item
await press(Keys.PageUp)
assertMenuLinkedWithMenuItem(items[0])
})
)
it(
'should be possible to use the PageUp key to go to the first non disabled menu item',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
// We opened via click, we don't have an active item
assertNoActiveMenuItem()
// We should not be able to go to the end
await press(Keys.PageUp)
let items = getMenuItems()
// We should be on the first non-disabled item
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should be possible to use the PageUp key to go to the last menu item if that is the only non-disabled menu item',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
// We opened via click, we don't have an active item
assertNoActiveMenuItem()
// We should not be able to go to the end
await press(Keys.PageUp)
let items = getMenuItems()
assertMenuLinkedWithMenuItem(items[3])
})
)
it(
'should have no active menu item upon PageUp key press, when there are no non-disabled menu items',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
// We opened via click, we don't have an active item
assertNoActiveMenuItem()
// We should not be able to go to the end
await press(Keys.PageUp)
assertNoActiveMenuItem()
})
)
})
describe('`Any` key aka search', () => {
it(
'should be possible to type a full word that has a perfect match',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
let items = getMenuItems()
// We should be able to go to the second item
await type(word('bob'))
assertMenuLinkedWithMenuItem(items[1])
// We should be able to go to the first item
await type(word('alice'))
assertMenuLinkedWithMenuItem(items[0])
// We should be able to go to the last item
await type(word('charlie'))
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should be possible to type a partial of a word',
suppressConsoleLogs(async () => {
render(
)
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.ArrowUp)
let items = getMenuItems()
// We should be on the last item
assertMenuLinkedWithMenuItem(items[2])
// We should be able to go to the second item
await type(word('bo'))
assertMenuLinkedWithMenuItem(items[1])
// We should be able to go to the first item
await type(word('ali'))
assertMenuLinkedWithMenuItem(items[0])
// We should be able to go to the last item
await type(word('char'))
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should be possible to type words with spaces',
suppressConsoleLogs(async () => {
render(
)
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.ArrowUp)
let items = getMenuItems()
// We should be on the last item
assertMenuLinkedWithMenuItem(items[2])
// We should be able to go to the second item
await type(word('value b'))
assertMenuLinkedWithMenuItem(items[1])
// We should be able to go to the first item
await type(word('value a'))
assertMenuLinkedWithMenuItem(items[0])
// We should be able to go to the last item
await type(word('value c'))
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should not be possible to search for a disabled item',
suppressConsoleLogs(async () => {
render(
)
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.ArrowUp)
let items = getMenuItems()
// We should be on the last item
assertMenuLinkedWithMenuItem(items[2])
// We should not be able to go to the disabled item
await type(word('bo'))
// We should still be on the last item
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should be possible to search for a word (case insensitive)',
suppressConsoleLogs(async () => {
render(
)
// Focus the button
await focus(getMenuButton())
// Open menu
await press(Keys.ArrowUp)
let items = getMenuItems()
// We should be on the last item
assertMenuLinkedWithMenuItem(items[2])
// Search for bob in a different casing
await type(word('BO'))
// We should be on `bob`
assertMenuLinkedWithMenuItem(items[1])
})
)
it(
'should be possible to search for the next occurrence',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
let items = getMenuItems()
// Search for bob
await type(word('b'))
// We should be on the first `bob`
assertMenuLinkedWithMenuItem(items[1])
// Search for bob again
await type(word('b'))
// We should be on the second `bob`
assertMenuLinkedWithMenuItem(items[3])
})
)
it(
'should stay on the same item while keystrokes still match',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
let items = getMenuItems()
// ---
// Reset: Go to first item
await press(Keys.Home)
// Search for "b" in "bob"
await type(word('b'))
// We should be on the first `bob`
assertMenuLinkedWithMenuItem(items[1])
// Search for "b" in "bob" again
await type(word('b'))
// We should be on the next `bob`
assertMenuLinkedWithMenuItem(items[3])
// ---
// Reset: Go to first item
await press(Keys.Home)
// Search for "bo" in "bob"
await type(word('bo'))
// We should be on the first `bob`
assertMenuLinkedWithMenuItem(items[1])
// Search for "bo" in "bob" again
await type(word('bo'))
// We should be on the next `bob`
assertMenuLinkedWithMenuItem(items[3])
// ---
// Reset: Go to first item
await press(Keys.Home)
// Search for "bob" in "bob"
await type(word('bob'))
// We should be on the first `bob`
assertMenuLinkedWithMenuItem(items[1])
// Search for "bob" in "bob" again
await type(word('bob'))
// We should be on the next `bob`
assertMenuLinkedWithMenuItem(items[3])
})
)
})
})
describe('Mouse interactions', () => {
it(
'should be possible to open a menu on click',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Open menu
await click(getMenuButton())
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
assertMenu({ state: MenuState.Visible })
assertMenuButtonLinkedWithMenu()
// Verify we have menu items
let items = getMenuItems()
expect(items).toHaveLength(3)
items.forEach((item) => assertMenuItem(item))
})
)
it(
'should not be possible to open a menu on right click',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Try to open the menu
await click(getMenuButton(), MouseButton.Right)
// Verify it is still closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
})
)
it(
'should not be possible to open a menu on click when the button is disabled',
suppressConsoleLogs(async () => {
render(
)
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
// Try to open the menu
await click(getMenuButton())
// Verify it is still closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
})
)
it(
'should be possible to close a menu on click',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
// Verify it is open
assertMenuButton({ state: MenuState.Visible })
// Click to close
await click(getMenuButton())
// Verify it is closed
assertMenuButton({ state: MenuState.InvisibleUnmounted })
assertMenu({ state: MenuState.InvisibleUnmounted })
})
)
it(
'should be a no-op when we click outside of a closed menu',
suppressConsoleLogs(async () => {
render(
)
// Verify that the window is closed
assertMenu({ state: MenuState.InvisibleUnmounted })
// Click something that is not related to the menu
await click(document.body)
// Should still be closed
assertMenu({ state: MenuState.InvisibleUnmounted })
})
)
it(
'should be possible to click outside of the menu which should close the menu',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
assertMenu({ state: MenuState.Visible })
// Click something that is not related to the menu
await click(document.body)
// Should be closed now
assertMenu({ state: MenuState.InvisibleUnmounted })
// Verify the button is focused again
assertActiveElement(getMenuButton())
})
)
it(
'should be possible to click outside of the menu which should close the menu (even if we press the menu button)',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
assertMenu({ state: MenuState.Visible })
// Click the menu button again
await click(getMenuButton())
// Should be closed now
assertMenu({ state: MenuState.InvisibleUnmounted })
// Verify the button is focused again
assertActiveElement(getMenuButton())
})
)
it(
'should be possible to click outside of the menu on another menu button which should close the current menu and open the new menu',
suppressConsoleLogs(async () => {
render(
)
let [button1, button2] = getMenuButtons()
// Click the first menu button
await click(button1)
expect(getMenus()).toHaveLength(1) // Only 1 menu should be visible
// Ensure the open menu is linked to the first button
assertMenuButtonLinkedWithMenu(button1, getMenu())
// Click the second menu button
await click(button2)
expect(getMenus()).toHaveLength(1) // Only 1 menu should be visible
// Ensure the open menu is linked to the second button
assertMenuButtonLinkedWithMenu(button2, getMenu())
})
)
it(
'should be possible to click outside of the menu, on an element which is within a focusable element, which closes the menu',
suppressConsoleLogs(async () => {
let focusFn = jest.fn()
render(
)
// Click the menu button
await click(getMenuButton())
// Ensure the menu is open
assertMenu({ state: MenuState.Visible })
// Click the span inside the button
await click(getByText('Next'))
// Ensure the menu is closed
assertMenu({ state: MenuState.InvisibleUnmounted })
// Ensure the outside button is focused
assertActiveElement(document.getElementById('btn'))
// Ensure that the focus button only got focus once (first click)
expect(focusFn).toHaveBeenCalledTimes(1)
})
)
// TODO: This test doesn't work — and it would be more suited for browser testing anyway
it.skip(
'should be possible to click outside of the menu into an iframe and which should close the menu',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
assertMenu({ state: MenuState.Visible })
// Click the input element in the iframe
await click(document.querySelector('iframe')?.contentDocument!.querySelector('button')!)
// Should be closed now
assertMenu({ state: MenuState.InvisibleUnmounted })
// Verify the button is focused again
assertActiveElement(getMenuButton())
})
)
it(
'should be possible to hover an item and make it active',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
let items = getMenuItems()
// We should be able to go to the second item
await mouseMove(items[1])
assertMenuLinkedWithMenuItem(items[1])
// We should be able to go to the first item
await mouseMove(items[0])
assertMenuLinkedWithMenuItem(items[0])
// We should be able to go to the last item
await mouseMove(items[2])
assertMenuLinkedWithMenuItem(items[2])
})
)
it(
'should make a menu item active when you move the mouse over it',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
let items = getMenuItems()
// We should be able to go to the second item
await mouseMove(items[1])
assertMenuLinkedWithMenuItem(items[1])
})
)
it(
'should be a no-op when we move the mouse and the menu item is already active',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
let items = getMenuItems()
// We should be able to go to the second item
await mouseMove(items[1])
assertMenuLinkedWithMenuItem(items[1])
await mouseMove(items[1])
// Nothing should be changed
assertMenuLinkedWithMenuItem(items[1])
})
)
it(
'should be a no-op when we move the mouse and the menu item is disabled',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
let items = getMenuItems()
await mouseMove(items[1])
assertNoActiveMenuItem()
})
)
it(
'should not be possible to hover an item that is disabled',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
let items = getMenuItems()
// Try to hover over item 1, which is disabled
await mouseMove(items[1])
// We should not have an active item now
assertNoActiveMenuItem()
})
)
it(
'should be possible to mouse leave an item and make it inactive',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
let items = getMenuItems()
// We should be able to go to the second item
await mouseMove(items[1])
assertMenuLinkedWithMenuItem(items[1])
await mouseLeave(items[1])
assertNoActiveMenuItem()
// We should be able to go to the first item
await mouseMove(items[0])
assertMenuLinkedWithMenuItem(items[0])
await mouseLeave(items[0])
assertNoActiveMenuItem()
// We should be able to go to the last item
await mouseMove(items[2])
assertMenuLinkedWithMenuItem(items[2])
await mouseLeave(items[2])
assertNoActiveMenuItem()
})
)
it(
'should be possible to mouse leave a disabled item and be a no-op',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
let items = getMenuItems()
// Try to hover over item 1, which is disabled
await mouseMove(items[1])
assertNoActiveMenuItem()
await mouseLeave(items[1])
assertNoActiveMenuItem()
})
)
it(
'should be possible to click a menu item, which closes the menu',
suppressConsoleLogs(async () => {
let clickHandler = jest.fn()
render(
)
// Open menu
await click(getMenuButton())
assertMenu({ state: MenuState.Visible })
let items = getMenuItems()
// We should be able to click the first item
await click(items[1])
assertMenu({ state: MenuState.InvisibleUnmounted })
expect(clickHandler).toHaveBeenCalled()
})
)
it(
'should be possible to click a menu item, which closes the menu and invokes the @click handler',
suppressConsoleLogs(async () => {
let clickHandler = jest.fn()
render(
)
// Open menu
await click(getMenuButton())
assertMenu({ state: MenuState.Visible })
// We should be able to click the first item
await click(getMenuItems()[1])
assertMenu({ state: MenuState.InvisibleUnmounted })
// Verify the callback has been called
expect(clickHandler).toHaveBeenCalledTimes(1)
// Let's re-open the window for now
await click(getMenuButton())
// Click the last item, which should close and invoke the handler
await click(getMenuItems()[2])
assertMenu({ state: MenuState.InvisibleUnmounted })
// Verify the callback has been called
expect(clickHandler).toHaveBeenCalledTimes(2)
})
)
it(
'should be possible to click a disabled menu item, which is a no-op',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
assertMenu({ state: MenuState.Visible })
let items = getMenuItems()
// We should be able to click the first item
await click(items[1])
assertMenu({ state: MenuState.Visible })
})
)
it(
'should be possible focus a menu item, so that it becomes active',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
assertMenu({ state: MenuState.Visible })
let items = getMenuItems()
// Verify that nothing is active yet
assertNoActiveMenuItem()
// We should be able to focus the first item
await focus(items[1])
assertMenuLinkedWithMenuItem(items[1])
})
)
it(
'should not be possible to focus a menu item which is disabled',
suppressConsoleLogs(async () => {
render(
)
// Open menu
await click(getMenuButton())
assertMenu({ state: MenuState.Visible })
let items = getMenuItems()
// We should not be able to focus the first item
await focus(items[1])
assertNoActiveMenuItem()
})
)
it(
'should not be possible to activate a disabled item',
suppressConsoleLogs(async () => {
let clickHandler = jest.fn()
render(
)
// Open menu
await click(getMenuButton())
assertMenu({ state: MenuState.Visible })
let items = getMenuItems()
await focus(items[0])
await click(items[1])
expect(clickHandler).not.toHaveBeenCalled()
// Activate the last item
await click(getMenuItems()[2])
expect(clickHandler).not.toHaveBeenCalled()
})
)
})