Koenig tests. (#564)

* 👷   Editor tests
Added acceptance tests for koenig markdown-like support.
Added some unit tests for koenig located in the /lib/koenig/test-support directory.
This commit is contained in:
Ryan McCarvill 2017-03-07 23:57:09 +13:00 committed by GitHub
parent 9c137df839
commit c5b0301e87
21 changed files with 612 additions and 31 deletions

55
ghost/admin/lib/gh-koenig/.gitignore vendored Normal file
View File

@ -0,0 +1,55 @@
b-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
npm-debug.log
.nvmrc
.bowerrc
.idea/*
*.iml
*.sublime-*
projectFilesBackup
.DS_Store
# vim-related
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
.vimrc
*~
# TernJS
.tern-project
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
# dependencies
/node_modules
/bower_components
# misc
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
# built by grunt
public/assets/img/contributors/
app/templates/-contributors.hbs

View File

@ -1,6 +1,6 @@
import Component from 'ember-component';
import layout from '../../templates/components/markdown-card';
import {formatMarkdown} from '../../libs/format-markdown';
import {formatMarkdown} from '../../lib/format-markdown';
import injectService from 'ember-service/inject';
import {invokeAction} from 'ember-invoke-action';
import {isEmberArray} from 'ember-array/utils';

View File

@ -4,7 +4,7 @@ import run from 'ember-runloop';
import layout from '../templates/components/gh-koenig';
import Mobiledoc from 'mobiledoc-kit';
import {MOBILEDOC_VERSION} from 'mobiledoc-kit/renderers/mobiledoc';
import createCardFactory from '../libs/card-factory';
import createCardFactory from '../lib/card-factory';
import defaultCommands from '../options/default-commands';
import editorCards from '../cards/index';
// import { VALID_MARKUP_SECTION_TAGNAMES } from 'mobiledoc-kit/models/markup-section'; //the block elements supported by mobile-doc

View File

@ -1,6 +1,5 @@
import Component from 'ember-component';
import layout from '../templates/components/koenig-menu-item';
import Range from 'mobiledoc-kit/utils/cursor/range';
export default Component.extend({
layout,
@ -12,7 +11,7 @@ export default Component.extend({
actions: {
select() {
let {section, startOffset, endOffset} = this.get('range');
let {section/* , startOffset, endOffset */} = this.get('range');
window.getSelection().removeAllRanges();
let range = document.createRange();
@ -23,20 +22,6 @@ export default Component.extend({
let selection = window.getSelection();
selection.addRange(range);
// TODO: remove console.log
// eslint-disable-next-line no-console
console.log(startOffset, endOffset, Range);
// let editor = this.get('editor');
// let range = editor.range;
// console.log(endOffset, startOffset);
// range = range.extend(endOffset - startOffset);
// editor.run((postEditor) => {
// let position = postEditor.deleteRange(range);
// let em = postEditor.builder.createMarkup('em');
// let nextPosition = postEditor.insertTextWithMarkup(position, 'BOO', [em]);
// postEditor.insertTextWithMarkup(nextPosition, '', []); // insert the un-marked-up space
// });
this.get('tool').onClick(this.get('editor'));
}
}

View File

@ -187,6 +187,7 @@ export default Component.extend({
this.$('.koenig-menu').hide();
// note: below is using a mobiledoc Private API.
// there is no way to unregister a keycommand when it's registered so we have to remove it ourselves.
// edit: I've put a PR in place and there is now a public API to remove, will add when released.
for (let i = this.editor._keyCommands.length - 1; i > -1; i--) {
let keyCommand = this.editor._keyCommands[i];

View File

@ -20,7 +20,7 @@ export default Component.extend({
willRender() {
// TODO: remove console.log
// eslint-disable-next-line no-console
console.log(`gh-toolbar-btn-${this.tool.class}`);
this.set(`gh-toolbar-btn-${this.tool.class}`, true);
if (this.tool.selected) {
this.set('selected', true);

View File

@ -9,7 +9,7 @@ export default Component.extend({
init() {
this._super(...arguments);
let editor = this.editor = this.get('editor');
let editor = this.get('editor');
let tools = new Tools(editor, this);
this.tools = [];
@ -20,19 +20,10 @@ export default Component.extend({
});
this.iconURL = `${this.get('assetPath')}/tools/`;
// this.set('toolbar') =
// this.tools =new Tools(this.get('editor'), this);
// let tools = [ ];
// let match = (this.query || "").trim().toLowerCase();
// this.tools.forEach((tool) => {
// if ((tool.type === 'block' || tool.type === 'newline') && tool.name.startsWith(match)) {
// tools.push(tool);
// }
// });
},
didRender() {
let $this = this.$();
let {editor} = this;
let editor = this.get('editor');
let $editor = $('.gh-editor-container');
if (!editor.range || !editor.range.head.section || !editor.range.head.section.isBlank
@ -41,8 +32,8 @@ export default Component.extend({
}
editor.cursorDidChange(() => {
// if there is no cursor:
// if there is no cursor:
if (!editor.range || !editor.range.head.section || !editor.range.head.section.isBlank
|| editor.range.head.section.renderNode._element.tagName.toLowerCase() !== 'p') {
$this.hide();

View File

@ -0,0 +1,21 @@
/* jshint expr:true */
import {expect} from 'chai';
import {describe, it} from 'mocha';
import {setupComponentTest} from 'ember-mocha';
import {editorShim} from '../../utils';
describe.skip('Unit: Component: koenig-menu', function () {
setupComponentTest('koenig-menu', {
unit: true
});
it('renders', function () {
let component = this.subject();
component.editor = editorShim;
expect(component._state).to.equal('preRender');
// renders the component on the page
this.render();
expect(component._state).to.equal('inDOM');
});
});

View File

@ -0,0 +1,20 @@
/* jshint expr:true */
import {expect} from 'chai';
import {describe, it} from 'mocha';
import {setupComponentTest} from 'ember-mocha';
describe.skip('Unit: Component: koenig-toolbar-button', function () {
setupComponentTest('koenig-toolbar-button', {
unit: true
});
it('renders', function () {
let component = this.subject();
expect(component._state).to.equal('preRender');
// renders the component on the page
this.render();
expect(component._state).to.equal('inDOM');
});
});

View File

@ -0,0 +1,26 @@
/* jshint expr:true */
import {expect} from 'chai';
import {describe, it} from 'mocha';
import {setupComponentTest} from 'ember-mocha';
import {editorShim} from '../../utils';
describe.skip('Unit: Component: koenig-toolbar-newitem', function () {
setupComponentTest('koenig-toolbar-newitem', {
unit: true,
needs: [
'component:koenig-toolbar-button'
]
});
it('renders', function () {
let component = this.subject();
component.editor = editorShim;
expect(component._state).to.equal('preRender');
// renders the component on the page
this.render();
expect(component._state).to.equal('inDOM');
});
});

View File

@ -0,0 +1,22 @@
/* jshint expr:true */
import {expect} from 'chai';
import {describe, it} from 'mocha';
import {setupComponentTest} from 'ember-mocha';
import sinon from 'sinon';
describe.skip('Unit: Component: koenig-toolbar', function () {
setupComponentTest('koenig-toolbar', {
unit: true
});
it('The toolbar is not rendered by default.', function () {
let component = this.subject();
expect(component.isVisible).to.be.false;
});
it('The toolbar contains tools.', function () {
let component = this.subject();
expect(component.get('toolbar').length).to.be.greaterThan(0); // the standard toolbar tools (strong, em, strikethrough, link)
expect(component.get('toolbarBlocks').length).to.be.greaterThan(0); // extended toolbar block bases tools (h1, h2, quote);
});
});

View File

@ -0,0 +1,38 @@
/* jshint expr:true */
import {expect} from 'chai';
import {describe, it} from 'mocha';
import {setupComponentTest} from 'ember-mocha';
import sinon from 'sinon';
describe.skip('Unit: Component: koenig-toolbar', function () {
setupComponentTest('koenig-toolbar', {
unit: true
});
it('The toolbar is not visible by default.', function () {
let component = this.subject();
expect(component.isVisible).to.be.false;
});
it('The toolbar contains tools.', function () {
let component = this.subject();
expect(component.get('toolbar').length).to.be.greaterThan(0); // the standard toolbar tools (strong, em, strikethrough, link)
expect(component.get('toolbarBlocks').length).to.be.greaterThan(0); // extended toolbar block bases tools (h1, h2, quote);
});
// it('The toolbar appears when a range is selected.', function () {
// let component = this.subject();
// });
// it('A tool is selected when the cursor moves over text of that style.', function () {
// let component = this.subject();
// });
// it('A tool manipulates the content.', function () {
// let component = this.subject();
// });
// it('links stuff', function() {
// });
});

View File

@ -0,0 +1,15 @@
export let editorShim = {
range: {
head: {
section: {
renderNode: {
_element: {
tagName: 'P'
}
},
isBlank: false
}
}
},
cursorDidChange: function() {}
};

View File

@ -0,0 +1,362 @@
/* jshint expr:true */
import {
describe,
it,
beforeEach,
afterEach
} from 'mocha';
import {expect} from 'chai';
import startApp from '../helpers/start-app';
import destroyApp from '../helpers/destroy-app';
import {editorRendered, testInput} from '../helpers/editor-helpers';
import {authenticateSession} from 'ghost-admin/tests/helpers/ember-simple-auth';
describe('Acceptance: Editor', function() {
this.timeout(25000);
let application;
beforeEach(function() {
application = startApp();
});
afterEach(function() {
destroyApp(application);
});
describe('Markerable markdown support.', function () {
beforeEach(function () {
let role = server.create('role', {name: 'Administrator'});
server.create('user', {roles: [role]});
server.loadFixtures('settings');
return authenticateSession(application);
});
it('the editor renders correctly', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
expect(currentURL(), 'currentURL')
.to.equal('/editor/1');
expect(find('.surface').prop('contenteditable'), 'editor is editable')
.to.equal('true');
expect(window.editor)
.to.be.an('object');
});
});
it('plain text inputs (placebo)', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('abcdef', '<p>abcdef</p>', expect);
});
});
// bold
it('** bolds at start of line', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('**test**', '<p><strong>test</strong></p>', expect);
});
});
it('** bolds in a line', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('123**test**', '<p>123<strong>test</strong></p>', expect);
});
});
it('__ bolds at start of line', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('__test__', '<p><strong>test</strong></p>', expect);
});
});
it('__ bolds in a line', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('123__test__', '<p>123<strong>test</strong></p>', expect);
});
});
// italic
it('* italicises at start of line', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('*test*', '<p><em>test</em></p>', expect);
});
});
it('* italicises in a line', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('123*test*', '<p>123<em>test</em></p>', expect);
});
});
it('_ italicises at start of line', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('_test_', '<p><em>test</em></p>', expect);
});
});
it('_ italicises in a line', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('123_test_', '<p>123<em>test</em></p>', expect);
});
});
// strikethrough
it('~~ strikethroughs at start of line', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('~~test~~', '<p><s>test</s></p>', expect);
});
});
it('~~ strikethroughs in a line', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('123~~test~~', '<p>123<s>test</s></p>', expect);
});
});
// links
it('[]() creates a link at start of line', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('[ghost](https://www.ghost.org/)', '<p><a href="https://www.ghost.org/">ghost</a></p>', expect);
});
});
it('[]() creates a link in a line', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('123[ghost](https://www.ghost.org/)', '<p>123<a href="https://www.ghost.org/">ghost</a></p>', expect);
});
});
});
describe('Block markdown support.', function () {
beforeEach(function () {
let role = server.create('role', {name: 'Administrator'});
server.create('user', {roles: [role]});
server.loadFixtures('settings');
return authenticateSession(application);
});
// headings
it('# creates an H1', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('# ', '<h1><br></h1>', expect);
});
});
it('## creates an H2', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('## ', '<h2><br></h2>', expect);
});
});
it('### creates an H3', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('### ', '<h3><br></h3>', expect);
});
});
// lists
it('* creates an UL', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('* ', '<ul><li><br></li></ul>', expect);
});
});
it('- creates an UL', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('- ', '<ul><li><br></li></ul>', expect);
});
});
it('1. creates an OL', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('1. ', '<ol><li><br></li></ol>', expect);
});
});
// quote
it('> creates an blockquote', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('> ', '<blockquote><br></blockquote>', expect);
});
});
});
// card interactions and styling are still a WIP
describe.skip('Card markdown support.', function () {
beforeEach(function () {
let role = server.create('role', {name: 'Administrator'});
server.create('user', {roles: [role]});
server.loadFixtures('settings');
return authenticateSession(application);
});
it('![]() creates an image card.', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('![image of something](https://unsplash.it/200/300/?random) ', '...', expect);
});
});
it('``` creates a markdown card.', function () {
server.createList('post', 1);
visit('/editor/1');
andThen(() => {
return editorRendered();
});
andThen(() => {
return testInput('```some code``` ', '...', expect);
});
});
});
});

View File

@ -0,0 +1,45 @@
import Ember from 'ember';
// polls the editor until it's started.
export function editorRendered() {
return Ember.Test.promise(function (resolve) { // eslint-disable-line
function checkEditor() {
if (window.editor) {
return resolve();
} else {
window.requestAnimationFrame(checkEditor);
}
}
checkEditor();
});
}
// simulates text inputs into the editor, unfortunately the helper Ember helper functions
// don't work on content editable so we have to manipuate the text input event manager
// in mobiledoc-kit directly. This is a private API.
export function inputText(editor, text) {
editor._eventManager._textInputHandler.handle(text);
}
// inputs text and waits for the editor to modify the dom with the desired result or timesout.
export function testInput(input, output, expect) {
window.editor.element.focus(); // for some reason the editor doesn't work until it's focused when run in ghost-admin.
return Ember.Test.promise(function (resolve, reject) { // eslint-disable-line
let lastRender = '';
let isRejected = false;
let rejectTimeout = window.setTimeout(() => {
expect(lastRender).to.equal(output); // we know this is false but include it for the output.
reject(lastRender);
isRejected = true;
}, 500);
window.editor.didRender(() => {
lastRender = window.editor.element.innerHTML;
if (window.editor.element.innerHTML === output && !isRejected) {
window.clearTimeout(rejectTimeout);
expect(lastRender).to.equal(output); // we know this is true but include it for the output.
return resolve(lastRender);
}
});
inputText(window.editor, input);
});
}