Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions wework/e2e/desktop/modules/conversation-layout.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ async function verifyShortConversationLayout({ composerSelector, control }) {
taskRowsBeforeConversation,
'WEWORK_DESKTOP_E2E_FRESH_CHAT'
)
await verifyConversationRenameSpaceDoesNotDrag(control, shortConversationTaskRowTestId)
await control.command('click', '[data-testid="new-chat-button"]')
await control.command('waitFor', composerSelector, { timeoutMs: WORKBENCH_READY_TIMEOUT_MS })
await control.command('clickWhenEnabled', `[data-testid="${shortConversationTaskRowTestId}"]`, {
Expand Down Expand Up @@ -233,6 +234,38 @@ async function verifyShortConversationLayout({ composerSelector, control }) {
return shortConversationTaskRowTestId
}

async function verifyConversationRenameSpaceDoesNotDrag(control, taskRowTestId) {
const taskId = taskRowTestId.replace('runtime-local-task-row-', '')
const taskRow = `[data-testid="${taskRowTestId}"]`
const renameMenuItem = `[data-testid="runtime-local-task-menu-rename-${taskId}"]`
const renameInput = `[data-testid="rename-runtime-local-task-input-${taskId}"]`
const renameCloseButton = `[data-testid="rename-runtime-local-task-input-${taskId}-close-button"]`
const sortable = `[data-sidebar-sortable-id]:has(${taskRow})`

await control.command('contextMenu', taskRow)
await control.command('waitFor', renameMenuItem, {
visible: true,
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})
await control.command('click', renameMenuItem)
await control.command('waitFor', renameInput, {
visible: true,
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})
await control.command('press', renameInput, { key: 'Space' })
assert.equal(
await control.command('getAttribute', sortable, { value: 'data-dragging' }),
'',
'Typing a space in the conversation rename input started sidebar dragging'
)
await control.command('click', renameCloseButton)
await waitForSnapshot(
control,
snapshot => !snapshot.testIds.includes(`rename-runtime-local-task-input-${taskId}`),
'The rename dialog did not close after the space-key drag regression check'
)
}

function countTextOccurrences(value, search) {
if (!search) return 0
let count = 0
Expand Down
4 changes: 3 additions & 1 deletion wework/scripts/ai-verify.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const corsHeaders = {
function usage() {
console.error(`Usage:
pnpm --filter wework ai:verify start
pnpm --filter wework ai:verify <capture|capture-popout|capture-workspace|snapshot|debug|click|click-at|click-then-macrotask|seed-local-project|terminal-snapshot|reload|close-to-tray|request-close|dismiss-popout|drag|drop-file|drop-paths|fill|hover|metrics|navigate|paste-paths|pointer-move|press|scroll-into-view|select-text|show-popout|system-drag-drop|wait-for|window-focus-snapshot|text|status|stop> --session PATH [options]
pnpm --filter wework ai:verify <capture|capture-popout|capture-workspace|snapshot|debug|click|click-at|click-then-macrotask|context-menu|seed-local-project|terminal-snapshot|reload|close-to-tray|request-close|dismiss-popout|drag|drop-file|drop-paths|fill|get-attribute|hover|metrics|navigate|paste-paths|pointer-move|press|scroll-into-view|select-text|show-popout|system-drag-drop|wait-for|window-focus-snapshot|text|status|stop> --session PATH [options]

Options:
--codex-home-initialization true
Expand Down Expand Up @@ -407,6 +407,7 @@ async function main() {
click: 'click',
'click-at': 'clickAt',
'click-then-macrotask': 'clickThenMacrotask',
'context-menu': 'contextMenu',
'seed-local-project': 'seedLocalProject',
'terminal-snapshot': 'readLocalTerminalSnapshot',
reload: 'reloadApp',
Expand All @@ -417,6 +418,7 @@ async function main() {
'drop-file': 'dropFile',
'drop-paths': 'dropPaths',
fill: 'fill',
'get-attribute': 'getAttribute',
hover: 'hover',
metrics: 'getElementMetrics',
navigate: 'navigate',
Expand Down
36 changes: 30 additions & 6 deletions wework/src/components/layout/DesktopSidebar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2115,6 +2115,13 @@ describe('DesktopSidebar', () => {
expect(onOpenRuntimeTask).toHaveBeenCalledTimes(2)
expect(onReorderRuntimeProjectTasks).not.toHaveBeenCalled()

firstSortable.focus()
fireEvent.keyDown(firstSortable, { key: ' ', code: 'Space' })
expect(firstSortable).toHaveAttribute('data-dragging', 'true')
await waitForSidebarPointerSensorCleanup()
fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' })
await waitFor(() => expect(firstSortable).not.toHaveAttribute('data-dragging'))

for (const [pointerId, target] of [firstTitleSpace, firstTrailing, firstActions].entries()) {
fireEvent.pointerDown(target, {
button: 0,
Expand Down Expand Up @@ -2226,9 +2233,10 @@ describe('DesktopSidebar', () => {
expect(screen.getByTestId('runtime-local-task-time-chat-time')).toHaveTextContent('2m')
})

test('renames a runtime conversation from double click dialog', async () => {
test('renames a runtime conversation from its context menu without space starting drag', async () => {
const user = userEvent.setup()
const onOpenRuntimeTask = vi.fn()
const onReorderRuntimeProjectTasks = vi.fn().mockResolvedValue(undefined)
const onRenameRuntimeTask = vi.fn().mockResolvedValue(undefined)

renderSidebar({
Expand All @@ -2248,7 +2256,7 @@ describe('DesktopSidebar', () => {
taskId: 'codex-rename',
workspacePath: '/workspace/chats/chat-rename',
workspaceKind: 'chat',
title: '对齐需求核心点',
title: '对齐 需求 核心点',
runtime: 'codex',
},
],
Expand All @@ -2257,18 +2265,34 @@ describe('DesktopSidebar', () => {
totalTasks: 1,
},
onOpenRuntimeTask,
onReorderRuntimeProjectTasks,
onRenameRuntimeTask,
})

await user.dblClick(screen.getByTestId('runtime-local-task-row-codex-rename'))
const taskRow = screen.getByTestId('runtime-local-task-row-codex-rename')
const sortable = document.querySelector(
'[data-sidebar-sortable-id="local-device:codex-rename"]'
) as HTMLElement

fireEvent.contextMenu(taskRow, {
clientX: 20,
clientY: 10,
})
await user.click(screen.getByTestId('runtime-local-task-menu-rename-codex-rename'))

expect(screen.getByTestId('rename-runtime-local-task-input-codex-rename')).toHaveValue(
'对齐需求核心点'
'对齐 需求 核心点'
)
expect(screen.getByText('保持简短且易于识别')).toBeInTheDocument()
expect(onReorderRuntimeProjectTasks).not.toHaveBeenCalled()

const renameInput = screen.getByTestId('rename-runtime-local-task-input-codex-rename')
await user.type(renameInput, ' ')
expect(sortable).not.toHaveAttribute('data-dragging')
expect(onReorderRuntimeProjectTasks).not.toHaveBeenCalled()

await user.clear(screen.getByTestId('rename-runtime-local-task-input-codex-rename'))
await user.type(screen.getByTestId('rename-runtime-local-task-input-codex-rename'), '对齐方案')
await user.clear(renameInput)
await user.type(renameInput, '对齐方案')
await user.click(screen.getByTestId('confirm-rename-runtime-local-task-codex-rename'))

await waitFor(() => {
Expand Down
23 changes: 19 additions & 4 deletions wework/src/components/layout/SidebarSortableList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { useMemo, useState, type ReactNode } from 'react'
import { useCallback, useMemo, useState, type ReactNode } from 'react'
import { cn } from '@/lib/utils'
import { SidebarPointerSensor } from './sidebarDragActivator'

Expand All @@ -40,12 +40,27 @@ interface SortableItemProps {
}

function SortableItem({ id, disabled, children }: SortableItemProps) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging, isOver } =
useSortable({ id, disabled })
const {
attributes,
listeners,
setNodeRef,
setActivatorNodeRef,
transform,
transition,
isDragging,
isOver,
} = useSortable({ id, disabled })
const setSortableNodeRef = useCallback(
(node: HTMLElement | null) => {
setNodeRef(node)
setActivatorNodeRef(node)
},
[setActivatorNodeRef, setNodeRef]
)

return (
<div
ref={setNodeRef}
ref={setSortableNodeRef}
data-sidebar-sortable-id={id}
data-dragging={isDragging ? 'true' : undefined}
className={cn(
Expand Down
16 changes: 16 additions & 0 deletions wework/src/e2e/automation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,19 @@ async function dragDesktopControlElement(command: DesktopControlCommand): Promis
return element.textContent?.trim() ?? ''
}

function contextMenuDesktopControlElement(selector: string): string {
const element = findDesktopControlElements(selector)[0]
if (!element) throw new Error(`Unable to find selector "${selector}"`)
element.dispatchEvent(
new MouseEvent('contextmenu', {
...desktopControlEventOptions(element),
button: 2,
buttons: 0,
})
)
return element.textContent?.trim() ?? ''
}

async function waitForDesktopControlElement(command: DesktopControlCommand): Promise<string> {
const timeoutMs = command.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS
const startedAt = Date.now()
Expand Down Expand Up @@ -968,6 +981,8 @@ async function executeDesktopControlCommand(command: DesktopControlCommand): Pro
return ''
case 'drag':
return dragDesktopControlElement(command)
case 'contextMenu':
return contextMenuDesktopControlElement(command.selector)
case 'dropFile':
return dropDesktopControlFile(command)
case 'dropPaths':
Expand Down Expand Up @@ -1359,6 +1374,7 @@ async function executeDesktopControlCommand(command: DesktopControlCommand): Pro
new KeyboardEvent(type, { ...keyboardEvent, bubbles: true, cancelable: true })
)
}
await waitForDesktopControlTick()
return element.textContent?.trim() ?? ''
}
case 'select': {
Expand Down
2 changes: 2 additions & 0 deletions wework/src/e2e/desktop-control-keyboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ describe('parseDesktopControlKey', () => {
['Meta+Minus', { key: '-', metaKey: true }],
['Meta+0', { key: '0', metaKey: true }],
['Control+Shift+M', { key: 'M', ctrlKey: true, shiftKey: true }],
['Space', { key: ' ', code: 'Space' }],
['Escape', { key: 'Escape', code: 'Escape' }],
])('parses %s', (value, expected) => {
expect(parseDesktopControlKey(value)).toMatchObject(expected)
})
Expand Down
11 changes: 11 additions & 0 deletions wework/src/e2e/desktop-control-keyboard.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
const MODIFIER_KEYS = new Set(['Alt', 'Control', 'Meta', 'Shift'])
const NAMED_CODE_KEYS = new Set([
'Space',
'Enter',
'Escape',
'Tab',
'ArrowUp',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
])

export function parseDesktopControlKey(value: string): KeyboardEventInit {
if (value === '+') return { key: '+' }
Expand All @@ -9,6 +19,7 @@ export function parseDesktopControlKey(value: string): KeyboardEventInit {

return {
key,
...(NAMED_CODE_KEYS.has(keyPart) ? { code: keyPart } : {}),
altKey: parts.includes('Alt'),
ctrlKey: parts.includes('Control'),
metaKey: parts.includes('Meta'),
Expand Down
Loading