Files
k88936 4db6d28603
Project CI / Repository checks (push) Successful in 3m6s
Project CI / Frontend tests (push) Successful in 3m44s
Project CI / Backend tests (push) Successful in 8m16s
Project CI / Native shell tests (push) Successful in 20m48s
UI编辑器一批细节优化 (#278)
* 提示词:完善组件建议,增加容器背景与节点大小一致的实现
* 支持Delete删除节点 ctrl + -缩放
* 步骤结束弹窗提醒
![shotmd-1788599068.jpg](/attachments/99a1e4aa-d778-41bf-9dda-0df703723407)
![shotmd-1788605840.jpg](/attachments/31ab883d-32bc-467b-9308-616b83387a79)

* UI 树快捷删除
![shotmd-1788605655.jpg](/attachments/3d451201-3f68-4eb6-b558-864ecf0cb5a3)

* zoom滑动条样式

before:
![image.png](/attachments/e4a04304-e6eb-4da6-9352-99e093fee42e)
after:
![shotmd-1788606577.jpg](/attachments/6cdc377c-f2a3-4a7e-b78e-772a3fdb13b7)

Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/278
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-09-08 11:56:36 +08:00

177 lines
5.3 KiB
TypeScript

// @vitest-environment jsdom
import { fireEvent } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import {
handlePreviewZoomKeyDown,
type PreviewZoomKeyboardActions,
type PreviewZoomKeyboardContext,
previewZoomUsesMetaModifier,
} from '../src/view/ui-editor/components/preview/previewZoomKeyboard';
function createActions(): PreviewZoomKeyboardActions {
return {
fit: vi.fn(),
resetToActualSize: vi.fn(),
zoomIn: vi.fn(),
zoomOut: vi.fn(),
};
}
function dispatchShortcut({
actions = createActions(),
context = {},
event,
target,
}: {
actions?: PreviewZoomKeyboardActions;
context?: Partial<PreviewZoomKeyboardContext>;
event: KeyboardEventInit;
target?: HTMLElement;
}) {
const resolvedTarget = target ?? document.createElement('div');
document.body.append(resolvedTarget);
const handler = vi.fn((keyboardEvent: KeyboardEvent) =>
handlePreviewZoomKeyDown(
keyboardEvent,
{
hasZoomableViewport: true,
isFocused: false,
isHovered: true,
usesMetaModifier: false,
...context,
},
actions,
),
);
resolvedTarget.addEventListener('keydown', handler);
fireEvent.keyDown(resolvedTarget, event);
const keyboardEvent = handler.mock.calls[0]?.[0];
return { actions, handler, keyboardEvent };
}
afterEach(() => {
document.body.replaceChildren();
});
describe('preview zoom keyboard shortcuts', () => {
it.each([
{ key: '+', code: 'Equal' },
{ key: '=', code: 'Equal' },
{ key: '+', code: 'NumpadAdd' },
])('zooms in for the supported main and numpad keys (%o)', (event) => {
const { actions, keyboardEvent } = dispatchShortcut({
event: { ...event, ctrlKey: true, cancelable: true, repeat: true },
});
expect(actions.zoomIn).toHaveBeenCalledTimes(1);
expect(keyboardEvent?.defaultPrevented).toBe(true);
});
it.each([
{ key: '-', code: 'Minus' },
{ key: '-', code: 'NumpadSubtract' },
])('zooms out for the supported main and numpad keys (%o)', (event) => {
const { actions, keyboardEvent } = dispatchShortcut({
event: { ...event, ctrlKey: true, cancelable: true },
});
expect(actions.zoomOut).toHaveBeenCalledTimes(1);
expect(keyboardEvent?.defaultPrevented).toBe(true);
});
it('does not treat underscore as zoom out', () => {
const { actions, keyboardEvent } = dispatchShortcut({
event: {
key: '_',
code: 'Minus',
ctrlKey: true,
shiftKey: true,
cancelable: true,
},
});
expect(actions.zoomOut).not.toHaveBeenCalled();
expect(keyboardEvent?.defaultPrevented).toBe(false);
});
it.each([
{ key: '0', action: 'fit' as const },
{ key: '1', action: 'resetToActualSize' as const },
])('keeps the existing $key shortcut', ({ key, action }) => {
const { actions } = dispatchShortcut({
event: { key, ctrlKey: true, cancelable: true },
});
expect(actions[action]).toHaveBeenCalledTimes(1);
});
it('uses Cmd on Apple platforms and Ctrl elsewhere', () => {
expect(previewZoomUsesMetaModifier('MacIntel')).toBe(true);
expect(previewZoomUsesMetaModifier('iPad')).toBe(true);
expect(previewZoomUsesMetaModifier('Win32')).toBe(false);
const appleActions = createActions();
dispatchShortcut({
actions: appleActions,
context: { usesMetaModifier: true },
event: { key: '=', ctrlKey: true, cancelable: true },
});
expect(appleActions.zoomIn).not.toHaveBeenCalled();
dispatchShortcut({
actions: appleActions,
context: { usesMetaModifier: true },
event: { key: '=', metaKey: true, cancelable: true },
});
expect(appleActions.zoomIn).toHaveBeenCalledTimes(1);
});
it.each([
{ isHovered: false, isFocused: false, hasZoomableViewport: true },
{ isHovered: true, isFocused: false, hasZoomableViewport: false },
])('leaves inactive preview shortcuts to the host (%o)', (context) => {
const { actions, keyboardEvent } = dispatchShortcut({
context,
event: { key: '=', ctrlKey: true, cancelable: true },
});
expect(actions.zoomIn).not.toHaveBeenCalled();
expect(keyboardEvent?.defaultPrevented).toBe(false);
});
it('works while the preview is focused without being hovered', () => {
const { actions } = dispatchShortcut({
context: { isFocused: true, isHovered: false },
event: { key: '=', ctrlKey: true, cancelable: true },
});
expect(actions.zoomIn).toHaveBeenCalledTimes(1);
});
it.each(['input', 'button', 'a'])('ignores interactive %s targets', (tag) => {
const target = document.createElement(tag);
if (target instanceof HTMLAnchorElement) target.href = '#preview';
const { actions, keyboardEvent } = dispatchShortcut({
target,
event: { key: '=', ctrlKey: true, cancelable: true },
});
expect(actions.zoomIn).not.toHaveBeenCalled();
expect(keyboardEvent?.defaultPrevented).toBe(false);
});
it('does not override an event already handled by another control', () => {
const target = document.createElement('div');
target.addEventListener('keydown', (event) => event.preventDefault(), {
once: true,
});
const { actions } = dispatchShortcut({
target,
event: { key: '=', ctrlKey: true, cancelable: true },
});
expect(actions.zoomIn).not.toHaveBeenCalled();
});
});