diff --git a/openerp/addons/test_assetsbundle/__init__.py b/openerp/addons/test_assetsbundle/__init__.py
new file mode 100644
index 00000000000..ee5959455ad
--- /dev/null
+++ b/openerp/addons/test_assetsbundle/__init__.py
@@ -0,0 +1 @@
+import controllers
diff --git a/openerp/addons/test_assetsbundle/__openerp__.py b/openerp/addons/test_assetsbundle/__openerp__.py
new file mode 100644
index 00000000000..8f4c8577ffa
--- /dev/null
+++ b/openerp/addons/test_assetsbundle/__openerp__.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': 'test-assetsbundle',
+ 'version': '0.1',
+ 'category': 'Tests',
+ 'description': """A module to verify the Assets Bundle mechanism.""",
+ 'author': 'Odoo SA',
+ 'maintainer': 'Odoo SA',
+ 'website': 'https://www.odoo.com',
+ 'depends': ['base'],
+ 'installable': True,
+ 'data': [
+ "views/views.xml",
+ ],
+ 'auto_install': False,
+}
diff --git a/openerp/addons/test_assetsbundle/controllers/__init__.py b/openerp/addons/test_assetsbundle/controllers/__init__.py
new file mode 100644
index 00000000000..8ee9bae18d9
--- /dev/null
+++ b/openerp/addons/test_assetsbundle/controllers/__init__.py
@@ -0,0 +1 @@
+import main
diff --git a/openerp/addons/test_assetsbundle/controllers/main.py b/openerp/addons/test_assetsbundle/controllers/main.py
new file mode 100644
index 00000000000..66b507a8a83
--- /dev/null
+++ b/openerp/addons/test_assetsbundle/controllers/main.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from openerp.http import Controller, request, route
+from openerp import SUPERUSER_ID
+
+
+class TestAssetsBundleController(Controller):
+ @route('/test_assetsbundle/js', type='http', auth='user')
+ def bundle(self):
+ cr, uid, registry, context = request.cr, SUPERUSER_ID, request.registry, request.context
+ bundle_id = request.env.ref('test_assetsbundle.bundle1')
+ extra_view_ids = registry['ir.ui.view'].search(cr, uid, [('inherit_id', '=', bundle_id.id)])
+ context = dict(context, check_view_ids=extra_view_ids)
+ return request.registry['ir.ui.view'].render(cr, uid, 'test_assetsbundle.template1', context=context)
diff --git a/openerp/addons/test_assetsbundle/static/src/css/test_cssfile1.css b/openerp/addons/test_assetsbundle/static/src/css/test_cssfile1.css
new file mode 100644
index 00000000000..e3fd4208c11
--- /dev/null
+++ b/openerp/addons/test_assetsbundle/static/src/css/test_cssfile1.css
@@ -0,0 +1,11 @@
+.rule1 {
+ color: black;
+}
+
+.rule2 {
+ color: yellow;
+}
+
+.rule3 {
+ color: red;
+}
diff --git a/openerp/addons/test_assetsbundle/static/src/css/test_cssfile2.css b/openerp/addons/test_assetsbundle/static/src/css/test_cssfile2.css
new file mode 100644
index 00000000000..a0bb8717d62
--- /dev/null
+++ b/openerp/addons/test_assetsbundle/static/src/css/test_cssfile2.css
@@ -0,0 +1,3 @@
+.rule4 {
+ color: blue;
+}
diff --git a/openerp/addons/test_assetsbundle/static/src/js/test_jsfile1.js b/openerp/addons/test_assetsbundle/static/src/js/test_jsfile1.js
new file mode 100644
index 00000000000..16a62bc21dd
--- /dev/null
+++ b/openerp/addons/test_assetsbundle/static/src/js/test_jsfile1.js
@@ -0,0 +1 @@
+var a = 1;
\ No newline at end of file
diff --git a/openerp/addons/test_assetsbundle/static/src/js/test_jsfile2.js b/openerp/addons/test_assetsbundle/static/src/js/test_jsfile2.js
new file mode 100644
index 00000000000..128f06f57a8
--- /dev/null
+++ b/openerp/addons/test_assetsbundle/static/src/js/test_jsfile2.js
@@ -0,0 +1 @@
+var b = 2;
\ No newline at end of file
diff --git a/openerp/addons/test_assetsbundle/static/src/js/test_jsfile3.js b/openerp/addons/test_assetsbundle/static/src/js/test_jsfile3.js
new file mode 100644
index 00000000000..0d6484825f5
--- /dev/null
+++ b/openerp/addons/test_assetsbundle/static/src/js/test_jsfile3.js
@@ -0,0 +1 @@
+var c = 3;
\ No newline at end of file
diff --git a/openerp/addons/test_assetsbundle/static/src/js/test_jsfile4.js b/openerp/addons/test_assetsbundle/static/src/js/test_jsfile4.js
new file mode 100644
index 00000000000..70978f41211
--- /dev/null
+++ b/openerp/addons/test_assetsbundle/static/src/js/test_jsfile4.js
@@ -0,0 +1 @@
+var d = 4;
\ No newline at end of file
diff --git a/openerp/addons/test_assetsbundle/tests/__init__.py b/openerp/addons/test_assetsbundle/tests/__init__.py
new file mode 100644
index 00000000000..91e5899f251
--- /dev/null
+++ b/openerp/addons/test_assetsbundle/tests/__init__.py
@@ -0,0 +1 @@
+import test_assetsbundle
diff --git a/openerp/addons/test_assetsbundle/tests/test_assetsbundle.py b/openerp/addons/test_assetsbundle/tests/test_assetsbundle.py
new file mode 100644
index 00000000000..e1b5a7dd6f2
--- /dev/null
+++ b/openerp/addons/test_assetsbundle/tests/test_assetsbundle.py
@@ -0,0 +1,311 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from openerp.tests import HttpCase
+from openerp.tests.common import TransactionCase
+from openerp.addons.base.ir.ir_qweb import AssetsBundle
+from openerp.modules.module import get_resource_path
+
+from os import utime
+
+
+class TestJavascriptAssetsBundle(TransactionCase):
+ def setUp(self):
+ super(TestJavascriptAssetsBundle, self).setUp()
+ self.jsbundle_xmlid = 'test_assetsbundle.bundle1'
+ self.cssbundle_xmlid = 'test_assetsbundle.bundle2'
+
+ def _any_ira_for_bundle(self, type):
+ """ Returns all ir.attachments associated to a bundle, regardless of the verion.
+ """
+ bundle = self.jsbundle_xmlid if type == 'js' else self.cssbundle_xmlid
+ return self.registry['ir.attachment'].search(self.cr, self.uid,[
+ ('url', '=like', '/web/content/%-%/{0}%.{1}'.format(bundle, type))
+ ])
+
+ def test_01_generation(self):
+ """ Checks that a bundle creates an ir.attachment record when its `js` method is called
+ for the first time.
+ """
+ self.bundle = AssetsBundle(self.jsbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry)
+
+ # there shouldn't be any attachment associated to this bundle
+ self.assertEquals(len(self._any_ira_for_bundle('js')), 0)
+ self.assertEquals(len(self.bundle.get_attachments('js')), 0)
+
+ # trigger the first generation and, thus, the first save in database
+ self.bundle.js()
+
+ # there should be one attachment associated to this bundle
+ self.assertEquals(len(self._any_ira_for_bundle('js')), 1)
+ self.assertEquals(len(self.bundle.get_attachments('js')), 1)
+
+ def test_02_access(self):
+ """ Checks that the bundle's cache is working, i.e. that the bundle creates only one
+ ir.attachment record when rendered multiple times.
+ """
+ bundle0 = AssetsBundle(self.jsbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry)
+ bundle0.js()
+
+ self.assertEquals(len(self._any_ira_for_bundle('js')), 1)
+
+ version0 = bundle0.version
+ ira0 = self.registry['ir.attachment'].browse(self.cr, self.uid, self._any_ira_for_bundle('js')[0])
+ date0 = ira0.create_date
+
+ bundle1 = AssetsBundle(self.jsbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry)
+ bundle1.js()
+
+ self.assertEquals(len(self._any_ira_for_bundle('js')), 1)
+
+ version1 = bundle1.version
+ ira1 = self.registry['ir.attachment'].browse(self.cr, self.uid, self._any_ira_for_bundle('js')[0])
+ date1 = ira1.create_date
+
+ self.assertEquals(version0, version1)
+ self.assertEquals(date0, date1)
+
+ def test_03_date_invalidation(self):
+ """ Checks that a bundle is invalidated when one of its assets' modification date is changed.
+ """
+ bundle0 = AssetsBundle(self.jsbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry)
+ bundle0.js()
+ last_modified0 = bundle0.last_modified
+ version0 = bundle0.version
+
+ path = get_resource_path('test_assetsbundle', 'static', 'src', 'js', 'test_jsfile1.js')
+ utime(path, None) # touch
+
+ bundle1 = AssetsBundle(self.jsbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry)
+ bundle1.js()
+ last_modified1 = bundle1.last_modified
+ version1 = bundle1.version
+ self.assertNotEquals(last_modified0, last_modified1)
+ self.assertNotEquals(version0, version1)
+
+ # check if the previous attachment is correctly cleaned
+ self.assertEquals(len(self._any_ira_for_bundle('js')), 1)
+
+ def test_04_content_invalidation(self):
+ """ Checks that a bundle is invalidated when its content is modified by adding a file to
+ source.
+ """
+ bundle0 = AssetsBundle(self.jsbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry)
+ bundle0.js()
+ html0 = bundle0.html
+ version0 = bundle0.version
+
+ self.assertEquals(len(self._any_ira_for_bundle('js')), 1)
+
+ view_arch = """
+
+
+
+
+
+ """
+ bundle_id = self.browse_ref(self.jsbundle_xmlid).id
+ newid = self.registry['ir.ui.view'].create(self.cr, self.uid, {
+ 'name': 'test bundle inheritance',
+ 'type': 'qweb',
+ 'arch': view_arch,
+ 'inherit_id': bundle_id,
+ })
+
+ bundle1 = AssetsBundle(self.jsbundle_xmlid, cr=self.cr, uid=self.uid, context={'check_view_ids': [newid]}, registry=self.registry)
+ bundle1.js()
+ html1 = bundle1.html
+ version1 = bundle1.version
+
+ self.assertNotEquals(html0, html1)
+ self.assertNotEquals(version0, version1)
+
+ # check if the previous attachment are correctly cleaned
+ self.assertEquals(len(self._any_ira_for_bundle('js')), 1)
+
+ def test_05_debug(self):
+ """ Checks that a bundle rendered in debug mode outputs non-minified assets.
+ """
+ debug_bundle = AssetsBundle(self.jsbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry)
+ content = debug_bundle.to_html(debug=True)
+ # find back one of the original asset file
+ self.assertIn('/test_assetsbundle/static/src/js/test_jsfile1.js', content)
+
+ # there shouldn't be any assets created in debug mode
+ self.assertEquals(len(self._any_ira_for_bundle('js')), 0)
+
+ def test_06_paginated_css_generation1(self):
+ """ Checks that a bundle creates enough ir.attachment records when its `css` method is called
+ for the first time while the number of css rules exceed the limit.
+ """
+ # note: changing the max_css_rules of a bundle does not invalidate its attachments
+ # self.cssbundle_xlmid contains 3 rules
+ self.bundle = AssetsBundle(self.cssbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry, max_css_rules=1)
+ self.bundle.css()
+ self.assertEquals(len(self._any_ira_for_bundle('css')), 3)
+ self.assertEquals(len(self.bundle.get_attachments('css')), 3)
+
+ def test_07_paginated_css_generation2(self):
+ # self.cssbundle_xlmid contains 3 rules
+ self.bundle = AssetsBundle(self.cssbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry, max_css_rules=2)
+ self.bundle.css()
+ self.assertEquals(len(self._any_ira_for_bundle('css')), 2)
+ self.assertEquals(len(self.bundle.get_attachments('css')), 2)
+
+ def test_08_paginated_css_generation3(self):
+ # self.cssbundle_xlmid contains 3 rules
+ self.bundle = AssetsBundle(self.cssbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry, max_css_rules=3)
+ self.bundle.css()
+ self.assertEquals(len(self._any_ira_for_bundle('css')), 1)
+ self.assertEquals(len(self.bundle.get_attachments('css')), 1)
+
+ def test_09_paginated_css_access(self):
+ """ Checks that the bundle's cache is working, i.e. that a bundle creates only enough
+ ir.attachment records when rendered multiple times.
+ """
+ bundle0 = AssetsBundle(self.cssbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry, max_css_rules=1)
+ bundle0.css()
+
+ self.assertEquals(len(self._any_ira_for_bundle('css')), 3)
+
+ version0 = bundle0.version
+ ira0 = self.registry['ir.attachment'].browse(self.cr, self.uid, self._any_ira_for_bundle('css')[0])
+ date0 = ira0.create_date
+ ira1 = self.registry['ir.attachment'].browse(self.cr, self.uid, self._any_ira_for_bundle('css')[1])
+ date1 = ira1.create_date
+ ira2 = self.registry['ir.attachment'].browse(self.cr, self.uid, self._any_ira_for_bundle('css')[2])
+ date2 = ira2.create_date
+
+ bundle1 = AssetsBundle(self.cssbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry, max_css_rules=1)
+ bundle1.css()
+
+ self.assertEquals(len(self._any_ira_for_bundle('css')), 3)
+
+ version1 = bundle1.version
+ ira3 = self.registry['ir.attachment'].browse(self.cr, self.uid, self._any_ira_for_bundle('css')[0])
+ date3 = ira1.create_date
+ ira4 = self.registry['ir.attachment'].browse(self.cr, self.uid, self._any_ira_for_bundle('css')[1])
+ date4 = ira1.create_date
+ ira5 = self.registry['ir.attachment'].browse(self.cr, self.uid, self._any_ira_for_bundle('css')[2])
+ date5 = ira1.create_date
+
+ self.assertEquals(version0, version1)
+ self.assertEquals(date0, date3)
+ self.assertEquals(date1, date4)
+ self.assertEquals(date2, date5)
+
+ def test_10_paginated_css_date_invalidation(self):
+ """ Checks that a bundle is invalidated when one of its assets' modification date is changed.
+ """
+ bundle0 = AssetsBundle(self.cssbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry, max_css_rules=1)
+ bundle0.css()
+ last_modified0 = bundle0.last_modified
+ version0 = bundle0.version
+
+ path = get_resource_path('test_assetsbundle', 'static', 'src', 'css', 'test_cssfile1.css')
+ utime(path, None) # touch
+
+ bundle1 = AssetsBundle(self.cssbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry, max_css_rules=1)
+ bundle1.css()
+ last_modified1 = bundle1.last_modified
+ version1 = bundle1.version
+
+ self.assertNotEquals(last_modified0, last_modified1)
+ self.assertNotEquals(version0, version1)
+
+ # check if the previous attachment is correctly cleaned
+ self.assertEquals(len(self._any_ira_for_bundle('css')), 3)
+
+ def test_11_paginated_css_content_invalidation(self):
+ """ Checks that a bundle is invalidated when its content is modified by adding a file to
+ source.
+ """
+ bundle0 = AssetsBundle(self.cssbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry, max_css_rules=1)
+ bundle0.css()
+ html0 = bundle0.html
+ version0 = bundle0.version
+
+ self.assertEquals(len(self._any_ira_for_bundle('css')), 3)
+
+ view_arch = """
+
+
+
+
+
+ """
+ bundle_id = self.browse_ref(self.cssbundle_xmlid).id
+ newid = self.registry['ir.ui.view'].create(self.cr, self.uid, {
+ 'name': 'test bundle inheritance',
+ 'type': 'qweb',
+ 'arch': view_arch,
+ 'inherit_id': bundle_id,
+ })
+
+ bundle1 = AssetsBundle(self.cssbundle_xmlid, cr=self.cr, uid=self.uid, context={'check_view_ids': [newid]}, registry=self.registry, max_css_rules=1)
+ bundle1.css()
+ html1 = bundle1.html
+ version1 = bundle1.version
+
+ self.assertNotEquals(html0, html1)
+ self.assertNotEquals(version0, version1)
+
+ # check if the previous attachment are correctly cleaned
+ self.assertEquals(len(self._any_ira_for_bundle('css')), 4)
+
+ def test_12_paginated_css_debug(self):
+ """ Check that a bundle in debug mode outputs non-minified assets.
+ """
+ debug_bundle = AssetsBundle(self.cssbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry, max_css_rules=1)
+ content = debug_bundle.to_html(debug=True)
+ # find back one of the original asset file
+ self.assertIn('/test_assetsbundle/static/src/css/test_cssfile1.css', content)
+
+ # there shouldn't be any assets created in debug mode
+ self.assertEquals(len(self._any_ira_for_bundle('css')), 0)
+
+ def test_13_paginated_css_order(self):
+ # self.cssbundle_xlmid contains 3 rules
+ self.bundle = AssetsBundle(self.cssbundle_xmlid, cr=self.cr, uid=self.uid, context={}, registry=self.registry, max_css_rules=1)
+ stylesheets = self.bundle.css()
+
+ self.assertTrue(stylesheets[0].url.endswith('.0.css'))
+ self.assertTrue(stylesheets[1].url.endswith('.1.css'))
+ self.assertTrue(stylesheets[2].url.endswith('.2.css'))
+
+
+class TestAssetsBundleInBrowser(HttpCase):
+ def test_01_js_interpretation(self):
+ """ Checks that the javascript of a bundle is correctly interpreted.
+ """
+ self.phantom_js(
+ "/test_assetsbundle/js",
+ "a + b + c === 6 ? console.log('ok') : console.log('error')",
+ login="admin"
+ )
+
+ def test_02_js_interpretation_inline(self):
+ """ Checks that the javascript of a bundle is correctly interpretet when mixed with inline.
+ """
+ with self.registry.cursor() as test_cursor:
+ view_arch = """
+
+
+
+
+
+ """
+ self.registry['ir.ui.view'].create(test_cursor, self.uid, {
+ 'name': 'test bundle inheritance inline js',
+ 'type': 'qweb',
+ 'arch': view_arch,
+ 'inherit_id': self.browse_ref('test_assetsbundle.bundle1').id,
+ })
+
+ self.phantom_js(
+ "/test_assetsbundle/js",
+ "a + b + c + d === 10 ? console.log('ok') : console.log('error')",
+ login="admin",
+ )
diff --git a/openerp/addons/test_assetsbundle/views/views.xml b/openerp/addons/test_assetsbundle/views/views.xml
new file mode 100644
index 00000000000..4f382bfdd74
--- /dev/null
+++ b/openerp/addons/test_assetsbundle/views/views.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+<!DOCTYPE html>
+
+
+
+
+
+
+
+
+
+
+
+
+