odoo.define('web_editor.test_utils', function (require) { "use strict"; var ajax = require('web.ajax'); var MockServer = require('web.MockServer'); var testUtils = require('web.test_utils'); var Widget = require('web.Widget'); var Wysiwyg = require('web_editor.wysiwyg'); var snippetOptions = require('web_editor.snippets.options'); const COLOR_PICKER_TEMPLATE = `
`; const SNIPPETS_TEMPLATE = `

Add blocks

First Panel

Title

Content

Background Image
Top Middle Bottom Equal height
`; MockServer.include({ //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- /** * @override * @private * @returns {Promise} */ async _performRpc(route, args) { if (args.model === "ir.ui.view") { if (args.method === 'read_template' && args.args[0] === "web_editor.colorpicker") { return COLOR_PICKER_TEMPLATE; } if (args.method === 'render_public_asset' && args.args[0] === "web_editor.snippets") { return SNIPPETS_TEMPLATE; } } return this._super(...arguments); }, }); /** * Options with animation and edition for test. */ snippetOptions.registry.option_test = snippetOptions.SnippetOptionWidget.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(); }, }); function patch() { testUtils.mock.patch(ajax, { loadAsset: function (xmlId) { if (xmlId === 'template.assets') { return Promise.resolve({ cssLibs: [], cssContents: ['body {background-color: red;}'] }); } if (xmlId === 'template.assets_all_style') { return Promise.resolve({ cssLibs: $('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(args) { if (args[0] === 'web_editor.colorpicker') { return COLOR_PICKER_TEMPLATE; } }, render_template(args) { if (args[0] === 'web_editor.snippets') { return SNIPPETS_TEMPLATE; } }, }, 'ir.attachment': { fields: { display_name: { string: "display_name", type: 'char', }, description: { string: "description", 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', }, public: { string: "public", type: 'boolean', }, access_token: { string: "access_token", type: 'char', }, image_src: { string: "image_src", type: 'char', }, image_width: { string: "image_width", type: 'integer', }, image_height: { string: "image_height", type: 'integer', }, original_id: { string: "original_id", type: 'many2one', relation: 'ir.attachment', }, }, records: [{ id: 1, name: 'image', description: '', mimetype: 'image/png', checksum: false, url: '/web/image/123/transparent.png', type: 'url', res_id: 0, res_model: false, public: true, access_token: false, image_src: '/web/image/123/transparent.png', image_width: 256, image_height: 256, }], generate_access_token: function () { return; }, }, }); } /** * Create the wysiwyg instance for test (contains patch, usefull ir.ui.view, snippets). */ async function createWysiwyg() { patch(); var params = {data: wysiwygData({})}; var parent = new Widget(); await testUtils.mock.addMockEnvironment(parent, params); var wysiwygOptions = _.extend(this._getWysiwygOptions(), await this._getWysiwygOptions(), { recordInfo: { context: {}, res_model: 'module.test', res_id: 1, }, useOnlyTestUnbreakable: params.useOnlyTestUnbreakable, }); this.wysiwyg = new WysiwygTest(this, wysiwygOptions); this.wysiwyg._parentToDestroyForTest = this; var self = this return this.wysiwyg.attachTo(this).then(function () { self._appendTranslateButton(); if (wysiwygOptions.snippets) { var defSnippets = testUtils.makeTestPromise(); testUtils.mock.intercept(self.wysiwyg, "snippets_loaded", function () { defSnippets.resolve(self.wysiwyg); }); return defSnippets; } return self.wysiwyg; }); } /** * Char codes. */ 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 */ /** * 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); if (!event.isDefaultPrevented()) { if (keyPress.key.length === 1) { textInput($target[0], keyPress.key); } else { console.warn('Native "' + keyPress.key + '" is not supported in test'); } } }; var textInput = function (target, char) { var ev = new CustomEvent('textInput', { bubbles: true, cancelBubble: false, cancelable: true, composed: true, data: char, defaultPrevented: false, detail: 0, eventPhase: 3, isTrusted: true, returnValue: true, sourceCapabilities: null, type: "textInput", which: 0, }); ev.data = char; target.dispatchEvent(ev); if (!ev.defaultPrevented) { document.execCommand("insertText", 0, ev.data); } }; return { wysiwygData: wysiwygData, createWysiwyg: createWysiwyg, select: select, keydown: keydown, patch: patch, unpatch: unpatch, }; });