Skip to content

Commit c7f0c4d

Browse files
committed
improvement(tables): rename columns on double click
1 parent 334575d commit c7f0c4d

4 files changed

Lines changed: 199 additions & 27 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
@@ -26,7 +26,7 @@ interface ColumnHeaderMenuProps {
2626
onColumnSelect: (colIndex: number, shiftKey: boolean) => void
2727
onInsertLeft: (columnName: string) => void
2828
onInsertRight: (columnName: string) => void
29-
/** Starts inline renaming for a plain or enrichment column. */
29+
/** Starts inline renaming when a plain or enrichment header is double-clicked. */
3030
onRenameColumn?: (columnName: string) => void
3131
/** Opens the table targeted by a Reference column. */
3232
onGoToReferenceTable?: (tableId: string) => void
@@ -235,9 +235,11 @@ export const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({
235235
}
236236
if (isRenaming) return
237237
onColumnSelect(colIndex, e.shiftKey)
238-
if (!e.shiftKey) {
239-
onOpenConfig(column.key)
240-
}
238+
}
239+
240+
function handleHeaderDoubleClick() {
241+
if (isRenaming || isWorkflowOutput) return
242+
onRenameColumn?.(column.key)
241243
}
242244

243245
function handleChevronClick(e: React.MouseEvent) {
@@ -331,6 +333,7 @@ export const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({
331333
type='button'
332334
className='flex min-w-0 flex-1 cursor-pointer items-center px-2 py-[7px] outline-none'
333335
onClick={handleHeaderClick}
336+
onDoubleClick={handleHeaderDoubleClick}
334337
draggable={false}
335338
>
336339
<ColumnTypeIcon
@@ -360,7 +363,6 @@ export const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({
360363
column={column}
361364
deleteLabel={deleteLabel}
362365
onOpenConfig={onOpenConfig}
363-
onRenameColumn={isWorkflowOutput ? undefined : onRenameColumn}
364366
onGoToReferenceTable={onGoToReferenceTable}
365367
onInsertLeft={onInsertLeft}
366368
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
@@ -35,7 +35,6 @@ vi.mock('@sim/emcn/icons', () => ({
3535
Pin: () => null,
3636
PinOff: () => null,
3737
PlayOutline: () => null,
38-
Settings: () => null,
3938
SquareArrowUpRight: () => null,
4039
Trash: () => null,
4140
Workflow: () => null,
@@ -74,11 +73,7 @@ afterEach(() => {
7473
container.remove()
7574
})
7675

77-
function renderMenu(
78-
column: ColumnDefinition,
79-
onGoToReferenceTable: (tableId: string) => void,
80-
onRenameColumn?: (columnName: string) => void
81-
) {
76+
function renderMenu(column: ColumnDefinition, onGoToReferenceTable: (tableId: string) => void) {
8277
act(() => {
8378
root.render(
8479
<ColumnOptionsMenu
@@ -98,7 +93,6 @@ function renderMenu(
9893
onDeleteColumn={vi.fn()}
9994
onOpenConfig={vi.fn()}
10095
onGoToReferenceTable={onGoToReferenceTable}
101-
onRenameColumn={onRenameColumn}
10296
/>
10397
)
10498
})
@@ -142,12 +136,9 @@ describe('ColumnOptionsMenu Reference navigation', () => {
142136
})
143137

144138
describe('ColumnOptionsMenu editing', () => {
145-
it('starts inline rename from the column menu', () => {
146-
const onRenameColumn = vi.fn()
147-
renderMenu({ id: 'col-name', name: 'Name', type: 'string' }, vi.fn(), onRenameColumn)
148-
149-
act(() => findButton('Rename column')?.click())
139+
it('keeps rename out of the column menu', () => {
140+
renderMenu({ id: 'col-name', name: 'Name', type: 'string' }, vi.fn())
150141

151-
expect(onRenameColumn).toHaveBeenCalledWith('col-name')
142+
expect(findButton('Rename column')).toBeUndefined()
152143
})
153144
})

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,6 @@ interface ColumnOptionsMenuProps {
7171
* it leaves the group with siblings). */
7272
deleteLabel?: string
7373
onOpenConfig: (columnName: string) => void
74-
/** Starts inline renaming for a plain or enrichment column. */
75-
onRenameColumn?: (columnName: string) => void
7674
/** Opens the table targeted by a Reference column. */
7775
onGoToReferenceTable?: (tableId: string) => void
7876
onInsertLeft: (columnName: string) => void
@@ -127,7 +125,6 @@ export function ColumnOptionsMenu({
127125
column,
128126
deleteLabel,
129127
onOpenConfig,
130-
onRenameColumn,
131128
onGoToReferenceTable,
132129
onInsertLeft,
133130
onInsertRight,
@@ -246,12 +243,6 @@ export function ColumnOptionsMenu({
246243
<Pencil />
247244
Edit column
248245
</DropdownMenuItem>
249-
{onRenameColumn && (
250-
<DropdownMenuItem onSelect={() => onRenameColumn(column.key)}>
251-
<Pencil />
252-
Rename column
253-
</DropdownMenuItem>
254-
)}
255246
{onPinToggle && (
256247
<DropdownMenuItem onSelect={() => onPinToggle(column.key)}>
257248
{isPinned ? <PinOff /> : <Pin />}

0 commit comments

Comments
 (0)