From a4b7481a99f58b9abe85ab5712d27593cde20096 Mon Sep 17 00:00:00 2001 From: Erez Rokah Date: Mon, 27 Jul 2020 18:02:42 +0200 Subject: [PATCH] fix(widget-markdown): properly check for selection when inserting links (#4075) --- .../integration/markdown_widget_link_spec.js | 72 +++++++++++++++++++ cypress/support/commands.js | 7 ++ .../src/MarkdownControl/plugins/Link.js | 16 +++-- 3 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 cypress/integration/markdown_widget_link_spec.js diff --git a/cypress/integration/markdown_widget_link_spec.js b/cypress/integration/markdown_widget_link_spec.js new file mode 100644 index 00000000..4baeb90c --- /dev/null +++ b/cypress/integration/markdown_widget_link_spec.js @@ -0,0 +1,72 @@ +import '../utils/dismiss-local-backup'; + +describe('Markdown widget link', () => { + before(() => { + Cypress.config('defaultCommandTimeout', 4000); + cy.task('setupBackend', { backend: 'test' }); + }); + + beforeEach(() => { + cy.clearLocalStorage(); + cy.reload(); + cy.loginAndNewPost(); + cy.clearMarkdownEditorContent(); + }); + + after(() => { + cy.task('teardownBackend', { backend: 'test' }); + }); + + describe('pressing backspace', () => { + it('can add a new valid link', () => { + const link = 'https://www.netlifycms.org/'; + cy.window().then(win => { + cy.stub(win, 'prompt').returns(link); + }); + cy.focused().clickLinkButton(); + + cy.confirmMarkdownEditorContent(`

${link}

`); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(300); + cy.clickModeToggle(); + + cy.confirmRawEditorContent(`<${link}>`); + }); + + it('can add a new invalid link', () => { + const link = 'www.netlifycms.org'; + cy.window().then(win => { + cy.stub(win, 'prompt').returns(link); + }); + cy.focused().clickLinkButton(); + + cy.confirmMarkdownEditorContent(`

${link}

`); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(300); + cy.clickModeToggle(); + + cy.confirmRawEditorContent(`[${link}](${link})`); + }); + + it('can select existing text as link', () => { + const link = 'https://www.netlifycms.org'; + cy.window().then(win => { + cy.stub(win, 'prompt').returns(link); + }); + + const text = 'Netlify CMS'; + cy.focused() + .getMarkdownEditor() + .type(text) + .setSelection(text) + .clickLinkButton(); + + cy.confirmMarkdownEditorContent(`

${text}

`); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(300); + cy.clickModeToggle(); + + cy.confirmRawEditorContent(`[${text}](${link})`); + }); + }); +}); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 145f7070..02468f8e 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -268,6 +268,7 @@ Cypress.Commands.add('insertEditorComponent', title => { ['clickCodeButton', 'Code'], ['clickItalicButton', 'Italic'], ['clickQuoteButton', 'Quote'], + ['clickLinkButton', 'Link'], ].forEach(([commandName, toolbarButtonName]) => { Cypress.Commands.add(commandName, opts => { return cy.clickToolbarButton(toolbarButtonName, opts); @@ -316,6 +317,12 @@ Cypress.Commands.add('clearMarkdownEditorContent', () => { .backspace({ times: 2 }); }); +Cypress.Commands.add('confirmRawEditorContent', expectedDomString => { + cy.get('.cms-editor-raw').within(() => { + cy.contains('span', expectedDomString); + }); +}); + function toPlainTree(domString) { return rehype() .use(removeSlateArtifacts) diff --git a/packages/netlify-cms-widget-markdown/src/MarkdownControl/plugins/Link.js b/packages/netlify-cms-widget-markdown/src/MarkdownControl/plugins/Link.js index 7d181b71..97e86a5f 100644 --- a/packages/netlify-cms-widget-markdown/src/MarkdownControl/plugins/Link.js +++ b/packages/netlify-cms-widget-markdown/src/MarkdownControl/plugins/Link.js @@ -7,12 +7,18 @@ const Link = ({ type }) => ({ const url = getUrl(); if (!url) return; - // If no text is selected, use the entered URL as text. - if (editor.value.isCollapsed) { - editor.insertText(url).moveFocusBackward(0 - url.length); + const selection = editor.value.selection; + const isCollapsed = selection && selection.isCollapsed; + if (isCollapsed) { + // If no text is selected, use the entered URL as text. + return editor.insertInline({ + type, + data: { url }, + nodes: [{ object: 'text', text: url }], + }); + } else { + return editor.wrapInline({ type, data: { url } }).moveToEnd(); } - - return editor.wrapInline({ type, data: { url } }).moveToEnd(); } }, },