feat: Code Widget + Markdown Widget Internal Overhaul (#2828)

* wip - upgrade to slate 0.43

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* finish list handling logic

* add plugins directory

* tests wip

* setup testing

* wip

* add selection commands

* finish list testing

* stuff

* add codemirror

* abstract codemirror from slate

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* codemirror mostly working, some bugs

* upgrade to slate 46

* upgrade to slate 47

* wip

* wip

* progress

* wip

* mostly working links with surrounding marks

* wip

* tests passing

* add test

* fix formatting

* update snapshots

* close self closing tag in markdown html output

* wip - commonmark

* hold on commonmark work

* all tests passing

* fix e2e specs

* ignore tests in esm builds

* break/backspace plugins wip

* finish enter/backspace spec

* fix soft break handling

* wip - editor component deletion

* add insertion points

* make insertion points invisible

* fix empty mark nodes output to markdown

* fix pasting

* improve insertion points

* add static bottom insertion point

* improve click handling at insertion points

* restore current table functionality

* add paste support for Slate fragments

* support cut/copy markdown, paste between rich/raw editor

* fix copy paste

* wip - paste/select bug fixing

* fixed known slate issues

* split plugins

* fix editor toggles

* force text cursor in code widget

* wip - reorg plugins

* finish markdown control reorg

* configure plugin types

* quote block adjacent handling with tests

* wip

* finish quote logic and tests

* fix copy paste plugin migration regressions

* fix force insert before node

* fix trailing insertion point

* remove empty headers

* codemirror working properly in markdown widget

* return focus to codemirror on lang select enter

* fix state issues for widgets with local state

* wip - vim working, just need to work out distribution

* add settings pane

* wip - default modes

* fix deps

* add programming language data

* implement linguist langs in code widget

* everything built in

* remove old registration code, fix focus styling

* fix/update linting setup

* fix js lint errors

* remove stylelint from format script

* fix remaining linting errors

* fix reducer test failures

* chore: update commitlint for worktree support

* chore: fix remaining tests

* chore: drop unused monaco plugin

* chore: remove extraneous global styles rendering

* chore: fix failing tests

* fix: tests

* fix: quote/list nesting (tests still broken)

* fix: update quote tests

* chore: bring back code widget test config

* fix: autofocus

* fix: code blocks without the code widget

* fix: code editor component state issues

* fix: error

* fix: add code block test, few fixes

* chore: remove notes

* fix: [wip] update stateful shortcodes on undo/redo

* fix: support code styled links, handle unknown langs

* fix: few fixes

* fix: autofocus on insert, focus on all clicks

* fix: linting

* fix: autofocus

* fix: update code block fixture

* fix: remove unused cypress snapshot plugin

* fix: drop node 8 test, add node 12

* fix: use lodash.flatten instead of Array.flat

* fix: remove console logs
This commit is contained in:
Shawn Erquhart
2019-12-16 12:17:37 -05:00
committed by Erez Rokah
parent be46293f82
commit 18c579d0e9
110 changed files with 12693 additions and 8516 deletions

View File

@ -0,0 +1,82 @@
import '../utils/dismiss-local-backup';
describe('Markdown widget', () => {
before(() => {
Cypress.config('defaultCommandTimeout', 4000);
cy.task('setupBackend', { backend: 'test' });
cy.loginAndNewPost();
});
beforeEach(() => {
cy.clearMarkdownEditorContent();
});
after(() => {
cy.task('teardownBackend', { backend: 'test' });
});
describe('pressing backspace', () => {
it('sets non-default block to default when empty', () => {
cy.focused()
.clickHeadingOneButton()
.backspace()
.confirmMarkdownEditorContent(`
<p></p>
`);
});
it('does nothing at start of first block in document when non-empty and non-default', () => {
cy.focused()
.clickHeadingOneButton()
.type('foo')
.setCursorBefore('foo')
.backspace({ times: 4 })
.confirmMarkdownEditorContent(`
<h1>foo</h1>
`);
});
it('deletes individual characters in middle of non-empty non-default block in document', () => {
cy.focused()
.clickHeadingOneButton()
.type('foo')
.setCursorAfter('fo')
.backspace({ times: 3 })
.confirmMarkdownEditorContent(`
<h1>o</h1>
`);
});
it('at beginning of non-first block, moves default block content to previous block', () => {
cy.focused()
.clickHeadingOneButton()
.type('foo')
.enter()
.type('bar')
.setCursorBefore('bar')
.backspace()
.confirmMarkdownEditorContent(`
<h1>foobar</h1>
`);
});
it('at beginning of non-first block, moves non-default block content to previous block', () => {
cy.focused()
.type('foo')
.enter()
.clickHeadingOneButton()
.type('bar')
.enter()
.clickHeadingTwoButton()
.type('baz')
.setCursorBefore('baz')
.backspace()
.confirmMarkdownEditorContent(`
<p>foo</p>
<h1>barbaz</h1>
`)
.setCursorBefore('bar')
.backspace()
.confirmMarkdownEditorContent(`
<p>foobarbaz</p>
`);
});
});
});

View File

@ -0,0 +1,120 @@
import { oneLineTrim, stripIndent } from 'common-tags';
import '../utils/dismiss-local-backup';
describe('Markdown widget', () => {
before(() => {
Cypress.config('defaultCommandTimeout', 4000);
cy.task('setupBackend', { backend: 'test' });
cy.loginAndNewPost();
});
beforeEach(() => {
cy.clearMarkdownEditorContent();
});
after(() => {
cy.task('teardownBackend', { backend: 'test' });
});
describe('code block', () => {
it('outputs code', () => {
cy.insertCodeBlock()
.type('foo')
.enter()
.type('bar')
.confirmMarkdownEditorContent(`
${codeBlock(`
foo
bar
`)}
`)
.clickModeToggle()
.confirmMarkdownEditorContent(`
${codeBlockRaw(`
foo
bar
`)}
`)
})
})
})
function codeBlockRaw(content) {
return ['```', ...stripIndent(content).split('\n'), '```'].map(line => oneLineTrim`
<div>
<span>
<span>
<span>${line}</span>
</span>
</span>
</div>
`).join('');
}
function codeBlock(content) {
const lines = stripIndent(content).split('\n').map((line, idx) => `
<div>
<div>
<div>${idx + 1}</div>
</div>
<pre><span>${line}</span></pre>
</div>
`).join('');
return oneLineTrim`
<div>
<div><span><span><span><span></span><span></span></span></span></span></div>
<div>
<div>
<div></div>
<div>
<div><label>Code Block</label>
<div><button><span><svg>
<path></path>
</svg></span></button>
<div>
<div>
<div><textarea></textarea></div>
<div>
<div></div>
</div>
<div>
<div></div>
</div>
<div></div>
<div></div>
<div>
<div>
<div>
<div>
<div>
<div>
<pre><span>xxxxxxxxxx</span></pre>
</div>
<div></div>
<div></div>
<div>
<div> </div>
</div>
<div>
${lines}
</div>
</div>
</div>
</div>
</div>
<div></div>
<div>
<div></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div></div>
</div>
</div>
</div>
`;
}

View File

@ -0,0 +1,108 @@
import '../utils/dismiss-local-backup';
describe('Markdown widget breaks', () => {
before(() => {
Cypress.config('defaultCommandTimeout', 4000);
cy.task('setupBackend', { backend: 'test' });
cy.loginAndNewPost();
});
beforeEach(() => {
cy.clearMarkdownEditorContent();
});
after(() => {
cy.task('teardownBackend', { backend: 'test' });
});
describe('pressing enter', () => {
it('creates new default block from empty block', () => {
cy.focused()
.enter()
.confirmMarkdownEditorContent(`
<p></p>
<p></p>
`);
});
it('creates new default block when selection collapsed at end of block', () => {
cy.focused()
.type('foo')
.enter()
.confirmMarkdownEditorContent(`
<p>foo</p>
<p></p>
`);
});
it('creates new default block when selection collapsed at end of non-default block', () => {
cy.clickHeadingOneButton()
.type('foo')
.enter()
.confirmMarkdownEditorContent(`
<h1>foo</h1>
<p></p>
`);
});
it('creates new default block when selection collapsed in empty non-default block', () => {
cy.clickHeadingOneButton()
.enter()
.confirmMarkdownEditorContent(`
<h1></h1>
<p></p>
`);
});
it('splits block into two same-type blocks when collapsed selection at block start', () => {
cy.clickHeadingOneButton()
.type('foo')
.setCursorBefore('foo')
.enter()
.confirmMarkdownEditorContent(`
<h1></h1>
<h1>foo</h1>
`);
});
it('splits block into two same-type blocks when collapsed in middle of selection at block start', () => {
cy.clickHeadingOneButton()
.type('foo')
.setCursorBefore('oo')
.enter()
.confirmMarkdownEditorContent(`
<h1>f</h1>
<h1>oo</h1>
`);
});
it('deletes selected content and splits to same-type block when selection is expanded', () => {
cy.clickHeadingOneButton()
.type('foo bar')
.setSelection('o b')
.enter()
.confirmMarkdownEditorContent(`
<h1>fo</h1>
<h1>ar</h1>
`);
});
});
describe('pressing shift+enter', () => {
it('creates line break', () => {
cy.focused()
.enter({ shift: true })
.confirmMarkdownEditorContent(`
<p>
<br>
</p>
`);
});
it('creates consecutive line break', () => {
cy.focused()
.enter({ shift: true, times: 4 })
.confirmMarkdownEditorContent(`
<p>
<br>
<br>
<br>
<br>
</p>
`);
});
});
});

View File

@ -0,0 +1,681 @@
import '../utils/dismiss-local-backup';
describe('Markdown widget', () => {
describe('list', () => {
before(() => {
Cypress.config('defaultCommandTimeout', 4000);
cy.task('setupBackend', { backend: 'test' });
cy.loginAndNewPost();
});
beforeEach(() => {
cy.clearMarkdownEditorContent();
});
after(() => {
cy.task('teardownBackend', { backend: 'test' });
});
describe('toolbar buttons', () => {
it('creates and focuses empty list', () => {
cy.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p></p>
</li>
</ul>
`);
});
it('removes list', () => {
cy.clickUnorderedListButton({ times: 2 })
.confirmMarkdownEditorContent(`
<p></p>
`);
});
it('creates nested list when selection is collapsed in non-first block of list item', () => {
cy.clickUnorderedListButton()
.type('foo')
.enter()
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<ul>
<li>
<p></p>
</li>
</ul>
</li>
</ul>
`)
.type('bar')
.enter()
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<ul>
<li>
<p>bar</p>
<ul>
<li>
<p></p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
`);
});
it('converts empty nested list item to empty block in parent list item', () => {
cy.clickUnorderedListButton()
.type('foo')
.enter()
.clickUnorderedListButton()
.type('bar')
.enter()
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<ul>
<li>
<p>bar</p>
<ul>
<li>
<p></p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
`)
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<ul>
<li>
<p>bar</p>
<p></p>
</li>
</ul>
</li>
</ul>
`)
.backspace({ times: 4 })
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<p></p>
</li>
</ul>
`);
});
it('moves nested list item content to parent list item when in first block', () => {
cy.clickUnorderedListButton()
.type('foo')
.enter()
.clickUnorderedListButton()
.type('bar')
.enter()
.clickUnorderedListButton()
.type('baz')
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<ul>
<li>
<p>bar</p>
<p>baz</p>
</li>
</ul>
</li>
</ul>
`)
.up()
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<p>bar</p>
<p>baz</p>
</li>
</ul>
`)
.up()
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<p>foo</p>
<p>bar</p>
<p>baz</p>
`);
});
it('affects only the current block with collapsed selection', () => {
cy.focused()
.type('foo')
.enter()
.type('bar')
.enter()
.type('baz')
.up()
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<p>foo</p>
<ul>
<li>
<p>bar</p>
</li>
</ul>
<p>baz</p>
`);
});
it('combines adjacent same-typed lists, not differently typed lists', () => {
cy.focused()
.type('foo')
.enter()
.type('bar')
.enter()
.type('baz')
.up()
.clickUnorderedListButton()
.up()
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
<li>
<p>bar</p>
</li>
</ul>
<p>baz</p>
`)
.down({ times: 2 })
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
<li>
<p>bar</p>
</li>
<li>
<p>baz</p>
</li>
</ul>
`)
.up()
.enter()
.type('qux')
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
<li>
<p>bar</p>
<ul>
<li>
<p>qux</p>
</li>
</ul>
</li>
<li>
<p>baz</p>
</li>
</ul>
`)
.up()
.enter()
.type('quux')
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
<li>
<p>bar</p>
<ul>
<li>
<p>quux</p>
</li>
<li>
<p>qux</p>
</li>
</ul>
</li>
<li>
<p>baz</p>
</li>
</ul>
`)
.clickOrderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
<li>
<p>bar</p>
<ol>
<li>
<p>quux</p>
</li>
</ol>
<ul>
<li>
<p>qux</p>
</li>
</ul>
</li>
<li>
<p>baz</p>
</li>
</ul>
`)
.setSelection({
anchorQuery: 'ul > li > ol p',
anchorOffset: 1,
focusQuery: 'ul > li > ul:last-child p',
focusOffset: 2,
});
});
it('affects only selected list items', () => {
cy.clickUnorderedListButton()
.type('foo')
.enter({ times: 2 })
.type('bar')
.enter({ times: 2 })
.type('baz')
.setSelection('bar')
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
</ul>
<p>bar</p>
<ul>
<li>
<p>baz</p>
</li>
</ul>
`)
.clickUnorderedListButton()
.setSelection('bar', 'baz')
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
</ul>
<p>bar</p>
<p>baz</p>
`)
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
<li>
<p>bar</p>
<p>baz</p>
</li>
</ul>
`)
.setSelection('baz')
.clickUnorderedListButton()
.setCursorAfter('baz')
.enter()
.clickUnorderedListButton()
.type('qux')
.setSelection('baz')
.clickOrderedListButton()
.setCursorAfter('qux')
.enter({ times: 4 })
.clickUnorderedListButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
<li>
<p>bar</p>
<ol>
<li>
<p>baz</p>
<ul>
<li>
<p>qux</p>
</li>
</ul>
</li>
</ol>
<ul>
<li>
<p></p>
</li>
</ul>
</li>
</ul>
`)
});
});
describe('on Enter', () => {
it('removes the list item and list if empty', () => {
cy.clickUnorderedListButton()
.enter()
.confirmMarkdownEditorContent(`
<p></p>
`);
});
it('creates a new paragraph in a non-empty paragraph within a list item', () => {
cy.clickUnorderedListButton()
.type('foo')
.enter()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<p></p>
</li>
</ul>
`)
.type('bar')
.enter()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<p>bar</p>
<p></p>
</li>
</ul>
`);
});
it('creates a new list item in an empty paragraph within a non-empty list item', () => {
cy.clickUnorderedListButton()
.type('foo')
.enter({ times: 2 })
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
<li>
<p></p>
</li>
</ul>
`)
.type('bar')
.enter()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
<li>
<p>bar</p>
<p></p>
</li>
</ul>
`);
});
it('creates a new block below list', () => {
cy.clickUnorderedListButton()
.type('foo')
.enter({ times: 3 })
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
</ul>
<p></p>
`);
});
});
describe('on Backspace', () => {
it('removes the list item and list if empty', () => {
cy.clickUnorderedListButton()
.backspace()
.confirmMarkdownEditorContent(`
<p></p>
`);
});
it('removes empty block in non-empty list item', () => {
cy.clickUnorderedListButton()
.type('foo')
.enter()
.backspace()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
</ul>
`);
});
it('removes the list item if list not empty', () => {
cy.clickUnorderedListButton()
.type('foo')
.enter({ times: 2 })
.backspace()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<p></p>
</li>
</ul>
`);
});
it('does not remove list item if empty with non-default block', () => {
cy.clickUnorderedListButton()
.clickHeadingOneButton()
.backspace()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p></p>
</li>
</ul>
`);
});
});
describe('on Tab', () => {
it('does nothing in top level list', () => {
cy.clickUnorderedListButton()
.tabkey()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p></p>
</li>
</ul>
`)
.type('foo')
.tabkey()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
</ul>
`)
});
it('indents nested list items', () => {
cy.clickUnorderedListButton()
.type('foo')
.enter({ times: 2 })
.type('bar')
.tabkey()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<ul>
<li>
<p>bar</p>
</li>
</ul>
</li>
</ul>
`)
.enter({ times: 2 })
.tabkey()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<ul>
<li>
<p>bar</p>
<ul>
<li>
<p></p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
`)
});
it('only nests up to one level down from the parent list', () => {
cy.clickUnorderedListButton()
.type('foo')
.enter({ times: 2 })
.tabkey({ times: 5 })
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<ul>
<li>
<p></p>
</li>
</ul>
</li>
</ul>
`);
});
it('unindents nested list items with shift', () => {
cy.clickUnorderedListButton()
.type('foo')
.enter({ times: 2 })
.tabkey()
.tabkey({ shift: true })
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
<li>
<p></p>
</li>
</ul>
`)
});
it('indents and unindents from one level below parent back to document root', () => {
cy.clickUnorderedListButton()
.type('foo')
.enter({ times: 2 })
.tabkey()
.type('bar')
.enter({ times: 2 })
.tabkey()
.type('baz')
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<ul>
<li>
<p>bar</p>
<ul>
<li>
<p>baz</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
`)
.tabkey({ shift: true })
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<ul>
<li>
<p>bar</p>
</li>
<li>
<p>baz</p>
</li>
</ul>
</li>
</ul>
`)
.tabkey({ shift: true })
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
<ul>
<li>
<p>bar</p>
</li>
</ul>
</li>
<li>
<p>baz</p>
</li>
</ul>
`)
});
});
});
});

View File

@ -0,0 +1,36 @@
import '../utils/dismiss-local-backup';
describe('Markdown widget', () => {
describe('code mark', () => {
before(() => {
Cypress.config('defaultCommandTimeout', 4000);
cy.task('setupBackend', { backend: 'test' });
cy.loginAndNewPost();
});
beforeEach(() => {
cy.clearMarkdownEditorContent();
});
after(() => {
cy.task('teardownBackend', { backend: 'test' });
});
describe('toolbar button', () => {
it('can combine code mark with other marks', () => {
cy.clickItalicButton()
.type('foo')
.setSelection('oo')
.clickCodeButton()
.confirmMarkdownEditorContent(`
<p>
<em>f</em>
<code>
<em>oo</em>
</code>
</p>
`);
});
});
});
});

View File

@ -0,0 +1,307 @@
import '../utils/dismiss-local-backup';
describe('Markdown widget', () => {
describe('quote block', () => {
before(() => {
Cypress.config('defaultCommandTimeout', 4000);
cy.task('setupBackend', { backend: 'test' });
cy.loginAndNewPost();
});
beforeEach(() => {
cy.clearMarkdownEditorContent();
});
after(() => {
cy.task('teardownBackend', { backend: 'test' });
});
describe('toggle quote', () => {
it('toggles empty quote block on and off in empty editor', () => {
cy.clickQuoteButton()
.confirmMarkdownEditorContent(`
<blockquote>
<p></p>
</blockquote>
`)
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<p></p>
`);
});
it('toggles empty quote block on and off for current block', () => {
cy.focused()
.type('foo')
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<blockquote>
<p>foo</p>
</blockquote>
`)
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<p>foo</p>
`);
});
it('toggles entire quote block without expanded selection', () => {
cy.clickQuoteButton()
.type('foo')
.enter()
.type('bar')
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<p>foo</p>
<p>bar</p>
`);
});
it('toggles entire quote block with complex content', () => {
cy.clickQuoteButton()
.clickUnorderedListButton()
.clickHeadingOneButton()
.type('foo')
.enter({ times: 3 })
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<h1>foo</h1>
</li>
</ul>
<p></p>
`);
});
it('toggles empty quote block on and off for selected blocks', () => {
cy.focused()
.type('foo')
.enter()
.type('bar')
.setSelection('foo', 'bar')
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<blockquote>
<p>foo</p>
<p>bar</p>
</blockquote>
`)
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<p>foo</p>
<p>bar</p>
`)
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<blockquote>
<p>foo</p>
<p>bar</p>
</blockquote>
`);
});
it('toggles empty quote block on and off for partially selected blocks', () => {
cy.focused()
.type('foo')
.enter()
.type('bar')
.setSelection('oo', 'ba')
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<blockquote>
<p>foo</p>
<p>bar</p>
</blockquote>
`)
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<p>foo</p>
<p>bar</p>
`)
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<blockquote>
<p>foo</p>
<p>bar</p>
</blockquote>
`);
});
it('toggles quote block on and off for multiple selected list items', () => {
cy.focused()
.clickUnorderedListButton()
.type('foo')
.enter({ times: 2 })
.type('bar')
.setSelection('foo', 'bar')
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<blockquote>
<ul>
<li>
<p>foo</p>
</li>
<li>
<p>bar</p>
</li>
</ul>
</blockquote>
`)
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
<li>
<p>bar</p>
</li>
</ul>
`)
.setCursorAfter('bar')
.enter({ times: 2 })
.type('baz')
.setSelection('bar', 'baz')
.clickQuoteButton()
.confirmMarkdownEditorContent(`
<ul>
<li>
<p>foo</p>
</li>
</ul>
<blockquote>
<ul>
<li>
<p>bar</p>
</li>
<li>
<p>baz</p>
</li>
</ul>
</blockquote>
`)
});
it('creates new quote block if parent is not a quote, can deeply nest', () => {
cy.clickQuoteButton()
.clickUnorderedListButton()
.clickQuoteButton()
.clickUnorderedListButton()
.clickQuoteButton()
.clickUnorderedListButton()
.clickQuoteButton()
.type('foo')
.enter({ times: 10 })
.type('bar')
.confirmMarkdownEditorContent(`
<blockquote>
<ul>
<li>
<blockquote>
<ul>
<li>
<blockquote>
<ul>
<li>
<blockquote>
<p>foo</p>
</blockquote>
</li>
</ul>
</blockquote>
</li>
</ul>
</blockquote>
</li>
</ul>
<p>bar</p>
</blockquote>
`)
.backspace({ times: 12 })
});
});
describe('backspace inside quote', () => {
it('joins two paragraphs', () => {
cy.clickQuoteButton()
.type('foo')
.enter()
.type('bar')
.setCursorBefore('bar')
.backspace()
.confirmMarkdownEditorContent(`
<blockquote>
<p>foobar</p>
</blockquote>
`);
});
it('joins quote with previous quote', () => {
cy.clickQuoteButton()
.type('foo')
.enter({ times: 2 })
.clickQuoteButton()
.type('bar')
.confirmMarkdownEditorContent(`
<blockquote>
<p>foo</p>
</blockquote>
<blockquote>
<p>bar</p>
</blockquote>
`)
.setCursorBefore('bar')
.backspace()
.confirmMarkdownEditorContent(`
<blockquote>
<p>foo</p>
<p>bar</p>
</blockquote>
`);
});
it('removes first block from quote when focused at first block at start', () => {
cy.clickQuoteButton()
.type('foo')
.enter()
.type('bar')
.setCursorBefore('foo')
.backspace()
.confirmMarkdownEditorContent(`
<p>foo</p>
<blockquote>
<p>bar</p>
</blockquote>
`)
});
});
describe('enter inside quote', () => {
it('creates new block inside quote', () => {
cy.clickQuoteButton()
.type('foo')
.enter()
.confirmMarkdownEditorContent(`
<blockquote>
<p>foo</p>
<p></p>
</blockquote>
`)
.type('bar')
.setCursorAfter('ba')
.enter()
.confirmMarkdownEditorContent(`
<blockquote>
<p>foo</p>
<p>ba</p>
<p>r</p>
</blockquote>
`);
});
it('creates new block after quote from empty last block', () => {
cy.clickQuoteButton()
.type('foo')
.enter()
.enter()
.confirmMarkdownEditorContent(`
<blockquote>
<p>foo</p>
</blockquote>
<p></p>
`)
});
});
});
});