fix connection and release regressions

This commit is contained in:
cita
2026-03-18 18:00:59 +08:00
parent d928e067c1
commit 38e46e165b
16 changed files with 528 additions and 63 deletions
+22 -4
View File
@@ -10,6 +10,9 @@ type CenteredModalProps = {
footer?: React.ReactNode;
maxWidth?: number;
bodyStyle?: React.CSSProperties;
closeOnBackdrop?: boolean;
closeOnEscape?: boolean;
showCloseButton?: boolean;
};
export default function CenteredModal({
@@ -20,6 +23,9 @@ export default function CenteredModal({
footer,
maxWidth = 860,
bodyStyle,
closeOnBackdrop = false,
closeOnEscape = false,
showCloseButton = true,
}: CenteredModalProps) {
const presence = useAnimatedVisibility(open, 220);
@@ -33,7 +39,7 @@ export default function CenteredModal({
}, [open]);
useEffect(() => {
if (!open || typeof document === 'undefined') return;
if (!open || !closeOnEscape || typeof document === 'undefined') return;
const handleKeydown = (event: KeyboardEvent) => {
if (event.key === 'Escape') onClose();
};
@@ -41,21 +47,33 @@ export default function CenteredModal({
return () => {
document.removeEventListener('keydown', handleKeydown);
};
}, [open, onClose]);
}, [closeOnEscape, open, onClose]);
if (!presence.shouldRender) return null;
const modal = (
<div
className={`modal-backdrop ${presence.isVisible ? '' : 'is-closing'}`.trim()}
onClick={onClose}
onClick={closeOnBackdrop ? onClose : undefined}
>
<div
className={`modal-content ${presence.isVisible ? '' : 'is-closing'}`.trim()}
style={{ maxWidth }}
onClick={(event) => event.stopPropagation()}
>
<div className="modal-header">{title}</div>
<div className="modal-header">
<div className="modal-title">{title}</div>
{showCloseButton ? (
<button
type="button"
className="modal-close-button"
onClick={onClose}
aria-label="关闭弹框"
>
×
</button>
) : null}
</div>
<div className="modal-body" style={bodyStyle}>
{children}
</div>
+39 -1
View File
@@ -1,8 +1,46 @@
import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import { act, create } from 'react-test-renderer';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import CenteredModal from './CenteredModal.js';
describe('CenteredModal component', () => {
it('does not close on backdrop click by default and exposes an explicit close button', async () => {
const onClose = vi.fn();
let root: ReturnType<typeof create> | null = null;
try {
await act(async () => {
root = create(
<CenteredModal open onClose={onClose} title="测试弹框">
<div>content</div>
</CenteredModal>,
);
});
const closeButton = root.root.find((node) => (
node.type === 'button'
&& node.props['aria-label'] === '关闭弹框'
));
const backdrop = root.root.find((node) => (
typeof node.props.className === 'string'
&& node.props.className.includes('modal-backdrop')
));
expect(backdrop.props.onClick).toBeUndefined();
expect(onClose).not.toHaveBeenCalled();
await act(async () => {
closeButton.props.onClick();
});
expect(onClose).toHaveBeenCalledTimes(1);
} finally {
root?.unmount();
}
});
it('uses the shared centered modal shell pattern', () => {
const source = readFileSync(resolve(process.cwd(), 'src/web/components/CenteredModal.tsx'), 'utf8');