Skip to content

Commit bdd56dd

Browse files
committed
♻️ move logics of TableOptions into a custom hook
1 parent 227ab97 commit bdd56dd

File tree

3 files changed

+224
-45
lines changed

3 files changed

+224
-45
lines changed

frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/TableOptions.tsx

Lines changed: 4 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,21 @@
11
import { Table2 } from '@liam-hq/ui'
22
import { Command } from 'cmdk'
3-
import { type FC, useCallback, useEffect } from 'react'
3+
import type { FC } from 'react'
44
import { useSchemaOrThrow } from '../../../../../../stores'
5-
import { useTableSelection } from '../../../../hooks'
65
import { getTableLinkHref } from '../../../../utils/url/getTableLinkHref'
7-
import { useCommandPaletteOrThrow } from '../CommandPaletteProvider'
86
import type { CommandPaletteSuggestion } from '../types'
97
import { getSuggestionText } from '../utils'
108
import styles from './CommandPaletteOptions.module.css'
9+
import { useTableOptionSelect } from './hooks/useTableOptionSelect'
1110

1211
type Props = {
1312
suggestion: CommandPaletteSuggestion | null
1413
}
1514

1615
export const TableOptions: FC<Props> = ({ suggestion }) => {
17-
const { setOpen } = useCommandPaletteOrThrow()
18-
1916
const schema = useSchemaOrThrow()
20-
const { selectTable } = useTableSelection()
21-
22-
const goToERD = useCallback(
23-
(tableName: string) => {
24-
selectTable({ tableId: tableName, displayArea: 'main' })
25-
setOpen(false)
26-
},
27-
[selectTable, setOpen],
28-
)
29-
30-
// Select option by pressing [Enter] key (with/without ⌘ key)
31-
useEffect(() => {
32-
// It doesn't subscribe a keydown event listener if the suggestion type is not "table"
33-
if (suggestion?.type !== 'table') return
3417

35-
const down = (event: KeyboardEvent) => {
36-
const suggestedTableName = suggestion.name
37-
38-
if (event.key === 'Enter') {
39-
event.preventDefault()
40-
41-
if (event.metaKey || event.ctrlKey) {
42-
window.open(getTableLinkHref(suggestedTableName))
43-
} else {
44-
goToERD(suggestedTableName)
45-
}
46-
}
47-
}
48-
49-
document.addEventListener('keydown', down)
50-
return () => document.removeEventListener('keydown', down)
51-
}, [suggestion, goToERD])
18+
const { tableOptionSelectHandler } = useTableOptionSelect(suggestion)
5219

5320
return (
5421
<Command.Group heading="Tables">
@@ -60,15 +27,7 @@ export const TableOptions: FC<Props> = ({ suggestion }) => {
6027
<a
6128
className={styles.item}
6229
href={getTableLinkHref(table.name)}
63-
onClick={(event) => {
64-
// Do not call preventDefault to allow the default link behavior when ⌘ key is pressed
65-
if (event.ctrlKey || event.metaKey) {
66-
return
67-
}
68-
69-
event.preventDefault()
70-
goToERD(table.name)
71-
}}
30+
onClick={(event) => tableOptionSelectHandler(event, table.name)}
7231
>
7332
<Table2 className={styles.itemIcon} />
7433
<span className={styles.itemText}>{table.name}</span>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { render, renderHook, screen } from '@testing-library/react'
2+
import userEvent from '@testing-library/user-event'
3+
import { ReactFlowProvider } from '@xyflow/react'
4+
import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
5+
import type { FC, ReactNode } from 'react'
6+
import { afterEach, describe, expect, it, vi } from 'vitest'
7+
import { UserEditingProvider } from '../../../../../../../stores'
8+
import * as UseTableSelection from '../../../../../hooks'
9+
import { CommandPaletteProvider } from '../../CommandPaletteProvider'
10+
import * as UseCommandPalette from '../../CommandPaletteProvider/hooks'
11+
import type { CommandPaletteSuggestion } from '../../types'
12+
import { useTableOptionSelect } from './useTableOptionSelect'
13+
14+
afterEach(() => {
15+
vi.clearAllMocks()
16+
})
17+
18+
const mockSetCommandPaletteDialogOpen = vi.fn()
19+
const mockSelectTable = vi.fn()
20+
const mockWindowOpen = vi.fn()
21+
22+
const originalUseCommandPaletteOrThrow =
23+
UseCommandPalette.useCommandPaletteOrThrow
24+
vi.spyOn(UseCommandPalette, 'useCommandPaletteOrThrow').mockImplementation(
25+
() => {
26+
const original = originalUseCommandPaletteOrThrow()
27+
return {
28+
...original,
29+
setOpen: mockSetCommandPaletteDialogOpen,
30+
}
31+
},
32+
)
33+
const originalUseTableSelection = UseTableSelection.useTableSelection
34+
vi.spyOn(UseTableSelection, 'useTableSelection').mockImplementation(() => {
35+
const original = originalUseTableSelection()
36+
return {
37+
...original,
38+
selectTable: mockSelectTable,
39+
}
40+
})
41+
vi.spyOn(window, 'open').mockImplementation(mockWindowOpen)
42+
43+
const wrapper = ({ children }: { children: ReactNode }) => (
44+
<NuqsTestingAdapter>
45+
<ReactFlowProvider>
46+
<UserEditingProvider>
47+
<CommandPaletteProvider>{children}</CommandPaletteProvider>
48+
</UserEditingProvider>
49+
</ReactFlowProvider>
50+
</NuqsTestingAdapter>
51+
)
52+
53+
describe('keyboard interactions', () => {
54+
describe('when suggestion is a table', () => {
55+
it('moves to suggested table in ERD and closes the dialog on Enter', async () => {
56+
const user = userEvent.setup()
57+
renderHook(() => useTableOptionSelect({ type: 'table', name: 'users' }), {
58+
wrapper,
59+
})
60+
61+
await user.keyboard('{Enter}')
62+
63+
expect(mockSelectTable).toHaveBeenCalledWith({
64+
displayArea: 'main',
65+
tableId: 'users',
66+
})
67+
expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
68+
69+
// other functions are not called
70+
expect(mockWindowOpen).not.toHaveBeenCalled()
71+
})
72+
73+
it('opens suggested table in another tab on ⌘Enter', async () => {
74+
const user = userEvent.setup()
75+
renderHook(() => useTableOptionSelect({ type: 'table', name: 'users' }), {
76+
wrapper,
77+
})
78+
79+
await user.keyboard('{Meta>}{Enter}{/Meta}')
80+
81+
expect(mockWindowOpen).toHaveBeenCalledWith('?active=users')
82+
83+
// other functions are not called
84+
expect(mockSelectTable).not.toHaveBeenCalled()
85+
expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
86+
})
87+
})
88+
89+
describe.each<CommandPaletteSuggestion | null>([
90+
{ type: 'command', name: 'copy link' },
91+
{ type: 'column', tableName: 'users', columnName: 'created_at' },
92+
null,
93+
])('when suggestion is other than tables, suggestion = %o', (suggestion) => {
94+
it('does nothing when on Enter', async () => {
95+
const user = userEvent.setup()
96+
renderHook(() => useTableOptionSelect(suggestion), { wrapper })
97+
98+
await user.keyboard('{Enter}')
99+
100+
expect(mockSelectTable).not.toHaveBeenCalled()
101+
expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
102+
expect(mockWindowOpen).not.toHaveBeenCalled()
103+
})
104+
105+
it('does nothing when on ⌘Enter', async () => {
106+
const user = userEvent.setup()
107+
renderHook(() => useTableOptionSelect(suggestion), { wrapper })
108+
109+
await user.keyboard('{Meta>}{Enter}{/Meta}')
110+
111+
expect(mockSelectTable).not.toHaveBeenCalled()
112+
expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
113+
expect(mockWindowOpen).not.toHaveBeenCalled()
114+
})
115+
})
116+
})
117+
118+
describe('tableOptionSelectHandler', () => {
119+
// a component for testing the "tableOptionSelectHandler" method of the hook
120+
// in the test cases, we simulate the method clicking a link of a table option
121+
const TableOptionLinkWithSelectHandler: FC<{ tableName: string }> = ({
122+
tableName,
123+
}) => {
124+
const { tableOptionSelectHandler } = useTableOptionSelect(null)
125+
126+
return (
127+
<a
128+
href="/"
129+
// use the handler by passing the event object and table name
130+
onClick={(event) => tableOptionSelectHandler(event, tableName)}
131+
>
132+
table option link
133+
</a>
134+
)
135+
}
136+
137+
it('moves to clicked table in ERD and closes the dialog', async () => {
138+
const user = userEvent.setup()
139+
render(<TableOptionLinkWithSelectHandler tableName="follows" />, {
140+
wrapper,
141+
})
142+
143+
await user.click(screen.getByRole('link', { name: 'table option link' }))
144+
145+
expect(mockSelectTable).toHaveBeenCalled()
146+
expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
147+
})
148+
149+
it('does nothing with ⌘ + click (default browser action: open in new tab)', async () => {
150+
const user = userEvent.setup()
151+
render(<TableOptionLinkWithSelectHandler tableName="follows" />, {
152+
wrapper,
153+
})
154+
155+
await user.keyboard('{Meta>}')
156+
await user.click(screen.getByRole('link', { name: 'table option link' }))
157+
await user.keyboard('{/Meta}')
158+
159+
expect(mockSelectTable).not.toHaveBeenCalled()
160+
expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
161+
})
162+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { useCallback, useEffect } from 'react'
2+
import { useTableSelection } from '../../../../../../erd/hooks'
3+
import { getTableLinkHref } from '../../../../../utils'
4+
import { useCommandPaletteOrThrow } from '../../CommandPaletteProvider'
5+
import type { CommandPaletteSuggestion } from '../../types'
6+
7+
export const useTableOptionSelect = (
8+
suggestion: CommandPaletteSuggestion | null,
9+
) => {
10+
const { setOpen } = useCommandPaletteOrThrow()
11+
12+
const { selectTable } = useTableSelection()
13+
const goToERD = useCallback(
14+
(tableName: string) => {
15+
selectTable({ tableId: tableName, displayArea: 'main' })
16+
setOpen(false)
17+
},
18+
[selectTable, setOpen],
19+
)
20+
21+
const tableOptionSelectHandler = useCallback(
22+
(event: React.MouseEvent, tableName: string) => {
23+
// Do not call preventDefault to allow the default link behavior when ⌘ key is pressed
24+
if (event.ctrlKey || event.metaKey) {
25+
return
26+
}
27+
28+
event.preventDefault()
29+
goToERD(tableName)
30+
},
31+
[goToERD],
32+
)
33+
34+
// Select option by pressing [Enter] key (with/without ⌘ key)
35+
useEffect(() => {
36+
// It doesn't subscribe a keydown event listener if the suggestion type is not "table"
37+
if (suggestion?.type !== 'table') return
38+
39+
const down = (event: KeyboardEvent) => {
40+
const suggestedTableName = suggestion.name
41+
42+
if (event.key === 'Enter') {
43+
event.preventDefault()
44+
45+
if (event.metaKey || event.ctrlKey) {
46+
window.open(getTableLinkHref(suggestedTableName))
47+
} else {
48+
goToERD(suggestedTableName)
49+
}
50+
}
51+
}
52+
53+
document.addEventListener('keydown', down)
54+
return () => document.removeEventListener('keydown', down)
55+
}, [suggestion, goToERD])
56+
57+
return { tableOptionSelectHandler }
58+
}

0 commit comments

Comments
 (0)