import { act, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Fragment, useState } from 'react'
import { beforeEach, describe, expect, it } from 'vitest'
import { CommandProvider } from '../CommandProvider'
import type { ICommand } from '../types'
import { useCommands, useRegisterCommands } from './commandsHooks'
const oneCommand = [
{
id: 'command-one',
name: 'Command one',
action: () => alert('One!'),
},
]
const twoCommands = [
...oneCommand,
{
id: 'command-two',
name: 'Command two',
action: () => alert('Two!'),
},
]
let mockCommandMenuRenderCount = 0
const MockCommandMenu = () => {
mockCommandMenuRenderCount++
const commandSections = useCommands()
return (
{commandSections.map((section) => (
{section.name}
{section.commands.map((cmd) => (
- {cmd.name}
))}
))}
)
}
const SimpleComponent = ({ commands }: { commands: Array }) => {
useRegisterCommands('Section', commands)
return Simple
}
const ToggledComponent = ({ commands }: { commands: Array }) => {
const [visible, setVisible] = useState(true)
return (
<>
{visible && }
>
)
}
const DisableableComponent = ({ commands }: { commands: Array }) => {
const [enabled, setIsEnabled] = useState(true)
useRegisterCommands('Section', commands, { enabled })
return (
)
}
const ChangeableCommands = () => {
const [commands, setCommands] = useState([{ ...twoCommands[0] }])
useRegisterCommands('Section', commands, { deps: [commands] })
return
}
const RerenderableComponent = () => {
const [renderFlag, setRerenderFlag] = useState(0)
// Define commands inline so the array identity changes each render
const commands = [...oneCommand]
useRegisterCommands('Section', commands) // Note no dependency array
return (
<>
Render flag: {renderFlag}
>
)
}
describe('useRegisterCommand', () => {
beforeEach(() => {
mockCommandMenuRenderCount = 0
})
it('registers a command when component mounts', () => {
render(
)
// @ts-ignore
expect(screen.getByRole('heading')).toHaveTextContent('Section')
expect(screen.getByRole('listitem')).toHaveTextContent('Command one')
})
it('registers multiple commands when component mounts', () => {
render(
)
expect(screen.getByRole('heading')).toHaveTextContent('Section')
const items = screen.getAllByRole('listitem')
expect(items).toHaveLength(2)
expect(items[0]).toHaveTextContent('Command one')
expect(items[1]).toHaveTextContent('Command two')
})
it('unregisters and reregisters a command when component unmounts and remounts', async () => {
act(() => {
render(
)
})
expect(screen.getByRole('listitem')).toHaveTextContent('Command one')
act(() => {
userEvent.click(screen.getByRole('button'))
})
await waitFor(() => {
expect(screen.queryByRole('listitem')).toBeNull()
})
act(() => {
userEvent.click(screen.getByRole('button'))
})
await waitFor(() => {
expect(screen.getByRole('listitem')).toHaveTextContent('Command one')
})
})
it('unregisters multiple commands when component unmounts', async () => {
act(() => {
render(
)
})
expect(screen.getAllByRole('listitem')).toHaveLength(2)
act(() => {
userEvent.click(screen.getByRole('button'))
})
await waitFor(() => {
expect(screen.queryAllByRole('listitem')).toHaveLength(0)
})
})
it('unregisters and reregisters a command when enabled changes', async () => {
act(() => {
render(
)
})
expect(screen.getByRole('listitem')).toHaveTextContent('Command one')
act(() => {
userEvent.click(screen.getByRole('button'))
})
await waitFor(() => {
expect(screen.queryByRole('listitem')).toBeNull()
})
act(() => {
userEvent.click(screen.getByRole('button'))
})
await waitFor(() => {
expect(screen.getByRole('listitem')).toHaveTextContent('Command one')
})
})
it('reregisters commands when dependencies change', async () => {
act(() => {
render(
)
})
expect(screen.getByText('Command one')).toBeVisible()
act(() => {
userEvent.click(screen.getByRole('button'))
})
await waitFor(() => {
expect(screen.queryByText('Command one')).toBeNull()
})
expect(screen.getByRole('listitem')).toHaveTextContent('Command two')
})
it("doesn't trigger command menu rerender when dependencies are unchanged", async () => {
expect(mockCommandMenuRenderCount).toBe(0)
act(() => {
render(
)
})
// React renders twice in dev mode
expect(mockCommandMenuRenderCount).toBe(2)
act(() => {
userEvent.click(screen.getByRole('button'))
})
await waitFor(() => screen.getByText('Render flag: 1'))
expect(mockCommandMenuRenderCount).toBe(2)
})
})