[ADD] web_editor,*: Tests for new structure of web editor
The refactoring also had to be done in order to create a set of unit tests. Each behavior can be tested, including the behaviors performed as a consequence of keyboard interactions (for instance: Enter, Tab...). This commit also contains some changes to test_utils that were necessary given the new structure of the wysiwyg editor. Co-authored-by: Gorash <chm@odoo.com>
This commit is contained in:
committed by
Christophe Matthieu
co-authored by
Gorash
parent
f296992317
commit
eb0ea8efbe
@@ -56,8 +56,8 @@ var MassMailingFieldHtml = FieldHtml.extend({
|
||||
convertInline.classToStyle($editable);
|
||||
|
||||
self.trigger_up('field_changed', {
|
||||
dataPointID: this.dataPointID,
|
||||
changes: _.object([fieldName], [this._unWrap($editable.html())])
|
||||
dataPointID: self.dataPointID,
|
||||
changes: _.object([fieldName], [self._unWrap($editable.html())])
|
||||
});
|
||||
|
||||
if (self._isDirty && self.mode === 'edit') {
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
odoo.define('mass_mailing.field_html_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var ajax = require('web.ajax');
|
||||
var FormView = require('web.FormView');
|
||||
var testUtils = require('web.test_utils');
|
||||
var weTestUtils = require('web_editor.test_utils');
|
||||
var Wysiwyg = require('web_editor.wysiwyg');
|
||||
|
||||
|
||||
QUnit.module('mass_mailing', {}, function () {
|
||||
QUnit.module('field html', {
|
||||
beforeEach: function () {
|
||||
this.data = weTestUtils.wysiwygData({
|
||||
'mail.mass_mailing': {
|
||||
fields: {
|
||||
display_name: {
|
||||
string: "Displayed name",
|
||||
type: "char"
|
||||
},
|
||||
body_html: {
|
||||
string: "Message Body inline (to send)",
|
||||
type: "html"
|
||||
},
|
||||
body_arch: {
|
||||
string: "Message Body for edition",
|
||||
type: "html"
|
||||
},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
display_name: "first record",
|
||||
body_html: "<div class='field_body' style='background-color: red;'><p>code to edit</p></div>",
|
||||
body_arch: "<div class='field_body'><p>code to edit</p></div>",
|
||||
}],
|
||||
},
|
||||
});
|
||||
|
||||
testUtils.mock.patch(ajax, {
|
||||
loadAsset: function (xmlId) {
|
||||
if (xmlId === 'template.assets') {
|
||||
return $.when({
|
||||
cssLibs: [],
|
||||
cssContents: ['.field_body {background-color: red;}']
|
||||
});
|
||||
}
|
||||
if (xmlId === 'template.assets_all_style') {
|
||||
return $.when({
|
||||
cssLibs: $('head link[href]:not([type="image/x-icon"])').map(function () {
|
||||
return $(this).attr('href');
|
||||
}).get(),
|
||||
cssContents: ['.field_body {background-color: red;}']
|
||||
});
|
||||
}
|
||||
throw 'Wrong template';
|
||||
},
|
||||
});
|
||||
},
|
||||
afterEach: function () {
|
||||
testUtils.mock.unpatch(ajax);
|
||||
},
|
||||
}, function () {
|
||||
|
||||
QUnit.test('save arch and html', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(6);
|
||||
|
||||
testUtils.createAsyncView({
|
||||
View: FormView,
|
||||
model: 'mail.mass_mailing',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
' <field name="body_html" class="oe_read_only" widget="html"'+
|
||||
' options="{'+
|
||||
' \'cssReadonly\': \'template.assets\','+
|
||||
' }"'+
|
||||
' />'+
|
||||
' <field name="body_arch" class="oe_edit_only" widget="mass_mailing_html"'+
|
||||
' options="{'+
|
||||
' \'snippets\': \'template.assets\','+
|
||||
' \'cssEdit\': \'template.assets\','+
|
||||
' \'inline-field\': \'body_html\''+
|
||||
' }"'+
|
||||
' />'+
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === "write") {
|
||||
var values = args.args[1];
|
||||
assert.strictEqual(values.body_html,
|
||||
'<div class="field_body" style="background-color:red;"><p>c<b>od</b>e to edit</p></div>',
|
||||
"should save the content");
|
||||
assert.strictEqual(values.body_arch,
|
||||
'<div class="field_body"><p>c<b>od</b>e to edit</p></div>',
|
||||
"should save the content");
|
||||
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
}).then(function (form) {
|
||||
var $fieldReadonly = form.$('.oe_form_field[name="body_html"]');
|
||||
var $fieldEdit = form.$('.oe_form_field[name="body_arch"]');
|
||||
|
||||
assert.strictEqual($fieldReadonly.css('display'), 'block', "should display the readonly mode");
|
||||
assert.strictEqual($fieldEdit.css('display'), 'none', "should hide the edit mode");
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
|
||||
$fieldReadonly = form.$('.oe_form_field[name="body_html"]');
|
||||
$fieldEdit = form.$('.oe_form_field[name="body_arch"]');
|
||||
|
||||
assert.strictEqual($fieldReadonly.css('display'), 'none', "should hide the readonly mode");
|
||||
assert.strictEqual($fieldEdit.css('display'), 'block', "should display the edit mode");
|
||||
|
||||
var $iframe = $fieldEdit.find('iframe');
|
||||
|
||||
$iframe.data('load-def').then(function () {
|
||||
var doc = $iframe.contents()[0];
|
||||
var $content = $('#iframe_target', doc);
|
||||
|
||||
// select the text
|
||||
var pText = $content.find('.note-editable p').first().contents()[0];
|
||||
Wysiwyg.setRange(pText, 1, pText, 3);
|
||||
// text is selected
|
||||
|
||||
$content.find('.note-toolbar .note-font .note-btn-bold').mousedown().click();
|
||||
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
|
||||
form.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -23,4 +23,10 @@
|
||||
<template id="iframe_css_assets_readonly">
|
||||
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/css/basic_theme_readonly.css"/>
|
||||
</template>
|
||||
|
||||
<template id="qunit_suite" inherit_id="web.qunit_suite">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/mass_mailing/static/tests/mass_mailing_html_tests.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
@@ -69,6 +69,7 @@ function removeSrcAttribute(el, widget) {
|
||||
if (widget) {
|
||||
widget._rpc({ route: src });
|
||||
}
|
||||
$(node).trigger('load');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,6 +229,7 @@ function addMockEnvironment(widget, params) {
|
||||
}
|
||||
|
||||
$('body').off('DOMNodeInserted.removeSRC');
|
||||
$('body').removeClass('debug');
|
||||
$('.blockUI').remove();
|
||||
|
||||
widgetDestroy.call(this);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,674 @@
|
||||
odoo.define('web_editor.test_utils', function (require) {
|
||||
"use strict";
|
||||
|
||||
var ajax = require('web.ajax');
|
||||
var testUtils = require('web.test_utils');
|
||||
var Widget = require('web.Widget');
|
||||
var Wysiwyg = require('web_editor.wysiwyg');
|
||||
var options = require('web_editor.snippets.options');
|
||||
|
||||
|
||||
/**
|
||||
* Options with animation and edition for test.
|
||||
*/
|
||||
options.registry.option_test = options.Class.extend({
|
||||
cleanForSave: function () {
|
||||
this.$target.addClass('cleanForSave');
|
||||
},
|
||||
onBuilt: function () {
|
||||
this.$target.addClass('built');
|
||||
},
|
||||
onBlur: function () {
|
||||
this.$target.removeClass('focus');
|
||||
},
|
||||
onClone: function () {
|
||||
this.$target.addClass('clone');
|
||||
this.$target.removeClass('focus');
|
||||
},
|
||||
onFocus: function () {
|
||||
this.$target.addClass('focus');
|
||||
},
|
||||
onMove: function () {
|
||||
this.$target.addClass('move');
|
||||
},
|
||||
onRemove: function () {
|
||||
this.$target.closest('.note-editable').addClass('snippet_has_removed');
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Constructor WysiwygTest why editable and unbreakable node used in test.
|
||||
*/
|
||||
var WysiwygTest = Wysiwyg.extend({
|
||||
_parentToDestroyForTest: null,
|
||||
/**
|
||||
* Override 'destroy' of discuss so that it calls 'destroy' on the parent.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
destroy: function () {
|
||||
unpatch();
|
||||
this._super();
|
||||
this.$target.remove();
|
||||
this._parentToDestroyForTest.destroy();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
isUnbreakableNode: function (node) {
|
||||
var Node = (node.tagName ? node : node.parentNode);
|
||||
return (!this.options.useOnlyTestUnbreakable && this._super(node)) ||
|
||||
!this.isEditableNode(node) ||
|
||||
Node.tagName === "UNBREAKABLE" ||
|
||||
(Node.className + '').indexOf('unbreakable') !== -1;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
isEditableNode: function (node) {
|
||||
if (!$(node).closest(this.$el).length) {
|
||||
return false;
|
||||
}
|
||||
while (node) {
|
||||
if (node.tagName === "EDITABLE") {
|
||||
return true;
|
||||
}
|
||||
if (node.tagName === "NOTEDITABLE") {
|
||||
return false;
|
||||
}
|
||||
if ((node.className + '').indexOf('editable') !== -1) {
|
||||
return true;
|
||||
}
|
||||
if (this.$el[0] === node) {
|
||||
return true;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
function patch() {
|
||||
testUtils.mock.patch(ajax, {
|
||||
loadAsset: function (xmlId) {
|
||||
if (xmlId === 'template.assets') {
|
||||
return $.when({
|
||||
cssLibs: [],
|
||||
cssContents: ['body {background-color: red;}']
|
||||
});
|
||||
}
|
||||
if (xmlId === 'template.assets_all_style') {
|
||||
return $.when({
|
||||
cssLibs: $('head link[href]:not([type="image/x-icon"])').map(function () {
|
||||
return $(this).attr('href');
|
||||
}).get(),
|
||||
cssContents: ['body {background-color: red;}']
|
||||
});
|
||||
}
|
||||
throw 'Wrong template';
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function unpatch() {
|
||||
testUtils.mock.unpatch(ajax);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} data
|
||||
* @returns {object}
|
||||
*/
|
||||
function wysiwygData(data) {
|
||||
return _.defaults({}, data, {
|
||||
'ir.ui.view': {
|
||||
fields: {
|
||||
display_name: {
|
||||
string: "Displayed name",
|
||||
type: "char",
|
||||
},
|
||||
},
|
||||
records: [],
|
||||
read_template: function (args) {
|
||||
if (args[0] === 'web_editor.colorpicker') {
|
||||
return '<templates><t t-name="web_editor.colorpicker">' +
|
||||
'<colorpicker>' +
|
||||
' <div class="o_colorpicker_section" data-name="theme" data-display="Theme Colors" data-icon-class="fa fa-flask">' +
|
||||
' <button data-color="alpha"></button>' +
|
||||
' <button data-color="beta"></button>' +
|
||||
' <button data-color="gamma"></button>' +
|
||||
' <button data-color="delta"></button>' +
|
||||
' <button data-color="epsilon"></button>' +
|
||||
' </div>' +
|
||||
' <div class="o_colorpicker_section" data-name="transparent_grayscale" data-display="Transparent Colors" data-icon-class="fa fa-eye-slash">' +
|
||||
' <button class="o_btn_transparent"></button>' +
|
||||
' <button data-color="black-25"></button>' +
|
||||
' <button data-color="black-50"></button>' +
|
||||
' <button data-color="black-75"></button>' +
|
||||
' <button data-color="white-25"></button>' +
|
||||
' <button data-color="white-50"></button>' +
|
||||
' <button data-color="white-75"></button>' +
|
||||
' </div>' +
|
||||
' <div class="o_colorpicker_section" data-name="common" data-display="Common Colors" data-icon-class="fa fa-paint-brush"></div>' +
|
||||
'</colorpicker>' +
|
||||
'</t></templates>';
|
||||
}
|
||||
},
|
||||
render_template: function (args) {
|
||||
if (args[0] === 'web_editor.snippets') {
|
||||
return '<h2 id="snippets_menu">Add blocks</h2>' +
|
||||
'<div id="o_scroll">' +
|
||||
' <div id="snippet_structure" class="o_panel">' +
|
||||
' <div class="o_panel_header">' +
|
||||
' <i class="fa fa-th-large"></i> First Panel' +
|
||||
' </div>' +
|
||||
' <div class="o_panel_body">' +
|
||||
' <div name="Separator" data-oe-type="snippet" data-oe-thumbnail="/website/static/src/img/snippets_thumbs/s_separator.png">' +
|
||||
' <div class="s_hr pt32 pb32">' +
|
||||
' <hr class="s_hr_1px s_hr_solid w-100 mx-auto"/>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
' <div name="Content" data-oe-type="snippet" data-oe-thumbnail="/website/static/src/img/snippets_thumbs/s_text_block.png">' +
|
||||
' <section name="Content+Options" class="test_option_all pt32 pb32" data-oe-type="snippet" data-oe-thumbnail="/website/static/src/img/snippets_thumbs/s_text_block.png">' +
|
||||
' <div class="container">' +
|
||||
' <div class="row">' +
|
||||
' <div class="col-lg-10 offset-lg-1 pt32 pb32">' +
|
||||
' <h2>Title</h2>' +
|
||||
' <p class="lead o_default_snippet_text">Content</p>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
' </section>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
'</div>' +
|
||||
'<div id="snippet_options" class="d-none">' +
|
||||
' <div data-js="many2one" data-selector="[data-oe-many2one-model]:not([data-oe-readonly])" data-no-check="true"></div>' +
|
||||
' <div data-js="content" data-selector=".s_hr, .test_option_all" ' +
|
||||
'data-drop-in=".note-editable" data-drop-near="p, h1, h2, h3, blockquote, .s_hr"></div>' +
|
||||
' <div data-js="sizing_y" data-selector=".s_hr, .test_option_all"></div>' +
|
||||
' <div data-js="colorpicker" data-selector=".test_option_all">' +
|
||||
' <div class="dropdown-submenu">' +
|
||||
' <a tabindex="-1" href="#" class="dropdown-item"><i class="fa fa-eyedropper"/>Background Color</a>' +
|
||||
' <div class="dropdown-menu" role="menu"/>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
' <div data-js="background" data-selector=".test_option_all">' +
|
||||
' <a tabindex="-1" href="#" class="dropdown-item" data-choose-image="true" data-no-preview="true"><i class="fa fa-picture-o"/>Background Image</a>' +
|
||||
' </div>' +
|
||||
' <div data-js="option_test" data-selector=".s_hr">' +
|
||||
' <div class="dropdown-submenu">' +
|
||||
' <a tabindex="-2" href="#" class="dropdown-item"><i class="fa fa-arrows-v"/>Alignment</a>' +
|
||||
' <div class="dropdown-menu" role="menu">' +
|
||||
' <a href="#" class="dropdown-item" data-select-class="align-items-start">Top</a>' +
|
||||
' <a href="#" class="dropdown-item" data-select-class="align-items-center">Middle</a>' +
|
||||
' <a href="#" class="dropdown-item" data-select-class="align-items-end">Bottom</a>' +
|
||||
' <div class="dropdown-divider"/>' +
|
||||
' <a href="#" class="dropdown-item" data-select-class="align-items-stretch">Equal height</a>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
},
|
||||
'ir.attachment': {
|
||||
fields: {
|
||||
display_name: {
|
||||
string: "display_name",
|
||||
type: 'char',
|
||||
},
|
||||
datas_fname: {
|
||||
string: "datas_fname",
|
||||
type: 'char',
|
||||
},
|
||||
mimetype: {
|
||||
string: "mimetype",
|
||||
type: 'char',
|
||||
},
|
||||
checksum: {
|
||||
string: "checksum",
|
||||
type: 'char',
|
||||
},
|
||||
url: {
|
||||
string: "url",
|
||||
type: 'char',
|
||||
},
|
||||
type: {
|
||||
string: "type",
|
||||
type: 'char',
|
||||
},
|
||||
res_id: {
|
||||
string: "res_id",
|
||||
type: 'integer',
|
||||
},
|
||||
res_model: {
|
||||
string: "res_model",
|
||||
type: 'char',
|
||||
},
|
||||
access_token: {
|
||||
string: "access_token",
|
||||
type: 'char',
|
||||
},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
public: true,
|
||||
name: 'image',
|
||||
datas_fname: 'image.png',
|
||||
mimetype: 'image/png',
|
||||
checksum: false,
|
||||
url: '/web_editor/static/src/img/transparent.png',
|
||||
type: 'url',
|
||||
res_id: 0,
|
||||
res_model: false,
|
||||
access_token: false
|
||||
}],
|
||||
generate_access_token: function () {
|
||||
return;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the wysiwyg instance for test (contains patch, usefull ir.ui.view, snippets).
|
||||
*
|
||||
* @param {object} params
|
||||
*/
|
||||
function createWysiwyg(params) {
|
||||
patch();
|
||||
params.data = wysiwygData(params.data);
|
||||
|
||||
var parent = new Widget();
|
||||
testUtils.mock.addMockEnvironment(parent, params);
|
||||
|
||||
var wysiwygOptions = _.extend({}, params.wysiwygOptions, {
|
||||
recordInfo: {
|
||||
context: {},
|
||||
res_model: 'module.test',
|
||||
res_id: 1,
|
||||
},
|
||||
useOnlyTestUnbreakable: params.useOnlyTestUnbreakable,
|
||||
});
|
||||
|
||||
var wysiwyg = new WysiwygTest(parent, wysiwygOptions);
|
||||
wysiwyg._parentToDestroyForTest = parent;
|
||||
|
||||
var $textarea = $('<textarea/>');
|
||||
if (wysiwygOptions.value) {
|
||||
$textarea.val(wysiwygOptions.value);
|
||||
}
|
||||
var selector = params.debug ? 'body' : '#qunit-fixture';
|
||||
$textarea.prependTo($(selector));
|
||||
if (params.debug) {
|
||||
$('body').addClass('debug');
|
||||
}
|
||||
return wysiwyg.attachTo($textarea).then(function () {
|
||||
if (wysiwygOptions.snippets) {
|
||||
var defSnippets = $.Deferred();
|
||||
testUtils.mock.intercept(wysiwyg, "snippets_loaded", function () {
|
||||
defSnippets.resolve(wysiwyg);
|
||||
});
|
||||
return defSnippets;
|
||||
}
|
||||
return wysiwyg;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Char codes.
|
||||
*/
|
||||
var dom = $.summernote.dom;
|
||||
var keyboardMap = {
|
||||
"8": "BACKSPACE",
|
||||
"9": "TAB",
|
||||
"13": "ENTER",
|
||||
"16": "SHIFT",
|
||||
"17": "CONTROL",
|
||||
"18": "ALT",
|
||||
"19": "PAUSE",
|
||||
"20": "CAPS_LOCK",
|
||||
"27": "ESCAPE",
|
||||
"32": "SPACE",
|
||||
"33": "PAGE_UP",
|
||||
"34": "PAGE_DOWN",
|
||||
"35": "END",
|
||||
"36": "HOME",
|
||||
"37": "LEFT",
|
||||
"38": "UP",
|
||||
"39": "RIGHT",
|
||||
"40": "DOWN",
|
||||
"45": "INSERT",
|
||||
"46": "DELETE",
|
||||
"91": "OS_KEY", // 'left command': Windows Key (Windows) or Command Key (Mac)
|
||||
"93": "CONTEXT_MENU", // 'right command'
|
||||
};
|
||||
_.each(_.range(40, 127), function (keyCode) {
|
||||
if (!keyboardMap[keyCode]) {
|
||||
keyboardMap[keyCode] = String.fromCharCode(keyCode);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Perform a series of tests (`keyboardTests`) for using keyboard inputs.
|
||||
*
|
||||
* @see wysiwyg_keyboard_tests.js
|
||||
* @see wysiwyg_tests.js
|
||||
*
|
||||
* @param {jQuery} $editable
|
||||
* @param {object} assert
|
||||
* @param {object[]} keyboardTests
|
||||
* @param {string} keyboardTests.name
|
||||
* @param {string} keyboardTests.content
|
||||
* @param {object[]} keyboardTests.steps
|
||||
* @param {string} keyboardTests.steps.start
|
||||
* @param {string} [keyboardTests.steps.end] default: steps.start
|
||||
* @param {string} keyboardTests.steps.key
|
||||
* @param {object} keyboardTests.test
|
||||
* @param {string} [keyboardTests.test.content]
|
||||
* @param {string} [keyboardTests.test.start]
|
||||
* @param {string} [keyboardTests.test.end] default: steps.start
|
||||
* @param {function($editable, assert)} [keyboardTests.test.check]
|
||||
* @param {Number} addTests
|
||||
*/
|
||||
var testKeyboard = function ($editable, assert, keyboardTests, addTests) {
|
||||
var tests = _.compact(_.pluck(keyboardTests, 'test'));
|
||||
var testNumber = _.compact(_.pluck(tests, 'start')).length +
|
||||
_.compact(_.pluck(tests, 'content')).length +
|
||||
_.compact(_.pluck(tests, 'check')).length +
|
||||
(addTests | 0);
|
||||
assert.expect(testNumber);
|
||||
|
||||
function keydown(target, keypress) {
|
||||
var $target = $(target.tagName ? target : target.parentNode);
|
||||
if (!keypress.keyCode) {
|
||||
keypress.keyCode = +_.findKey(keyboardMap, function (key) {
|
||||
return key === keypress.key;
|
||||
});
|
||||
} else {
|
||||
keypress.key = keyboardMap[keypress.keyCode] || String.fromCharCode(keypress.keyCode);
|
||||
}
|
||||
keypress.keyCode = keypress.keyCode;
|
||||
var event = $.Event("keydown", keypress);
|
||||
$target.trigger(event);
|
||||
if (!event.isDefaultPrevented()) {
|
||||
if (keypress.key.length === 1) {
|
||||
document.execCommand("insertText", 0, keypress.key);
|
||||
} else {
|
||||
console.warn('Native "' + keypress.key + '" is not supported in test');
|
||||
}
|
||||
}
|
||||
$target.trigger($.Event("keyup", keypress));
|
||||
return $target;
|
||||
}
|
||||
|
||||
function _select(selector) {
|
||||
// eg: ".class:contents()[0]->1" selects the first contents of the 'class' class, with an offset of 1
|
||||
var reDOMSelection = /^(.+?)(:contents(\(\)\[|\()([0-9]+)[\]|\)])?(->([0-9]+))?$/;
|
||||
var sel = selector.match(reDOMSelection);
|
||||
var $node = $editable.find(sel[1]);
|
||||
var point = {
|
||||
node: sel[3] ? $node.contents()[+sel[4]] : $node[0],
|
||||
offset: sel[5] ? +sel[6] : 0,
|
||||
};
|
||||
if (!point.node || point.offset > (point.node.tagName ? point.node.childNodes : point.node.textContent).length) {
|
||||
assert.notOk("Node not found: '" + selector + "' " + (point.node ? "(container: '" + (point.node.outerHTML || point.node.textContent) + "')" : ""));
|
||||
}
|
||||
return point;
|
||||
}
|
||||
|
||||
function selectText(start, end) {
|
||||
start = _select(start);
|
||||
var target = start.node;
|
||||
$(target.tagName ? target : target.parentNode).trigger("mousedown");
|
||||
if (end) {
|
||||
end = _select(end);
|
||||
Wysiwyg.setRange(start.node, start.offset, end.node, end.offset);
|
||||
} else {
|
||||
Wysiwyg.setRange(start.node, start.offset);
|
||||
}
|
||||
target = end ? end.node : start.node;
|
||||
$(target.tagName ? target : target.parentNode).trigger('mouseup');
|
||||
}
|
||||
|
||||
function endOfAreaBetweenTwoNodes(point) {
|
||||
// move the position because some browser make the caret on the end of the previous area after normalize
|
||||
if (
|
||||
!point.node.tagName &&
|
||||
point.offset === point.node.textContent.length &&
|
||||
!/\S|\u00A0/.test(point.node.textContent)
|
||||
) {
|
||||
point = dom.nextPoint(dom.nextPoint(point));
|
||||
while (point.node.tagName && point.node.textContent.length) {
|
||||
point = dom.nextPoint(point);
|
||||
}
|
||||
}
|
||||
return point;
|
||||
}
|
||||
|
||||
var defPollTest = $.when();
|
||||
|
||||
function pollTest(test) {
|
||||
var def = $.when();
|
||||
$editable.data('wysiwyg').setValue(test.content);
|
||||
|
||||
function poll(step) {
|
||||
var def = $.Deferred();
|
||||
if (step.start) {
|
||||
selectText(step.start, step.end);
|
||||
if (!Wysiwyg.getRange($editable[0])) {
|
||||
throw 'Wrong range! \n' +
|
||||
'Test: ' + test.name + '\n' +
|
||||
'Selection: ' + step.start + '" to "' + step.end + '"\n' +
|
||||
'DOM: ' + $editable.html();
|
||||
}
|
||||
}
|
||||
setTimeout(function () {
|
||||
if (step.keyCode || step.key) {
|
||||
var target = Wysiwyg.getRange($editable[0]).ec;
|
||||
if (window.location.search.indexOf('notrycatch') !== -1) {
|
||||
keydown(target, {
|
||||
key: step.key,
|
||||
keyCode: step.keyCode,
|
||||
ctrlKey: !!step.ctrlKey,
|
||||
shiftKey: !!step.shiftKey,
|
||||
altKey: !!step.altKey,
|
||||
metaKey: !!step.metaKey,
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
keydown(target, {
|
||||
key: step.key,
|
||||
keyCode: step.keyCode,
|
||||
ctrlKey: !!step.ctrlKey,
|
||||
shiftKey: !!step.shiftKey,
|
||||
altKey: !!step.altKey,
|
||||
metaKey: !!step.metaKey,
|
||||
});
|
||||
} catch (e) {
|
||||
assert.notOk(e.name + '\n\n' + e.stack, test.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(function () {
|
||||
if (step.keyCode || step.key) {
|
||||
var $target = $(target.tagName ? target : target.parentNode);
|
||||
$target.trigger($.Event('keyup', {
|
||||
key: step.key,
|
||||
keyCode: step.keyCode,
|
||||
ctrlKey: !!step.ctrlKey,
|
||||
shiftKey: !!step.shiftKey,
|
||||
altKey: !!step.altKey,
|
||||
metaKey: !!step.metaKey,
|
||||
}));
|
||||
}
|
||||
setTimeout(def.resolve.bind(def));
|
||||
});
|
||||
});
|
||||
return def;
|
||||
}
|
||||
while (test.steps.length) {
|
||||
def = def.then(poll.bind(null, test.steps.shift()));
|
||||
}
|
||||
|
||||
return def.then(function () {
|
||||
if (!test.test) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (test.test.check) {
|
||||
test.test.check($editable, assert);
|
||||
}
|
||||
|
||||
// test content
|
||||
if (test.test.content) {
|
||||
var value = $editable.data('wysiwyg').getValue({
|
||||
keepPopover: true,
|
||||
});
|
||||
var allInvisible = /\u200B/g;
|
||||
value = value.replace(allInvisible, '​');
|
||||
var result = test.test.content.replace(allInvisible, '​');
|
||||
assert.strictEqual(value, result, test.name);
|
||||
|
||||
if (test.test.start && value !== result) {
|
||||
assert.notOk("Wrong DOM (see previous assert)", test.name + " (carret position)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$editable[0].normalize();
|
||||
|
||||
// test carret position
|
||||
if (test.test.start) {
|
||||
var start = _select(test.test.start);
|
||||
var range = Wysiwyg.getRange($editable[0]);
|
||||
if ((range.sc !== range.ec || range.so !== range.eo) && !test.test.end) {
|
||||
assert.ok(false, test.name + ": the carret is not colapsed and the 'end' selector in test is missing");
|
||||
return;
|
||||
}
|
||||
var end = test.test.end ? _select(test.test.end) : start;
|
||||
if (start.node && end.node) {
|
||||
range = Wysiwyg.getRange($editable[0]);
|
||||
var startPoint = endOfAreaBetweenTwoNodes({
|
||||
node: range.sc,
|
||||
offset: range.so,
|
||||
});
|
||||
var endPoint = endOfAreaBetweenTwoNodes({
|
||||
node: range.ec,
|
||||
offset: range.eo,
|
||||
});
|
||||
var sameDOM = (startPoint.node.outerHTML || startPoint.node.textContent) === (start.node.outerHTML || start.node.textContent);
|
||||
var stringify = function (obj) {
|
||||
if (!sameDOM) {
|
||||
delete obj.sameDOMsameNode;
|
||||
}
|
||||
return JSON.stringify(obj, null, 2)
|
||||
.replace(/"([^"\s-]+)":/g, "\$1:")
|
||||
.replace(/([^\\])"/g, "\$1'")
|
||||
.replace(/\\"/g, '"');
|
||||
};
|
||||
assert.deepEqual(stringify({
|
||||
startNode: startPoint.node.outerHTML || startPoint.node.textContent,
|
||||
startOffset: startPoint.offset,
|
||||
endPoint: endPoint.node.outerHTML || endPoint.node.textContent,
|
||||
endOffset: endPoint.offset,
|
||||
sameDOMsameNode: sameDOM && startPoint.node === start.node,
|
||||
}),
|
||||
stringify({
|
||||
startNode: start.node.outerHTML || start.node.textContent,
|
||||
startOffset: start.offset,
|
||||
endPoint: end.node.outerHTML || end.node.textContent,
|
||||
endOffset: end.offset,
|
||||
sameDOMsameNode: true,
|
||||
}),
|
||||
test.name + " (carret position)");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
while (keyboardTests.length) {
|
||||
defPollTest = defPollTest.then(pollTest.bind(null, keyboardTests.shift()));
|
||||
}
|
||||
|
||||
return defPollTest;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Select a node in the dom with is offset.
|
||||
*
|
||||
* @param {String} startSelector
|
||||
* @param {String} endSelector
|
||||
* @param {jQuery} $editable
|
||||
* @returns {Object} {sc, so, ec, eo}
|
||||
*/
|
||||
var select = (function () {
|
||||
var __select = function (selector, $editable) {
|
||||
var sel = selector.match(/^(.+?)(:contents\(\)\[([0-9]+)\]|:contents\(([0-9]+)\))?(->([0-9]+))?$/);
|
||||
var $node = $editable.find(sel[1]);
|
||||
return {
|
||||
node: sel[2] ? $node.contents()[sel[3] ? +sel[3] : +sel[4]] : $node[0],
|
||||
offset: sel[5] ? +sel[6] : 0,
|
||||
};
|
||||
};
|
||||
return function (startSelector, endSelector, $editable) {
|
||||
var start = __select(startSelector, $editable);
|
||||
var end = endSelector ? __select(endSelector, $editable) : start;
|
||||
return {
|
||||
sc: start.node,
|
||||
so: start.offset,
|
||||
ec: end.node,
|
||||
eo: end.offset,
|
||||
};
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Trigger a keydown event.
|
||||
*
|
||||
* @param {String or Number} key (name or code)
|
||||
* @param {jQuery} $editable
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.firstDeselect] (default: false) true to deselect before pressing
|
||||
*/
|
||||
var keydown = function (key, $editable, options) {
|
||||
var keyPress = {};
|
||||
if (typeof key === 'string') {
|
||||
keyPress.key = key;
|
||||
keyPress.keyCode = +_.findKey(keyboardMap, function (k) {
|
||||
return k === key;
|
||||
});
|
||||
} else {
|
||||
keyPress.key = keyboardMap[key] || String.fromCharCode(key);
|
||||
keyPress.keyCode = key;
|
||||
}
|
||||
var range = Wysiwyg.getRange($editable[0]);
|
||||
if (!range) {
|
||||
console.error("Editor have not any range");
|
||||
return;
|
||||
}
|
||||
if (options && options.firstDeselect) {
|
||||
range.sc = range.ec;
|
||||
range.so = range.eo;
|
||||
Wysiwyg.setRange(range.sc, range.so, range.ec, range.eo);
|
||||
}
|
||||
var target = range.ec;
|
||||
var $target = $(target.tagName ? target : target.parentNode);
|
||||
var event = $.Event("keydown", keyPress);
|
||||
$target.trigger(event);
|
||||
};
|
||||
|
||||
return {
|
||||
wysiwygData: wysiwygData,
|
||||
createWysiwyg: createWysiwyg,
|
||||
testKeyboard: testKeyboard,
|
||||
select: select,
|
||||
keydown: keydown,
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,403 @@
|
||||
odoo.define('web_editor.wysiwyg_snippets_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var testUtils = require('web.test_utils');
|
||||
var weTestUtils = require('web_editor.test_utils');
|
||||
var MediaDialog = require('wysiwyg.widgets.MediaDialog');
|
||||
|
||||
|
||||
QUnit.module('web_editor', {}, function () {
|
||||
QUnit.module('wysiwyg', {}, function () {
|
||||
QUnit.module('Snippets', {}, function () {
|
||||
|
||||
QUnit.test('drag&drop', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(2);
|
||||
|
||||
return weTestUtils.createWysiwyg({
|
||||
wysiwygOptions: {
|
||||
snippets: true,
|
||||
value: '<p>toto toto toto</p><p>tata</p>',
|
||||
},
|
||||
}).then(function (wysiwyg) {
|
||||
var $editable = wysiwyg.getEditable();
|
||||
|
||||
var $hr = wysiwyg.snippets.$('.oe_snippet_thumbnail:first');
|
||||
testUtils.dom.dragAndDrop($hr, $editable.find('p'));
|
||||
|
||||
assert.strictEqual($editable.html().replace(/\s+/g, ' '),
|
||||
'<p>toto toto toto</p><div class=\"s_hr pt32 pb32\"> <hr class=\"s_hr_1px s_hr_solid w-100 mx-auto\"> </div><p>tata</p>',
|
||||
"should drop the snippet");
|
||||
|
||||
testUtils.mock.intercept(wysiwyg, "snippet_focused", function () {
|
||||
assert.strictEqual($editable.html().replace(/\s+/g, ' '),
|
||||
'<p>toto toto toto</p><div class=\"s_hr pt32 pb32 built focus\"> <hr class=\"s_hr_1px s_hr_solid w-100 mx-auto\"> </div><p>tata</p>',
|
||||
"should build and focus the snippet");
|
||||
|
||||
wysiwyg.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('begin drag&drop must hide popovers', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(2);
|
||||
|
||||
return weTestUtils.createWysiwyg({
|
||||
wysiwygOptions: {
|
||||
snippets: true,
|
||||
value:
|
||||
'<div class="s_hr pt32 pb32"> ' +
|
||||
'<hr class="s_hr_1px s_hr_solid w-100 mx-auto"> ' +
|
||||
'<img class="img-fluid o_we_custom_image" data-src="/web_editor/static/src/img/transparent.png"> ' +
|
||||
'</div>' +
|
||||
'<h1>test</h1>' +
|
||||
'<p><b>test</b></p>',
|
||||
},
|
||||
}).then(function (wysiwyg) {
|
||||
var $editable = wysiwyg.getEditable();
|
||||
|
||||
$editable.find('.s_hr img').mousedown().click();
|
||||
assert.strictEqual($('.note-popover:visible').length, 1, "should display the image popover");
|
||||
|
||||
var $hr = wysiwyg.snippets.$('.oe_snippet_thumbnail:first');
|
||||
testUtils.dom.dragAndDrop($hr, $editable.find('h1'), {
|
||||
disableDrop: true,
|
||||
});
|
||||
assert.strictEqual($('.note-popover:visible').length, 0, "should hide the image popover");
|
||||
testUtils.dom.dragAndDrop($hr, $editable.find('h1'), {
|
||||
continueMove: true,
|
||||
});
|
||||
|
||||
wysiwyg.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('change size option must hide popovers', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(4);
|
||||
|
||||
return weTestUtils.createWysiwyg({
|
||||
wysiwygOptions: {
|
||||
snippets: true,
|
||||
value:
|
||||
'<div class="s_hr pt32 pb32"> ' +
|
||||
'<hr class="s_hr_1px s_hr_solid w-100 mx-auto"> ' +
|
||||
'<img class="img-fluid o_we_custom_image" data-src="/web_editor/static/src/img/transparent.png"> ' +
|
||||
'</div>' +
|
||||
'<h1>test</h1>' +
|
||||
'<p><b>test</b></p>',
|
||||
},
|
||||
}).then(function (wysiwyg) {
|
||||
var $editable = wysiwyg.getEditable();
|
||||
|
||||
$editable.find('.s_hr img').mousedown().click();
|
||||
assert.strictEqual($('.note-popover:visible').length, 1, "should display the image popover");
|
||||
|
||||
var $handle = $('#oe_manipulators .o_handle.s');
|
||||
testUtils.dom.dragAndDrop($handle, $editable.find('b'), {
|
||||
disableDrop: true,
|
||||
});
|
||||
assert.ok($handle.hasClass('o_active'), "should active the handle");
|
||||
assert.notOk($handle.hasClass('pb32'), "should change the padding bottom");
|
||||
assert.strictEqual($('.note-popover:visible').length, 0, "should hide the image popover");
|
||||
testUtils.dom.dragAndDrop($handle, $editable.find('b'), {
|
||||
continueMove: true,
|
||||
});
|
||||
|
||||
wysiwyg.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('clean the dom before save, after drag&drop', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(1);
|
||||
|
||||
return weTestUtils.createWysiwyg({
|
||||
wysiwygOptions: {
|
||||
snippets: true,
|
||||
value: '<p>toto toto toto</p><p>tata</p>',
|
||||
},
|
||||
}).then(function (wysiwyg) {
|
||||
var $editable = wysiwyg.getEditable();
|
||||
|
||||
var $hr = wysiwyg.snippets.$('.oe_snippet_thumbnail:first');
|
||||
testUtils.dom.dragAndDrop($hr, $editable.find('p'));
|
||||
|
||||
testUtils.mock.intercept(wysiwyg, "snippet_focused", function () {
|
||||
wysiwyg.save().then(function (isDirty, html) {
|
||||
assert.strictEqual(html.replace(/\s+/g, ' '),
|
||||
'<p>toto toto toto</p><div class=\"s_hr pt32 pb32 built cleanForSave\"> <hr class=\"s_hr_1px s_hr_solid w-100 mx-auto\"> </div><p>tata</p>',
|
||||
"should clean the snippet");
|
||||
|
||||
wysiwyg.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('clean the dom before save', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(1);
|
||||
|
||||
return weTestUtils.createWysiwyg({
|
||||
wysiwygOptions: {
|
||||
snippets: true,
|
||||
value: '<div class="s_hr pt32 pb32"> <hr class="s_hr_1px s_hr_solid w-100 mx-auto"> </div>',
|
||||
},
|
||||
}).then(function (wysiwyg) {
|
||||
var $editable = wysiwyg.getEditable();
|
||||
|
||||
testUtils.mock.intercept(wysiwyg, "snippet_focused", function () {
|
||||
// add dirty flag to remove warning because the cleaned dom is different of the initial value and no dirty flag
|
||||
$editable.find('.s_hr').keydown();
|
||||
|
||||
// trigger change to avoid warning because the DOM change on clean without trigger onchange by snippet or edition
|
||||
$editable.trigger('content_changed');
|
||||
|
||||
wysiwyg.save().then(function (isDirty, html) {
|
||||
assert.strictEqual(html.replace(/\s+/g, ' '),
|
||||
'<div class=\"s_hr pt32 pb32 cleanForSave\"> <hr class=\"s_hr_1px s_hr_solid w-100 mx-auto\"> </div>',
|
||||
"should clean the snippet");
|
||||
|
||||
wysiwyg.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
$editable.find('.s_hr').mousedown().click();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('remove snippet', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(1);
|
||||
|
||||
return weTestUtils.createWysiwyg({
|
||||
wysiwygOptions: {
|
||||
snippets: true,
|
||||
value: '<div class="s_hr pt32 pb32"> <hr class="s_hr_1px s_hr_solid w-100 mx-auto"> </div>',
|
||||
},
|
||||
}).then(function (wysiwyg) {
|
||||
var $editable = wysiwyg.getEditable();
|
||||
|
||||
testUtils.mock.intercept(wysiwyg, "snippet_focused", function () {
|
||||
$('#oe_manipulators .oe_overlay_options .oe_snippet_remove').click();
|
||||
|
||||
wysiwyg.save().then(function (isDirty, html) {
|
||||
assert.strictEqual(html.replace(/\s+/g, ' '), '', "should remove the snippet");
|
||||
|
||||
wysiwyg.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
$editable.find('.s_hr').mousedown().click();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('move a snippet', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(1);
|
||||
|
||||
return weTestUtils.createWysiwyg({
|
||||
wysiwygOptions: {
|
||||
snippets: true,
|
||||
value: '<div class="s_hr pt32 pb32"> <hr class="s_hr_1px s_hr_solid w-100 mx-auto"> </div><h1>test</h1><p><b>test</b></p>',
|
||||
},
|
||||
}).then(function (wysiwyg) {
|
||||
var $editable = wysiwyg.getEditable();
|
||||
|
||||
var first = true;
|
||||
testUtils.mock.intercept(wysiwyg, "snippet_focused", function () {
|
||||
if (first) {
|
||||
first = false;
|
||||
var $hr = $('#oe_manipulators .oe_overlay_options .oe_snippet_move');
|
||||
testUtils.dom.dragAndDrop($hr, $editable.find('b'));
|
||||
} else {
|
||||
wysiwyg.save().then(function (isDirty, html) {
|
||||
assert.strictEqual(html.replace(/\s+/g, ' '),
|
||||
'<h1>test</h1><div class="s_hr pt32 pb32 move cleanForSave"> <hr class="s_hr_1px s_hr_solid w-100 mx-auto"> </div><p><b>test</b></p>',
|
||||
"should move the snippet on the bottom");
|
||||
|
||||
wysiwyg.destroy();
|
||||
done();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$editable.find('.s_hr').mousedown().click();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('clone a snippet', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(2);
|
||||
|
||||
return weTestUtils.createWysiwyg({
|
||||
wysiwygOptions: {
|
||||
snippets: true,
|
||||
value: '<div class="s_hr pt32 pb32"> <hr class="s_hr_1px s_hr_solid w-100 mx-auto"> </div><h1>test</h1><p><b>test</b></p>',
|
||||
},
|
||||
}).then(function (wysiwyg) {
|
||||
var $editable = wysiwyg.getEditable();
|
||||
|
||||
testUtils.mock.intercept(wysiwyg, "snippet_focused", function () {
|
||||
$('#oe_manipulators .oe_overlay_options .oe_snippet_clone').click();
|
||||
|
||||
assert.strictEqual($editable.html().replace(/\s+/g, ' '),
|
||||
'<div class="s_hr pt32 pb32 focus"> <hr class="s_hr_1px s_hr_solid w-100 mx-auto"> </div><div class="s_hr pt32 pb32 clone"> <hr class="s_hr_1px s_hr_solid w-100 mx-auto"> </div><h1>test</h1><p><b>test</b></p>',
|
||||
"should duplicate the snippet");
|
||||
|
||||
wysiwyg.save().then(function (isDirty, html) {
|
||||
assert.strictEqual(html.replace(/\s+/g, ' '),
|
||||
'<div class="s_hr pt32 pb32 cleanForSave"> <hr class="s_hr_1px s_hr_solid w-100 mx-auto"> </div><div class="s_hr pt32 pb32 clone cleanForSave"> <hr class="s_hr_1px s_hr_solid w-100 mx-auto"> </div><h1>test</h1><p><b>test</b></p>',
|
||||
"should duplicate the snippet");
|
||||
|
||||
wysiwyg.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
$editable.find('.s_hr').mousedown().click();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('customize snippet', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(3);
|
||||
|
||||
return weTestUtils.createWysiwyg({
|
||||
wysiwygOptions: {
|
||||
snippets: true,
|
||||
value: '<h1>test</h1><div class="s_hr pt32 pb32"> <hr class="s_hr_1px s_hr_solid w-100 mx-auto"> </div><p><b>test</b></p>',
|
||||
},
|
||||
}).then(function (wysiwyg) {
|
||||
var $editable = wysiwyg.getEditable();
|
||||
|
||||
testUtils.mock.intercept(wysiwyg, "snippet_focused", function () {
|
||||
$('#oe_manipulators .oe_overlay_options .oe_options a:first').click();
|
||||
var $option = $('#oe_manipulators .oe_overlay_options a[data-select-class="align-items-center"]');
|
||||
|
||||
assert.strictEqual($option.size(), 1, "should display the snippet option");
|
||||
|
||||
$option.click();
|
||||
|
||||
assert.strictEqual($editable.html().replace(/\s+/g, ' '),
|
||||
'<h1>test</h1><div class="s_hr pt32 pb32 focus align-items-center"> <hr class="s_hr_1px s_hr_solid w-100 mx-auto"> </div><p><b>test</b></p>',
|
||||
"should customized the snippet");
|
||||
|
||||
$('#oe_manipulators .oe_overlay_options .oe_options a:first').click();
|
||||
$('#oe_manipulators .oe_overlay_options a[data-select-class="align-items-end"]').click();
|
||||
|
||||
assert.strictEqual($editable.html().replace(/\s+/g, ' '),
|
||||
'<h1>test</h1><div class="s_hr pt32 pb32 focus align-items-end"> <hr class="s_hr_1px s_hr_solid w-100 mx-auto"> </div><p><b>test</b></p>',
|
||||
"should twice customized the snippet");
|
||||
|
||||
wysiwyg.destroy();
|
||||
done();
|
||||
});
|
||||
|
||||
$editable.find('.s_hr').mousedown().click();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('background-image', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(2);
|
||||
|
||||
return weTestUtils.createWysiwyg({
|
||||
wysiwygOptions: {
|
||||
snippets: true,
|
||||
value:
|
||||
'<h1>Big Title</h1>' +
|
||||
'<section class="test_option_all pt32 pb32">' +
|
||||
' <div class="container">' +
|
||||
' <div class="row">' +
|
||||
' <div class="col-lg-10 offset-lg-1 pt32 pb32">' +
|
||||
' <h2>Title</h2>' +
|
||||
' <p class="lead o_default_snippet_text">Content</p>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
'</section>',
|
||||
},
|
||||
}).then(function (wysiwyg) {
|
||||
var $editable = wysiwyg.getEditable();
|
||||
|
||||
var defMediaDialogInit = $.Deferred();
|
||||
var defMediaDialogSave = $.Deferred();
|
||||
testUtils.mock.patch(MediaDialog, {
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.opened(defMediaDialogInit.resolve.bind(defMediaDialogInit));
|
||||
},
|
||||
save: function () {
|
||||
$.when(this._super.apply(this, arguments)).then(function () {
|
||||
defMediaDialogSave.resolve();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
testUtils.mock.intercept(wysiwyg, "snippet_focused", function () {
|
||||
$('#oe_manipulators .oe_overlay_options .oe_options a:first').click();
|
||||
$('#oe_manipulators .oe_overlay_options a.snippet-option-background[data-choose-image]').click();
|
||||
|
||||
defMediaDialogInit.then(function () {
|
||||
$('.modal.note-image-dialog .existing-attachments .o_image').click();
|
||||
$('.modal.note-image-dialog .modal-footer .btn-primary').click();
|
||||
});
|
||||
|
||||
defMediaDialogSave.then(function () {
|
||||
var $snippet = $editable.find('.test_option_all');
|
||||
assert.ok($snippet.hasClass('oe_custom_bg'), "should use custom background");
|
||||
var background = $snippet.css('background-image') || '';
|
||||
assert.strictEqual(background.replace(/[^(\("'\/]+\/\/[^\("'\/]+/, ''),
|
||||
'url("/web_editor/static/src/img/transparent.png")',
|
||||
"should change the background image");
|
||||
|
||||
testUtils.mock.unpatch(MediaDialog);
|
||||
wysiwyg.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
$editable.find('h2').mousedown().click();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('update content', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(1);
|
||||
|
||||
return weTestUtils.createWysiwyg({
|
||||
wysiwygOptions: {
|
||||
snippets: true,
|
||||
value: '<div class="s_hr pt32 pb32"> ' +
|
||||
'<img class="img-fluid o_we_custom_image test_option_all" data-src="/web_editor/static/src/img/transparent.png"> ' +
|
||||
'</div>',
|
||||
},
|
||||
}).then(function (wysiwyg) {
|
||||
var $editable = wysiwyg.getEditable();
|
||||
|
||||
testUtils.mock.intercept(wysiwyg, "snippet_focused", function () {
|
||||
var $snippetOption = $('#oe_manipulators .oe_overlay_options:visible');
|
||||
wysiwyg.$el.siblings('.note-image-popover').find('.note-media .note-icon-trash').mousedown().click();
|
||||
assert.ok($snippetOption.is(':hidden'), "should remove the snippet editor because the target are removed");
|
||||
|
||||
wysiwyg.destroy();
|
||||
done();
|
||||
});
|
||||
|
||||
$editable.find('img').mousedown().click();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_ui
|
||||
from . import test_converter
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo.tests
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class TestUi(odoo.tests.HttpCase):
|
||||
|
||||
def test_01_admin_rte(self):
|
||||
self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('rte')", "odoo.__DEBUG__.services['web_tour.tour'].tours.rte.ready", login='admin')
|
||||
|
||||
def test_02_admin_rte_inline(self):
|
||||
self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('rte_inline')", "odoo.__DEBUG__.services['web_tour.tour'].tours.rte_inline.ready", login='admin')
|
||||
@@ -92,7 +92,11 @@ var RunningTourActionHelper = core.Class.extend({
|
||||
$selectedOption.prop("selected", true);
|
||||
this._click(values);
|
||||
} else {
|
||||
values.$element.focusIn();
|
||||
values.$element.trigger($.Event( "keydown", {key: '_', keyCode: 95}));
|
||||
values.$element.text(text);
|
||||
values.$element.focusInEnd();
|
||||
values.$element.trigger($.Event( "keyup", {key: '_', keyCode: 95}));
|
||||
}
|
||||
values.$element.trigger("change");
|
||||
},
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
odoo.define('website.tour.rte', function (require) {
|
||||
'use strict';
|
||||
|
||||
var ajax = require('web.ajax');
|
||||
var session = require('web.session');
|
||||
var tour = require('web_tour.tour');
|
||||
var base = require('web_editor.base');
|
||||
var Wysiwyg = require('web_editor.wysiwyg');
|
||||
|
||||
var domReady = $.Deferred();
|
||||
$(domReady.resolve.bind(domReady));
|
||||
var ready = $.when(domReady, session.is_bound, ajax.loadXML());
|
||||
|
||||
tour.register('rte_translator', {
|
||||
test: true,
|
||||
wait_for: base.ready(),
|
||||
wait_for: ready,
|
||||
}, [{
|
||||
content: "click on Add a language",
|
||||
trigger: '.js_language_selector a:has(i.fa)',
|
||||
@@ -59,10 +65,10 @@ tour.register('rte_translator', {
|
||||
content: "save",
|
||||
trigger: 'button[data-action=save]',
|
||||
extra_trigger: '#wrap p:first b',
|
||||
|
||||
}, {
|
||||
content : "click language dropdown",
|
||||
trigger : '.js_language_selector .dropdown-toggle',
|
||||
extra_trigger: 'body:not(.o_wait_reload):not(:has(.note-editor))',
|
||||
}, {
|
||||
content: "click on french version",
|
||||
trigger: '.js_language_selector a[data-lang="fr_BE"]',
|
||||
@@ -81,14 +87,17 @@ tour.register('rte_translator', {
|
||||
trigger: '#wrap p font:first',
|
||||
run: function (action_helper) {
|
||||
action_helper.text('translated french text');
|
||||
this.$anchor.trigger('keydown');
|
||||
Wysiwyg.setRange(this.$anchor.contents()[0], 22);
|
||||
this.$anchor.trigger($.Event( "keyup", {key: '_', keyCode: 95}));
|
||||
},
|
||||
}, {
|
||||
content: "translate text with special char",
|
||||
trigger: '#wrap input + p span:first',
|
||||
run: function (action_helper) {
|
||||
action_helper.click();
|
||||
this.$anchor.prepend('<{translated}>').trigger('keydown');
|
||||
this.$anchor.prepend('<{translated}>');
|
||||
Wysiwyg.setRange(this.$anchor.contents()[0], 0);
|
||||
this.$anchor.trigger($.Event( "keyup", {key: '_', keyCode: 95}));
|
||||
},
|
||||
}, {
|
||||
content: "click on input",
|
||||
@@ -124,10 +133,13 @@ tour.register('rte_translator', {
|
||||
content : "click language dropdown",
|
||||
trigger : '.js_language_selector .dropdown-toggle',
|
||||
|
||||
}, {
|
||||
content: "open language selector",
|
||||
trigger: '.js_language_selector button:first',
|
||||
extra_trigger: 'html[lang*="fr"]:not(:has(#wrap p span))',
|
||||
}, {
|
||||
content: "return to english version",
|
||||
trigger: '.js_language_selector a[data-lang="en_US"]',
|
||||
extra_trigger: 'html[lang*="fr"]:not(:has(#wrap p span))',
|
||||
}, {
|
||||
content: "edit english version",
|
||||
trigger: 'a[data-action=edit]',
|
||||
@@ -139,27 +151,25 @@ tour.register('rte_translator', {
|
||||
run: function (action_helper) {
|
||||
action_helper.click();
|
||||
var el = this.$anchor[0];
|
||||
var evt = document.createEvent("MouseEvents");
|
||||
$.summernote.core.range.create(el.childNodes[2], 6, el.childNodes[2], 13).select();
|
||||
evt.initMouseEvent('mouseup', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, el);
|
||||
el.dispatchEvent(evt);
|
||||
this.$anchor.trigger('mousedown');
|
||||
Wysiwyg.setRange(el.childNodes[2], 6, el.childNodes[2], 13);
|
||||
this.$anchor.trigger('mouseup');
|
||||
},
|
||||
}, {
|
||||
content: "underline",
|
||||
trigger: '.note-air-popover button[data-event="underline"]',
|
||||
trigger: 'button.note-btn-underline',
|
||||
}, {
|
||||
content: "save new change",
|
||||
trigger: 'button[data-action=save]',
|
||||
extra_trigger: '#wrap p u',
|
||||
extra_trigger: '#wrap.o_dirty p u',
|
||||
|
||||
}, {
|
||||
content : "click language dropdown",
|
||||
trigger : '.js_language_selector .dropdown-toggle',
|
||||
|
||||
extra_trigger: '#wrap p u',
|
||||
}, {
|
||||
content: "return in french",
|
||||
trigger: 'html[lang="en-US"] .js_language_selector a[data-lang="fr_BE"]',
|
||||
extra_trigger: 'html[lang*="en"]:not(:has(button[data-action=save]))',
|
||||
trigger : 'html[lang="en-US"] .js_language_selector .js_change_lang[data-lang="fr_BE"]',
|
||||
}, {
|
||||
content: "check bis: content is translated",
|
||||
trigger: '#wrap p font:first:contains(translated french text)',
|
||||
|
||||
@@ -16,7 +16,7 @@ odoo.define("website_blog.tour", function (require) {
|
||||
position: "bottom",
|
||||
}, {
|
||||
trigger: "h1[data-oe-expression=\"blog_post.name\"]",
|
||||
extra_trigger: "body.editor_has_snippets",
|
||||
extra_trigger: ".o_snippets_loaded",
|
||||
content: _t("Write a title, the subtitle is optional."),
|
||||
position: "top",
|
||||
run: "text",
|
||||
@@ -26,7 +26,7 @@ odoo.define("website_blog.tour", function (require) {
|
||||
content: _t("Set a blog post <b>cover</b>."),
|
||||
position: "bottom",
|
||||
}, {
|
||||
trigger: "a:containsExact(" + _t("Change Cover")+ "):eq(1)",
|
||||
trigger: "a:containsExact(" + _t("Change Cover")+ "):visible",
|
||||
content: _t("Click here to change your post cover."),
|
||||
position: "right",
|
||||
}, {
|
||||
|
||||
@@ -2,15 +2,11 @@ odoo.define("website_sale.tour_shop", function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require("web.core");
|
||||
var tour = require("web_tour.tour");
|
||||
var base = require("web_editor.base");
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
tour.register("shop", {
|
||||
url: "/shop",
|
||||
wait_for: base.ready(),
|
||||
}, [{
|
||||
// return the steps, used for backend and frontend (because web_editor.base is only in the website)
|
||||
|
||||
return [{
|
||||
trigger: "#new-content-menu > a",
|
||||
content: _t("Let's create your first product."),
|
||||
extra_trigger: ".js_sale",
|
||||
@@ -28,13 +24,14 @@ odoo.define("website_sale.tour_shop", function (require) {
|
||||
content: _t("Click on <em>Continue</em> to create the product."),
|
||||
position: "right",
|
||||
}, {
|
||||
trigger: ".product_price .o_is_inline_editable .oe_currency_value",
|
||||
trigger: ".product_price .oe_currency_value:visible",
|
||||
extra_trigger: ".note-editable",
|
||||
content: _t("Edit the price of this product by clicking on the amount."),
|
||||
position: "bottom",
|
||||
run: "text 1.99",
|
||||
}, {
|
||||
trigger: "#wrap img.product_detail_img",
|
||||
extra_trigger: ".product_price .o_is_inline_editable .oe_currency_value:not(:containsExact(1.00))",
|
||||
extra_trigger: ".product_price .o_dirty .oe_currency_value:not(:containsExact(1.00))",
|
||||
content: _t("Double click here to set an image describing your product."),
|
||||
position: "top",
|
||||
run: function (actions) {
|
||||
@@ -66,10 +63,10 @@ odoo.define("website_sale.tour_shop", function (require) {
|
||||
trigger: ".o_main_navbar .o_menu_toggle, #oe_applications .dropdown-toggle",
|
||||
content: _t("Let's now take a look at your administration dashboard to get your eCommerce website ready in no time."),
|
||||
position: "bottom",
|
||||
}, {
|
||||
}, { // backend
|
||||
trigger: '.o_apps > a[data-menu-xmlid="website.menu_website_configuration"], #oe_main_menu_navbar a[data-menu-xmlid="website.menu_website_configuration"]',
|
||||
content: _t("Open your website app here."),
|
||||
extra_trigger: ".o_apps,#oe_applications",
|
||||
position: "bottom",
|
||||
}]);
|
||||
}];
|
||||
});
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
odoo.define("website_sale.tour_shop_backend", function (require) {
|
||||
"use strict";
|
||||
|
||||
var tour = require("web_tour.tour");
|
||||
var steps = require("website_sale.tour_shop");
|
||||
tour.register("shop", steps);
|
||||
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
odoo.define("website_sale.tour_shop_frontend", function (require) {
|
||||
"use strict";
|
||||
|
||||
var tour = require("web_tour.tour");
|
||||
var base = require("web_editor.base");
|
||||
var steps = require("website_sale.tour_shop");
|
||||
tour.register("shop", {
|
||||
url: "/shop",
|
||||
wait_for: base.ready(),
|
||||
}, steps);
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user