[MERGE] Web assets moved from manifests to ir.ui.view bundles

bzr revid: fme@openerp.com-20140430151302-e42monb4klz4udw5
This commit is contained in:
Fabien Meghazi 2014-04-30 17:13:02 +02:00
commit acf2123123
50 changed files with 406 additions and 1029 deletions

View File

@ -14,81 +14,7 @@ This module provides the core of the OpenERP Web Client.
'data': [
'views/webclient_templates.xml',
],
'js': [
"static/lib/es5-shim/es5-shim.min.js",
"static/lib/datejs/globalization/en-US.js",
"static/lib/datejs/core.js",
"static/lib/datejs/parser.js",
"static/lib/datejs/sugarpak.js",
"static/lib/datejs/extras.js",
"static/lib/jquery/jquery.js",
"static/lib/jquery.form/jquery.form.js",
"static/lib/jquery.validate/jquery.validate.js",
"static/lib/jquery.ba-bbq/jquery.ba-bbq.js",
"static/lib/spinjs/spin.js",
"static/lib/jquery.autosize/jquery.autosize.js",
"static/lib/jquery.blockUI/jquery.blockUI.js",
"static/lib/jquery.hotkeys/jquery.hotkeys.js",
"static/lib/jquery.placeholder/jquery.placeholder.js",
"static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js",
"static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js",
"static/lib/jquery.ui.notify/js/jquery.notify.js",
"static/lib/jquery.deferred-queue/jquery.deferred-queue.js",
"static/lib/jquery.scrollTo/jquery.scrollTo-min.js",
"static/lib/jquery.textext/jquery.textext.js",
"static/lib/jquery.timeago/jquery.timeago.js",
"static/lib/bootstrap/js/bootstrap.js",
"static/lib/qweb/qweb2.js",
"static/lib/underscore/underscore.js",
"static/lib/underscore.string/lib/underscore.string.js",
"static/lib/backbone/backbone.js",
"static/lib/cleditor/jquery.cleditor.js",
"static/lib/py.js/lib/py.js",
"static/lib/select2/select2.js",
"static/src/js/openerpframework.js",
"static/src/js/boot.js",
"static/src/js/testing.js",
"static/src/js/pyeval.js",
"static/src/js/core.js",
"static/src/js/formats.js",
"static/src/js/chrome.js",
"static/src/js/views.js",
"static/src/js/data.js",
"static/src/js/data_export.js",
"static/src/js/search.js",
"static/src/js/view_list.js",
"static/src/js/view_form.js",
"static/src/js/view_list_editable.js",
"static/src/js/view_tree.js",
],
'css' : [
"static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.9.0.custom.css",
"static/lib/jquery.ui.timepicker/css/jquery-ui-timepicker-addon.css",
"static/lib/jquery.ui.notify/css/ui.notify.css",
"static/lib/jquery.textext/jquery.textext.css",
"static/lib/fontawesome/css/font-awesome.css",
"static/lib/bootstrap/css/bootstrap.css",
"static/lib/select2/select2.css",
"static/src/css/base.css",
"static/src/css/data_export.css",
"static/lib/cleditor/jquery.cleditor.css",
],
'qweb' : [
"static/src/xml/*.xml",
],
'test': [
"static/test/testing.js",
"static/test/framework.js",
"static/test/registry.js",
"static/test/form.js",
"static/test/data.js",
"static/test/list-utils.js",
"static/test/formats.js",
"static/test/rpc-misordered.js",
"static/test/evals.js",
"static/test/search.js",
"static/test/list.js",
"static/test/list-editable.js",
"static/test/mutex.js"
],
}

View File

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

View File

@ -31,6 +31,7 @@ except ImportError:
import openerp
import openerp.modules.registry
from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebTemplateNotFound
from openerp.tools.translate import _
from openerp import http
@ -52,50 +53,6 @@ env.filters["json"] = simplejson.dumps
# OpenERP Web helpers
#----------------------------------------------------------
def rjsmin(script):
""" Minify js with a clever regex.
Taken from http://opensource.perlig.de/rjsmin
Apache License, Version 2.0 """
def subber(match):
""" Substitution callback """
groups = match.groups()
return (
groups[0] or
groups[1] or
groups[2] or
groups[3] or
(groups[4] and '\n') or
(groups[5] and ' ') or
(groups[6] and ' ') or
(groups[7] and ' ') or
''
)
result = re.sub(
r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?'
r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|'
r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?:(?<=[(,=:\[!&|?{};\r\n]'
r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'
r'))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*'
r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*'
r'))|(?:(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\01'
r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*((?:/(?![\r\n/*])[^/'
r'\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]'
r'*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*))|(?<=[^\000-!#%&(*,./'
r':-@\[\\^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/'
r'*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\01'
r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#'
r'%-\047)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-'
r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^'
r'\000-#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|'
r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\0'
r'13\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\0'
r'00-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:'
r'(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*'
r']*\*+(?:[^/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script
).strip()
return result
db_list = http.db_list
db_monodb = http.db_monodb
@ -309,45 +266,6 @@ def concat_xml(file_list):
root.append(child)
return ElementTree.tostring(root, 'utf-8'), checksum.hexdigest()
def concat_files(file_list, reader=None, intersperse=""):
""" Concatenates contents of all provided files
:param list(str) file_list: list of files to check
:param function reader: reading procedure for each file
:param str intersperse: string to intersperse between file contents
:returns: (concatenation_result, checksum)
:rtype: (str, str)
"""
checksum = hashlib.new('sha1')
if not file_list:
return '', checksum.hexdigest()
if reader is None:
def reader(f):
import codecs
with codecs.open(f, 'rb', "utf-8-sig") as fp:
return fp.read().encode("utf-8")
files_content = []
for fname in file_list:
contents = reader(fname)
checksum.update(contents)
files_content.append(contents)
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=';')
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))
@ -371,30 +289,17 @@ def manifest_glob(extension, addons=None, db=None, include_remotes=False):
r.append((None, pattern))
else:
for path in glob.glob(os.path.normpath(os.path.join(addons_path, addon, pattern))):
# Hack for IE, who limit 288Ko, 4095 rules, 31 sheets
# http://support.microsoft.com/kb/262161/en
if pattern == "static/lib/bootstrap/css/bootstrap.css":
if include_remotes:
r.insert(0, (None, fs2web(path[len(addons_path):])))
else:
r.append((path, fs2web(path[len(addons_path):])))
r.append((path, fs2web(path[len(addons_path):])))
return r
def manifest_list(extension, mods=None, db=None, debug=False):
def manifest_list(extension, mods=None, db=None, debug=None):
""" list ressources to load specifying either:
mods: a comma separated string listing modules
db: a database name (return all installed modules in that database)
"""
if debug is not None:
_logger.warning("openerp.addons.web.main.manifest_list(): debug parameter is deprecated")
files = manifest_glob(extension, addons=mods, db=db, include_remotes=True)
if not debug:
path = '/web/webclient/' + extension
if mods is not None:
path += '?' + werkzeug.url_encode({'mods': mods})
elif db:
path += '?' + werkzeug.url_encode({'db': db})
remotes = [wp for fp, wp in files if fp is None]
return [path] + remotes
return [wp for _fp, wp in files]
def get_last_modified(files):
@ -411,7 +316,7 @@ def get_last_modified(files):
for f in files)
return datetime.datetime(1970, 1, 1)
def make_conditional(response, last_modified=None, etag=None):
def make_conditional(response, last_modified=None, etag=None, max_age=0):
""" Makes the provided response conditional based upon the request,
and mandates revalidation from clients
@ -426,7 +331,7 @@ def make_conditional(response, last_modified=None, etag=None):
:rtype: werkzeug.wrappers.Response
"""
response.cache_control.must_revalidate = True
response.cache_control.max_age = 0
response.cache_control.max_age = max_age
if last_modified:
response.last_modified = last_modified
if etag:
@ -603,6 +508,10 @@ html_template = """<!DOCTYPE html>
<title>OpenERP</title>
<link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
<link rel="stylesheet" href="/web/static/src/css/full.css" />
<link rel="stylesheet" href="/web/css/web.assets_backend"/>
<script type="text/javascript" src="/web/js/web.assets_backend"></script>
%(css)s
%(js)s
<script type="text/javascript">
@ -621,26 +530,16 @@ html_template = """<!DOCTYPE html>
</html>
"""
def render_bootstrap_template(template, values=None, debug=False, db=None, **kw):
if not db:
db = request.db
if request.debug:
debug = True
def render_bootstrap_template(template, values=None, **kw):
if values is None:
values = {}
values['debug'] = debug
values['current_db'] = db
values = dict()
try:
values['databases'] = http.db_list()
except openerp.exceptions.AccessDenied:
values['databases'] = None
for res in ['js', 'css']:
if res not in values:
values[res] = manifest_list(res, db=db, debug=debug)
if 'modules' not in values:
values['modules'] = module_boot(db=db)
values['modules'] = module_boot()
values['modules'] = simplejson.dumps(values['modules'])
return request.render(template, values, **kw)
@ -692,6 +591,38 @@ class Home(http.Controller):
def login(self, db, login, key, redirect="/web", **kw):
return login_and_redirect(db, login, key, redirect_url=redirect)
@http.route('/web/js/<xmlid>', type='http', auth="public")
def js_bundle(self, xmlid, **kw):
# manifest backward compatible mode, to be removed
values = {'manifest_list': manifest_list}
try:
assets_html = request.render(xmlid, lazy=False, qcontext=values)
except QWebTemplateNotFound:
return request.not_found()
bundle = AssetsBundle(xmlid, assets_html, debug=request.debug)
response = request.make_response(
bundle.js(), [('Content-Type', 'application/javascript')])
# TODO: check that we don't do weird lazy overriding of __call__ which break body-removal
return make_conditional(
response, bundle.last_modified, bundle.checksum, max_age=60*5)
@http.route('/web/css/<xmlid>', type='http', auth='public')
def css_bundle(self, xmlid, **kw):
values = {'manifest_list': manifest_list} # manifest backward compatible mode, to be removed
try:
assets_html = request.render(xmlid, lazy=False, qcontext=values)
except QWebTemplateNotFound:
return request.not_found()
bundle = AssetsBundle(xmlid, assets_html, debug=request.debug)
response = request.make_response(
bundle.css(), [('Content-Type', 'text/css')])
return make_conditional(
response, bundle.last_modified, bundle.checksum, max_age=60*5)
class WebClient(http.Controller):
@http.route('/web/webclient/csslist', type='json', auth="none")
@ -706,70 +637,6 @@ class WebClient(http.Controller):
def qweblist(self, mods=None):
return manifest_list('qweb', mods=mods)
@http.route('/web/webclient/css', type='http', auth="none")
def css(self, mods=None, db=None):
files = list(manifest_glob('css', addons=mods, db=db))
last_modified = get_last_modified(f[0] for f in files)
if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified:
return werkzeug.wrappers.Response(status=304)
file_map = dict(files)
rx_import = re.compile(r"""@import\s+('|")(?!'|"|/|https?://)""", re.U)
rx_url = re.compile(r"""url\s*\(\s*('|"|)(?!'|"|/|https?://|data:)""", re.U)
def reader(f):
"""read the a css file and absolutify all relative uris"""
with open(f, 'rb') as fp:
data = fp.read().decode('utf-8')
path = file_map[f]
web_dir = os.path.dirname(path)
data = re.sub(
rx_import,
r"""@import \1%s/""" % (web_dir,),
data,
)
data = re.sub(
rx_url,
r"url(\1%s/" % (web_dir,),
data,
)
return data.encode('utf-8')
content, checksum = concat_files((f[0] for f in files), reader)
# move up all @import and @charset rules to the top
matches = []
def push(matchobj):
matches.append(matchobj.group(0))
return ''
content = re.sub(re.compile("(@charset.+;$)", re.M), push, content)
content = re.sub(re.compile("(@import.+;$)", re.M), push, content)
matches.append(content)
content = '\n'.join(matches)
return make_conditional(
request.make_response(content, [('Content-Type', 'text/css')]),
last_modified, checksum)
@http.route('/web/webclient/js', type='http', auth="none")
def js(self, mods=None, db=None):
files = [f[0] for f in manifest_glob('js', addons=mods, db=db)]
last_modified = get_last_modified(files)
if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified:
return werkzeug.wrappers.Response(status=304)
content, checksum = concat_js(files)
return make_conditional(
request.make_response(content, [('Content-Type', 'application/javascript')]),
last_modified, checksum)
@http.route('/web/webclient/qweb', type='http', auth="none")
def qweb(self, mods=None, db=None):
files = [f[0] for f in manifest_glob('qweb', addons=mods, db=db)]
@ -843,6 +710,10 @@ class WebClient(http.Controller):
def version_info(self):
return openerp.service.common.exp_version()
@http.route('/web/tests', type='http', auth="none")
def index(self, mod=None, **kwargs):
return request.render('web.qunit_suite')
class Proxy(http.Controller):
@http.route('/web/proxy/load', type='json', auth="none")

View File

@ -1,159 +0,0 @@
# 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 openerp import http
from openerp.http import request
from .main import module_topological_sort
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>
""", default_filters=['h'])
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>
""", default_filters=['h'])
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">
// List of modules, each module is preceded by its dependencies
var oe_all_dependencies = ${dependencies | n};
QUnit.config.testTimeout = 5 * 60 * 1000;
</script>
</head>
<body id="oe" class="openerp">
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
<!-- TODO xmo please use the regular template even for testing -->
% for module, jss, tests, templates in files:
% for js in jss:
% if not js.endswith('/apps.js'):
<script src="${to_path(module, js)}"></script>
% endif
% 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>
""", default_filters=['h'])
class TestRunnerController(http.Controller):
@http.route('/web/tests', type='http', auth="none")
def index(self, 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 request.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)
]
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']]))
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

@ -1457,7 +1457,7 @@ instance.web.embed = function (origin, dbname, login, key, action, options) {
$('head').append($('<link>', {
'rel': 'stylesheet',
'type': 'text/css',
'href': origin +'/web/webclient/css'
'href': origin +'/web/css/web.assets_webclient'
}));
var currentScript = document.currentScript;
if (!currentScript) {

View File

@ -48,15 +48,6 @@ openerp.testing = {};
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
*/
@ -206,7 +197,7 @@ openerp.testing = {};
teardown: testing.noop
});
QUnit.module(testing.current_module + '.' + name, {_oe: options});
QUnit.module(name, {_oe: options});
body(testing['case']);
};
testing['case'] = function (name, options, callback) {
@ -244,18 +235,6 @@ openerp.testing = {};
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;

View File

@ -361,8 +361,8 @@ openerp.testing.section('list.edition.onwrite', {
strictEqual(
$fix.find('tbody tr:eq(1)').css('color'), 'rgb(255, 0, 0)',
'shoud have color applied');
strictEqual(
$fix.find('tbody tr:eq(2)').css('color'), 'rgb(0, 0, 0)',
notStrictEqual(
$fix.find('tbody tr:eq(2)').css('color'), 'rgb(255, 0, 0)',
'should have default color applied');
});
});

View File

@ -2,4 +2,3 @@
import test_js
import test_menu
import test_serving_base
import test_ui

View File

@ -1,116 +0,0 @@
QUnitSuite is a ``unittest.TestSuite`` able to run QUnit_ test suites
within the normal unittest process, through PhantomJS_.
QUnitSuite is built upon `Ben Alman`_'s work of for the interfacing
between PhantomJS_ and the host/reporting code: the shims and the
PhantomJS_ configuration files are those of grunt_'s ``qunit`` task.
Why
---
You're a Python shop or developer, you have tools and tests built
around unittest (or compatible with unittests) and your testing
pipeline is predicated upon that, you're doing web development of some
sort these days (as so many are) and you'd like to do some testing of
your web stuff.
But you don't really want to redo your whole testing stack just for
that.
QUnitSuite simply grafts QUnit_-based tests, run in PhantomJS_, in
your existing ``unittest``-based architecture.
What
----
QUnitSuite currently provides a single object as part of its API:
``qunitsuite.QUnitSuite(testfile[, timeout])``.
This produces a ``unittest.TestSuite`` suitable for all the usual
stuff (running it, and giving it to an other test suite which will run
it, that is).
``testfile`` is the HTML file bootstrapping your qunit tests, as would
usually be accessed via a browser. It can be either a local
(``file:``) url, or an HTTP one. As long as a regular browser can open
and execute it, PhantomJS_ will manage.
``timeout`` is a check passed to the PhantomJS_ runner: if the runner
produces no information for longer than ``timeout`` milliseconds, the
run will be cancelled and a test error will be generated. This
situation usually means either your ``testfile`` is not a qunit test
file, qunit is not running or qunit's runner was stopped (for an async
test) and never restarted.
The default value is very conservative, most tests should run
correctly with lower timeouts (especially if all tests are
synchronous).
How
---
``unittest``'s autodiscovery protocol does not directly work with test
suites (it looks for test cases). If you want autodiscovery to work
correctly, you will have to use the ``load_tests`` protocol::
# in a testing module
def load_tests(loader, tests, pattern):
tests.addTest(QUnitSuite(qunit_test_path.html))
return tests
outside of that specific case, you can use a ``QUnitSuite`` as a
standard ``TestSuite`` instance, running it, adding it to an other
suite or passing it to a ``TestRunner``
Complaints and Grievances
-------------------------
Speed
~~~~~
Starting up a phantomjs instance and running a suite turns out to have
a rather high overhead, on the order of a second on this machine
(2.4GHz, 8GB RAM and an SSD).
As each ``QUnitSuite`` currently creates its own phantomjs instance,
it's probably a good idea to create bigger suites (put many modules &
tests in the same QUnit html file, which doesn't preclude splitting
them across multiple js files).
Hacks
~~~~~
QUnitSuite contains a pretty big hack which may or may not cause
problem depending on your exact setup: in case of case failure or
error, ``unittest.TestResult`` formats the error traceback provided
alongside the test object. This goes through Python's
traceback-formatting code and there are no hooks there.
One could expect to use a custom ``TestResult``, but for test suites
the ``TestResult`` instance must be provided by the caller, so there
is no direct hook onto it.
This leaves three options:
* Create a custom ``TestResult`` class and require that it be the one
provided to the test suite. This requires altered work flows,
customization of the test runner and (as far as I know) isn't
available through Python 2.7's autodiscovery. It's the cleanest
option but completely fails on practicality.
* Create a custom ``TestResult`` which directly alters the original
result's ``errors`` and ``failures`` attributes as they're part of
the testrunner API. This would work but may put custom results in a
strange state and break e.g. unittest2's ``@failfast``.
* Lastly, monkeypatch the undocumented and implementation detail
``_exc_info_to_string`` on the provided ``result``. This is the
route taken, at least for now.
.. _QUnit: http://qunitjs.com/
.. _PhantomJS: http://phantomjs.org/
.. _Ben Alman: http://benalman.com/
.. _grunt: http://gruntjs.com/

View File

@ -1,95 +0,0 @@
/*
* grunt
* http://gruntjs.com/
*
* Copyright (c) 2012 "Cowboy" Ben Alman
* Licensed under the MIT license.
* http://benalman.com/about/license/
*/
/*global phantom:true*/
'use strict';
var fs = require('fs');
// The page .html file to load.
var url = phantom.args[0];
// Extra, optionally overridable stuff.
var options = JSON.parse(phantom.args[1] || {});
// Default options.
if (!options.timeout) { options.timeout = 5000; }
// Keep track of the last time a client message was sent.
var last = new Date();
// Messages are sent to the parent by appending them to the tempfile.
var sendMessage = function(arg) {
var args = Array.isArray(arg) ? arg : [].slice.call(arguments);
last = new Date();
console.log(JSON.stringify(args));
};
// This allows grunt to abort if the PhantomJS version isn't adequate.
sendMessage('private', 'version', phantom.version);
// Abort if the page doesn't send any messages for a while.
setInterval(function() {
if (new Date() - last > options.timeout) {
sendMessage('fail.timeout');
phantom.exit();
}
}, 100);
// Create a new page.
var page = require('webpage').create();
// The client page must send its messages via alert(jsonstring).
page.onAlert = function(args) {
sendMessage(JSON.parse(args));
};
// Keep track if the client-side helper script already has been injected.
var injected;
page.onUrlChanged = function(newUrl) {
injected = false;
sendMessage('onUrlChanged', newUrl);
};
// Relay console logging messages.
page.onConsoleMessage = function(message) {
sendMessage('console', message);
};
// For debugging.
page.onResourceRequested = function(request) {
sendMessage('onResourceRequested', request.url);
};
page.onResourceReceived = function(request) {
if (request.stage === 'end') {
sendMessage('onResourceReceived', request.url);
}
};
// Run when the page has finished loading.
page.onLoadFinished = function(status) {
// The window has loaded.
sendMessage('onLoadFinished', status);
if (status === 'success') {
if (options.inject && !injected) {
// Inject client-side helper script, but only if it has not yet been
// injected.
sendMessage('inject', options.inject);
page.injectJs(options.inject);
}
} else {
// File loading failure.
sendMessage('fail.load', url);
phantom.exit();
}
};
// Actually load url.
page.open(url);

View File

@ -1,22 +0,0 @@
Copyright (c) 2012 "Cowboy" Ben Alman
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,88 +0,0 @@
/*
* grunt
* http://gruntjs.com/
*
* Copyright (c) 2012 "Cowboy" Ben Alman
* Licensed under the MIT license.
* http://benalman.com/about/license/
*/
/*global QUnit:true, alert:true*/
'use strict';
// Don't re-order tests.
QUnit.config.reorder = false;
// Run tests serially, not in parallel.
QUnit.config.autorun = false;
// Send messages to the parent PhantomJS process via alert! Good times!!
function sendMessage() {
var args = [].slice.call(arguments);
alert(JSON.stringify(args));
}
// These methods connect QUnit to PhantomJS.
QUnit.log(function(obj) {
// What is this I dont even
if (obj.message === '[object Object], undefined:undefined') { return; }
// Parse some stuff before sending it.
var actual = QUnit.jsDump.parse(obj.actual);
var expected = QUnit.jsDump.parse(obj.expected);
// Send it.
sendMessage('qunit.log', obj.result, actual, expected, obj.message, obj.source);
});
QUnit.testStart(function(obj) {
sendMessage('qunit.testStart', obj.name);
});
QUnit.testDone(function(obj) {
sendMessage('qunit.testDone', obj.name, obj.failed, obj.passed, obj.total);
});
QUnit.moduleStart(function(obj) {
sendMessage('qunit.moduleStart', obj.name);
});
QUnit.moduleDone(function(obj) {
sendMessage('qunit.moduleDone', obj.name, obj.failed, obj.passed, obj.total);
});
QUnit.begin(function() {
sendMessage('qunit.begin');
});
QUnit.done(function(obj) {
sendMessage('qunit.done', obj.failed, obj.passed, obj.total, obj.runtime);
});
// PhantomJS (up to and including 1.7) uses a version of webkit so old
// it does not have Function.prototype.bind:
// http://code.google.com/p/phantomjs/issues/detail?id=522
// Use moz polyfill:
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}

View File

@ -1,136 +0,0 @@
import json
import subprocess
import unittest
import os
import time
ROOT = os.path.join(os.path.dirname(__file__), 'grunt')
__all__ = ['QUnitSuite']
def _exc_info_to_string(err, test):
return err
class QUnitTest(unittest.TestCase):
def __init__(self, module, name):
self.module = module
self.name = name
self.failed = False
def shortDescription(self):
return None
def __repr__(self):
return '<QUnitTest %s:%s>' % (self.module, self.name)
def __str__(self):
return '%s: %s' % (self.module, self.name)
class QUnitSuite(unittest.TestSuite):
def __init__(self, qunitfile, timeout=5000):
super(QUnitSuite, self).__init__()
self.testfile = qunitfile
self.timeout = timeout
self._module = None
self._test = None
def run(self, result):
try:
subprocess.call(['phantomjs', '-v'],
stdout=open(os.devnull, 'w'),
stderr=subprocess.STDOUT)
except OSError:
test = QUnitTest('phantomjs', 'javascript tests')
result.startTest(test)
result.startTest(test)
result.addSkip(test , "phantomjs command not found")
result.stopTest(test)
return
result._exc_info_to_string = _exc_info_to_string
try:
self._run(result)
finally:
del result._exc_info_to_string
def _run(self, result):
phantom = subprocess.Popen([
'phantomjs',
'--config=%s' % os.path.join(ROOT, 'phantomjs.json'),
os.path.join(ROOT, 'bootstrap.js'), self.testfile,
json.dumps({
'timeout': self.timeout,
'inject': os.path.join(ROOT, 'qunit-phantomjs-bridge.js')
})
], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
try:
while True:
line = phantom.stdout.readline()
if line:
if self.process(line, result):
break
else:
time.sleep(0.1)
finally:
# If the phantomjs process hasn't quit, kill it
if phantom.poll() is None:
phantom.terminate()
def process(self, line, result):
try:
args = json.loads(line)
except ValueError: # phantomjs stderr
if 'CoreText' not in line:
print line
return False
event_name = args[0]
if event_name == 'qunit.done':
return True
elif event_name == 'fail.load':
self.add_error(result, "PhantomJS unable to load %s" % args[1])
return True
elif event_name == 'fail.timeout':
self.add_error(result, "PhantomJS timed out, possibly due to a"
" missing QUnit start() call")
return True
elif event_name == 'qunit.moduleStart':
self._module = args[1].encode('utf-8') if args[1] else ''
elif event_name == 'qunit.moduleStop':
self._test = None
self._module = None
elif event_name == 'qunit.testStart':
self._test = QUnitTest(self._module, args[1].encode('utf-8'))
result.startTest(self._test)
elif event_name == 'qunit.testDone':
if not self._test.failed:
result.addSuccess(self._test)
result.stopTest(self._test)
self._test = None
elif event_name == 'qunit.log':
if args[1]:
return False
self._test.failed = True
result.addFailure(
self._test, self.failure_to_str(*args[2:]))
elif event_name == 'console':
print args[1]
return False
def add_error(self, result, s):
test = QUnitTest('phantomjs', 'startup')
result.startTest(test)
result.addError(test, s)
result.stopTest(test)
def failure_to_str(self, actual, expected, message, source):
if message or actual == expected:
formatted = str(message or '')
else:
formatted = "%s != %s" % (actual, expected)
if source:
formatted += '\n\n' + source
return formatted

View File

@ -1,24 +1,6 @@
import urllib
import urlparse
from openerp import sql_db, tools
from qunitsuite.suite import QUnitSuite
import openerp
class WebSuite(QUnitSuite):
def __init__(self, module):
url = urlparse.urlunsplit([
'http',
'localhost:{port}'.format(port=tools.config['xmlrpc_port']),
'/web/tests',
urllib.urlencode({
'mod': module,
'source': tools.config['db_name'],
'supadmin': tools.config['admin_passwd'],
'password': 'admin',
}),
''
])
super(WebSuite, self).__init__(url, 50000)
class WebSuite(openerp.tests.HttpCase):
def test_01_js(self):
self.phantom_js('/web/tests?mod=web',"","", login='admin')
def load_tests(loader, standard_tests, _):
standard_tests.addTest(WebSuite('web'))
return standard_tests

View File

@ -3,6 +3,101 @@
-->
<openerp>
<data>
<template id="web.assets_common">
<script type="text/javascript" src="/web/static/lib/es5-shim/es5-shim.min.js"></script>
<script type="text/javascript" src="/web/static/lib/underscore/underscore.js"></script>
<script type="text/javascript" src="/web/static/lib/underscore.string/lib/underscore.string.js"></script>
<script type="text/javascript" src="/web/static/lib/datejs/globalization/en-US.js"></script>
<script type="text/javascript" src="/web/static/lib/spinjs/spin.js"></script>
<!-- jQuery stuff -->
<script type="text/javascript" src="/web/static/lib/jquery/jquery.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.blockUI/jquery.blockUI.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.hotkeys/jquery.hotkeys.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.placeholder/jquery.placeholder.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.timeago/jquery.timeago.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.form/jquery.form.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script>
<link rel="stylesheet" href="/web/static/lib/fontawesome/css/font-awesome.css"/>
</template>
<template id="web.assets_backend">
<t t-call="web.assets_common"/>
<!-- Datejs -->
<script type="text/javascript" src="/web/static/lib/datejs/core.js"></script>
<script type="text/javascript" src="/web/static/lib/datejs/parser.js"></script>
<script type="text/javascript" src="/web/static/lib/datejs/sugarpak.js"></script>
<script type="text/javascript" src="/web/static/lib/datejs/extras.js"></script>
<!-- jQuery addons -->
<script type="text/javascript" src="/web/static/lib/jquery.validate/jquery.validate.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.autosize/jquery.autosize.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.scrollTo/jquery.scrollTo-min.js"></script>
<link rel="stylesheet" href="/web/static/lib/cleditor/jquery.cleditor.css"/>
<script type="text/javascript" src="/web/static/lib/cleditor/jquery.cleditor.js"></script>
<link rel="stylesheet" href="/web/static/lib/jquery.textext/jquery.textext.css"/>
<script type="text/javascript" src="/web/static/lib/jquery.textext/jquery.textext.js"></script>
<link rel="stylesheet" href="/web/static/lib/select2/select2.css"/>
<script type="text/javascript" src="/web/static/lib/select2/select2.js"></script>
<!-- jQuery ui -->
<link rel="stylesheet" href="/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.9.0.custom.css"/>
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
<link rel="stylesheet" href="/web/static/lib/jquery.ui.timepicker/css/jquery-ui-timepicker-addon.css"/>
<script type="text/javascript" src="/web/static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js"></script>
<link rel="stylesheet" href="/web/static/lib/jquery.ui.notify/css/ui.notify.css"/>
<script type="text/javascript" src="/web/static/lib/jquery.ui.notify/js/jquery.notify.js"></script>
<!-- Bootstrap must be loaded after jquery.ui until jquery.ui is removed -->
<link rel="stylesheet" href="/web/static/lib/bootstrap/css/bootstrap.css"/>
<script type="text/javascript" src="/web/static/lib/bootstrap/js/bootstrap.js"></script>
<!-- Backbone -->
<script type="text/javascript" src="/web/static/lib/backbone/backbone.js"></script>
<!-- Internals -->
<link rel="stylesheet" href="/web/static/src/css/base.css"/>
<link rel="stylesheet" href="/web/static/src/css/data_export.css"/>
<link rel="stylesheet" href="/base/static/src/css/modules.css"/>
<script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
<script type="text/javascript" src="/web/static/src/js/openerpframework.js"></script>
<script type="text/javascript" src="/web/static/lib/py.js/lib/py.js"></script>
<script type="text/javascript" src="/web/static/src/js/boot.js"></script>
<script type="text/javascript" src="/web/static/src/js/testing.js"></script>
<script type="text/javascript" src="/web/static/src/js/pyeval.js"></script>
<script type="text/javascript" src="/web/static/src/js/core.js"></script>
<script type="text/javascript" src="/web/static/src/js/formats.js"></script>
<script type="text/javascript" src="/web/static/src/js/chrome.js"></script>
<script type="text/javascript" src="/web/static/src/js/views.js"></script>
<script type="text/javascript" src="/web/static/src/js/data.js"></script>
<script type="text/javascript" src="/web/static/src/js/data_export.js"></script>
<script type="text/javascript" src="/web/static/src/js/search.js"></script>
<script type="text/javascript" src="/web/static/src/js/view_list.js"></script>
<script type="text/javascript" src="/web/static/src/js/view_form.js"></script>
<script type="text/javascript" src="/web/static/src/js/view_list_editable.js"></script>
<script type="text/javascript" src="/web/static/src/js/view_tree.js"></script>
<script type="text/javascript" src="/base/static/src/js/apps.js"></script>
</template>
<template id="web.assets_webclient_manifest">
<!-- This bundle can be used for module manifest asset declaration backward compatibility -->
<t t-foreach="manifest_list('css')" t-as="css_file">
<link rel="stylesheet" t-att-href="css_file"/>
</t>
<t t-foreach="manifest_list('js')" t-as="js_file">
<script type="text/javascript" t-att-src="js_file"></script>
</t>
</template>
<template id="web.layout" name="Web layout">&lt;!DOCTYPE html&gt;
<html style="height: 100%">
@ -23,12 +118,7 @@
<template id="web.webclient_bootstrap" name="Webclient Bootstrap">
<t t-call="web.layout">
<t t-set="head">
<t t-foreach="css" t-as="css_file">
<link rel="stylesheet" t-att-href="css_file"/>
</t>
<t t-foreach="js" t-as="js_file">
<script type="text/javascript" t-att-src="js_file"></script>
</t>
<t t-call-assets="web.assets_backend"/>
<script type="text/javascript">
$(function() {
var s = new openerp.init(<t t-raw="modules"/>);
@ -78,10 +168,10 @@
</script>
<div class="form-group field-db" t-if="databases and len(databases) &gt; 1">
<label for="db" class="control-label">Database</label>
<select name="db" id="db" class="form-control" required="required" t-att-autofocus="'autofocus' if current_db not in databases else None" onchange="dbchanged(this.value)">
<select name="db" id="db" class="form-control" required="required" t-att-autofocus="'autofocus' if request.db not in databases else None" onchange="dbchanged(this.value)">
<option></option>
<t t-foreach="databases" t-as="db">
<option t-att-selected="'selected' if db == current_db else None">
<option t-att-selected="'selected' if db == request.db else None">
<t t-esc="db"/>
</option>
</t>
@ -121,5 +211,55 @@
</t>
</template>
<template id="web.qunit_suite">
&lt;!DOCTYPE html&gt;
<html style="height: 100%">
<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>
<t t-call="web.assets_backend"/>
<script type="text/javascript" id="qunit_config">
QUnit.config.testTimeout = 5 * 60 * 1000;
QUnit.moduleDone(function(result) {
console.log(result.name + " (" + result.passed + "/" + result.total + " passed tests)");
});
QUnit.done(function(result) {
if (result.failed === 0) {
console.log('ok');
}
});
openerp.web.qweb.add_template("/web/webclient/qweb");
</script>
<script type="text/javascript" src="/web/static/test/testing.js"></script>
<script type="text/javascript" src="/web/static/test/framework.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/data.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-misordered.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/list.js"></script>
<script type="text/javascript" src="/web/static/test/list-editable.js"></script>
<script type="text/javascript" src="/web/static/test/mutex.js"></script>
</head>
<body id="oe" class="openerp">
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
</html>
</template>
</data>
</openerp>

View File

@ -10,8 +10,4 @@ Openerp Web API.
'depends': ['web'],
'installable': True,
'auto_install': False,
'js' : [
],
'css' : [
],
}

View File

@ -9,14 +9,8 @@ OpenERP Web Calendar view.
'author': 'OpenERP SA, Valentino Lab (Kalysto)',
'version': '2.0',
'depends': ['web'],
'data' : [],
'js': [
'static/lib/fullcalendar/js/fullcalendar.js',
'static/src/js/*.js'
],
'css': [
'static/lib/fullcalendar/css/*.css',
'static/src/css/*.css'
'data' : [
'views/web_calendar.xml',
],
'qweb': [
'static/src/xml/*.xml',

View File

@ -11,7 +11,6 @@ _.str.toBoolElse = function (str, elseValues, trueValues, falseValues) {
};
openerp.web_calendar = function(instance) {
var _t = instance.web._t,
_lt = instance.web._lt,
QWeb = instance.web.qweb;

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:fdn=3:
-->
<openerp>
<data>
<template id="assets_backend" name="web_calendar assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/web_calendar/static/src/css/web_fullcalendar.css"/>
<link rel="stylesheet" href="/web_calendar/static/lib/fullcalendar/css/fullcalendar.css"/>
<script type="text/javascript" src="/web_calendar/static/lib/fullcalendar/js/fullcalendar.js"></script>
<script type="text/javascript" src="/web_calendar/static/src/js/web_calendar.js"></script>
</xpath>
</template>
</data>
</openerp>

View File

@ -8,15 +8,8 @@ Openerp Web Diagram view.
""",
'version': '2.0',
'depends': ['web'],
'js': [
'static/lib/js/raphael.js',
'static/lib/js/jquery.mousewheel.js',
'static/src/js/vec2.js',
'static/src/js/graph.js',
'static/src/js/diagram.js',
],
'css': [
'static/src/css/base_diagram.css',
'data' : [
'views/web_diagram.xml',
],
'qweb': [
'static/src/xml/*.xml',

View File

@ -24,6 +24,15 @@ instance.web.DiagramView = instance.web.View.extend({
this.ids = this.dataset.ids;
this.on('pager_action_executed', self, self.pager_action_trigger);
},
start: function () {
return this._super().then(function () {
return $.when(
openerp.webclient.session.load_js(['/web/js/web_diagram.assets_raphael'])
);
}).fail(function () {
throw new Error("Could not load raphael.js");
});
},
view_loading: function(r) {
return this.load_diagram(r);

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:fdn=3:
-->
<openerp>
<data>
<template id="assets_backend" name="web_diagram assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/web_diagram/static/src/css/base_diagram.css"/>
<script type="text/javascript" src="/web_diagram/static/lib/js/jquery.mousewheel.js"></script>
<script type="text/javascript" src="/web_diagram/static/src/js/vec2.js"></script>
<script type="text/javascript" src="/web_diagram/static/src/js/graph.js"></script>
<script type="text/javascript" src="/web_diagram/static/src/js/diagram.js"></script>
</xpath>
</template>
<template id="assets_raphael" name="raphael.js">
<script type="text/javascript" src="/web_diagram/static/lib/js/raphael.js"></script>
</template>
</data>
</openerp>

View File

@ -8,12 +8,9 @@ OpenERP Web Gantt chart view.
""",
'version': '2.0',
'depends': ['web'],
'js': [
'static/lib/dhtmlxGantt/sources/dhtmlxcommon.js',
'static/lib/dhtmlxGantt/sources/dhtmlxgantt.js',
'static/src/js/gantt.js'
'data' : [
'views/web_gantt.xml',
],
'css': ['static/src/css/gantt.css', 'static/lib/dhtmlxGantt/codebase/dhtmlxgantt.css'],
'qweb': [
'static/src/xml/*.xml',
],

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:fdn=3:
-->
<openerp>
<data>
<template id="assets_backend" name="web_gantt assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/web_gantt/static/src/css/gantt.css"/>
<link rel="stylesheet" href="/web_gantt/static/lib/dhtmlxGantt/codebase/dhtmlxgantt.css"/>
<script type="text/javascript" src="/web_gantt/static/lib/dhtmlxGantt/sources/dhtmlxcommon.js"></script>
<script type="text/javascript" src="/web_gantt/static/lib/dhtmlxGantt/sources/dhtmlxgantt.js"></script>
<script type="text/javascript" src="/web_gantt/static/src/js/gantt.js"></script>
</xpath>
</template>
</data>
</openerp>

View File

@ -14,15 +14,8 @@ Graph Views for Web Client.
""",
'version': '3.0',
'depends': ['web'],
'js': [
'static/lib/nvd3/d3.v3.js',
'static/lib/nvd3/nv.d3.js',
'static/src/js/graph_view.js',
'static/src/js/pivot_table.js',
'static/src/js/graph_widget.js',
],
'css': [
'static/src/css/*.css',
'data' : [
'views/web_graph.xml',
],
'qweb' : [
'static/src/xml/*.xml',

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:fdn=3:
-->
<openerp>
<data>
<template id="assets_backend" name="web_graph assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/web_graph/static/src/css/graph.css"/>
<link rel="stylesheet" href="/web_graph/static/src/css/nv.d3.css"/>
<script type="text/javascript" src="/web_graph/static/lib/nvd3/d3.v3.js"></script>
<script type="text/javascript" src="/web_graph/static/lib/nvd3/nv.d3.js"></script>
<script type="text/javascript" src="/web_graph/static/src/js/graph_view.js"></script>
<script type="text/javascript" src="/web_graph/static/src/js/pivot_table.js"></script>
<script type="text/javascript" src="/web_graph/static/src/js/graph_widget.js"></script>
</xpath>
</template>
</data>
</openerp>

View File

@ -1,15 +0,0 @@
{
'name': 'Hello',
'category': 'Hidden',
'description':"""
OpenERP Web example module.
===========================
""",
'version': '2.0',
'depends': [],
'js': ['static/*/*.js', 'static/*/js/*.js'],
'css': [],
'auto_install': False,
'web_preload': False,
}

View File

@ -1,16 +0,0 @@
/*---------------------------------------------------------
* OpenERP base_hello (Example module)
*---------------------------------------------------------*/
openerp.web_hello = function(instance) {
instance.web.SearchView = instance.web.SearchView.extend({
init:function() {
this._super.apply(this,arguments);
this.on('search_data', this, function(){console.log('hello');});
}
});
};
// vim:et fdc=0 fdl=0:

View File

@ -8,11 +8,8 @@ OpenERP Web kanban view.
""",
'version': '2.0',
'depends': ['web'],
'js': [
'static/src/js/kanban.js'
],
'css': [
'static/src/css/kanban.css'
'data' : [
'views/web_kanban.xml',
],
'qweb' : [
'static/src/xml/*.xml',

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:fdn=3:
-->
<openerp>
<data>
<template id="assets_backend" name="web_kanban assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/web_kanban/static/src/css/kanban.css"/>
<script type="text/javascript" src="/web_kanban/static/src/js/kanban.js"></script>
</xpath>
</template>
</data>
</openerp>

View File

@ -6,11 +6,8 @@ This widget allows to display gauges using justgage library.
""",
'version': '1.0',
'depends': ['web_kanban'],
'js': [
'static/lib/justgage/justgage.js',
'static/src/js/kanban_gauge.js'
],
'css': [
'data' : [
'views/web_kanban_gauge.xml',
],
'qweb': [
],

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:fdn=3:
-->
<openerp>
<data>
<template id="assets_backend" name="web_kanban_gauge assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/web_kanban_gauge/static/lib/justgage/justgage.js"></script>
<script type="text/javascript" src="/web_kanban_gauge/static/src/js/kanban_gauge.js"></script>
</xpath>
</template>
</data>
</openerp>

View File

@ -6,11 +6,8 @@ This widget allows to display sparklines using jquery.sparkline library.
""",
'version': '1.0',
'depends': ['web_kanban'],
'js': [
"static/lib/jquery.sparkline/jquery.sparkline.js",
'static/src/js/kanban_sparkline.js'
],
'css': [
'data' : [
'views/web_kanban_sparkline.xml',
],
'qweb': [
],

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:fdn=3:
-->
<openerp>
<data>
<template id="assets_backend" name="web_kanban_sparkline assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/web_kanban_sparkline/static/lib/jquery.sparkline/jquery.sparkline.js"></script>
<script type="text/javascript" src="/web_kanban_sparkline/static/src/js/kanban_sparkline.js"></script>
</xpath>
</template>
</data>
</openerp>

View File

@ -1 +1,2 @@
# -*- coding: utf-8 -*-
import tests

View File

@ -7,8 +7,9 @@ OpenERP Web test suite.
""",
'version': '2.0',
'depends': [],
'js': ['static/src/js/*.js'],
'css': ['static/src/css/*.css'],
'depends': ['web', 'web_kanban'],
'data' : [
'views/web_tests.xml',
],
'auto_install': True,
}

View File

@ -1,2 +1,2 @@
# -*- coding: utf-8 -*-
import test_js
import test_ui

View File

@ -11,6 +11,6 @@ class TestUi(openerp.tests.HttpCase):
def test_03_js_public(self):
self.phantom_js('/',"console.log('ok')","console")
def test_04_js_admin(self):
self.phantom_js('/',"console.log('ok')","openerp.client.action_manager.inner_widget.views.form", login='admin')
self.phantom_js('/web',"console.log('ok')","openerp.client.action_manager.inner_widget.views.form", login='admin')
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:fdn=3:
-->
<openerp>
<data>
<template id="assets_backend" name="web_tests assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/web_tests/static/src/css/web_tests.css"/>
<script type="text/javascript" src="/web_tests/static/src/js/web_tests.js"></script>
</xpath>
</template>
</data>
</openerp>

View File

@ -8,7 +8,8 @@ OpenERP Web demo of a test suite
Test suite example, same code as that used in the testing documentation.
""",
'depends': ['web'],
'js': ['static/src/js/demo.js'],
'test': ['static/test/demo.js'],
'data' : [
'views/web_tests_demo.xml',
],
'qweb': ['static/src/xml/demo.xml'],
}

View File

@ -1,11 +1,12 @@
// static/src/js/demo.js
openerp.web_tests_demo = function (instance) {
_.extend(instance.web_tests_demo, {
(function () {
openerp.web_tests_demo = {
value_true: true,
SomeType: instance.web.Class.extend({
SomeType: openerp.web.Class.extend({
init: function (value) {
this.value = value;
}
})
});
};
};
}());

View File

@ -1,6 +0,0 @@
# -*- coding: utf-8 -*-
from openerp.addons.web.tests.test_js import WebSuite
def load_tests(loader, standard_tests, _):
standard_tests.addTest(WebSuite('web_tests_demo'))
return standard_tests

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:fdn=3:
-->
<openerp>
<data>
<template id="assets_backend" name="web_tests_demo assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/web_tests_demo/static/src/js/demo.js"></script>
</xpath>
</template>
<template id="qunit_suite" name="web_tests_demo qunit" inherit_id="web.qunit_suite">
<xpath expr="//head" position="inside">
<script type="text/javascript" src="/web_tests_demo/static/test/demo.js"></script>
</xpath>
</template>
</data>
</openerp>

View File

@ -8,8 +8,9 @@ OpenERP Web to edit views.
""",
'version': '2.0',
'depends':['web'],
'js': ['static/src/js/view_editor.js'],
'css': ['static/src/css/view_editor.css'],
'data' : [
'views/web_view_editor.xml',
],
'qweb': ['static/src/xml/view_editor.xml'],
'auto_install': True,
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:fdn=3:
-->
<openerp>
<data>
<template id="assets_backend" name="web_view_editor assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/web_view_editor/static/src/css/view_editor.css"/>
<script type="text/javascript" src="/web_view_editor/static/src/js/view_editor.js"></script>
</xpath>
</template>
</data>
</openerp>