diff --git a/src/renderer/src/components/right-sidebar/ssh-port-forward-dialog.ime-enter.test.tsx b/src/renderer/src/components/right-sidebar/ssh-port-forward-dialog.ime-enter.test.tsx new file mode 100644 index 00000000000..c03751ba3e3 --- /dev/null +++ b/src/renderer/src/components/right-sidebar/ssh-port-forward-dialog.ime-enter.test.tsx @@ -0,0 +1,65 @@ +// @vitest-environment happy-dom +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' +import { endComposition } from '@/lib/ime-composition-event.test-support' +import { SshPortForwardDialog } from './ssh-port-forward-dialog' + +const addPortForward = vi.fn(() => Promise.resolve()) + +beforeEach(() => { + vi.useFakeTimers() + addPortForward.mockClear() + Object.defineProperty(window, 'api', { + configurable: true, + value: { ssh: { addPortForward } } + }) +}) + +afterEach(() => { + cleanup() + vi.useRealTimers() +}) + +// happy-dom has no implicit submission, so a keydown left unprevented submits the form here. +function pressEnterWithImplicitSubmit(field: HTMLElement): boolean { + const notPrevented = fireEvent.keyDown(field, { key: 'Enter', keyCode: 13 }) + if (notPrevented) { + fireEvent.submit(field.closest('form')!) + } + return notPrevented +} + +describe('SshPortForwardDialog IME confirmation', () => { + it.each([ + { field: 'Remote Port', value: '8080', sent: { remotePort: 8080 } }, + { field: 'Local Port', value: '18080', sent: { localPort: 18080 } }, + { field: 'Remote Host', value: '開発機', sent: { remoteHost: '開発機' } }, + { field: 'Label (optional)', value: '開発', sent: { label: '開発' } } + ])('keeps the confirm redispatch in $field from submitting', async ({ field, value, sent }) => { + render( + + ) + const input = screen.getByLabelText(field) + + fireEvent.compositionStart(input) + fireEvent.keyDown(input, { key: 'Process', keyCode: 229, isComposing: true }) + fireEvent.change(input, { target: { value } }) + endComposition(input, value) + fireEvent.keyUp(input, { key: 'Process', keyCode: 229 }) + + await act(async () => { + expect(pressEnterWithImplicitSubmit(input)).toBe(false) + }) + expect(addPortForward).not.toHaveBeenCalled() + + await act(async () => { + expect(pressEnterWithImplicitSubmit(input)).toBe(true) + }) + expect(addPortForward).toHaveBeenCalledOnce() + expect(addPortForward).toHaveBeenCalledWith(expect.objectContaining(sent)) + }) +}) diff --git a/src/renderer/src/components/right-sidebar/ssh-port-forward-dialog.tsx b/src/renderer/src/components/right-sidebar/ssh-port-forward-dialog.tsx index bbd7108cc2f..24a44768cf4 100644 --- a/src/renderer/src/components/right-sidebar/ssh-port-forward-dialog.tsx +++ b/src/renderer/src/components/right-sidebar/ssh-port-forward-dialog.tsx @@ -9,6 +9,7 @@ import { import { Button } from '@/components/ui/button' import type { PortForwardEntry } from '../../../../shared/ssh-types' import { translate } from '@/i18n/i18n' +import { ImeInput } from '@/lib/ime-text-field' // Remap privileged ports to avoid EACCES when binding locally without root. function safeLocalPort(remotePort: number): number { @@ -218,7 +219,7 @@ function PortForwardForm({ {translate('auto.components.right.sidebar.PortsPanel.9e5a4118b0', 'Remote Port')} - {translate('auto.components.right.sidebar.PortsPanel.b950b1948b', 'Local Port')} - {translate('auto.components.right.sidebar.PortsPanel.a3721a50b0', 'Remote Host')} - setRemoteHost(e.target.value)} @@ -276,7 +277,7 @@ function PortForwardForm({ {translate('auto.components.right.sidebar.PortsPanel.8dfed0a15c', 'Label (optional)')} - setLabel(e.target.value)}