diff --git a/packages/editable-html-tip-tap/src/extensions/__tests__/math.test.js b/packages/editable-html-tip-tap/src/extensions/__tests__/math.test.js index ac3d40393..a1a9d27e2 100644 --- a/packages/editable-html-tip-tap/src/extensions/__tests__/math.test.js +++ b/packages/editable-html-tip-tap/src/extensions/__tests__/math.test.js @@ -648,6 +648,31 @@ describe('MathNodeView', () => { }); }); + it('re-registers click listener when node changes', async () => { + const addEventListenerSpy = jest.spyOn(document, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener'); + const nodeA = { attrs: { latex: 'x^2' } }; + const nodeB = { attrs: { latex: 'y^2' } }; + + const { rerender } = render(); + + await waitFor(() => { + expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function)); + }); + + const initialCallCount = addEventListenerSpy.mock.calls.length; + + rerender(); + + await waitFor(() => { + expect(removeEventListenerSpy).toHaveBeenCalled(); + expect(addEventListenerSpy.mock.calls.length).toBeGreaterThan(initialCallCount); + }); + + addEventListenerSpy.mockRestore(); + removeEventListenerSpy.mockRestore(); + }); + it('does not close toolbar when clicking the math node preview', async () => { const { getByTestId, queryByTestId } = render(); diff --git a/packages/editable-html-tip-tap/src/extensions/math.js b/packages/editable-html-tip-tap/src/extensions/math.js index 5c100d560..3f79eeddb 100644 --- a/packages/editable-html-tip-tap/src/extensions/math.js +++ b/packages/editable-html-tip-tap/src/extensions/math.js @@ -298,7 +298,7 @@ export const MathNodeView = (props) => { } return () => document.removeEventListener('click', handleClickOutside); - }, [editor, showToolbar]); + }, [editor, showToolbar, node]); return ( { onBlur: jest.fn(), }; + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + it('renders with default props', () => { const { container } = render(); expect(container.firstChild).toBeInTheDocument(); }); + + describe('autoFocus', () => { + it('focuses input immediately via setTimeout when autoFocus is true', () => { + const mockFocus = jest.fn(); + const component = new EditorAndPad({ ...defaultProps, autoFocus: true }); + component.input = { focus: mockFocus }; + + component.componentDidMount(); + + expect(mockFocus).not.toHaveBeenCalled(); + + jest.runAllTimers(); + + expect(mockFocus).toHaveBeenCalledTimes(1); + }); + + it('does not focus input when autoFocus is false', () => { + const mockFocus = jest.fn(); + const component = new EditorAndPad({ ...defaultProps, autoFocus: false }); + component.input = { focus: mockFocus }; + + component.componentDidMount(); + jest.runAllTimers(); + + expect(mockFocus).not.toHaveBeenCalled(); + }); + + it('does not focus when input ref is not set', () => { + const component = new EditorAndPad({ ...defaultProps, autoFocus: true }); + component.input = null; + + expect(() => { + component.componentDidMount(); + jest.runAllTimers(); + }).not.toThrow(); + }); + + it('uses setTimeout with 0ms delay to defer focus', () => { + const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + const mockFocus = jest.fn(); + const component = new EditorAndPad({ ...defaultProps, autoFocus: true }); + component.input = { focus: mockFocus }; + + component.componentDidMount(); + + expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 0); + + setTimeoutSpy.mockRestore(); + }); + }); }); diff --git a/packages/math-toolbar/src/editor-and-pad.jsx b/packages/math-toolbar/src/editor-and-pad.jsx index 56ce922f6..1dd4adc22 100644 --- a/packages/math-toolbar/src/editor-and-pad.jsx +++ b/packages/math-toolbar/src/editor-and-pad.jsx @@ -263,7 +263,8 @@ export class EditorAndPad extends React.Component { componentDidMount() { if (this.input && this.props.autoFocus) { - this.input.focus(); + // adding a timeout to wait for other stuff related to focus to be finished + setTimeout(() => this.input.focus(), 0); } }