Skip to content

Commit 242207e

Browse files
committed
improvement(tables): rename columns on double click
1 parent 0811127 commit 242207e

4 files changed

Lines changed: 199 additions & 32 deletions

File tree

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { act } from 'react'
5+
import { createRoot, type Root } from 'react-dom/client'
6+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
7+
import type { WorkflowGroup } from '@/lib/table'
8+
import type { DisplayColumn } from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/types'
9+
10+
vi.mock('@sim/emcn', () => ({
11+
cn: (...values: Array<string | false | null | undefined>) => values.filter(Boolean).join(' '),
12+
}))
13+
14+
vi.mock('@sim/emcn/icons', () => ({
15+
ChevronDown: () => null,
16+
}))
17+
18+
vi.mock(
19+
'@/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/column-type-icon',
20+
() => ({ ColumnTypeIcon: () => null })
21+
)
22+
23+
vi.mock(
24+
'@/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/header-label',
25+
() => ({ HeaderLabel: ({ label }: { label: string }) => <span>{label}</span> })
26+
)
27+
28+
vi.mock(
29+
'@/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/workflow-group-meta-cell',
30+
() => ({ ColumnOptionsMenu: () => null })
31+
)
32+
33+
import { ColumnHeaderMenu } from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/column-header-menu'
34+
35+
let container: HTMLDivElement
36+
let root: Root
37+
38+
const DEFAULT_COLUMN: DisplayColumn = {
39+
id: 'col-name',
40+
key: 'col-name',
41+
name: 'Name',
42+
type: 'string',
43+
groupSize: 1,
44+
groupStartColIndex: 0,
45+
headerLabel: 'Name',
46+
isGroupStart: true,
47+
}
48+
49+
beforeEach(() => {
50+
globalThis.IS_REACT_ACT_ENVIRONMENT = true
51+
container = document.createElement('div')
52+
document.body.appendChild(container)
53+
act(() => {
54+
root = createRoot(container)
55+
})
56+
})
57+
58+
afterEach(() => {
59+
act(() => root.unmount())
60+
container.remove()
61+
})
62+
63+
function renderHeader({
64+
column = DEFAULT_COLUMN,
65+
workflowGroups,
66+
onColumnSelect = vi.fn(),
67+
onOpenConfig = vi.fn(),
68+
onRenameColumn = vi.fn(),
69+
}: {
70+
column?: DisplayColumn
71+
workflowGroups?: WorkflowGroup[]
72+
onColumnSelect?: (colIndex: number, shiftKey: boolean) => void
73+
onOpenConfig?: (columnName: string) => void
74+
onRenameColumn?: (columnName: string) => void
75+
} = {}) {
76+
act(() => {
77+
root.render(
78+
<table>
79+
<thead>
80+
<tr>
81+
<ColumnHeaderMenu
82+
column={column}
83+
colIndex={2}
84+
isRenaming={false}
85+
isColumnSelected={false}
86+
renameValue=''
87+
onRenameValueChange={vi.fn()}
88+
onRenameSubmit={vi.fn()}
89+
onRenameCancel={vi.fn()}
90+
onColumnSelect={onColumnSelect}
91+
onInsertLeft={vi.fn()}
92+
onInsertRight={vi.fn()}
93+
onRenameColumn={onRenameColumn}
94+
onDeleteColumn={vi.fn()}
95+
onResizeStart={vi.fn()}
96+
onResize={vi.fn()}
97+
onResizeEnd={vi.fn()}
98+
onAutoResize={vi.fn()}
99+
onOpenConfig={onOpenConfig}
100+
workflowGroups={workflowGroups}
101+
/>
102+
</tr>
103+
</thead>
104+
</table>
105+
)
106+
})
107+
108+
const headerButton = Array.from(container.querySelectorAll('button')).find((button) =>
109+
button.textContent?.includes(column.workflowGroupId ? column.headerLabel : column.name)
110+
)
111+
if (!headerButton) throw new Error('Column header button was not rendered')
112+
return headerButton
113+
}
114+
115+
describe('ColumnHeaderMenu interactions', () => {
116+
it('selects the column without opening configuration on a single click', () => {
117+
const onColumnSelect = vi.fn()
118+
const onOpenConfig = vi.fn()
119+
const onRenameColumn = vi.fn()
120+
const headerButton = renderHeader({ onColumnSelect, onOpenConfig, onRenameColumn })
121+
122+
act(() => headerButton.click())
123+
124+
expect(onColumnSelect).toHaveBeenCalledWith(2, false)
125+
expect(onOpenConfig).not.toHaveBeenCalled()
126+
expect(onRenameColumn).not.toHaveBeenCalled()
127+
})
128+
129+
it('selects before starting inline rename on a double click', () => {
130+
const onColumnSelect = vi.fn()
131+
const onRenameColumn = vi.fn()
132+
const headerButton = renderHeader({ onColumnSelect, onRenameColumn })
133+
134+
act(() => {
135+
headerButton.click()
136+
headerButton.click()
137+
headerButton.dispatchEvent(new MouseEvent('dblclick', { bubbles: true }))
138+
})
139+
140+
expect(onColumnSelect).toHaveBeenCalledTimes(2)
141+
expect(onRenameColumn).toHaveBeenCalledWith('col-name')
142+
})
143+
144+
it('does not rename a workflow-output column on double click', () => {
145+
const onRenameColumn = vi.fn()
146+
const headerButton = renderHeader({
147+
column: { ...DEFAULT_COLUMN, workflowGroupId: 'workflow-group' },
148+
workflowGroups: [
149+
{
150+
id: 'workflow-group',
151+
workflowId: 'workflow-1',
152+
type: 'manual',
153+
outputs: [{ blockId: 'block-1', path: 'result', columnName: 'col-name' }],
154+
},
155+
],
156+
onRenameColumn,
157+
})
158+
159+
act(() => {
160+
headerButton.dispatchEvent(new MouseEvent('dblclick', { bubbles: true }))
161+
})
162+
163+
expect(onRenameColumn).not.toHaveBeenCalled()
164+
})
165+
166+
it('renames an enrichment column on double click', () => {
167+
const onRenameColumn = vi.fn()
168+
const headerButton = renderHeader({
169+
column: { ...DEFAULT_COLUMN, workflowGroupId: 'enrichment-group' },
170+
workflowGroups: [
171+
{
172+
id: 'enrichment-group',
173+
workflowId: '',
174+
enrichmentId: 'company-domain',
175+
type: 'enrichment',
176+
outputs: [{ blockId: '', path: '', outputId: 'domain', columnName: 'col-name' }],
177+
},
178+
],
179+
onRenameColumn,
180+
})
181+
182+
act(() => {
183+
headerButton.dispatchEvent(new MouseEvent('dblclick', { bubbles: true }))
184+
})
185+
186+
expect(onRenameColumn).toHaveBeenCalledWith('col-name')
187+
})
188+
})

‎apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/column-header-menu.tsx‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ interface ColumnHeaderMenuProps {
3030
onColumnSelect: (colIndex: number, shiftKey: boolean) => void
3131
onInsertLeft: (columnName: string) => void
3232
onInsertRight: (columnName: string) => void
33-
/** Starts inline renaming for a plain or enrichment column. */
33+
/** Starts inline renaming when a plain or enrichment header is double-clicked. */
3434
onRenameColumn?: (columnName: string) => void
3535
/** Opens the table targeted by a Reference column. */
3636
onGoToReferenceTable?: (tableId: string) => void
@@ -241,9 +241,11 @@ export const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({
241241
}
242242
if (isRenaming) return
243243
onColumnSelect(colIndex, e.shiftKey)
244-
if (!e.shiftKey) {
245-
onOpenConfig(column.key)
246-
}
244+
}
245+
246+
function handleHeaderDoubleClick() {
247+
if (isRenaming || isWorkflowOutput) return
248+
onRenameColumn?.(column.key)
247249
}
248250

249251
function handleChevronClick(e: React.MouseEvent) {
@@ -350,6 +352,7 @@ export const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({
350352
type='button'
351353
className='flex min-w-0 flex-1 cursor-pointer items-center px-2 py-[7px] outline-hidden'
352354
onClick={handleHeaderClick}
355+
onDoubleClick={handleHeaderDoubleClick}
353356
draggable={false}
354357
>
355358
<ColumnTypeIcon
@@ -381,7 +384,6 @@ export const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({
381384
onOpenConfig={onOpenConfig}
382385
schemaLockedReason={schemaLockedReason}
383386
deleteLockedReason={deleteLockedReason}
384-
onRenameColumn={isWorkflowOutput ? undefined : onRenameColumn}
385387
onGoToReferenceTable={onGoToReferenceTable}
386388
onInsertLeft={onInsertLeft}
387389
onInsertRight={onInsertRight}

‎apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/workflow-group-meta-cell.test.tsx‎

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ vi.mock('@sim/emcn/icons', () => ({
4242
Pin: () => null,
4343
PinOff: () => null,
4444
PlayOutline: () => null,
45-
Settings: () => null,
4645
SquareArrowUpRight: () => null,
4746
Trash: () => null,
4847
Workflow: () => null,
@@ -84,11 +83,7 @@ afterEach(() => {
8483
container.remove()
8584
})
8685

87-
function renderMenu(
88-
column: ColumnDefinition,
89-
onGoToReferenceTable: (tableId: string) => void,
90-
onRenameColumn?: (columnName: string) => void
91-
) {
86+
function renderMenu(column: ColumnDefinition, onGoToReferenceTable: (tableId: string) => void) {
9287
act(() => {
9388
root.render(
9489
<ColumnOptionsMenu
@@ -108,7 +103,6 @@ function renderMenu(
108103
onDeleteColumn={vi.fn()}
109104
onOpenConfig={vi.fn()}
110105
onGoToReferenceTable={onGoToReferenceTable}
111-
onRenameColumn={onRenameColumn}
112106
/>
113107
)
114108
})
@@ -204,12 +198,9 @@ describe('ColumnHeaderMenu read-only Reference navigation', () => {
204198
})
205199

206200
describe('ColumnOptionsMenu editing', () => {
207-
it('starts inline rename from the column menu', () => {
208-
const onRenameColumn = vi.fn()
209-
renderMenu({ id: 'col-name', name: 'Name', type: 'string' }, vi.fn(), onRenameColumn)
210-
211-
act(() => findButton('Rename column')?.click())
201+
it('keeps rename out of the column menu', () => {
202+
renderMenu({ id: 'col-name', name: 'Name', type: 'string' }, vi.fn())
212203

213-
expect(onRenameColumn).toHaveBeenCalledWith('col-name')
204+
expect(findButton('Rename column')).toBeUndefined()
214205
})
215206
})

‎apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/workflow-group-meta-cell.tsx‎

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ interface ColumnOptionsMenuProps {
7676
schemaLockedReason?: string
7777
/** Why deleting is unavailable; disables the destructive column row. */
7878
deleteLockedReason?: string
79-
/** Starts inline renaming for a plain or enrichment column. */
80-
onRenameColumn?: (columnName: string) => void
8179
/** Opens the table targeted by a Reference column. */
8280
onGoToReferenceTable?: (tableId: string) => void
8381
onInsertLeft: (columnName: string) => void
@@ -152,7 +150,6 @@ export function ColumnOptionsMenu({
152150
onOpenConfig,
153151
schemaLockedReason,
154152
deleteLockedReason,
155-
onRenameColumn,
156153
onGoToReferenceTable,
157154
onInsertLeft,
158155
onInsertRight,
@@ -279,17 +276,6 @@ export function ColumnOptionsMenu({
279276
Edit column
280277
</DropdownMenuItem>
281278
</MenuRow>
282-
{onRenameColumn && (
283-
<MenuRow reason={schemaLockedReason}>
284-
<DropdownMenuItem
285-
disabled={Boolean(schemaLockedReason)}
286-
onSelect={() => onRenameColumn(column.key)}
287-
>
288-
<Pencil />
289-
Rename column
290-
</DropdownMenuItem>
291-
</MenuRow>
292-
)}
293279
{onPinToggle && (
294280
<DropdownMenuItem onSelect={() => onPinToggle(column.key)}>
295281
{isPinned ? <PinOff /> : <Pin />}

0 commit comments

Comments
 (0)