[codex] tighten update center and proxy debug UX (#325)
* tighten update center and proxy debug UX * Address update center review feedback
This commit is contained in:
@@ -28,18 +28,22 @@ export default function CenteredModal({
|
||||
showCloseButton = true,
|
||||
}: CenteredModalProps) {
|
||||
const presence = useAnimatedVisibility(open, 220);
|
||||
const canUsePortal = typeof document !== 'undefined'
|
||||
&& !!document.body
|
||||
&& typeof document.body.appendChild === 'function'
|
||||
&& typeof document.body.removeChild === 'function';
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || typeof document === 'undefined') return;
|
||||
if (!open || !canUsePortal) return;
|
||||
const previousOverflow = document.body.style.overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
return () => {
|
||||
document.body.style.overflow = previousOverflow;
|
||||
};
|
||||
}, [open]);
|
||||
}, [canUsePortal, open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !closeOnEscape || typeof document === 'undefined') return;
|
||||
if (!open || !closeOnEscape || !canUsePortal) return;
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') onClose();
|
||||
};
|
||||
@@ -47,7 +51,7 @@ export default function CenteredModal({
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
};
|
||||
}, [closeOnEscape, open, onClose]);
|
||||
}, [canUsePortal, closeOnEscape, open, onClose]);
|
||||
|
||||
if (!presence.shouldRender) return null;
|
||||
|
||||
@@ -82,5 +86,5 @@ export default function CenteredModal({
|
||||
</div>
|
||||
);
|
||||
|
||||
return typeof document !== 'undefined' ? createPortal(modal, document.body) : modal;
|
||||
return canUsePortal ? createPortal(modal, document.body) : modal;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { afterEach, 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', () => {
|
||||
const originalDocument = globalThis.document;
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.document = originalDocument;
|
||||
});
|
||||
|
||||
it('does not close on backdrop click by default and exposes an explicit close button', async () => {
|
||||
const onClose = vi.fn();
|
||||
let root!: WebTestRenderer;
|
||||
@@ -49,4 +55,28 @@ describe('CenteredModal component', () => {
|
||||
expect(source).toContain('useAnimatedVisibility');
|
||||
expect(source).toContain('createPortal');
|
||||
});
|
||||
|
||||
it('renders safely without touching document.body or listeners when no usable body exists', async () => {
|
||||
globalThis.document = {
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
} as unknown as Document;
|
||||
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
<CenteredModal open onClose={() => {}} title="测试弹框" closeOnEscape>
|
||||
<div>content</div>
|
||||
</CenteredModal>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.toJSON()).toBeTruthy();
|
||||
expect(globalThis.document.addEventListener).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
root?.unmount();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user