Files
supabase/packages/ui-patterns/CommandMenu/api/hooks/commandsHooks.test.tsx
T
Ivan Vasilov 326c7535f6 chore: Bump vulnerable deps. (#34395)
* Bump all testing libs to force babel to upgrade itself.

* Remove react-hooks package, it's included in @testing-library/react.

* Add await to all useEvent calls.

* Remove duplicate babel/helpers.

* Deduplicate babel/core.

* Bump vite to non-vuln version.

* Bump estree-util-value-to-estree.
2025-04-16 10:02:06 +02:00

251 lines
6.5 KiB
TypeScript

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 (
<div>
{commandSections.map((section) => (
<Fragment key={section.id}>
<h2>{section.name}</h2>
<ul>
{section.commands.map((cmd) => (
<li key={cmd.id}>{cmd.name}</li>
))}
</ul>
</Fragment>
))}
</div>
)
}
const SimpleComponent = ({ commands }: { commands: Array<ICommand> }) => {
useRegisterCommands('Section', commands)
return <span>Simple</span>
}
const ToggledComponent = ({ commands }: { commands: Array<ICommand> }) => {
const [visible, setVisible] = useState(true)
return (
<>
<button onClick={() => setVisible((vis) => !vis)}>Toggle</button>
{visible && <SimpleComponent commands={commands} />}
</>
)
}
const DisableableComponent = ({ commands }: { commands: Array<ICommand> }) => {
const [enabled, setIsEnabled] = useState(true)
useRegisterCommands('Section', commands, { enabled })
return (
<button onClick={() => setIsEnabled((enabled) => !enabled)}>
{enabled ? 'Disable commands' : 'Enable commands'}
</button>
)
}
const ChangeableCommands = () => {
const [commands, setCommands] = useState([{ ...twoCommands[0] }])
useRegisterCommands('Section', commands, { deps: [commands] })
return <button onClick={() => setCommands([{ ...twoCommands[1] }])}>Use second command</button>
}
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 (
<>
<button onClick={() => setRerenderFlag((flag) => ++flag)}>Rerender</button>
<span>Render flag: {renderFlag}</span>
</>
)
}
describe('useRegisterCommand', () => {
beforeEach(() => {
mockCommandMenuRenderCount = 0
})
it('registers a command when component mounts', () => {
render(
<CommandProvider>
<SimpleComponent commands={oneCommand} />
<MockCommandMenu />
</CommandProvider>
)
// @ts-ignore
expect(screen.getByRole('heading')).toHaveTextContent('Section')
expect(screen.getByRole('listitem')).toHaveTextContent('Command one')
})
it('registers multiple commands when component mounts', () => {
render(
<CommandProvider>
<SimpleComponent commands={twoCommands} />
<MockCommandMenu />
</CommandProvider>
)
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(
<CommandProvider>
<ToggledComponent commands={oneCommand} />
<MockCommandMenu />
</CommandProvider>
)
})
expect(screen.getByRole('listitem')).toHaveTextContent('Command one')
await act(async () => {
await userEvent.click(screen.getByRole('button'))
})
await waitFor(() => {
expect(screen.queryByRole('listitem')).toBeNull()
})
await act(async () => {
await userEvent.click(screen.getByRole('button'))
})
await waitFor(() => {
expect(screen.getByRole('listitem')).toHaveTextContent('Command one')
})
})
it('unregisters multiple commands when component unmounts', async () => {
act(() => {
render(
<CommandProvider>
<ToggledComponent commands={twoCommands} />
<MockCommandMenu />
</CommandProvider>
)
})
expect(screen.getAllByRole('listitem')).toHaveLength(2)
await act(async () => {
await 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(
<CommandProvider>
<DisableableComponent commands={oneCommand} />
<MockCommandMenu />
</CommandProvider>
)
})
expect(screen.getByRole('listitem')).toHaveTextContent('Command one')
await act(async () => {
await userEvent.click(screen.getByRole('button'))
})
await waitFor(() => {
expect(screen.queryByRole('listitem')).toBeNull()
})
await act(async () => {
await userEvent.click(screen.getByRole('button'))
})
await waitFor(() => {
expect(screen.getByRole('listitem')).toHaveTextContent('Command one')
})
})
it('reregisters commands when dependencies change', async () => {
act(() => {
render(
<CommandProvider>
<ChangeableCommands />
<MockCommandMenu />
</CommandProvider>
)
})
expect(screen.getByText('Command one')).toBeVisible()
await act(async () => {
await 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(
<CommandProvider>
<RerenderableComponent />
<MockCommandMenu />
</CommandProvider>
)
})
// React renders twice in dev mode
expect(mockCommandMenuRenderCount).toBe(2)
await act(async () => {
await userEvent.click(screen.getByRole('button'))
})
await waitFor(() => screen.getByText('Render flag: 1'))
expect(mockCommandMenuRenderCount).toBe(2)
})
})