diff --git a/entry_types/scrolled/package/spec/contentElements/textBlock/editor-spec.js b/entry_types/scrolled/package/spec/contentElements/textBlock/editor-spec.js index 7956218d44..f2c132666b 100644 --- a/entry_types/scrolled/package/spec/contentElements/textBlock/editor-spec.js +++ b/entry_types/scrolled/package/spec/contentElements/textBlock/editor-spec.js @@ -1,5 +1,6 @@ import 'contentElements/textBlock/editor'; import {editor} from 'editor'; +import {ContentElement} from 'editor/models/ContentElement'; describe('contentElements/textBlock/editor', () => { const type = editor.contentElementTypes.findByTypeName('textBlock'); @@ -129,4 +130,31 @@ describe('contentElements/textBlock/editor', () => { }); }); + describe('#handleDestroy', () => { + it('removes selected blocks if selection does not cover all blocks', () => { + const contentElement = new ContentElement({ + typeName: 'textBlock', + transientState: {editableTextSelectionCoversAllBlocks: false} + }); + jest.spyOn(contentElement, 'postCommand'); + + const result = type.handleDestroy(contentElement); + + expect(contentElement.postCommand).toHaveBeenCalledWith({type: 'REMOVE'}); + expect(result).toBe(false); + }); + + it('lets content element be deleted if selection covers all blocks', () => { + const contentElement = new ContentElement({ + typeName: 'textBlock', + transientState: {editableTextSelectionCoversAllBlocks: true} + }); + jest.spyOn(contentElement, 'postCommand'); + + const result = type.handleDestroy(contentElement); + + expect(contentElement.postCommand).not.toHaveBeenCalled(); + expect(result).not.toBe(false); + }); + }); }); diff --git a/entry_types/scrolled/package/spec/frontend/commenting/EditableText-spec.js b/entry_types/scrolled/package/spec/frontend/commenting/EditableText-spec.js index 2ad10a6584..4aec9b572c 100644 --- a/entry_types/scrolled/package/spec/frontend/commenting/EditableText-spec.js +++ b/entry_types/scrolled/package/spec/frontend/commenting/EditableText-spec.js @@ -33,6 +33,17 @@ describe('commenting EditableText', () => { expect(entry.queryByText('Select text to comment')).not.toBeInTheDocument(); }); + it('renders empty paragraph if value is empty array', () => { + const entry = renderEntry({ + contentElement: { + ui: , + typeOptions: {inlineComments: true} + } + }); + + expect(entry.container.querySelector('[data-slate-node="element"]')).toBeInTheDocument(); + }); + it('shows hint in add comment mode', async () => { const user = userEvent.setup(); const entry = renderEntry({ diff --git a/entry_types/scrolled/package/spec/frontend/inlineEditing/EditableText/features/placeholder-spec.js b/entry_types/scrolled/package/spec/frontend/inlineEditing/EditableText/features/placeholder-spec.js index 4241478e8e..58573c27fd 100644 --- a/entry_types/scrolled/package/spec/frontend/inlineEditing/EditableText/features/placeholder-spec.js +++ b/entry_types/scrolled/package/spec/frontend/inlineEditing/EditableText/features/placeholder-spec.js @@ -36,6 +36,16 @@ describe('inline editing EditableText placeholder', () => { expect(entry.queryByText('Some placeholder')).toBeInTheDocument(); }); + it('renders placeholder if value is empty array', () => { + const entry = renderEntry({ + contentElement: { + ui: + } + }); + + expect(entry.queryByText('Some placeholder')).toBeInTheDocument(); + }); + it('does not render placeholder if value is present', () => { const entry = renderEntry({ contentElement: { diff --git a/entry_types/scrolled/package/spec/frontend/inlineEditing/EditableText/features/selectionTransientState-spec.js b/entry_types/scrolled/package/spec/frontend/inlineEditing/EditableText/features/selectionTransientState-spec.js new file mode 100644 index 0000000000..3829ebf45d --- /dev/null +++ b/entry_types/scrolled/package/spec/frontend/inlineEditing/EditableText/features/selectionTransientState-spec.js @@ -0,0 +1,60 @@ +import React from 'react'; + +import {EditableText} from 'frontend'; +import {renderEntry, useInlineEditingPageObjects} from 'support/pageObjects/inlineEditing'; + +import {waitFor} from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; + +describe('inline editing EditableText selection transient state', () => { + useInlineEditingPageObjects(); + + beforeEach(() => { + Range.prototype.getBoundingClientRect = () => document.body.getBoundingClientRect(); + Range.prototype.getClientRects = () => []; + }); + + afterEach(() => { + delete Range.prototype.getBoundingClientRect; + delete Range.prototype.getClientRects; + }); + + const value = [ + {type: 'paragraph', children: [{text: 'First paragraph'}]}, + {type: 'paragraph', children: [{text: 'Second paragraph'}]} + ]; + + it('reports selection covering all blocks', async () => { + const entry = renderEntry({ + contentElement: { + ui: , + typeOptions: {customSelectionRect: true} + } + }); + + entry.simulateSelectMessage({type: 'contentElement', id: 1, range: [0, 2]}); + + await waitFor(() => + expect(entry.getTransientContentElementState(1)).toMatchObject({ + editableTextSelectionCoversAllBlocks: true + }) + ); + }); + + it('reports selection not covering all blocks', async () => { + const entry = renderEntry({ + contentElement: { + ui: , + typeOptions: {customSelectionRect: true} + } + }); + + entry.simulateSelectMessage({type: 'contentElement', id: 1, range: [1, 2]}); + + await waitFor(() => + expect(entry.getTransientContentElementState(1)).toMatchObject({ + editableTextSelectionCoversAllBlocks: false + }) + ); + }); +}); diff --git a/entry_types/scrolled/package/spec/support/pageObjects/inlineEditing.js b/entry_types/scrolled/package/spec/support/pageObjects/inlineEditing.js index 4bfef69af8..936592a0d4 100644 --- a/entry_types/scrolled/package/spec/support/pageObjects/inlineEditing.js +++ b/entry_types/scrolled/package/spec/support/pageObjects/inlineEditing.js @@ -24,7 +24,27 @@ export function renderEntry({commenting, ...options} = {}) { return { ...result, queryAllCommentBadges: () => - result.queryAllByRole('status').map(createCommentBadgePageObject) + result.queryAllByRole('status').map(createCommentBadgePageObject), + + simulateSelectMessage(payload) { + act(() => { + window.dispatchEvent(new MessageEvent('message', { + data: {type: 'SELECT', payload}, + origin: window.location.origin + })); + }); + }, + + getTransientContentElementState(id) { + return window.parent.postMessage.mock.calls + .map(([message]) => message) + .filter(message => + message.type === 'UPDATE_TRANSIENT_CONTENT_ELEMENT_STATE' && + message.payload.id === id + ) + .map(message => message.payload.state) + .pop(); + } }; } diff --git a/entry_types/scrolled/package/src/contentElements/textBlock/editor.js b/entry_types/scrolled/package/src/contentElements/textBlock/editor.js index 3e3237d240..12df429482 100644 --- a/entry_types/scrolled/package/src/contentElements/textBlock/editor.js +++ b/entry_types/scrolled/package/src/contentElements/textBlock/editor.js @@ -161,7 +161,7 @@ editor.contentElementTypes.register('textBlock', { handleDestroy(contentElement) { const transientState = contentElement.get('transientState') || {}; - if (!transientState.editableTextIsSingleBlock) { + if (!transientState.editableTextSelectionCoversAllBlocks) { contentElement.postCommand({type: 'REMOVE'}); return false; } diff --git a/entry_types/scrolled/package/src/frontend/commenting/EditableText.js b/entry_types/scrolled/package/src/frontend/commenting/EditableText.js index 28302cdf9d..95b786e119 100644 --- a/entry_types/scrolled/package/src/frontend/commenting/EditableText.js +++ b/entry_types/scrolled/package/src/frontend/commenting/EditableText.js @@ -91,7 +91,7 @@ function CommentingEditableText({ typographyVariant={typographyVariant} typographySize={typographySize}> {}}> [{ + type: 'paragraph', + children: [{text: ''}] + }], []); + + const [cachedValue, setCachedValue] = useCachedValue(value?.length ? value : emptyValue, { onDebouncedChange: nextValue => { if (onChange) { onChange(nextValue, {commentThreadSubjectRanges: getTrackedSubjectRanges()});