diff --git a/packages/inline-dropdown/configure/src/__tests__/inline-dropdown-toolbar.test.jsx b/packages/inline-dropdown/configure/src/__tests__/inline-dropdown-toolbar.test.jsx index ea0e8f120c..fca689d831 100644 --- a/packages/inline-dropdown/configure/src/__tests__/inline-dropdown-toolbar.test.jsx +++ b/packages/inline-dropdown/configure/src/__tests__/inline-dropdown-toolbar.test.jsx @@ -534,6 +534,101 @@ describe('RespAreaToolbar', () => { expect(instance.state.editedChoiceIndex).toBe(index); }); + + it('should set preventDone to true', () => { + const instance = createInstance(); + const value = 'dog'; + const index = 1; + + instance.preventDone = false; + instance.onEditChoice(value, index); + + expect(instance.preventDone).toBe(true); + }); + + it('should save current edit before starting a new edit', () => { + const localOnAddChoice = jest.fn(); + const localEditor = { + ...editor, + commands: { + ...editor.commands, + refreshResponseArea: jest.fn(), + }, + }; + const instance = createInstance({ + onAddChoice: localOnAddChoice, + editor: localEditor, + }); + instance.editorRef = { + getHTML: jest.fn().mockReturnValue('
modified dog
'), + }; + + // Start editing first choice + instance.onEditChoice('dog', 1); + expect(instance.state.editedChoiceIndex).toBe(1); + + // Start editing second choice without finishing the first + instance.onEditChoice('cat', 2); + + // Should have called onDone with the previous edit + expect(localOnAddChoice).toHaveBeenCalledWith('0', '
modified dog
', 1); + expect(instance.state.editedChoiceIndex).toBe(2); + expect(instance.state.respAreaMarkup).toBe('cat'); + }); + + it('should not call onDone if no choice is currently being edited', () => { + const localOnAddChoice = jest.fn(); + const localEditor = { + ...editor, + commands: { + ...editor.commands, + refreshResponseArea: jest.fn(), + }, + }; + const instance = createInstance({ + onAddChoice: localOnAddChoice, + editor: localEditor, + }); + instance.editorRef = { + getHTML: jest.fn().mockReturnValue('
test
'), + }; + + // editedChoiceIndex is -1 (no choice being edited) + instance.state.editedChoiceIndex = -1; + instance.onEditChoice('dog', 1); + + // Should not have called onDone + expect(localOnAddChoice).not.toHaveBeenCalled(); + expect(instance.state.editedChoiceIndex).toBe(1); + }); + + it('should handle empty HTML when saving previous edit', () => { + const localOnAddChoice = jest.fn(); + const localEditor = { + ...editor, + commands: { + ...editor.commands, + refreshResponseArea: jest.fn(), + }, + }; + const instance = createInstance({ + onAddChoice: localOnAddChoice, + editor: localEditor, + }); + instance.editorRef = { + getHTML: jest.fn().mockReturnValue(''), + }; + + // Start editing first choice + instance.onEditChoice('dog', 1); + + // Start editing second choice with empty content in first + instance.onEditChoice('cat', 2); + + // Should not call onAddChoice for empty content + expect(localOnAddChoice).not.toHaveBeenCalled(); + expect(instance.state.editedChoiceIndex).toBe(2); + }); }); }); @@ -839,12 +934,12 @@ describe('RespAreaToolbar', () => { expect(localEditor.commands.refreshResponseArea).toHaveBeenCalled(); }); - it('should prevent onDone when clicking inside', () => { + it('should handle switching between editing choices without finishing', () => { const localOnAddChoice = jest.fn(); const localEditor = { ...editor, commands: { - ...editor.commands, + updateAttributes: jest.fn(), refreshResponseArea: jest.fn(), }, }; @@ -852,13 +947,61 @@ describe('RespAreaToolbar', () => { onAddChoice: localOnAddChoice, editor: localEditor, }); - instance.clickedInside = true; - instance.onDone('
test
'); + // Mock editor with HTML content + instance.editorRef = { + getHTML: jest.fn() + .mockReturnValueOnce('
modified dog
') + .mockReturnValueOnce('
modified cat
'), + }; - // onDone should still proceed - the clickedInside check is in the EditableHtml callback - // This test verifies the method doesn't crash when called - expect(localOnAddChoice).toHaveBeenCalled(); + // Start editing first choice + instance.onEditChoice('dog', 1); + expect(instance.state.editedChoiceIndex).toBe(1); + expect(instance.state.respAreaMarkup).toBe('dog'); + + // Switch to editing second choice without explicitly calling onDone + localOnAddChoice.mockClear(); + instance.onEditChoice('cat', 2); + + // Should have auto-saved the previous edit + expect(localOnAddChoice).toHaveBeenCalledWith('0', '
modified dog
', 1); + expect(instance.state.editedChoiceIndex).toBe(2); + expect(instance.state.respAreaMarkup).toBe('cat'); + }); + + it('should handle editing the correct choice and switching to another', () => { + const localOnAddChoice = jest.fn(); + const localEditor = { + ...editor, + commands: { + updateAttributes: jest.fn(), + refreshResponseArea: jest.fn(), + }, + }; + const localOnToolbarDone = jest.fn(); + const instance = createInstance({ + onAddChoice: localOnAddChoice, + editor: localEditor, + onToolbarDone: localOnToolbarDone, + }); + + // Mock editor with HTML content + instance.editorRef = { + getHTML: jest.fn().mockReturnValue('
modified cow
'), + }; + + // Start editing the correct choice (index 0) + instance.onEditChoice('cow', 0); + expect(instance.state.editedChoiceIndex).toBe(0); + + // Switch to editing another choice + instance.onEditChoice('dog', 1); + + // Should have auto-saved and updated attributes since it was the correct choice + expect(localEditor.commands.updateAttributes).toHaveBeenCalledWith('inline_dropdown', { value: '
modified cow
' }); + expect(localOnToolbarDone).toHaveBeenCalledWith(false); + expect(instance.state.editedChoiceIndex).toBe(1); }); }); @@ -1175,6 +1318,10 @@ describe('MenuItem Integration Tests', () => { ]; const instance = createToolbar(choices); + // Mock editorRef to prevent errors when auto-saving + instance.editorRef = { + getHTML: jest.fn().mockReturnValue(''), + }; const rendered = render(<>{instance.render()}); const editButtons = rendered.getAllByLabelText('Edit'); @@ -1336,6 +1483,10 @@ describe('MenuItem Integration Tests', () => { ]; const instance = createToolbar(choices); + // Mock editorRef to prevent errors when auto-saving + instance.editorRef = { + getHTML: jest.fn().mockReturnValue(''), + }; const rendered = render(<>{instance.render()}); const editButtons = rendered.getAllByLabelText('Edit'); @@ -1347,6 +1498,58 @@ describe('MenuItem Integration Tests', () => { expect(instance.state.editedChoiceIndex).toBe(1); }); + it('should auto-save when switching between editing choices', () => { + const localOnAddChoice = jest.fn(); + const localEditor = { + ...editor, + commands: { + updateAttributes: jest.fn(), + refreshResponseArea: jest.fn(), + }, + }; + + const choices = [ + { label: 'choice1', correct: false }, + { label: 'choice2', correct: false }, + ]; + + const props = { + onAddChoice: localOnAddChoice, + onRemoveChoice, + onSelectChoice, + onToolbarDone, + node: { + key: '1', + attrs: { index: '0', value: 'cow' }, + }, + editor: localEditor, + choices, + }; + + const instance = new RespAreaToolbar(props); + instance.props = props; + instance.setState = jest.fn((state) => { + if (typeof state === 'function') { + instance.state = { ...instance.state, ...state(instance.state) }; + } else { + instance.state = { ...instance.state, ...state }; + } + }); + instance.editorRef = { + getHTML: jest.fn().mockReturnValue('
modified choice1
'), + }; + instance.componentDidMount(); + + // Start editing first choice directly + instance.onEditChoice('choice1', 0); + expect(instance.state.editedChoiceIndex).toBe(0); + + // Start editing second choice - should auto-save first choice + instance.onEditChoice('choice2', 1); + expect(localOnAddChoice).toHaveBeenCalledWith('0', '
modified choice1
', 0); + expect(instance.state.editedChoiceIndex).toBe(1); + }); + it('should allow removing multiple choices', () => { const choices = [ { label: 'choice1', correct: false }, diff --git a/packages/inline-dropdown/configure/src/inline-dropdown-toolbar.jsx b/packages/inline-dropdown/configure/src/inline-dropdown-toolbar.jsx index 313879d5c5..46cae49820 100644 --- a/packages/inline-dropdown/configure/src/inline-dropdown-toolbar.jsx +++ b/packages/inline-dropdown/configure/src/inline-dropdown-toolbar.jsx @@ -280,6 +280,16 @@ class RespAreaToolbar extends React.Component { }; onEditChoice = (val, index) => { + const { editedChoiceIndex } = this.state; + + this.preventDone = true; + + if (editedChoiceIndex >= 0) { + const html = this.editorRef.getHTML() || ''; + + this.onDone(html); + } + this.onRespAreaChange(val); this.setState({ editedChoiceIndex: index }); }; @@ -373,7 +383,8 @@ class RespAreaToolbar extends React.Component { this.onRespAreaChange(respAreaMarkup); }} onDone={(val) => { - if (this.preventDone || this.clickedInside) { + if (this.preventDone) { + this.preventDone = false; return; }