mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
Improve React Doctor score and badge (#1127)
* fix(react doctor): improve quality score and badge * fix(react doctor): address review feedback * fix(review): address final bot feedback
This commit is contained in:
@@ -32,6 +32,7 @@ const testState = vi.hoisted(() => ({
|
||||
quoteInsertNumber: undefined as number | undefined,
|
||||
quoteInsertRequestId: 0,
|
||||
quoteInsertSelectedText: '',
|
||||
dragHandler: undefined as ((state: { active: boolean; event: Pick<Event, 'preventDefault'>; offset: [number, number] }) => void) | undefined,
|
||||
replyIndex: undefined as number | undefined,
|
||||
resetPublishReplyOptionsMock: vi.fn(),
|
||||
resolvedCommunityAddress: undefined as string | undefined,
|
||||
@@ -208,7 +209,10 @@ vi.mock('@react-spring/web', async () => {
|
||||
});
|
||||
|
||||
vi.mock('@use-gesture/react', () => ({
|
||||
useDrag: () => () => ({}),
|
||||
useDrag: (handler: (state: { active: boolean; event: Pick<Event, 'preventDefault'>; offset: [number, number] }) => void) => {
|
||||
testState.dragHandler = handler;
|
||||
return () => ({});
|
||||
},
|
||||
}));
|
||||
|
||||
let container: HTMLDivElement;
|
||||
@@ -292,6 +296,7 @@ describe('ReplyModal', () => {
|
||||
testState.quoteInsertNumber = undefined;
|
||||
testState.quoteInsertRequestId = 0;
|
||||
testState.quoteInsertSelectedText = '';
|
||||
testState.dragHandler = undefined;
|
||||
testState.replyIndex = undefined;
|
||||
testState.resetPublishReplyOptionsMock.mockReset();
|
||||
testState.resolvedCommunityAddress = undefined;
|
||||
@@ -326,6 +331,8 @@ describe('ReplyModal', () => {
|
||||
afterEach(() => {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
document.body.style.userSelect = '';
|
||||
document.body.style.webkitUserSelect = '';
|
||||
});
|
||||
|
||||
it('initializes quoted content, display name, upload controls, and shared offline warning on board routes', async () => {
|
||||
@@ -493,6 +500,41 @@ describe('ReplyModal', () => {
|
||||
expect(modal?.style.touchAction).toBe('none');
|
||||
});
|
||||
|
||||
it('closes with Escape from the document on desktop', async () => {
|
||||
await renderReplyModal('/mu/thread/post-1');
|
||||
|
||||
await act(async () => {
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: 'Escape' }));
|
||||
});
|
||||
|
||||
expect(testState.closeModalMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('restores body selection styles if unmounted during a drag', async () => {
|
||||
document.body.style.userSelect = 'text';
|
||||
document.body.style.webkitUserSelect = 'auto';
|
||||
|
||||
await renderReplyModal('/mu/thread/post-1');
|
||||
|
||||
await act(async () => {
|
||||
testState.dragHandler?.({
|
||||
active: true,
|
||||
event: { preventDefault: vi.fn() },
|
||||
offset: [140, 100],
|
||||
});
|
||||
});
|
||||
|
||||
expect(document.body.style.userSelect).toBe('none');
|
||||
expect(document.body.style.webkitUserSelect).toBe('none');
|
||||
|
||||
await act(async () => {
|
||||
root.render(createElement(React.Fragment));
|
||||
});
|
||||
|
||||
expect(document.body.style.userSelect).toBe('text');
|
||||
expect(document.body.style.webkitUserSelect).toBe('auto');
|
||||
});
|
||||
|
||||
it('initializes the drag spring once so typing rerenders do not recenter the modal', async () => {
|
||||
await renderReplyModal('/mu/thread/post-1');
|
||||
|
||||
|
||||
@@ -54,6 +54,16 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
const account = useAccount();
|
||||
const { displayName } = account?.author || {};
|
||||
const textRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const setTextRef = useRef((element: HTMLTextAreaElement | null) => {
|
||||
textRef.current = element;
|
||||
if (!element) return;
|
||||
|
||||
window.setTimeout(() => {
|
||||
if (textRef.current === element) {
|
||||
element.focus();
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
const urlRef = useRef<HTMLInputElement>(null);
|
||||
const lastSelectionStartRef = useRef(0);
|
||||
const lastSelectionEndRef = useRef(0);
|
||||
@@ -126,6 +136,27 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
[],
|
||||
);
|
||||
|
||||
const bodySelectionStyleBeforeDragRef = useRef<{ userSelect: string; webkitUserSelect: string } | null>(null);
|
||||
|
||||
const disableBodyTextSelection = () => {
|
||||
if (!bodySelectionStyleBeforeDragRef.current) {
|
||||
bodySelectionStyleBeforeDragRef.current = {
|
||||
userSelect: document.body.style.userSelect,
|
||||
webkitUserSelect: document.body.style.webkitUserSelect,
|
||||
};
|
||||
}
|
||||
Object.assign(document.body.style, { userSelect: 'none', webkitUserSelect: 'none' });
|
||||
};
|
||||
|
||||
const restoreBodyTextSelection = () => {
|
||||
const previousStyle = bodySelectionStyleBeforeDragRef.current;
|
||||
Object.assign(document.body.style, {
|
||||
userSelect: previousStyle?.userSelect ?? '',
|
||||
webkitUserSelect: previousStyle?.webkitUserSelect ?? '',
|
||||
});
|
||||
bodySelectionStyleBeforeDragRef.current = null;
|
||||
};
|
||||
|
||||
const bind = useDrag(
|
||||
({ active, event, offset: [ox, oy] }) => {
|
||||
const nextLeft = Math.round(ox);
|
||||
@@ -133,11 +164,9 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
|
||||
if (active) {
|
||||
event.preventDefault();
|
||||
document.body.style.userSelect = 'none';
|
||||
document.body.style.webkitUserSelect = 'none';
|
||||
disableBodyTextSelection();
|
||||
} else {
|
||||
document.body.style.userSelect = '';
|
||||
document.body.style.webkitUserSelect = '';
|
||||
restoreBodyTextSelection();
|
||||
}
|
||||
api.start({ left: nextLeft, top: nextTop, immediate: true });
|
||||
},
|
||||
@@ -148,6 +177,12 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
restoreBodyTextSelection();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (nodeRef.current && isMobile) {
|
||||
const viewportHeight = window.innerHeight;
|
||||
@@ -158,6 +193,21 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
|
||||
const parentCidRef = useRef<HTMLSpanElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showReplyModal || isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const closeReplyModalOnEscape = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', closeReplyModalOnEscape);
|
||||
return () => document.removeEventListener('keydown', closeReplyModalOnEscape);
|
||||
}, [showReplyModal, isMobile, closeModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (parentCidRef.current) {
|
||||
const cidWidth = parentCidRef.current.offsetWidth;
|
||||
@@ -165,29 +215,6 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
}
|
||||
}, [parentCid]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showReplyModal) {
|
||||
setTimeout(() => {
|
||||
if (textRef.current) {
|
||||
textRef.current.focus();
|
||||
}
|
||||
}, 0);
|
||||
|
||||
if (!isMobile) {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleEscape);
|
||||
};
|
||||
}
|
||||
}
|
||||
}, [showReplyModal, closeModal, isMobile]);
|
||||
|
||||
useEffect(() => {
|
||||
if (textRef.current) {
|
||||
const len = textRef.current.value.length;
|
||||
@@ -209,11 +236,15 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
setPublishReplyOptions({ content });
|
||||
checkContentLengthRef.current(content, t);
|
||||
|
||||
setTimeout(() => {
|
||||
const spellcheckTimeout = window.setTimeout(() => {
|
||||
if (textRef.current) {
|
||||
textRef.current.spellcheck = true;
|
||||
}
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(spellcheckTimeout);
|
||||
};
|
||||
}
|
||||
}, [showReplyModal, openEmpty, defaultParentQuote, selectedText]);
|
||||
|
||||
@@ -351,7 +382,7 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
cols={48}
|
||||
rows={4}
|
||||
wrap='soft'
|
||||
ref={textRef}
|
||||
ref={setTextRef.current}
|
||||
aria-label={t('comment')}
|
||||
spellCheck={true}
|
||||
onInput={handleContentInput}
|
||||
|
||||
Reference in New Issue
Block a user