Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b1328d280 |
@@ -58,7 +58,6 @@ import {
|
|||||||
suggestionsPlugin,
|
suggestionsPlugin,
|
||||||
triggerCharacters,
|
triggerCharacters,
|
||||||
} from '@chatwoot/prosemirror-schema/src/mentions/plugin';
|
} from '@chatwoot/prosemirror-schema/src/mentions/plugin';
|
||||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
|
||||||
|
|
||||||
import TagAgents from '../conversation/TagAgents.vue';
|
import TagAgents from '../conversation/TagAgents.vue';
|
||||||
import CannedResponse from '../conversation/CannedResponse.vue';
|
import CannedResponse from '../conversation/CannedResponse.vue';
|
||||||
@@ -66,10 +65,6 @@ import VariableList from '../conversation/VariableList.vue';
|
|||||||
import {
|
import {
|
||||||
appendSignature,
|
appendSignature,
|
||||||
removeSignature,
|
removeSignature,
|
||||||
insertAtCursor,
|
|
||||||
scrollCursorIntoView,
|
|
||||||
findNodeToInsertImage,
|
|
||||||
setURLWithQueryAndSize,
|
|
||||||
} from 'dashboard/helper/editorHelper';
|
} from 'dashboard/helper/editorHelper';
|
||||||
|
|
||||||
const TYPING_INDICATOR_IDLE_TIME = 4000;
|
const TYPING_INDICATOR_IDLE_TIME = 4000;
|
||||||
@@ -305,7 +300,6 @@ export default {
|
|||||||
const tr = this.editorView.state.tr.replaceSelectionWith(node);
|
const tr = this.editorView.state.tr.replaceSelectionWith(node);
|
||||||
this.editorView.focus();
|
this.editorView.focus();
|
||||||
this.state = this.editorView.state.apply(tr);
|
this.state = this.editorView.state.apply(tr);
|
||||||
this.editorView.updateState(this.state);
|
|
||||||
this.emitOnChange();
|
this.emitOnChange();
|
||||||
this.$emit('clear-selection');
|
this.$emit('clear-selection');
|
||||||
}
|
}
|
||||||
@@ -331,17 +325,7 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.createEditorView();
|
this.createEditorView();
|
||||||
this.editorView.updateState(this.state);
|
this.editorView.updateState(this.state);
|
||||||
this.focusEditorInputField();
|
this.focusEditor(this.value);
|
||||||
|
|
||||||
// BUS Event to insert text or markdown into the editor at the
|
|
||||||
// current cursor position.
|
|
||||||
// Components using this
|
|
||||||
// 1. SearchPopover.vue
|
|
||||||
|
|
||||||
bus.$on(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, this.insertContentIntoEditor);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
bus.$off(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, this.insertContentIntoEditor);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
reloadState(content = this.value) {
|
reloadState(content = this.value) {
|
||||||
@@ -428,7 +412,6 @@ export default {
|
|||||||
state: this.state,
|
state: this.state,
|
||||||
dispatchTransaction: tx => {
|
dispatchTransaction: tx => {
|
||||||
this.state = this.state.apply(tx);
|
this.state = this.state.apply(tx);
|
||||||
this.editorView.updateState(this.state);
|
|
||||||
this.emitOnChange();
|
this.emitOnChange();
|
||||||
},
|
},
|
||||||
handleDOMEvents: {
|
handleDOMEvents: {
|
||||||
@@ -532,7 +515,11 @@ export default {
|
|||||||
userFullName: mentionItem.name,
|
userFullName: mentionItem.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.insertNodeIntoEditor(node, this.range.from, this.range.to);
|
const tr = this.editorView.state.tr
|
||||||
|
.replaceWith(this.range.from, this.range.to, node)
|
||||||
|
.insertText(` `);
|
||||||
|
this.state = this.editorView.state.apply(tr);
|
||||||
|
this.emitOnChange();
|
||||||
this.$track(CONVERSATION_EVENTS.USED_MENTIONS);
|
this.$track(CONVERSATION_EVENTS.USED_MENTIONS);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -546,12 +533,26 @@ export default {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let from = this.range.from - 1;
|
||||||
let node = new MessageMarkdownTransformer(messageSchema).parse(
|
let node = new MessageMarkdownTransformer(messageSchema).parse(
|
||||||
updatedMessage
|
updatedMessage
|
||||||
);
|
);
|
||||||
|
|
||||||
this.insertNodeIntoEditor(node, this.range.from, this.range.to);
|
if (node.textContent === updatedMessage) {
|
||||||
|
node = this.editorView.state.schema.text(updatedMessage);
|
||||||
|
from = this.range.from;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tr = this.editorView.state.tr.replaceWith(
|
||||||
|
from,
|
||||||
|
this.range.to,
|
||||||
|
node
|
||||||
|
);
|
||||||
|
|
||||||
|
this.state = this.editorView.state.apply(tr);
|
||||||
|
this.emitOnChange();
|
||||||
|
|
||||||
|
tr.scrollIntoView();
|
||||||
this.$track(CONVERSATION_EVENTS.INSERTED_A_CANNED_RESPONSE);
|
this.$track(CONVERSATION_EVENTS.INSERTED_A_CANNED_RESPONSE);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
@@ -559,14 +560,23 @@ export default {
|
|||||||
if (!this.editorView) {
|
if (!this.editorView) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
let node = this.editorView.state.schema.text(`{{${variable}}}`);
|
||||||
|
const from = this.range.from;
|
||||||
|
|
||||||
const content = `{{${variable}}}`;
|
const tr = this.editorView.state.tr.replaceWith(
|
||||||
let node = this.editorView.state.schema.text(content);
|
from,
|
||||||
const { from, to } = this.range;
|
this.range.to,
|
||||||
|
node
|
||||||
|
);
|
||||||
|
|
||||||
this.insertNodeIntoEditor(node, from, to);
|
this.state = this.editorView.state.apply(tr);
|
||||||
|
this.emitOnChange();
|
||||||
|
|
||||||
|
// The `{{ }}` are added to the message, but the cursor is placed
|
||||||
|
// and onExit of suggestionsPlugin is not called. So we need to manually hide
|
||||||
this.showVariables = false;
|
this.showVariables = false;
|
||||||
this.$track(CONVERSATION_EVENTS.INSERTED_A_VARIABLE);
|
this.$track(CONVERSATION_EVENTS.INSERTED_A_VARIABLE);
|
||||||
|
tr.scrollIntoView();
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
openFileBrowser() {
|
openFileBrowser() {
|
||||||
@@ -622,6 +632,8 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
emitOnChange() {
|
emitOnChange() {
|
||||||
|
this.editorView.updateState(this.state);
|
||||||
|
|
||||||
this.$emit('input', this.contentFromEditor);
|
this.$emit('input', this.contentFromEditor);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -682,19 +694,6 @@ export default {
|
|||||||
onFocus() {
|
onFocus() {
|
||||||
this.$emit('focus');
|
this.$emit('focus');
|
||||||
},
|
},
|
||||||
insertContentIntoEditor(content, defaultFrom = 0) {
|
|
||||||
const from = defaultFrom || this.editorView.state.selection.from || 0;
|
|
||||||
let node = new MessageMarkdownTransformer(messageSchema).parse(content);
|
|
||||||
|
|
||||||
this.insertNodeIntoEditor(node, from, undefined);
|
|
||||||
},
|
|
||||||
insertNodeIntoEditor(node, from = 0, to = 0) {
|
|
||||||
this.state = insertAtCursor(this.editorView, node, from, to);
|
|
||||||
this.emitOnChange();
|
|
||||||
this.$nextTick(() => {
|
|
||||||
scrollCursorIntoView(this.editorView);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -156,128 +156,3 @@ export function extractTextFromMarkdown(markdown) {
|
|||||||
.replace(/\n{2,}/g, '\n') // Remove multiple consecutive newlines (blank lines)
|
.replace(/\n{2,}/g, '\n') // Remove multiple consecutive newlines (blank lines)
|
||||||
.trim(); // Trim any extra space
|
.trim(); // Trim any extra space
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Scrolls the editor view into current cursor position
|
|
||||||
*
|
|
||||||
* @param {EditorView} view - The Prosemirror EditorView
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export const scrollCursorIntoView = view => {
|
|
||||||
// Get the current selection's head position (where the cursor is).
|
|
||||||
const pos = view.state.selection.head;
|
|
||||||
|
|
||||||
// Get the corresponding DOM node for that position.
|
|
||||||
const domAtPos = view.domAtPos(pos);
|
|
||||||
const node = domAtPos.node;
|
|
||||||
|
|
||||||
// Scroll the node into view.
|
|
||||||
if (node && node.scrollIntoView) {
|
|
||||||
node.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a transaction that inserts a node into editor at the given position
|
|
||||||
* Has an optional param 'content' to check if the
|
|
||||||
*
|
|
||||||
* @param {Node} node - The prosemirror node that needs to be inserted into the editor
|
|
||||||
* @param {number} from - Position in the editor where the node needs to be inserted
|
|
||||||
* @param {number} to - Position in the editor where the node needs to be replaced
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export function insertAtCursor(editorView, node, from, to) {
|
|
||||||
if (!editorView) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a workaround to prevent inserting content into new line rather than on the exiting line
|
|
||||||
// If the node is of type 'doc' and has only one child which is a paragraph,
|
|
||||||
// then extract its inline content to be inserted as inline.
|
|
||||||
const isWrappedInParagraph =
|
|
||||||
node.type.name === 'doc' &&
|
|
||||||
node.childCount === 1 &&
|
|
||||||
node.firstChild.type.name === 'paragraph';
|
|
||||||
|
|
||||||
if (isWrappedInParagraph) {
|
|
||||||
node = node.firstChild.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
let tr;
|
|
||||||
if (to) {
|
|
||||||
tr = editorView.state.tr.replaceWith(from, to, node).insertText(` `);
|
|
||||||
} else {
|
|
||||||
tr = editorView.state.tr.insert(from, node);
|
|
||||||
}
|
|
||||||
const state = editorView.state.apply(tr);
|
|
||||||
editorView.updateState(state);
|
|
||||||
editorView.focus();
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines the appropriate node and position to insert an image in the editor.
|
|
||||||
*
|
|
||||||
* Based on the current editor state and the provided image URL, this function finds out the correct node (either
|
|
||||||
* a standalone image node or an image wrapped in a paragraph) and its respective position in the editor.
|
|
||||||
*
|
|
||||||
* 1. If the current node is a paragraph and doesn't contain an image or text, the image is inserted directly into it.
|
|
||||||
* 2. If the current node isn't a paragraph or it's a paragraph containing text, the image will be wrapped
|
|
||||||
* in a new paragraph and then inserted.
|
|
||||||
* 3. If the current node is a paragraph containing an image, the new image will be inserted directly into it.
|
|
||||||
*
|
|
||||||
* @param {Object} editorState - The current state of the editor. It provides necessary details like selection, schema, etc.
|
|
||||||
* @param {string} fileUrl - The URL of the image to be inserted into the editor.
|
|
||||||
* @returns {Object|null} An object containing details about the node to be inserted and its position. It returns null if no image node can be created.
|
|
||||||
* @property {Node} node - The ProseMirror node to be inserted (either an image node or a paragraph containing the image).
|
|
||||||
* @property {number} pos - The position where the new node should be inserted in the editor.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const findNodeToInsertImage = (editorState, fileUrl) => {
|
|
||||||
const { selection, schema } = editorState;
|
|
||||||
const { nodes } = schema;
|
|
||||||
const currentNode = selection.$from.node();
|
|
||||||
const {
|
|
||||||
type: { name: typeName },
|
|
||||||
content: { size, content },
|
|
||||||
} = currentNode;
|
|
||||||
|
|
||||||
const imageNode = nodes.image.create({ src: fileUrl });
|
|
||||||
|
|
||||||
if (!imageNode) return null;
|
|
||||||
|
|
||||||
const isInParagraph = typeName === 'paragraph';
|
|
||||||
const needsNewLine =
|
|
||||||
!content.some(n => n.type.name === 'image') && size !== 0 ? 1 : 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
node: isInParagraph ? imageNode : nodes.paragraph.create({}, imageNode),
|
|
||||||
pos: selection.from + needsNewLine,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set URL with query and size.
|
|
||||||
*
|
|
||||||
* @param {Object} selectedImageNode - The current selected node.
|
|
||||||
* @param {Object} size - The size to set.
|
|
||||||
* @param {Object} editorView - The editor view.
|
|
||||||
*/
|
|
||||||
export function setURLWithQueryAndSize(selectedImageNode, size, editorView) {
|
|
||||||
if (selectedImageNode) {
|
|
||||||
// Create and apply the transaction
|
|
||||||
const tr = editorView.state.tr.setNodeMarkup(
|
|
||||||
editorView.state.selection.from,
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
src: selectedImageNode.src,
|
|
||||||
height: size.height,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tr.docChanged) {
|
|
||||||
editorView.dispatch(tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,43 +5,7 @@ import {
|
|||||||
replaceSignature,
|
replaceSignature,
|
||||||
cleanSignature,
|
cleanSignature,
|
||||||
extractTextFromMarkdown,
|
extractTextFromMarkdown,
|
||||||
insertAtCursor,
|
|
||||||
findNodeToInsertImage,
|
|
||||||
setURLWithQueryAndSize,
|
|
||||||
} from '../editorHelper';
|
} from '../editorHelper';
|
||||||
import { EditorState } from 'prosemirror-state';
|
|
||||||
import { EditorView } from 'prosemirror-view';
|
|
||||||
import { Schema } from 'prosemirror-model';
|
|
||||||
|
|
||||||
// Define a basic ProseMirror schema
|
|
||||||
const schema = new Schema({
|
|
||||||
nodes: {
|
|
||||||
doc: { content: 'paragraph+' },
|
|
||||||
paragraph: {
|
|
||||||
content: 'text*',
|
|
||||||
toDOM: () => ['p', 0], // Represents a paragraph as a <p> tag in the DOM.
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
toDOM: node => node.text, // Represents text as its actual string value.
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize a basic EditorState for testing
|
|
||||||
const createEditorState = (content = '') => {
|
|
||||||
if (!content) {
|
|
||||||
return EditorState.create({
|
|
||||||
schema,
|
|
||||||
doc: schema.node('doc', null, [schema.node('paragraph')]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return EditorState.create({
|
|
||||||
schema,
|
|
||||||
doc: schema.node('doc', null, [
|
|
||||||
schema.node('paragraph', null, [schema.text(content)]),
|
|
||||||
]),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const NEW_SIGNATURE = 'This is a new signature';
|
const NEW_SIGNATURE = 'This is a new signature';
|
||||||
|
|
||||||
@@ -234,208 +198,3 @@ describe('extractTextFromMarkdown', () => {
|
|||||||
expect(extractTextFromMarkdown(markdown)).toEqual(expected);
|
expect(extractTextFromMarkdown(markdown)).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('insertAtCursor', () => {
|
|
||||||
it('should return undefined if editorView is not provided', () => {
|
|
||||||
const result = insertAtCursor(undefined, schema.text('Hello'), 0);
|
|
||||||
expect(result).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should unwrap doc nodes that are wrapped in a paragraph', () => {
|
|
||||||
const docNode = schema.node('doc', null, [
|
|
||||||
schema.node('paragraph', null, [schema.text('Hello')]),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const editorState = createEditorState();
|
|
||||||
const editorView = new EditorView(document.body, { state: editorState });
|
|
||||||
|
|
||||||
insertAtCursor(editorView, docNode, 0);
|
|
||||||
|
|
||||||
// Check if node was unwrapped and inserted correctly
|
|
||||||
expect(editorView.state.doc.firstChild.firstChild.text).toBe('Hello');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should insert node without replacing any content if "to" is not provided', () => {
|
|
||||||
const editorState = createEditorState();
|
|
||||||
const editorView = new EditorView(document.body, { state: editorState });
|
|
||||||
|
|
||||||
insertAtCursor(editorView, schema.text('Hello'), 0);
|
|
||||||
|
|
||||||
// Check if node was inserted correctly
|
|
||||||
expect(editorView.state.doc.firstChild.firstChild.text).toBe('Hello');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should replace content between "from" and "to" with the provided node', () => {
|
|
||||||
const editorState = createEditorState('ReplaceMe');
|
|
||||||
const editorView = new EditorView(document.body, { state: editorState });
|
|
||||||
|
|
||||||
insertAtCursor(editorView, schema.text('Hello'), 0, 8);
|
|
||||||
|
|
||||||
// Check if content was replaced correctly
|
|
||||||
expect(editorView.state.doc.firstChild.firstChild.text).toBe('Hello Me');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('findNodeToInsertImage', () => {
|
|
||||||
let mockEditorState;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockEditorState = {
|
|
||||||
selection: {
|
|
||||||
$from: {
|
|
||||||
node: jest.fn(() => ({})),
|
|
||||||
},
|
|
||||||
from: 0,
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
nodes: {
|
|
||||||
image: {
|
|
||||||
create: jest.fn(attrs => ({ type: { name: 'image' }, attrs })),
|
|
||||||
},
|
|
||||||
paragraph: {
|
|
||||||
create: jest.fn((_, node) => ({
|
|
||||||
type: { name: 'paragraph' },
|
|
||||||
content: [node],
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should insert image directly into an empty paragraph', () => {
|
|
||||||
const mockNode = {
|
|
||||||
type: { name: 'paragraph' },
|
|
||||||
content: { size: 0, content: [] },
|
|
||||||
};
|
|
||||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
|
||||||
|
|
||||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
|
||||||
expect(result).toEqual({
|
|
||||||
node: { type: { name: 'image' }, attrs: { src: 'image-url' } },
|
|
||||||
pos: 0,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should insert image directly into a paragraph without an image but with other content', () => {
|
|
||||||
const mockNode = {
|
|
||||||
type: { name: 'paragraph' },
|
|
||||||
content: {
|
|
||||||
size: 1,
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: { name: 'text' },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
|
||||||
mockEditorState.selection.from = 1;
|
|
||||||
|
|
||||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
|
||||||
expect(result).toEqual({
|
|
||||||
node: { type: { name: 'image' }, attrs: { src: 'image-url' } },
|
|
||||||
pos: 2, // Because it should insert after the text, on a new line.
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should wrap image in a new paragraph when the current node isn't a paragraph", () => {
|
|
||||||
const mockNode = {
|
|
||||||
type: { name: 'not-a-paragraph' },
|
|
||||||
content: { size: 0, content: [] },
|
|
||||||
};
|
|
||||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
|
||||||
|
|
||||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
|
||||||
expect(result.node.type.name).toBe('paragraph');
|
|
||||||
expect(result.node.content[0].type.name).toBe('image');
|
|
||||||
expect(result.node.content[0].attrs.src).toBe('image-url');
|
|
||||||
expect(result.pos).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should insert a new image directly into the paragraph that already contains an image', () => {
|
|
||||||
const mockNode = {
|
|
||||||
type: { name: 'paragraph' },
|
|
||||||
content: {
|
|
||||||
size: 1,
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: { name: 'image', attrs: { src: 'existing-image-url' } },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
|
||||||
mockEditorState.selection.from = 1;
|
|
||||||
|
|
||||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
|
||||||
expect(result.node.type.name).toBe('image');
|
|
||||||
expect(result.node.attrs.src).toBe('image-url');
|
|
||||||
expect(result.pos).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('setURLWithQueryAndSize', () => {
|
|
||||||
let selectedNode;
|
|
||||||
let editorView;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
selectedNode = {
|
|
||||||
setAttribute: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const tr = {
|
|
||||||
setNodeMarkup: jest.fn().mockReturnValue({
|
|
||||||
docChanged: true,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
selection: { from: 0 },
|
|
||||||
tr,
|
|
||||||
};
|
|
||||||
|
|
||||||
editorView = {
|
|
||||||
state,
|
|
||||||
dispatch: jest.fn(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updates the URL with the given size and updates the editor view', () => {
|
|
||||||
const size = { height: '20px' };
|
|
||||||
|
|
||||||
setURLWithQueryAndSize(selectedNode, size, editorView);
|
|
||||||
|
|
||||||
// Check if the editor view is updated
|
|
||||||
expect(editorView.dispatch).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updates the URL with the given size and updates the editor view with original size', () => {
|
|
||||||
const size = { height: 'auto' };
|
|
||||||
|
|
||||||
setURLWithQueryAndSize(selectedNode, size, editorView);
|
|
||||||
|
|
||||||
// Check if the editor view is updated
|
|
||||||
expect(editorView.dispatch).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not update the editor view if the document has not changed', () => {
|
|
||||||
editorView.state.tr.setNodeMarkup = jest.fn().mockReturnValue({
|
|
||||||
docChanged: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const size = { height: '20px' };
|
|
||||||
|
|
||||||
setURLWithQueryAndSize(selectedNode, size, editorView);
|
|
||||||
|
|
||||||
// Check if the editor view dispatch was not called
|
|
||||||
expect(editorView.dispatch).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not perform any operations if selectedNode is not provided', () => {
|
|
||||||
setURLWithQueryAndSize(null, { height: '20px' }, editorView);
|
|
||||||
|
|
||||||
// Ensure the dispatch method wasn't called
|
|
||||||
expect(editorView.dispatch).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
Reference in New Issue
Block a user