From 8f7fdb0d99bb99f122b7a5be6795ebb174c27bc6 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Tue, 31 Jul 2018 01:04:30 +0200 Subject: [PATCH] [FIX] web: fix JavaScript qweb and activate tests Fix the issue of the t-esc display the content before the tag. Activate qweb qunit test (same test for the JavaScript and python) Use the same behavior as python: keep attributes order, and display content as default value when the variable is falsy. --- addons/web/static/lib/qweb/qweb-test-call.xml | 4 +- .../web/static/lib/qweb/qweb-test-global.xml | 2 +- addons/web/static/lib/qweb/qweb2.js | 55 ++++++----- addons/web/static/tests/qweb_tests.js | 97 +++++++++++++++++++ .../services/notification_service_tests.js | 2 +- addons/web/views/webclient_templates.xml | 2 + 6 files changed, 135 insertions(+), 27 deletions(-) create mode 100644 addons/web/static/tests/qweb_tests.js diff --git a/addons/web/static/lib/qweb/qweb-test-call.xml b/addons/web/static/lib/qweb/qweb-test-call.xml index 45a65eabce9..8cf1d1d657f 100644 --- a/addons/web/static/lib/qweb/qweb-test-call.xml +++ b/addons/web/static/lib/qweb/qweb-test-call.xml @@ -56,8 +56,8 @@ ok - + - ok + ok diff --git a/addons/web/static/lib/qweb/qweb-test-global.xml b/addons/web/static/lib/qweb/qweb-test-global.xml index 1db3e5920fb..8bdefc6ab44 100644 --- a/addons/web/static/lib/qweb/qweb-test-global.xml +++ b/addons/web/static/lib/qweb/qweb-test-global.xml @@ -18,7 +18,7 @@ - i + i diff --git a/addons/web/static/lib/qweb/qweb2.js b/addons/web/static/lib/qweb/qweb2.js index 6ce74539926..d0297ee12e5 100644 --- a/addons/web/static/lib/qweb/qweb2.js +++ b/addons/web/static/lib/qweb/qweb2.js @@ -28,7 +28,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. var QWeb2 = { expressions_cache: { }, RESERVED_WORDS: 'true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,typeof,eval,void,Math,RegExp,Array,Object,Date'.split(','), - ACTIONS_PRECEDENCE: 'foreach,if,elif,else,call,set,esc,raw,js,debug,log'.split(','), + ACTIONS_PRECEDENCE: 'foreach,if,elif,else,call,set,tag,esc,raw,js,debug,log'.split(','), WORD_REPLACEMENT: { 'and': '&&', 'or': '||', @@ -542,7 +542,7 @@ QWeb2.Element = (function() { this.engine = engine; this.node = node; this.tag = node.tagName; - this.actions = {}; + this.actions = {tag: this.tag}; this.actions_done = []; this.attributes = {}; this.children = []; @@ -568,7 +568,11 @@ QWeb2.Element = (function() { if (name === 'name') { continue; } - this.actions[name] = attr.value; + if (name.match(/^attf?(-.*)?/)) { + this.attributes[m[0]] = attr.value; + } else { + this.actions[name] = attr.value; + } } else { this.attributes[name] = attr.value; } @@ -736,26 +740,25 @@ QWeb2.Element = (function() { } } } + }, + compile_action_tag : function() { if (this.tag.toLowerCase() !== this.engine.prefix) { - var tag = "<" + this.tag; + this.top_string("<" + this.tag); for (var a in this.attributes) { - tag += this.engine.tools.gen_attribute([a, this.attributes[a]]); - } - this.top_string(tag); - if (this.actions.att) { - this.top("r.push(context.engine.tools.gen_attribute(" + (this.format_expression(this.actions.att)) + "));"); - } - for (var a in this.actions) { - var v = this.actions[a]; - var m = a.match(/att-(.+)/); - if (m) { - this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.format_expression(v)) + ")]));"); - } - var m = a.match(/attf-(.+)/); - if (m) { - this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.string_interpolation(v)) + ")]));"); + var v = this.attributes[a]; + var d = a.split('-'); + if (d[0] === this.engine.prefix && d.length > 1) { + if (d.length === 2) { + this.top("r.push(context.engine.tools.gen_attribute(" + (this.format_expression(v)) + "));"); + } else { + this.top("r.push(context.engine.tools.gen_attribute(['" + d.slice(2).join('-') + "', (" + + (d[1] === 'att' ? this.format_expression(v) : this.string_interpolation(v)) + ")]));"); + } + } else { + this.top_string(this.engine.tools.gen_attribute([a, v])); } } + if (this.actions.opentag === 'true' || (!this.children.length && this.is_void_element)) { // We do not enforce empty content on void elements // because QWeb rendering is not necessarily html. @@ -823,12 +826,18 @@ QWeb2.Element = (function() { } }, compile_action_esc : function(value) { - this.top("r.push(context.engine.tools.html_escape(" - + this.format_expression(value) - + "));"); + this.top("var t = " + this.format_str(value) + ";"); + this.top("if (t != null) r.push(context.engine.tools.html_escape(t));"); + this.top("else {"); + this.bottom("}"); + this.indent(); }, compile_action_raw : function(value) { - this.top("r.push(" + (this.format_str(value)) + ");"); + this.top("var t = " + this.format_str(value) + ";"); + this.top("if (t != null) r.push(t);"); + this.top("else {"); + this.bottom("}"); + this.indent(); }, compile_action_js : function(value) { this.top("(function(" + value + ") {"); diff --git a/addons/web/static/tests/qweb_tests.js b/addons/web/static/tests/qweb_tests.js new file mode 100644 index 00000000000..6e40dd1b1cb --- /dev/null +++ b/addons/web/static/tests/qweb_tests.js @@ -0,0 +1,97 @@ +odoo.define('web.qweb', function (require) { +"use strict"; + +var qwebPath = '/web/static/lib/qweb/'; + +function trim(s) { + return s.replace(/(^\s+|\s+$)/g, ''); +} + +/** + * Loads the template file, and executes all the test template in a + * qunit module $title + */ +function loadTest(assert, template, context) { + var done = assert.async(); + assert.expect(1); + + var def = $.Deferred(); + var qweb = new window.QWeb2.Engine(); + + qweb.add_template(qwebPath + template, function (error, doc) { + if (error) { + return $.Deferred().reject(error); + } + def.resolve({ + qweb: qweb, + doc: doc + }); + }); + + def.then(function (r) { + var qweb = r.qweb; + var doc = r.doc; + assert.expect(doc.querySelectorAll('result').length); + + var templates = qweb.templates; + for (var template in templates) { + try { + if (!templates.hasOwnProperty(template)) { + continue; + } + // ignore templates whose name starts with _, they're + // helpers/internal + if (/^_/.test(template)) { + continue; + } + + var params = doc.querySelector('params#' + template); + var args = params ? JSON.parse(params.textContent) : (context ? _.clone(context) : {}); + + var results = doc.querySelector('result#' + template); + assert.equal( + trim(qweb.render(template, args)), + trim(results.textContent), + template); + } catch (error) { + assert.notOk(error.stack || error, 'Rendering error'); + } + } + done(); + }); + return def; +} + +QUnit.module('QWeb', {}, function () { + QUnit.test('Output', function (assert) { + loadTest(assert, 'qweb-test-output.xml'); + }); + QUnit.test('Context-setting', function (assert) { + loadTest(assert, 'qweb-test-set.xml'); + }); + QUnit.test('Conditionals', function (assert) { + loadTest(assert, 'qweb-test-conditionals.xml'); + }); + QUnit.test('Attributes manipulation', function (assert) { + loadTest(assert, 'qweb-test-attributes.xml'); + }); + QUnit.test('Template calling (to the faraway pages)', function (assert) { + loadTest(assert, 'qweb-test-call.xml', {True: true}); + }); + QUnit.test('Foreach', function (assert) { + loadTest(assert, 'qweb-test-foreach.xml'); + }); + QUnit.test('Global', function (assert) { + // test use python syntax + var WORD_REPLACEMENT = window.QWeb2.WORD_REPLACEMENT; + window.QWeb2.WORD_REPLACEMENT = _.extend({not: '!', None: 'undefined'}, WORD_REPLACEMENT); + loadTest(assert, 'qweb-test-global.xml', {bool: function (v) { return !!v ? 'True' : 'False';}}) + .always(function () { + window.QWeb2.WORD_REPLACEMENT = WORD_REPLACEMENT; + }); + }); + QUnit.test('Template inheritance', function (assert) { + loadTest(assert, 'qweb-test-extend.xml'); + }); +}); +}); diff --git a/addons/web/static/tests/services/notification_service_tests.js b/addons/web/static/tests/services/notification_service_tests.js index ac8603ae6f3..195222881c5 100644 --- a/addons/web/static/tests/services/notification_service_tests.js +++ b/addons/web/static/tests/services/notification_service_tests.js @@ -186,7 +186,7 @@ QUnit.module('Services', { var $notification = $('body .o_notification_manager .o_notification'); assert.strictEqual($notification.eq(0).find('.o_close').length, 1, "should display the close button in notification"); assert.strictEqual(_.str.trim($notification.eq(0).html().replace(/\s+/g, ' ')), - "
a0
b0
", + "
a0
b0
", "should display notification"); $notification.find('.o_buttons button:contains(accept0)').click(); diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index dd43d18e79d..222d109be56 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -525,6 +525,8 @@ + +