Skip to content

Commit ed5ab2f

Browse files
authored
Merge pull request #3755 from liam-hq/refact/table-options-common-logic
♻️ refactor(command palette): make common custom hook for Table and Column options
2 parents 4c6bc1f + 7324f77 commit ed5ab2f

File tree

5 files changed

+659
-131
lines changed

5 files changed

+659
-131
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
import { aColumn, aTable } from '@liam-hq/schema'
2+
import { render, screen, within } from '@testing-library/react'
3+
import userEvent from '@testing-library/user-event'
4+
import { ReactFlowProvider } from '@xyflow/react'
5+
import { Command } from 'cmdk'
6+
import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
7+
import type { ReactNode } from 'react'
8+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
9+
import {
10+
SchemaProvider,
11+
type SchemaProviderValue,
12+
UserEditingProvider,
13+
} from '../../../../../../stores'
14+
import * as UseTableSelection from '../../../../hooks'
15+
import { CommandPaletteProvider } from '../CommandPaletteProvider'
16+
import * as UseCommandPalette from '../CommandPaletteProvider/hooks'
17+
import { TableColumnOptions } from './TableColumnOptions'
18+
19+
beforeEach(() => {
20+
window.location.hash = ''
21+
})
22+
23+
afterEach(() => {
24+
vi.clearAllMocks()
25+
})
26+
27+
const mockSetCommandPaletteDialogOpen = vi.fn()
28+
const mockSelectTable = vi.fn()
29+
const mockWindowOpen = vi.fn()
30+
31+
const originalUseCommandPaletteOrThrow =
32+
UseCommandPalette.useCommandPaletteOrThrow
33+
vi.spyOn(UseCommandPalette, 'useCommandPaletteOrThrow').mockImplementation(
34+
() => {
35+
const original = originalUseCommandPaletteOrThrow()
36+
return {
37+
...original,
38+
setOpen: mockSetCommandPaletteDialogOpen,
39+
}
40+
},
41+
)
42+
const originalUseTableSelection = UseTableSelection.useTableSelection
43+
vi.spyOn(UseTableSelection, 'useTableSelection').mockImplementation(() => {
44+
const original = originalUseTableSelection()
45+
return {
46+
...original,
47+
selectTable: mockSelectTable,
48+
}
49+
})
50+
vi.spyOn(window, 'open').mockImplementation(mockWindowOpen)
51+
52+
const schema: SchemaProviderValue = {
53+
current: {
54+
tables: {
55+
users: aTable({
56+
name: 'users',
57+
columns: {
58+
id: aColumn({ name: 'id' }),
59+
created_at: aColumn({ name: 'created_at', type: 'timestamp' }),
60+
},
61+
}),
62+
posts: aTable({ name: 'posts' }),
63+
},
64+
enums: {},
65+
extensions: {},
66+
},
67+
}
68+
69+
const wrapper = ({ children }: { children: ReactNode }) => (
70+
<NuqsTestingAdapter>
71+
<ReactFlowProvider>
72+
<UserEditingProvider>
73+
<SchemaProvider {...schema}>
74+
<CommandPaletteProvider>
75+
<Command>{children}</Command>
76+
</CommandPaletteProvider>
77+
</SchemaProvider>
78+
</UserEditingProvider>
79+
</ReactFlowProvider>
80+
</NuqsTestingAdapter>
81+
)
82+
83+
it('displays selected table option and its columns', () => {
84+
render(<TableColumnOptions tableName="users" suggestion={null} />, {
85+
wrapper,
86+
})
87+
88+
// table option
89+
const userTableOption = screen.getByRole('option', { name: 'users' })
90+
expect(userTableOption).toBeInTheDocument()
91+
expect(within(userTableOption).getByRole('link')).toHaveAttribute(
92+
'href',
93+
'?active=users',
94+
)
95+
96+
// column options
97+
const idColumnOption = screen.getByRole('option', { name: 'id' })
98+
expect(idColumnOption).toBeInTheDocument()
99+
expect(within(idColumnOption).getByRole('link')).toHaveAttribute(
100+
'href',
101+
'?active=users#users__columns__id',
102+
)
103+
const createdAtColumnOption = screen.getByRole('option', {
104+
name: 'created_at',
105+
})
106+
expect(createdAtColumnOption).toBeInTheDocument()
107+
expect(within(createdAtColumnOption).getByRole('link')).toHaveAttribute(
108+
'href',
109+
'?active=users#users__columns__created_at',
110+
)
111+
112+
// other tables are not displayed
113+
expect(screen.queryByRole('link', { name: 'posts' })).not.toBeInTheDocument()
114+
})
115+
116+
describe('mouse interactions', () => {
117+
describe('table option', () => {
118+
it('moves to clicked table in ERD and closes the dialog', async () => {
119+
const user = userEvent.setup()
120+
render(<TableColumnOptions tableName="users" suggestion={null} />, {
121+
wrapper,
122+
})
123+
124+
await user.click(screen.getByRole('link', { name: 'users' }))
125+
126+
expect(mockSelectTable).toHaveBeenCalled()
127+
expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
128+
})
129+
130+
it('does nothing with ⌘ + click (default browser action: open in new tab)', async () => {
131+
const user = userEvent.setup()
132+
render(<TableColumnOptions tableName="users" suggestion={null} />, {
133+
wrapper,
134+
})
135+
136+
await user.keyboard('{Meta>}')
137+
await user.click(screen.getByRole('link', { name: 'users' }))
138+
await user.keyboard('{/Meta}')
139+
140+
expect(mockSelectTable).not.toHaveBeenCalled()
141+
expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
142+
})
143+
})
144+
145+
describe('column options', () => {
146+
it('moves to clicked table column in ERD and closes the dialog', async () => {
147+
const user = userEvent.setup()
148+
render(<TableColumnOptions tableName="users" suggestion={null} />, {
149+
wrapper,
150+
})
151+
152+
await user.click(screen.getByRole('link', { name: 'created_at' }))
153+
154+
expect(mockSelectTable).toHaveBeenCalled()
155+
expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
156+
expect(window.location.hash).toBe('#users__columns__created_at')
157+
})
158+
159+
it('does nothing with ⌘ + click (default browser action: open in new tab)', async () => {
160+
const user = userEvent.setup()
161+
render(<TableColumnOptions tableName="users" suggestion={null} />, {
162+
wrapper,
163+
})
164+
165+
await user.keyboard('{Meta>}')
166+
await user.click(screen.getByRole('link', { name: 'created_at' }))
167+
await user.keyboard('{/Meta}')
168+
169+
expect(mockSelectTable).not.toHaveBeenCalled()
170+
expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
171+
172+
// FIXME: jsdom doesn't implement behavior of ⌘ + click to open a link in a new tab, but it changes the URL of the current window
173+
// So, the following assertion doesn't pass
174+
// expect(window.location.hash).toBe('')
175+
})
176+
})
177+
})
178+
179+
describe('keyboard interactions', () => {
180+
describe('table option', () => {
181+
it('moves to suggested table in ERD and closes the dialog on Enter', async () => {
182+
const user = userEvent.setup()
183+
render(
184+
<TableColumnOptions
185+
tableName="users"
186+
suggestion={{ type: 'table', name: 'users' }}
187+
/>,
188+
{ wrapper },
189+
)
190+
191+
await user.keyboard('{Enter}')
192+
193+
expect(mockSelectTable).toHaveBeenCalledWith({
194+
displayArea: 'main',
195+
tableId: 'users',
196+
})
197+
expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
198+
expect(window.location.hash).toBe('')
199+
200+
// other functions are not called
201+
expect(mockWindowOpen).not.toHaveBeenCalled()
202+
})
203+
204+
it('opens suggested table in another tab on ⌘Enter', async () => {
205+
const user = userEvent.setup()
206+
render(
207+
<TableColumnOptions
208+
tableName="users"
209+
suggestion={{ type: 'table', name: 'users' }}
210+
/>,
211+
{ wrapper },
212+
)
213+
214+
await user.keyboard('{Meta>}{Enter}{/Meta}')
215+
216+
expect(mockWindowOpen).toHaveBeenCalledWith('?active=users')
217+
218+
// other functions are not called
219+
expect(mockSelectTable).not.toHaveBeenCalled()
220+
expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
221+
})
222+
})
223+
224+
describe('column option', () => {
225+
it('moves to suggested table column in ERD and closes the dialog on Enter', async () => {
226+
const user = userEvent.setup()
227+
render(
228+
<TableColumnOptions
229+
tableName="users"
230+
suggestion={{
231+
type: 'column',
232+
tableName: 'users',
233+
columnName: 'created_at',
234+
}}
235+
/>,
236+
{ wrapper },
237+
)
238+
239+
await user.keyboard('{Enter}')
240+
241+
expect(mockSelectTable).toHaveBeenCalledWith({
242+
displayArea: 'main',
243+
tableId: 'users',
244+
})
245+
expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
246+
expect(window.location.hash).toBe('#users__columns__created_at')
247+
248+
// other functions are not called
249+
expect(mockWindowOpen).not.toHaveBeenCalled()
250+
})
251+
252+
it('opens suggested table column in another tab on ⌘Enter', async () => {
253+
const user = userEvent.setup()
254+
render(
255+
<TableColumnOptions
256+
tableName="users"
257+
suggestion={{
258+
type: 'column',
259+
tableName: 'users',
260+
columnName: 'created_at',
261+
}}
262+
/>,
263+
{ wrapper },
264+
)
265+
266+
await user.keyboard('{Meta>}{Enter}{/Meta}')
267+
268+
expect(mockWindowOpen).toHaveBeenCalledWith(
269+
'?active=users#users__columns__created_at',
270+
)
271+
272+
// other functions are not called
273+
expect(mockSelectTable).not.toHaveBeenCalled()
274+
expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
275+
})
276+
})
277+
278+
it('does nothing on Enter when suggestion is not table', async () => {
279+
const user = userEvent.setup()
280+
render(
281+
<TableColumnOptions
282+
tableName="users"
283+
suggestion={{ type: 'command', name: 'copy link' }}
284+
/>,
285+
{ wrapper },
286+
)
287+
288+
await user.keyboard('{Meta>}{Enter}{/Meta}')
289+
290+
expect(mockWindowOpen).not.toHaveBeenCalled()
291+
expect(mockSelectTable).not.toHaveBeenCalled()
292+
expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
293+
})
294+
})

0 commit comments

Comments
 (0)