restore composer.json, add mysqli extension

This commit is contained in:
2026-04-15 17:02:52 +05:00
commit 77cf56a348
4317 changed files with 1397107 additions and 0 deletions

View File

@@ -0,0 +1,243 @@
/**
* Buttons.spec.js
* (c) 2015~ Summernote Team
* summernote may be freely distributed under the MIT license./
*/
import $ from 'jquery';
import chai from 'chai';
import chaidom from 'test/chaidom';
import env from 'src/js/base/core/env';
import range from 'src/js/base/core/range';
import Context from 'src/js/base/Context';
import 'src/js/bs4/settings';
chai.use(chaidom);
describe('Buttons', () => {
var expect = chai.expect;
var assert = chai.assert;
var context, $toolbar, $editable;
beforeEach(() => {
$('body').empty(); // important !
var $note = $('<div><p>hello</p></div>').appendTo('body');
var options = $.extend({}, $.summernote.options);
options.toolbar = [
['font1', ['style', 'clear']],
['font2', ['bold', 'underline', 'italic', 'superscript', 'subscript', 'strikethrough']],
['font3', ['fontname', 'fontsize']],
['color', ['color', 'forecolor', 'backcolor']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'picture', 'video']],
['view', ['fullscreen', 'codeview', 'help']],
];
context = new Context($note, options);
context.initialize();
$toolbar = context.layoutInfo.toolbar;
$editable = context.layoutInfo.editable;
// Select the first paragraph
range.createFromNode($editable.find('p')[0]).normalize().select();
// [workaround]
// - IE8~11 can't create range in headless mode
if (env.isMSIE) {
this.skip();
}
});
describe('bold button', () => {
it('should execute bold command when it is clicked', (done) => {
$toolbar.find('.note-btn-bold').click();
expect($editable.html()).await(done).to.equalsIgnoreCase('<p><b>hello</b></p>');
});
});
describe('bold button state updated', () => {
it('should look toggled immediately when clicked', (done) => {
var $button = $toolbar.find('.note-btn-bold');
assert.isTrue($button.length === 1);
assert.isFalse($button.hasClass('active'));
$button.click();
expect($button.hasClass('active')).await(done).to.be.true;
});
});
describe('italic button', () => {
it('should execute italic command when it is clicked', (done) => {
$toolbar.find('.note-btn-italic').click();
expect($editable.html()).await(done).to.equalsIgnoreCase('<p><i>hello</i></p>');
});
});
describe('italic button state updated', () => {
it('should look toggled immediately when clicked', (done) => {
var $button = $toolbar.find('.note-btn-italic');
assert.isTrue($button.length === 1);
assert.isFalse($button.hasClass('active'));
$button.click();
expect($button.hasClass('active')).await(done).to.be.true;
});
});
describe('underline button', () => {
it('should execute underline command when it is clicked', (done) => {
$toolbar.find('.note-btn-underline').click();
expect($editable.html()).await(done).to.equalsIgnoreCase('<p><u>hello</u></p>');
});
});
describe('underline button state updated', () => {
it('should look toggled immediately when clicked', (done) => {
var $button = $toolbar.find('.note-btn-underline');
assert.isTrue($button.length === 1);
assert.isFalse($button.hasClass('active'));
$button.click();
expect($button.hasClass('active')).await(done).to.be.true;
});
});
describe('superscript button', () => {
it('should execute superscript command when it is clicked', (done) => {
$toolbar.find('.note-btn-superscript').click();
expect($editable.html()).await(done).to.equalsIgnoreCase('<p><sup>hello</sup></p>');
});
});
describe('superscript button state updated', () => {
it('should look toggled immediately when clicked', (done) => {
var $button = $toolbar.find('.note-btn-superscript');
assert.isTrue($button.length === 1);
assert.isFalse($button.hasClass('active'));
$button.click();
expect($button.hasClass('active')).await(done).to.be.true;
});
});
describe('subscript button', () => {
it('should execute subscript command when it is clicked', (done) => {
$toolbar.find('.note-btn-subscript').click();
expect($editable.html()).await(done).to.equalsIgnoreCase('<p><sub>hello</sub></p>');
});
});
describe('subscript button state updated', () => {
it('should look toggled immediately when clicked', (done) => {
var $button = $toolbar.find('.note-btn-subscript');
assert.isTrue($button.length === 1);
assert.isFalse($button.hasClass('active'));
$button.click();
expect($button.hasClass('active')).await(done).to.be.true;
});
});
describe('strikethrough button', () => {
it('should execute strikethrough command when it is clicked', (done) => {
$toolbar.find('.note-btn-strikethrough').click();
expect($editable.html()).await(done).to.equalsIgnoreCase('<p><strike>hello</strike></p>');
});
});
describe('strikethrough button state updated', () => {
it('should look toggled immediately when clicked', (done) => {
var $button = $toolbar.find('.note-btn-strikethrough');
assert.isTrue($button.length === 1);
assert.isFalse($button.hasClass('active'));
$button.click();
expect($button.hasClass('active')).await(done).to.be.true;
});
});
describe('clear button state not updated when clicked', () => {
it('should never look toggled when clicked', (done) => {
var $button = $toolbar.find('i.note-icon-eraser').parent();
assert.isTrue($button.length === 1);
assert.isFalse($button.hasClass('active'));
$button.click();
expect($button.hasClass('active')).await(done).to.be.false;
});
});
/* Below test cannot be passed under Firefox
describe('font family button', () => {
it('should select the right font family name in the dropdown list when it is clicked', (done) => {
var $li = $toolbar.find('.dropdown-fontname a[data-value="Comic Sans MS"]');
var $span = $toolbar.find('span.note-current-fontname');
assert.isTrue($li.length === 1);
assert.isTrue($span.text() !== 'Comic Sans MS');
$li.click();
expect($span.text()).await(done).to.equalsIgnoreCase('Comic Sans MS');
});
});
*/
describe('font family button', () => {
it('should change font family when it is clicked', (done) => {
var $li = $toolbar.find('.dropdown-fontname a[data-value="Comic Sans MS"]');
var $span = $toolbar.find('span.note-current-fontname');
assert.isTrue($li.length === 1);
assert.isTrue($span.text() !== 'Comic Sans MS');
$li.click();
expect($editable.find('p').children().first()).await(done).to.be.equalsStyle('"Comic Sans MS"', 'font-family');
});
});
describe('recent color button in all color button', () => {
it('should execute color command when it is clicked', (done) => {
$toolbar.find('.note-color-all').find('.note-current-color-button').click();
expect($editable.find('p').children().first()).await(done).to.be.equalsStyle('#FFFF00', 'background-color');
});
});
describe('fore color button in all color button', () => {
it('should execute fore color command when it is clicked', (done) => {
var $button = $toolbar.find('.note-color-all .note-holder').find('.note-color-btn[data-event=foreColor]').eq(10);
$button.click();
expect($editable.find('p').children().first()).await(done).to.be.equalsStyle($button.data('value'), 'color');
});
});
describe('back color button in all color button', () => {
it('should execute back color command when it is clicked', (done) => {
var $button = $toolbar.find('.note-color-all .note-holder').find('.note-color-btn[data-event=backColor]').eq(10);
$button.click();
expect($editable.find('p').children().first()).await(done).to.be.equalsStyle($button.data('value'), 'background-color');
});
});
describe('color button in fore color button', () => {
it('should execute fore color command when it is clicked', (done) => {
var $button = $toolbar.find('.note-color-fore').find('.note-color-btn[data-event=foreColor]').eq(4);
$button.click();
expect($editable.find('p').children().first()).await(done).to.be.equalsStyle($button.data('value'), 'color');
});
});
describe('back color button in back color button', () => {
it('should execute back color command when it is clicked', (done) => {
var $button = $toolbar.find('.note-color-back').find('.note-color-btn[data-event=backColor]').eq(20);
$button.click();
expect($editable.find('p').children().first()).await(done).to.be.equalsStyle($button.data('value'), 'background-color');
});
});
describe('font size button', () => {
it('should update font size button value when changing font size', (done) => {
var $fontSizeDropdown = $toolbar.find('.dropdown-fontsize');
var $fontSizeButton = $fontSizeDropdown.siblings('button');
var $fontSizeList = $fontSizeDropdown.find('a');
var selectedSize = '36';
// click on dropdown button
$fontSizeButton.trigger('click');
// select a font size
$fontSizeList.filter('[data-value="' + selectedSize + '"]').trigger('click');
expect($fontSizeButton.text().trim()).await(done).to.equal(selectedSize);
});
});
});

View File

@@ -0,0 +1,67 @@
/**
* Codeview.spec.js
* (c) 2015~ Summernote Team
* summernote may be freely distributed under the MIT license./
*/
import $ from 'jquery';
import chai from 'chai';
import chaidom from 'test/chaidom';
import Context from 'src/js/base/Context';
import Codeview from 'src/js/base/module/Codeview';
import 'src/js/bs4/settings';
chai.use(chaidom);
describe('Codeview', () => {
var expect = chai.expect;
var options, codeview, context;
beforeEach(() => {
options = $.extend({}, $.summernote.options);
options.codeviewFilter = true;
context = new Context($('<div><p>hello</p></div>'), options);
codeview = new Codeview(context);
});
it('should toggle codeview mode', () => {
expect(codeview.isActivated()).to.be.false;
codeview.toggle();
expect(codeview.isActivated()).to.be.true;
codeview.toggle();
expect(codeview.isActivated()).to.be.false;
});
it('should purify malicious codes', () => {
expect(codeview.purify('<script>alert("summernote");</script>')).to.equalsIgnoreCase(
'alert("summernote");'
);
expect(codeview.purify('<iframe frameborder="0" src="//www.youtube.com/embed/CXgsA98krxA" width="640" height="360" class="note-video-clip"></iframe>')).to.equalsIgnoreCase(
'<iframe frameborder="0" src="//www.youtube.com/embed/CXgsA98krxA" width="640" height="360" class="note-video-clip"></iframe>'
);
expect(codeview.purify('<iframe frameborder="0" src="//wwwXyoutube.com/embed/CXgsA98krxA" width="640" height="360" class="note-video-clip">')).to.equalsIgnoreCase(
''
);
expect(codeview.purify('<iframe frameborder="0" src="//www.fake-youtube.com/embed/CXgsA98krxA" width="640" height="360" class="note-video-clip">')).to.equalsIgnoreCase(
''
);
expect(codeview.purify('<iframe frameborder="0" src="//www.youtube.com/embed/CXgsA98krxA" width="640" height="360" class="note-video-clip" src = "//www.fake-youtube.com/embed/CXgsA98krxA"/>')).to.equalsIgnoreCase(
''
);
});
it('should purify can be customized', () => {
codeview.options = options;
codeview.options.codeviewIframeFilter = false;
expect(codeview.purify('<iframe frameborder="0" src="//www.fake-youtube.com/embed/CXgsA98krxA" width="640" height="360" class="note-video-clip">')).to.equalsIgnoreCase(
'<iframe frameborder="0" src="//www.fake-youtube.com/embed/CXgsA98krxA" width="640" height="360" class="note-video-clip">'
);
codeview.options = options;
codeview.options.codeviewFilterRegex = /\d+/;
expect(codeview.purify('<script>alert("summernote");</script>')).to.equalsIgnoreCase(
'<script>alert("summernote");</script>'
);
expect(codeview.purify('<span>Tel: 012345678</span>')).to.equalsIgnoreCase(
'<span>Tel: </span>'
);
});
});

View File

@@ -0,0 +1,557 @@
/**
* Editor.spec.js
* (c) 2015~ Summernote Team
* summernote may be freely distributed under the MIT license./
*/
import chai from 'chai';
import spies from 'chai-spies';
import chaidom from 'test/chaidom';
import $ from 'jquery';
import env from 'src/js/base/core/env';
import range from 'src/js/base/core/range';
import Context from 'src/js/base/Context';
import 'src/js/bs4/settings';
describe('Editor', () => {
var expect = chai.expect;
chai.use(spies);
chai.use(chaidom);
var editor, context, $editable;
function expectContents(context, markup) {
expect(context.layoutInfo.editable.html()).to.equalsIgnoreCase(markup);
}
function expectContentsChain(context, markup, next) {
setTimeout(() => {
expect(context.layoutInfo.editable.html()).to.equalsIgnoreCase(markup);
next();
}, 10);
}
function expectContentsAwait(context, markup, done) {
expect(context.layoutInfo.editable.html()).await(done).to.equalsIgnoreCase(markup);
}
function expectToHaveBeenCalled(context, customEvent, handler) {
const $note = context.layoutInfo.note;
const spy = chai.spy();
$note.on(customEvent, spy);
handler();
expect(spy).to.have.been.called();
}
beforeEach(function() {
$('body').empty(); // important !
var options = $.extend({}, $.summernote.options);
options.historyLimit = 5;
context = new Context($('<div><p>hello</p></div>'), options);
editor = context.modules.editor;
$editable = context.layoutInfo.editable;
// [workaround]
// - IE8-11 can't create range in headless mode
if (env.isMSIE) {
this.skip();
}
});
describe('initialize', () => {
it('should bind custom events', (done) => {
[
'keydown', 'keyup', 'blur', 'mousedown', 'mouseup', 'scroll', 'focusin', 'focusout',
].forEach((eventName) => {
expectToHaveBeenCalled(context, 'summernote.' + eventName, () => {
$editable.trigger(eventName);
});
});
expectToHaveBeenCalled(context, 'summernote.change', () => {
editor.insertText('hello');
done();
});
});
});
describe('undo and redo', () => {
it('should control history', (done) => {
editor.insertText(' world');
setTimeout(() => {
expectContents(context, '<p>hello world</p>');
editor.undo();
setTimeout(() => {
expectContents(context, '<p>hello</p>');
editor.redo();
setTimeout(() => {
expectContents(context, '<p>hello world</p>');
done();
}, 10);
}, 10);
}, 10);
});
it('should be limited by option.historyLimit value', (done) => {
editor.insertText(' world');
editor.insertText(' world');
editor.insertText(' world');
editor.insertText(' world');
editor.insertText(' world');
setTimeout(() => {
expectContents(context, '<p>hello world world world world world</p>');
editor.undo();
editor.undo();
editor.undo();
setTimeout(() => {
expectContents(context, '<p>hello world world</p>');
editor.undo();
editor.undo();
editor.undo();
setTimeout(() => {
expectContents(context, '<p>hello world</p>');
done();
}, 10);
}, 10);
}, 10);
});
});
describe('tab', () => {
it('should insert tab', (done) => {
editor.tab();
expectContentsAwait(context, '<p>hello&nbsp;&nbsp;&nbsp;&nbsp;</p>', done);
});
});
describe('insertParagraph', () => {
it('should insert paragraph', (done) => {
editor.insertParagraph();
setTimeout(() => {
expectContents(context, '<p>hello</p><p><br></p>');
editor.insertParagraph();
setTimeout(() => {
expectContents(context, '<p>hello</p><p><br></p><p><br></p>');
done();
}, 10);
}, 10);
});
});
describe('insertImage', () => {
it('should insert image', () => {
var source = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAF0lEQVQYGWP8////fwYsgAmLGFiIHhIAT+oECGHuN2UAAAAASUVORK5CYII=';
return editor.insertImage(source, 'image').then(() => {
expect($editable.find('img').attr('src')).to.equalsIgnoreCase(source);
});
});
});
describe('insertOrderedList and insertUnorderedList', () => {
it('should toggle paragraph to list', (done) => {
editor.insertOrderedList();
expectContentsChain(context, '<ol><li>hello</li></ol>', () => {
editor.insertUnorderedList();
expectContentsChain(context, '<ul><li>hello</li></ul>', () => {
editor.insertUnorderedList();
expectContentsChain(context, '<p>hello</p>', () => {
done();
});
});
});
});
});
describe('indent and outdent', () => {
// [workaround] style is different by browser
it('should indent and outdent paragraph', (done) => {
editor.indent();
expectContentsChain(context, '<p style="margin-left: 25px;">hello</p>', () => {
editor.outdent();
expect($editable.find('p').css('margin-left')).await(done).to.be.empty;
});
});
it('should indent and outdent list', (done) => {
editor.insertOrderedList();
expectContentsChain(context, '<ol><li>hello</li></ol>', () => {
editor.indent();
expectContentsChain(context, '<ol><li><ol><li>hello</li></ol></li></ol>', () => {
editor.indent();
expectContentsChain(context, '<ol><li><ol><li><ol><li>hello</li></ol></li></ol></li></ol>', () => {
editor.outdent();
expectContentsChain(context, '<ol><li><ol><li>hello</li></ol></li></ol>', () => {
editor.outdent();
expectContentsChain(context, '<ol><li>hello</li></ol>', () => {
done();
});
});
});
});
});
});
});
describe('setLastRange', () => {
it('should set last range', (done) => {
document.body.click();
editor.setLastRange();
expect(editor.lastRange.sc).await(done).to.equal(editor.editable.lastChild);
});
it('should set last range without content', (done) => {
context.layoutInfo.editable.html('');
document.body.click();
editor.setLastRange();
expect(editor.lastRange.sc).await(done).to.equal(editor.editable);
});
});
describe('insertNode', () => {
it('should insert node', (done) => {
editor.insertNode($('<span> world</span>')[0]);
expectContentsAwait(context, '<p>hello<span> world</span></p>', done);
});
it('should be limited', (done) => {
var options = $.extend({}, $.summernote.options);
options.maxTextLength = 5;
context = new Context($('<div><p>hello</p></div>'), options);
editor = context.modules.editor;
editor.insertNode($('<span> world</span>')[0]);
expectContentsAwait(context, '<p>hello</p>', done);
});
it('should insert node in last focus', (done) => {
$editable.appendTo('body');
context.invoke('editor.focus');
setTimeout(() => {
var textNode = $editable.find('p')[0].firstChild;
editor.setLastRange(range.create(textNode, 0, textNode, 0).select());
setTimeout(() => {
editor.insertNode($('<span> world</span>')[0]);
setTimeout(() => {
$('body').focus();
editor.insertNode($('<span> hello</span>')[0]);
setTimeout(() => {
expectContentsAwait(context, '<p><span> world</span><span> hello</span>hello</p>', done);
}, 10);
}, 10);
}, 10);
}, 10);
});
});
describe('insertText', () => {
it('should insert text', (done) => {
editor.insertText(' world');
expectContentsAwait(context, '<p>hello world</p>', done);
});
it('should be limited', (done) => {
var options = $.extend({}, $.summernote.options);
options.maxTextLength = 5;
context = new Context($('<div><p>hello</p></div>'), options);
editor = context.modules.editor;
editor.insertText(' world');
expectContentsAwait(context, '<p>hello</p>', done);
});
it('should insert text in last focus', (done) => {
$editable.appendTo('body');
context.invoke('editor.focus');
var textNode = $editable.find('p')[0].firstChild;
editor.setLastRange(range.create(textNode, 0, textNode, 0).select());
setTimeout(() => {
editor.insertText(' world');
setTimeout(() => {
$('body').focus();
setTimeout(() => {
editor.insertText(' summernote');
setTimeout(() => {
expectContentsAwait(context, '<p> world summernotehello</p>', done);
}, 10);
}, 10);
}, 10);
}, 10);
});
});
describe('pasteHTML', () => {
it('should paste html', (done) => {
editor.pasteHTML('<span> world</span>');
expectContentsAwait(context, '<p>hello<span> world</span></p>', done);
});
it('should not call change change event more than once per paste event', () => {
var generateLargeHtml = () => {
var html = '<div>';
for (var i = 0; i < 1000; i++) {
html += '<p>HTML element #' + i + '</p>';
}
html += '</div>';
return html;
};
var $note = context.layoutInfo.note;
var spy = chai.spy();
$note.on('summernote.change', spy);
var html = generateLargeHtml();
editor.pasteHTML(html);
expect(spy).to.have.been.called.once;
});
it('should be limited', (done) => {
var options = $.extend({}, $.summernote.options);
options.maxTextLength = 5;
context = new Context($('<div><p>hello</p></div>'), options);
editor = context.modules.editor;
editor.pasteHTML('<span> world</span>');
expectContentsAwait(context, '<p>hello</p>', done);
});
});
describe('insertHorizontalRule', () => {
it('should insert horizontal rule', (done) => {
editor.insertHorizontalRule();
expectContentsAwait(context, '<p>hello</p><hr><p><br></p>', done);
});
});
describe('insertTable', () => {
it('should insert table', (done) => {
var markup = [
'<p>hello</p>',
'<table class="table table-bordered"><tbody>',
'<tr><td><br></td><td><br></td></tr>',
'<tr><td><br></td><td><br></td></tr>',
'</tbody></table>',
'<p><br></p>',
].join('');
editor.insertTable('2x2');
expectContentsAwait(context, markup, done);
});
});
describe('empty', () => {
it('should make contents empty', (done) => {
editor.empty();
expect(editor.isEmpty()).await(done).to.be.true;
});
});
describe('styleWithCSS', () => {
it('should style with tag when it is false (default)', (done) => {
$editable.appendTo('body');
range.createFromNode($editable.find('p')[0]).normalize().select();
editor.bold();
expectContentsAwait(context, '<p><b>hello</b></p>', done);
});
it('should style with CSS when it is true', (done) => {
var options = $.extend({}, $.summernote.options);
options.styleWithCSS = true;
$('body').empty();
context = new Context($('<div><p>hello</p></div>').appendTo('body'), options);
editor = context.modules.editor;
$editable = context.layoutInfo.editable;
$editable.appendTo('body');
range.createFromNode($editable.find('p')[0]).normalize().select();
editor.bold();
expectContentsAwait(context, '<p><span style="font-weight: bold;">hello</span></p>', done);
});
});
describe('formatBlock', () => {
it('should apply formatBlock', (done) => {
$editable.appendTo('body');
var textNode = $editable.find('p')[0].firstChild;
editor.setLastRange(range.create(textNode, 0, textNode, 0).select());
setTimeout(() => {
editor.formatBlock('h1');
expectContentsAwait(context, '<h1>hello</h1>', done);
}, 10);
});
it('should apply multi formatBlock', (done) => {
var codes = [
'<p><a href="http://summernote.org">hello world</a></p>',
'<p><a href="http://summernote.org">hello world</a></p>',
'<p><a href="http://summernote.org">hello world</a></p>',
];
context.invoke('code', codes.join(''));
$editable.appendTo('body');
var startNode = $editable.find('p').first()[0];
var endNode = $editable.find('p').last()[0];
// all p tags is wrapped
range.create(startNode, 0, endNode, 1).normalize().select();
editor.formatBlock('h3');
var nodeName = $editable.children()[0].nodeName;
expect(nodeName).to.equalsIgnoreCase('h3');
// p -> h3, p is none
expect($editable.find('p').length).await(done).to.equals(0);
});
it('should apply custom className in formatBlock', (done) => {
var $target = $('<h4 class="customH4Class" />');
$editable.appendTo('body');
range.createFromNode($editable.find('p')[0]).normalize().select();
editor.formatBlock('h4', $target);
// start <p>hello</p> => <h4 class="h4">hello</h4>
expectContentsAwait(context, '<h4 class="customH4Class">hello</h4>', done);
});
it('should find exact target in formatBlock', (done) => {
var $target = $('<a class="dropdown-item" href="#" data-value="h6" role="listitem" aria-label="h6"><h6 class="customH6Class">H6</h6></a>');
$editable.appendTo('body');
range.createFromNode($editable.find('p')[0]).normalize().select();
editor.formatBlock('h6', $target);
// start <p>hello</p> => <h6 class="h6">hello</h6>
expectContentsAwait(context, '<h6 class="customH6Class">hello</h6>', done);
});
});
describe('createLink', () => {
it('should create normal link', (done) => {
var text = 'hello';
var pNode = $editable.find('p')[0];
var textNode = pNode.childNodes[0];
var startIndex = textNode.wholeText.indexOf(text);
var endIndex = startIndex + text.length;
range.create(textNode, startIndex, textNode, endIndex).normalize().select();
// check creation normal link
editor.createLink({
url: 'http://summernote.org',
text: 'summernote',
});
expectContentsAwait(context, '<p>hello<a href="http://summernote.org">summernote</a></p>', done);
});
it('should create a link with range', (done) => {
var text = 'hello';
var pNode = $editable.find('p')[0];
var textNode = pNode.childNodes[0];
var startIndex = textNode.wholeText.indexOf(text);
var endIndex = startIndex + text.length;
var rng = range.create(textNode, startIndex, textNode, endIndex);
editor.createLink({
url: 'http://summernote.org',
text: 'summernote',
range: rng,
});
expectContentsAwait(context, '<p><a href="http://summernote.org">summernote</a></p>', done);
});
it('should create a link with isNewWindow', (done) => {
var text = 'hello';
var pNode = $editable.find('p')[0];
var textNode = pNode.childNodes[0];
var startIndex = textNode.wholeText.indexOf(text);
var endIndex = startIndex + text.length;
var rng = range.create(textNode, startIndex, textNode, endIndex);
editor.createLink({
url: 'http://summernote.org',
text: 'summernote',
range: rng,
isNewWindow: true,
});
expectContentsAwait(context, '<p><a href="http://summernote.org" target="_blank">summernote</a></p>', done);
});
it('should create a relative link without scheme', (done) => {
var text = 'hello';
var pNode = $editable.find('p')[0];
var textNode = pNode.childNodes[0];
var startIndex = textNode.wholeText.indexOf(text);
var endIndex = startIndex + text.length;
var rng = range.create(textNode, startIndex, textNode, endIndex);
editor.createLink({
url: '/relative/url',
text: 'summernote',
range: rng,
isNewWindow: true,
});
expectContentsAwait(context, '<p><a href="/relative/url" target="_blank">summernote</a></p>', done);
});
it('should modify a link', (done) => {
context.invoke('code', '<p><a href="http://summernote.org">hello world</a></p>');
var anchorNode = $editable.find('a')[0];
var rng = range.createFromNode(anchorNode);
editor.createLink({
url: 'http://wow.summernote.org',
text: 'summernote wow',
range: rng,
});
expectContentsAwait(context, '<p><a href="http://wow.summernote.org">summernote wow</a></p>', done);
});
it('should be limited when creating a link', (done) => {
var options = $.extend({}, $.summernote.options);
options.maxTextLength = 5;
context = new Context($('<div><p>hello</p></div>'), options);
editor = context.modules.editor;
editor.createLink({
url: 'http://summernote.org',
text: 'summernote',
});
expectContentsAwait(context, '<p>hello</p>', done);
});
it('should be limited when modifying a link', (done) => {
var options = $.extend({}, $.summernote.options);
options.maxTextLength = 5;
context = new Context($('<p><a href="http://summernote.org">hello</a></p>'), options);
var editable = context.layoutInfo.editable;
var anchorNode = editable.find('a')[0];
var rng = range.createFromNode(anchorNode);
editor = context.modules.editor;
editor.createLink({
url: 'http://summernote.org',
text: 'hello world',
range: rng,
});
expectContentsAwait(context, '<a href="http://summernote.org">hello</a>', done);
});
});
});

View File

@@ -0,0 +1,30 @@
/**
* Fullscreen.spec.js
* (c) 2015~ Summernote Team
* summernote may be freely distributed under the MIT license./
*/
import chai from 'chai';
import $ from 'jquery';
import Context from 'src/js/base/Context';
import Fullscreen from 'src/js/base/module/Fullscreen';
import 'src/js/bs4/settings';
describe('Fullscreen', () => {
var expect = chai.expect;
var fullscreen, context;
beforeEach(() => {
var options = $.extend({}, $.summernote.options);
context = new Context($('<div><p>hello</p></div>'), options);
fullscreen = new Fullscreen(context);
});
it('should toggle fullscreen mode', () => {
expect(fullscreen.isFullscreen()).to.be.false;
fullscreen.toggle();
expect(fullscreen.isFullscreen()).to.be.true;
fullscreen.toggle();
expect(fullscreen.isFullscreen()).to.be.false;
});
});

View File

@@ -0,0 +1,221 @@
/**
* HintPopover.spec.js
* (c) 2015~ Summernote Team
* summernote may be freely distributed under the MIT license./
*/
import $ from 'jquery';
import chai from 'chai';
import chaidom from 'test/chaidom';
import Context from 'src/js/base/Context';
import range from 'src/js/base/core/range';
import env from 'src/js/base/core/env';
import key from 'src/js/base/core/key';
import 'src/js/bs4/settings';
chai.use(chaidom);
describe('HintPopover', () => {
var expect = chai.expect;
var $note, editor, context, $editable;
function expectContents(context, markup) {
expect(context.layoutInfo.editable.html()).to.equalsIgnoreCase(markup);
}
describe('Single word hint', () => {
beforeEach(function() {
$('body').empty(); // important !
$note = $('<div><p>hello world</p></div>');
$note.appendTo('body');
var options = $.extend({}, $.summernote.options, {
hint: {
mentions: ['jayden', 'sam', 'alvin', 'david'],
match: /\B#(\w*)$/,
search: function(keyword, callback) {
callback($.grep(this.mentions, function(item) {
return item.indexOf(keyword) === 0;
}));
},
content: function(item) {
return '#' + item;
},
},
});
context = new Context($note, options);
editor = context.modules.editor;
$editable = context.layoutInfo.editable;
// [workaround]
// - Safari does not popup hintpopover
// - IE8-11 can't create range in headless mode
if (env.isMSIE || env.isSafari) {
this.skip();
}
});
it('should not be shown without matches', () => {
$editable.keyup();
expect($('.note-hint-popover').css('display')).to.equals('none');
});
it('should be shown when it matches the given condition', (done) => {
var textNode = $editable.find('p')[0].firstChild;
editor.setLastRange(range.create(textNode, 5, textNode, 5).select());
editor.insertText(' #');
$editable.keyup();
setTimeout(() => {
expect($('.note-hint-popover').css('display')).to.equals('block');
done();
}, 10);
});
it('should select the best matched item with the given condition', (done) => {
var textNode = $editable.find('p')[0].firstChild;
editor.setLastRange(range.create(textNode, 5, textNode, 5).select());
editor.insertText(' #al');
$editable.keyup();
setTimeout(() => {
// alvin should be activated
const item = $('.note-hint-popover').find('.note-hint-item');
expect(item.text()).to.equals('alvin');
expect(item.hasClass('active')).to.be.true;
done();
}, 10);
});
it('should be replaced with the selected hint', (done) => {
var textNode = $editable.find('p')[0].firstChild;
editor.setLastRange(range.create(textNode, 5, textNode, 5).select());
editor.insertText(' #');
$editable.keyup();
setTimeout(() => {
var e = $.Event('keydown');
e.keyCode = key.code.ENTER;
$note.trigger('summernote.keydown', e);
setTimeout(() => {
expectContents(context, '<p>hello #jayden world</p>');
done();
}, 10);
}, 10);
});
it('should move selection by pressing arrow key', (done) => {
var textNode = $editable.find('p')[0].firstChild;
editor.setLastRange(range.create(textNode, 5, textNode, 5).select());
editor.insertText(' #');
$editable.keyup();
setTimeout(() => {
var e = $.Event('keydown');
e.keyCode = key.code.DOWN;
$note.trigger('summernote.keydown', e);
e.keyCode = key.code.ENTER;
$note.trigger('summernote.keydown', e);
setTimeout(() => {
expectContents(context, '<p>hello #sam world</p>');
done();
}, 10);
}, 10);
});
});
describe('Multiple words hint', () => {
beforeEach(function() {
$('body').empty();
$note = $('<div><p>hello world</p></div>');
$note.appendTo('body');
var options = $.extend({}, $.summernote.options, {
hintMode: 'words',
hintSelect: 'next',
hint: {
mentions: [
{
name: 'Jayden Smith',
url: 'http://example.org/person/jayden-smith',
},
{
name: 'Peter Pan',
url: 'http://example.org/person/peter-pan',
},
{
name: 'Lorca',
url: 'http://example.org/person/lorca',
},
{
name: 'David Summer',
url: 'http://example.org/person/david-summer',
},
],
match: /\B@([a-z ]*)/i,
search: function(keyword, callback) {
callback($.grep(this.mentions, function(item) {
return item.name.toLowerCase().indexOf(keyword.toLowerCase()) === 0;
}));
},
template: function(item) {
return item.name;
},
content: function(item) {
return $('<a>')
.attr('href', item.url)
.text('@' + item.name)
.get(0);
},
},
});
context = new Context($note, options);
editor = context.modules.editor;
$editable = context.layoutInfo.editable;
// [workaround]
// - Safari does not popup hintpopover
// - IE8-11 can't create range in headless mode
if (env.isMSIE || env.isSafari) {
this.skip();
}
});
it('should select the best matched item with the given condition', (done) => {
var textNode = $editable.find('p')[0].firstChild;
editor.setLastRange(range.create(textNode, 5, textNode, 5).select());
editor.insertText(' @David S');
$editable.keyup();
setTimeout(() => {
// David Summer should be activated
const item = $('.note-hint-popover').find('.note-hint-item');
expect(item.text()).to.equals('David Summer');
expect(item.hasClass('active')).to.be.true;
done();
}, 10);
});
it('should render hint result with given content', (done) => {
var textNode = $editable.find('p')[0].firstChild;
editor.setLastRange(range.create(textNode, 5, textNode, 5).select());
editor.insertText(' @David S');
$editable.keyup();
setTimeout(() => {
// alvin should be activated
var e = $.Event('keydown');
e.keyCode = key.code.ENTER;
$note.trigger('summernote.keydown', e);
setTimeout(() => {
expectContents(context, '<p>hello <a href="http://example.org/person/david-summer">@David Summer</a> world</p>');
done();
}, 10);
}, 10);
});
});
});

View File

@@ -0,0 +1,110 @@
/**
* LinkDialog.spec.js
* (c) 2015~ Summernote Team
* summernote may be freely distributed under the MIT license./
*/
import chai from 'chai';
import $ from 'jquery';
import range from 'src/js/base/core/range';
import Context from 'src/js/base/Context';
import LinkDialog from 'src/js/base/module/LinkDialog';
import 'src/js/bs4/settings';
describe('LinkDialog', () => {
var expect = chai.expect;
var context, dialog, $editable;
beforeEach(() => {
var options = $.extend({}, $.summernote.options);
options.toolbar = [
['insert', ['link']],
];
context = new Context(
$('<div>' +
'<p><a href="https://summernote.org/" target="_blank">hello</a></p>' +
'<p><a href="https://summernote.org/">world</a></p>' +
'<p><a href="summernote.org/">summer</a></p>' +
'<p>summer</p>' +
'<p>http://summer</p>' +
'</div>'),
options
);
context.initialize();
dialog = new LinkDialog(context);
dialog.initialize();
$editable = context.layoutInfo.editable;
$editable.appendTo('body');
});
describe('LinkDialog', () => {
// open-in-new-window
it('should check new window when target=_blank', () => {
range.createFromNode($editable.find('a')[0]).normalize().select();
context.invoke('editor.setLastRange');
dialog.show();
var checked = dialog.$dialog
.find('.sn-checkbox-open-in-new-window input[type=checkbox]')
.is(':checked');
expect(checked).to.be.true;
});
it('should uncheck new window without target=_blank', () => {
range.createFromNode($editable.find('a')[1]).normalize().select();
context.invoke('editor.setLastRange');
dialog.show();
var checked = dialog.$dialog
.find('.sn-checkbox-open-in-new-window input[type=checkbox]')
.is(':checked');
expect(checked).to.be.false;
});
// use default protocol
it('should uncheck default protocol if link (with protocol) exists', () => {
range.createFromNode($editable.find('a')[1]).normalize().select();
context.invoke('editor.setLastRange');
dialog.show();
var checked = dialog.$dialog
.find('.sn-checkbox-use-protocol input[type=checkbox]')
.is(':checked');
expect(checked).to.be.false;
});
it('should uncheck default protocol if link (without protocol) exists', () => {
range.createFromNode($editable.find('a')[2]).normalize().select();
context.invoke('editor.setLastRange');
dialog.show();
var checked = dialog.$dialog
.find('.sn-checkbox-use-protocol input[type=checkbox]')
.is(':checked');
expect(checked).to.be.false;
});
it('should check default protocol if link not exists', () => {
range.createFromNode($editable.find('p')[3]).normalize().select();
context.invoke('editor.setLastRange');
dialog.show();
var checked = dialog.$dialog
.find('.sn-checkbox-use-protocol input[type=checkbox]')
.is(':checked');
expect(checked).to.be.true;
});
it('should check default protocol if link not exists although it has protocol', () => {
range.createFromNode($editable.find('p')[4]).normalize().select();
context.invoke('editor.setLastRange');
dialog.show();
var checked = dialog.$dialog
.find('.sn-checkbox-use-protocol input[type=checkbox]')
.is(':checked');
expect(checked).to.be.true;
});
});
});

View File

@@ -0,0 +1,31 @@
/**
* Placeholder.spec.js
* (c) 2015~ Summernote Team
* summernote may be freely distributed under the MIT license./
*/
import chai from 'chai';
import $ from 'jquery';
import Context from 'src/js/base/Context';
import 'src/js/bs4/settings';
describe('Placeholder', () => {
var assert = chai.assert;
it('should not be initialized by placeholder attribute without inheritPlaceHolder', () => {
var options = $.extend({}, $.summernote.options);
var context = new Context($('<textarea placeholder="custom_placeholder"><p>hello</p></textarea>'), options);
var $editor = context.layoutInfo.editor;
assert.isTrue($editor.find('.note-placeholder').length === 0);
});
it('should be initialized by placeholder attribute with inheritPlaceHolder', () => {
var options = $.extend({}, $.summernote.options);
options.inheritPlaceholder = true;
var context = new Context($('<textarea placeholder="custom_placeholder"><p>hello</p></textarea>'), options);
var $editor = context.layoutInfo.editor;
assert.isTrue($editor.find('.note-placeholder').length === 1);
assert.isTrue($editor.find('.note-placeholder').html() === 'custom_placeholder');
});
});

View File

@@ -0,0 +1,64 @@
/**
* VideoDialog.spec.js
* (c) 2015~ Summernote Team
* summernote may be freely distributed under the MIT license./
*/
import chai from 'chai';
import $ from 'jquery';
import Context from 'src/js/base/Context';
import VideoDialog from 'src/js/base/module/VideoDialog';
import 'src/js/bs4/settings';
describe('VideoDialog', () => {
var expect = chai.expect;
var context, $video;
function expectUrl(source, target) {
var iframe = $video.createVideoNode(source);
expect(iframe).to.not.equal(false);
expect(iframe.tagName).to.equal('IFRAME');
expect(iframe.src).to.be.have.string(target);
}
beforeEach(() => {
var $note = $('<div></div>').appendTo('body');
var options = $.extend({}, $.summernote.options);
options.toolbar = [
['video', ['video']],
];
context = new Context($note, options);
context.initialize();
$video = new VideoDialog(context);
});
describe('#createVideoNode', () => {
it('should get false when insert invalid urls', () => {
expect($video.createVideoNode('http://www.google.com')).to.equal(false);
expect($video.createVideoNode('http://www.youtube.com')).to.equal(false);
expect($video.createVideoNode('http://www.facebook.com')).to.equal(false);
});
it('should get proper iframe src when insert valid video urls', () => {
// YouTube
expectUrl('https://www.youtube.com/watch?v=jNQXAC9IVRw',
'//www.youtube.com/embed/jNQXAC9IVRw');
// Instagram
expectUrl('https://www.instagram.com/p/Bi9cbsxjn-F',
'//instagram.com/p/Bi9cbsxjn-F/embed/');
// v.qq.com
expectUrl('http://v.qq.com/cover/6/640ewqy2v071ppd.html?vid=f0196y2b2cx',
'//v.qq.com/iframe/player.html?vid=f0196y2b2cx&amp;auto=0');
expectUrl('http://v.qq.com/x/page/p0330y279lm.html',
'//v.qq.com/iframe/player.html?vid=p0330y279lm&amp;auto=0');
// Facebook
expectUrl('https://www.facebook.com/Engineering/videos/631826881803/',
'//www.facebook.com/plugins/video.php?href=www.facebook.com%2FEngineering%2Fvideos%2F631826881803');
});
it('should be embedded start parameter when insert YouTube video with t', () => {
expectUrl('https://youtu.be/wZZ7oFKsKzY?t=4h2m42s',
'//www.youtube.com/embed/wZZ7oFKsKzY?start=14562');
});
});
});