diff --git a/addons/web/static/lib/qweb/qweb-test-call.xml b/addons/web/static/lib/qweb/qweb-test-call.xml
index b0c5deb9fe6b57658ae1c994bb41b56d915665de..45a65eabce981c46a22fcc1784df9cb24faeb477 100644
--- a/addons/web/static/lib/qweb/qweb-test-call.xml
+++ b/addons/web/static/lib/qweb/qweb-test-call.xml
@@ -57,7 +57,7 @@
     </result>
 
     <t t-name="basic-caller">
-        <t t-call="{{1 and '_basic-callee' or 'other'}}"/>
+        <t t-call="{{True and '_basic-callee' or 'other'}}"/>
     </t>
     <result id="basic-caller">ok</result>
 </templates>
diff --git a/addons/web/static/lib/qweb/qweb-test-global.xml b/addons/web/static/lib/qweb/qweb-test-global.xml
index bfcca8c1eba3ffc3e09f2786dcdd7e943c248593..1db3e5920fb254c973e638c97dfe306e569e74ee 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 @@
             </t>
         </t>
         <t t-call="_callee-asc-toto"/>
-        <t t-set="toto"><t t-set="truc" t-value="'bbb'"/><i t-att-notruc="not truc" t-att-truc="truc and 'ok'">i</i></t>
+        <t t-set="toto"><t t-set="truc" t-value="'bbb'"/><i t-att-notruc="not truc" t-att-truc="bool(truc)">i</i></t>
         <t t-call="_callee-asc-toto"/>
     </t>
 
@@ -49,6 +49,6 @@
         
         <div>toto default</div>
         
-        <div><i truc="ok">i</i></div>
+        <div><i truc="True">i</i></div>
     ]]></result>
 </templates>
diff --git a/addons/web/static/lib/qweb/qweb-test.js.html b/addons/web/static/lib/qweb/qweb-test.js.html
new file mode 100644
index 0000000000000000000000000000000000000000..0ee5f27c3bdb05ddf86ba0952c3bb3b9da69f26e
--- /dev/null
+++ b/addons/web/static/lib/qweb/qweb-test.js.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+<head>
+    <script src="/web/static/lib/jquery/jquery.js"></script>
+    <link rel="stylesheet" href="/web/static/lib/qunit/qunit.css" type="text/css" media="screen"/>
+    <script type="text/javascript" src="/web/static/lib/qunit/qunit.js"></script>
+
+    <script type="text/javascript" src="qweb2.js"></script>
+
+    <script>
+        QWeb = new QWeb2.Engine();
+        function trim(s) {
+            return s.replace(/(^\s+|\s+$)/g, '');
+        }
+        function render(template, context) {
+            return trim(QWeb.render(template, context)).toLowerCase();
+        }
+
+        /**
+         * Loads the template file, and executes all the test template in a
+         * qunit module $title
+         */
+        function test(title, template) {
+            QUnit.module(title, {
+                setup: function () {
+                    var self = this;
+                    this.qweb = new QWeb2.Engine();
+                    QUnit.stop();
+                    this.qweb.add_template(template, function (_, doc) {
+                        self.doc = doc;
+                        QUnit.start();
+                    })
+                }
+            });
+            QUnit.test('autotest', function (assert) {
+                var templates = this.qweb.templates;
+                for (var template in templates) {
+                    if (!templates.hasOwnProperty(template)) { continue; }
+                    // ignore templates whose name starts with _, they're
+                    // helpers/internal
+                    if (/^_/.test(template)) { continue; }
+
+                    var params = this.doc.querySelector('params#' + template);
+                    var args = params ? JSON.parse(params.textContent) : {};
+
+                    var results = this.doc.querySelector('result#' + template);
+                    assert.equal(
+                        trim(this.qweb.render(template, args)),
+                        trim(results.textContent),
+                        template);
+                }
+            });
+        }
+        $(document).ready(function() {
+            test("Output", 'qweb-test-output.xml');
+            test("Context-setting", 'qweb-test-set.xml');
+            test("Conditionals", 'qweb-test-conditionals.xml');
+            test("Attributes manipulation", 'qweb-test-attributes.xml');
+            test("Template calling (to the faraway pages)",
+                 'qweb-test-call.xml');
+            test("Foreach", 'qweb-test-foreach.xml');
+            test("Global", 'qweb-test-global.xml');
+
+            test('Template inheritance', 'qweb-test-extend.xml');
+        });
+    </script>
+
+</head>
+<body>
+  <div id="qunit"></div>
+  <div id="qunit-fixture"></div>
+</body>
+</html>
diff --git a/addons/web/static/lib/qweb/qweb2.js b/addons/web/static/lib/qweb/qweb2.js
index 8058cb7745f645e9b352ac96c9a6b2f020d48336..c4bb7128e9bec9fadd91f3aa4efe38d762ab4883 100644
--- a/addons/web/static/lib/qweb/qweb2.js
+++ b/addons/web/static/lib/qweb/qweb2.js
@@ -28,10 +28,8 @@ 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,js,debug,log'.split(','),
+    ACTIONS_PRECEDENCE: 'foreach,if,elif,else,call,set,esc,raw,js,debug,log'.split(','),
     WORD_REPLACEMENT: {
-        // FUCKING CHM FOR FUCK'S SAKE
-        'not': '!',
         'and': '&&',
         'or': '||',
         'gt': '>',
@@ -78,9 +76,9 @@ var QWeb2 = {
             return s;
         },
         gen_attribute: function(o) {
-            if (o) {
+            if (o !== null && o !== undefined) {
                 if (o.constructor === Array) {
-                    if (o[1]) {
+                    if (o[1] !== null && o[1] !== undefined) {
                         return this.format_attribute(o[0], o[1]);
                     }
                 } else if (typeof o === 'object') {
@@ -720,52 +718,36 @@ QWeb2.Element = (function() {
         },
         compile_element : function() {
             for (var i = 0, ilen = this.engine.actions_precedence.length; i < ilen; i++) {
-                var compiled_action = this.engine.actions_precedence[i];
-                if (compiled_action in this.actions) {
-                    var value = this.actions[compiled_action];
-                    var key = 'compile_action_' + compiled_action;
+                var a = this.engine.actions_precedence[i];
+                if (a in this.actions) {
+                    var value = this.actions[a];
+                    var key = 'compile_action_' + a;
                     if (this[key]) {
                         this[key](value);
                     } else if (this.engine[key]) {
                         this.engine[key].call(this, value);
                     } else {
-                        this.engine.tools.exception("No handler method for action '" + compiled_action + "'");
+                        this.engine.tools.exception("No handler method for action '" + a + "'");
                     }
                 }
             }
-            function print_contents(self) {
-                var expr, esc, raw;
-                if (esc = self.actions['esc']) {
-                    expr = "context.engine.tools.html_escape(" + self.format_str(esc) + ");";
-                } else if(raw = self.actions['raw']) {
-                    expr = self.format_str(raw);
-                }
-                // print non-empty result of t-esc or t-raw, otherwise
-                // print/process body
-                if (expr) {
-                    self.top('var expr_result = ' + expr + ';');
-                    self.top('if (expr_result) {');
-                    self.top('    r.push(expr_result);');
-                    self.top('} else {');
-                    self.bottom('}');
-                }
-             }
             if (this.tag.toLowerCase() !== this.engine.prefix) {
                 var tag = "<" + this.tag;
-                for (var att in this.attributes) {
-                    tag += this.engine.tools.gen_attribute([att, this.attributes[att]]);
+                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 m, v = this.actions[a];
-                    if (a === 'att') {
-                        this.top("r.push(context.engine.tools.gen_attribute(" + (this.format_expression(v)) + "));");
-                    } else if (m = a.match(/att-(.+)/)) {
+                    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)) + ")]));");
-                    } else if (m = a.match(/attf-(.+)/)) {
+                    }
+                    var m = a.match(/attf-(.+)/);
+                    if (m) {
                         this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.string_interpolation(v)) + ")]));");
                     }
                 }
@@ -773,13 +755,11 @@ QWeb2.Element = (function() {
                     // We do not enforce empty content on void elements
                     // because QWeb rendering is not necessarily html.
                     this.top_string("/>");
-                    return; // ensure we skip processing content
                 } else {
                     this.top_string(">");
                     this.bottom_string("</" + this.tag + ">");
                 }
             }
-            print_contents(this);
         },
         compile_action_if : function(value) {
             this.top("if (" + (this.format_expression(value)) + ") {");
@@ -837,6 +817,14 @@ QWeb2.Element = (function() {
                 }
             }
         },
+        compile_action_esc : function(value) {
+            this.top("r.push(context.engine.tools.html_escape("
+                    + this.format_expression(value)
+                    + "));");
+        },
+        compile_action_raw : function(value) {
+            this.top("r.push(" + (this.format_str(value)) + ");");
+        },
         compile_action_js : function(value) {
             this.top("(function(" + value + ") {");
             this.bottom("})(dict);");
diff --git a/addons/web/static/lib/qweb/tests.js b/addons/web/static/lib/qweb/tests.js
deleted file mode 100644
index 5b961acd427bdc0a32a2a458d1b896108d0c786f..0000000000000000000000000000000000000000
--- a/addons/web/static/lib/qweb/tests.js
+++ /dev/null
@@ -1,77 +0,0 @@
-odoo.define('qweb.tests', function () {
-'use strict';
-
-function trim(s) {
-    return s.replace(/(^\s+|\s+$)/g, '');
-}
-
-/**
- * Generates a QUnit.module hook object loading the specified test file
- * (from /web/static/lib/qweb) and setting ``this.qweb`` (the qweb
- * instance for this module) and ``this.doc`` (the loaded XML file).
- *
- * Note that test files mix template elements <t t-name>, params elements
- * <params> and result elements <result>. A result and an optional params
- * object are linked to the corresponding template via the ``id``
- * attribute (result and params have the template name as id).
- */
-function hooks(testfile) {
-    var template = '/web/static/lib/qweb/' + testfile;
-    return {
-        before: function () {
-            var self = this;
-            this.qweb = new QWeb2.Engine();
-            var p = $.Deferred();
-            this.qweb.add_template(template, function (_, doc) {
-                self.doc = doc;
-                p.resolve(doc);
-            });
-            return p.promise();
-        }
-    }
-}
-// can't get generative QUnit.test (e.g. QUnit.test in a for(;;)
-// or Array#forEach() loop) to work, so each test file has a single test,
-// that seems to work
-function runtest() {
-    QUnit.test('', function (assert) {
-    var templates = this.qweb.templates;
-    assert.expect(_.reduce(templates, function (acc, _, k) {
-        return acc + (/^_/.test(k) ? 0 : 1);
-    }, 0));
-    for (var template in templates) {
-        if (!templates.hasOwnProperty(template)) { continue; }
-        // ignore templates whose name starts with _, they're
-        // helpers/internal
-        if (/^_/.test(template)) { continue; }
-
-        var params = this.doc.querySelector('params#' + template);
-        var args = params ? JSON.parse(params.textContent) : {};
-
-        var results = this.doc.querySelector('result#' + template);
-        assert.equal(
-            trim(this.qweb.render(template, args)),
-            trim(results.textContent),
-            template);
-        }
-    });
-}
-
-var TEMPLATES = [
-    ["Output", 'qweb-test-output.xml'],
-    ["Context-setting", 'qweb-test-set.xml'],
-    ["Conditionals", 'qweb-test-conditionals.xml'],
-    ["Attributes manipulation", 'qweb-test-attributes.xml'],
-    ["Template calling (to the faraway pages)", 'qweb-test-call.xml'],
-    ["Foreach", 'qweb-test-foreach.xml'],
-    ["Global", 'qweb-test-global.xml'],
-    ['Template inheritance', 'qweb-test-extend.xml']
-];
-
-QUnit.module('qweb', {}, function () {
-    TEMPLATES.forEach(function (it) {
-        QUnit.module(it[0], hooks(it[1]), runtest);
-    })
-});
-
-});
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index 605003443d079d346473b9709b18fcefdc5cec29..4f69ed286b33dd399f2b214f584153bc25b0463a 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -448,9 +448,6 @@
                         display: inherit;
                     }
                 </style>
-
-                <script type="text/javascript" src="/web/static/lib/qweb/tests.js"></script>
-
                 <script type="text/javascript" src="/web/static/tests/helpers/test_utils.js"></script>
                 <script type="text/javascript" src="/web/static/tests/helpers/mock_server.js"></script>