diff options
| author | Aaron Bohy <aab@odoo.com> | 2018-11-20 10:09:09 +0000 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2018-11-20 10:17:37 +0000 |
| commit | 49e78f46ee5829c89cc20676ec56ca08bdf8e573 (patch) | |
| tree | c1bb4e72a8c2539655b7940ba54b4ebd5a0ec437 /addons/web/static/tests/helpers/test_utils.js | |
| parent | 24ca67b54550126013e96f0dc59f0240b6421d76 (diff) | |
[IMP] web: tests: backport of new helpers
This rev. is a partial backport of abf32b8b, so that the newly
introduced helpers are already available in 12.0.
closes odoo/odoo#28827
Diffstat (limited to 'addons/web/static/tests/helpers/test_utils.js')
| -rw-r--r-- | addons/web/static/tests/helpers/test_utils.js | 992 |
1 files changed, 82 insertions, 910 deletions
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js index f20f183d799..d90a50ad2ca 100644 --- a/addons/web/static/tests/helpers/test_utils.js +++ b/addons/web/static/tests/helpers/test_utils.js @@ -10,900 +10,18 @@ odoo.define('web.test_utils', function (require) { * instance of a view, appended in the dom, ready to be tested. */ -var ActionManager = require('web.ActionManager'); var ajax = require('web.ajax'); -var basic_fields = require('web.basic_fields'); -var config = require('web.config'); -var ControlPanel = require('web.ControlPanel'); var core = require('web.core'); -var DebugManager = require('web.DebugManager'); -var dom = require('web.dom'); var session = require('web.session'); -var MockServer = require('web.MockServer'); -var Widget = require('web.Widget'); - -var DebouncedField = basic_fields.DebouncedField; - -/** - * intercepts an event bubbling up the widget hierarchy. The event intercepted - * must be a "custom event", i.e. an event generated by the method 'trigger_up'. - * - * Note that this method really intercepts the event if @propagate is not set. - * It will not be propagated further, and even the handlers on the target will - * not fire. - * - * @param {Widget} widget the target widget (any Odoo widget) - * @param {string} eventName description of the event - * @param {function} fn callback executed when the even is intercepted - * @param {boolean} [propagate=false] - */ -function intercept(widget, eventName, fn, propagate) { - var _trigger_up = widget._trigger_up.bind(widget); - widget._trigger_up = function (event) { - if (event.name === eventName) { - fn(event); - if (!propagate) { return; } - } - _trigger_up(event); - }; -} - -/** - * logs all event going through the target widget. - * - * @param {Widget} widget - */ -function observe(widget) { - var _trigger_up = widget._trigger_up.bind(widget); - widget._trigger_up = function (event) { - console.log('%c[event] ' + event.name, 'color: blue; font-weight: bold;', event); - _trigger_up(event); - }; -} - -/** - * create and return an instance of ActionManager with all rpcs going through a - * mock method using the data, actions and archs objects as sources. - * - * @param {Object} [params] - * @param {Object} [params.actions] the actions given to the mock server - * @param {Object} [params.archs] this archs given to the mock server - * @param {Object} [params.data] the business data given to the mock server - * @param {boolean} [params.debug] - * @param {function} [params.mockRPC] - * @returns {ActionManager} - */ -var createActionManager = function (params) { - params = params || {}; - var $target = $('#qunit-fixture'); - if (params.debug) { - $target = $('body'); - $target.addClass('debug'); - } - - var widget = new Widget(); - // when 'document' addon is installed, the sidebar does a 'search_read' on - // model 'ir_attachment' each time a record is open, so we monkey-patch - // 'mockRPC' to mute those RPCs, so that the tests can be written uniformly, - // whether or not 'document' is installed - var mockRPC = params.mockRPC; - _.extend(params, { - mockRPC: function (route, args) { - if (args.model === 'ir.attachment') { - return $.when([]); - } - if (mockRPC) { - return mockRPC.apply(this, arguments); - } - return this._super.apply(this, arguments); - }, - }); - addMockEnvironment(widget, _.defaults(params, {debounce: false})); - widget.prependTo($target); - widget.$el.addClass('o_web_client'); - if (config.device.isMobile) { - widget.$el.addClass('o_touch_device'); - } - - var userContext = params.context && params.context.user_context || {}; - var actionManager = new ActionManager(widget, userContext); - - var originalDestroy = ActionManager.prototype.destroy; - actionManager.destroy = function () { - actionManager.destroy = originalDestroy; - widget.destroy(); - }; - actionManager.appendTo(widget.$el); - - return actionManager; -}; -/** - * Create and return an instance of DebugManager with all rpcs going through a - * mock method, assuming that the user has access rights, and is an admin. - * - * @param {Object} [params={}] - */ -var createDebugManager = function (params) { - params = params || {}; - var mockRPC = params.mockRPC; - _.extend(params, { - mockRPC: function (route, args) { - if (args.method === 'check_access_rights') { - return $.when(true); - } - if (args.method === 'xmlid_to_res_id') { - return $.when(true); - } - if (mockRPC) { - return mockRPC.apply(this, arguments); - } - return this._super.apply(this, arguments); - }, - session: { - user_has_group: function (group) { - if (group === 'base.group_no_one') { - return $.when(true); - } - return this._super.apply(this, arguments); - }, - }, - }); - var debugManager = new DebugManager(); - addMockEnvironment(debugManager, params); - return debugManager; -}; - -/** - * performs a fields_view_get, and mocks the postprocessing done by the - * data_manager to return an equivalent structure. - * - * @param {MockServer} server - * @param {Object} params - * @param {string} params.model - * @returns {Object} an object with 3 keys: arch, fields and viewFields - */ -function fieldsViewGet(server, params) { - var fieldsView = server.fieldsViewGet(params); - // mock the structure produced by the DataManager - fieldsView.viewFields = fieldsView.fields; - fieldsView.fields = server.fieldsGet(params.model); - return fieldsView; -} - -/** - * create a view synchronously. This method uses the createAsyncView method. - * Most views are synchronous, so the deferred can be resolved immediately and - * this method will work. - * - * Be careful, if for some reason a view is async, this method will crash. - * @see createAsyncView - * - * @param {Object} params will be given to createAsyncView - * @returns {AbstractView} - */ -function createView(params) { - var view; - createAsyncView(params).then(function (result) { - view = result; - }); - if (!view) { - throw "The view that you are trying to create is async. Please use createAsyncView instead"; - } - return view; -} - -/** - * create a view from various parameters. Here, a view means a javascript - * instance of an AbstractView class, such as a form view, a list view or a - * kanban view. - * - * It returns the instance of the view, properly created, with all rpcs going - * through a mock method using the data object as source, and already loaded/ - * started. - * - * Most views can be tested synchronously (@see createView), but some view have - * external dependencies (like lazy loaded libraries). In that case, it is - * necessary to use this method. - * - * @param {Object} params - * @param {string} params.arch the xml (arch) of the view to be instantiated - * @param {any[]} [params.domain] the initial domain for the view - * @param {Object} [params.context] the initial context for the view - * @param {Object} [params.debug=false] if true, the widget will be appended in - * the DOM. Also, RPCs and uncaught OdooEvent will be logged - * @param {string[]} [params.groupBy] the initial groupBy for the view - * @param {integer} [params.fieldDebounce=0] the debounce value to use for the - * duration of the test. - * @param {AbstractView} params.View the class that will be instantiated - * @param {string} params.model a model name, will be given to the view - * @param {Object} params.intercepts an object with event names as key, and - * callback as value. Each key,value will be used to intercept the event. - * Note that this is particularly useful if you want to intercept events going - * up in the init process of the view, because there are no other way to do it - * after this method returns - * @returns {Deferred<AbstractView>} resolves with the instance of the view - */ -function createAsyncView(params) { - var $target = $('#qunit-fixture'); - var widget = new Widget(); - if (params.debug) { - $target = $('body'); - $target.addClass('debug'); - } - - // add mock environment: mock server, session, fieldviewget, ... - var mockServer = addMockEnvironment(widget, params); - var viewInfo = fieldsViewGet(mockServer, params); - // create the view - var viewOptions = { - modelName: params.model || 'foo', - ids: 'res_id' in params ? [params.res_id] : undefined, - currentId: 'res_id' in params ? params.res_id : undefined, - domain: params.domain || [], - context: params.context || {}, - groupBy: params.groupBy || [], - }; - if (params.hasSelectors) { - viewOptions.hasSelectors = params.hasSelectors; - } - - _.extend(viewOptions, params.viewOptions); - - var view = new params.View(viewInfo, viewOptions); - - // reproduce the DOM environment of views - var $web_client = $('<div>').addClass('o_web_client').prependTo($target); - var controlPanel = new ControlPanel(widget); - controlPanel.appendTo($web_client); - var $content = $('<div>').addClass('o_content').appendTo($web_client); - - if (params.interceptsPropagate) { - _.each(params.interceptsPropagate, function (cb, name) { - intercept(widget, name, cb, true); - }); - } - - return view.getController(widget).then(function (view) { - // override the view's 'destroy' so that it calls 'destroy' on the widget - // instead, as the widget is the parent of the view and the mockServer. - view.__destroy = view.destroy; - view.destroy = function () { - // remove the override to properly destroy the view and its children - // when it will be called the second time (by its parent) - delete view.destroy; - widget.destroy(); - }; - - // link the view to the control panel - view.set_cp_bus(controlPanel.get_bus()); - - // render the view in a fragment as they must be able to render correctly - // without being in the DOM - var fragment = document.createDocumentFragment(); - return view.appendTo(fragment).then(function () { - dom.append($content, fragment, { - callbacks: [{widget: view}], - in_DOM: true, - }); - view.$el.on('click', 'a', function (ev) { - ev.preventDefault(); - }); - - return view; - }); - }); -} - -/** - * Patch window.Date so that the time starts its flow from the provided Date. - * - * Usage: - * - * ``` - * var unpatchDate = testUtils.patchDate(2018, 0, 10, 17, 59, 30) - * new window.Date(); // "Wed Jan 10 2018 17:59:30 GMT+0100 (Central European Standard Time)" - * ... // 5 hours delay - * new window.Date(); // "Wed Jan 10 2018 22:59:30 GMT+0100 (Central European Standard Time)" - * ... - * unpatchDate(); - * new window.Date(); // actual current date time - * ``` - * - * @param {integer} year - * @param {integer} month index of the month, starting from zero. - * @param {integer} day the day of the month. - * @param {integer} hours the digits for hours (24h) - * @param {integer} minutes - * @param {integer} seconds - * @returns {function} a callback to unpatch window.Date. - */ -function patchDate(year, month, day, hours, minutes, seconds) { - var RealDate = window.Date; - var actualDate = new RealDate(); - var fakeDate = new RealDate(year, month, day, hours, minutes, seconds); - var timeInterval = actualDate.getTime() - (fakeDate.getTime()); - - window.Date = function Date() { - if (arguments.length > 0) { - return RealDate.apply(this, arguments); - } else { - var date = new RealDate(); - var time = date.getTime(); - time -= timeInterval; - date.setTime(time); - return date; - } - }; - - _.mapObject(RealDate, function (val, key) { - window.Date[key] = val; - }); - window.Date.prototype = RealDate.prototype; - - return function () {window.Date = RealDate;}; -} - -/** - * Add a mock environment to a widget. This helper function can simulate - * various kind of side effects, such as mocking RPCs, changing the session, - * or the translation settings. - * - * The simulated environment lasts for the lifecycle of the widget, meaning it - * disappears when the widget is destroyed. It is particularly relevant for the - * session mocks, because the previous session is restored during the destroy - * call. So, it means that you have to be careful and make sure that it is - * properly destroyed before another test is run, otherwise you risk having - * interferences between tests. - * - * @param {Widget} widget - * @param {Object} params - * @param {Object} [params.archs] a map of string [model,view_id,view_type] to - * a arch object. It is used to mock answers to 'load_views' custom events. - * This is useful when the widget instantiate a formview dialog that needs - * to load a particular arch. - * @param {string} [params.currentDate] a string representation of the current - * date. It is given to the mock server. - * @param {Object} params.data the data given to the created mock server. It is - * used to generate mock answers for every kind of routes supported by odoo - * @param {number} [params.debug] if set to true, logs RPCs and uncaught Odoo - * events. - * @param {function} [params.mockRPC] a function that will be used to override - * the _performRpc method from the mock server. It is really useful to add - * some custom rpc mocks, or to check some assertions. - * @param {Object} [params.session] if it is given, it will be used as answer - * for all calls to this.getSession() by the widget, of its children. Also, - * it will be used to extend the current, real session. This side effect is - * undone when the widget is destroyed. - * @param {Object} [params.translateParameters] if given, it will be used to - * extend the core._t.database.parameters object. After the widget - * destruction, the original parameters will be restored. - * @param {Object} [params.intercepts] an object with event names as key, and - * callback as value. Each key,value will be used to intercept the event. - * Note that this is particularly useful if you want to intercept events going - * up in the init process of the view, because there are no other way to do it - * after this method returns. Some events ('call_service', "load_views", - * "get_session", "load_filters") have a special treatment beforehand. - * @param {Object} [params.services={}] list of services to load in - * addition to the ajax service. For instance, if a test needs the local - * storage service in order to work, it can provide a mock version of it. - * @param {boolean} [debounce=true] set to false to completely remove the - * debouncing, forcing the handler to be called directly (not on the next - * execution stack, like it does with delay=0). - * @param {boolean} [throttle=false] set to true to keep the throttling, which - * is completely removed by default. - * - * @returns {MockServer} the instance of the mock server, created by this - * function. It is necessary for createAsyncView so that method can call some - * other methods on it. - */ -function addMockEnvironment(widget, params) { - var Server = MockServer; - params.services = params.services || {}; - if (params.mockRPC) { - Server = MockServer.extend({_performRpc: params.mockRPC}); - } - if (params.debug) { - observe(widget); - var separator = window.location.href.indexOf('?') !== -1 ? "&" : "?"; - var url = window.location.href + separator + 'testId=' + QUnit.config.current.testId; - console.log('%c[debug] debug mode activated', 'color: blue; font-weight: bold;', url); - } - - var mockServer = new Server(params.data, { - actions: params.actions, - archs: params.archs, - currentDate: params.currentDate, - debug: params.debug, - widget: widget, - }); - - // make sure images do not trigger a GET on the server - $('body').on('DOMNodeInserted.removeSRC', function (event) { - removeSrcAttribute(event.target, widget); - }); - - // make sure the debounce value for input fields is set to 0 - var initialDebounceValue = DebouncedField.prototype.DEBOUNCE; - DebouncedField.prototype.DEBOUNCE = params.fieldDebounce || 0; - var initialSession, initialConfig, initialParameters, initialDebounce, initialThrottle; - initialSession = _.extend({}, session); - session.getTZOffset = function () { - return 0; // by default, but may be overriden in specific tests - }; - if ('session' in params) { - _.extend(session, params.session); - } - if ('config' in params) { - initialConfig = _.clone(config); - initialConfig.device = _.clone(config.device); - if ('device' in params.config) { - _.extend(config.device, params.config.device); - } - if ('debug' in params.config) { - config.debug = params.config.debug; - } - } - if ('translateParameters' in params) { - initialParameters = _.extend({}, core._t.database.parameters); - _.extend(core._t.database.parameters, params.translateParameters); - } - if (params.debounce === false) { - initialDebounce = _.debounce; - _.debounce = function (func) { - return func; - }; - } - if (!('throttle' in params) || !params.throttle) { - initialThrottle = _.throttle; - _.throttle = function (func) { - return func; - }; - } - - var widgetDestroy = widget.destroy; - widget.destroy = function () { - // clear the caches (e.g. data_manager, ModelFieldSelector) when the - // widget is destroyed, at the end of each test to avoid collisions - core.bus.trigger('clear_cache'); - - DebouncedField.prototype.DEBOUNCE = initialDebounceValue; - if (params.debounce === false) { - _.debounce = initialDebounce; - } - if (!('throttle' in params) || !params.throttle) { - _.throttle = initialThrottle; - } - - var key; - if ('session' in params) { - for (key in session) { - delete session[key]; - } - } - _.extend(session, initialSession); - if ('config' in params) { - for (key in config) { - delete config[key]; - } - _.extend(config, initialConfig); - } - if ('translateParameters' in params) { - for (key in core._t.database.parameters) { - delete core._t.database.parameters[key]; - } - _.extend(core._t.database.parameters, initialParameters); - } - - $('body').off('DOMNodeInserted.removeSRC'); - $('.blockUI').remove(); - - widgetDestroy.call(this); - }; - - // Dispatch service calls - // Note: some services could call other services at init, - // Which is why we have to init services after that - var services = {}; - intercept(widget, 'call_service', function (ev) { - var args, result; - if (services[ev.data.service]) { - var service = services[ev.data.service]; - args = (ev.data.args || []); - result = service[ev.data.method].apply(service, args); - } else if (ev.data.service === 'ajax') { - // use ajax service that is mocked by the server - var route = ev.data.args[0]; - args = ev.data.args[1]; - result = mockServer.performRpc(route, args); - } - ev.data.callback(result); - }); - - intercept(widget, 'load_action', function (event) { - mockServer.performRpc('/web/action/load', { - kwargs: { - action_id: event.data.actionID, - additional_context: event.data.context, - }, - }).then(function (action) { - event.data.on_success(action); - }); - }); - - intercept(widget, "load_views", function (event) { - mockServer.performRpc('/web/dataset/call_kw/' + event.data.modelName, { - args: [], - kwargs: { - context: event.data.context, - options: event.data.options, - views: event.data.views, - }, - method: 'load_views', - model: event.data.modelName, - }).then(function (views) { - views = _.mapObject(views, function (viewParams) { - return fieldsViewGet(mockServer, viewParams); - }); - event.data.on_success(views); - }); - }); - - intercept(widget, "get_session", function (event) { - event.data.callback(session); - }); - - intercept(widget, "load_filters", function (event) { - if (params.debug) { - console.log('[mock] load_filters', event.data); - } - event.data.on_success([]); - }); - - // make sure all Odoo events bubbling up are intercepted - if ('intercepts' in params) { - _.each(params.intercepts, function (cb, name) { - intercept(widget, name, cb); - }); - } - - // Deploy services - var done = false; - var servicesToDeploy = _.clone(params.services); - if (!servicesToDeploy.ajax) { - services.ajax = null; // use mocked ajax from mocked server - } - while (!done) { - var serviceName = _.findKey(servicesToDeploy, function (Service) { - return !_.some(Service.prototype.dependencies, function (depName) { - return !_.has(services, depName); - }); - }); - if (serviceName) { - var Service = servicesToDeploy[serviceName]; - var service = services[serviceName] = new Service(widget); - delete servicesToDeploy[serviceName]; - - intercept(service, "get_session", function (event) { - event.data.callback(session); - }); - - service.start(); - } else { - var serviceNames = _.keys(servicesToDeploy); - if (serviceNames.length) { - console.warn("Non loaded services:", serviceNames); - } - done = true; - } - } - - return mockServer; -} - -/** - * create a model from given parameters. - * - * @param {Object} params This object will be given to addMockEnvironment, so - * any parameters from that method applies - * @param {Class} params.Model the model class to use - * @returns {Model} - */ -function createModel(params) { - var widget = new Widget(); - - var model = new params.Model(widget); - - addMockEnvironment(widget, params); - - // override the model's 'destroy' so that it calls 'destroy' on the widget - // instead, as the widget is the parent of the model and the mockServer. - model.destroy = function () { - // remove the override to properly destroy the model when it will be - // called the second time (by its parent) - delete model.destroy; - widget.destroy(); - }; - - return model; -} - -/** - * create a widget parent from given parameters. - * - * @param {Object} params This object will be given to addMockEnvironment, so - * any parameters from that method applies - * @returns {Widget} - */ -function createParent(params) { - var widget = new Widget(); - addMockEnvironment(widget, params); - return widget; -} - -/** - * simulate a drag and drop operation between 2 jquery nodes: $el and $to. - * This is a crude simulation, with only the mousedown, mousemove and mouseup - * events, but it is enough to help test drag and drop operations with jqueryUI - * sortable. - * - * @param {jqueryElement} $el - * @param {jqueryElement} $to - * @param {Object} [options] - * @param {string|Object} [options.position='center'] target position: - * can either be one of {'top', 'bottom', 'left', 'right'} or - * an object with two attributes (top and left)) - * @param {boolean} [options.disableDrop=false] whether to trigger the drop action - * @param {boolean} [options.continueMove=false] whether to trigger the - * mousedown action (will only work after another call of this function with - * without this option) - */ -function dragAndDrop($el, $to, options) { - var position = (options && options.position) || 'center'; - var elementCenter = $el.offset(); - var toOffset = $to.offset(); - - if (_.isObject(position)) { - toOffset.top += position.top; - toOffset.left += position.left; - } else { - toOffset.top += $to.outerHeight()/2; - toOffset.left += $to.outerWidth()/2; - var vertical_offset = (toOffset.top < elementCenter.top) ? -1 : 1; - if (position === 'top') { - toOffset.top -= $to.outerHeight()/2 + vertical_offset; - } else if (position === 'bottom') { - toOffset.top += $to.outerHeight()/2 - vertical_offset; - } else if (position === 'left') { - toOffset.left -= $to.outerWidth()/2; - } else if (position === 'right') { - toOffset.left += $to.outerWidth()/2; - } - } - - if ($to[0].ownerDocument !== document) { - // we are in an iframe - var bound = $('iframe')[0].getBoundingClientRect(); - toOffset.left += bound.left; - toOffset.top += bound.top; - } - $el.trigger($.Event("mouseenter")); - if (!(options && options.continueMove)) { - elementCenter.left += $el.outerWidth()/2; - elementCenter.top += $el.outerHeight()/2; - - $el.trigger($.Event("mousedown", { - which: 1, - pageX: elementCenter.left, - pageY: elementCenter.top - })); - } - - $el.trigger($.Event("mousemove", { - which: 1, - pageX: toOffset.left, - pageY: toOffset.top - })); - - if (!(options && options.disableDrop)) { - $el.trigger($.Event("mouseup", { - which: 1, - pageX: toOffset.left, - pageY: toOffset.top - })); - } else { - // It's impossible to drag another element when one is already - // being dragged. So it's necessary to finish the drop when the test is - // over otherwise it's impossible for the next tests to drag and - // drop elements. - $el.on("remove", function () { - $el.trigger($.Event("mouseup")); - }); - } -} - -/** - * simulate a mouse event with a custom event who add the item position. This is - * sometimes necessary because the basic way to trigger an event (such as - * $el.trigger('mousemove')); ) is too crude for some uses. - * - * @param {jqueryElement} $el - * @param {string} type a mouse event type, such as 'mousedown' or 'mousemove' - */ -function triggerMouseEvent($el, type) { - var pos = $el.offset(); - var e = new $.Event(type); - e.pageX = e.layerX = e.screenX = pos.left; - e.pageY = e.layerY = e.screenY =pos.top; - e.which = 1; - $el.trigger(e); -} - -/** - * simulate a mouse event with a custom event on a position x and y. This is - * sometimes necessary because the basic way to trigger an event (such as - * $el.trigger('mousemove')); ) is too crude for some uses. - * - * @param {integer} x - * @param {integer} y - * @param {string} type a mouse event type, such as 'mousedown' or 'mousemove' - */ -function triggerPositionalMouseEvent(x, y, type){ - var ev = document.createEvent("MouseEvent"); - var el = document.elementFromPoint(x,y); - ev.initMouseEvent( - type, - true /* bubble */, - true /* cancelable */, - window, null, - x, y, x, y, /* coordinates */ - false, false, false, false, /* modifier keys */ - 0 /*left button*/, null - ); - el.dispatchEvent(ev); - return el; -} - -/** - * simulate a keypress event for a given character - * @param {string} the character - */ -function triggerKeypressEvent(char) { - var keycode; - if (char === "Enter") { - keycode = $.ui.keyCode.ENTER; - } else { - keycode = char.charCodeAt(0); - } - return $('body').trigger($.Event('keypress', {which: keycode, keyCode: keycode})); -} - -/** - * Removes the src attribute on images and iframes to prevent not found errors, - * and optionally triggers an rpc with the src url as route on a widget. - * This method is critical and must be fastest (=> no jQuery, no underscore) - * - * @param {DOM Node} el - * @param {[Widget]} widget the widget on which the rpc should be performed - */ -function removeSrcAttribute(el, widget) { - var nodes; - if (el.nodeName === 'IMG' || el.nodeName === 'IFRAME') { - nodes = [el]; - } else { - nodes = Array.prototype.slice.call(el.getElementsByTagName('img')) - .concat(Array.prototype.slice.call(el.getElementsByTagName('iframe'))); - } - var node; - while (node = nodes.pop()) { - var src = node.attributes.src && node.attributes.src.value; - if (src && src !== 'about:blank') { - var $el = $(node); - node.setAttribute('data-src', src); - if (node.nodeName === 'IMG') { - node.attributes.removeNamedItem('src'); - } else { - node.setAttribute('src', 'about:blank'); - } - if (widget) { - widget._rpc({route: src}); - } - } - } -} - -var patches = {}; -/** - * Patches a given Class or Object with the given properties. - * - * @param {Class|Object} target - * @param {Object} props - */ -function patch(target, props) { - var patchID = _.uniqueId('patch_'); - target.__patchID = patchID; - patches[patchID] = { - target: target, - otherPatchedProps: [], - ownPatchedProps: [], - }; - if (target.prototype) { - _.each(props, function (value, key) { - if (target.prototype.hasOwnProperty(key)) { - patches[patchID].ownPatchedProps.push({ - key: key, - initialValue: target.prototype[key], - }); - } else { - patches[patchID].otherPatchedProps.push(key); - } - }); - target.include(props); - } else { - _.each(props, function (value, key) { - if (key in target) { - var oldValue = target[key]; - patches[patchID].ownPatchedProps.push({ - key: key, - initialValue: oldValue, - }); - if (typeof value === 'function') { - target[key] = function () { - var oldSuper = this._super; - this._super = oldValue; - var result = value.apply(this, arguments); - if (oldSuper === undefined) { - delete this._super; - } else { - this._super = oldSuper; - } - return result; - }; - } else { - target[key] = value; - } - } else { - patches[patchID].otherPatchedProps.push(key); - target[key] = value; - } - }); - } -} - -/** - * Unpatches a given Class or Object. - * - * @param {Class|Object} target - */ -function unpatch(target) { - var patchID = target.__patchID; - var patch = patches[patchID]; - if (target.prototype) { - _.each(patch.ownPatchedProps, function (p) { - target.prototype[p.key] = p.initialValue; - }); - _.each(patch.otherPatchedProps, function (key) { - delete target.prototype[key]; - }); - } else { - _.each(patch.ownPatchedProps, function (p) { - target[p.key] = p.initialValue; - }); - _.each(patch.otherPatchedProps, function (key) { - delete target[key]; - }); - } - delete patches[patchID]; - delete target.__patchID; -} - -/** - * Opens the datepicker of a given element. - * - * @param {jQuery} $datepickerEl element to which a datepicker is attached - */ -function openDatepicker($datepickerEl) { - $datepickerEl.find('.o_datepicker_input').trigger('focus.datetimepicker'); -} +var testUtilsCreate = require('web.test_utils_create'); +var testUtilsDom = require('web.test_utils_dom'); +var testUtilsFields = require('web.test_utils_fields'); +var testUtilsForm = require('web.test_utils_form'); +var testUtilsGraph = require('web.test_utils_graph'); +var testUtilsKanban = require('web.test_utils_kanban'); +var testUtilsMock = require('web.test_utils_mock'); +var testUtilsModal = require('web.test_utils_modal'); +var testUtilsPivot = require('web.test_utils_pivot'); // Loading static files cannot be properly simulated when their real content is // really needed. This is the case for static XML files so we load them here, @@ -920,25 +38,79 @@ return $.when( QUnit.start(); }, 0); return { - addMockEnvironment: addMockEnvironment, - createActionManager: createActionManager, - createDebugManager: createDebugManager, - createAsyncView: createAsyncView, - createModel: createModel, - createParent: createParent, - createView: createView, - dragAndDrop: dragAndDrop, - fieldsViewGet: fieldsViewGet, - intercept: intercept, - observe: observe, - openDatepicker: openDatepicker, - patch: patch, - patchDate: patchDate, - removeSrcAttribute: removeSrcAttribute, - triggerKeypressEvent: triggerKeypressEvent, - triggerMouseEvent: triggerMouseEvent, - triggerPositionalMouseEvent: triggerPositionalMouseEvent, - unpatch: unpatch, + addMockEnvironment: testUtilsMock.addMockEnvironment, + createActionManager: testUtilsCreate.createActionManager, + createDebugManager: testUtilsCreate.createDebugManager, + createAsyncView: testUtilsCreate.createAsyncView, + createModel: testUtilsCreate.createModel, + createParent: testUtilsCreate.createParent, + createView: testUtilsCreate.createView, + dragAndDrop: testUtilsDom.dragAndDrop, + fieldsViewGet: testUtilsMock.fieldsViewGet, + intercept: testUtilsMock.intercept, + openDatepicker: testUtilsDom.openDatepicker, + patch: testUtilsMock.patch, + patchDate: testUtilsMock.patchDate, + triggerKeypressEvent: testUtilsDom.triggerKeypressEvent, + triggerMouseEvent: testUtilsDom.triggerMouseEvent, + triggerPositionalMouseEvent: testUtilsDom.triggerPositionalMouseEvent, + unpatch: testUtilsMock.unpatch, + + // backport of new tests helpers -> DO NOT FORWARD PORT + mock: { + addMockEnvironment: testUtilsMock.addMockEnvironment, + intercept: testUtilsMock.intercept, + patch: testUtilsMock.patch, + patchDate: testUtilsMock.patchDate, + unpatch: testUtilsMock.unpatch, + fieldsViewGet: testUtilsMock.fieldsViewGet, + }, + dom: { + triggerKeypressEvent: testUtilsDom.triggerKeypressEvent, + triggerMouseEvent: testUtilsDom.triggerMouseEvent, + triggerPositionalMouseEvent: testUtilsDom.triggerPositionalMouseEvent, + dragAndDrop: testUtilsDom.dragAndDrop, + openDatepicker: testUtilsDom.openDatepicker, + click: testUtilsDom.click, + clickFirst: testUtilsDom.clickFirst, + clickLast: testUtilsDom.clickLast, + }, + form: { + clickEdit: testUtilsForm.clickEdit, + clickSave: testUtilsForm.clickSave, + clickCreate: testUtilsForm.clickCreate, + clickDiscard: testUtilsForm.clickDiscard, + reload: testUtilsForm.reload, + }, + graph: { + reload: testUtilsGraph.reload, + }, + kanban: { + reload: testUtilsKanban.reload, + clickCreate: testUtilsKanban.clickCreate, + quickCreate: testUtilsKanban.quickCreate, + toggleGroupSettings: testUtilsKanban.toggleGroupSettings, + toggleRecordDropdown: testUtilsKanban.toggleRecordDropdown, + }, + modal: { + clickButton: testUtilsModal.clickButton, + }, + pivot: { + clickMeasure: testUtilsPivot.clickMeasure, + toggleMeasuresDropdown: testUtilsPivot.toggleMeasuresDropdown, + reload: testUtilsPivot.reload, + }, + fields: { + many2one: { + clickOpenDropdown: testUtilsFields.clickOpenM2ODropdown, + clickHighlightedItem: testUtilsFields.clickM2OHighlightedItem, + clickItem: testUtilsFields.clickM2OItem, + searchAndClickItem: testUtilsFields.searchAndClickM2OItem, + }, + editInput: testUtilsFields.editInput, + editSelect: testUtilsFields.editSelect, + editAndTrigger: testUtilsFields.editAndTrigger, + }, }; }); |
