merge upstream

bzr revid: chs@openerp.com-20121116183212-1ke59kde4ilicw6d
This commit is contained in:
Christophe Simonis 2012-11-16 19:32:12 +01:00
commit 4aa5e76174
57 changed files with 3049 additions and 1549 deletions

View File

@ -11,4 +11,4 @@ RE:^share/
RE:^man/
RE:^lib/
RE:^doc/_build/
RE:^addons/\w+/doc/_build/

View File

@ -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,21 @@ 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",
"static/test/mutex.js"
],
'bootstrap': True,
'twitter': False,
}

View File

@ -1 +1,2 @@
from . import main
from . import testing

View File

@ -258,10 +258,20 @@ def concat_files(file_list, reader=None, intersperse=""):
files_concat = intersperse.join(files_content)
return files_concat, checksum.hexdigest()
concat_js_cache = {}
def concat_js(file_list):
content, checksum = concat_files(file_list, intersperse=';')
content = rjsmin(content)
return content, checksum
if checksum in concat_js_cache:
content = concat_js_cache[checksum]
else:
content = rjsmin(content)
concat_js_cache[checksum] = content
return content, checksum
def fs2web(path):
"""convert FS path into web path"""
return '/'.join(path.split(os.path.sep))
def manifest_glob(req, addons, key):
if addons is None:
@ -278,7 +288,7 @@ def manifest_glob(req, addons, key):
globlist = manifest.get(key, [])
for pattern in globlist:
for path in glob.glob(os.path.normpath(os.path.join(addons_path, addon, pattern))):
r.append((path, path[len(addons_path):]))
r.append((path, fs2web(path[len(addons_path):])))
return r
def manifest_list(req, mods, extension):
@ -637,8 +647,7 @@ class WebClient(openerpweb.Controller):
data = fp.read().decode('utf-8')
path = file_map[f]
# convert FS path into web path
web_dir = '/'.join(os.path.dirname(path).split(os.path.sep))
web_dir = os.path.dirname(path)
data = re.sub(
rx_import,
@ -781,6 +790,17 @@ class Database(openerpweb.Controller):
params['db_original_name'],
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(
@ -887,10 +907,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"}

View File

@ -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, '/')

View File

@ -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

View File

@ -254,4 +254,5 @@ intersphinx_mapping = {
'python': ('http://docs.python.org/', 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),
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -14,6 +14,8 @@ Contents:
presentation
async
testing
widget
qweb
rpc
@ -24,7 +26,6 @@ Contents:
changelog-7.0
Indices and tables
==================

View File

@ -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

693
addons/web/doc/testing.rst Normal file
View File

@ -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/

View File

@ -458,6 +458,16 @@ class DisableCacheMiddleware(object):
start_response(status, new_headers)
return self.app(environ, start_wrapped)
def session_path():
try:
username = getpass.getuser()
except Exception:
username = "unknown"
path = os.path.join(tempfile.gettempdir(), "oe-sessions-" + username)
if not os.path.exists(path):
os.mkdir(path, 0700)
return path
class Root(object):
"""Root WSGI application for the OpenERP Web Client.
"""
@ -468,13 +478,7 @@ class Root(object):
self._load_addons()
# Setup http sessions
try:
username = getpass.getuser()
except Exception:
username = "unknown"
path = os.path.join(tempfile.gettempdir(), "oe-sessions-" + username)
if not os.path.exists(path):
os.mkdir(path, 0700)
path = session_path()
self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path)
self.session_lock = threading.Lock()
_logger.debug('HTTP sessions stored in: %s', path)
@ -563,7 +567,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)

View File

@ -27,7 +27,8 @@
var $tip = this.tip();
$tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
$tip[0].className = 'tipsy openerp oe_tooltip '; // reset classname in case of dynamic gravity
$tip[0].className = 'tipsy '; // reset classname in case of dynamic gravity
$tip.openerpClass('oe_tooltip');
$tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body);
var pos = $.extend({}, this.$element.offset(), {

View File

@ -20,20 +20,6 @@
font-style: normal;
}
@media print {
.oe_topbar, .oe_leftbar, .oe_loading {
display: none !important;
}
}
.openerp.openerp_webclient_container {
height: 100%;
}
.text-tag .text-button {
height: auto !important;
min-height: 16px;
}
.openerp {
padding: 0;
margin: 0;
@ -46,6 +32,9 @@
* http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image
*/
}
.openerp.openerp_webclient_container {
height: 100%;
}
.openerp :-moz-placeholder {
color: #afafb6 !important;
font-style: italic !important;
@ -197,6 +186,10 @@
.openerp .oe_bounce_container {
display: inline-block;
}
.openerp .text-tag .text-button {
height: auto !important;
min-height: 16px;
}
.openerp .ui-tabs {
position: static;
}
@ -1146,6 +1139,7 @@
height: 40px;
width: 157px;
margin: 14px 0;
border: 0;
}
.openerp .oe_footer {
position: fixed;
@ -2371,6 +2365,77 @@
.openerp .oe_form .oe_form_field_image:hover .oe_form_field_image_controls {
display: block;
}
.openerp .oe_fileupload {
display: inline-block;
clear: both;
width: 100%;
}
.openerp .oe_fileupload .oe_add {
float: left;
position: relative;
width: 100%;
left: 2px;
top: 7px;
}
.openerp .oe_fileupload .oe_add button {
display: inline;
height: 24px;
font-size: 12px;
line-height: 12px;
vertical-align: middle;
}
.openerp .oe_fileupload .oe_add button.oe_attach {
width: 24px;
overflow: hidden;
width: 24px;
overflow: hidden;
background: transparent;
color: #7c7bad;
box-shadow: none;
border: none;
text-shadow: none;
}
.openerp .oe_fileupload .oe_add button.oe_attach .oe_e {
position: relative;
top: -1px;
left: -9px;
}
.openerp .oe_fileupload .oe_add input.oe_form_binary_file {
display: inline-block;
margin-left: -5px;
height: 28px;
width: 52px;
margin-top: -26px;
}
.openerp .oe_fileupload .oe_add .oe_attach_label {
color: #7c7bad;
margin-left: -3px;
}
.openerp .oe_fileupload .oe_attachments {
margin-bottom: 4px;
margin-right: 0px;
font-size: 12px;
border-radius: 2px;
border: solid 1px rgba(124, 123, 173, 0.14);
}
.openerp .oe_fileupload .oe_attachments .oe_attachment {
padding: 2px;
padding-left: 4px;
padding-right: 4px;
}
.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e {
font-size: 23px;
margin-top: -5px;
}
.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e:hover {
text-decoration: none;
}
.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(odd) {
background: white;
}
.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(even) {
background: #f4f5fa;
}
.openerp .oe_form_field_many2one td:first-child {
position: relative;
}
@ -2903,78 +2968,6 @@
color: #333333;
}
.openerp .oe_fileupload {
display: inline-block;
clear: both;
width: 100%;
}
.openerp .oe_fileupload .oe_add {
float: left;
position: relative;
width: 100%;
left: 2px;
top: 7px;
}
.openerp .oe_fileupload .oe_add button {
display: inline;
height: 24px;
font-size: 12px;
line-height: 12px;
vertical-align: middle;
}
.openerp .oe_fileupload .oe_add button.oe_attach {
width: 24px;
overflow: hidden;
width: 24px;
overflow: hidden;
background: transparent;
color: #7c7bad;
box-shadow: none;
border: none;
text-shadow: none;
}
.openerp .oe_fileupload .oe_add button.oe_attach .oe_e {
position: relative;
top: -1px;
left: -9px;
}
.openerp .oe_fileupload .oe_add input.oe_form_binary_file {
display: inline-block;
margin-left: -5px;
height: 28px;
width: 52px;
margin-top: -26px;
}
.openerp .oe_fileupload .oe_add .oe_attach_label {
color: #7c7bad;
margin-left: -3px;
}
.openerp .oe_fileupload .oe_attachments {
margin-bottom: 4px;
margin-right: 0px;
font-size: 12px;
border-radius: 2px;
border: solid 1px rgba(124, 123, 173, 0.14);
}
.openerp .oe_fileupload .oe_attachments .oe_attachment {
padding: 2px;
padding-left: 4px;
padding-right: 4px;
}
.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e {
font-size: 23px;
margin-top: -5px;
}
.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e:hover {
text-decoration: none;
}
.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(odd) {
background: white;
}
.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(even) {
background: #f4f5fa;
}
.kitten-mode-activated {
background-image: url(http://placekitten.com/g/1365/769);
background-size: cover;
@ -3033,8 +3026,8 @@ div.ui-widget-overlay {
.openerp {
text-shadow: none;
}
.openerp .oe_header_row, .openerp ul.oe_header, .openerp div.oe_mail_thread_action, .openerp .oe_mail_recthread_actions, .openerp .oe_button_box, .openerp .oe_form button, .openerp button.oe_invite, .openerp .oe_form header, .openerp .openerp .oe_notebook > li.ui-state-default {
display: none;
.openerp .oe_header_row, .openerp ul.oe_header, .openerp div.oe_mail_thread_action, .openerp .oe_mail_recthread_actions, .openerp .oe_button_box, .openerp .oe_form button, .openerp button.oe_invite, .openerp .oe_form header, .openerp .openerp .oe_notebook > li.ui-state-default, .openerp .oe_topbar, .openerp .oe_leftbar, .openerp .oe_loading {
display: none !important;
}
.openerp .oe_list_content button, .openerp .oe_list_content input[type=checkbox] {
visibility: hidden;

View File

@ -140,18 +140,6 @@ $sheet-padding: 16px
// }}}
@media print
.oe_topbar, .oe_leftbar, .oe_loading
display: none !important
.openerp.openerp_webclient_container
height: 100%
// jQueryUI css bug fixing
.text-tag .text-button
height: auto !important
min-height: 16px
.openerp
// Global style {{{
padding: 0
@ -161,6 +149,8 @@ $sheet-padding: 16px
font-size: 13px
background: white
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5)
&.openerp_webclient_container
height: 100%
// }}}
//Placeholder style{{{
\:-moz-placeholder
@ -254,6 +244,11 @@ $sheet-padding: 16px
.oe_bounce_container
display: inline-block
// Bug lp:1051746
.text-tag .text-button
height: auto !important
min-height: 16px
// bug noted in jquery ui CSS doesn't seem to occur in IE9,
// so remove position:relative
.ui-tabs
@ -937,6 +932,7 @@ $sheet-padding: 16px
height: 40px
width: 157px
margin: 14px 0
border: 0
.oe_footer
position: fixed
bottom: 0
@ -1895,6 +1891,64 @@ $sheet-padding: 16px
@include box-sizing(border)
&:hover .oe_form_field_image_controls
display: block
.oe_fileupload
display: inline-block
clear: both
width: 100%
.oe_add
float: left
position: relative
width: 100%
left: +2px
top: +7px
button
display: inline
height: 24px
font-size: 12px
line-height: 12px
vertical-align: middle
button.oe_attach
width: 24px
overflow: hidden
width: 24px
overflow: hidden
background: transparent
color: #7C7BAD
box-shadow: none
border: none
text-shadow: none
.oe_e
position: relative
top: -1px
left: -9px
input.oe_form_binary_file
display: inline-block
margin-left: -5px
height: 28px
width: 52px
margin-top: -26px
.oe_attach_label
color: #7C7BAD
margin-left: -3px
.oe_attachments
margin-bottom: 4px
margin-right: 0px
font-size: 12px
border-radius: 2px
border: solid 1px rgba(124,123,173,0.14)
.oe_attachment
padding: 2px
padding-left: 4px
padding-right: 4px
.oe_e
font-size: 23px
margin-top: -5px
.oe_e:hover
text-decoration: none
.oe_attachment:nth-child(odd)
background: white
.oe_attachment:nth-child(even)
background: #F4F5FA
// }}}
// FormView.many2one {{{
.oe_form_field_many2one
@ -2297,67 +2351,6 @@ $sheet-padding: 16px
float: right
color: #333
// }}}
.openerp
.oe_fileupload
display: inline-block
clear: both
width: 100%
.oe_add
float: left
position: relative
width: 100%
left: +2px
top: +7px
button
display: inline
height: 24px
font-size: 12px
line-height: 12px
vertical-align: middle
button.oe_attach
width: 24px
overflow: hidden
width: 24px
overflow: hidden
background: transparent
color: #7C7BAD
box-shadow: none
border: none
text-shadow: none
.oe_e
position: relative
top: -1px
left: -9px
input.oe_form_binary_file
display: inline-block
margin-left: -5px
height: 28px
width: 52px
margin-top: -26px
.oe_attach_label
color: #7C7BAD
margin-left: -3px
.oe_attachments
margin-bottom: 4px
margin-right: 0px
font-size: 12px
border-radius: 2px
border: solid 1px rgba(124,123,173,0.14)
.oe_attachment
padding: 2px
padding-left: 4px
padding-right: 4px
.oe_e
font-size: 23px
margin-top: -5px
.oe_e:hover
text-decoration: none
.oe_attachment:nth-child(odd)
background: white
.oe_attachment:nth-child(even)
background: #F4F5FA
// Kitten Mode {{{
.kitten-mode-activated
background-image: url(http://placekitten.com/g/1365/769)
@ -2367,11 +2360,13 @@ $sheet-padding: 16px
opacity: 0.70
// }}}
// jQueryUI top level {{{
// The jQuery-ui overlay and Autocomplete are outside the .openerp div, please don't add indentation !!!
div.ui-widget-overlay
background: black
@include opacity(0.3)
// TODO: I think only the overlay is problematic, the other top level widgets should use $.fn.openerpClass()
// eg: $el.autocomplete().openerpClass();
.ui-widget
font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif
color: #4c4c4c
@ -2398,11 +2393,14 @@ div.ui-widget-overlay
.ui-corner-all
@include radius(3px)
// }}}
// @media print {{{
@media print
.openerp
.oe_header_row, ul.oe_header, div.oe_mail_thread_action, .oe_mail_recthread_actions, .oe_button_box, .oe_form button, button.oe_invite, .oe_form header, .openerp .oe_notebook > li.ui-state-default
display: none
.oe_header_row, ul.oe_header, div.oe_mail_thread_action, .oe_mail_recthread_actions, .oe_button_box, .oe_form button, button.oe_invite, .oe_form header, .openerp .oe_notebook > li.ui-state-default, .oe_topbar, .oe_leftbar, .oe_loading
// We use !important here because jQuery adds @style = display: block on elements when using $.fn.show()
display: none !important
.oe_list_content
button, input[type=checkbox]
visibility: hidden
@ -2428,5 +2426,7 @@ div.ui-widget-overlay
background: none
.openerp div.oe_mail_wall
overflow: hidden !important
// }}}
// au BufWritePost,FileWritePost *.sass :!sass --style expanded --line-numbers <afile> > "%:p:r.css"
// vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker:

View File

@ -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,

View File

@ -48,7 +48,7 @@ instance.web.Notification = instance.web.Widget.extend({
*/
instance.web.dialog = function(element) {
var result = element.dialog.apply(element, _.rest(_.toArray(arguments)));
result.dialog("widget").addClass("openerp");
result.dialog("widget").openerpClass();
return result;
};
@ -190,7 +190,14 @@ instance.web.Dialog = instance.web.Widget.extend({
});
instance.web.CrashManager = instance.web.Class.extend({
init: function() {
this.active = true;
},
rpc_error: function(error) {
if (!this.active) {
return;
}
if (error.data.fault_code) {
var split = ("" + error.data.fault_code).split('\n')[0].split(' -- ');
if (split.length > 1) {
@ -205,6 +212,9 @@ instance.web.CrashManager = instance.web.Class.extend({
}
},
show_warning: function(error) {
if (!this.active) {
return;
}
instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), {
title: "OpenERP " + _.str.capitalize(error.type),
buttons: [
@ -213,7 +223,9 @@ instance.web.CrashManager = instance.web.Class.extend({
});
},
show_error: function(error) {
var self = this;
if (!this.active) {
return;
}
var buttons = {};
buttons[_t("Ok")] = function() {
$(this).dialog("close");
@ -310,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);
},
@ -643,7 +655,7 @@ instance.web.client_actions.add("login", "instance.web.Login");
instance.web.redirect = function(url, wait) {
// Dont display a dialog if some xmlhttprequest are in progress
if (instance.client && instance.client.crashmanager) {
instance.client.crashmanager.destroy();
instance.client.crashmanager.active = false;
}
var wait_server = function() {
@ -659,7 +671,7 @@ instance.web.redirect = function(url, wait) {
} else {
window.location = url;
}
}
};
/**
* Client action to reload the whole interface.
@ -941,7 +953,7 @@ instance.web.UserMenu = instance.web.Widget.extend({
on_menu_settings: function() {
var self = this;
if (!this.getParent().has_uncommitted_changes()) {
self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }, function(result) {
self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }).done(function(result) {
result.res_id = instance.session.uid;
self.getParent().action_manager.do_action(result);
});
@ -972,6 +984,7 @@ instance.web.Client = instance.web.Widget.extend({
return instance.session.session_bind(this.origin).then(function() {
var $e = $(QWeb.render(self._template, {}));
self.replaceElement($e);
$e.openerpClass();
self.bind_events();
return self.show_common();
});
@ -1148,10 +1161,10 @@ instance.web.WebClient = instance.web.Client.extend({
var self = this;
var state = event.getState(true);
if (!_.isEqual(this._current_state, state)) {
if(state.action === undefined && state.menu_id) {
if(!state.action && state.menu_id) {
self.menu.has_been_loaded.done(function() {
self.menu.do_reload().done(function() {
self.menu.menu_click(state.menu_id)
self.menu.menu_click(state.menu_id);
});
});
} else {

View File

@ -774,10 +774,10 @@ instance.web.Widget = instance.web.Class.extend(instance.web.PropertiesMixin, {
}
return false;
},
rpc: function(url, data, success, error) {
var def = $.Deferred().done(success).fail(error);
rpc: function(url, data, options) {
var def = $.Deferred();
var self = this;
instance.session.rpc(url, data).done(function() {
instance.session.rpc(url, data, options).done(function() {
if (!self.isDestroyed())
def.resolve.apply(def, arguments);
}).fail(function() {
@ -1231,12 +1231,14 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
*
* @param {String} url RPC endpoint
* @param {Object} params call parameters
* @param {Object} options additional options for rpc call
* @param {Function} success_callback function to execute on RPC call success
* @param {Function} error_callback function to execute on RPC call failure
* @returns {jQuery.Deferred} jquery-provided ajax deferred
*/
rpc: function(url, params) {
rpc: function(url, params, options) {
var self = this;
options = options || {};
// url can be an $.ajax option object
if (_.isString(url)) {
url = { url: url };
@ -1251,10 +1253,12 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
id: _.uniqueId('r')
};
var deferred = $.Deferred();
this.trigger('request', url, payload);
if (! options.shadow)
this.trigger('request', url, payload);
var request = this.rpc_function(url, payload).done(
function (response, textStatus, jqXHR) {
self.trigger('response', response);
if (! options.shadow)
self.trigger('response', response);
if (!response.error) {
if (url.url === '/web/session/eval_domain_and_context') {
self.test_eval(params, response.result);
@ -1268,7 +1272,8 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
}
).fail(
function(jqXHR, textStatus, errorThrown) {
self.trigger('response_failed', jqXHR);
if (! options.shadow)
self.trigger('response_failed', jqXHR);
var error = {
code: -32098,
message: "XmlHttpRequestError " + errorThrown,

View File

@ -22,9 +22,9 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
this.name = instance._session_id;
this.qweb_mutex = new $.Mutex();
},
rpc: function(url, params) {
rpc: function(url, params, options) {
params.session_id = this.session_id;
return this._super(url, params);
return this._super(url, params, options);
},
/**
* Setup a sessionm
@ -466,6 +466,16 @@ $.fn.getAttributes = function() {
}
return o;
}
$.fn.openerpClass = function(additionalClass) {
// This plugin should be applied on top level elements
additionalClass = additionalClass || '';
if (!!$.browser.msie) {
additionalClass += ' openerp_ie';
}
return this.each(function() {
$(this).addClass('openerp ' + additionalClass);
});
};
/** Jquery extentions */
$.Mutex = (function() {

View File

@ -278,9 +278,10 @@ instance.web.Model = instance.web.Class.extend({
* @param {String} method name of the method to call
* @param {Array} [args] positional arguments
* @param {Object} [kwargs] keyword arguments
* @param {Object} [options] additional options for the rpc() method
* @returns {jQuery.Deferred<>} call result
*/
call: function (method, args, kwargs) {
call: function (method, args, kwargs, options) {
args = args || [];
kwargs = kwargs || {};
if (!_.isArray(args)) {
@ -294,7 +295,7 @@ instance.web.Model = instance.web.Class.extend({
method: method,
args: args,
kwargs: kwargs
});
}, options);
},
/**
* Fetches a Query instance bound to this model, for searching

View File

@ -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);

View File

@ -622,6 +622,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
return self._process_save(save_obj).then(function() {
save_obj.ret = _.toArray(arguments);
return iterate();
}, function() {
save_obj.error = true;
});
}
return $.when();
@ -805,6 +807,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
var save_obj = {prepend_on_create: prepend_on_create, ret: null};
this.save_list.push(save_obj);
return this._process_operations().then(function() {
if (save_obj.error)
return $.Deferred().reject();
return $.when.apply($, save_obj.ret);
});
},
@ -3122,7 +3126,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
minLength: 0,
delay: 0
});
this.$input.autocomplete("widget").addClass("openerp");
this.$input.autocomplete("widget").openerpClass();
// used to correct a bug when selecting an element by pushing 'enter' in an editable list
this.$input.keyup(function(e) {
if (e.which === 13) { // ENTER
@ -3833,26 +3837,13 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
this._super.apply(this, arguments);
},
do_delete: function (ids) {
var self = this;
var next = $.when();
var _super = this._super;
// handle deletion of an item which does not exist
// TODO: better handle that in the editable list?
var false_id_index = _(ids).indexOf(false);
if (false_id_index !== -1) {
ids.splice(false_id_index, 1);
next = this.cancel_edition(true);
var confirm = window.confirm;
window.confirm = function () { return true; };
try {
return this._super(ids);
} finally {
window.confirm = confirm;
}
return next.then(function () {
// wheeee
var confirm = window.confirm;
window.confirm = function () { return true; };
try {
return _super.call(self, ids);
} finally {
window.confirm = confirm;
}
});
}
});
instance.web.form.One2ManyGroups = instance.web.ListView.Groups.extend({
@ -4952,7 +4943,7 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
if (this.get('value') && ! /^\d+(\.\d*)? \w+$/.test(this.get('value'))) {
url = 'data:image/png;base64,' + this.get('value');
} else if (this.get('value')) {
var id = escape(JSON.stringify(this.view.datarecord.id || null));
var id = JSON.stringify(this.view.datarecord.id || null);
var field = this.name;
if (this.options.preview_image)
field = this.options.preview_image;
@ -5000,14 +4991,14 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
* Options on attribute ; "blockui" {Boolean} block the UI or not
* during the file is uploading
*/
instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend({
instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend({
template: "FieldBinaryFileUploader",
init: function(field_manager, node) {
this._super(field_manager, node);
this.field_manager = field_manager;
this.node = node;
if(this.field.type != "one2many" || this.field.relation != 'ir.attachment') {
throw "The type of the field '"+this.field.string+"' must be a one2many field with a relation to 'ir.attachment' model.";
if(this.field.type != "many2many" || this.field.relation != 'ir.attachment') {
throw "The type of the field '"+this.field.string+"' must be a many2many field with a relation to 'ir.attachment' model.";
}
this.ds_file = new instance.web.DataSetSearch(this, 'ir.attachment');
this.fileupload_id = _.uniqueId('oe_fileupload_temp');
@ -5017,21 +5008,85 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel
this._super(this);
this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change );
},
set_value: function(value_) {
var value_ = value_ || [];
var self = this;
var ids = [];
_.each(value_, function(command) {
if (isNaN(command) && command.id == undefined) {
switch (command[0]) {
case commands.CREATE:
ids = ids.concat(command[2]);
return;
case commands.REPLACE_WITH:
ids = ids.concat(command[2]);
return;
case commands.UPDATE:
ids = ids.concat(command[2]);
return;
case commands.LINK_TO:
ids = ids.concat(command[1]);
return;
case commands.DELETE:
ids = _.filter(ids, function (id) { return id != command[1];});
return;
case commands.DELETE_ALL:
ids = [];
return;
}
} else {
ids.push(command);
}
});
this._super( ids );
},
get_value: function() {
return _.map(this.get('value'), function (value) { return commands.link_to( value.id ); });
},
get_file_url: function (attachment) {
return this.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: attachment['id']});
},
read_name_values : function () {
var self = this;
// select the list of id for a get_name
var values = [];
_.each(this.get('value'), function (val) {
if (typeof val != 'object') {
values.push(val);
}
});
// send request for get_name
if (values.length) {
return this.ds_file.call('read', [values, ['id', 'name', 'datas_fname']]).done(function (datas) {
_.each(datas, function (data) {
data.no_unlink = true;
data.url = self.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: data.id});
_.each(self.get('value'), function (val, key) {
if(val == data.id) {
self.get('value')[key] = data;
}
});
});
});
} else {
return $.when(this.get('value'));
}
},
render_value: function () {
var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': this}));
render.on('click', '.oe_delete', _.bind(this.on_file_delete, this));
this.$('.oe_placeholder_files, .oe_attachments').replaceWith( render );
var self = this;
this.read_name_values().then(function (datas) {
// reinit input type file
var $input = this.$('input.oe_form_binary_file');
$input.after($input.clone(true)).remove();
this.$(".oe_fileupload").show();
var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': self}));
render.on('click', '.oe_delete', _.bind(self.on_file_delete, self));
self.$('.oe_placeholder_files, .oe_attachments').replaceWith( render );
// reinit input type file
var $input = self.$('input.oe_form_binary_file');
$input.after($input.clone(true)).remove();
self.$(".oe_fileupload").show();
});
},
on_file_change: function (event) {
event.stopPropagation();
@ -5111,7 +5166,7 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel
if(file_id != this.get('value')[i].id){
files.push(this.get('value')[i]);
}
else {
else if(!this.get('value')[i].no_unlink) {
this.ds_file.unlink([file_id]);
}
}
@ -5273,7 +5328,7 @@ instance.web.form.widgets = new instance.web.Registry({
'progressbar': 'instance.web.form.FieldProgressBar',
'image': 'instance.web.form.FieldBinaryImage',
'binary': 'instance.web.form.FieldBinaryFile',
'one2many_binary': 'instance.web.form.FieldOne2ManyBinaryMultiFiles',
'many2many_binary': 'instance.web.form.FieldMany2ManyBinaryMultiFiles',
'statusbar': 'instance.web.form.FieldStatus',
'monetary': 'instance.web.form.FieldMonetary',
});

View File

@ -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# */ {

View File

@ -77,6 +77,15 @@ openerp.web.list_editable = function (instance) {
do_edit: function (index, id, dataset) {
_.extend(this.dataset, dataset);
},
do_delete: function (ids) {
var _super = this._super.bind(this);
var next = this.editor.is_editing()
? this.cancel_edition(true)
: $.when();
return next.then(function () {
return _super(ids);
});
},
editable: function () {
return this.fields_view.arch.attrs.editable
|| this._context_editable

View File

@ -310,15 +310,15 @@ instance.web.ActionManager = instance.web.Widget.extend({
}
var widget = executor.widget();
if (executor.action.target === 'new') {
if (this.dialog_widget && ! this.dialog_widget.isDestroyed())
if (this.dialog_widget && !this.dialog_widget.isDestroyed()) {
this.dialog_widget.destroy();
if (this.dialog === null || this.dialog.isDestroyed()) {
this.dialog = new instance.web.Dialog(this, {
dialogClass: executor.klass,
});
this.dialog.on("closing", null, options.on_close);
this.dialog.init_dialog();
}
this.dialog_stop();
this.dialog = new instance.web.Dialog(this, {
dialogClass: executor.klass,
});
this.dialog.on("closing", null, options.on_close);
this.dialog.init_dialog();
this.dialog.dialog_title = executor.action.name;
if (widget instanceof instance.web.ViewManager) {
_.extend(widget.flags, {
@ -789,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();
@ -1239,6 +1239,7 @@ instance.web.View = instance.web.Widget.extend({
});
}, null);
} else {
self.do_action({"type":"ir.actions.act_window_close"});
return result_handler();
}
};

View File

@ -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");
});
});
});

View File

@ -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);
});
});

View File

@ -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]

View File

@ -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));
});
});

View File

@ -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);
}
});
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('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 (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('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');
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');
});
});

View File

@ -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'}}
]
}
}};
});
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:create': function (params) {
records[42] = _.extend({}, params.params.args[0]);
return {result: 42};
},
'/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'}}
]
}
}};
},
'/web/dataset/call_kw:read': function (params) {
return {result: [{
id: 1,
a: 'foo',
b: 'bar',
c: 'baz'
}]};
}
});
}
});
asyncTest('edition events', 4, function () {
});
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"}
},
arch: {
tag: 'tree',
attrs: {},
children: [
{tag: 'field', attrs: {name: 'a'}},
{tag: 'field', attrs: {name: 'b'}},
{tag: 'field', attrs: {name: 'c'}}
]
}
};
});
}
}, 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 (args, kwargs) {
if (_.isEmpty(args[0])) {
return [];
} else if (_.isEqual(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(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(_.toArray(arguments)));
});
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);
});
});
});

View File

@ -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);

View File

@ -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', 'web.form'],
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,26 +18,25 @@ $(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];
if (_.isEqual(args, [1, 2, 3])) {
return {result: [
};
});
mock('demo:read', function (args, kwargs) {
if (_.isEqual(args[0], [1, 2, 3])) {
return [
{id: 1, a: 'foo'}, {id: 2, a: 'bar'}, {id: 3, a: 'baz'}
]};
} else if (_.isEqual(args, [2])) {
];
} else if (_.isEqual(args[0], [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(_.toArray(arguments)));
});
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({}, ds, false, {editable: 'top'});
l.appendTo($fix)
var l = new instance.web.ListView({
do_action: openerp.testing.noop
}, ds, false, {editable: 'top'});
return l.appendTo($fix)
.then(l.proxy('reload_content'))
.then(function () {
var d = $.Deferred();
@ -55,14 +46,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);
});
});
});

View File

@ -0,0 +1,60 @@
openerp.testing.section('mutex', {
dependencies: ['web.coresetup'],
setup: function (instance) {
}
}, function (test) {
test('simpleScheduling', function (instance) {
var m = new $.Mutex();
var def1 = $.Deferred();
var def2 = $.Deferred();
var p1 = m.exec(function() { return def1; });
var p2 = m.exec(function() { return def2; });
equal(p1.state(), "pending");
equal(p2.state(), "pending");
def1.resolve();
equal(p1.state(), "resolved");
equal(p2.state(), "pending");
def2.resolve();
equal(p1.state(), "resolved");
equal(p2.state(), "resolved");
});
test('simpleScheduling2', function (instance) {
var m = new $.Mutex();
var def1 = $.Deferred();
var def2 = $.Deferred();
var p1 = m.exec(function() { return def1; });
var p2 = m.exec(function() { return def2; });
equal(p1.state(), "pending");
equal(p2.state(), "pending");
def2.resolve();
equal(p1.state(), "pending");
equal(p2.state(), "pending");
def1.resolve();
equal(p1.state(), "resolved");
equal(p2.state(), "resolved");
});
test('reject', function (instance) {
var m = new $.Mutex();
var def1 = $.Deferred();
var def2 = $.Deferred();
var def3 = $.Deferred();
var p1 = m.exec(function() {return def1;});
var p2 = m.exec(function() {return def2;});
var p3 = m.exec(function() {return def3;});
equal(p1.state(), "pending");
equal(p2.state(), "pending");
equal(p3.state(), "pending");
def1.resolve();
equal(p1.state(), "resolved");
equal(p2.state(), "pending");
equal(p3.state(), "pending");
def2.reject();
equal(p1.state(), "resolved");
equal(p2.state(), "rejected");
equal(p3.state(), "pending");
def3.resolve();
equal(p1.state(), "resolved");
equal(p2.state(), "rejected");
equal(p3.state(), "resolved");
});
});

View File

@ -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);
});
});

View File

@ -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)
});
});

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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();
});
});
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];
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)");
});
});
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('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);
});
var deps = dependencies[module];
if (!deps) { throw new Error("Unknown dependencies for " + module); }
});
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);
}
}
})();
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();
});
});
test('arguments passing', {asserts: 9}, function () {
var asserter = function (a, b, c) {
strictEqual(a, 1);
strictEqual(b, "foo");
deepEqual(c, {bar: "baz", qux: 42});
};
return openerp.testing.Stack()
.push(asserter, asserter)
.execute(asserter, 1, "foo", {bar: 'baz', qux: 42});
});
});

View File

@ -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
}, '')

View File

@ -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,
}

View File

@ -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 = [

View File

@ -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

View File

@ -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]

View File

@ -277,7 +277,7 @@
width: 100%;
}
.openerp .oe_kanban_view.oe_kanban_ungrouped .oe_kanban_column .oe_kanban_record {
float: left;
display: inline-block;
padding: 2px;
box-sizing: border-box;
-moz-box-sizing: border-box;

View File

@ -264,7 +264,7 @@
width: 100%
&.oe_kanban_ungrouped .oe_kanban_column
.oe_kanban_record
float: left
display: inline-block
padding: 2px
box-sizing: border-box
-moz-box-sizing: border-box

View File

@ -992,7 +992,7 @@ instance.web_kanban.KanbanRecord = instance.web.Widget.extend({
} else if (this.record[field] && ! this.record[field].value) {
url = "/web/static/src/img/placeholder.png";
} else {
id = escape(JSON.stringify(id));
id = JSON.stringify(id);
if (options.preview_image)
field = options.preview_image;
url = this.session.url('/web/binary/image', {model: model, field: field, id: id});

View File

@ -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"
}

View File

@ -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'],
}

View File

@ -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;
}
})
};
};

View File

@ -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>

View File

@ -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');
});
});
});