summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Morel <xmo@openerp.com>2012-11-16 12:15:13 +0100
committerXavier Morel <xmo@openerp.com>2012-11-16 12:15:13 +0100
commit04efcd997f4459a6582c3b8a3f2b596237f55380 (patch)
treecb667df3eac0ece239094bc8f419784dadb6a096
parente0f98596419442acbcb633b7a788397f10340ec7 (diff)
parentdca7adbc74ad558f05e8dda0805cb7262a4b8a9a (diff)
[MERGE] JS test framework + python runner
bzr revid: xmo@openerp.com-20121116111513-kddq4tohsgaj3hub
-rw-r--r--.bzrignore31
-rw-r--r--addons/web/__openerp__.py16
-rw-r--r--addons/web/controllers/__init__.py1
-rw-r--r--addons/web/controllers/main.py16
-rw-r--r--addons/web/controllers/testing.py162
-rw-r--r--addons/web/doc/async.rst97
-rw-r--r--addons/web/doc/conf.py23
-rw-r--r--addons/web/doc/images/db-query.pngbin0 -> 7142 bytes
-rw-r--r--addons/web/doc/images/runner.pngbin0 -> 3963 bytes
-rw-r--r--addons/web/doc/images/runner2.pngbin0 -> 6807 bytes
-rw-r--r--addons/web/doc/images/tests.pngbin0 -> 65388 bytes
-rw-r--r--addons/web/doc/images/tests2.pngbin0 -> 20114 bytes
-rw-r--r--addons/web/doc/images/tests3.pngbin0 -> 20382 bytes
-rw-r--r--addons/web/doc/index.rst3
-rw-r--r--addons/web/doc/test-report.txt25
-rw-r--r--addons/web/doc/testing.rst693
-rw-r--r--addons/web/http.py2
-rw-r--r--addons/web/static/src/js/boot.js10
-rw-r--r--addons/web/static/src/js/chrome.js2
-rw-r--r--addons/web/static/src/js/testing.js393
-rw-r--r--addons/web/static/src/js/view_list.js2
-rw-r--r--addons/web/static/src/js/views.js5
-rw-r--r--addons/web/static/test/Widget.js158
-rw-r--r--addons/web/static/test/class.js67
-rw-r--r--addons/web/static/test/evals.js39
-rw-r--r--addons/web/static/test/form.js40
-rw-r--r--addons/web/static/test/formats.js203
-rw-r--r--addons/web/static/test/list-editable.js319
-rw-r--r--addons/web/static/test/list-utils.js223
-rw-r--r--addons/web/static/test/list.js51
-rw-r--r--addons/web/static/test/registry.js77
-rw-r--r--addons/web/static/test/rpc.js48
-rw-r--r--addons/web/static/test/search.js657
-rw-r--r--addons/web/static/test/test.html63
-rw-r--r--addons/web/static/test/testing.js331
-rw-r--r--addons/web/test_support/__init__.py37
-rw-r--r--addons/web/test_support/controllers.py42
-rw-r--r--addons/web/tests/__init__.py2
-rw-r--r--addons/web/tests/test_js.py25
-rw-r--r--addons/web_graph/static/lib/flotr2/lib/bean.js4
-rw-r--r--addons/web_tests_demo/__init__.py14
-rw-r--r--addons/web_tests_demo/__openerp__.py8
-rw-r--r--addons/web_tests_demo/static/src/js/demo.js11
-rw-r--r--addons/web_tests_demo/static/src/xml/demo.xml7
-rw-r--r--addons/web_tests_demo/static/test/demo.js102
45 files changed, 2670 insertions, 1339 deletions
diff --git a/.bzrignore b/.bzrignore
index 8531ad42bbb..1e0564801ab 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -1,19 +1,14 @@
-.*.swp
-.bzrignore
-.idea
-.project
-.pydevproject
-.ropeproject
-.settings
-.DS_Store
-openerp/addons/*
-openerp/filestore*
-.Python
-*.pyc
-*.pyo
-bin/*
+.*
+*.egg-info
+*.orig
+*.vim
build/
-include/
-lib/
-share/
-doc/_build/*
+RE:^bin/
+RE:^dist/
+RE:^include/
+
+RE:^share/
+RE:^man/
+RE:^lib/
+
+RE:^addons/\w+/doc/_build/
diff --git a/addons/web/__openerp__.py b/addons/web/__openerp__.py
index 36e7ad2db27..b9865e25757 100644
--- a/addons/web/__openerp__.py
+++ b/addons/web/__openerp__.py
@@ -41,6 +41,7 @@ This module provides the core of the OpenERP Web Client.
"static/lib/cleditor/jquery.cleditor.js",
"static/lib/py.js/lib/py.js",
"static/src/js/boot.js",
+ "static/src/js/testing.js",
"static/src/js/corelib.js",
"static/src/js/coresetup.js",
"static/src/js/dates.js",
@@ -68,5 +69,20 @@ This module provides the core of the OpenERP Web Client.
'qweb' : [
"static/src/xml/*.xml",
],
+ 'test': [
+ "static/test/testing.js",
+ "static/test/class.js",
+ "static/test/registry.js",
+ "static/test/form.js",
+ "static/test/list-utils.js",
+ "static/test/formats.js",
+ "static/test/rpc.js",
+ "static/test/evals.js",
+ "static/test/search.js",
+ "static/test/Widget.js",
+ "static/test/list.js"
+ "static/test/list-editable.js"
+ ],
'bootstrap': True,
+ 'twitter': False,
}
diff --git a/addons/web/controllers/__init__.py b/addons/web/controllers/__init__.py
index 12a7e529b67..74c27518ece 100644
--- a/addons/web/controllers/__init__.py
+++ b/addons/web/controllers/__init__.py
@@ -1 +1,2 @@
from . import main
+from . import testing
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index cfdad2e3730..de70412b390 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -787,6 +787,17 @@ class Database(openerpweb.Controller):
params['db_name'])
@openerpweb.jsonrequest
+ def duplicate(self, req, fields):
+ params = dict(map(operator.itemgetter('name', 'value'), fields))
+ duplicate_attrs = (
+ params['super_admin_pwd'],
+ params['db_original_name'],
+ params['db_name'],
+ )
+
+ return req.session.proxy("db").duplicate_database(*duplicate_attrs)
+
+ @openerpweb.jsonrequest
def drop(self, req, fields):
password, db = operator.itemgetter(
'drop_pwd', 'drop_db')(
@@ -892,10 +903,7 @@ class Session(openerpweb.Controller):
@openerpweb.jsonrequest
def get_lang_list(self, req):
try:
- return {
- 'lang_list': (req.session.proxy("db").list_lang() or []),
- 'error': ""
- }
+ return req.session.proxy("db").list_lang() or []
except Exception, e:
return {"error": e, "title": "Languages"}
diff --git a/addons/web/controllers/testing.py b/addons/web/controllers/testing.py
new file mode 100644
index 00000000000..3c7c4899b6c
--- /dev/null
+++ b/addons/web/controllers/testing.py
@@ -0,0 +1,162 @@
+# coding=utf-8
+# -*- encoding: utf-8 -*-
+
+import glob
+import itertools
+import json
+import operator
+import os
+
+from mako.template import Template
+from openerp.modules import module
+
+from .main import module_topological_sort
+from ..http import Controller, httprequest
+
+NOMODULE_TEMPLATE = Template(u"""<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>OpenERP Testing</title>
+ </head>
+ <body>
+ <form action="/web/tests" method="GET">
+ <button name="mod" value="*">Run all tests</button>
+ <ul>
+ % for name, module in modules:
+ <li>${name} <button name="mod" value="${module}">
+ Run Tests</button></li>
+ % endfor
+ </ul>
+ </form>
+ </body>
+</html>
+""")
+NOTFOUND = Template(u"""
+<p>Unable to find the module [${module}], please check that the module
+ name is correct and the module is on OpenERP's path.</p>
+<a href="/web/tests">&lt;&lt; Back to tests</a>
+""")
+TESTING = Template(u"""<!DOCTYPE html>
+<html style="height: 100%">
+<%def name="to_path(module, p)">/${module}/${p}</%def>
+<head>
+ <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>OpenERP Web Tests</title>
+ <link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
+
+ <link rel="stylesheet" href="/web/static/lib/qunit/qunit.css">
+ <script src="/web/static/lib/qunit/qunit.js"></script>
+
+ <script type="text/javascript">
+ var oe_db_info = ${db_info};
+ // List of modules, each module is preceded by its dependencies
+ var oe_all_dependencies = ${dependencies};
+ QUnit.config.testTimeout = 5 * 60 * 1000;
+ </script>
+</head>
+<body id="oe" class="openerp">
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+</body>
+% for module, jss, tests, templates in files:
+ % for js in jss:
+ <script src="${to_path(module, js)}"></script>
+ % endfor
+ % if tests or templates:
+ <script>
+ openerp.testing.current_module = "${module}";
+ % for template in templates:
+ openerp.testing.add_template("${to_path(module, template)}");
+ % endfor
+ </script>
+ % endif
+ % if tests:
+ % for test in tests:
+ <script type="text/javascript" src="${to_path(module, test)}"></script>
+ % endfor
+ % endif
+% endfor
+</html>
+""")
+
+class TestRunnerController(Controller):
+ _cp_path = '/web/tests'
+
+ @httprequest
+ def index(self, req, mod=None, **kwargs):
+ ms = module.get_modules()
+ manifests = dict(
+ (name, desc)
+ for name, desc in zip(ms, map(self.load_manifest, ms))
+ if desc # remove not-actually-openerp-modules
+ )
+
+ if not mod:
+ return NOMODULE_TEMPLATE.render(modules=(
+ (manifest['name'], name)
+ for name, manifest in manifests.iteritems()
+ if any(testfile.endswith('.js')
+ for testfile in manifest['test'])
+ ))
+ sorted_mods = module_topological_sort(dict(
+ (name, manifest.get('depends', []))
+ for name, manifest in manifests.iteritems()
+ ))
+ # to_load and to_test should be zippable lists of the same length.
+ # A falsy value in to_test indicate nothing to test at that index (just
+ # load the corresponding part of to_load)
+ to_test = sorted_mods
+ if mod != '*':
+ if mod not in manifests:
+ return req.not_found(NOTFOUND.render(module=mod))
+ idx = sorted_mods.index(mod)
+ to_test = [None] * len(sorted_mods)
+ to_test[idx] = mod
+
+ tests_candicates = [
+ filter(lambda path: path.endswith('.js'),
+ manifests[mod]['test'] if mod else [])
+ for mod in to_test]
+ # remove trailing test-less modules
+ tests = reversed(list(
+ itertools.dropwhile(
+ operator.not_,
+ reversed(tests_candicates))))
+
+ files = [
+ (mod, manifests[mod]['js'], tests, manifests[mod]['qweb'])
+ for mod, tests in itertools.izip(sorted_mods, tests)
+ ]
+
+ # if all three db_info parameters are present, send them to the page
+ db_info = dict((k, v) for k, v in kwargs.iteritems()
+ if k in ['source', 'supadmin', 'password'])
+ if len(db_info) != 3:
+ db_info = None
+
+ return TESTING.render(files=files, dependencies=json.dumps(
+ [name for name in sorted_mods
+ if module.get_module_resource(name, 'static')
+ if manifests[name]['js']]), db_info=json.dumps(db_info))
+
+ def load_manifest(self, name):
+ manifest = module.load_information_from_description_file(name)
+ if manifest:
+ path = module.get_module_path(name)
+ manifest['js'] = list(
+ self.expand_patterns(path, manifest.get('js', [])))
+ manifest['test'] = list(
+ self.expand_patterns(path, manifest.get('test', [])))
+ manifest['qweb'] = list(
+ self.expand_patterns(path, manifest.get('qweb', [])))
+ return manifest
+
+ def expand_patterns(self, root, patterns):
+ for pattern in patterns:
+ normalized_pattern = os.path.normpath(os.path.join(root, pattern))
+ for path in glob.glob(normalized_pattern):
+ # replace OS path separators (from join & normpath) by URI ones
+ yield path[len(root):].replace(os.path.sep, '/')
diff --git a/addons/web/doc/async.rst b/addons/web/doc/async.rst
index 31e6199a6fa..6782fdac029 100644
--- a/addons/web/doc/async.rst
+++ b/addons/web/doc/async.rst
@@ -240,6 +240,103 @@ for instance (to take advantage of :js:func:`when` 's special
treatment of single-value promises).
+jQuery.Deferred API
+~~~~~~~~~~~~~~~~~~~
+
+.. js:function:: when(deferreds…)
+
+ :param deferreds: deferred objects to multiplex
+ :returns: a multiplexed deferred
+ :rtype: :js:class:`Deferred`
+
+.. js:class:: Deferred
+
+ .. js:function:: Deferred.then(doneCallback[, failCallback])
+
+ Attaches new callbacks to the resolution or rejection of the
+ deferred object. Callbacks are executed in the order they are
+ attached to the deferred.
+
+ To provide only a failure callback, pass ``null`` as the
+ ``doneCallback``, to provide only a success callback the
+ second argument can just be ignored (and not passed at all).
+
+ Returns a new deferred which resolves to the result of the
+ corresponding callback, if a callback returns a deferred
+ itself that new deferred will be used as the resolution of the
+ chain.
+
+ :param doneCallback: function called when the deferred is resolved
+ :type doneCallback: Function
+ :param failCallback: function called when the deferred is rejected
+ :type failCallback: Function
+ :returns: the deferred object on which it was called
+ :rtype: :js:class:`Deferred`
+
+ .. js:function:: Deferred.done(doneCallback)
+
+ Attaches a new success callback to the deferred, shortcut for
+ ``deferred.then(doneCallback)``.
+
+ This is a jQuery extension to `CommonJS Promises/A`_ providing
+ little value over calling :js:func:`~Deferred.then` directly,
+ it should be avoided.
+
+ :param doneCallback: function called when the deferred is resolved
+ :type doneCallback: Function
+ :returns: the deferred object on which it was called
+ :rtype: :js:class:`Deferred`
+
+ .. js:function:: Deferred.fail(failCallback)
+
+ Attaches a new failure callback to the deferred, shortcut for
+ ``deferred.then(null, failCallback)``.
+
+ A second jQuery extension to `Promises/A <CommonJS
+ Promises/A>`_. Although it provides more value than
+ :js:func:`~Deferred.done`, it still is not much and should be
+ avoided as well.
+
+ :param failCallback: function called when the deferred is rejected
+ :type failCallback: Function
+ :returns: the deferred object on which it was called
+ :rtype: :js:class:`Deferred`
+
+ .. js:function:: Deferred.promise()
+
+ Returns a read-only view of the deferred object, with all
+ mutators (resolve and reject) methods removed.
+
+ .. js:function:: Deferred.resolve(value…)
+
+ Called to resolve a deferred, any value provided will be
+ passed onto the success handlers of the deferred object.
+
+ Resolving a deferred which has already been resolved or
+ rejected has no effect.
+
+ .. js:function:: Deferred.reject(value…)
+
+ Called to reject (fail) a deferred, any value provided will be
+ passed onto the failure handler of the deferred object.
+
+ Rejecting a deferred which has already been resolved or
+ rejected has no effect.
+
+.. [#] or simply calling :js:class:`Deferred` as a function, the
+ result is the same
+
+.. [#] or not-promises, the `CommonJS Promises/B`_ role of
+ :js:func:`when` is to be able to treat values and promises
+ uniformly: :js:func:`when` will pass promises through directly,
+ but non-promise values and objects will be transformed into a
+ resolved promise (resolving themselves with the value itself).
+
+ jQuery's :js:func:`when` keeps this behavior making deferreds
+ easy to build from "static" values, or allowing defensive code
+ where expected promises are wrapped in :js:func:`when` just in
+ case.
+
.. _promises: http://en.wikipedia.org/wiki/Promise_(programming)
.. _jQuery's deferred: http://api.jquery.com/category/deferred-object/
.. _CommonJS Promises/A: http://wiki.commonjs.org/wiki/Promises/A
diff --git a/addons/web/doc/conf.py b/addons/web/doc/conf.py
index ac5b8907e0e..54434190707 100644
--- a/addons/web/doc/conf.py
+++ b/addons/web/doc/conf.py
@@ -16,10 +16,9 @@ import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
sys.path.append(os.path.abspath('_themes'))
-sys.path.append(os.path.abspath('..'))
-sys.path.append(os.path.abspath('../openerp'))
+sys.path.insert(0, os.path.abspath('../addons'))
+sys.path.insert(0, os.path.abspath('..'))
# -- General configuration -----------------------------------------------------
@@ -43,7 +42,7 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
-project = u'OpenERP Server Developers Documentation'
+project = u'OpenERP Web Developers Documentation'
copyright = u'2012, OpenERP s.a.'
# The version info for the project you're documenting, acts as replacement for
@@ -53,7 +52,7 @@ copyright = u'2012, OpenERP s.a.'
# The short X.Y version.
version = '7.0'
# The full version, including alpha/beta/rc tags.
-release = '7.0b'
+release = '7.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -171,7 +170,7 @@ html_sidebars = {
#html_file_suffix = None
# Output file base name for HTML help builder.
-htmlhelp_basename = 'openerp-server-doc'
+htmlhelp_basename = 'openerp-web-doc'
# -- Options for LaTeX output --------------------------------------------------
@@ -190,7 +189,7 @@ latex_elements = {
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
- ('index', 'openerp-server-doc.tex', u'OpenERP Server Developers Documentation',
+ ('index', 'openerp-web-doc.tex', u'OpenERP Web Developers Documentation',
u'OpenERP s.a.', 'manual'),
]
@@ -220,7 +219,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- ('index', 'openerp-server-doc', u'OpenERP Server Developers Documentation',
+ ('index', 'openerp-web-doc', u'OpenERP Web Developers Documentation',
[u'OpenERP s.a.'], 1)
]
@@ -234,8 +233,8 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
- ('index', 'OpenERPServerDocumentation', u'OpenERP Server Developers Documentation',
- u'OpenERP s.a.', 'OpenERPServerDocumentation', 'Developers documentation for the openobject-server project.',
+ ('index', 'OpenERPWebDocumentation', u'OpenERP Web Developers Documentation',
+ u'OpenERP s.a.', 'OpenERPWebDocumentation', 'Developers documentation for the openerp-web project.',
'Miscellaneous'),
]
@@ -248,10 +247,12 @@ texinfo_documents = [
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
+todo_include_todos = True
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('http://docs.python.org/', None),
- 'openerpweb': ('http://doc.openerp.com/trunk/developers/web', None),
+ 'openerpserver': ('http://doc.openerp.com/trunk/developers/server', None),
'openerpdev': ('http://doc.openerp.com/trunk/developers', None),
+ 'openerpcommand': ('http://doc.openerp.com/trunk/developers/command', None),
}
diff --git a/addons/web/doc/images/db-query.png b/addons/web/doc/images/db-query.png
new file mode 100644
index 00000000000..e063b724001
--- /dev/null
+++ b/addons/web/doc/images/db-query.png
Binary files differ
diff --git a/addons/web/doc/images/runner.png b/addons/web/doc/images/runner.png
new file mode 100644
index 00000000000..bd48e9d2922
--- /dev/null
+++ b/addons/web/doc/images/runner.png
Binary files differ
diff --git a/addons/web/doc/images/runner2.png b/addons/web/doc/images/runner2.png
new file mode 100644
index 00000000000..38ea2949cfc
--- /dev/null
+++ b/addons/web/doc/images/runner2.png
Binary files differ
diff --git a/addons/web/doc/images/tests.png b/addons/web/doc/images/tests.png
new file mode 100644
index 00000000000..84083d9e5ed
--- /dev/null
+++ b/addons/web/doc/images/tests.png
Binary files differ
diff --git a/addons/web/doc/images/tests2.png b/addons/web/doc/images/tests2.png
new file mode 100644
index 00000000000..c8a6f8ae9ee
--- /dev/null
+++ b/addons/web/doc/images/tests2.png
Binary files differ
diff --git a/addons/web/doc/images/tests3.png b/addons/web/doc/images/tests3.png
new file mode 100644
index 00000000000..247f70716af
--- /dev/null
+++ b/addons/web/doc/images/tests3.png
Binary files differ
diff --git a/addons/web/doc/index.rst b/addons/web/doc/index.rst
index 3e436200a8c..06534c50903 100644
--- a/addons/web/doc/index.rst
+++ b/addons/web/doc/index.rst
@@ -14,6 +14,8 @@ Contents:
presentation
async
+ testing
+
widget
qweb
rpc
@@ -24,7 +26,6 @@ Contents:
changelog-7.0
-
Indices and tables
==================
diff --git a/addons/web/doc/test-report.txt b/addons/web/doc/test-report.txt
new file mode 100644
index 00000000000..ce52618fe20
--- /dev/null
+++ b/addons/web/doc/test-report.txt
@@ -0,0 +1,25 @@
+test_empty_find (openerp.addons.web.tests.test_dataset.TestDataSetController) ... ok
+test_ids_shortcut (openerp.addons.web.tests.test_dataset.TestDataSetController) ... ok
+test_regular_find (openerp.addons.web.tests.test_dataset.TestDataSetController) ... ok
+web.testing.stack: direct, value, success ... ok
+web.testing.stack: direct, deferred, success ... ok
+web.testing.stack: direct, value, error ... ok
+web.testing.stack: direct, deferred, failure ... ok
+web.testing.stack: successful setup ... ok
+web.testing.stack: successful teardown ... ok
+web.testing.stack: successful setup and teardown ... ok
+
+[snip ~150 lines]
+
+test_convert_complex_context (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
+test_convert_complex_domain (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
+test_convert_literal_context (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
+test_convert_literal_domain (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
+test_retrieve_nonliteral_context (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
+test_retrieve_nonliteral_domain (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
+
+----------------------------------------------------------------------
+Ran 181 tests in 15.706s
+
+OK
+
diff --git a/addons/web/doc/testing.rst b/addons/web/doc/testing.rst
new file mode 100644
index 00000000000..600d379aa02
--- /dev/null
+++ b/addons/web/doc/testing.rst
@@ -0,0 +1,693 @@
+.. highlight:: javascript
+
+Testing in OpenERP Web
+======================
+
+Javascript Unit Testing
+-----------------------
+
+OpenERP Web 7.0 includes means to unit-test both the core code of
+OpenERP Web and your own javascript modules. On the javascript side,
+unit-testing is based on QUnit_ with a number of helpers and
+extensions for better integration with OpenERP.
+
+To see what the runner looks like, find (or start) an OpenERP server
+with the web client enabled, and navigate to ``/web/tests`` e.g. `on
+OpenERP's CI <http://trunk.runbot.openerp.com/web/tests>`_. This will
+show the runner selector, which lists all modules with javascript unit
+tests, and allows starting any of them (or all javascript tests in all
+modules at once).
+
+.. image:: ./images/runner.png
+ :align: center
+
+Clicking any runner button will launch the corresponding tests in the
+bundled QUnit_ runner:
+
+.. image:: ./images/tests.png
+ :align: center
+
+Writing a test case
+-------------------
+
+The first step is to list the test file(s). This is done through the
+``test`` key of the openerp manifest, by adding javascript files to it
+(next to the usual YAML files, if any):
+
+.. code-block:: python
+
+ {
+ 'name': "Demonstration of web/javascript tests",
+ 'category': 'Hidden',
+ 'depends': ['web'],
+ 'test': ['static/test/demo.js'],
+ }
+
+and to create the corresponding test file(s)
+
+.. note::
+
+ Test files which do not exist will be ignored, if all test files
+ of a module are ignored (can not be found), the test runner will
+ consider that the module has no javascript tests.
+
+After that, refreshing the runner selector will display the new module
+and allow running all of its (0 so far) tests:
+
+.. image:: ./images/runner2.png
+ :align: center
+
+The next step is to create a test case::
+
+ openerp.testing.section('basic section', function (test) {
+ test('my first test', function () {
+ ok(false, "this test has run");
+ });
+ });
+
+All testing helpers and structures live in the ``openerp.testing``
+module. OpenERP tests live in a :js:func:`~openerp.testing.section`,
+which is itself part of a module. The first argument to a section is
+the name of the section, the second one is the section body.
+
+:js:func:`test <openerp.testing.case>`, provided by the
+:js:func:`~openerp.testing.section` to the callback, is used to
+register a given test case which will be run whenever the test runner
+actually does its job. OpenERP Web test case use standard `QUnit
+assertions`_ within them.
+
+Launching the test runner at this point will run the test and display
+the corresponding assertion message, with red colors indicating the
+test failed:
+
+.. image:: ./images/tests2.png
+ :align: center
+
+Fixing the test (by replacing ``false`` to ``true`` in the assertion)
+will make it pass:
+
+.. image:: ./images/tests3.png
+ :align: center
+
+Assertions
+----------
+
+As noted above, OpenERP Web's tests use `qunit assertions`_. They are
+available globally (so they can just be called without references to
+anything). The following list is available:
+
+.. js:function:: ok(state[, message])
+
+ checks that ``state`` is truthy (in the javascript sense)
+
+.. js:function:: strictEqual(actual, expected[, message])
+
+ checks that the actual (produced by a method being tested) and
+ expected values are identical (roughly equivalent to ``ok(actual
+ === expected, message)``)
+
+.. js:function:: notStrictEqual(actual, expected[, message])
+
+ checks that the actual and expected values are *not* identical
+ (roughly equivalent to ``ok(actual !== expected, message)``)
+
+.. js:function:: deepEqual(actual, expected[, message])
+
+ deep comparison between actual and expected: recurse into
+ containers (objects and arrays) to ensure that they have the same
+ keys/number of elements, and the values match.
+
+.. js:function:: notDeepEqual(actual, expected[, message])
+
+ inverse operation to :js:func:`deepEqual`
+
+.. js:function:: throws(block[, expected][, message])
+
+ checks that, when called, the ``block`` throws an
+ error. Optionally validates that error against ``expected``.
+
+ :param Function block:
+ :param expected: if a regexp, checks that the thrown error's
+ message matches the regular expression. If an
+ error type, checks that the thrown error is of
+ that type.
+ :type expected: Error | RegExp
+
+.. js:function:: equal(actual, expected[, message])
+
+ checks that ``actual`` and ``expected`` are loosely equal, using
+ the ``==`` operator and its coercion rules.
+
+.. js:function:: notEqual(actual, expected[, message])
+
+ inverse operation to :js:func:`equal`
+
+Getting an OpenERP instance
+---------------------------
+
+The OpenERP instance is the base through which most OpenERP Web
+modules behaviors (functions, objects, …) are accessed. As a result,
+the test framework automatically builds one, and loads the module
+being tested and all of its dependencies inside it. This new instance
+is provided as the first positional parameter to your test
+cases. Let's observe by adding javascript code (not test code) to the
+test module:
+
+.. code-block:: python
+
+ {
+ 'name': "Demonstration of web/javascript tests",
+ 'category': 'Hidden',
+ 'depends': ['web'],
+ 'js': ['static/src/js/demo.js'],
+ 'test': ['static/test/demo.js'],
+ }
+
+::
+
+ // src/js/demo.js
+ openerp.web_tests_demo = function (instance) {
+ instance.web_tests_demo = {
+ value_true: true,
+ SomeType: instance.web.Class.extend({
+ init: function (value) {
+ this.value = value;
+ }
+ })
+ };
+ };
+
+and then adding a new test case, which simply checks that the
+``instance`` contains all the expected stuff we created in the
+module::
+
+ // test/demo.js
+ test('module content', function (instance) {
+ ok(instance.web_tests_demo.value_true, "should have a true value");
+ var type_instance = new instance.web_tests_demo.SomeType(42);
+ strictEqual(type_instance.value, 42, "should have provided value");
+ });
+
+DOM Scratchpad
+--------------
+
+As in the wider client, arbitrarily accessing document content is
+strongly discouraged during tests. But DOM access is still needed to
+e.g. fully initialize :js:class:`widgets <~openerp.web.Widget>` before
+testing them.
+
+Thus, a test case gets a DOM scratchpad as its second positional
+parameter, in a jQuery instance. That scratchpad is fully cleaned up
+before each test, and as long as it doesn't do anything outside the
+scratchpad your code can do whatever it wants::
+
+ // test/demo.js
+ test('DOM content', function (instance, $scratchpad) {
+ $scratchpad.html('<div><span class="foo bar">ok</span></div>');
+ ok($scratchpad.find('span').hasClass('foo'),
+ "should have provided class");
+ });
+ test('clean scratchpad', function (instance, $scratchpad) {
+ ok(!$scratchpad.children().length, "should have no content");
+ ok(!$scratchpad.text(), "should have no text");
+ });
+
+.. note::
+
+ The top-level element of the scratchpad is not cleaned up, test
+ cases can add text or DOM children but shoud not alter
+ ``$scratchpad`` itself.
+
+Loading templates
+-----------------
+
+To avoid the corresponding processing costs, by default templates are
+not loaded into QWeb. If you need to render e.g. widgets making use of
+QWeb templates, you can request their loading through the
+:js:attr:`~TestOptions.templates` option to the :js:func:`test case
+function <openerp.testing.case>`.
+
+This will automatically load all relevant templates in the instance's
+qweb before running the test case:
+
+.. code-block:: python
+
+ {
+ 'name': "Demonstration of web/javascript tests",
+ 'category': 'Hidden',
+ 'depends': ['web'],
+ 'js': ['static/src/js/demo.js'],
+ 'test': ['static/test/demo.js'],
+ 'qweb': ['static/src/xml/demo.xml'],
+ }
+
+.. code-block:: xml
+
+ <!-- src/xml/demo.xml -->
+ <templates id="template" xml:space="preserve">
+ <t t-name="DemoTemplate">
+ <t t-foreach="5" t-as="value">
+ <p><t t-esc="value"/></p>
+ </t>
+ </t>
+ </templates>
+
+::
+
+ // test/demo.js
+ test('templates', {templates: true}, function (instance) {
+ var s = instance.web.qweb.render('DemoTemplate');
+ var texts = $(s).find('p').map(function () {
+ return $(this).text();
+ }).get();
+
+ deepEqual(texts, ['0', '1', '2', '3', '4']);
+ });
+
+Asynchronous cases
+------------------
+
+The test case examples so far are all synchronous, they execute from
+the first to the last line and once the last line has executed the
+test is done. But the web client is full of :doc:`asynchronous code
+</async>`, and thus test cases need to be async-aware.
+
+This is done by returning a :js:class:`deferred <Deferred>` from the
+case callback::
+
+ // test/demo.js
+ test('asynchronous', {
+ asserts: 1
+ }, function () {
+ var d = $.Deferred();
+ setTimeout(function () {
+ ok(true);
+ d.resolve();
+ }, 100);
+ return d;
+ });
+
+This example also uses the :js:class:`options parameter <TestOptions>`
+to specify the number of assertions the case should expect, if less or
+more assertions are specified the case will count as failed.
+
+Asynchronous test cases *must* specify the number of assertions they
+will run. This allows more easily catching situations where e.g. the
+test architecture was not warned about asynchronous operations.
+
+.. note::
+
+ Asynchronous test cases also have a 2 seconds timeout: if the test
+ does not finish within 2 seconds, it will be considered
+ failed. This pretty much always means the test will not
+ resolve. This timeout *only* applies to the test itself, not to
+ the setup and teardown processes.
+
+.. note::
+
+ If the returned deferred is rejected, the test will be failed
+ unless :js:attr:`~TestOptions.fail_on_rejection` is set to
+ ``false``.
+
+RPC
+---
+
+An important subset of asynchronous test cases is test cases which
+need to perform (and chain, to an extent) RPC calls.
+
+.. note::
+
+ Because they are a subset of asynchronous cases, RPC cases must
+ also provide a valid :js:attr:`assertions count
+ <TestOptions.asserts>`.
+
+By default, test cases will fail when trying to perform an RPC
+call. The ability to perform RPC calls must be explicitly requested by
+a test case (or its containing test suite) through
+:js:attr:`~TestOptions.rpc`, and can be one of two modes: ``mock`` or
+``rpc``.
+
+Mock RPC
+++++++++
+
+The preferred (and fastest from a setup and execution time point of
+view) way to do RPC during tests is to mock the RPC calls: while
+setting up the test case, provide what the RPC responses "should" be,
+and only test the code between the "user" (the test itself) and the
+RPC call, before the call is effectively done.
+
+To do this, set the :js:attr:`rpc option <TestOptions.rpc>` to
+``mock``. This will add a third parameter to the test case callback:
+
+.. js:function:: mock(rpc_spec, handler)
+
+ Can be used in two different ways depending on the shape of the
+ first parameter:
+
+ * If it matches the pattern ``model:method`` (if it contains a
+ colon, essentially) the call will set up the mocking of an RPC
+ call straight to the OpenERP server (through XMLRPC) as
+ performed via e.g. :js:func:`openerp.web.Model.call`.
+
+ In that case, ``handler`` should be a function taking two
+ arguments ``args`` and ``kwargs``, matching the corresponding
+ arguments on the server side and should simply return the value
+ as if it were returned by the Python XMLRPC handler::
+
+ test('XML-RPC', {rpc: 'mock', asserts: 3}, function (instance, $s, mock) {
+ // set up mocking
+ mock('people.famous:name_search', function (args, kwargs) {
+ strictEqual(kwargs.name, 'bob');
+ return [
+ [1, "Microsoft Bob"],
+ [2, "Bob the Builder"],
+ [3, "Silent Bob"]
+ ];
+ });
+
+ // actual test code
+ return new instance.web.Model('people.famous')
+ .call('name_search', {name: 'bob'}).then(function (result) {
+ strictEqual(result.length, 3, "shoud return 3 people");
+ strictEqual(result[0][1], "Microsoft Bob",
+ "the most famous bob should be Microsoft Bob");
+ });
+ });
+
+ * Otherwise, if it matches an absolute path (e.g. ``/a/b/c``) it
+ will mock a JSON-RPC call to a web client controller, such as
+ ``/web/webclient/translations``. In that case, the handler takes
+ a single ``params`` argument holding all of the parameters
+ provided over JSON-RPC.
+
+ As previously, the handler should simply return the result value
+ as if returned by the original JSON-RPC handler::
+
+ test('JSON-RPC', {rpc: 'mock', asserts: 3, templates: true}, function (instance, $s, mock) {
+ var fetched_dbs = false, fetched_langs = false;
+ mock('/web/database/get_list', function () {
+ fetched_dbs = true;
+ return ['foo', 'bar', 'baz'];
+ });
+ mock('/web/session/get_lang_list', function () {
+ fetched_langs = true;
+ return [['vo_IS', 'Hopelandic / Vonlenska']];
+ });
+
+ // widget needs that or it blows up
+ instance.webclient = {toggle_bars: openerp.testing.noop};
+ var dbm = new instance.web.DatabaseManager({});
+ return dbm.appendTo($s).then(function () {
+ ok(fetched_dbs, "should have fetched databases");
+ ok(fetched_langs, "should have fetched languages");
+ deepEqual(dbm.db_list, ['foo', 'bar', 'baz']);
+ });
+ });
+
+.. note::
+
+ Mock handlers can contain assertions, these assertions should be
+ part of the assertions count (and if multiple calls are made to a
+ handler containing assertions, it multiplies the effective number
+ of assertions).
+
+.. _testing-rpc-rpc:
+
+Actual RPC
+++++++++++
+
+A more realistic (but significantly slower and more expensive) way to
+perform RPC calls is to perform actual calls to an actually running
+OpenERP server. To do this, set the :js:attr:`rpc option
+<~TestOptions.rpc>` to ``rpc``, it will not provide any new parameter
+but will enable actual RPC, and the automatic creation and destruction
+of databases (from a specified source) around tests.
+
+First, create a basic model we can test stuff with:
+
+.. code-block:: javascript
+
+ from openerp.osv import orm, fields
+
+ class TestObject(orm.Model):
+ _name = 'web_tests_demo.model'
+
+ _columns = {
+ 'name': fields.char("Name", required=True),
+ 'thing': fields.char("Thing"),
+ 'other': fields.char("Other", required=True)
+ }
+ _defaults = {
+ 'other': "bob"
+ }
+
+then the actual test::
+
+ test('actual RPC', {rpc: 'rpc', asserts: 4}, function (instance) {
+ var Model = new instance.web.Model('web_tests_demo.model');
+ return Model.call('create', [{name: "Bob"}])
+ .then(function (id) {
+ return Model.call('read', [[id]]);
+ }).then(function (records) {
+ strictEqual(records.length, 1);
+ var record = records[0];
+ strictEqual(record.name, "Bob");
+ strictEqual(record.thing, false);
+ // default value
+ strictEqual(record.other, 'bob');
+ });
+ });
+
+This test looks like a "mock" RPC test but for the lack of mock
+response (and the different ``rpc`` type), however it has further
+ranging consequences in that it will copy an existing database to a
+new one, run the test in full on that temporary database and destroy
+the database, to simulate an isolated and transactional context and
+avoid affecting other tests. One of the consequences is that it takes
+a *long* time to run (5~10s, most of that time being spent waiting for
+a database duplication).
+
+Furthermore, as the test needs to clone a database, it also has to ask
+which database to clone, the database/super-admin password and the
+password of the ``admin`` user (in order to authenticate as said
+user). As a result, the first time the test runner encounters an
+``rpc: "rpc"`` test configuration it will produce the following
+prompt:
+
+.. image:: ./images/db-query.png
+ :align: center
+
+and stop the testing process until the necessary information has been
+provided.
+
+The prompt will only appear once per test run, all tests will use the
+same "source" database.
+
+.. note::
+
+ The handling of that information is currently rather brittle and
+ unchecked, incorrect values will likely crash the runner.
+
+.. note::
+
+ The runner does not currently store this information (for any
+ longer than a test run that is), the prompt will have to be filled
+ every time.
+
+Testing API
+-----------
+
+.. js:function:: openerp.testing.section(name[, options], body)
+
+ A test section, serves as shared namespace for related tests (for
+ constants or values to only set up once). The ``body`` function
+ should contain the tests themselves.
+
+ Note that the order in which tests are run is essentially
+ undefined, do *not* rely on it.
+
+ :param String name:
+ :param TestOptions options:
+ :param body:
+ :type body: Function<:js:func:`~openerp.testing.case`, void>
+
+.. js:function:: openerp.testing.case(name[, options], callback)
+
+ Registers a test case callback in the test runner, the callback
+ will only be run once the runner is started (or maybe not at all,
+ if the test is filtered out).
+
+ :param String name:
+ :param TestOptions options:
+ :param callback:
+ :type callback: Function<instance, $, Function<String, Function, void>>
+
+.. js:class:: TestOptions
+
+ the various options which can be passed to
+ :js:func:`~openerp.testing.section` or
+ :js:func:`~openerp.testing.case`. Except for
+ :js:attr:`~TestOptions.setup` and
+ :js:attr:`~TestOptions.teardown`, an option on
+ :js:func:`~openerp.testing.case` will overwrite the corresponding
+ option on :js:func:`~openerp.testing.section` so
+ e.g. :js:attr:`~TestOptions.rpc` can be set for a
+ :js:func:`~openerp.testing.section` and then differently set for
+ some :js:func:`~openerp.testing.case` of that
+ :js:func:`~openerp.testing.section`
+
+ .. js:attribute:: TestOptions.asserts
+
+ An integer, the number of assertions which should run during a
+ normal execution of the test. Mandatory for asynchronous tests.
+
+ .. js:attribute:: TestOptions.setup
+
+ Test case setup, run right before each test case. A section's
+ :js:func:`~TestOptions.setup` is run before the case's own, if
+ both are specified.
+
+ .. js:attribute:: TestOptions.teardown
+
+ Test case teardown, a case's :js:func:`~TestOptions.teardown`
+ is run before the corresponding section if both are present.
+
+ .. js:attribute:: TestOptions.fail_on_rejection
+
+ If the test is asynchronous and its resulting promise is
+ rejected, fail the test. Defaults to ``true``, set to
+ ``false`` to not fail the test in case of rejection::
+
+ // test/demo.js
+ test('unfail rejection', {
+ asserts: 1,
+ fail_on_rejection: false
+ }, function () {
+ var d = $.Deferred();
+ setTimeout(function () {
+ ok(true);
+ d.reject();
+ }, 100);
+ return d;
+ });
+
+ .. js:attribute:: TestOptions.rpc
+
+ RPC method to use during tests, one of ``"mock"`` or
+ ``"rpc"``. Any other value will disable RPC for the test (if
+ they were enabled by the suite for instance).
+
+ .. js:attribute:: TestOptions.templates
+
+ Whether the current module (and its dependencies)'s templates
+ should be loaded into QWeb before starting the test. A
+ boolean, ``false`` by default.
+
+The test runner can also use two global configuration values set
+directly on the ``window`` object:
+
+* ``oe_all_dependencies`` is an ``Array`` of all modules with a web
+ component, ordered by dependency (for a module ``A`` with
+ dependencies ``A'``, any module of ``A'`` must come before ``A`` in
+ the array)
+
+* ``oe_db_info`` is an object with 3 keys ``source``, ``supadmin`` and
+ ``password``. It is used to pre-configure :ref:`actual RPC
+ <testing-rpc-rpc>` tests, to avoid a prompt being displayed
+ (especially for headless situations).
+
+Running through Python
+----------------------
+
+The web client includes the means to run these tests on the
+command-line (or in a CI system), but while actually running it is
+pretty simple the setup of the pre-requisite parts has some
+complexities.
+
+1. Install unittest2_ and QUnitSuite_ in your Python environment. Both
+ can trivially be installed via `pip <http://pip-installer.org>`_ or
+ `easy_install
+ <http://packages.python.org/distribute/easy_install.html>`_.
+
+ The former is the unit-testing framework used by OpenERP, the
+ latter is an adapter module to run qunit_ test suites and convert
+ their result into something unittest2_ can understand and report.
+
+2. Install PhantomJS_. It is a headless
+ browser which allows automating running and testing web
+ pages. QUnitSuite_ uses it to actually run the qunit_ test suite.
+
+ The PhantomJS_ website provides pre-built binaries for some
+ platforms, and your OS's package management probably provides it as
+ well.
+
+ If you're building PhantomJS_ from source, I recommend preparing
+ for some knitting time as it's not exactly fast (it needs to
+ compile both `Qt <http://qt-project.org/>`_ and `Webkit
+ <http://www.webkit.org/>`_, both being pretty big projects).
+
+ .. note::
+
+ Because PhantomJS_ is webkit-based, it will not be able to test
+ if Firefox, Opera or Internet Explorer can correctly run the
+ test suite (and it is only an approximation for Safari and
+ Chrome). It is therefore recommended to *also* run the test
+ suites in actual browsers once in a while.
+
+ .. note::
+
+ The version of PhantomJS_ this was build through is 1.7,
+ previous versions *should* work but are not actually supported
+ (and tend to just segfault when something goes wrong in
+ PhantomJS_ itself so they're a pain to debug).
+
+3. Set up :ref:`OpenERP Command <openerpcommand:openerp-command>`,
+ which will be used to actually run the tests: running the qunit_
+ test suite requires a running server, so at this point OpenERP
+ Server isn't able to do it on its own during the building/testing
+ process.
+
+4. Install a new database with all relevant modules (all modules with
+ a web component at least), then restart the server
+
+ .. note::
+
+ For some tests, a source database needs to be duplicated. This
+ operation requires that there be no connection to the database
+ being duplicated, but OpenERP doesn't currently break
+ existing/outstanding connections, so restarting the server is
+ the simplest way to ensure everything is in the right state.
+
+5. Launch ``oe run-tests -d $DATABASE -mweb`` with the correct
+ addons-path specified (and replacing ``$DATABASE`` by the source
+ database you created above)
+
+ .. note::
+
+ If you leave out ``-mweb``, the runner will attempt to run all
+ the tests in all the modules, which may or may not work.
+
+If everything went correctly, you should now see a list of tests with
+(hopefully) ``ok`` next to their names, closing with a report of the
+number of tests run and the time it took:
+
+.. literalinclude:: test-report.txt
+ :language: text
+
+Congratulation, you have just performed a successful "offline" run of
+the OpenERP Web test suite.
+
+.. note::
+
+ Note that this runs all the Python tests for the ``web`` module,
+ but all the web tests for all of OpenERP. This can be surprising.
+
+.. _qunit: http://qunitjs.com/
+
+.. _qunit assertions: http://api.qunitjs.com/category/assert/
+
+.. _unittest2: http://pypi.python.org/pypi/unittest2
+
+.. _QUnitSuite: http://pypi.python.org/pypi/QUnitSuite/
+
+.. _PhantomJS: http://phantomjs.org/
diff --git a/addons/web/http.py b/addons/web/http.py
index d702a5e5985..46025ae4c62 100644
--- a/addons/web/http.py
+++ b/addons/web/http.py
@@ -561,7 +561,7 @@ class Root(object):
:rtype: ``Controller | None``
"""
if l:
- ps = '/' + '/'.join(l)
+ ps = '/' + '/'.join(filter(None, l))
method_name = 'index'
while ps:
c = controllers_path.get(ps)
diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js
index 05ce709e80a..631a7f8c6f5 100644
--- a/addons/web/static/src/js/boot.js
+++ b/addons/web/static/src/js/boot.js
@@ -19,12 +19,14 @@
/**
* OpenERP instance constructor
*
- * @param {Array} modules list of modules to initialize
+ * @param {Array|String} modules list of modules to initialize
*/
init: function(modules) {
- // By default only web will be loaded, the rest will be by loaded
- // by openerp.web.Session on the first session_authenticate
- modules = _.union(['web'], modules || []);
+ if (modules === "fuck your shit, don't load anything you cunt") {
+ modules = [];
+ } else {
+ modules = _.union(['web'], modules || []);
+ }
var new_instance = {
// links to the global openerp
_openerp: openerp,
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index ca331d6753e..b95550c74a0 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -322,7 +322,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
self.db_list = null;
});
var fetch_langs = this.rpc("/web/session/get_lang_list", {}).done(function(result) {
- self.lang_list = result.lang_list;
+ self.lang_list = result;
});
return $.when(fetch_db, fetch_langs).done(self.do_render);
},
diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js
new file mode 100644
index 00000000000..bf39f543734
--- /dev/null
+++ b/addons/web/static/src/js/testing.js
@@ -0,0 +1,393 @@
+// Test support structures and methods for OpenERP
+openerp.testing = {};
+(function (testing) {
+ var dependencies = {
+ corelib: [],
+ coresetup: ['corelib'],
+ data: ['corelib', 'coresetup'],
+ dates: [],
+ formats: ['coresetup', 'dates'],
+ chrome: ['corelib', 'coresetup'],
+ views: ['corelib', 'coresetup', 'data', 'chrome'],
+ search: ['data', 'coresetup', 'formats'],
+ list: ['views', 'data'],
+ form: ['data', 'views', 'list', 'formats'],
+ list_editable: ['list', 'form', 'data'],
+ };
+
+ testing.dependencies = window['oe_all_dependencies'] || [];
+ testing.current_module = null;
+ testing.templates = { };
+ testing.add_template = function (name) {
+ var xhr = QWeb2.Engine.prototype.get_xhr();
+ xhr.open('GET', name, false);
+ xhr.send(null);
+ (testing.templates[testing.current_module] =
+ testing.templates[testing.current_module] || [])
+ .push(xhr.responseXML);
+ };
+ /**
+ * Function which does not do anything
+ */
+ testing.noop = function () { };
+ /**
+ * Alter provided instance's ``session`` attribute to make response
+ * mockable:
+ *
+ * * The ``responses`` parameter can be used to provide a map of (RPC)
+ * paths (e.g. ``/web/view/load``) to a function returning a response
+ * to the query.
+ * * ``instance.session`` grows a ``responses`` attribute which is
+ * a map of the same (and is in fact initialized to the ``responses``
+ * parameter if one is provided)
+ *
+ * Note that RPC requests to un-mocked URLs will be rejected with an
+ * error message: only explicitly specified urls will get a response.
+ *
+ * Mocked sessions will *never* perform an actual RPC connection.
+ *
+ * @param instance openerp instance being initialized
+ * @param {Object} [responses]
+ */
+ testing.mockifyRPC = function (instance, responses) {
+ var session = instance.session;
+ session.responses = responses || {};
+ session.rpc_function = function (url, payload) {
+ var fn, params;
+ var needle = payload.params.model + ':' + payload.params.method;
+ if (url.url === '/web/dataset/call_kw'
+ && needle in this.responses) {
+ fn = this.responses[needle];
+ params = [
+ payload.params.args || [],
+ payload.params.kwargs || {}
+ ];
+ } else {
+ fn = this.responses[url.url];
+ params = [payload];
+ }
+
+ if (!fn) {
+ return $.Deferred().reject({}, 'failed',
+ _.str.sprintf("Url %s not found in mock responses, with arguments %s",
+ url.url, JSON.stringify(payload.params))
+ ).promise();
+ }
+ try {
+ return $.when(fn.apply(null, params)).then(function (result) {
+ // Wrap for RPC layer unwrapper thingy
+ return {result: result};
+ });
+ } catch (e) {
+ // not sure why this looks like that
+ return $.Deferred().reject({}, 'failed', String(e));
+ }
+ };
+ };
+
+ var StackProto = {
+ execute: function (fn) {
+ var args = [].slice.call(arguments, 1);
+ // Warning: here be dragons
+ var i = 0, setups = this.setups, teardowns = this.teardowns;
+ var d = $.Deferred();
+
+ var succeeded, failed;
+ var success = function () {
+ succeeded = _.toArray(arguments);
+ return teardown();
+ };
+ var failure = function () {
+ // save first failure
+ if (!failed) {
+ failed = _.toArray(arguments);
+ }
+ // chain onto next teardown
+ return teardown();
+ };
+
+ var setup = function () {
+ // if setup to execute
+ if (i < setups.length) {
+ var f = setups[i] || testing.noop;
+ $.when(f.apply(null, args)).then(function () {
+ ++i;
+ setup();
+ }, failure);
+ } else {
+ $.when(fn.apply(null, args)).then(success, failure);
+ }
+ };
+ var teardown = function () {
+ // if teardown to execute
+ if (i > 0) {
+ var f = teardowns[--i] || testing.noop;
+ $.when(f.apply(null, args)).then(teardown, failure);
+ } else {
+ if (failed) {
+ d.reject.apply(d, failed);
+ } else if (succeeded) {
+ d.resolve.apply(d, succeeded);
+ } else {
+ throw new Error("Didn't succeed or fail?");
+ }
+ }
+ };
+ setup();
+
+ return d;
+ },
+ push: function (setup, teardown) {
+ return _.extend(Object.create(StackProto), {
+ setups: this.setups.concat([setup]),
+ teardowns: this.teardowns.concat([teardown])
+ });
+ },
+ unshift: function (setup, teardown) {
+ return _.extend(Object.create(StackProto), {
+ setups: [setup].concat(this.setups),
+ teardowns: [teardown].concat(this.teardowns)
+ });
+ }
+ };
+ /**
+ *
+ * @param {Function} [setup]
+ * @param {Function} [teardown]
+ * @return {*}
+ */
+ testing.Stack = function (setup, teardown) {
+ return _.extend(Object.create(StackProto), {
+ setups: setup ? [setup] : [],
+ teardowns: teardown ? [teardown] : []
+ });
+ };
+
+ var db = window['oe_db_info'];
+ testing.section = function (name, options, body) {
+ if (_.isFunction(options)) {
+ body = options;
+ options = {};
+ }
+ _.defaults(options, {
+ setup: testing.noop,
+ teardown: testing.noop
+ });
+
+ QUnit.module(testing.current_module + '.' + name, {_oe: options});
+ body(testing.case);
+ };
+ testing.case = function (name, options, callback) {
+ if (_.isFunction(options)) {
+ callback = options;
+ options = {};
+ }
+
+ var module = testing.current_module;
+ var module_index = _.indexOf(testing.dependencies, module);
+ var module_deps = testing.dependencies.slice(
+ // If module not in deps (because only tests, no JS) -> indexOf
+ // returns -1 -> index becomes 0 -> replace with ``undefined`` so
+ // Array#slice returns a full copy
+ 0, module_index + 1 || undefined);
+
+ // Serialize options for this precise test case
+ // WARNING: typo is from jquery, do not fix!
+ var env = QUnit.config.currentModuleTestEnviroment;
+ // section setup
+ // case setup
+ // test
+ // case teardown
+ // section teardown
+ var case_stack = testing.Stack()
+ .push(env._oe.setup, env._oe.teardown)
+ .push(options.setup, options.teardown);
+ var opts = _.defaults({}, options, env._oe);
+ // FIXME: if this test is ignored, will still query
+ if (opts.rpc === 'rpc' && !db) {
+ QUnit.config.autostart = false;
+ db = {
+ source: null,
+ supadmin: null,
+ password: null
+ };
+ var $msg = $('<form style="margin: 0 1em 1em;">')
+ .append('<h3>A test needs to clone a database</h3>')
+ .append('<h4>Please provide the source clone information</h4>')
+ .append(' Source DB: ').append('<input name="source">').append('<br>')
+ .append(' DB Password: ').append('<input name="supadmin">').append('<br>')
+ .append('Admin Password: ').append('<input name="password">').append('<br>')
+ .append('<input type="submit" value="OK"/>')
+ .submit(function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ db.source = $msg.find('input[name=source]').val();
+ db.supadmin = $msg.find('input[name=supadmin]').val();
+ db.password = $msg.find('input[name=password]').val();
+ QUnit.start();
+ $.unblockUI();
+ });
+ $.blockUI({
+ message: $msg,
+ css: {
+ fontFamily: 'monospace',
+ textAlign: 'left',
+ whiteSpace: 'pre-wrap',
+ cursor: 'default'
+ }
+ });
+ }
+
+ QUnit.test(name, function () {
+ var instance;
+ if (!opts.dependencies) {
+ instance = openerp.init(module_deps);
+ } else {
+ // empty-but-specified dependencies actually allow running
+ // without loading any module into the instance
+
+ // TODO: clean up this mess
+ var d = opts.dependencies.slice();
+ // dependencies list should be in deps order, reverse to make
+ // loading order from last
+ d.reverse();
+ var di = 0;
+ while (di < d.length) {
+ var m = /^web\.(\w+)$/.exec(d[di]);
+ if (m) {
+ d[di] = m[1];
+ }
+ d.splice.apply(d, [di+1, 0].concat(
+ _(dependencies[d[di]]).reverse()));
+ ++di;
+ }
+
+ instance = openerp.init("fuck your shit, don't load anything you cunt");
+ _(d).chain()
+ .reverse()
+ .uniq()
+ .each(function (module) {
+ openerp.web[module](instance);
+ });
+ }
+ if (_.isNumber(opts.asserts)) {
+ expect(opts.asserts);
+ }
+
+ if (opts.templates) {
+ for(var i=0; i<module_deps.length; ++i) {
+ var dep = module_deps[i];
+ var templates = testing.templates[dep];
+ if (_.isEmpty(templates)) { continue; }
+
+ for (var j=0; j < templates.length; ++j) {
+ instance.web.qweb.add_template(templates[j]);
+ }
+ }
+ }
+
+ var $fixture = $('#qunit-fixture');
+
+ var mock, async = false;
+ switch (opts.rpc) {
+ case 'mock':
+ async = true;
+ testing.mockifyRPC(instance);
+ mock = function (spec, handler) {
+ instance.session.responses[spec] = handler;
+ };
+ break;
+ case 'rpc':
+ async = true;
+ (function () {
+ // Bunch of random base36 characters
+ var dbname = 'test_' + Math.random().toString(36).slice(2);
+ // Add db setup/teardown at the start of the stack
+ case_stack = case_stack.unshift(function (instance) {
+ // FIXME hack: don't want the session to go through shitty loading process of everything
+ instance.session.session_init = testing.noop;
+ instance.session.load_modules = testing.noop;
+ instance.session.session_bind();
+ return instance.session.rpc('/web/database/duplicate', {
+ fields: [
+ {name: 'super_admin_pwd', value: db.supadmin},
+ {name: 'db_original_name', value: db.source},
+ {name: 'db_name', value: dbname}
+ ]
+ }).then(function (result) {
+ if (result.error) {
+ return $.Deferred().reject(result.error).promise();
+ }
+ return instance.session.session_authenticate(
+ dbname, 'admin', db.password, true);
+ });
+ }, function (instance) {
+ return instance.session.rpc('/web/database/drop', {
+ fields: [
+ {name: 'drop_pwd', value: db.supadmin},
+ {name: 'drop_db', value: dbname}
+ ]
+ }).then(function (result) {
+ if (result.error) {
+ return $.Deferred().reject(result.error).promise();
+ }
+ return result;
+ });
+ });
+ })();
+ }
+
+ // Always execute tests asynchronously
+ stop();
+ var timeout;
+ case_stack.execute(function () {
+ var result = callback.apply(null, arguments);
+ if (!(result && _.isFunction(result.then))) {
+ if (async) {
+ ok(false, "asynchronous test cases must return a promise");
+ }
+ } else {
+ if (!_.isNumber(opts.asserts)) {
+ ok(false, "asynchronous test cases must specify the "
+ + "number of assertions they expect");
+ }
+ }
+
+ return $.Deferred(function (d) {
+ $.when(result).then(function () {
+ d.resolve.apply(d, arguments)
+ }, function () {
+ d.reject.apply(d, arguments);
+ });
+ if (async || (result && result.then)) {
+ // async test can be either implicit async (rpc) or
+ // promise-returning
+ timeout = setTimeout(function () {
+ QUnit.config.semaphore = 1;
+ d.reject({message: "Test timed out"});
+ }, 2000);
+ }
+ });
+ }, instance, $fixture, mock).always(function () {
+ if (timeout) { clearTimeout(timeout); }
+ start();
+ }).fail(function (error) {
+ if (options.fail_on_rejection === false) {
+ return;
+ }
+ var message;
+ if (typeof error !== 'object'
+ || typeof error.message !== 'string') {
+ message = JSON.stringify([].slice.apply(arguments));
+ } else {
+ message = error.message;
+ if (error.data && error.data.debug) {
+ message += '\n\n' + error.data.debug;
+ }
+ }
+
+ ok(false, message);
+ });
+ });
+ };
+})(openerp.testing);
diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js
index db8c962efc7..df74674a343 100644
--- a/addons/web/static/src/js/view_list.js
+++ b/addons/web/static/src/js/view_list.js
@@ -1,6 +1,6 @@
openerp.web.list = function (instance) {
var _t = instance.web._t,
- _lt = instance.web._lt;
+ _lt = instance.web._lt;
var QWeb = instance.web.qweb;
instance.web.views.add('list', 'instance.web.ListView');
instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListView# */ {
diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js
index d804bd57cf8..641821152b2 100644
--- a/addons/web/static/src/js/views.js
+++ b/addons/web/static/src/js/views.js
@@ -25,7 +25,6 @@ instance.web.ActionManager = instance.web.Widget.extend({
dialog_stop: function () {
if (this.dialog) {
this.dialog.destroy();
- this.dialog = null;
}
},
/**
@@ -790,8 +789,8 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
name: "JS Tests",
target: 'new',
type : 'ir.actions.act_url',
- url: '/web/static/test/test.html'
- })
+ url: '/web/tests?mod=*'
+ });
break;
case 'perm_read':
var ids = current_view.get_selected_ids();
diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/Widget.js
index 3e6cc262565..44b7cfe0dfa 100644
--- a/addons/web/static/test/Widget.js
+++ b/addons/web/static/test/Widget.js
@@ -1,32 +1,7 @@
-$(document).ready(function () {
- var $fix = $('#qunit-fixture');
- var mod = {
- setup: function () {
- instance = window.openerp.init([]);
- window.openerp.web.corelib(instance);
-
- instance.web.qweb = new QWeb2.Engine();
- instance.web.qweb.add_template(
- '<no>' +
- '<t t-name="test.widget.template">' +
- '<ol>' +
- '<li t-foreach="5" t-as="counter" ' +
- 't-attf-class="class-#{counter}">' +
- '<input/>' +
- '<t t-esc="counter"/>' +
- '</li>' +
- '</ol>' +
- '</t>' +
- '<t t-name="test.widget.template-value">' +
- '<p><t t-esc="widget.value"/></p>' +
- '</t>' +
- '</no>');
- }
- };
- var instance;
-
- module('Widget.proxy', mod);
- test('(String)', function () {
+openerp.testing.section('Widget.proxy', {
+ dependencies: ['web.corelib']
+}, function (test) {
+ test('(String)', function (instance) {
var W = instance.web.Widget.extend({
exec: function () {
this.executed = true;
@@ -37,7 +12,7 @@ $(document).ready(function () {
fn();
ok(w.executed, 'should execute the named method in the right context');
});
- test('(String)(*args)', function () {
+ test('(String)(*args)', function (instance) {
var W = instance.web.Widget.extend({
exec: function (arg) {
this.executed = arg;
@@ -49,7 +24,7 @@ $(document).ready(function () {
ok(w.executed, "should execute the named method in the right context");
equal(w.executed, 42, "should be passed the proxy's arguments");
});
- test('(String), include', function () {
+ test('(String), include', function (instance) {
// the proxy function should handle methods being changed on the class
// and should always proxy "by name", to the most recent one
var W = instance.web.Widget.extend({
@@ -67,23 +42,43 @@ $(document).ready(function () {
equal(w.executed, 2, "should be lazily resolved");
});
- test('(Function)', function () {
+ test('(Function)', function (instance) {
var w = new (instance.web.Widget.extend({ }));
var fn = w.proxy(function () { this.executed = true; });
fn();
ok(w.executed, "should set the function's context (like Function#bind)");
});
- test('(Function)(*args)', function () {
+ test('(Function)(*args)', function (instance) {
var w = new (instance.web.Widget.extend({ }));
var fn = w.proxy(function (arg) { this.executed = arg; });
fn(42);
equal(w.executed, 42, "should be passed the proxy's arguments");
});
-
- module('Widget.renderElement', mod);
- test('no template, default', function () {
+});
+openerp.testing.section('Widget.renderElement', {
+ dependencies: ['web.corelib'],
+ setup: function (instance) {
+ instance.web.qweb = new QWeb2.Engine();
+ instance.web.qweb.add_template(
+ '<no>' +
+ '<t t-name="test.widget.template">' +
+ '<ol>' +
+ '<li t-foreach="5" t-as="counter" ' +
+ 't-attf-class="class-#{counter}">' +
+ '<input/>' +
+ '<t t-esc="counter"/>' +
+ '</li>' +
+ '</ol>' +
+ '</t>' +
+ '<t t-name="test.widget.template-value">' +
+ '<p><t t-esc="widget.value"/></p>' +
+ '</t>' +
+ '</no>');
+ }
+}, function (test) {
+ test('no template, default', function (instance) {
var w = new (instance.web.Widget.extend({ }));
var $original = w.$el;
@@ -98,7 +93,7 @@ $(document).ready(function () {
equal(w.el.attributes.length, 0, "should not have generated any attribute");
ok(_.isEmpty(w.$el.html(), "should not have generated any content"));
});
- test('no template, custom tag', function () {
+ test('no template, custom tag', function (instance) {
var w = new (instance.web.Widget.extend({
tagName: 'ul'
}));
@@ -106,7 +101,7 @@ $(document).ready(function () {
equal(w.el.nodeName, 'UL', "should have generated the custom element tag");
});
- test('no template, @id', function () {
+ test('no template, @id', function (instance) {
var w = new (instance.web.Widget.extend({
id: 'foo'
}));
@@ -116,7 +111,7 @@ $(document).ready(function () {
equal(w.$el.attr('id'), 'foo', "should have generated the id attribute");
equal(w.el.id, 'foo', "should also be available via property");
});
- test('no template, @className', function () {
+ test('no template, @className', function (instance) {
var w = new (instance.web.Widget.extend({
className: 'oe_some_class'
}));
@@ -125,7 +120,7 @@ $(document).ready(function () {
equal(w.el.className, 'oe_some_class', "should have the right property");
equal(w.$el.attr('class'), 'oe_some_class', "should have the right attribute");
});
- test('no template, bunch of attributes', function () {
+ test('no template, bunch of attributes', function (instance) {
var w = new (instance.web.Widget.extend({
attributes: {
'id': 'some_id',
@@ -152,7 +147,7 @@ $(document).ready(function () {
equal(w.$el.attr('spoiler'), 'snape kills dumbledore');
});
- test('template', function () {
+ test('template', function (instance) {
var w = new (instance.web.Widget.extend({
template: 'test.widget.template'
}));
@@ -162,9 +157,41 @@ $(document).ready(function () {
equal(w.$el.children().length, 5);
equal(w.el.textContent, '01234');
});
-
- module('Widget.$', mod);
- test('basic-alias', function () {
+ test('repeated', { asserts: 4 }, function (instance, $fix) {
+ var w = new (instance.web.Widget.extend({
+ template: 'test.widget.template-value'
+ }));
+ w.value = 42;
+ return w.appendTo($fix)
+ .done(function () {
+ equal($fix.find('p').text(), '42', "DOM fixture should contain initial value");
+ equal(w.$el.text(), '42', "should set initial value");
+ w.value = 36;
+ w.renderElement();
+ equal($fix.find('p').text(), '36', "DOM fixture should use new value");
+ equal(w.$el.text(), '36', "should set new value");
+ });
+ });
+});
+openerp.testing.section('Widget.$', {
+ dependencies: ['web.corelib'],
+ setup: function (instance) {
+ instance.web.qweb = new QWeb2.Engine();
+ instance.web.qweb.add_template(
+ '<no>' +
+ '<t t-name="test.widget.template">' +
+ '<ol>' +
+ '<li t-foreach="5" t-as="counter" ' +
+ 't-attf-class="class-#{counter}">' +
+ '<input/>' +
+ '<t t-esc="counter"/>' +
+ '</li>' +
+ '</ol>' +
+ '</t>' +
+ '</no>');
+ }
+}, function (test) {
+ test('basic-alias', function (instance) {
var w = new (instance.web.Widget.extend({
template: 'test.widget.template'
}));
@@ -173,9 +200,26 @@ $(document).ready(function () {
ok(w.$('li:eq(3)').is(w.$el.find('li:eq(3)')),
"should do the same thing as calling find on the widget root");
});
-
- module('Widget.events', mod);
- test('delegate', function () {
+});
+openerp.testing.section('Widget.events', {
+ dependencies: ['web.corelib'],
+ setup: function (instance) {
+ instance.web.qweb = new QWeb2.Engine();
+ instance.web.qweb.add_template(
+ '<no>' +
+ '<t t-name="test.widget.template">' +
+ '<ol>' +
+ '<li t-foreach="5" t-as="counter" ' +
+ 't-attf-class="class-#{counter}">' +
+ '<input/>' +
+ '<t t-esc="counter"/>' +
+ '</li>' +
+ '</ol>' +
+ '</t>' +
+ '</no>');
+ }
+}, function (test) {
+ test('delegate', function (instance) {
var a = [];
var w = new (instance.web.Widget.extend({
template: 'test.widget.template',
@@ -199,7 +243,7 @@ $(document).ready(function () {
ok(a[i], "should pass test " + i);
}
});
- test('undelegate', function () {
+ test('undelegate', function (instance) {
var clicked = false, newclicked = false;
var w = new (instance.web.Widget.extend({
template: 'test.widget.template',
@@ -218,22 +262,4 @@ $(document).ready(function () {
ok(!clicked, "undelegate should unbind events delegated");
ok(newclicked, "undelegate should only unbind events it created");
});
-
- module('Widget.renderElement', mod);
- asyncTest('repeated', 4, function () {
- var w = new (instance.web.Widget.extend({
- template: 'test.widget.template-value'
- }));
- w.value = 42;
- w.appendTo($fix)
- .always(start)
- .done(function () {
- equal($fix.find('p').text(), '42', "DOM fixture should contain initial value");
- equal(w.$el.text(), '42', "should set initial value");
- w.value = 36;
- w.renderElement();
- equal($fix.find('p').text(), '36', "DOM fixture should use new value");
- equal(w.$el.text(), '36', "should set new value");
- });
- });
});
diff --git a/addons/web/static/test/class.js b/addons/web/static/test/class.js
index faad4421e31..bb3fb7e12b9 100644
--- a/addons/web/static/test/class.js
+++ b/addons/web/static/test/class.js
@@ -1,30 +1,25 @@
-$(document).ready(function () {
- var openerp;
- module('web-class', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- }
- });
- test('Basic class creation', function () {
- var C = openerp.web.Class.extend({
+openerp.testing.section('class', {
+ dependencies: ['web.corelib']
+}, function (test) {
+ test('Basic class creation', function (instance) {
+ var C = instance.web.Class.extend({
foo: function () {
return this.somevar;
}
});
- var instance = new C();
- instance.somevar = 3;
+ var i = new C();
+ i.somevar = 3;
- ok(instance instanceof C);
- strictEqual(instance.foo(), 3);
+ ok(i instanceof C);
+ strictEqual(i.foo(), 3);
});
- test('Class initialization', function () {
- var C1 = openerp.web.Class.extend({
+ test('Class initialization', function (instance) {
+ var C1 = instance.web.Class.extend({
init: function () {
this.foo = 3;
}
});
- var C2 = openerp.web.Class.extend({
+ var C2 = instance.web.Class.extend({
init: function (arg) {
this.foo = arg;
}
@@ -36,8 +31,8 @@ $(document).ready(function () {
strictEqual(i1.foo, 3);
strictEqual(i2.foo, 42);
});
- test('Inheritance', function () {
- var C0 = openerp.web.Class.extend({
+ test('Inheritance', function (instance) {
+ var C0 = instance.web.Class.extend({
foo: function () {
return 1;
}
@@ -57,8 +52,8 @@ $(document).ready(function () {
strictEqual(new C1().foo(), 2);
strictEqual(new C2().foo(), 3);
});
- test('In-place extension', function () {
- var C0 = openerp.web.Class.extend({
+ test('In-place extension', function (instance) {
+ var C0 = instance.web.Class.extend({
foo: function () {
return 3;
},
@@ -83,8 +78,8 @@ $(document).ready(function () {
strictEqual(new C0().foo(), 5);
strictEqual(new C0().qux(), 5);
});
- test('In-place extension and inheritance', function () {
- var C0 = openerp.web.Class.extend({
+ test('In-place extension and inheritance', function (instance) {
+ var C0 = instance.web.Class.extend({
foo: function () { return 1; },
bar: function () { return 1; }
});
@@ -101,24 +96,24 @@ $(document).ready(function () {
strictEqual(new C1().foo(), 4);
strictEqual(new C1().bar(), 2);
});
- test('In-place extensions alter existing instances', function () {
- var C0 = openerp.web.Class.extend({
+ test('In-place extensions alter existing instances', function (instance) {
+ var C0 = instance.web.Class.extend({
foo: function () { return 1; },
bar: function () { return 1; }
});
- var instance = new C0();
- strictEqual(instance.foo(), 1);
- strictEqual(instance.bar(), 1);
+ var i = new C0();
+ strictEqual(i.foo(), 1);
+ strictEqual(i.bar(), 1);
C0.include({
foo: function () { return 2; },
bar: function () { return 2 + this._super(); }
});
- strictEqual(instance.foo(), 2);
- strictEqual(instance.bar(), 3);
+ strictEqual(i.foo(), 2);
+ strictEqual(i.bar(), 3);
});
- test('In-place extension of subclassed types', function () {
- var C0 = openerp.web.Class.extend({
+ test('In-place extension of subclassed types', function (instance) {
+ var C0 = instance.web.Class.extend({
foo: function () { return 1; },
bar: function () { return 1; }
});
@@ -126,13 +121,13 @@ $(document).ready(function () {
foo: function () { return 1 + this._super(); },
bar: function () { return 1 + this._super(); }
});
- var instance = new C1();
- strictEqual(instance.foo(), 2);
+ var i = new C1();
+ strictEqual(i.foo(), 2);
C0.include({
foo: function () { return 2; },
bar: function () { return 2 + this._super(); }
});
- strictEqual(instance.foo(), 3);
- strictEqual(instance.bar(), 4);
+ strictEqual(i.foo(), 3);
+ strictEqual(i.bar(), 4);
});
});
diff --git a/addons/web/static/test/evals.js b/addons/web/static/test/evals.js
index 757d98c5bcc..6eec615b199 100644
--- a/addons/web/static/test/evals.js
+++ b/addons/web/static/test/evals.js
@@ -1,18 +1,11 @@
-$(document).ready(function () {
- var openerp;
-
- module("eval.contexts", {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- }
- });
- test('context_sequences', function () {
+openerp.testing.section('eval.contexts', {
+ dependencies: ['web.coresetup']
+}, function (test) {
+ test('context_sequences', function (instance) {
// Context n should have base evaluation context + all of contexts
// 0..n-1 in its own evaluation context
var active_id = 4;
- var result = openerp.session.test_eval_contexts([
+ var result = instance.session.test_eval_contexts([
{
"__contexts": [
{
@@ -55,8 +48,8 @@ $(document).ready(function () {
record_id: active_id
});
});
- test('non-literal_eval_contexts', function () {
- var result = openerp.session.test_eval_contexts([{
+ test('non-literal_eval_contexts', function (instance) {
+ var result = instance.session.test_eval_contexts([{
"__ref": "compound_context",
"__contexts": [
{"__ref": "context", "__debug": "{'type':parent.type}",
@@ -133,17 +126,15 @@ $(document).ready(function () {
}]);
deepEqual(result, {type: 'out_invoice'});
});
- module('eval.domains', {
- setup: function () {
- openerp = window.openerp.testing.instanceFor('coresetup');
- window.openerp.web.dates(openerp);
- }
- });
- test('current_date', function () {
- var current_date = openerp.web.date_to_str(new Date());
- var result = openerp.session.test_eval_domains(
+});
+openerp.testing.section('eval.contexts', {
+ dependencies: ['web.coresetup', 'web.dates']
+}, function (test) {
+ test('current_date', function (instance) {
+ var current_date = instance.web.date_to_str(new Date());
+ var result = instance.session.test_eval_domains(
[[],{"__ref":"domain","__debug":"[('name','>=',current_date),('name','<=',current_date)]","__id":"5dedcfc96648"}],
- openerp.session.test_eval_get_context());
+ instance.session.test_eval_get_context());
deepEqual(result, [
['name', '>=', current_date],
['name', '<=', current_date]
diff --git a/addons/web/static/test/form.js b/addons/web/static/test/form.js
index aace926a96a..0c8107ce9bc 100644
--- a/addons/web/static/test/form.js
+++ b/addons/web/static/test/form.js
@@ -1,33 +1,21 @@
-$(document).ready(function () {
- var openerp;
- module("form.widget", {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.chrome(openerp);
- // views loader stuff
- window.openerp.web.data(openerp);
- window.openerp.web.views(openerp);
- window.openerp.web.list(openerp);
- window.openerp.web.form(openerp);
- }
- });
- test("compute_domain", function () {
+openerp.testing.section('compute_domain', {
+ dependencies: ['web.form']
+}, function (test) {
+ test("basic", function (instance) {
var fields = {
'a': {value: 3},
'group_method': {value: 'line'},
'select1': {value: 'day'},
'rrule_type': {value: 'monthly'}
};
- ok(openerp.web.form.compute_domain(
+ ok(instance.web.form.compute_domain(
[['a', '=', 3]], fields));
- ok(openerp.web.form.compute_domain(
+ ok(instance.web.form.compute_domain(
[['group_method','!=','count']], fields));
- ok(openerp.web.form.compute_domain(
+ ok(instance.web.form.compute_domain(
[['select1','=','day'], ['rrule_type','=','monthly']], fields));
});
- test("compute_domain or", function () {
+ test("or", function (instance) {
var web = {
'section_id': {value: null},
'user_id': {value: null},
@@ -38,22 +26,22 @@ $(document).ready(function () {
'|', ['user_id','=',3],
['member_ids', 'in', [3]]];
- ok(openerp.web.form.compute_domain(domain, _.extend(
+ ok(instance.web.form.compute_domain(domain, _.extend(
{}, web, {'section_id': {value: 42}})));
- ok(openerp.web.form.compute_domain(domain, _.extend(
+ ok(instance.web.form.compute_domain(domain, _.extend(
{}, web, {'user_id': {value: 3}})));
- ok(openerp.web.form.compute_domain(domain, _.extend(
+ ok(instance.web.form.compute_domain(domain, _.extend(
{}, web, {'member_ids': {value: 3}})));
});
- test("compute_domain not", function () {
+ test("not", function (instance) {
var fields = {
'a': {value: 5},
'group_method': {value: 'line'}
};
- ok(openerp.web.form.compute_domain(
+ ok(instance.web.form.compute_domain(
['!', ['a', '=', 3]], fields));
- ok(openerp.web.form.compute_domain(
+ ok(instance.web.form.compute_domain(
['!', ['group_method','=','count']], fields));
});
});
diff --git a/addons/web/static/test/formats.js b/addons/web/static/test/formats.js
index 4fe07ff0a9c..4536501d71d 100644
--- a/addons/web/static/test/formats.js
+++ b/addons/web/static/test/formats.js
@@ -1,16 +1,8 @@
-$(document).ready(function () {
- var openerp;
-
- module('server-formats', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.dates(openerp);
- }
- });
- test('Parse server datetime', function () {
- var date = openerp.web.str_to_datetime("2009-05-04 12:34:23");
+openerp.testing.section('server-formats', {
+ dependencies: ['web.coresetup', 'web.dates']
+}, function (test) {
+ test('Parse server datetime', function (instance) {
+ var date = instance.web.str_to_datetime("2009-05-04 12:34:23");
deepEqual(
[date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(),
date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()],
@@ -20,92 +12,86 @@ $(document).ready(function () {
date.getHours(), date.getMinutes(), date.getSeconds()],
[2009, 5 - 1, 4, 12 - (date.getTimezoneOffset() / 60), 34, 23]);
- var date2 = openerp.web.str_to_datetime('2011-12-10 00:00:00');
+ var date2 = instance.web.str_to_datetime('2011-12-10 00:00:00');
deepEqual(
[date2.getUTCFullYear(), date2.getUTCMonth(), date2.getUTCDate(),
date2.getUTCHours(), date2.getUTCMinutes(), date2.getUTCSeconds()],
[2011, 12 - 1, 10, 0, 0, 0]);
});
- test('Parse server date', function () {
- var date = openerp.web.str_to_date("2009-05-04");
+ test('Parse server date', function (instance) {
+ var date = instance.web.str_to_date("2009-05-04");
deepEqual(
[date.getFullYear(), date.getMonth(), date.getDate()],
[2009, 5 - 1, 4]);
});
- test('Parse server time', function () {
- var date = openerp.web.str_to_time("12:34:23");
+ test('Parse server time', function (instance) {
+ var date = instance.web.str_to_time("12:34:23");
deepEqual(
[date.getHours(), date.getMinutes(), date.getSeconds()],
[12, 34, 23]);
});
-
- module('web-formats', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.dates(openerp);
- window.openerp.web.formats(openerp);
- }
- });
- test("format_datetime", function () {
- var date = openerp.web.str_to_datetime("2009-05-04 12:34:23");
- var str = openerp.web.format_value(date, {type:"datetime"});
+});
+openerp.testing.section('web-formats', {
+ dependencies: ['web.formats']
+}, function (test) {
+ test("format_datetime", function (instance) {
+ var date = instance.web.str_to_datetime("2009-05-04 12:34:23");
+ var str = instance.web.format_value(date, {type:"datetime"});
equal(str, date.toString("MM/dd/yyyy HH:mm:ss"));
});
- test("format_date", function () {
- var date = openerp.web.str_to_datetime("2009-05-04 12:34:23");
- var str = openerp.web.format_value(date, {type:"date"});
+ test("format_date", function (instance) {
+ var date = instance.web.str_to_datetime("2009-05-04 12:34:23");
+ var str = instance.web.format_value(date, {type:"date"});
equal(str, date.toString("MM/dd/yyyy"));
});
- test("format_time", function () {
- var date = openerp.web.str_to_datetime("2009-05-04 12:34:23");
- var str = openerp.web.format_value(date, {type:"time"});
+ test("format_time", function (instance) {
+ var date = instance.web.str_to_datetime("2009-05-04 12:34:23");
+ var str = instance.web.format_value(date, {type:"time"});
equal(str, date.toString("HH:mm:ss"));
});
- test("format_float_time", function () {
+ test("format_float_time", function (instance) {
strictEqual(
- openerp.web.format_value(1.0, {type:'float', widget:'float_time'}),
+ instance.web.format_value(1.0, {type:'float', widget:'float_time'}),
'01:00');
strictEqual(
- openerp.web.format_value(0.9853, {type:'float', widget:'float_time'}),
+ instance.web.format_value(0.9853, {type:'float', widget:'float_time'}),
'00:59');
strictEqual(
- openerp.web.format_value(0.0085, {type:'float', widget:'float_time'}),
+ instance.web.format_value(0.0085, {type:'float', widget:'float_time'}),
'00:01');
strictEqual(
- openerp.web.format_value(-1.0, {type:'float', widget:'float_time'}),
+ instance.web.format_value(-1.0, {type:'float', widget:'float_time'}),
'-01:00');
strictEqual(
- openerp.web.format_value(-0.9853, {type:'float', widget:'float_time'}),
+ instance.web.format_value(-0.9853, {type:'float', widget:'float_time'}),
'-00:59');
strictEqual(
- openerp.web.format_value(-0.0085, {type:'float', widget:'float_time'}),
+ instance.web.format_value(-0.0085, {type:'float', widget:'float_time'}),
'-00:01');
});
- test("format_float", function () {
+ test("format_float", function (instance) {
var fl = 12.1234;
- var str = openerp.web.format_value(fl, {type:"float"});
+ var str = instance.web.format_value(fl, {type:"float"});
equal(str, "12.12");
- equal(openerp.web.format_value(12.02, {type: 'float'}),
+ equal(instance.web.format_value(12.02, {type: 'float'}),
'12.02');
- equal(openerp.web.format_value(0.0002, {type: 'float', digits: [1, 3]}),
+ equal(instance.web.format_value(0.0002, {type: 'float', digits: [1, 3]}),
'0.000');
- equal(openerp.web.format_value(0.0002, {type: 'float', digits: [1, 4]}),
+ equal(instance.web.format_value(0.0002, {type: 'float', digits: [1, 4]}),
'0.0002');
- equal(openerp.web.format_value(0.0002, {type: 'float', digits: [1, 6]}),
+ equal(instance.web.format_value(0.0002, {type: 'float', digits: [1, 6]}),
'0.000200');
- equal(openerp.web.format_value(1, {type: 'float', digits: [1, 6]}),
+ equal(instance.web.format_value(1, {type: 'float', digits: [1, 6]}),
'1.000000');
- equal(openerp.web.format_value(1, {type: 'float'}),
+ equal(instance.web.format_value(1, {type: 'float'}),
'1.00');
- equal(openerp.web.format_value(-11.25, {type: 'float'}),
+ equal(instance.web.format_value(-11.25, {type: 'float'}),
"-11.25");
- openerp.web._t.database.parameters.grouping = [1, 2, -1];
- equal(openerp.web.format_value(1111111.25, {type: 'float'}),
+ instance.web._t.database.parameters.grouping = [1, 2, -1];
+ equal(instance.web.format_value(1111111.25, {type: 'float'}),
"1111,11,1.25");
- openerp.web._t.database.parameters.grouping = [1, 0];
- equal(openerp.web.format_value(-11.25, {type: 'float'}),
+ instance.web._t.database.parameters.grouping = [1, 0];
+ equal(instance.web.format_value(-11.25, {type: 'float'}),
"-1,1.25");
});
// test("parse_datetime", function () {
@@ -123,29 +109,29 @@ $(document).ready(function () {
// var res = openerp.web.parse_value(val.toString("HH:mm:ss"), {type:"time"});
// equal(val.toString("HH:mm:ss"), res.toString("HH:mm:ss"));
// });
- test('parse_integer', function () {
- var val = openerp.web.parse_value('123,456', {type: 'integer'});
+ test('parse_integer', function (instance) {
+ var val = instance.web.parse_value('123,456', {type: 'integer'});
equal(val, 123456);
- openerp.web._t.database.parameters.thousands_sep = '|';
- var val2 = openerp.web.parse_value('123|456', {type: 'integer'});
+ instance.web._t.database.parameters.thousands_sep = '|';
+ var val2 = instance.web.parse_value('123|456', {type: 'integer'});
equal(val2, 123456);
});
- test("parse_float", function () {
+ test("parse_float", function (instance) {
var str = "134,112.1234";
- var val = openerp.web.parse_value(str, {type:"float"});
+ var val = instance.web.parse_value(str, {type:"float"});
equal(val, 134112.1234);
var str = "-134,112.1234";
- var val = openerp.web.parse_value(str, {type:"float"});
+ var val = instance.web.parse_value(str, {type:"float"});
equal(val, -134112.1234);
- _.extend(openerp.web._t.database.parameters, {
+ _.extend(instance.web._t.database.parameters, {
decimal_point: ',',
thousands_sep: '.'
});
- var val3 = openerp.web.parse_value('123.456,789', {type: 'float'});
+ var val3 = instance.web.parse_value('123.456,789', {type: 'float'});
equal(val3, 123456.789);
});
- test('intersperse', function () {
- var g = openerp.web.intersperse;
+ test('intersperse', function (instance) {
+ var g = instance.web.intersperse;
equal(g("", []), "");
equal(g("0", []), "0");
equal(g("012", []), "012");
@@ -176,60 +162,61 @@ $(document).ready(function () {
equal(g("12345678", [3,3,3,3], '.'), '12.345.678');
equal(g("12345678", [3,0], '.'), '12.345.678');
});
- test('format_integer', function () {
- openerp.web._t.database.parameters.grouping = [3, 3, 3, 3];
- equal(openerp.web.format_value(1000000, {type: 'integer'}),
+ test('format_integer', function (instance) {
+ instance.web._t.database.parameters.grouping = [3, 3, 3, 3];
+ equal(instance.web.format_value(1000000, {type: 'integer'}),
'1,000,000');
- openerp.web._t.database.parameters.grouping = [3, 2, -1];
- equal(openerp.web.format_value(106500, {type: 'integer'}),
+ instance.web._t.database.parameters.grouping = [3, 2, -1];
+ equal(instance.web.format_value(106500, {type: 'integer'}),
'1,06,500');
- openerp.web._t.database.parameters.grouping = [1, 2, -1];
- equal(openerp.web.format_value(106500, {type: 'integer'}),
+ instance.web._t.database.parameters.grouping = [1, 2, -1];
+ equal(instance.web.format_value(106500, {type: 'integer'}),
'106,50,0');
});
- test('format_float', function () {
- openerp.web._t.database.parameters.grouping = [3, 3, 3, 3];
- equal(openerp.web.format_value(1000000, {type: 'float'}),
+ test('format_float', function (instance) {
+ instance.web._t.database.parameters.grouping = [3, 3, 3, 3];
+ equal(instance.web.format_value(1000000, {type: 'float'}),
'1,000,000.00');
- openerp.web._t.database.parameters.grouping = [3, 2, -1];
- equal(openerp.web.format_value(106500, {type: 'float'}),
+ instance.web._t.database.parameters.grouping = [3, 2, -1];
+ equal(instance.web.format_value(106500, {type: 'float'}),
'1,06,500.00');
- openerp.web._t.database.parameters.grouping = [1, 2, -1];
- equal(openerp.web.format_value(106500, {type: 'float'}),
+ instance.web._t.database.parameters.grouping = [1, 2, -1];
+ equal(instance.web.format_value(106500, {type: 'float'}),
'106,50,0.00');
- _.extend(openerp.web._t.database.parameters, {
+ _.extend(instance.web._t.database.parameters, {
grouping: [3, 0],
decimal_point: ',',
thousands_sep: '.'
});
- equal(openerp.web.format_value(6000, {type: 'float'}),
+ equal(instance.web.format_value(6000, {type: 'float'}),
'6.000,00');
});
- module('custom-date-formats', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.dates(openerp);
- window.openerp.web.formats(openerp);
- }
- });
- test('format stripper', function () {
- strictEqual(openerp.web.strip_raw_chars('%a, %Y %b %d'), '%a, %Y %b %d');
- strictEqual(openerp.web.strip_raw_chars('%a, %Y.eko %bren %da'), '%a, %Y. %b %d');
- });
- test('ES date format', function () {
- openerp.web._t.database.parameters.date_format = '%a, %Y %b %d';
- var date = openerp.web.str_to_date("2009-05-04");
- strictEqual(openerp.web.format_value(date, {type:"date"}), 'Mon, 2009 May 04');
- strictEqual(openerp.web.parse_value('Mon, 2009 May 04', {type: 'date'}), '2009-05-04');
- });
- test('extended ES date format', function () {
- openerp.web._t.database.parameters.date_format = '%a, %Y.eko %bren %da';
- var date = openerp.web.str_to_date("2009-05-04");
- strictEqual(openerp.web.format_value(date, {type:"date"}), 'Mon, 2009. May 04');
- strictEqual(openerp.web.parse_value('Mon, 2009. May 04', {type: 'date'}), '2009-05-04');
+});
+openerp.testing.section('web-formats', {
+ dependencies: ['web.formats']
+}, function (test) {
+ test('format stripper', function (instance) {
+ strictEqual(instance.web.strip_raw_chars('%a, %Y %b %d'),
+ '%a, %Y %b %d');
+ strictEqual(instance.web.strip_raw_chars('%a, %Y.eko %bren %da'),
+ '%a, %Y. %b %d');
+ });
+ test('ES date format', function (instance) {
+ instance.web._t.database.parameters.date_format = '%a, %Y %b %d';
+ var date = instance.web.str_to_date("2009-05-04");
+ strictEqual(instance.web.format_value(date, {type:"date"}),
+ 'Mon, 2009 May 04');
+ strictEqual(instance.web.parse_value('Mon, 2009 May 04', {type: 'date'}),
+ '2009-05-04');
+ });
+ test('extended ES date format', function (instance) {
+ instance.web._t.database.parameters.date_format = '%a, %Y.eko %bren %da';
+ var date = instance.web.str_to_date("2009-05-04");
+ strictEqual(instance.web.format_value(date, {type:"date"}),
+ 'Mon, 2009. May 04');
+ strictEqual(instance.web.parse_value('Mon, 2009. May 04', {type: 'date'}),
+ '2009-05-04');
});
});
diff --git a/addons/web/static/test/list-editable.js b/addons/web/static/test/list-editable.js
index 282ef70f97b..4c31d455598 100644
--- a/addons/web/static/test/list-editable.js
+++ b/addons/web/static/test/list-editable.js
@@ -1,16 +1,13 @@
-$(document).ready(function () {
- var $fix = $('#qunit-fixture');
-
- var instance;
- var baseSetup = function () {
- instance = openerp.testing.instanceFor('list_editable');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- };
-
-
+openerp.testing.section('editor', {
+ dependencies: ['web.list_editable'],
+ rpc: 'mock',
+ templates: true,
+ setup: function (instance, $s, mock) {
+ mock('test.model:create', function () {
+ return 42;
+ });
+ }
+}, function (test) {
/**
*
* @param {String} name
@@ -30,7 +27,7 @@ $(document).ready(function () {
}
/**
- * @param {Array} fields
+ * @param {Array} [fields]
* @return {Object}
*/
function makeFormView(fields) {
@@ -67,46 +64,37 @@ $(document).ready(function () {
};
}
- module('editor', {
- setup: baseSetup
- });
- asyncTest('base-state', 2, function () {
+ test('base-state', {asserts: 2}, function (instance, $fix) {
var e = new instance.web.list.Editor({
dataset: {ids: []},
edition_view: function () {
return makeFormView();
}
});
- e.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ return e.appendTo($fix)
.done(function () {
ok(!e.is_editing(), "should not be editing");
ok(e.form instanceof instance.web.FormView,
"should use default form type");
});
});
- asyncTest('toggle-edition-save', 4, function () {
- instance.session.responses['/web/dataset/call_kw:create'] = function () {
- return { result: 42 };
- };
- instance.session.responses['/web/dataset/call_kw:read'] = function () {
- return { result: [{
- id: 42,
- a: false,
- b: false,
- c: false
- }]};
- };
+ test('toggle-edition-save', {
+ asserts: 4,
+ setup: function (instance, $s, mock) {
+ mock('test.model:read', function () {
+ return [{id: 42, a: false, b: false, c: false}];
+ });
+ }
+ }, function (instance, $fix) {
var e = new instance.web.list.Editor({
- dataset: new instance.web.DataSetSearch(),
+ dataset: new instance.web.DataSetSearch(null, 'test.model'),
prepends_on_create: function () { return false; },
edition_view: function () {
return makeFormView([ field('a'), field('b'), field('c') ]);
}
});
var counter = 0;
- e.appendTo($fix)
+ return e.appendTo($fix)
.then(function () {
return e.edit({}, function () {
++counter;
@@ -117,26 +105,21 @@ $(document).ready(function () {
equal(counter, 3, "should have configured all fields");
return e.save();
})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
.done(function (record) {
ok(!e.is_editing(), "should have stopped editing");
equal(record.id, 42, "should have newly created id");
})
});
- asyncTest('toggle-edition-cancel', 2, function () {
- instance.session.responses['/web/dataset/call_kw:create'] = function () {
- return { result: 42 };
- };
+ test('toggle-edition-cancel', { asserts: 2 }, function (instance, $fix) {
var e = new instance.web.list.Editor({
- dataset: new instance.web.DataSetSearch(),
+ dataset: new instance.web.DataSetSearch(null, 'test.model'),
prepends_on_create: function () { return false; },
edition_view: function () {
return makeFormView([ field('a'), field('b'), field('c') ]);
}
});
var counter = 0;
- e.appendTo($fix)
+ return e.appendTo($fix)
.then(function () {
return e.edit({}, function () {
++counter;
@@ -145,22 +128,20 @@ $(document).ready(function () {
.then(function (form) {
return e.cancel();
})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
.done(function (record) {
ok(!e.is_editing(), "should have stopped editing");
ok(!record.id, "should have no id");
})
});
- asyncTest('toggle-save-required', 2, function () {
- instance.session.responses['/web/dataset/call_kw:create'] = function () {
- return { result: 42 };
- };
+ test('toggle-save-required', {
+ asserts: 2,
+ fail_on_rejection: false
+ }, function (instance, $fix) {
var e = new instance.web.list.Editor({
do_warn: function () {
warnings++;
},
- dataset: new instance.web.DataSetSearch(),
+ dataset: new instance.web.DataSetSearch(null, 'test.model'),
prepends_on_create: function () { return false; },
edition_view: function () {
return makeFormView([
@@ -169,7 +150,7 @@ $(document).ready(function () {
});
var counter = 0;
var warnings = 0;
- e.appendTo($fix)
+ return e.appendTo($fix)
.then(function () {
return e.edit({}, function () {
++counter;
@@ -178,78 +159,73 @@ $(document).ready(function () {
.then(function (form) {
return e.save();
})
- .always(start)
.done(function () { ok(false, "cancel should not succeed"); })
.fail(function () {
equal(warnings, 1, "should have been warned");
ok(e.is_editing(), "should have kept editing");
- })
+ });
});
-
- module('list-edition', {
- setup: function () {
- baseSetup();
-
- var records = {};
- _.extend(instance.session.responses, {
- '/web/view/load': function () {
- return {result: {
- type: 'tree',
- fields: {
- a: {type: 'char', string: "A"},
- b: {type: 'char', string: "B"},
- c: {type: 'char', string: "C"}
- },
- arch: {
- tag: 'tree',
- attrs: {},
- children: [
- {tag: 'field', attrs: {name: 'a'}},
- {tag: 'field', attrs: {name: 'b'}},
- {tag: 'field', attrs: {name: 'c'}}
- ]
- }
- }};
- },
- '/web/dataset/call_kw:create': function (params) {
- records[42] = _.extend({}, params.params.args[0]);
- return {result: 42};
+});
+openerp.testing.section('list.edition', {
+ dependencies: ['web.list_editable'],
+ rpc: 'mock',
+ templates: true,
+ setup: function (instance, $s, mock) {
+ var records = {};
+ mock('demo:create', function (args) {
+ records[42] = _.extend({}, args[0]);
+ return 42;
+ });
+ mock('demo:read', function (args) {
+ var id = args[0][0];
+ if (id in records) {
+ return [records[id]];
+ }
+ return [];
+ });
+ mock('/web/view/load', function () {
+ return {
+ type: 'tree',
+ fields: {
+ a: {type: 'char', string: "A"},
+ b: {type: 'char', string: "B"},
+ c: {type: 'char', string: "C"}
},
- '/web/dataset/call_kw:read': function (params) {
- var id = params.params.args[0][0];
- if (id in records) {
- return {result: [records[id]]};
- }
- return {result: []};
+ arch: {
+ tag: 'tree',
+ attrs: {},
+ children: [
+ {tag: 'field', attrs: {name: 'a'}},
+ {tag: 'field', attrs: {name: 'b'}},
+ {tag: 'field', attrs: {name: 'c'}}
+ ]
}
- })
- }
- });
- asyncTest('newrecord', 6, function () {
+ };
+ });
+ }
+}, function (test) {
+ test('newrecord', {asserts: 6}, function (instance, $fix, mock) {
var got_defaults = false;
- instance.session.responses['/web/dataset/call_kw:default_get'] = function (params) {
- var fields = params.params.args[0];
+ mock('demo:default_get', function (args) {
+ var fields = args[0];
deepEqual(
fields, ['a', 'b', 'c'],
"should ask defaults for all fields");
got_defaults = true;
- return {result: {
- a: "qux",
- b: "quux"
- }};
- };
+ return { a: "qux", b: "quux" };
+ });
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
- l.appendTo($fix)
+ return l.appendTo($fix)
.then(l.proxy('reload_content'))
.then(function () {
return l.start_edition();
})
- .always(start)
.then(function () {
ok(got_defaults, "should have fetched default values for form");
+
return l.save_edition();
})
.then(function (result) {
@@ -260,45 +236,39 @@ $(document).ready(function () {
"should have used default values");
ok(!result.record.get('c'),
"should have no value if there was no default");
- })
- .fail(function (e) { ok(false, e && e.message || e); });
+ });
});
-
- module('list-edition-events', {
- setup: function () {
- baseSetup();
- _.extend(instance.session.responses, {
- '/web/view/load': function () {
- return {result: {
- type: 'tree',
- fields: {
- a: {type: 'char', string: "A"},
- b: {type: 'char', string: "B"},
- c: {type: 'char', string: "C"}
- },
- arch: {
- tag: 'tree',
- attrs: {},
- children: [
- {tag: 'field', attrs: {name: 'a'}},
- {tag: 'field', attrs: {name: 'b'}},
- {tag: 'field', attrs: {name: 'c'}}
- ]
- }
- }};
+});
+openerp.testing.section('list.edition.events', {
+ dependencies: ['web.list_editable'],
+ rpc: 'mock',
+ templates: true,
+ setup: function (instance, $s, mock) {
+ mock('demo:read', function () {
+ return [{ id: 1, a: 'foo', b: 'bar', c: 'baz' }];
+ });
+ mock('/web/view/load', function () {
+ return {
+ type: 'tree',
+ fields: {
+ a: {type: 'char', string: "A"},
+ b: {type: 'char', string: "B"},
+ c: {type: 'char', string: "C"}
},
- '/web/dataset/call_kw:read': function (params) {
- return {result: [{
- id: 1,
- a: 'foo',
- b: 'bar',
- c: 'baz'
- }]};
+ arch: {
+ tag: 'tree',
+ attrs: {},
+ children: [
+ {tag: 'field', attrs: {name: 'a'}},
+ {tag: 'field', attrs: {name: 'b'}},
+ {tag: 'field', attrs: {name: 'c'}}
+ ]
}
- });
- }
- });
- asyncTest('edition events', 4, function () {
+ };
+ });
+ }
+}, function (test) {
+ test('edition events', {asserts: 4}, function (instance, $fix) {
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
var o = {
counter: 0,
@@ -306,9 +276,8 @@ $(document).ready(function () {
};
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
l.on('edit:before edit:after', o, o.onEvent);
- l.appendTo($fix)
+ return l.appendTo($fix)
.then(l.proxy('reload_content'))
- .always(start)
.then(function () {
ok(l.options.editable, "should be editable");
equal(o.counter, 0, "should have seen no event yet");
@@ -317,11 +286,10 @@ $(document).ready(function () {
.then(function () {
ok(l.editor.is_editing(), "should be editing");
equal(o.counter, 2, "should have seen two edition events");
- })
- .fail(function (e) { ok(false, e && e.message); });
+ });
});
- asyncTest('edition events: cancelling', 3, function () {
+ test('edition events: cancelling', {asserts: 3}, function (instance, $fix) {
var edit_after = false;
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
@@ -331,9 +299,8 @@ $(document).ready(function () {
l.on('edit:after', {}, function () {
edit_after = true;
});
- l.appendTo($fix)
+ return l.appendTo($fix)
.then(l.proxy('reload_content'))
- .always(start)
.then(function () {
ok(l.options.editable, "should be editable");
return l.start_edition();
@@ -343,19 +310,18 @@ $(document).ready(function () {
ok(!l.editor.is_editing(), "should not be editing");
ok(!edit_after, "should not have fired the edit:after event");
return $.when();
- })
- .fail(function (e) { ok(false, e && e.message || e); });
- });
-
- module('list-edition-onwrite', {
- setup: function () {
- baseSetup();
- }
+ });
});
+});
- asyncTest('record-to-read', 4, function () {
- instance.session.responses['/web/view/load'] = function () {
- return {result: {
+openerp.testing.section('list.edition.onwrite', {
+ dependencies: ['web.list_editable'],
+ rpc: 'mock',
+ templates: true,
+}, function (test) {
+ test('record-to-read', {asserts: 4}, function (instance, $fix, mock) {
+ mock('/web/view/load', function () {
+ return {
type: 'tree',
fields: {
a: {type: 'char', string: "A"}
@@ -367,35 +333,27 @@ $(document).ready(function () {
{tag: 'field', attrs: {name: 'a'}}
]
}
- }};
- };
- instance.session.responses['/web/dataset/call_kw:read'] = function (req) {
- if (_.isEmpty(req.params.args[0])) {
- return {result: []};
- } else if (_.isEqual(req.params.args[0], [1])) {
- return {result: [
+ };
+ });
+ mock('demo:read', function (params) {
+ if (_.isEmpty(params.args[0])) {
+ return [];
+ } else if (_.isEqual(params.args[0], [1])) {
+ return [
{id: 1, a: 'some value'}
- ]};
- } else if (_.isEqual(req.params.args[0], [42])) {
- return {result: [
- {id: 42, a: 'foo'}
- ]};
+ ];
+ } else if (_.isEqual(params.args[0], [42])) {
+ return [ {id: 42, a: 'foo'} ];
}
- throw new Error(JSON.stringify(req.params));
- };
- instance.session.responses['/web/dataset/call_kw:default_get'] = function () {
- return {result: {}};
- };
- instance.session.responses['/web/dataset/call_kw:create'] = function () {
- return {result: 1};
- };
- instance.session.responses['/web/dataset/call_kw:on_write'] = function () {
- return {result: [42]};
- };
+ throw new Error(JSON.stringify(params));
+ });
+ mock('demo:default_get', function () { return {}; });
+ mock('demo:create', function () { return 1; });
+ mock('demo:on_write', function () { return [42]; });
var ds = new instance.web.DataSetStatic(null, 'demo', null, []);
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
- l.appendTo($fix)
+ return l.appendTo($fix)
.then(l.proxy('reload_content'))
.then(function () {
return l.start_edition();
@@ -406,7 +364,6 @@ $(document).ready(function () {
.then(function () {
return l.save_edition();
})
- .always(function () { start(); })
.then(function () {
strictEqual(ds.ids.length, 2,
'should have id of created + on_write');
@@ -418,8 +375,6 @@ $(document).ready(function () {
strictEqual(
$fix.find('tbody tr:eq(2)').css('color'), 'rgb(0, 0, 0)',
'should have default color applied');
- }, function (e) {
- ok(false, e && e.message || e);
});
});
});
diff --git a/addons/web/static/test/list-utils.js b/addons/web/static/test/list-utils.js
index 6200ba788ce..84661138315 100644
--- a/addons/web/static/test/list-utils.js
+++ b/addons/web/static/test/list-utils.js
@@ -1,45 +1,34 @@
-$(document).ready(function () {
- var openerp,
- create = function (o) {
- if (typeof Object.create === 'function') {
- return Object.create(o);
- }
- function Cls() {}
- Cls.prototype = o;
- return new Cls;
- };
- module('list-events', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.chrome(openerp);
- // views loader stuff
- window.openerp.web.data(openerp);
- window.openerp.web.views(openerp);
- window.openerp.web.list(openerp);
+openerp.testing.section('list.events', {
+ dependencies: ['web.list']
+}, function (test) {
+ var create = function (o) {
+ if (typeof Object.create === 'function') {
+ return Object.create(o);
}
- });
- test('Simple event triggering', function () {
- var e = create(openerp.web.list.Events), passed = false;
+ function Cls() {}
+ Cls.prototype = o;
+ return new Cls;
+ };
+ test('Simple event triggering', function (instance) {
+ var e = create(instance.web.list.Events), passed = false;
e.bind('foo', function () { passed = true; });
e.trigger('foo');
ok(passed);
});
- test('Bind all', function () {
- var e = create(openerp.web.list.Events), event = null;
+ test('Bind all', function (instance) {
+ var e = create(instance.web.list.Events), event = null;
e.bind(null, function (ev) { event = ev; });
e.trigger('foo');
strictEqual(event, 'foo');
});
- test('Propagate trigger params', function () {
- var e = create(openerp.web.list.Events), p = false;
+ test('Propagate trigger params', function (instance) {
+ var e = create(instance.web.list.Events), p = false;
e.bind(null, function (_, param) { p = param });
e.trigger('foo', true);
strictEqual(p, true)
});
- test('Bind multiple callbacks', function () {
- var e = create(openerp.web.list.Events), count;
+ test('Bind multiple callbacks', function (instance) {
+ var e = create(instance.web.list.Events), count;
e.bind('foo', function () { count++; })
.bind('bar', function () { count++; })
.bind(null, function () { count++; })
@@ -59,20 +48,20 @@ $(document).ready(function () {
e.trigger('baz');
strictEqual(count, 3);
});
- test('Mixin events', function () {
- var cls = openerp.web.Class.extend({
+ test('Mixin events', function (instance) {
+ var cls = instance.web.Class.extend({
method: function () { this.trigger('e'); }
});
- cls.include(openerp.web.list.Events);
- var instance = new cls, triggered = false;
+ cls.include(instance.web.list.Events);
+ var i = new cls, triggered = false;
- instance.bind('e', function () { triggered = true; });
- instance.method();
+ i.bind('e', function () { triggered = true; });
+ i.method();
ok(triggered);
});
- test('Unbind all handlers', function () {
- var e = create(openerp.web.list.Events), passed = 0;
+ test('Unbind all handlers', function (instance) {
+ var e = create(instance.web.list.Events), passed = 0;
e.bind('foo', function () { passed++; });
e.trigger('foo');
strictEqual(passed, 1);
@@ -80,8 +69,8 @@ $(document).ready(function () {
e.trigger('foo');
strictEqual(passed, 1);
});
- test('Unbind one handler', function () {
- var e = create(openerp.web.list.Events), p1 = 0, p2 = 0,
+ test('Unbind one handler', function (instance) {
+ var e = create(instance.web.list.Events), p1 = 0, p2 = 0,
h1 = function () { p1++; }, h2 = function () { p2++; };
e.bind('foo', h1);
e.bind('foo', h2);
@@ -93,29 +82,20 @@ $(document).ready(function () {
strictEqual(p1, 1);
strictEqual(p2, 2);
});
-
- module('list-records', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.chrome(openerp);
- // views loader stuff
- window.openerp.web.data(openerp);
- window.openerp.web.views(openerp);
- window.openerp.web.list(openerp);
- }
- });
- test('Basic record initialization', function () {
- var r = new openerp.web.list.Record({qux: 3});
+});
+openerp.testing.section('list.records', {
+ dependencies: ['web.list']
+}, function (test) {
+ test('Basic record initialization', function (instance) {
+ var r = new instance.web.list.Record({qux: 3});
r.set('foo', 1);
r.set('bar', 2);
strictEqual(r.get('foo'), 1);
strictEqual(r.get('bar'), 2);
strictEqual(r.get('qux'), 3);
});
- test('Change all the things', function () {
- var r = new openerp.web.list.Record(), changed = false, field;
+ test('Change all the things', function (instance) {
+ var r = new instance.web.list.Record(), changed = false, field;
r.bind('change', function () { changed = true; });
r.bind(null, function (e) { field = field || e.split(':')[1]});
r.set('foo', 1);
@@ -123,8 +103,8 @@ $(document).ready(function () {
ok(changed);
strictEqual(field, 'foo');
});
- test('Change single field', function () {
- var r = new openerp.web.list.Record(), changed = 0;
+ test('Change single field', function (instance) {
+ var r = new instance.web.list.Record(), changed = 0;
r.bind('change:foo', function () { changed++; });
r.set('foo', 1);
r.set('bar', 1);
@@ -132,21 +112,12 @@ $(document).ready(function () {
strictEqual(r.get('bar'), 1);
strictEqual(changed, 1);
});
-
- module('list-collections', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.chrome(openerp);
- // views loader stuff
- window.openerp.web.data(openerp);
- window.openerp.web.views(openerp);
- window.openerp.web.list(openerp);
- }
- });
- test('degenerate-fetch', function () {
- var c = new openerp.web.list.Collection();
+});
+openerp.testing.section('list.collections', {
+ dependencies: ['web.list']
+}, function (test) {
+ test('degenerate-fetch', function (instance) {
+ var c = new instance.web.list.Collection();
strictEqual(c.length, 0);
c.add({id: 1, value: 2});
c.add({id: 2, value: 3});
@@ -155,16 +126,16 @@ $(document).ready(function () {
strictEqual(c.length, 4);
var r = c.at(2), r2 = c.get(1);
- ok(r instanceof openerp.web.list.Record);
+ ok(r instanceof instance.web.list.Record);
strictEqual(r.get('id'), 3);
strictEqual(r.get('value'), 5);
- ok(r2 instanceof openerp.web.list.Record);
+ ok(r2 instanceof instance.web.list.Record);
strictEqual(r2.get('id'), 1);
strictEqual(r2.get('value'), 2);
});
- test('degenerate-indexed-add', function () {
- var c = new openerp.web.list.Collection([
+ test('degenerate-indexed-add', function (instance) {
+ var c = new instance.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
{id: 3, value: 20}
@@ -175,8 +146,8 @@ $(document).ready(function () {
strictEqual(c.at(1).get('value'), 55);
strictEqual(c.at(3).get('value'), 20);
});
- test('degenerate-remove', function () {
- var c = new openerp.web.list.Collection([
+ test('degenerate-remove', function (instance) {
+ var c = new instance.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
{id: 3, value: 20}
@@ -188,9 +159,9 @@ $(document).ready(function () {
equal(c.get(2), undefined);
strictEqual(c.at(1).get('value'), 20);
});
- test('degenerate-remove-bound', function () {
+ test('degenerate-remove-bound', function (instance) {
var changed = false,
- c = new openerp.web.list.Collection([ {id: 1, value: 5} ]);
+ c = new instance.web.list.Collection([ {id: 1, value: 5} ]);
c.bind('change', function () { changed = true; });
var record = c.get(1);
c.remove(record);
@@ -198,8 +169,8 @@ $(document).ready(function () {
ok(!changed, 'removed records should not trigger events in their ' +
'parent collection');
});
- test('degenerate-reset', function () {
- var event, obj, c = new openerp.web.list.Collection([
+ test('degenerate-reset', function (instance) {
+ var event, obj, c = new instance.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
{id: 3, value: 20}
@@ -218,9 +189,9 @@ $(document).ready(function () {
strictEqual(c.length, 1);
strictEqual(c.get(42).get('value'), 55);
});
- test('degenerate-reset-bound', function () {
+ test('degenerate-reset-bound', function (instance) {
var changed = false,
- c = new openerp.web.list.Collection([ {id: 1, value: 5} ]);
+ c = new instance.web.list.Collection([ {id: 1, value: 5} ]);
c.bind('change', function () { changed = true; });
var record = c.get(1);
c.reset();
@@ -229,9 +200,9 @@ $(document).ready(function () {
'parent collection');
});
- test('degenerate-propagations', function () {
+ test('degenerate-propagations', function (instance) {
var values = [];
- var c = new openerp.web.list.Collection([
+ var c = new instance.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
{id: 3, value: 20}
@@ -244,8 +215,8 @@ $(document).ready(function () {
c.get(3).set('value', 21);
deepEqual(values, [6, 11, 21]);
});
- test('BTree', function () {
- var root = new openerp.web.list.Collection(),
+ test('BTree', function (instance) {
+ var root = new instance.web.list.Collection(),
c = root.proxy('admin'),
total = 0;
c.add({id: 1, name: "Administrator", login: 'admin'});
@@ -260,8 +231,8 @@ $(document).ready(function () {
c.at(1).set('wealth', 5);
strictEqual(total, 47);
});
- test('degenerate-successor', function () {
- var root = new openerp.web.list.Collection([
+ test('degenerate-successor', function (instance) {
+ var root = new instance.web.list.Collection([
{id: 1, value: 1},
{id: 2, value: 2},
{id: 3, value: 3},
@@ -282,8 +253,8 @@ $(document).ready(function () {
root.at(3).attributes,
"wraparound should have no effect if not succ(last_record)");
});
- test('successor', function () {
- var root = new openerp.web.list.Collection();
+ test('successor', function (instance) {
+ var root = new instance.web.list.Collection();
root.proxy('first').add([{id: 1, value: 1}, {id: 2, value: 2}]);
root.proxy('second').add([{id: 3, value: 3}, {id: 4, value: 5}]);
root.proxy('third').add([{id: 5, value: 8}, {id: 6, value: 13}]);
@@ -298,8 +269,8 @@ $(document).ready(function () {
root.get(3).attributes,
"should wraparound within a collection");
});
- test('degenerate-predecessor', function () {
- var root = new openerp.web.list.Collection([
+ test('degenerate-predecessor', function (instance) {
+ var root = new instance.web.list.Collection([
{id: 1, value: 1},
{id: 2, value: 2},
{id: 3, value: 3},
@@ -320,8 +291,8 @@ $(document).ready(function () {
root.at(0).attributes,
"wraparound should have no effect if not pred(first_record)");
});
- test('predecessor', function () {
- var root = new openerp.web.list.Collection();
+ test('predecessor', function (instance) {
+ var root = new instance.web.list.Collection();
root.proxy('first').add([{id: 1, value: 1}, {id: 2, value: 2}]);
root.proxy('second').add([{id: 3, value: 3}, {id: 4, value: 5}]);
root.proxy('third').add([{id: 5, value: 8}, {id: 6, value: 13}]);
@@ -336,21 +307,12 @@ $(document).ready(function () {
root.get(4).attributes,
"should wraparound within a collection");
});
-
- module('list-hofs', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.chrome(openerp);
- // views loader stuff
- window.openerp.web.data(openerp);
- window.openerp.web.views(openerp);
- window.openerp.web.list(openerp);
- }
- });
- test('each, degenerate', function () {
- var c = new openerp.web.list.Collection([
+});
+openerp.testing.section('list.collections.hom', {
+ dependencies: ['web.list']
+}, function (test) {
+ test('each, degenerate', function (instance) {
+ var c = new instance.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
{id: 3, value: 20}
@@ -362,8 +324,8 @@ $(document).ready(function () {
ids, [1, 2, 3],
'degenerate collections should be iterated in record order');
});
- test('each, deep', function () {
- var root = new openerp.web.list.Collection(),
+ test('each, deep', function (instance) {
+ var root = new instance.web.list.Collection(),
ids = [];
root.proxy('foo').add([
{id: 1, value: 5},
@@ -382,8 +344,8 @@ $(document).ready(function () {
ids, [1, 2, 3, 10, 20, 30],
'tree collections should be deeply iterated');
});
- test('map, degenerate', function () {
- var c = new openerp.web.list.Collection([
+ test('map, degenerate', function (instance) {
+ var c = new instance.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
{id: 3, value: 20}
@@ -395,8 +357,8 @@ $(document).ready(function () {
ids, [1, 2, 3],
'degenerate collections should be iterated in record order');
});
- test('map, deep', function () {
- var root = new openerp.web.list.Collection();
+ test('map, deep', function (instance) {
+ var root = new instance.web.list.Collection();
root.proxy('foo').add([
{id: 1, value: 5},
{id: 2, value: 10},
@@ -414,29 +376,20 @@ $(document).ready(function () {
ids, [1, 2, 3, 10, 20, 30],
'tree collections should be deeply iterated');
});
-
- module("list-weirds", {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.chrome(openerp);
- // views loader stuff
- window.openerp.web.data(openerp);
- window.openerp.web.views(openerp);
- window.openerp.web.list(openerp);
- }
- });
- test('set-from-noid', function () {
- var root = new openerp.web.list.Collection();
+});
+openerp.testing.section('list.collection.weirdoes', {
+ dependencies: ['web.list']
+}, function (test) {
+ test('set-from-noid', function (instance) {
+ var root = new instance.web.list.Collection();
root.add({v: 3});
root.at(0).set('id', 42);
var record = root.get(42);
equal(root.length, 1);
equal(record.get('v'), 3, "should have fetched the original record");
});
- test('set-from-previd', function () {
- var root = new openerp.web.list.Collection();
+ test('set-from-previd', function (instance) {
+ var root = new instance.web.list.Collection();
root.add({id: 1, v: 2});
root.get(1).set('id', 42);
var record = root.get(42);
diff --git a/addons/web/static/test/list.js b/addons/web/static/test/list.js
index 1f4b23b3155..2305b758390 100644
--- a/addons/web/static/test/list.js
+++ b/addons/web/static/test/list.js
@@ -1,19 +1,11 @@
-$(document).ready(function () {
- var instance;
- var $fix = $('#qunit-fixture');
-
- module('list.buttons', {
- setup: function () {
- instance = openerp.testing.instanceFor('list');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
- });
- asyncTest('record-deletion', 2, function () {
- instance.session.responses['/web/view/load'] = function () {
- return {result: {
+openerp.testing.section('list.buttons', {
+ dependencies: ['web.list'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('record-deletion', {asserts: 2}, function (instance, $fix, mock) {
+ mock('/web/view/load', function () {
+ return {
type: 'tree',
fields: {
a: {type: 'char', string: "A"}
@@ -26,28 +18,26 @@ $(document).ready(function () {
{tag: 'button', attrs: {type: 'object', name: 'foo'}}
]
}
- }};
- };
- instance.session.responses['/web/dataset/call_kw:read'] = function (req) {
- var args = req.params.args[0];
+ };
+ });
+ mock('demo:read', function (params) {
+ var args = params.args[0];
if (_.isEqual(args, [1, 2, 3])) {
- return {result: [
+ return [
{id: 1, a: 'foo'}, {id: 2, a: 'bar'}, {id: 3, a: 'baz'}
- ]};
+ ];
} else if (_.isEqual(args, [2])) {
// button action virtually removed record
- return {result: []};
+ return [];
}
- throw new Error(JSON.stringify(req.params));
- };
- instance.session.responses['/web/dataset/call_button'] = function () {
- return {result: false};
- };
+ throw new Error(JSON.stringify(params));
+ });
+ mock('/web/dataset/call_button', function () { return false; });
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1, 2, 3]);
var l = new instance.web.ListView({
do_action: openerp.testing.noop
}, ds, false, {editable: 'top'});
- l.appendTo($fix)
+ return l.appendTo($fix)
.then(l.proxy('reload_content'))
.then(function () {
var d = $.Deferred();
@@ -57,14 +47,11 @@ $(document).ready(function () {
$fix.find('table tbody tr:eq(1) button').click();
return d.promise();
})
- .always(function () { start(); })
.then(function () {
strictEqual(l.records.length, 2,
"should have 2 records left");
strictEqual($fix.find('table tbody tr[data-id]').length, 2,
"should have 2 rows left");
- }, function (e) {
- ok(false, e && e.message || e);
});
});
});
diff --git a/addons/web/static/test/registry.js b/addons/web/static/test/registry.js
index ca689a351e0..2ad69a541bc 100644
--- a/addons/web/static/test/registry.js
+++ b/addons/web/static/test/registry.js
@@ -1,58 +1,55 @@
-$(document).ready(function () {
- var openerp;
- module('Registry', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- openerp.web.Foo = {};
- openerp.web.Bar = {};
- openerp.web.Foo2 = {};
- }
- });
- test('key set', function () {
- var reg = new openerp.web.Registry();
+openerp.testing.section('registry', {
+ dependencies: ['web.corelib'],
+ setup: function (instance) {
+ instance.web.Foo = {};
+ instance.web.Bar = {};
+ instance.web.Foo2 = {};
+ }
+}, function (test) {
+ test('key set', function (instance) {
+ var reg = new instance.web.Registry();
- reg.add('foo', 'openerp.web.Foo')
- .add('bar', 'openerp.web.Bar');
- strictEqual(reg.get_object('bar'), openerp.web.Bar);
+ reg.add('foo', 'instance.web.Foo')
+ .add('bar', 'instance.web.Bar');
+ strictEqual(reg.get_object('bar'), instance.web.Bar);
});
- test('extension', function () {
- var reg = new openerp.web.Registry({
- foo: 'openerp.web.Foo',
- bar: 'openerp.web.Bar'
+ test('extension', function (instance) {
+ var reg = new instance.web.Registry({
+ foo: 'instance.web.Foo',
+ bar: 'instance.web.Bar'
});
- var reg2 = reg.extend({ 'foo': 'openerp.web.Foo2' });
- strictEqual(reg.get_object('foo'), openerp.web.Foo);
- strictEqual(reg2.get_object('foo'), openerp.web.Foo2);
+ var reg2 = reg.extend({ 'foo': 'instance.web.Foo2' });
+ strictEqual(reg.get_object('foo'), instance.web.Foo);
+ strictEqual(reg2.get_object('foo'), instance.web.Foo2);
});
- test('remain-linked', function () {
- var reg = new openerp.web.Registry({
- foo: 'openerp.web.Foo',
- bar: 'openerp.web.Bar'
+ test('remain-linked', function (instance) {
+ var reg = new instance.web.Registry({
+ foo: 'instance.web.Foo',
+ bar: 'instance.web.Bar'
});
var reg2 = reg.extend();
- reg.add('foo2', 'openerp.web.Foo2');
- strictEqual(reg.get_object('foo2'), openerp.web.Foo2);
- strictEqual(reg2.get_object('foo2'), openerp.web.Foo2);
+ reg.add('foo2', 'instance.web.Foo2');
+ strictEqual(reg.get_object('foo2'), instance.web.Foo2);
+ strictEqual(reg2.get_object('foo2'), instance.web.Foo2);
});
- test('multiget', function () {
- var reg = new openerp.web.Registry({
- foo: 'openerp.web.Foo',
- bar: 'openerp.web.Bar'
+ test('multiget', function (instance) {
+ var reg = new instance.web.Registry({
+ foo: 'instance.web.Foo',
+ bar: 'instance.web.Bar'
});
strictEqual(reg.get_any(['qux', 'grault', 'bar', 'foo']),
- openerp.web.Bar);
+ instance.web.Bar);
});
- test('extended-multiget', function () {
- var reg = new openerp.web.Registry({
- foo: 'openerp.web.Foo',
- bar: 'openerp.web.Bar'
+ test('extended-multiget', function (instance) {
+ var reg = new instance.web.Registry({
+ foo: 'instance.web.Foo',
+ bar: 'instance.web.Bar'
});
var reg2 = reg.extend();
strictEqual(reg2.get_any(['qux', 'grault', 'bar', 'foo']),
- openerp.web.Bar);
+ instance.web.Bar);
});
});
diff --git a/addons/web/static/test/rpc.js b/addons/web/static/test/rpc.js
index be7cbce9f41..85f562ef225 100644
--- a/addons/web/static/test/rpc.js
+++ b/addons/web/static/test/rpc.js
@@ -1,16 +1,8 @@
-$(document).ready(function () {
- var openerp;
-
- module('Misordered resolution management', {
- setup: function () {
- openerp = window.openerp.init([]);
- window.openerp.web.corelib(openerp);
- window.openerp.web.coresetup(openerp);
- window.openerp.web.data(openerp);
- }
- });
- test('Resolve all correctly ordered, sync', function () {
- var dm = new openerp.web.DropMisordered(), flag = false;
+openerp.testing.section('misordered resolution managemeng', {
+ dependencies: ['web.data']
+}, function (test) {
+ test('Resolve all correctly ordered, sync', function (instance) {
+ var dm = new instance.web.DropMisordered(), flag = false;
var d1 = $.Deferred(), d2 = $.Deferred(),
r1 = dm.add(d1), r2 = dm.add(d2);
@@ -23,8 +15,8 @@ $(document).ready(function () {
ok(flag);
});
- test("Don't resolve mis-ordered, sync", function () {
- var dm = new openerp.web.DropMisordered(),
+ test("Don't resolve mis-ordered, sync", function (instance) {
+ var dm = new instance.web.DropMisordered(),
done1 = false, done2 = false,
fail1 = false, fail2 = false;
@@ -44,8 +36,8 @@ $(document).ready(function () {
ok(done2);
ok(!fail2);
});
- test('Fail mis-ordered flag, sync', function () {
- var dm = new openerp.web.DropMisordered(true),
+ test('Fail mis-ordered flag, sync', function (instance) {
+ var dm = new instance.web.DropMisordered(true),
done1 = false, done2 = false,
fail1 = false, fail2 = false;
@@ -66,8 +58,8 @@ $(document).ready(function () {
ok(!fail2);
});
- asyncTest('Resolve all correctly ordered, async', 1, function () {
- var dm = new openerp.web.DropMisordered();
+ test('Resolve all correctly ordered, async', {asserts: 1}, function (instance) {
+ var dm = new instance.web.DropMisordered();
var d1 = $.Deferred(), d2 = $.Deferred(),
r1 = dm.add(d1), r2 = dm.add(d2);
@@ -75,13 +67,12 @@ $(document).ready(function () {
setTimeout(function () { d1.resolve(); }, 100);
setTimeout(function () { d2.resolve(); }, 200);
- $.when(r1, r2).done(function () {
- start();
+ return $.when(r1, r2).done(function () {
ok(true);
});
});
- asyncTest("Don't resolve mis-ordered, async", 4, function () {
- var dm = new openerp.web.DropMisordered(),
+ test("Don't resolve mis-ordered, async", {asserts: 4}, function (instance) {
+ var dm = new instance.web.DropMisordered(),
done1 = false, done2 = false,
fail1 = false, fail2 = false;
@@ -94,18 +85,20 @@ $(document).ready(function () {
setTimeout(function () { d1.resolve(); }, 200);
setTimeout(function () { d2.resolve(); }, 100);
+ var done = $.Deferred();
setTimeout(function () {
- start();
// d1 is in limbo
ok(!done1);
ok(!fail1);
// d2 is resolved
ok(done2);
ok(!fail2);
+ done.resolve();
}, 400);
+ return $.when(d1, d2, done);
});
- asyncTest('Fail mis-ordered flag, async', 4, function () {
- var dm = new openerp.web.DropMisordered(true),
+ test('Fail mis-ordered flag, async', {asserts: 4}, function (instance) {
+ var dm = new instance.web.DropMisordered(true),
done1 = false, done2 = false,
fail1 = false, fail2 = false;
@@ -118,6 +111,7 @@ $(document).ready(function () {
setTimeout(function () { d1.resolve(); }, 200);
setTimeout(function () { d2.resolve(); }, 100);
+ var done = $.Deferred();
setTimeout(function () {
start();
// d1 is failed
@@ -126,6 +120,8 @@ $(document).ready(function () {
// d2 is resolved
ok(done2);
ok(!fail2);
+ done.resolve();
}, 400);
+ return $.when(d1, d2, done)
});
});
diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js
index 3ef4d6840e2..20534eff76a 100644
--- a/addons/web/static/test/search.js
+++ b/addons/web/static/test/search.js
@@ -1,11 +1,7 @@
-$(document).ready(function () {
- var instance;
- module('query', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
- }
- });
- test('Adding a facet to the query creates a facet and a value', function () {
+openerp.testing.section('query', {
+ dependencies: ['web.search']
+}, function (test) {
+ test('Adding a facet to the query creates a facet and a value', function (instance) {
var query = new instance.web.search.SearchQuery;
var field = {};
query.add({
@@ -19,7 +15,7 @@ $(document).ready(function () {
equal(facet.get('field'), field);
deepEqual(facet.get('values'), [{label: 'Value', value: 3}]);
});
- test('Adding two facets', function () {
+ test('Adding two facets', function (instance) {
var query = new instance.web.search.SearchQuery;
query.add([
{ category: 'Foo', field: {}, values: [{label: 'Value', value: 3}] },
@@ -30,7 +26,7 @@ $(document).ready(function () {
equal(query.at(0).values.length, 1);
equal(query.at(1).values.length, 1);
});
- test('If a facet already exists, add values to it', function () {
+ test('If a facet already exists, add values to it', function (instance) {
var query = new instance.web.search.SearchQuery;
var field = {};
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -43,7 +39,7 @@ $(document).ready(function () {
{label: 'V2', value: 1}
]);
});
- test('Facet being implicitly changed should trigger change, not add', function () {
+ test('Facet being implicitly changed should trigger change, not add', function (instance) {
var query = new instance.web.search.SearchQuery;
var field = {}, added = false, changed = false;
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -54,7 +50,7 @@ $(document).ready(function () {
ok(!added, "query.add adding values to a facet should not trigger an add");
ok(changed, "query.add adding values to a facet should not trigger a change");
});
- test('Toggling a facet, value which does not exist should add it', function () {
+ test('Toggling a facet, value which does not exist should add it', function (instance) {
var query = new instance.web.search.SearchQuery;
var field = {};
query.toggle({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -65,7 +61,7 @@ $(document).ready(function () {
deepEqual(facet.get('values'), [{label: 'V1', value: 0}],
"Facet's value should match input");
});
- test('Toggling a facet which exists with a value which does not should add the value to the facet', function () {
+ test('Toggling a facet which exists with a value which does not should add the value to the facet', function (instance) {
var field = {};
var query = new instance.web.search.SearchQuery;
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -79,7 +75,7 @@ $(document).ready(function () {
{label: 'V2', value: 1}
]);
});
- test('Toggling a facet which exists with a value which does as well should remove the value from the facet', function () {
+ test('Toggling a facet which exists with a value which does as well should remove the value from the facet', function (instance) {
var field = {};
var query = new instance.web.search.SearchQuery;
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -94,7 +90,7 @@ $(document).ready(function () {
{label: 'V1', value: 0}
]);
});
- test('Toggling off the last value of a facet should remove the facet', function () {
+ test('Toggling off the last value of a facet should remove the facet', function (instance) {
var field = {};
var query = new instance.web.search.SearchQuery;
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -103,7 +99,7 @@ $(document).ready(function () {
equal(query.length, 0, 'Should have removed the facet');
});
- test('Intermediate emptiness should not remove the facet', function () {
+ test('Intermediate emptiness should not remove the facet', function (instance) {
var field = {};
var query = new instance.web.search.SearchQuery;
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
@@ -121,7 +117,7 @@ $(document).ready(function () {
]);
});
- test('Reseting with multiple facets should still work to load defaults', function () {
+ test('Reseting with multiple facets should still work to load defaults', function (instance) {
var query = new instance.web.search.SearchQuery;
var field = {};
query.reset([
@@ -135,78 +131,75 @@ $(document).ready(function () {
{label: 'V2', value: 1}
])
});
+});
- module('defaults', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
- });
-
- /**
- * Builds a basic search view with a single "dummy" field. The dummy
- * extends `instance.web.search.Field`, it does not add any (class)
- * attributes beyond what is provided through ``dummy_widget_attributes``.
- *
- * The view is returned un-started, it is the caller's role to start it
- * (or use DOM-insertion methods to start it indirectly).
- *
- * @param [dummy_widget_attributes={}]
- * @param [defaults={}]
- * @return {instance.web.SearchView}
- */
- function makeSearchView(dummy_widget_attributes, defaults) {
- instance.web.search.fields.add(
- 'dummy', 'instance.dummy.DummyWidget');
- instance.dummy = {};
- instance.dummy.DummyWidget = instance.web.search.Field.extend(
- dummy_widget_attributes || {});
- if (!('/web/searchview/load' in instance.session.responses)) {
- instance.session.responses['/web/searchview/load'] = function () {
- return {result: {fields_view: {
- type: 'search',
- fields: {
- dummy: {type: 'char', string: "Dummy"}
- },
- arch: {
- tag: 'search',
- attrs: {},
- children: [{
- tag: 'field',
- attrs: {
- name: 'dummy',
- widget: 'dummy'
- },
- children: []
- }]
- }
- }}};
- };
- }
- instance.session.responses['/web/searchview/get_filters'] = function () {
- return {result: []};
- };
- instance.session.responses['/web/searchview/fields_get'] = function () {
- return {result: {fields: {
- dummy: {type: 'char', string: 'Dummy'}
- }}};
+/**
+ * Builds a basic search view with a single "dummy" field. The dummy
+ * extends `instance.web.search.Field`, it does not add any (class)
+ * attributes beyond what is provided through ``dummy_widget_attributes``.
+ *
+ * The view is returned un-started, it is the caller's role to start it
+ * (or use DOM-insertion methods to start it indirectly).
+ *
+ * @param instance
+ * @param [dummy_widget_attributes={}]
+ * @param [defaults={}]
+ * @return {instance.web.SearchView}
+ */
+var makeSearchView = function (instance, dummy_widget_attributes, defaults) {
+ instance.web.search.fields.add(
+ 'dummy', 'instance.dummy.DummyWidget');
+ instance.dummy = {};
+ instance.dummy.DummyWidget = instance.web.search.Field.extend(
+ dummy_widget_attributes || {});
+ if (!('/web/searchview/load' in instance.session.responses)) {
+ instance.session.responses['/web/searchview/load'] = function () {
+ return {fields_view: {
+ type: 'search',
+ fields: {
+ dummy: {type: 'char', string: "Dummy"}
+ },
+ arch: {
+ tag: 'search',
+ attrs: {},
+ children: [{
+ tag: 'field',
+ attrs: {
+ name: 'dummy',
+ widget: 'dummy'
+ },
+ children: []
+ }]
+ }
+ }};
};
-
- var dataset = {model: 'dummy.model', get_context: function () { return {}; }};
- var view = new instance.web.SearchView(null, dataset, false, defaults);
- var self = this;
- view.on('invalid_search', self, function () {
- ok(false, JSON.stringify([].slice(arguments)));
- });
- return view;
}
- asyncTest('calling', 2, function () {
+ instance.session.responses['/web/searchview/get_filters'] = function () {
+ return [];
+ };
+ instance.session.responses['/web/searchview/fields_get'] = function () {
+ return {fields: {
+ dummy: {type: 'char', string: 'Dummy'}
+ }};
+ };
+
+ var dataset = {model: 'dummy.model', get_context: function () { return {}; }};
+ var view = new instance.web.SearchView(null, dataset, false, defaults);
+ var self = this;
+ view.on('invalid_search', self, function () {
+ ok(false, JSON.stringify([].slice(arguments)));
+ });
+ return view;
+};
+openerp.testing.section('defaults', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true,
+}, function (test) {
+ test('calling', {asserts: 2}, function (instance, $s) {
var defaults_called = false;
- var view = makeSearchView({
+ var view = makeSearchView(instance, {
facet_for_defaults: function (defaults) {
defaults_called = true;
return $.when({
@@ -216,9 +209,7 @@ $(document).ready(function () {
});
}
}, {dummy: 42});
- view.appendTo($('#qunit-fixture'))
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($s)
.done(function () {
ok(defaults_called, "should have called defaults");
deepEqual(
@@ -227,7 +218,7 @@ $(document).ready(function () {
"should have generated a facet with the default value");
});
});
- asyncTest('FilterGroup', 3, function () {
+ test('FilterGroup', {asserts: 3}, function (instance) {
var view = {inputs: [], query: {on: function () {}}};
var filter_a = new instance.web.search.Filter(
{attrs: {name: 'a'}}, view);
@@ -235,9 +226,7 @@ $(document).ready(function () {
{attrs: {name: 'b'}}, view);
var group = new instance.web.search.FilterGroup(
[filter_a, filter_b], view);
- group.facet_for_defaults({a: true, b: true})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ return group.facet_for_defaults({a: true, b: true})
.done(function (facet) {
var model = facet;
if (!(model instanceof instance.web.search.Facet)) {
@@ -249,13 +238,11 @@ $(document).ready(function () {
strictEqual(values.at(1).get('value'), filter_b);
});
});
- asyncTest('Field', 4, function () {
+ test('Field', {asserts: 4}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.Field(
{attrs: {string: 'Dummy', name: 'dummy'}}, {}, view);
- f.facet_for_defaults({dummy: 42})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ return f.facet_for_defaults({dummy: 42})
.done(function (facet) {
var model = facet;
if (!(model instanceof instance.web.search.Facet)) {
@@ -275,15 +262,13 @@ $(document).ready(function () {
"facet value should match provided default");
});
});
- asyncTest('Selection: valid value', 4, function () {
+ test('Selection: valid value', {asserts: 4}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.SelectionField(
{attrs: {name: 'dummy', string: 'Dummy'}},
{selection: [[1, "Foo"], [2, "Bar"], [3, "Baz"], [4, "Qux"]]},
view);
- f.facet_for_defaults({dummy: 3})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ return f.facet_for_defaults({dummy: 3})
.done(function (facet) {
var model = facet;
if (!(model instanceof instance.web.search.Facet)) {
@@ -303,36 +288,28 @@ $(document).ready(function () {
"facet value should match provided default's selection");
});
});
- asyncTest('Selection: invalid value', 1, function () {
+ test('Selection: invalid value', {asserts: 1}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.SelectionField(
{attrs: {name: 'dummy', string: 'Dummy'}},
{selection: [[1, "Foo"], [2, "Bar"], [3, "Baz"], [4, "Qux"]]},
view);
- f.facet_for_defaults({dummy: 42})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ return f.facet_for_defaults({dummy: 42})
.done(function (facet) {
ok(!facet, "an invalid value should result in a not-facet");
});
});
- asyncTest("M2O default: value", 7, function () {
+ test("M2O default: value", {asserts: 5}, function (instance, $s, mock) {
var view = {inputs: []}, id = 4;
var f = new instance.web.search.ManyToOneField(
{attrs: {name: 'dummy', string: 'Dummy'}},
{relation: 'dummy.model.name'},
view);
- instance.session.responses['/web/dataset/call_kw'] = function (req) {
- equal(req.params.method, 'name_get',
- "m2o should resolve default id");
- equal(req.params.model, f.attrs.relation,
- "query model should match m2o relation");
- equal(req.params.args[0], id);
- return {result: [[id, "DumDumDum"]]};
- };
- f.facet_for_defaults({dummy: id})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ mock('dummy.model.name:name_get', function (args) {
+ equal(args[0], id);
+ return [[id, "DumDumDum"]];
+ });
+ return f.facet_for_defaults({dummy: id})
.done(function (facet) {
var model = facet;
if (!(model instanceof instance.web.search.Facet)) {
@@ -352,34 +329,26 @@ $(document).ready(function () {
"facet value should match provided default's selection");
});
});
- asyncTest("M2O default: value", 1, function () {
+ test("M2O default: value", {asserts: 1}, function (instance, $s, mock) {
var view = {inputs: []}, id = 4;
var f = new instance.web.search.ManyToOneField(
{attrs: {name: 'dummy', string: 'Dummy'}},
{relation: 'dummy.model.name'},
view);
- instance.session.responses['/web/dataset/call_kw'] = function (req) {
- return {result: []};
- };
- f.facet_for_defaults({dummy: id})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ mock('dummy.model.name:name_get', function () { return [] });
+ return f.facet_for_defaults({dummy: id})
.done(function (facet) {
ok(!facet, "an invalid m2o default should yield a non-facet");
});
});
-
- module('completions', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
- });
- asyncTest('calling', 4, function () {
- var view = makeSearchView({
+});
+openerp.testing.section('completions', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('calling', {asserts: 4}, function (instance, $s) {
+ var view = makeSearchView(instance, {
complete: function () {
return $.when({
label: "Dummy",
@@ -391,10 +360,11 @@ $(document).ready(function () {
});
}
});
- view.appendTo($('#qunit-fixture'))
- .done(function () {
+ var done = $.Deferred();
+ view.appendTo($s)
+ .then(function () {
view.complete_global_search({term: "dum"}, function (completions) {
- start();
+ done.resolve();
equal(completions.length, 1, "should have a single completion");
var completion = completions[0];
equal(completion.label, "Dummy",
@@ -405,9 +375,10 @@ $(document).ready(function () {
[{label: 'dummy', value: 42}],
"should have provided values");
});
- });
+ }).fail(function () { done.reject.apply(done, arguments); });
+ return done;
});
- asyncTest('facet selection', 2, function () {
+ test('facet selection', {asserts: 2}, function (instance, $s) {
var completion = {
label: "Dummy",
facet: {
@@ -421,10 +392,8 @@ $(document).ready(function () {
}
};
- var view = makeSearchView({});
- view.appendTo($('#qunit-fixture'))
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ var view = makeSearchView(instance);
+ return view.appendTo($s)
.done(function () {
view.select_completion(
{preventDefault: function () {}},
@@ -436,7 +405,7 @@ $(document).ready(function () {
"should have the right facet in the query");
});
});
- asyncTest('facet selection: new value existing facet', 3, function () {
+ test('facet selection: new value existing facet', {asserts: 3}, function (instance, $s) {
var field = {
get_domain: openerp.testing.noop,
get_context: openerp.testing.noop,
@@ -451,10 +420,8 @@ $(document).ready(function () {
}
};
- var view = makeSearchView({});
- view.appendTo($('#qunit-fixture'))
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ var view = makeSearchView(instance);
+ return view.appendTo($s)
.done(function () {
view.query.add({field: field, category: 'Dummy',
values: [{label: 'previous', value: 41}]});
@@ -470,23 +437,19 @@ $(document).ready(function () {
"should have added selected value to old one");
});
});
- asyncTest('Field', 1, function () {
+ test('Field', {asserts: 1}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.Field({attrs: {}}, {}, view);
- f.complete('foo')
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete('foo')
.done(function (completions) {
ok(_(completions).isEmpty(), "field should not provide any completion");
});
});
- asyncTest('CharField', 6, function () {
+ test('CharField', {asserts: 6}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.CharField(
{attrs: {string: "Dummy"}}, {}, view);
- f.complete('foo<')
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete('foo<')
.done(function (completions) {
equal(completions.length, 1, "should provide a single completion");
var c = completions[0];
@@ -503,15 +466,13 @@ $(document).ready(function () {
"facet should have single value using completion item");
});
});
- asyncTest('Selection: match found', 14, function () {
+ test('Selection: match found', {asserts: 14}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.SelectionField(
{attrs: {string: "Dummy"}},
{selection: [[1, "Foo"], [2, "Bar"], [3, "Baz"], [4, "Bazador"]]},
view);
- f.complete("ba")
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete("ba")
.done(function (completions) {
equal(completions.length, 4,
"should provide two completions and a section title");
@@ -536,20 +497,18 @@ $(document).ready(function () {
deepEqual(c3.facet.values, [{label: "Bazador", value: 4}]);
});
});
- asyncTest('Selection: no match', 1, function () {
+ test('Selection: no match', {asserts: 1}, function (instance) {
var view = {inputs: []};
var f = new instance.web.search.SelectionField(
{attrs: {string: "Dummy"}},
{selection: [[1, "Foo"], [2, "Bar"], [3, "Baz"], [4, "Bazador"]]},
view);
- f.complete("qux")
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete("qux")
.done(function (completions) {
ok(!completions, "if no value matches the needle, no completion shall be provided");
});
});
- asyncTest('Date', 6, function () {
+ test('Date', {asserts: 6}, function (instance) {
instance.web._t.database.parameters = {
date_format: '%Y-%m-%d',
time_format: '%H:%M:%S'
@@ -557,9 +516,7 @@ $(document).ready(function () {
var view = {inputs: []};
var f = new instance.web.search.DateField(
{attrs: {string: "Dummy"}}, {type: 'datetime'}, view);
- f.complete('2012-05-21T21:21:21')
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete('2012-05-21T21:21:21')
.done(function (completions) {
equal(completions.length, 1, "should provide a single completion");
var c = completions[0];
@@ -573,21 +530,17 @@ $(document).ready(function () {
new Date(2012, 4, 21, 21, 21, 21).getTime());
});
});
- asyncTest("M2O", 15, function () {
- instance.session.responses['/web/dataset/call_kw'] = function (req) {
- equal(req.params.method, "name_search");
- equal(req.params.model, "dummy.model");
- deepEqual(req.params.args, []);
- deepEqual(req.params.kwargs.name, 'bob');
- return {result: [[42, "choice 1"], [43, "choice @"]]}
- };
+ test("M2O", {asserts: 13}, function (instance, $s, mock) {
+ mock('dummy.model:name_search', function (args, kwargs) {
+ deepEqual(args, []);
+ strictEqual(kwargs.name, 'bob');
+ return [[42, "choice 1"], [43, "choice @"]];
+ });
var view = {inputs: []};
var f = new instance.web.search.ManyToOneField(
{attrs: {string: 'Dummy'}}, {relation: 'dummy.model'}, view);
- f.complete("bob")
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete("bob")
.done(function (c) {
equal(c.length, 3, "should return results + title");
var title = c[0];
@@ -607,38 +560,29 @@ $(document).ready(function () {
deepEqual(f2.values.toJSON(), [{label: 'choice @', value: 43}]);
});
});
- asyncTest("M2O no match", 5, function () {
- instance.session.responses['/web/dataset/call_kw'] = function (req) {
- equal(req.params.method, "name_search");
- equal(req.params.model, "dummy.model");
- deepEqual(req.params.args, []);
- deepEqual(req.params.kwargs.name, 'bob');
- return {result: []}
- };
+ test("M2O no match", {asserts: 3}, function (instance, $s, mock) {
+ mock('dummy.model:name_search', function (args, kwargs) {
+ deepEqual(args, []);
+ strictEqual(kwargs.name, 'bob');
+ return [];
+ });
var view = {inputs: []};
var f = new instance.web.search.ManyToOneField(
{attrs: {string: 'Dummy'}}, {relation: 'dummy.model'}, view);
- f.complete("bob")
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return f.complete("bob")
.done(function (c) {
ok(!c, "no match should yield no completion");
});
});
-
- module('search-serialization', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
- });
- asyncTest('No facet, no call', 6, function () {
+});
+openerp.testing.section('search-serialization', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('No facet, no call', {asserts: 6}, function (instance, $s) {
var got_domain = false, got_context = false, got_groupby = false;
- var $fix = $('#qunit-fixture');
- var view = makeSearchView({
+ var view = makeSearchView(instance, {
get_domain: function () {
got_domain = true;
return null;
@@ -654,11 +598,9 @@ $(document).ready(function () {
});
var ds, cs, gs;
view.on('search_data', this, function (d, c, g) {
- ds = d, cs = c, gs = g;
+ ds = d; cs = c; gs = g;
});
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($s)
.done(function () {
view.do_search();
ok(!got_domain, "no facet, should not have fetched domain");
@@ -671,10 +613,9 @@ $(document).ready(function () {
ok(_(gs).isEmpty(), "groupby list should be empty");
})
});
- asyncTest('London, calling', 8, function () {
+ test('London, calling', {asserts: 8}, function (instance, $fix) {
var got_domain = false, got_context = false, got_groupby = false;
- var $fix = $('#qunit-fixture');
- var view = makeSearchView({
+ var view = makeSearchView(instance, {
get_domain: function (facet) {
equal(facet.get('category'), "Dummy");
deepEqual(facet.values.toJSON(), [{label: "42", value: 42}]);
@@ -692,11 +633,9 @@ $(document).ready(function () {
}, {dummy: 42});
var ds, cs, gs;
view.on('search_data', this, function (d, c, g) {
- ds = d, cs = c, gs = g;
+ ds = d; cs = c; gs = g;
});
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
view.do_search();
ok(got_domain, "should have fetched domain");
@@ -709,9 +648,8 @@ $(document).ready(function () {
ok(_(gs).isEmpty(), "groupby list should be empty");
})
});
- asyncTest('Generate domains', 1, function () {
- var $fix = $('#qunit-fixture');
- var view = makeSearchView({
+ test('Generate domains', {asserts: 1}, function (instance, $fix) {
+ var view = makeSearchView(instance, {
get_domain: function (facet) {
return facet.values.map(function (value) {
return ['win', '4', value.get('value')];
@@ -720,9 +658,7 @@ $(document).ready(function () {
}, {dummy: 42});
var ds;
view.on('search_data', this, function (d) { ds = d; });
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
view.do_search();
deepEqual(ds, [[['win', '4', 42]]],
@@ -730,7 +666,9 @@ $(document).ready(function () {
});
});
- test('Field single value, default domain & context', function () {
+ test('Field single value, default domain & context', {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.Field({}, {name: 'foo'}, {inputs: []});
var facet = new instance.web.search.Facet({
field: f,
@@ -742,7 +680,9 @@ $(document).ready(function () {
equal(f.get_context(facet), null,
"default field context is null");
});
- test('Field multiple values, default domain & context', function () {
+ test('Field multiple values, default domain & context', {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.Field({}, {name: 'foo'}, {inputs: []});
var facet = new instance.web.search.Facet({
field: f,
@@ -763,7 +703,9 @@ $(document).ready(function () {
equal(f.get_context(facet), null,
"default field context is null");
});
- test('Field single value, custom domain & context', function () {
+ test('Field single value, custom domain & context', {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.Field({attrs:{
context: "{'bob': self}",
filter_domain: "[['edmund', 'is', self]]"
@@ -793,7 +735,9 @@ $(document).ready(function () {
self: "great"
}, "evaluation context should hold facet value as self");
});
- test("M2O default", function () {
+ test("M2O default", {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.ManyToOneField(
{}, {name: 'foo'}, {inputs: []});
var facet = new instance.web.search.Facet({
@@ -806,7 +750,9 @@ $(document).ready(function () {
deepEqual(f.get_context(facet), {default_foo: 42},
"m2o should use value as context default");
});
- test("M2O default multiple values", function () {
+ test("M2O default multiple values", {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.ManyToOneField(
{}, {name: 'foo'}, {inputs: []});
var facet = new instance.web.search.Facet({
@@ -823,7 +769,9 @@ $(document).ready(function () {
equal(f.get_context(facet), null,
"m2o should not have default context in case of multiple values");
});
- test("M2O custom operator", function () {
+ test("M2O custom operator", {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.ManyToOneField(
{attrs: {operator: 'boos'}}, {name: 'foo'}, {inputs: []});
var facet = new instance.web.search.Facet({
@@ -836,7 +784,9 @@ $(document).ready(function () {
deepEqual(f.get_context(facet), {default_foo: 42},
"m2o should use value as context default");
});
- test("M2O custom domain & context", function () {
+ test("M2O custom domain & context", {
+ rpc: false
+ }, function (instance) {
var f = new instance.web.search.ManyToOneField({attrs: {
context: "{'whee': self}",
filter_domain: "[['filter', 'is', self]]"
@@ -862,7 +812,7 @@ $(document).ready(function () {
}, "custom context's self should be label");
});
- asyncTest('FilterGroup', 6, function () {
+ test('FilterGroup', {asserts: 6}, function (instance) {
var view = {inputs: [], query: {on: function () {}}};
var filter_a = new instance.web.search.Filter(
{attrs: {name: 'a', context: 'c1', domain: 'd1'}}, view);
@@ -872,9 +822,7 @@ $(document).ready(function () {
{attrs: {name: 'c', context: 'c3', domain: 'd3'}}, view);
var group = new instance.web.search.FilterGroup(
[filter_a, filter_b, filter_c], view);
- group.facet_for_defaults({a: true, c: true})
- .always(start)
- .fail(function (error) { ok(false, error && error.message); })
+ return group.facet_for_defaults({a: true, c: true})
.done(function (facet) {
var model = facet;
if (!(model instanceof instance.web.search.Facet)) {
@@ -897,19 +845,14 @@ $(document).ready(function () {
ok(!context.get_eval_context(), "context should have no evaluation context");
});
});
-
- module('removal', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
- });
- asyncTest('clear button', function () {
- var $fix = $('#qunit-fixture');
- var view = makeSearchView({
+});
+openerp.testing.section('removal', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('clear button', {asserts: 2}, function (instance, $fix) {
+ var view = makeSearchView(instance, {
facet_for_defaults: function (defaults) {
return $.when({
field: this,
@@ -918,31 +861,22 @@ $(document).ready(function () {
});
}
}, {dummy: 42});
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
equal(view.query.length, 1, "view should have default facet");
$fix.find('.oe_searchview_clear').click();
equal(view.query.length, 0, "cleared view should not have any facet");
});
});
-
- module('drawer', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
- });
- asyncTest('is-drawn', 2, function () {
- var view = makeSearchView();
- var $fix = $('#qunit-fixture');
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+});
+openerp.testing.section('drawer', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('is-drawn', {asserts: 2}, function (instance, $fix) {
+ var view = makeSearchView(instance);
+ return view.appendTo($fix)
.done(function () {
ok($fix.find('.oe_searchview_filters').length,
"filters drawer control has been drawn");
@@ -950,50 +884,44 @@ $(document).ready(function () {
"filters advanced search has been drawn");
});
});
-
- module('filters', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance, {
- '/web/searchview/load': function () {
- // view with a single group of filters
- return {result: {fields_view: {
- type: 'search',
- fields: {},
- arch: {
- tag: 'search',
- attrs: {},
- children: [{
- tag: 'filter',
- attrs: { string: "Foo1", domain: [ ['foo', '=', '1'] ] },
- children: []
- }, {
- tag: 'filter',
- attrs: {
- name: 'foo2',
- string: "Foo2",
- domain: [ ['foo', '=', '2'] ] },
- children: []
- }, {
- tag: 'filter',
- attrs: { string: "Foo3", domain: [ ['foo', '=', '3'] ] },
- children: []
- }]
- }
- }}};
+});
+openerp.testing.section('filters', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true,
+ setup: function (instance, $s, mock) {
+ mock('/web/searchview/load', function () {
+ // view with a single group of filters
+ return {fields_view: {
+ type: 'search',
+ fields: {},
+ arch: {
+ tag: 'search',
+ attrs: {},
+ children: [{
+ tag: 'filter',
+ attrs: { string: "Foo1", domain: [ ['foo', '=', '1'] ] },
+ children: []
+ }, {
+ tag: 'filter',
+ attrs: {
+ name: 'foo2',
+ string: "Foo2",
+ domain: [ ['foo', '=', '2'] ] },
+ children: []
+ }, {
+ tag: 'filter',
+ attrs: { string: "Foo3", domain: [ ['foo', '=', '3'] ] },
+ children: []
+ }]
}
- });
- }
- });
- asyncTest('drawn', 3, function () {
- var view = makeSearchView();
- var $fix = $('#qunit-fixture');
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ }};
+ });
+ }
+}, function (test) {
+ test('drawn', {asserts: 3}, function (instance, $fix) {
+ var view = makeSearchView(instance);
+ return view.appendTo($fix)
.done(function () {
var $fs = $fix.find('.oe_searchview_filters ul');
// 3 filters, 1 filtergroup, 1 custom filters widget,
@@ -1006,12 +934,9 @@ $(document).ready(function () {
"Text content of first filter option should match filter string");
});
});
- asyncTest('click adding from empty query', 4, function () {
- var view = makeSearchView();
- var $fix = $('#qunit-fixture');
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ test('click adding from empty query', {asserts: 4}, function (instance, $fix) {
+ var view = makeSearchView(instance);
+ return view.appendTo($fix)
.done(function () {
var $fs = $fix.find('.oe_searchview_filters ul');
$fs.children(':eq(2)').trigger('click');
@@ -1025,12 +950,9 @@ $(document).ready(function () {
"value should be third filter");
});
});
- asyncTest('click adding from existing query', 4, function () {
- var view = makeSearchView({}, {foo2: true});
- var $fix = $('#qunit-fixture');
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ test('click adding from existing query', {asserts: 4}, function (instance, $fix) {
+ var view = makeSearchView(instance, {}, {foo2: true});
+ return view.appendTo($fix)
.done(function () {
var $fs = $fix.find('.oe_searchview_filters ul');
$fs.children(':eq(2)').trigger('click');
@@ -1045,16 +967,13 @@ $(document).ready(function () {
"second value should be clicked filter");
});
});
- asyncTest('click removing from query', 4, function () {
+ test('click removing from query', {asserts: 4}, function (instance, $fix) {
var calls = 0;
- var view = makeSearchView({}, {foo2: true});
+ var view = makeSearchView(instance, {}, {foo2: true});
view.on('search_data', null, function () {
++calls;
});
- var $fix = $('#qunit-fixture');
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
var $fs = $fix.find('.oe_searchview_filters ul');
// sanity check
@@ -1065,29 +984,19 @@ $(document).ready(function () {
strictEqual(calls, 1, "one search should have been triggered");
});
});
+});
+openerp.testing.section('saved_filters', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('checkboxing', {asserts: 6}, function (instance, $fix, mock) {
+ var view = makeSearchView(instance);
+ mock('/web/searchview/get_filters', function () {
+ return [{ name: "filter name", user_id: 42 }];
+ });
- module('saved_filters', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
- });
- asyncTest('checkboxing', 6, function () {
- var view = makeSearchView();
- instance.session.responses['/web/searchview/get_filters'] = function () {
- return {result: [{
- name: "filter name",
- user_id: 42
- }]};
- };
- var $fix = $('#qunit-fixture');
-
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
var $row = $fix.find('.oe_searchview_custom li:first').click();
@@ -1104,44 +1013,31 @@ $(document).ready(function () {
"should have no value set");
})
});
- asyncTest('removal', 1, function () {
- var view = makeSearchView();
- instance.session.responses['/web/searchview/get_filters'] = function () {
- return {result: [{
- name: "filter name",
- user_id: 42
- }]};
- };
- var $fix = $('#qunit-fixture');
+ test('removal', {asserts: 1}, function (instance, $fix, mock) {
+ var view = makeSearchView(instance);
+ mock('/web/searchview/get_filters', function () {
+ return [{ name: "filter name", user_id: 42 }];
+ });
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
var $row = $fix.find('.oe_searchview_custom li:first').click();
view.query.remove(view.query.at(0));
ok(!$row.hasClass('oe_selected'),
"should not be checked anymore");
- })
- });
-
- module('advanced', {
- setup: function () {
- instance = openerp.testing.instanceFor('search');
-
- openerp.testing.loadTemplate(instance);
-
- openerp.testing.mockifyRPC(instance);
- }
+ });
});
- asyncTest('single-advanced', 6, function () {
- var view = makeSearchView();
- var $fix = $('#qunit-fixture');
-
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+});
+openerp.testing.section('advanced', {
+ dependencies: ['web.search'],
+ rpc: 'mock',
+ templates: true
+}, function (test) {
+ test('single-advanced', {asserts: 6}, function (instance, $fix) {
+ var view = makeSearchView(instance);
+
+ return view.appendTo($fix)
.done(function () {
var $advanced = $fix.find('.oe_searchview_advanced');
// open advanced search (not actually useful)
@@ -1174,13 +1070,10 @@ $(document).ready(function () {
"advanced search facet should return proposed domain");
});
});
- asyncTest('multiple-advanced', 3, function () {
- var view = makeSearchView();
- var $fix = $('#qunit-fixture');
+ test('multiple-advanced', {asserts: 3}, function (instance, $fix) {
+ var view = makeSearchView(instance);
- view.appendTo($fix)
- .always(start)
- .fail(function (error) { ok(false, error.message); })
+ return view.appendTo($fix)
.done(function () {
var $advanced = $fix.find('.oe_searchview_advanced');
// open advanced search (not actually useful)
diff --git a/addons/web/static/test/test.html b/addons/web/static/test/test.html
deleted file mode 100644
index 275db0f37a7..00000000000
--- a/addons/web/static/test/test.html
+++ /dev/null
@@ -1,63 +0,0 @@
-<!DOCTYPE html>
-<html style="height: 100%">
-<head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <title>OpenERP Web Test Suite</title>
- <link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
-
- <link rel="stylesheet" href="/web/static/lib/qunit/qunit.css">
- <script src="/web/static/lib/qunit/qunit.js" type="text/javascript"></script>
-
- <script src="/web/static/lib/underscore/underscore.js" type="text/javascript"></script>
- <script src="/web/static/lib/underscore/underscore.string.js" type="text/javascript"></script>
- <script src="/web/static/lib/backbone/backbone.js" type="text/javascript"></script>
-
- <!-- jquery -->
- <script src="/web/static/lib/jquery/jquery-1.8.2.js"></script>
- <script src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
- <script src="/web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script>
-
- <script src="/web/static/lib/datejs/globalization/en-US.js"></script>
- <script src="/web/static/lib/datejs/core.js"></script>
- <script src="/web/static/lib/datejs/parser.js"></script>
- <script src="/web/static/lib/datejs/sugarpak.js"></script>
- <script src="/web/static/lib/datejs/extras.js"></script>
-
- <script src="/web/static/lib/qweb/qweb2.js"></script>
-
- <script src="/web/static/lib/py.js/lib/py.js"></script>
-
- <script src="/web/static/src/js/boot.js"></script>
- <script src="/web/static/src/js/corelib.js"></script>
- <script src="/web/static/src/js/coresetup.js"></script>
- <script src="/web/static/src/js/dates.js"></script>
- <script src="/web/static/src/js/formats.js"></script>
- <script src="/web/static/src/js/chrome.js"></script>
- <script src="/web/static/src/js/data.js"></script>
- <script src="/web/static/src/js/views.js"></script>
- <script src="/web/static/src/js/search.js"></script>
- <script src="/web/static/src/js/view_form.js"></script>
- <script src="/web/static/src/js/view_list.js"></script>
- <script src="/web/static/src/js/view_list_editable.js"></script>
-
- <script src="/web/static/test/testing.js"></script>
- <script type="text/javascript">
- QUnit.config.testTimeout = 2000;
- </script>
-</head>
- <body id="oe" class="openerp">
- <div id="qunit"></div>
- <div id="qunit-fixture"></div>
- </body>
- <script type="text/javascript" src="/web/static/test/class.js"></script>
- <script type="text/javascript" src="/web/static/test/registry.js"></script>
- <script type="text/javascript" src="/web/static/test/form.js"></script>
- <script type="text/javascript" src="/web/static/test/list-utils.js"></script>
- <script type="text/javascript" src="/web/static/test/formats.js"></script>
- <script type="text/javascript" src="/web/static/test/rpc.js"></script>
- <script type="text/javascript" src="/web/static/test/evals.js"></script>
- <script type="text/javascript" src="/web/static/test/search.js"></script>
- <script type="text/javascript" src="/web/static/test/Widget.js"></script>
- <script type="text/javascript" src="/web/static/test/list.js"></script>
- <script type="text/javascript" src="/web/static/test/list-editable.js"></script>
-</html>
diff --git a/addons/web/static/test/testing.js b/addons/web/static/test/testing.js
index 1be8e94e74b..5bd008ad7ec 100644
--- a/addons/web/static/test/testing.js
+++ b/addons/web/static/test/testing.js
@@ -1,97 +1,244 @@
-// Test support structures and methods for OpenERP
-openerp.testing = (function () {
- var xhr = QWeb2.Engine.prototype.get_xhr();
- xhr.open('GET', '/web/static/src/xml/base.xml', false);
- xhr.send(null);
- var doc = xhr.responseXML;
-
- var dependencies = {
- corelib: [],
- coresetup: ['corelib'],
- data: ['corelib', 'coresetup'],
- dates: [],
- formats: ['coresetup', 'dates'],
- chrome: ['corelib', 'coresetup'],
- views: ['corelib', 'coresetup', 'data', 'chrome'],
- search: ['data', 'coresetup', 'formats'],
- list: ['views', 'data'],
- form: ['data', 'views', 'list', 'formats'],
- list_editable: ['list', 'form', 'data'],
+openerp.testing.section('testing.stack', function (test) {
+ // I heard you like tests, so I put tests in your testing infrastructure,
+ // so you can test what you test
+ var reject = function () {
+ // utility function, rejects a success
+ var args = _.toArray(arguments);
+ return $.Deferred(function (d) {
+ d.reject.apply(d, ["unexpected success"].concat(args));
+ });
};
+ test('direct, value, success', {asserts: 1}, function () {
+ var s = openerp.testing.Stack();
+ return s.execute(function () {
+ return 42;
+ }).then(function (val) {
+ strictEqual(val, 42, "should return the handler value");
+ });
+ });
+ test('direct, deferred, success', {asserts: 1}, function () {
+ var s = openerp.testing.Stack();
+ return s.execute(function () {
+ return $.when(42);
+ }).then(function (val) {
+ strictEqual(val, 42, "should return the handler value")
+ });
+ });
+ test('direct, deferred, failure', {asserts: 1}, function () {
+ var s = openerp.testing.Stack();
+ return s.execute(function () {
+ return $.Deferred(function (d) {
+ d.reject("failed");
+ });
+ }).then(reject, function (f) {
+ strictEqual(f, "failed", "should propagate failure");
+ return $.when();
+ });
+ });
+
+ test('successful setup', {asserts: 2}, function () {
+ var setup_done = false;
+ var s = openerp.testing.Stack();
+ return s.push(function () {
+ return $.Deferred(function (d) {
+ setTimeout(function () {
+ setup_done = true;
+ d.resolve(2);
+ }, 50);
+ });
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ ok(setup_done, "should have executed setup");
+ strictEqual(val, 42, "should return executed function value (not setup)");
+ });
+ });
+ test('successful teardown', {asserts: 2}, function () {
+ var teardown = false;
+ var s = openerp.testing.Stack();
+ return s.push(null, function () {
+ return $.Deferred(function (d) {
+ setTimeout(function () {
+ teardown = true;
+ d.resolve(2);
+ }, 50);
+ });
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ ok(teardown, "should have executed teardown");
+ strictEqual(val, 42, "should return executed function value (not setup)");
+ });
+ });
+ test('successful setup and teardown', {asserts: 3}, function () {
+ var setup = false, teardown = false;
+ var s = openerp.testing.Stack();
+ return s.push(function () {
+ return $.Deferred(function (d) {
+ setTimeout(function () {
+ setup = true;
+ d.resolve(2);
+ }, 50);
+ });
+ }, function () {
+ return $.Deferred(function (d) {
+ setTimeout(function () {
+ teardown = true;
+ d.resolve(2);
+ }, 50);
+ });
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ ok(setup, "should have executed setup");
+ ok(teardown, "should have executed teardown");
+ strictEqual(val, 42, "should return executed function value (not setup)");
+ });
+ });
+
+ test('multiple setups', {asserts: 2}, function () {
+ var setups = 0;
+ var s = openerp.testing.Stack();
+ return s.push(function () {
+ setups++;
+ }).push(function () {
+ setups++;
+ }).push(function () {
+ setups++;
+ }).push(function () {
+ setups++;
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ strictEqual(setups, 4, "should have executed all setups of stack");
+ strictEqual(val, 42);
+ });
+ });
+ test('multiple teardowns', {asserts: 2}, function () {
+ var teardowns = 0;
+ var s = openerp.testing.Stack();
+ return s.push(null, function () {
+ teardowns++;
+ }).push(null, function () {
+ teardowns++;
+ }).push(null, function () {
+ teardowns++;
+ }).push(null, function () {
+ teardowns++;
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ strictEqual(teardowns, 4, "should have executed all teardowns of stack");
+ strictEqual(val, 42);
+ });
+ });
+ test('holes in setups', {asserts: 2}, function () {
+ var setups = [];
+ var s = openerp.testing.Stack();
+ return s.push(function () {
+ setups.push(0);
+ }).push().push().push(function () {
+ setups.push(3);
+ }).push(function () {
+ setups.push(4);
+ }).push().push(function () {
+ setups.push(6);
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ deepEqual(setups, [0, 3, 4, 6],
+ "should have executed setups in correct order");
+ strictEqual(val, 42);
+ });
+ });
+ test('holes in teardowns', {asserts: 2}, function () {
+ var teardowns = [];
+ var s = openerp.testing.Stack();
+ return s.push(null, function () {
+ teardowns.push(0);
+ }).push().push().push(null, function () {
+ teardowns.push(3);
+ }).push(null, function () {
+ teardowns.push(4);
+ }).push().push(null, function () {
+ teardowns.push(6);
+ }).execute(function () {
+ return 42;
+ }).then(function (val) {
+ deepEqual(teardowns, [6, 4, 3, 0],
+ "should have executed teardowns in correct order");
+ strictEqual(val, 42);
+ });
- return {
- /**
- * Function which does not do anything
- */
- noop: function () { },
- /**
- * Loads 'base.xml' template file into qweb for the provided instance
- *
- * @param instance openerp instance being initialized, to load the template file in
- */
- loadTemplate: function (instance) {
- instance.web.qweb.add_template(doc);
- },
- /**
- * Alter provided instance's ``session`` attribute to make response
- * mockable:
- *
- * * The ``responses`` parameter can be used to provide a map of (RPC)
- * paths (e.g. ``/web/view/load``) to a function returning a response
- * to the query.
- * * ``instance.session`` grows a ``responses`` attribute which is
- * a map of the same (and is in fact initialized to the ``responses``
- * parameter if one is provided)
- *
- * Note that RPC requests to un-mocked URLs will be rejected with an
- * error message: only explicitly specified urls will get a response.
- *
- * Mocked sessions will *never* perform an actual RPC connection.
- *
- * @param instance openerp instance being initialized
- * @param {Object} [responses]
- */
- mockifyRPC: function (instance, responses) {
- var session = instance.session;
- session.responses = responses || {};
- session.rpc_function = function (url, payload) {
- var fn = this.responses[url.url + ':' + payload.params.method]
- || this.responses[url.url];
+ });
- if (!fn) {
- return $.Deferred().reject({}, 'failed',
- _.str.sprintf("Url %s not found in mock responses, with arguments %s",
- url.url, JSON.stringify(payload.params))
- ).promise();
- }
- return $.when(fn(payload));
- };
- },
- /**
- * Creates an openerp web instance loading the specified module after
- * all of its dependencies.
- *
- * @param {String} module
- * @returns OpenERP Web instance
- */
- instanceFor: function (module) {
- var instance = openerp.init([]);
- this._load(instance, module);
- return instance;
- },
- _load: function (instance, module, loaded) {
- if (!loaded) { loaded = []; }
+ test('failed setup', {asserts: 5}, function () {
+ var setup, teardown, teardown2, code;
+ return openerp.testing.Stack().push(function () {
+ setup = true;
+ }, function () {
+ teardown = true;
+ }).push(function () {
+ return $.Deferred().reject("Fail!");
+ }, function () {
+ teardown2 = true;
+ }).execute(function () {
+ code = true;
+ return 42;
+ }).then(reject, function (m) {
+ ok(setup, "should have executed first setup function");
+ ok(teardown, "should have executed first teardown function");
+ ok(!teardown2, "should not have executed second teardown function");
+ strictEqual(m, "Fail!", "should return setup failure message");
+ ok(!code, "should not have executed callback");
+ return $.when();
+ });
+ });
+ test('failed teardown', {asserts: 2}, function () {
+ var teardowns = 0;
+ return openerp.testing.Stack().push(null, function () {
+ teardowns++;
+ return $.Deferred().reject('Fail 1');
+ }).push(null, function () {
+ teardowns++;
+ }).push(null, function () {
+ teardowns++;
+ return $.Deferred().reject('Fail 3');
+ }).execute(function () {
+ return 42;
+ }).then(reject, function (m) {
+ strictEqual(teardowns, 3,
+ "should have tried executing all teardowns");
+ strictEqual(m, "Fail 3", "should return first failure message");
+ return $.when();
+ });
+ });
+ test('failed call + teardown', {asserts: 2}, function () {
+ var teardowns = 0;
+ return openerp.testing.Stack().push(null, function () {
+ teardowns++;
+ }).push(null, function () {
+ teardowns++;
+ return $.Deferred().reject('Fail 2');
+ }).execute(function () {
+ return $.Deferred().reject("code");
+ }).then(reject, function (m) {
+ strictEqual(teardowns, 2,
+ "should have tried executing all teardowns");
+ strictEqual(m, "code", "should return first failure message");
+ return $.when();
+ });
+ });
- var deps = dependencies[module];
- if (!deps) { throw new Error("Unknown dependencies for " + module); }
+ test('arguments passing', {asserts: 9}, function () {
+ var asserter = function (a, b, c) {
+ strictEqual(a, 1);
+ strictEqual(b, "foo");
+ deepEqual(c, {bar: "baz", qux: 42});
+ };
- var to_load = _.difference(deps, loaded);
- while (!_.isEmpty(to_load)) {
- this._load(instance, to_load[0], loaded);
- to_load = _.difference(deps, loaded);
- }
- openerp.web[module](instance);
- loaded.push(module);
- }
- }
-})();
+ return openerp.testing.Stack()
+ .push(asserter, asserter)
+ .execute(asserter, 1, "foo", {bar: 'baz', qux: 42});
+ });
+});
diff --git a/addons/web/test_support/__init__.py b/addons/web/test_support/__init__.py
deleted file mode 100644
index 59f6cc67ca2..00000000000
--- a/addons/web/test_support/__init__.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-import xmlrpclib
-from ..common.openerplib.main import Connector
-
-execute_map = {}
-
-class TestConnector(Connector):
- def db_list_lang(self):
- return [('en_US', u'Test Language')]
-
- def common_authenticate(self, db, login, password, environment):
- return 87539319
-
- def common_login(self, db, login, password):
- return self.common_authenticate(db, login, password, {})
-
- def object_execute_kw(self, db, uid, password, model, method, args, kwargs):
- if model in execute_map and hasattr(execute_map[model], method):
- return getattr(execute_map[model], method)(*args, **kwargs)
-
- raise xmlrpclib.Fault({
- 'model': model,
- 'method': method,
- 'args': args,
- 'kwargs': kwargs
- }, '')
-
- def send(self, service_name, method, *args):
- method_name = '%s_%s' % (service_name, method)
- if hasattr(self, method_name):
- return getattr(self, method_name)(*args)
-
- raise xmlrpclib.Fault({
- 'service': service_name,
- 'method': method,
- 'args': args
- }, '')
diff --git a/addons/web/test_support/controllers.py b/addons/web/test_support/controllers.py
deleted file mode 100644
index f1013fbaaf8..00000000000
--- a/addons/web/test_support/controllers.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from ..common import http, nonliterals
-from ..controllers.main import Session
-
-UID = 87539319
-DB = 'test_db'
-LOGIN = 'test_login'
-PASSWORD = 'test_password'
-CONTEXT = {'lang': 'en_US', 'tz': 'UTC', 'uid': UID}
-
-def bind(session):
- session.bind(DB, UID, LOGIN, PASSWORD)
- session.context = CONTEXT
- session.build_connection().set_login_info(DB, LOGIN, PASSWORD, UID)
-
-class TestController(http.Controller):
- _cp_path = '/tests'
-
- @http.jsonrequest
- def add_nonliterals(self, req, domains, contexts):
- return {
- 'domains': [nonliterals.Domain(req.session, domain)
- for domain in domains],
- 'contexts': [nonliterals.Context(req.session, context)
- for context in contexts]
- }
-
-class TestSession(Session):
- _cp_path = '/web/session'
-
- def session_info(self, req):
- if not req.session._uid:
- bind(req.session)
-
- return {
- "session_id": req.session_id,
- "uid": req.session._uid,
- "context": CONTEXT,
- "db": req.session._db,
- "login": req.session._login,
- }
diff --git a/addons/web/tests/__init__.py b/addons/web/tests/__init__.py
index fb1a593bc78..8cb2118e0b6 100644
--- a/addons/web/tests/__init__.py
+++ b/addons/web/tests/__init__.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from . import test_dataset, test_menu, test_serving_base, test_view
+from . import test_dataset, test_menu, test_serving_base, test_view, test_js
fast_suite = []
checks = [
diff --git a/addons/web/tests/test_js.py b/addons/web/tests/test_js.py
new file mode 100644
index 00000000000..a126ce4b2f3
--- /dev/null
+++ b/addons/web/tests/test_js.py
@@ -0,0 +1,25 @@
+import urlparse
+from openerp import sql_db, tools
+from qunitsuite.suite import QUnitSuite
+
+class WebSuite(QUnitSuite):
+ def __init__(self):
+ url = urlparse.urlunsplit([
+ 'http',
+ 'localhost:{port}'.format(port=tools.config['xmlrpc_port']),
+ '/web/tests',
+ 'mod=*&source={db}&supadmin={supadmin}&password={password}'.format(
+ db=tools.config['db_name'],
+ supadmin=tools.config['db_password'] or 'admin',
+ password=tools.config['admin_passwd'] or 'admin'),
+ ''
+ ])
+ super(WebSuite, self).__init__(url, 50000)
+ def run(self, result):
+ if sql_db._Pool is not None:
+ sql_db._Pool.close_all(sql_db.dsn(tools.config['db_name']))
+ return super(WebSuite, self).run(result)
+
+def load_tests(loader, standard_tests, _):
+ standard_tests.addTest(WebSuite())
+ return standard_tests
diff --git a/addons/web_graph/static/lib/flotr2/lib/bean.js b/addons/web_graph/static/lib/flotr2/lib/bean.js
index 6e6e3ef4eb5..1a854771006 100644
--- a/addons/web_graph/static/lib/flotr2/lib/bean.js
+++ b/addons/web_graph/static/lib/flotr2/lib/bean.js
@@ -9,9 +9,7 @@
*/
/*global module:true, define:true*/
!function (name, context, definition) {
- if (typeof module !== 'undefined') module.exports = definition(name, context);
- else if (typeof define === 'function' && typeof define.amd === 'object') define(definition);
- else context[name] = definition(name, context);
+ context[name] = definition(name, context);
}('bean', this, function (name, context) {
var win = window
, old = context[name]
diff --git a/addons/web_tests_demo/__init__.py b/addons/web_tests_demo/__init__.py
new file mode 100644
index 00000000000..137d472ec74
--- /dev/null
+++ b/addons/web_tests_demo/__init__.py
@@ -0,0 +1,14 @@
+from openerp.osv import orm, fields
+
+class TestObject(orm.Model):
+ _name = 'web_tests_demo.model'
+
+ _columns = {
+ 'name': fields.char("Name", required=True),
+ 'thing': fields.char("Thing"),
+ 'other': fields.char("Other", required=True)
+ }
+ _defaults = {
+ 'other': "bob"
+ }
+
diff --git a/addons/web_tests_demo/__openerp__.py b/addons/web_tests_demo/__openerp__.py
new file mode 100644
index 00000000000..7404ef6b4fc
--- /dev/null
+++ b/addons/web_tests_demo/__openerp__.py
@@ -0,0 +1,8 @@
+{
+ 'name': "Demonstration of web/javascript tests",
+ 'category': 'Hidden',
+ 'depends': ['web'],
+ 'js': ['static/src/js/demo.js'],
+ 'test': ['static/test/demo.js'],
+ 'qweb': ['static/src/xml/demo.xml'],
+}
diff --git a/addons/web_tests_demo/static/src/js/demo.js b/addons/web_tests_demo/static/src/js/demo.js
new file mode 100644
index 00000000000..b35b44b80f8
--- /dev/null
+++ b/addons/web_tests_demo/static/src/js/demo.js
@@ -0,0 +1,11 @@
+// static/src/js/demo.js
+openerp.web_tests_demo = function (instance) {
+ instance.web_tests_demo = {
+ value_true: true,
+ SomeType: instance.web.Class.extend({
+ init: function (value) {
+ this.value = value;
+ }
+ })
+ };
+};
diff --git a/addons/web_tests_demo/static/src/xml/demo.xml b/addons/web_tests_demo/static/src/xml/demo.xml
new file mode 100644
index 00000000000..1bd3862d70e
--- /dev/null
+++ b/addons/web_tests_demo/static/src/xml/demo.xml
@@ -0,0 +1,7 @@
+<templates id="template" xml:space="preserve">
+ <div t-name="DemoTemplate">
+ <t t-foreach="5" t-as="value">
+ <p><t t-esc="value"/></p>
+ </t>
+ </div>
+</templates>
diff --git a/addons/web_tests_demo/static/test/demo.js b/addons/web_tests_demo/static/test/demo.js
new file mode 100644
index 00000000000..b2d54c83d68
--- /dev/null
+++ b/addons/web_tests_demo/static/test/demo.js
@@ -0,0 +1,102 @@
+openerp.testing.section('basic section', function (test) {
+ test('my first test', function () {
+ ok(true, "this test has run");
+ });
+ test('module content', function (instance) {
+ ok(instance.web_tests_demo.value_true, "should have a true value");
+ var type_instance = new instance.web_tests_demo.SomeType(42);
+ strictEqual(type_instance.value, 42, "should have provided value");
+ });
+ test('DOM content', function (instance, $scratchpad) {
+ $scratchpad.html('<div><span class="foo bar">ok</span></div>');
+ ok($scratchpad.find('span').hasClass('foo'),
+ "should have provided class");
+ });
+ test('clean scratchpad', function (instance, $scratchpad) {
+ ok(!$scratchpad.children().length, "should have no content");
+ ok(!$scratchpad.text(), "should have no text");
+ });
+
+ test('templates', {templates: true}, function (instance) {
+ var s = instance.web.qweb.render('DemoTemplate');
+ var texts = $(s).find('p').map(function () {
+ return $(this).text();
+ }).get();
+
+ deepEqual(texts, ['0', '1', '2', '3', '4']);
+ });
+
+ test('asynchronous', {
+ asserts: 1
+ }, function () {
+ var d = $.Deferred();
+ setTimeout(function () {
+ ok(true);
+ d.resolve();
+ }, 100);
+ return d;
+ });
+ test('unfail rejection', {
+ asserts: 1,
+ fail_on_rejection: false
+ }, function () {
+ var d = $.Deferred();
+ setTimeout(function () {
+ ok(true);
+ d.reject();
+ }, 100);
+ return d;
+ });
+
+ test('XML-RPC', {rpc: 'mock', asserts: 3}, function (instance, $s, mock) {
+ mock('people.famous:name_search', function (args, kwargs) {
+ strictEqual(kwargs.name, 'bob');
+ return [
+ [1, "Microsoft Bob"],
+ [2, "Bob the Builder"],
+ [3, "Silent Bob"]
+ ];
+ });
+ return new instance.web.Model('people.famous')
+ .call('name_search', {name: 'bob'}).then(function (result) {
+ strictEqual(result.length, 3, "shoud return 3 people");
+ strictEqual(result[0][1], "Microsoft Bob",
+ "the most famous bob should be Microsoft Bob");
+ });
+ });
+ test('JSON-RPC', {rpc: 'mock', asserts: 3, templates: true}, function (instance, $s, mock) {
+ var fetched_dbs = false, fetched_langs = false;
+ mock('/web/database/get_list', function () {
+ fetched_dbs = true;
+ return ['foo', 'bar', 'baz'];
+ });
+ mock('/web/session/get_lang_list', function () {
+ fetched_langs = true;
+ return [['vo_IS', 'Hopelandic / Vonlenska']];
+ });
+
+ // widget needs that or it blows up
+ instance.webclient = {toggle_bars: openerp.testing.noop};
+ var dbm = new instance.web.DatabaseManager({});
+ return dbm.appendTo($s).then(function () {
+ ok(fetched_dbs, "should have fetched databases");
+ ok(fetched_langs, "should have fetched languages");
+ deepEqual(dbm.db_list, ['foo', 'bar', 'baz']);
+ });
+ });
+
+ test('actual RPC', {rpc: 'rpc', asserts: 4}, function (instance) {
+ var Model = new instance.web.Model('web_tests_demo.model');
+ return Model.call('create', [{name: "Bob"}])
+ .then(function (id) {
+ return Model.call('read', [[id]]);
+ }).then(function (records) {
+ strictEqual(records.length, 1);
+ var record = records[0];
+ strictEqual(record.name, "Bob");
+ strictEqual(record.thing, false);
+ // default value
+ strictEqual(record.other, 'bob');
+ });
+ });
+});