[MERGE] from trunk
authorXavier Morel <xmo@openerp.com>
Tue, 20 Nov 2012 10:17:33 +0000 (11:17 +0100)
committerXavier Morel <xmo@openerp.com>
Tue, 20 Nov 2012 10:17:33 +0000 (11:17 +0100)
when creating the instance, set instance.session.uid to 42 so the evaluator has something to chew on when it tries to create the evaluation context

bzr revid: xmo@openerp.com-20121120101733-b0ire11bbuywfi8u

1  2 
addons/web/__openerp__.py
addons/web/static/src/js/boot.js
addons/web/static/src/js/corelib.js
addons/web/static/src/js/data.js
addons/web/static/src/js/testing.js
addons/web/static/test/evals.js
addons/web/static/test/test.html.THIS
doc/index.rst
setup.py

@@@ -38,9 -39,9 +39,10 @@@ This module provides the core of the Op
          "static/lib/underscore/underscore.string.js",
          "static/lib/backbone/backbone.js",
          "static/lib/cleditor/jquery.cleditor.js",
-         "static/lib/py.js/lib/py.js",        
+         "static/lib/py.js/lib/py.js",
          "static/src/js/boot.js",
+         "static/src/js/testing.js",
 +        "static/src/js/pyeval.js",
          "static/src/js/corelib.js",
          "static/src/js/coresetup.js",
          "static/src/js/dates.js",
Simple merge
@@@ -1026,29 -1253,27 +963,31 @@@ instance.web.JsonRPC = instance.web.Cla
              id: _.uniqueId('r')
          };
          var deferred = $.Deferred();
-         this.trigger('request', url, payload);
-         var aborter = params.aborter;
-         delete params.aborter;
+         if (! options.shadow)
+             this.trigger('request', url, payload);
 -        var request = this.rpc_function(url, payload).done(
 +        var request;
 +        if (url.url === '/web/session/eval_domain_and_context') {
 +            // intercept eval_domain_and_context
 +            request = instance.web.pyeval.eval_domains_and_contexts(
 +                params)
 +        } else {
 +            request = this.rpc_function(url, payload);
 +        }
-         request.then(function (response, textStatus, jqXHR) {
-                 self.trigger('response', response);
++        request.then(
+             function (response, textStatus, jqXHR) {
+                 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);
 -                    }
                      deferred.resolve(response["result"], textStatus, jqXHR);
                  } else if (response.error.data.type === "session_invalid") {
                      self.uid = false;
                  } else {
                      deferred.reject(response.error, $.Event());
                  }
 -            }
 -        ).fail(
 +            },
              function(jqXHR, textStatus, errorThrown) {
-                 self.trigger('error');
+                 if (! options.shadow)
+                     self.trigger('response_failed', jqXHR);
                  var error = {
                      code: -32098,
                      message: "XmlHttpRequestError " + errorThrown,
@@@ -282,8 -289,8 +291,9 @@@ instance.web.Model = instance.web.Class
              kwargs = args;
              args = [];
          }
 +        instance.web.pyeval.ensure_evaluated(args, kwargs);
-         return instance.session.rpc('/web/dataset/call_kw', {
+         var debug = instance.session.debug ? '/'+this.name+':'+method : '';
+         return instance.session.rpc('/web/dataset/call_kw' + debug, {
              model: this.name,
              method: method,
              args: args,
index 0000000,bf39f54..5aeeeb4
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,393 +1,397 @@@
+ // Test support structures and methods for OpenERP
+ openerp.testing = {};
+ (function (testing) {
+     var dependencies = {
 -        corelib: [],
++        pyeval: [],
++        corelib: ['pyeval'],
+         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 (instance.session) {
++                instance.session.uid = 42;
++            }
+             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);
@@@ -1,54 -1,11 +1,45 @@@
- $(document).ready(function () {
-     var openerp;
-     module("eval.types", {
-         setup: function () {
-             openerp = window.openerp.testing.instanceFor('coresetup');
-             openerp.session.uid = 42;
-         }
-     });
-     test('strftime', function () {
++openerp.testing.section('eval.types', {
++    dependencies: ['web.coresetup']
++}, function (test) {
++    test('strftime', function (instance) {
 +        var d = new Date();
-         var context = openerp.web.pyeval.context();
++        var context = instance.web.pyeval.context();
 +        strictEqual(
 +            py.eval("time.strftime('%Y')", context),
 +            String(d.getFullYear()));
 +        strictEqual(
 +            py.eval("time.strftime('%Y')+'-01-30'", context),
 +            String(d.getFullYear()) + '-01-30');
 +        strictEqual(
 +            py.eval("time.strftime('%Y-%m-%d %H:%M:%S')", context),
 +            _.str.sprintf('%04d-%02d-%02d %02d:%02d:%02d',
 +                d.getFullYear(), d.getMonth() + 1, d.getDate(),
 +                d.getHours(), d.getMinutes(), d.getSeconds()));
 +    });
-     module("eval.contexts", {
-         setup: function () {
-             openerp = window.openerp.testing.instanceFor('coresetup');
-             openerp.session.uid = 42;
-         }
-     });
-     test('context_recursive', function () {
++});
+ openerp.testing.section('eval.contexts', {
+     dependencies: ['web.coresetup']
+ }, function (test) {
++    test('context_recursive', function (instance) {
 +        var context_to_eval = [{
 +            __ref: 'context',
 +            __debug: '{"foo": context.get("bar", "qux")}'
 +        }];
 +        deepEqual(
-             openerp.web.pyeval.eval('contexts', context_to_eval, {bar: "ok"}),
++            instance.web.pyeval.eval('contexts', context_to_eval, {bar: "ok"}),
 +            {foo: 'ok'});
 +        deepEqual(
-             openerp.web.pyeval.eval('contexts', context_to_eval, {bar: false}),
++            instance.web.pyeval.eval('contexts', context_to_eval, {bar: false}),
 +            {foo: false});
 +        deepEqual(
-             openerp.web.pyeval.eval('contexts', context_to_eval),
++            instance.web.pyeval.eval('contexts', context_to_eval),
 +            {foo: 'qux'});
 +    });
-     test('context_sequences', function () {
+     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.web.pyeval.eval('contexts', [
 -        var result = instance.session.test_eval_contexts([
++        var result = instance.web.pyeval.eval('contexts', [
              {
                  "__contexts": [
                      {
@@@ -91,8 -48,8 +82,8 @@@
              record_id: active_id
          });
      });
-     test('non-literal_eval_contexts', function () {
-         var result = openerp.web.pyeval.eval('contexts', [{
+     test('non-literal_eval_contexts', function (instance) {
 -        var result = instance.session.test_eval_contexts([{
++        var result = instance.web.pyeval.eval('contexts', [{
              "__ref": "compound_context",
              "__contexts": [
                  {"__ref": "context", "__debug": "{'type':parent.type}",
          }]);
          deepEqual(result, {type: 'out_invoice'});
      });
-     module('eval.domains', {
-         setup: function () {
-             openerp = window.openerp.testing.instanceFor('coresetup');
-             window.openerp.web.dates(openerp);
-             openerp.session.uid = 42;
-         }
-     });
-     test('current_date', function () {
-         var current_date = openerp.web.date_to_str(new Date());
-         var result = openerp.web.pyeval.eval('domains',
-             [[],{"__ref":"domain","__debug":"[('name','>=',current_date),('name','<=',current_date)]","__id":"5dedcfc96648"}]);
+ });
+ 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(
++        var result = instance.web.pyeval.eval('domains',
+             [[],{"__ref":"domain","__debug":"[('name','>=',current_date),('name','<=',current_date)]","__id":"5dedcfc96648"}],
 -            instance.session.test_eval_get_context());
++            instance.web.pyeval.context());
          deepEqual(result, [
              ['name', '>=', current_date],
              ['name', '<=', current_date]
          ]);
 -    })
 +    });
-     test('context_freevar', function () {
++    test('context_freevar', function (instance) {
 +        var domains_to_eval = [{
 +            __ref: 'domain',
 +            __debug: '[("foo", "=", context.get("bar", "qux"))]'
 +        }, [['bar', '>=', 42]]];
 +        deepEqual(
-             openerp.web.pyeval.eval('domains', domains_to_eval, {bar: "ok"}),
++            instance.web.pyeval.eval('domains', domains_to_eval, {bar: "ok"}),
 +            [['foo', '=', 'ok'], ['bar', '>=', 42]]);
 +        deepEqual(
-             openerp.web.pyeval.eval('domains', domains_to_eval, {bar: false}),
++            instance.web.pyeval.eval('domains', domains_to_eval, {bar: false}),
 +            [['foo', '=', false], ['bar', '>=', 42]]);
 +        deepEqual(
-             openerp.web.pyeval.eval('domains', domains_to_eval),
++            instance.web.pyeval.eval('domains', domains_to_eval),
 +            [['foo', '=', 'qux'], ['bar', '>=', 42]]);
 +    });
-     module('eval.groupbys', {
-         setup: function () {
-             openerp = window.openerp.testing.instanceFor('coresetup');
-             openerp.session.uid = 42;
-         }
-     });
-     test('groupbys_00', function () {
-         var result = openerp.web.pyeval.eval('groupbys', [
++});
++openerp.testing.section('eval.groupbys', {
++    dependencies: ['web.coresetup']
++}, function (test) {
++    test('groupbys_00', function (instance) {
++        var result = instance.web.pyeval.eval('groupbys', [
 +            {group_by: 'foo'},
 +            {group_by: ['bar', 'qux']},
 +            {group_by: null},
 +            {group_by: 'grault'}
 +        ]);
 +        deepEqual(result, ['foo', 'bar', 'qux', 'grault']);
 +    });
-     test('groupbys_01', function () {
-         var result = openerp.web.pyeval.eval('groupbys', [
++    test('groupbys_01', function (instance) {
++        var result = instance.web.pyeval.eval('groupbys', [
 +            {group_by: 'foo'},
 +            { __ref: 'context', __debug: '{"group_by": "bar"}' },
 +            {group_by: 'grault'}
 +        ]);
 +        deepEqual(result, ['foo', 'bar', 'grault']);
 +    });
-     test('groupbys_02', function () {
-         var result = openerp.web.pyeval.eval('groupbys', [
++    test('groupbys_02', function (instance) {
++        var result = instance.web.pyeval.eval('groupbys', [
 +            {group_by: 'foo'},
 +            {
 +                __ref: 'compound_context',
 +                __contexts: [ {group_by: 'bar'} ],
 +                __eval_context: null
 +            },
 +            {group_by: 'grault'}
 +        ]);
 +        deepEqual(result, ['foo', 'bar', 'grault']);
 +    });
-     test('groupbys_03', function () {
-         var result = openerp.web.pyeval.eval('groupbys', [
++    test('groupbys_03', function (instance) {
++        var result = instance.web.pyeval.eval('groupbys', [
 +            {group_by: 'foo'},
 +            {
 +                __ref: 'compound_context',
 +                __contexts: [
 +                    { __ref: 'context', __debug: '{"group_by": value}' }
 +                ],
 +                __eval_context: { value: 'bar' }
 +            },
 +            {group_by: 'grault'}
 +        ]);
 +        deepEqual(result, ['foo', 'bar', 'grault']);
 +    });
-     test('groupbys_04', function () {
-         var result = openerp.web.pyeval.eval('groupbys', [
++    test('groupbys_04', function (instance) {
++        var result = instance.web.pyeval.eval('groupbys', [
 +            {group_by: 'foo'},
 +            {
 +                __ref: 'compound_context',
 +                __contexts: [
 +                    { __ref: 'context', __debug: '{"group_by": value}' }
 +                ],
 +                __eval_context: { value: 'bar' }
 +            },
 +            {group_by: 'grault'}
 +        ], { value: 'bar' });
 +        deepEqual(result, ['foo', 'bar', 'grault']);
 +    });
-     test('groupbys_05', function () {
-         var result = openerp.web.pyeval.eval('groupbys', [
++    test('groupbys_05', function (instance) {
++        var result = instance.web.pyeval.eval('groupbys', [
 +            {group_by: 'foo'},
 +            { __ref: 'context', __debug: '{"group_by": value}' },
 +            {group_by: 'grault'}
 +        ], { value: 'bar' });
 +        deepEqual(result, ['foo', 'bar', 'grault']);
 +    });
  });
index 0000000,0000000..f5f29f8
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,63 @@@
++<!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.7.2.js"></script>
++    <script src="/web/static/lib/jquery.ui/js/jquery-ui-1.8.17.custom.min.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/pyeval.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-editable.js"></script>
++</html>
diff --cc doc/index.rst
index 6a0d8de,5842a46..0000000
deleted file mode 100644,100644
+++ /dev/null
@@@ -1,32 -1,31 +1,0 @@@
- .. OpenERP Web documentation master file, created by
-    sphinx-quickstart on Fri Mar 18 16:31:55 2011.
-    You can adapt this file completely to your liking, but it should at least
-    contain the root `toctree` directive.
- Welcome to OpenERP Web's documentation!
- =======================================
- Contents:
- .. toctree::
-     :maxdepth: 1
-     changelog-7.0
-     async
-     rpc
-     widget
-     search-view
-     list-view
-     form-notes
-     guides/client-action
- Indices and tables
- ==================
- * :ref:`genindex`
- * :ref:`modindex`
- * :ref:`search`
 -:orphan:
 -
 -========================================
 -OpenERP Server Developers Documentation
 -========================================
 -
 -OpenERP Server
 -''''''''''''''
 -
 -.. toctree::
 -   :maxdepth: 2
 -
 -   01_getting_started
 -   02_architecture
 -   03_module_dev
 -   04_security
 -   05_test_framework
 -   06_misc
 -   09_deployment
 -
 -OpenERP Server API
 -''''''''''''''''''
 -
 -.. toctree::
 -   :maxdepth: 1
 -
 -   api_core.rst
 -   api_models.rst
 -
 -
 -
diff --cc setup.py
index 6e1adad,6efec4e..0000000
deleted file mode 100755,100755
+++ /dev/null
@@@ -1,126 -1,138 +1,0 @@@
--#!/usr/bin/env python
--# -*- coding: utf-8 -*-
--##############################################################################
--#
--#    OpenERP, Open Source Management Solution
--#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
--#
--#    This program is free software: you can redistribute it and/or modify
--#    it under the terms of the GNU Affero General Public License as
--#    published by the Free Software Foundation, either version 3 of the
--#    License, or (at your option) any later version.
--#
--#    This program is distributed in the hope that it will be useful,
--#    but WITHOUT ANY WARRANTY; without even the implied warranty of
--#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--#    GNU Affero General Public License for more details.
--#
--#    You should have received a copy of the GNU Affero General Public License
--#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
--#
--##############################################################################
--
--import glob, os, re, setuptools, sys
--from os.path import join, isfile
--
--# List all data files
--def data():
--    files = []
--    for root, dirnames, filenames in os.walk('openerp'):
--        for filename in filenames:
--            if not re.match(r'.*(\.pyc|\.pyo|\~)$',filename):
--                files.append(os.path.join(root, filename))
--    d = {}
--    for v in files:
--        k=os.path.dirname(v)
--        if k in d:
--            d[k].append(v)
--        else:
--            d[k]=[v]
--    r = d.items()
--    if os.name == 'nt':
--        r.append(("Microsoft.VC90.CRT", glob.glob('C:\Microsoft.VC90.CRT\*.*')))
--
--    import babel
--    r.append(("localedata",
--              glob.glob(os.path.join(os.path.dirname(babel.__file__), "localedata" , '*'))))
--
--    return r
--
--def gen_manifest():
--    file_list="\n".join(data())
--    open('MANIFEST','w').write(file_list)
--
--if os.name == 'nt':
--    sys.path.append("C:\Microsoft.VC90.CRT")
--
--def py2exe_options():
--    if os.name == 'nt':
--        import py2exe
--        return {
--            "console" : [ { "script": "openerp-server", "icon_resources": [(1, join("install","openerp-icon.ico"))], }],
--            'options' : {
--                "py2exe": {
--                    "skip_archive": 1,
--                    "optimize": 2,
--                    "dist_dir": 'dist',
--                    "packages": [ "DAV", "HTMLParser", "PIL", "asynchat", "asyncore", "commands", "dateutil", "decimal", "email", "encodings", "imaplib", "lxml", "lxml._elementpath", "lxml.builder", "lxml.etree", "lxml.objectify", "mako", "openerp", "poplib", "pychart", "pydot", "pyparsing", "reportlab", "select", "simplejson", "smtplib", "uuid", "vatnumber", "vobject", "xml", "xml.dom", "yaml", ],
--                    "excludes" : ["Tkconstants","Tkinter","tcl"],
--                }
--            }
--        }
--    else:
--        return {}
--
--execfile(join(os.path.dirname(__file__), 'openerp', 'release.py'))
--
- setuptools.setup(
-       name             = 'openerp',
-       version          = version,
-       description      = description,
-       long_description = long_desc,
-       url              = url,
-       author           = author,
-       author_email     = author_email,
-       classifiers      = filter(None, classifiers.split("\n")),
-       license          = license,
-       scripts          = ['openerp-server'],
-       data_files       = data(),
-       packages         = setuptools.find_packages(),
-       #include_package_data = True,
-       install_requires = [
-         # TODO the pychart package we include in openerp corresponds to PyChart 1.37.
-         # It seems there is a single difference, which is a spurious print in generate_docs.py.
-         # It is probably safe to move to PyChart 1.39 (the latest one).
-         # (Let setup.py choose the latest one, and we should check we can remove pychart from
-         # our tree.) http://download.gna.org/pychart/
-         # TODO  'pychart',
-           'babel',
-           'feedparser',
-           'gdata',
-           'lxml',
-           'mako',
-           'psycopg2',
-           'pydot',
-           'python-dateutil < 2',
-           'python-ldap',
-           'python-openid',
-           'pytz',
-           'pywebdav',
-           'pyyaml',
-           'reportlab',
-           'simplejson',
-           'vatnumber',
-           'vobject',
-           'werkzeug',
-           'xlwt',
-           'zsi',
-       ],
-       extras_require = {
-           'SSL' : ['pyopenssl'],
-       },
-       **py2exe_options()
- )
- # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 -# Notes for OpenERP developer on windows:
 -#
 -# To setup a windows developer evironement install python2.7 then pip and use
 -# "pip install <depencey>" for every dependency listed below.
 -#
 -# Dependecies that requires DLLs are not installable with pip install, for
 -# them we added comments with links where you can find the installers.
 -#
 -# OpenERP on windows also require the pywin32, the binary can be found at
 -# http://pywin32.sf.net
 -#
 -# Both python2.7 32bits and 64bits are known to work.
 -
 -setuptools.setup(
 -      name             = 'openerp',
 -      version          = version,
 -      description      = description,
 -      long_description = long_desc,
 -      url              = url,
 -      author           = author,
 -      author_email     = author_email,
 -      classifiers      = filter(None, classifiers.split("\n")),
 -      license          = license,
 -      scripts          = ['openerp-server'],
 -      data_files       = data(),
 -      packages         = setuptools.find_packages(),
 -      dependency_links = ['http://download.gna.org/pychart/'],
 -      #include_package_data = True,
 -      install_requires = [
 -          'pychart', # not on pypi, use: pip install http://download.gna.org/pychart/PyChart-1.39.tar.gz
 -          'babel',
 -          'docutils',
 -          'feedparser',
 -          'gdata',
 -          'lxml < 3', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
 -          'mako',
 -          'PIL', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
 -          'psutil', # windows binary code.google.com/p/psutil/downloads/list
 -          'psycopg2',
 -          'pydot',
 -          'python-dateutil < 2',
 -          'python-ldap', # optional
 -          'python-openid',
 -          'pytz',
 -          'pywebdav',
 -          'pyyaml',
 -          'reportlab', # windows binary pypi.python.org/pypi/reportlab
 -          'simplejson',
 -          'vatnumber',
 -          'vobject',
 -          'werkzeug',
 -          'xlwt',
 -      ],
 -      extras_require = {
 -          'SSL' : ['pyopenssl'],
 -      },
 -      tests_require = ['unittest2'],
 -      **py2exe_options()
 -)
 -
 -
 -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: