From eff170870451284a7317a9a0554e6e2060c9071d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 31 Jan 2013 16:31:00 +0100 Subject: [PATCH 1/5] [IMP] web module tutorial: start adding RPC stuff bzr revid: xmo@openerp.com-20130131153100-gze86fbchz4tnrg3 --- addons/web/doc/module.rst | 30 ++++++++ addons/web/doc/module/__init__.py.1.diff | 16 +++++ .../static/src/js/first_module.js.7.diff | 71 +++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 addons/web/doc/module/__init__.py.1.diff create mode 100644 addons/web/doc/module/static/src/js/first_module.js.7.diff diff --git a/addons/web/doc/module.rst b/addons/web/doc/module.rst index 8306e885f65..df849afc54c 100644 --- a/addons/web/doc/module.rst +++ b/addons/web/doc/module.rst @@ -273,6 +273,36 @@ the button displayed in the UI. Then comes some actual logic: Starting and stopping the watch now works, and correctly tracks time since having started the watch, neatly formatted. +Burning through the skies +------------------------- + +All work so far has been "local" outside of the original impetus +provided by the client action: the widget is self-contained and, once +started, does not communicate with anything outside itself. Not only +that, but it has no persistence: if the user leaves the stopwatch +screen (to go and see his inbox, or do some well-deserved accounting, +for instance) whatever was being timed will be lost. + +To prevent this irremediable loss, we can use OpenERP's support for +storing data as a model, allowing so that we don't lose our data and +can later retrieve, query and manipulate it. First let's create a +basic OpenERP model in which our data will be stored: + +.. literalinclude:: module/__init__.py.1.diff + :language: diff + +then let's add saving times to the database every time the stopwatch +is stopped, using :js:class:`the "high-level" Model API +`: + +.. literalinclude:: module/static/src/js/first_module.js.7.diff + :language: diff + +A look at the "Network" tab of your preferred browser's developer +tools while playing with the stopwatch will show that the save +(creation) request is indeed sent (and replied to, even though we're +ignoring the response at this point). + .. [#DOM-building] they are not alternative solutions: they work very well together. Templates are used to build "just DOM", sub-widgets are used to build DOM subsections diff --git a/addons/web/doc/module/__init__.py.1.diff b/addons/web/doc/module/__init__.py.1.diff new file mode 100644 index 00000000000..6f16f0e9a08 --- /dev/null +++ b/addons/web/doc/module/__init__.py.1.diff @@ -0,0 +1,16 @@ +--- web_example/__init__.py ++++ web_example/__init__.py +@@ -1,1 +1,14 @@ ++# __init__.py ++from openerp.osv import orm, fields ++ ++ ++class Times(orm.Model): ++ _name = 'web_example.stopwatch' ++ ++ _columns = { ++ 'time': fields.integer("Time", required=True, ++ help="Measured time in milliseconds"), ++ 'user_id': fields.many2one('res.users', "User", required=True, ++ help="User who registered the measurement") ++ } diff --git a/addons/web/doc/module/static/src/js/first_module.js.7.diff b/addons/web/doc/module/static/src/js/first_module.js.7.diff new file mode 100644 index 00000000000..1d3430656fc --- /dev/null +++ b/addons/web/doc/module/static/src/js/first_module.js.7.diff @@ -0,0 +1,71 @@ +--- web_example/static/src/js/first_module.js ++++ web_example/static/src/js/first_module.js +@@ -1,52 +1,60 @@ + // static/src/js/first_module.js + openerp.web_example = function (instance) { + instance.web.client_actions.add('example.action', 'instance.web_example.Action'); + instance.web_example.Action = instance.web.Widget.extend({ + template: 'web_example.action', + events: { + 'click .oe_web_example_start button': 'watch_start', + 'click .oe_web_example_stop button': 'watch_stop' + }, + init: function () { + this._super.apply(this, arguments); + this._start = null; + this._watch = null; + }, ++ current: function () { ++ // Subtracting javascript dates returns the difference in milliseconds ++ return new Date() - this._start; ++ }, +- update_counter: function () { ++ update_counter: function (time) { + var h, m, s; +- // Subtracting javascript dates returns the difference in milliseconds +- var diff = new Date() - this._start; +- s = diff / 1000; ++ s = time / 1000; + m = Math.floor(s / 60); + s -= 60*m; + h = Math.floor(m / 60); + m -= 60*h; + this.$('.oe_web_example_timer').text( + _.str.sprintf("%02d:%02d:%02d", h, m, s)); + }, + watch_start: function () { + this.$el.addClass('oe_web_example_started') + .removeClass('oe_web_example_stopped'); + this._start = new Date(); + // Update the UI to the current time +- this.update_counter(); ++ this.update_counter(this.current()); + // Update the counter at 30 FPS (33ms/frame) +- this._watch = setInterval( +- this.proxy('update_counter'), ++ this._watch = setInterval(function () { ++ this.update_counter(this.current()); ++ }.bind(this), + 33); + }, + watch_stop: function () { + clearInterval(this._watch); ++ var time = this.current(); +- this.update_counter(); ++ this.update_counter(time); + this._start = this._watch = null; + this.$el.removeClass('oe_web_example_started') + .addClass('oe_web_example_stopped'); ++ new instance.web.Model('web_example.stopwatch').call('create', [{ ++ user_id: instance.session.uid, ++ time: time, ++ }]); + }, + destroy: function () { + if (this._watch) { + clearInterval(this._watch); + } + this._super(); + } + }); + }; From 06954ded48b0b9e9736eaf7325e89454d8be22c3 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Feb 2013 08:52:00 +0100 Subject: [PATCH 2/5] [IMP] replace ad-hoc patch display by patchqueue bzr revid: xmo@openerp.com-20130211075200-lwd79pktewqf0wkk --- addons/web/doc/conf.py | 6 +- addons/web/doc/module.rst | 59 ++++++------------- addons/web/doc/module/0 | 17 ++++++ addons/web/doc/module/10 | 13 ++++ addons/web/doc/module/11 | 11 ++++ addons/web/doc/module/12 | 28 +++++++++ .../src/js/first_module.js.4.diff => 14} | 12 ++-- .../src/css/web_example.css.1.diff => 15} | 10 ++-- .../src/js/first_module.js.5.diff => 16} | 10 ++-- .../src/js/first_module.js.6.diff => 17} | 15 ++--- .../web/doc/module/{__init__.py.1.diff => 18} | 9 ++- .../src/js/first_module.js.7.diff => 19} | 37 +++--------- addons/web/doc/module/2 | 12 ++++ addons/web/doc/module/3 | 9 +++ addons/web/doc/module/4 | 11 ++++ addons/web/doc/module/5 | 11 ++++ addons/web/doc/module/6 | 29 +++++++++ .../src/js/first_module.js.2.diff => 8} | 7 ++- .../src/js/first_module.js.3.diff => 9} | 7 ++- addons/web/doc/module/__init__.py | 0 addons/web/doc/module/__openerp__.py | 7 --- addons/web/doc/module/__openerp__.py.1.diff | 11 ---- addons/web/doc/module/__openerp__.py.2.diff | 11 ---- addons/web/doc/module/__openerp__.py.3.diff | 12 ---- addons/web/doc/module/__openerp__.py.4.diff | 13 ---- addons/web/doc/module/__openerp__.py.5.diff | 14 ----- addons/web/doc/module/series | 17 ++++++ .../doc/module/static/src/css/web_example.css | 6 -- .../doc/module/static/src/js/first_module.js | 2 - .../static/src/js/first_module.js.1.diff | 8 --- .../doc/module/static/src/xml/web_example.xml | 11 ---- addons/web/doc/module/web_example.xml | 11 ---- 32 files changed, 232 insertions(+), 204 deletions(-) create mode 100644 addons/web/doc/module/0 create mode 100644 addons/web/doc/module/10 create mode 100644 addons/web/doc/module/11 create mode 100644 addons/web/doc/module/12 rename addons/web/doc/module/{static/src/js/first_module.js.4.diff => 14} (63%) rename addons/web/doc/module/{static/src/css/web_example.css.1.diff => 15} (59%) rename addons/web/doc/module/{static/src/js/first_module.js.5.diff => 16} (76%) rename addons/web/doc/module/{static/src/js/first_module.js.6.diff => 17} (80%) rename addons/web/doc/module/{__init__.py.1.diff => 18} (72%) rename addons/web/doc/module/{static/src/js/first_module.js.7.diff => 19} (65%) create mode 100644 addons/web/doc/module/2 create mode 100644 addons/web/doc/module/3 create mode 100644 addons/web/doc/module/4 create mode 100644 addons/web/doc/module/5 create mode 100644 addons/web/doc/module/6 rename addons/web/doc/module/{static/src/js/first_module.js.2.diff => 8} (61%) rename addons/web/doc/module/{static/src/js/first_module.js.3.diff => 9} (74%) delete mode 100644 addons/web/doc/module/__init__.py delete mode 100644 addons/web/doc/module/__openerp__.py delete mode 100644 addons/web/doc/module/__openerp__.py.1.diff delete mode 100644 addons/web/doc/module/__openerp__.py.2.diff delete mode 100644 addons/web/doc/module/__openerp__.py.3.diff delete mode 100644 addons/web/doc/module/__openerp__.py.4.diff delete mode 100644 addons/web/doc/module/__openerp__.py.5.diff create mode 100644 addons/web/doc/module/series delete mode 100644 addons/web/doc/module/static/src/css/web_example.css delete mode 100644 addons/web/doc/module/static/src/js/first_module.js delete mode 100644 addons/web/doc/module/static/src/js/first_module.js.1.diff delete mode 100644 addons/web/doc/module/static/src/xml/web_example.xml delete mode 100644 addons/web/doc/module/web_example.xml diff --git a/addons/web/doc/conf.py b/addons/web/doc/conf.py index 7fe38022ab1..e8ffc50d495 100644 --- a/addons/web/doc/conf.py +++ b/addons/web/doc/conf.py @@ -27,7 +27,11 @@ sys.path.insert(0, os.path.abspath('..')) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.viewcode'] +extensions = [ + 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', 'sphinx.ext.viewcode', + 'patchqueue' +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/addons/web/doc/module.rst b/addons/web/doc/module.rst index df849afc54c..02e0f9c250a 100644 --- a/addons/web/doc/module.rst +++ b/addons/web/doc/module.rst @@ -1,5 +1,7 @@ .. _module: +.. queue:: module/series + Building an OpenERP Web module ============================== @@ -19,8 +21,7 @@ A very basic OpenERP module structure will be our starting point: ├── __init__.py └── __openerp__.py -.. literalinclude:: module/__openerp__.py - :language: python +.. patch:: This is a sufficient minimal declaration of a valid OpenERP module. @@ -41,8 +42,7 @@ module is automatically recognized as "web-enabled" if it contains a is the extent of it. You should also change the dependency to list ``web``: -.. literalinclude:: module/__openerp__.py.1.diff - :language: diff +.. patch:: .. note:: @@ -67,15 +67,13 @@ The first one is to add javascript code. It's customary to put it in ``static/src/js``, to have room for e.g. other file types, or third-party libraries. -.. literalinclude:: module/static/src/js/first_module.js - :language: javascript +.. patch:: The client won't load any file unless specified, thus the new file should be listed in the module's manifest file, under a new key ``js`` (a list of file names, or glob patterns): -.. literalinclude:: module/__openerp__.py.2.diff - :language: diff +.. patch:: At this point, if the module is installed and the client reloaded the message should appear in your browser's development console. @@ -100,8 +98,7 @@ initialized, and it can't get access to the various APIs of the web client (such as making RPC requests to the server). This is done by providing a `javascript module`_: -.. literalinclude:: module/static/src/js/first_module.js.1.diff - :language: diff +.. patch:: If you reload the client, you'll see a message in the console exactly as previously. The differences, though invisible at this point, are: @@ -122,17 +119,12 @@ To demonstrate, let's build a simple :doc:`client action First, the action declaration: -.. literalinclude:: module/__openerp__.py.3.diff - :language: diff - -.. literalinclude:: module/web_example.xml - :language: xml +.. patch:: then set up the :doc:`client action hook ` to register a function (for now): -.. literalinclude:: module/static/src/js/first_module.js.2.diff - :language: diff +.. patch:: Updating the module (in order to load the XML description) and re-starting the server should display a new menu *Example Client @@ -148,8 +140,7 @@ client action function by a :doc:`widget`. Our widget will simply use its :js:func:`~openerp.web.Widget.start` to add some content to its DOM: -.. literalinclude:: module/static/src/js/first_module.js.3.diff - :language: diff +.. patch:: after reloading the client (to update the javascript file), instead of printing to the console the menu item clears the whole screen and @@ -159,15 +150,13 @@ Since we've added a class on the widget's :ref:`DOM root ` we can now see how to add a stylesheet to a module: first create the stylesheet file: -.. literalinclude:: module/static/src/css/web_example.css - :language: css +.. patch:: then add a reference to the stylesheet in the module's manifest (which will require restarting the OpenERP Server to see the changes, as usual): -.. literalinclude:: module/__openerp__.py.4.diff - :language: diff +.. patch:: the text displayed by the menu item should now be huge, and white-on-black (instead of small and black-on-white). From there on, @@ -204,22 +193,16 @@ integration to OpenERP Web widgets. Adding a template file is similar to adding a style sheet: -.. literalinclude:: module/static/src/xml/web_example.xml - :language: xml - -.. literalinclude:: module/__openerp__.py.5.diff - :language: diff +.. patch:: The template can then easily be hooked in the widget: -.. literalinclude:: module/static/src/js/first_module.js.4.diff - :language: diff +.. patch:: And finally the CSS can be altered to style the new (and more complex) template-generated DOM, rather than the code-generated one: -.. literalinclude:: module/static/src/css/web_example.css.1.diff - :language: diff +.. patch:: .. note:: @@ -238,15 +221,13 @@ The last step (until the next one) is to add some behavior and make our stopwatch watch. First hook some events on the buttons to toggle the widget's state: -.. literalinclude:: module/static/src/js/first_module.js.5.diff - :language: diff +.. patch:: This demonstrates the use of the "events hash" and event delegation to declaratively handle events on the widget's DOM. And already changes the button displayed in the UI. Then comes some actual logic: -.. literalinclude:: module/static/src/js/first_module.js.6.diff - :language: diff +.. patch:: * An initializer (the ``init`` method) is introduced to set-up a few internal variables: ``_start`` will hold the start of the timer (as @@ -288,15 +269,13 @@ storing data as a model, allowing so that we don't lose our data and can later retrieve, query and manipulate it. First let's create a basic OpenERP model in which our data will be stored: -.. literalinclude:: module/__init__.py.1.diff - :language: diff +.. patch:: then let's add saving times to the database every time the stopwatch is stopped, using :js:class:`the "high-level" Model API `: -.. literalinclude:: module/static/src/js/first_module.js.7.diff - :language: diff +.. patch:: A look at the "Network" tab of your preferred browser's developer tools while playing with the stopwatch will show that the save diff --git a/addons/web/doc/module/0 b/addons/web/doc/module/0 new file mode 100644 index 00000000000..16762e1afd0 --- /dev/null +++ b/addons/web/doc/module/0 @@ -0,0 +1,17 @@ +# HG changeset patch +# Parent 0000000000000000000000000000000000000000 + +diff --git a/__init__.py b/__init__.py +new file mode 100644 +diff --git a/__openerp__.py b/__openerp__.py +new file mode 100644 +--- /dev/null ++++ b/__openerp__.py +@@ -0,0 +1,7 @@ ++# __openerp__.py ++{ ++ 'name': "Web Example", ++ 'description': "Basic example of a (future) web module", ++ 'category': 'Hidden', ++ 'depends': ['base'], ++} diff --git a/addons/web/doc/module/10 b/addons/web/doc/module/10 new file mode 100644 index 00000000000..dc3b670c5a0 --- /dev/null +++ b/addons/web/doc/module/10 @@ -0,0 +1,13 @@ +# HG changeset patch +# Parent 72d9d59a93fcee06ba28cf0b98a1075331dcc8f4 +diff --git a/static/src/css/web_example.css b/static/src/css/web_example.css +new file mode 100644 +--- /dev/null ++++ b/static/src/css/web_example.css +@@ -0,0 +1,6 @@ ++.openerp .oe_web_example { ++ color: white; ++ background-color: black; ++ height: 100%; ++ font-size: 400%; ++} diff --git a/addons/web/doc/module/11 b/addons/web/doc/module/11 new file mode 100644 index 00000000000..fc4225afaa6 --- /dev/null +++ b/addons/web/doc/module/11 @@ -0,0 +1,11 @@ +# HG changeset patch +# Parent 3ed382d9a8fe64fbb8e2bf4045e3fcd5c74c92bc +diff --git a/__openerp__.py b/__openerp__.py +--- a/__openerp__.py ++++ b/__openerp__.py +@@ -6,4 +6,5 @@ + 'depends': ['web'], + 'data': ['web_example.xml'], + 'js': ['static/src/js/first_module.js'], ++ 'css': ['static/src/css/web_example.css'], + } diff --git a/addons/web/doc/module/12 b/addons/web/doc/module/12 new file mode 100644 index 00000000000..9a2982e7d20 --- /dev/null +++ b/addons/web/doc/module/12 @@ -0,0 +1,28 @@ +# HG changeset patch +# Parent 43f21611dacb7c2b2f3810baeeef359ad7c329f0 + +diff --git a/__openerp__.py b/__openerp__.py +--- a/__openerp__.py ++++ b/__openerp__.py +@@ -7,4 +7,5 @@ + 'data': ['web_example.xml'], + 'js': ['static/src/js/first_module.js'], + 'css': ['static/src/css/web_example.css'], ++ 'qweb': ['static/src/xml/web_example.xml'], + } +diff --git a/static/src/xml/web_example.xml b/static/src/xml/web_example.xml +new file mode 100644 +--- /dev/null ++++ b/static/src/xml/web_example.xml +@@ -0,0 +1,11 @@ ++ ++
++

00:00:00

++

++ ++

++

++ ++

++
++
diff --git a/addons/web/doc/module/static/src/js/first_module.js.4.diff b/addons/web/doc/module/14 similarity index 63% rename from addons/web/doc/module/static/src/js/first_module.js.4.diff rename to addons/web/doc/module/14 index 78a192147e6..a908cbc61e2 100644 --- a/addons/web/doc/module/static/src/js/first_module.js.4.diff +++ b/addons/web/doc/module/14 @@ -1,15 +1,17 @@ ---- web_example/static/src/js/first_module.js -+++ web_example/static/src/js/first_module.js -@@ -1,11 +1,7 @@ - // static/src/js/first_module.js +# HG changeset patch +# Parent ae3b427c96b532794a65357b3f075129cc991276 +diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js +--- a/static/src/js/first_module.js ++++ b/static/src/js/first_module.js +@@ -2,10 +2,6 @@ openerp.web_example = function (instance) { instance.web.client_actions.add('example.action', 'instance.web_example.Action'); instance.web_example.Action = instance.web.Widget.extend({ -+ template: 'web_example.action' - className: 'oe_web_example', - start: function () { - this.$el.text("Hello, world!"); - return this._super(); - } ++ template: 'web_example.action' }); }; diff --git a/addons/web/doc/module/static/src/css/web_example.css.1.diff b/addons/web/doc/module/15 similarity index 59% rename from addons/web/doc/module/static/src/css/web_example.css.1.diff rename to addons/web/doc/module/15 index 442211a252b..d82abee3284 100644 --- a/addons/web/doc/module/static/src/css/web_example.css.1.diff +++ b/addons/web/doc/module/15 @@ -1,7 +1,9 @@ ---- web_example/static/src/css/web_example.css -+++ web_example/static/src/css/web_example.css -@@ -1,6 +1,13 @@ - .openerp .oe_web_example { +# HG changeset patch +# Parent e2d2e1a4cc2d2496aebeb05d94768384427c9e8b +diff --git a/static/src/css/web_example.css b/static/src/css/web_example.css +--- a/static/src/css/web_example.css ++++ b/static/src/css/web_example.css +@@ -2,5 +2,12 @@ color: white; background-color: black; height: 100%; diff --git a/addons/web/doc/module/static/src/js/first_module.js.5.diff b/addons/web/doc/module/16 similarity index 76% rename from addons/web/doc/module/static/src/js/first_module.js.5.diff rename to addons/web/doc/module/16 index f345ea319ef..816c23aa0b0 100644 --- a/addons/web/doc/module/static/src/js/first_module.js.5.diff +++ b/addons/web/doc/module/16 @@ -1,7 +1,9 @@ ---- web_example/static/src/js/first_module.js -+++ web_example/static/src/js/first_module.js -@@ -1,7 +1,19 @@ - // static/src/js/first_module.js +# HG changeset patch +# Parent 2645d7a09dcba7f6d6074a33252c16c03c56fdf3 +diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js +--- a/static/src/js/first_module.js ++++ b/static/src/js/first_module.js +@@ -2,6 +2,18 @@ openerp.web_example = function (instance) { instance.web.client_actions.add('example.action', 'instance.web_example.Action'); instance.web_example.Action = instance.web.Widget.extend({ diff --git a/addons/web/doc/module/static/src/js/first_module.js.6.diff b/addons/web/doc/module/17 similarity index 80% rename from addons/web/doc/module/static/src/js/first_module.js.6.diff rename to addons/web/doc/module/17 index 7dd17af0e18..d6d6ecc71e3 100644 --- a/addons/web/doc/module/static/src/js/first_module.js.6.diff +++ b/addons/web/doc/module/17 @@ -1,12 +1,9 @@ ---- web_example/static/src/js/first_module.js -+++ web_example/static/src/js/first_module.js -@@ -1,19 +1,52 @@ - // static/src/js/first_module.js - openerp.web_example = function (instance) { - instance.web.client_actions.add('example.action', 'instance.web_example.Action'); - instance.web_example.Action = instance.web.Widget.extend({ - template: 'web_example.action', - events: { +# HG changeset patch +# Parent 2921a545adc3406d3139be7951f3225e94493466 +diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js +--- a/static/src/js/first_module.js ++++ b/static/src/js/first_module.js +@@ -7,13 +7,46 @@ openerp.web_example = function (instance 'click .oe_web_example_start button': 'watch_start', 'click .oe_web_example_stop button': 'watch_stop' }, diff --git a/addons/web/doc/module/__init__.py.1.diff b/addons/web/doc/module/18 similarity index 72% rename from addons/web/doc/module/__init__.py.1.diff rename to addons/web/doc/module/18 index 6f16f0e9a08..6781c985239 100644 --- a/addons/web/doc/module/__init__.py.1.diff +++ b/addons/web/doc/module/18 @@ -1,6 +1,9 @@ ---- web_example/__init__.py -+++ web_example/__init__.py -@@ -1,1 +1,14 @@ +# HG changeset patch +# Parent e0cc13c2b2ec4d6f6bfdb033b189a32e44106f2e +diff --git a/__init__.py b/__init__.py +--- a/__init__.py ++++ b/__init__.py +@@ -0,0 +1,13 @@ +# __init__.py +from openerp.osv import orm, fields + diff --git a/addons/web/doc/module/static/src/js/first_module.js.7.diff b/addons/web/doc/module/19 similarity index 65% rename from addons/web/doc/module/static/src/js/first_module.js.7.diff rename to addons/web/doc/module/19 index 1d3430656fc..d95a89a3441 100644 --- a/addons/web/doc/module/static/src/js/first_module.js.7.diff +++ b/addons/web/doc/module/19 @@ -1,25 +1,17 @@ ---- web_example/static/src/js/first_module.js -+++ web_example/static/src/js/first_module.js -@@ -1,52 +1,60 @@ - // static/src/js/first_module.js - openerp.web_example = function (instance) { - instance.web.client_actions.add('example.action', 'instance.web_example.Action'); - instance.web_example.Action = instance.web.Widget.extend({ - template: 'web_example.action', - events: { - 'click .oe_web_example_start button': 'watch_start', - 'click .oe_web_example_stop button': 'watch_stop' - }, - init: function () { - this._super.apply(this, arguments); +# HG changeset patch +# Parent 05797cc75b49634e640f44b24347f2905b464022 +diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js +--- a/static/src/js/first_module.js ++++ b/static/src/js/first_module.js +@@ -12,11 +12,13 @@ openerp.web_example = function (instance this._start = null; this._watch = null; }, +- update_counter: function () { + current: function () { + // Subtracting javascript dates returns the difference in milliseconds + return new Date() - this._start; + }, -- update_counter: function () { + update_counter: function (time) { var h, m, s; - // Subtracting javascript dates returns the difference in milliseconds @@ -29,12 +21,7 @@ m = Math.floor(s / 60); s -= 60*m; h = Math.floor(m / 60); - m -= 60*h; - this.$('.oe_web_example_timer').text( - _.str.sprintf("%02d:%02d:%02d", h, m, s)); - }, - watch_start: function () { - this.$el.addClass('oe_web_example_started') +@@ -29,18 +31,24 @@ openerp.web_example = function (instance .removeClass('oe_web_example_stopped'); this._start = new Date(); // Update the UI to the current time @@ -50,8 +37,8 @@ }, watch_stop: function () { clearInterval(this._watch); -+ var time = this.current(); - this.update_counter(); ++ var time = this.current(); + this.update_counter(time); this._start = this._watch = null; this.$el.removeClass('oe_web_example_started') @@ -63,9 +50,3 @@ }, destroy: function () { if (this._watch) { - clearInterval(this._watch); - } - this._super(); - } - }); - }; diff --git a/addons/web/doc/module/2 b/addons/web/doc/module/2 new file mode 100644 index 00000000000..08becf41602 --- /dev/null +++ b/addons/web/doc/module/2 @@ -0,0 +1,12 @@ +# HG changeset patch +# Parent 8a986919a3e22cd7cca51210820c09d4545dc60d +diff --git a/__openerp__.py b/__openerp__.py +--- a/__openerp__.py ++++ b/__openerp__.py +@@ -3,5 +3,5 @@ + 'name': "Web Example", + 'description': "Basic example of a (future) web module", + 'category': 'Hidden', +- 'depends': ['base'], ++ 'depends': ['web'], + } diff --git a/addons/web/doc/module/3 b/addons/web/doc/module/3 new file mode 100644 index 00000000000..c09925f93d4 --- /dev/null +++ b/addons/web/doc/module/3 @@ -0,0 +1,9 @@ +# HG changeset patch +# Parent dcf661a5eef8f82503831bdb8e6c9d2f9beb285e +diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js +new file mode 100644 +--- /dev/null ++++ b/static/src/js/first_module.js +@@ -0,0 +1,2 @@ ++// static/src/js/first_module.js ++console.log("Debug statement: file loaded"); diff --git a/addons/web/doc/module/4 b/addons/web/doc/module/4 new file mode 100644 index 00000000000..55535fe57da --- /dev/null +++ b/addons/web/doc/module/4 @@ -0,0 +1,11 @@ +# HG changeset patch +# Parent 139dae60de67efa0017f5032f71ab774685c5507 +diff --git a/__openerp__.py b/__openerp__.py +--- a/__openerp__.py ++++ b/__openerp__.py +@@ -4,4 +4,5 @@ + 'description': "Basic example of a (future) web module", + 'category': 'Hidden', + 'depends': ['web'], ++ 'js': ['static/src/js/first_module.js'], + } diff --git a/addons/web/doc/module/5 b/addons/web/doc/module/5 new file mode 100644 index 00000000000..49acad9cd52 --- /dev/null +++ b/addons/web/doc/module/5 @@ -0,0 +1,11 @@ +# HG changeset patch +# Parent c8ae7646cce3f271698c844eb2d67f9a8719650d +diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js +--- a/static/src/js/first_module.js ++++ b/static/src/js/first_module.js +@@ -1,2 +1,4 @@ + // static/src/js/first_module.js +-console.log("Debug statement: file loaded"); ++openerp.web_example = function (instance) { ++ console.log("Module loaded"); ++}; diff --git a/addons/web/doc/module/6 b/addons/web/doc/module/6 new file mode 100644 index 00000000000..f2c6a8762e7 --- /dev/null +++ b/addons/web/doc/module/6 @@ -0,0 +1,29 @@ +# HG changeset patch +# Parent 0026cb80097a724db8d36371bc00da993a51a06f + +diff --git a/__openerp__.py b/__openerp__.py +--- a/__openerp__.py ++++ b/__openerp__.py +@@ -4,5 +4,6 @@ + 'description': "Basic example of a (future) web module", + 'category': 'Hidden', + 'depends': ['web'], ++ 'data': ['web_example.xml'], + 'js': ['static/src/js/first_module.js'], + } +diff --git a/web_example.xml b/web_example.xml +new file mode 100644 +--- /dev/null ++++ b/web_example.xml +@@ -0,0 +1,11 @@ ++ ++ ++ ++ ++ Example Client Action ++ example.action ++ ++ ++ ++ diff --git a/addons/web/doc/module/static/src/js/first_module.js.2.diff b/addons/web/doc/module/8 similarity index 61% rename from addons/web/doc/module/static/src/js/first_module.js.2.diff rename to addons/web/doc/module/8 index 88a80f50fff..83e6c371bb3 100644 --- a/addons/web/doc/module/static/src/js/first_module.js.2.diff +++ b/addons/web/doc/module/8 @@ -1,5 +1,8 @@ ---- web_example/static/src/js/first_module.js -+++ web_example/static/src/js/first_module.js +# HG changeset patch +# Parent d987c9edd884de1de30f2ceb70d2e554474b8dd1 +diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js +--- a/static/src/js/first_module.js ++++ b/static/src/js/first_module.js @@ -1,4 +1,7 @@ // static/src/js/first_module.js openerp.web_example = function (instance) { diff --git a/addons/web/doc/module/static/src/js/first_module.js.3.diff b/addons/web/doc/module/9 similarity index 74% rename from addons/web/doc/module/static/src/js/first_module.js.3.diff rename to addons/web/doc/module/9 index 295e90b6840..9113f914e7c 100644 --- a/addons/web/doc/module/static/src/js/first_module.js.3.diff +++ b/addons/web/doc/module/9 @@ -1,5 +1,8 @@ ---- web_example/static/src/js/first_module.js -+++ web_example/static/src/js/first_module.js +# HG changeset patch +# Parent 6a1a7240ea0e63182f60abb1eb5c631089d56dbe +diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js +--- a/static/src/js/first_module.js ++++ b/static/src/js/first_module.js @@ -1,7 +1,11 @@ // static/src/js/first_module.js openerp.web_example = function (instance) { diff --git a/addons/web/doc/module/__init__.py b/addons/web/doc/module/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/addons/web/doc/module/__openerp__.py b/addons/web/doc/module/__openerp__.py deleted file mode 100644 index d8c23c34f10..00000000000 --- a/addons/web/doc/module/__openerp__.py +++ /dev/null @@ -1,7 +0,0 @@ -# __openerp__.py -{ - 'name': "Web Example", - 'description': "Basic example of a (future) web module", - 'category': 'Hidden', - 'depends': ['base'], -} diff --git a/addons/web/doc/module/__openerp__.py.1.diff b/addons/web/doc/module/__openerp__.py.1.diff deleted file mode 100644 index dbab2e65437..00000000000 --- a/addons/web/doc/module/__openerp__.py.1.diff +++ /dev/null @@ -1,11 +0,0 @@ ---- web_example/__openerp__.py -+++ web_example/__openerp__.py -@@ -1,7 +1,7 @@ - # __openerp__.py - { - 'name': "Web Example", - 'description': "Basic example of a (future) web module", - 'category': 'Hidden', -- 'depends': ['base'], -+ 'depends': ['web'], - } diff --git a/addons/web/doc/module/__openerp__.py.2.diff b/addons/web/doc/module/__openerp__.py.2.diff deleted file mode 100644 index 77025f11512..00000000000 --- a/addons/web/doc/module/__openerp__.py.2.diff +++ /dev/null @@ -1,11 +0,0 @@ ---- web_example/__openerp__.py -+++ web_example/__openerp__.py -@@ -1,7 +1,8 @@ - # __openerp__.py - { - 'name': "Web Example", - 'description': "Basic example of a (future) web module", - 'category': 'Hidden', - 'depends': ['web'], -+ 'js': ['static/src/js/first_module.js'], - } diff --git a/addons/web/doc/module/__openerp__.py.3.diff b/addons/web/doc/module/__openerp__.py.3.diff deleted file mode 100644 index e100098bce9..00000000000 --- a/addons/web/doc/module/__openerp__.py.3.diff +++ /dev/null @@ -1,12 +0,0 @@ ---- web_example/__openerp__.py -+++ web_example/__openerp__.py -@@ -1,8 +1,9 @@ - # __openerp__.py - { - 'name': "Web Example", - 'description': "Basic example of a (future) web module", - 'category': 'Hidden', - 'depends': ['web'], -+ 'data': ['web_example.xml'], - 'js': ['static/src/js/first_module.js'], - } diff --git a/addons/web/doc/module/__openerp__.py.4.diff b/addons/web/doc/module/__openerp__.py.4.diff deleted file mode 100644 index 78fc90c4b1c..00000000000 --- a/addons/web/doc/module/__openerp__.py.4.diff +++ /dev/null @@ -1,13 +0,0 @@ ---- web_example/__openerp__.py -+++ web_example/__openerp__.py -@@ -1,9 +1,10 @@ - # __openerp__.py - { - 'name': "Web Example", - 'description': "Basic example of a (future) web module", - 'category': 'Hidden', - 'depends': ['web'], - 'data': ['web_example.xml'], - 'js': ['static/src/js/first_module.js'], -+ 'css': ['static/src/css/web_example.css'], - } diff --git a/addons/web/doc/module/__openerp__.py.5.diff b/addons/web/doc/module/__openerp__.py.5.diff deleted file mode 100644 index c9492420ee8..00000000000 --- a/addons/web/doc/module/__openerp__.py.5.diff +++ /dev/null @@ -1,14 +0,0 @@ ---- web_example/__openerp__.py -+++ web_example/__openerp__.py -@@ -1,10 +1,11 @@ - # __openerp__.py - { - 'name': "Web Example", - 'description': "Basic example of a (future) web module", - 'category': 'Hidden', - 'depends': ['web'], - 'data': ['web_example.xml'], - 'js': ['static/src/js/first_module.js'], - 'css': ['static/src/css/web_example.css'], -+ 'qweb': ['static/src/xml/web_example.xml'], - } diff --git a/addons/web/doc/module/series b/addons/web/doc/module/series new file mode 100644 index 00000000000..119e58801de --- /dev/null +++ b/addons/web/doc/module/series @@ -0,0 +1,17 @@ +0 +2 +3 +4 +5 +6 +8 +9 +10 +11 +12 +14 +15 +16 +17 +18 +19 diff --git a/addons/web/doc/module/static/src/css/web_example.css b/addons/web/doc/module/static/src/css/web_example.css deleted file mode 100644 index b861af8d517..00000000000 --- a/addons/web/doc/module/static/src/css/web_example.css +++ /dev/null @@ -1,6 +0,0 @@ -.openerp .oe_web_example { - color: white; - background-color: black; - height: 100%; - font-size: 400%; -} diff --git a/addons/web/doc/module/static/src/js/first_module.js b/addons/web/doc/module/static/src/js/first_module.js deleted file mode 100644 index 74580d1c0ca..00000000000 --- a/addons/web/doc/module/static/src/js/first_module.js +++ /dev/null @@ -1,2 +0,0 @@ -// static/src/js/first_module.js -console.log("Debug statement: file loaded"); diff --git a/addons/web/doc/module/static/src/js/first_module.js.1.diff b/addons/web/doc/module/static/src/js/first_module.js.1.diff deleted file mode 100644 index 53c153e1971..00000000000 --- a/addons/web/doc/module/static/src/js/first_module.js.1.diff +++ /dev/null @@ -1,8 +0,0 @@ ---- web_example/static/src/js/first_module.js -+++ web_example/static/src/js/first_module.js -@@ -1,2 +1,4 @@ - // static/src/js/first_module.js --console.log("Debug statement: file loaded"); -+openerp.web_example = function (instance) { -+ console.log("Module loaded"); -+}; diff --git a/addons/web/doc/module/static/src/xml/web_example.xml b/addons/web/doc/module/static/src/xml/web_example.xml deleted file mode 100644 index b5a9918f2f0..00000000000 --- a/addons/web/doc/module/static/src/xml/web_example.xml +++ /dev/null @@ -1,11 +0,0 @@ - -
-

00:00:00

-

- -

-

- -

-
-
diff --git a/addons/web/doc/module/web_example.xml b/addons/web/doc/module/web_example.xml deleted file mode 100644 index dfcc1614df4..00000000000 --- a/addons/web/doc/module/web_example.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Example Client Action - example.action - - - - From f25c4725b8fdf43febcc9ed9dadd954fb0ec2901 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 15 Feb 2013 14:19:50 +0100 Subject: [PATCH 3/5] [ADD] sync-on-start tutoring bzr revid: xmo@openerp.com-20130215131950-df68dpp0iz3dhdte --- addons/web/doc/module.rst | 21 ++++++++++++ addons/web/doc/module/20 | 64 ++++++++++++++++++++++++++++++++++++ addons/web/doc/module/21 | 27 +++++++++++++++ addons/web/doc/module/series | 2 ++ 4 files changed, 114 insertions(+) create mode 100644 addons/web/doc/module/20 create mode 100644 addons/web/doc/module/21 diff --git a/addons/web/doc/module.rst b/addons/web/doc/module.rst index 02e0f9c250a..2e38733a25d 100644 --- a/addons/web/doc/module.rst +++ b/addons/web/doc/module.rst @@ -282,6 +282,27 @@ tools while playing with the stopwatch will show that the save (creation) request is indeed sent (and replied to, even though we're ignoring the response at this point). +These saved data should now be loaded and displayed when first opening +the action, so the user can see his previously recorded times. This is +done by overloading the model's ``start`` method: the purpose of +:js:func:`~openerp.base.Widget.start()` is to perform *asynchronous* +initialization steps, so the rest of the web client knows to "wait" +and gets a readiness signal. In this case, it will fetch the data +recorded previously using the :js:class:`~openerp.web.Query` interface +and add this data to an ordered list added to the widget's template: + +.. patch:: + +And for consistency's sake (so that the display a user leaves is +pretty much the same as the one he comes back to), newly created +records should also automatically be added to the list: + +.. patch:: + +Note that we're only displaying the record once we know it's been +saved from the database (the ``create`` call has returned without +error). + .. [#DOM-building] they are not alternative solutions: they work very well together. Templates are used to build "just DOM", sub-widgets are used to build DOM subsections diff --git a/addons/web/doc/module/20 b/addons/web/doc/module/20 new file mode 100644 index 00000000000..042ff28067c --- /dev/null +++ b/addons/web/doc/module/20 @@ -0,0 +1,64 @@ +Index: web_example/static/src/js/first_module.js +=================================================================== +--- web_example.orig/static/src/js/first_module.js ++++ web_example/static/src/js/first_module.js +@@ -11,20 +11,36 @@ openerp.web_example = function (instance + this._super.apply(this, arguments); + this._start = null; + this._watch = null; ++ this.model = new instance.web.Model('web_example.stopwatch'); ++ }, ++ start: function () { ++ var display = this.display_record.bind(this); ++ return this.model.query() ++ .filter([['user_id', '=', instance.session.uid]]) ++ .all().done(function (records) { ++ _(records).each(display); ++ }); + }, + current: function () { + // Subtracting javascript dates returns the difference in milliseconds + return new Date() - this._start; + }, +- update_counter: function (time) { ++ display_record: function (record) { ++ $('
  • ') ++ .text(this.format_time(record.time)) ++ .appendTo(this.$('.oe_web_example_saved')); ++ }, ++ format_time: function (time) { + var h, m, s; + s = time / 1000; + m = Math.floor(s / 60); + s -= 60*m; + h = Math.floor(m / 60); + m -= 60*h; +- this.$('.oe_web_example_timer').text( +- _.str.sprintf("%02d:%02d:%02d", h, m, s)); ++ return _.str.sprintf("%02d:%02d:%02d", h, m, s); ++ }, ++ update_counter: function (time) { ++ this.$('.oe_web_example_timer').text(this.format_time(time)); + }, + watch_start: function () { + this.$el.addClass('oe_web_example_started') +@@ -45,7 +61,7 @@ openerp.web_example = function (instance + this._start = this._watch = null; + this.$el.removeClass('oe_web_example_started') + .addClass('oe_web_example_stopped'); +- new instance.web.Model('web_example.stopwatch').call('create', [{ ++ this.model.call('create', [{ + user_id: instance.session.uid, + time: time, + }]); +Index: web_example/static/src/xml/web_example.xml +=================================================================== +--- web_example.orig/static/src/xml/web_example.xml ++++ web_example/static/src/xml/web_example.xml +@@ -7,5 +7,6 @@ +

    + +

    ++
      + + diff --git a/addons/web/doc/module/21 b/addons/web/doc/module/21 new file mode 100644 index 00000000000..0acfac9001d --- /dev/null +++ b/addons/web/doc/module/21 @@ -0,0 +1,27 @@ +Index: web_example/static/src/js/first_module.js +=================================================================== +--- web_example.orig/static/src/js/first_module.js ++++ web_example/static/src/js/first_module.js +@@ -55,16 +55,20 @@ openerp.web_example = function (instance + 33); + }, + watch_stop: function () { ++ var self = this; + clearInterval(this._watch); + var time = this.current(); + this.update_counter(time); + this._start = this._watch = null; + this.$el.removeClass('oe_web_example_started') + .addClass('oe_web_example_stopped'); +- this.model.call('create', [{ ++ var record = { + user_id: instance.session.uid, + time: time, +- }]); ++ }; ++ this.model.call('create', [record]).done(function () { ++ self.display_record(record); ++ }); + }, + destroy: function () { + if (this._watch) { diff --git a/addons/web/doc/module/series b/addons/web/doc/module/series index 119e58801de..6271f474f0b 100644 --- a/addons/web/doc/module/series +++ b/addons/web/doc/module/series @@ -15,3 +15,5 @@ 17 18 19 +20 +21 From 5e19bfd47b0bb8adca75133541540a0ee64a18af Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 18 Feb 2013 09:47:28 +0100 Subject: [PATCH 4/5] [WIP] testing in module guide bzr revid: xmo@openerp.com-20130218084728-q92rcstsr2ca8pqa --- addons/web/doc/module.rst | 88 ++++++++++++++++++++++++++++ addons/web/doc/module/22 | 6 ++ addons/web/doc/module/23 | 14 +++++ addons/web/doc/module/24 | 10 ++++ addons/web/doc/module/25 | 55 +++++++++++++++++ addons/web/doc/module/26 | 30 ++++++++++ addons/web/doc/module/series | 5 ++ addons/web/doc/module/testing_0.png | Bin 0 -> 23285 bytes addons/web/doc/widget.rst | 2 +- 9 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 addons/web/doc/module/22 create mode 100644 addons/web/doc/module/23 create mode 100644 addons/web/doc/module/24 create mode 100644 addons/web/doc/module/25 create mode 100644 addons/web/doc/module/26 create mode 100644 addons/web/doc/module/testing_0.png diff --git a/addons/web/doc/module.rst b/addons/web/doc/module.rst index 2e38733a25d..138a8e91a4c 100644 --- a/addons/web/doc/module.rst +++ b/addons/web/doc/module.rst @@ -303,6 +303,94 @@ Note that we're only displaying the record once we know it's been saved from the database (the ``create`` call has returned without error). +Mic check, is this working? +--------------------------- + +So far, features have been implemented, code has been worked and +tentatively tried. However, there is no guarantee they will *keep +working* as new changes are performed, new features added, … + +The original author (you, dear reader) could keep a notebook with a +list of workflows to check, to ensure everything keeps working. And +follow the notebook day after day, every time something is changed in +the module. + +That gets repetitive after a while. And computers are good at doing +repetitive stuff, as long as you tell them how to do it. + +So let's add test to the module, so that in the future the computer +can take care of ensuring what works today keeps working tomorrow. + +.. note:: + + Here we're writing tests after having implemented the widget. This + may or may not work, we may need to alter bits and pieces of code + to get them in a testable state. An other testing methodology is + :abbr:`TDD (Test-Driven Development)` where the tests are written + first, and the code necessary to make these tests pass is written + afterwards. + + Both methods have their opponents and detractors, advantages and + inconvenients. Pick the one you prefer. + +The first step of :doc:`testing` is to set up the basic testing +structure: + +1. Creating a javascript file + + .. patch:: + +2. Containing a test section (and a few tests to make sure the tests + are correctly run) + + .. patch:: + +3. Then declaring the test file in the module's manifest + + .. patch:: + +4. And finally — after restarting OpenERP — navigating to the test + runner at ``/web/tests`` and selecting your soon-to-be-tested + module: + + .. image:: module/testing_0.png + :align: center + + the testing result do indeed match the test. + +The simplest tests to write are for synchronous pure +functions. Synchronous means no RPC call or any other such thing +(e.g. ``setTimeout``), only direct data processing, and pure means no +side-effect: the function takes some input, manipulates it and yields +an output. + +In our widget, only ``format_time`` fits the bill: it takes a duration +(in milliseconds) and returns an ``hours:minutes:second`` formatting +of it. Let's test it: + +.. patch:: + +This series of simple tests passes with no issue. The next easy-ish +test type is to test basic DOM alterations from provided input, such +as (for our widget) updating the counter or displaying a record to the +records list: while it's not pure (it alters the DOM "in-place") it +has well-delimited side-effects and these side-effects come solely +from the provided input. + +Because these methods alter the widget's DOM, the widget needs a +DOM. Looking up :doc:`a widget's lifecycle `, the widget +really only gets its DOM when adding it to the document. However a +side-effect of this is to :js:func:`~openerp.web.Widget.start` it, +which for us means going to query the user's times. + +We don't have any records to get in our test, and we don't want to +test the initialization yet! So let's cheat a bit: we can manually +:js:func:`set a widget's DOM '); ++ // Then set it on the widget ++ w.setElement($fixture); ++ ++ // Update the counter with a known value ++ w.update_counter(22733958); ++ // And check the DOM matches ++ strictEqual($fixture.text(), '06:18:53'); ++ ++ w.update_counter(73451828) ++ strictEqual($fixture.text(), '20:24:11'); ++ }); ++ test('display_record', function (instance, $fixture) { ++ var w = new instance.web_example.Action(); ++ $fixture.append('
        ') ++ ok(false, "test display records"); ++ }); + }); diff --git a/addons/web/doc/module/series b/addons/web/doc/module/series index 6271f474f0b..491e7dc3c59 100644 --- a/addons/web/doc/module/series +++ b/addons/web/doc/module/series @@ -17,3 +17,8 @@ 19 20 21 +22 +23 +24 +25 +26 diff --git a/addons/web/doc/module/testing_0.png b/addons/web/doc/module/testing_0.png new file mode 100644 index 0000000000000000000000000000000000000000..62711799ff7320f162a8107fefeed3ad7e1219a4 GIT binary patch literal 23285 zcmZ^}bzEFQw=GC;cM0z91Pkso?ry=|-2(&&u8m88#@*fB3GVLhE#DrBn zmQOR4tgmIU2Okh_{W^bUcdbx1?yuqBNwh05Q?xZT$Np@>%z>1q0mMyXGl(f#DgFM+ zGD~U1@}ruj1NKd7euDJ!V(y76nombr)kXo^HKyQse>wQaDzH64+O0p5qP~NHVklINtaCV7QGI zLWO+7lj?t^{WK;^9cvx7|0ID8j1T`m;17rlHt_!iBs9gafh45ys#Lh9-|)$D9ykI& zP1(U)lY0U8HEC~m4W{Mk2`?>Qp=vKc4qU2(3GYLp#U&$=o%#2xHp7%mAKh8r%a{8V z*yhnF45a)~g~uM6Cj1ox`4!K8YMq<$p&I16dG=#NDU*jL6^UV-M3{?R6%o6~?N_%c z8$Yk=)=(!!1P4(QXH^X8Vt8nd(w|z`lB}oZ_v$iU-g%ktuub^<+GxfG86>U9@%|k2 z*T)Ky6?1I26nI#~wh^uHTR6?_$cSWpHx|fCujN4cDQF7J&~*3ZLs^F<@$2 z3-7$jkcJVSq?NQomL*cGNC~vV-O{02D?N$3+Ollb>?WP= zp*|kI`ulH^wf7MZa_DzE8=2LBKW+T5sbyFLcvfp~ruXj!->s$y_u^+g!^RD0RKiyj zVpW#`u&EIfeJ%%v<2daZRA`Ntx7#gZZ3io;F<=sYEg+Gqh9-bN#fJ2L?YG881{{9z z<~U$Sa|3mZSL=+rhgUtLWZqVL*=<9MCF(bK`~A_nB`+UEmtH^fy+|aYNrj|Y54}c- zcY>HK^nCE-ytxhws^gY~0IZfv#|q~7M*S$1W11OGojd;3&p(@%tl84lS|qkQ9FLx! zFVF#IU~)c;&e!cNd+4RESQR$w)?0&@8!jGjUhhx>(H=0YW~(YP9K!xo47b`MAZ50F zD2}ffnc&ReXow83%FKoIl|9)(x8IpsHpW-PX{|Y8#hDqW8nVff`GXBsNi{x(ue&qW zaT>uKBi_fmGl^KB`D(;0#BFnAQkWC|jhricj=8cBcLT}ScQ_>}SJ$KYq(ZqRTGG)D ztejEH(D_oxV>dBgO|jU1v^~3HQO8rJ!UM=u@I(B($OUzkOCgG;AA946t6Ih=4ef#g z!IGE5VGa7yL_oW56pmQ|pzlefk2P~JG($7CHn4I&B*N^POy(%%=c1BJhEwiN2>T>^ zL}O!$;XU4N=tc7vTWI+4SwtXvq~FEeC{}q!=JFEO*9Mn-s&(Mst$ORa9-H23=o9JJbPt({zE;jCgEPDZa2OKTWa~NL8hkj6FRZ~lN&CYY z(t_|B4Dd}gbP2|k@}hT7?iHl1e#Z5ksgZ@S(iFI__Ux*(q23ig*8XF5W1Inc{&aF$ zuS;4oX|i;d#2cL;=mV*J{ZBcfo<(h{t%R43!OgJojDUnMA57QuSNc7xGhOfzqcdfq z;|U%W>{BVc2Ps^E;>p%J?-3w{@*6~^MI(jO8HECAWH8F!>4;h|H_Pw5Lpwn&5bY#{Rd_A9h>-u-^ZGwWpbpQbkg~uoM`f#XGf6n%au3!18veZ6(q$1WxthC zy|XQe|76*~(2z}M^Y%wSY$Cb;fR%|BI2;x4L%Wrx!k;;$h3vZ>5 z%Bo&$<(bxI6_mQO{rcP-_39uMJOzC`AwX$QW1^I+s^lzLykCT+a6=m%bI{-*LN_>w z{-nu69Ol;ynr57w!}>aPdL(of3{tsSfM(cP(<_iJvAy>dKIi`VzsKU}YbQVn(6|<= zy(XfNe~43+{!c;&neRHM)DFawYBO|84prVu_=>y~@G%$dV`n|i za5=+T&J;*IZOhlshGXbvN08_5(|hqvktgb4H)&c{1j&m7B(BYcTQowr;;YOR6R9ck zl#SXcIpPaA5npa4{N6)UKLbV?8uhyoX=9b1UG&IPnS0g(n3$!5rb)M3-9)YLtRCVQ z1QJO~G9r$&F=o42nnlrhUqdJX{~msRKMAn<6ZcvwK>8wp+=4}#eL`96s4dEe1onh9 zSb$mH&JW5`aj-WsY-({H0$>msyJG|J)*dSgR{496``2h5E1O+g2s7~i2y@;f1L%AN zyUElqOd=&K>-}^Sg zSk#NRe=CJsho$EoaPyWH@1QvBs(I_Rb|Qguzi7exg_-_)PHF*i#f7$Bv+lPA3K5T$FRQWmfsa!cQwz25tJq`xJG-L6-kO;o`e;{PuF%u5AwB>63a_vvE24lP9ebX znMh$>6FTPf3co-)Cjv_|F=v3GSAqL(pA&XF>mmTwbFl=aY}uVjD>6(kNS7=y;pIRJJvAF^5%D6LlAg@8EA;}b2mt?c7E1ETr)Rt-@Kn7o3l5asJu`au=2(rHQ| zoAug&87$2=Nci`bVu_W&G@7$da5(4{QmWsX1r1VIQTY@6euB1%yZ(tM!NghF<{Wn3 zat5X`2~;^6~HU`3Qa9U?9M)N@>_%B_NLpXv|x-S5Ax zv8tc00_Z-q++$|^KP}7s1wi+Cp}_it{}`m8KJQ41KOt826aM?#rr~%mN5|^AI(A;( z7jyigSRy4TtyW+3=z(JhXr86={><7m$E?LgX(XM>e33ZKQtz2AUbDJ%33t9nstFj~ zT)!klMg>o-Mti?wLzmiFP6QqE>dSMfhsIpjEZE_vZ9`p@h|Dd0o8fT1wQ)QI3pD|M4r4R720o zgWTOMo;RHFNsq{sYU6x9X1}e}dqV7|B;&TbXEMan&4`C{TW{f$&-NDIcRpZ6n7n^S z-s|gi$vc2WB4gq*QNhu3AEJe^mv90Gj#{N68LKNMepbFPuY~SxXEd4lgY;D+(C_8- z=_Z}az6f0HCCR}%yoH;h*l?~szBHD#_i)I)*zk>k!7H)f>jC;loAG$8KigVIAZ*m#m1O4<4^ye11>RTuh(yJkCBobC&|v%>ZFcVtt=Ut`huwk~`I2lg`>9 zmGXWzRbGto*C*c^;rAfkn$S(q&U&KlnW;Mm9imjTAz?uW`1;eHS2o@e23JnV;_=aEsEL|9702K3JRcOdRL*Mzt8#8M@q%8mCVBdyzlxoMqiX~5rUs~ zY8hCClCnXEPjgk(lwIRSyZ6J^PH2qpChU+Uf#0Eu;hiGk7-sf z4Fm7!EzjSPxA>9fHNrR%->WC@TV^H$c5*u4ukpl3t-nweD$2xs z9*X7aI_22;Yo!8HmE&d!pL0XWyg$RVx_L5%d0P@FTx@cHnt!xf=B0nla^%IOj}g8+ zJz&CZs@_47K-&^R2VVuJ;AU>;O;>4kF!=^QQU6Gy(T7_mM%kRQcex@zCOUgX+HbFA z)H!-ClqvWvVHS#X@G~lF$JRZ3u4J1_L%+B(@O+&XTs+lTE?zVzo#WsX6r9Gj0*ej;+l~+h+4s&{0r4U&<6mSc`czwR7 zd35E2w3TerxxPxXJ9{DR@#6uwKem6IyOsS)h_~v9-3t@g%%Q}@ z8bp=iNW*Ib%iSY96byO6d=P%3sNpeX9D?h&&+@H7s%^cU&)`P#JTRZZk@J>{K4F^V z=By*4ODx;o&aUrrWlU#L$b{vl#k37Qd!Sf8HVd{J_tN>+oM6O18A6?mEOSbgj7|53 zII1=XX;Sn(EK@`8)!fe!pvWD{duLUwTYiWGXP=E%pc%B03DE;sJNmiOiTxYZHXA3; z?^}Zn!7UoCD)DaxQ*ly+aXUr{9vv2QhqDz(xxc>`;n?tO-ba}pElq2{p)#`D@}ENi*_Z(%;76uPwS9CGEzwv^tw?%xMkC6s5#w7w(USB(0~r4-6T zwz)Q#*(p5dQBHtHSx-d!)~6V%vdk?{=&aokTD1>1Rr}gZLPYfYtKxZPbp>;uDT-GY z7}pK0ay_3ZfgF!|pL2srCNlTS7f!+~62=>?0~StXo@|lplkC-L=mq7!Qukxs@`{{e zk$P^dxxgWKz;R6CY@tH-G=93>$r1kHLZ!indBD4Hr!wiB6b1SQbMf<2h-WOrY#BA} zcuS3sVaM4T?|~fMw-Rr&z4N0%bU_+F6RFi@%&nN!GVY zgsdfL`Q~%PiRu8d_ z5_m^&Mz+gvHAgKQ;5>x1fkyU!4O|7@33pBa=uR98J;cUgI8(6Z|1?3qMia!gj6tqm zAu?BJt_(YL3=HJ((gmCN>=KKls5p1~{5Gei8IAfkSS*UWL7C&7!5CLPu8+Px=zmEx zJlqtdD8AmbXZ+jd%5VOcP#Svn1*Ds49uVz0LUT@U&7P&A0zs8B_)N(tpMWmG+`Eno2~y^Az61 zsMO>AR*O0^ClS?N#cN&<3|3YSb|_+^%-W)GZECI|DjnVDWIk)4+a#mkz=F3r%2i!o10m=!TB0A#w$hGx+pMsc|JnQDoZs_$_vz+<+2)Ukml)O|(?__QwM)=E+i5VR9)StPuAWuI^iW$>GA@bT z)ky=@opx9Wt_|D<;lS^q9&Gi|FDm z`SV8t2Z2rbKH>c!q|C!M>#Zsm@lmH!ADkMjclbLq0NvVDp>lDn`^CE@UcK8cfOGQpcD%6-oJ9LiTJ`zww6R=6NG*3U%ca zHZp7`uCKI)i6G$*uP_&*+e`dGAlZcF2K)b&b3Z|!8<#@LU%WC|DhN#gw?RAH82iLpUcUTWjt z!`{#sA|9PdI>0$!KG46DHFE95ywYC;p#~KbsvmhPCQg@U$BAiy?-VwYzKiQQk=Yxr zT&hi~Z<|R=r%yW|r^owv645OgI;g@c%u$*AkV=@^$e_*PeHu+df5h%SNFjRz;jH+z zJ=Obw3yzS3357+Bgo_5$r&8}`uz1j!Rztwe3Rf~>1y z&u?U-yb}7Ft>dG8F9_PRcvh>wM!pi7&nW5H1g5trC#cAcCGrxM~pe) zhi+d<(D7MGu<)HLW6nBzb`P$kDDx_3>dk$LQN>H^nV`#L<8NI}^uDQx0E%haj&NWm zsvsz-1Qht(_lX|Yv`I4C5bMnY6*?3?ZiU=p8y4p8Blzf|@C;H7ad@koFXlqWI>}=m zBjz@1nN>38w1TjFThiarGgNr5d$wlu`y-dLO>1ikmU4QTn{jG0JIHzl%*or;gQ z9xbVhpvCps=qj(j^B|7q`R9?B6-YZ!(KlZx&=KJL!Y$?N8yx1{!NJ$Jk^ z5kl)Bro{YkRe7&xjQ~%Qqj@;bJzO^2qCPdA#&k16-Ca!fnrB_6yOGnhb2MdinuO_| z5fy%{#lbsb*F#l{foEAWfrn(A`)$B*f2DHiAY~{(*=}2rVSO7P=6lw_biM|!t!LZ` zrv16fi$mB_wOpaY+rY+XGPieIPyC>eo+=GU>s|EW!dMAzcViz@&k0V zw*ozR2A|q;%|f=L<|%=FuF%rXEMTsLelipQ6VMPLPLJ%@M2h!imxr#bBs*-mRAXbU ztgfov)g9@{-70(p&k1vh*p83icY*TyjcObty#ICe`k9oIktorJAQ86rYJa}^+M2Bv zn`%BL+bz)Q>-GCGF{);TXA!=MNyPV9K3{iqNTO2mXS{P+JOVX8zE1 z9p7L7QUIPEpuWz#gN?QA(%3ry;y&=Y6i2EFdE-++j4;rfl4`8MsM!xSnRBpMYNKPB zNA6ZjBT{Z>CApO#?zXf1JFAwXqCt?+kSQyfuT=?z=_8;D=UhK_!{HNuXKp%(-{Ky_?vV$kZVeu;Uc^Z2A){ ze?MpJ8#S+sIp@yWmJ^M1J2QnX^v&lS^H+Ww1cs_(a0~c<-E_xvNBcT8q(=aMcHBxSeq&bY8x&b+mPaM!a=oNnV{s)0UJB@^D)m5!T(_?$~eK2uG1k z(&r7canC9loO^c8WwyJ4zdg( zo_{VwUcqxRnZWT&zZ1%xHSgt+#%=3 zxp~e?p09HeE>aX-)0(){05(Yz3sg}HBJuKV4F zcs~g7tSSMbssUl8YgIQR9Cr>&0&J_g`PsJ4zwUCH_qR|d6F7Cp*V@MmpqU*9wT$01 z#=n==|A1M0A1db?jYrzow|c4Xo!uO~!DZ{^wSVRR@tkF1BoOW9yclBOFII~~*(ax8 zWfp(XNEATFCSJ&S#V$Mz(D3MLk;>{IB8A$opgwzuK!v`jjM!G%nMu*s|EgV%Q;GDD z(&!YPESRJZ2B{X<>!xSLM4a08!&v@c=&-w21yLv16|erIdB5qSI+CnhJr(a^DcM?Z z)dl+L2)=lHp^`+JAN(d&s{A1d#=snS%v0wvUT1qin2JNT9ITMtZQhOd`wiFwNxfZ` zXB^9MH1xw!AI(_dubbrH)31Y6Z+}MzYkipVukSY+)!H)$SL>ei6KljK1-49*W>2pU zbDkvqIeUhB52$j5)pC*zOmr=SaCQ~%JI7VSH=hB@slG*VW9Jk&Bz6k_$3ANlv*OOI zaZToeV<@eQ(fS#(y167{VWca}S$?ihDvt1DtUxdc~&XSudgh+i1!QR0+Z10s2cONixY!?`x?r>_R0=H*F>!Fuou}Q( z5JP5|1PLT84cBl8n|hH&a(Iffm3zZk`)&ZwJ}9`Okc7XL+aTcB-F45N*WF+P>1rZ1 zniEu>j#71D<`z%BAS4s{#-XJxp9|9m&(2UU&lXuK@hG*5&nfH8(j>cN zM~{%L6=K7OLc!cB@%7fim8EgPEo9bpv|UQh{rZ)*A?ftT(fJW1CY!KUtMrnD-E~%D z50UcVUpq9sTp zCZ7-nT52*X^6HP~tAuu1WT3HAFU!e9i8(1cS!~6H36&uXkLk zlD^uSlCrW~PneDu5)y6Lp9j8#oGGJ%*&Ph#b#B}fT%e)3a{uoGbB=JN)jx^Xi$=%= zEnb#t-lU&p5iw(6j$g*WT^Em>{6R;jf(mYrlHDXk&KeBTc1HcR>@N4beING-{!+F? z{PpIxyp0qLR}lW5W}dadzyY7PZHh@>U|B5C9iKP#>cJ$Nx!-FT=4bX=H>MPTCV(G) zzOAeQEom|);r;#&)XC^=edV@H1G&a7M*`9w^BgD~>r`7v6#T}zsO#7{lf?ITZmfJO zYaeQbxvl*chG+D+Edeq`wCvTLfDo~pcu!zm#|Qmb^F;s_B=?|*fKqHN8lTvo|HNg- z2rC~s=Q|piKMXgw$#6F-D6wjCz21^YBwF=U$%+on%dIuM-1FwG z(Ban9V%;09cXI$UJon}F$Eq4AcgrG`YR->>>UTfO51*-}otvrorSv2#uF+XPNs#QA zt@cX=DUk62&c|Fb0B8zC{fSN`&(#GWew(!aiSw6ZQtS+SK@4nG14IWHTG-pukBSzk z;hq!YGriGh$MOIkJZ-R2w!ECRj9hzmysY2kDeDm^J38&k6YWRbDcy9aXm!&D&s%0^{(w%o8$G892*)DQQ0#QZG1!OL>gB+iDLs zxajU!CrUYGdsS*`L>#^;c)%{?XRvtAVz3nywqhYssh!u@is+5ijyuHpS?VZp)mm%hCxh{W#q3>=(?42MOT|NoY;WLv ztgpIr?aZb-(oQK;8S@&_F-l&v8abC+?^d(E^1NG>*j2GHy?&RmH;f^;%d`p1Tyf@3 z!(XI8MzCuO99StiCN&mwpP42y%TBX)}D1V&`{HU+!vs z=wNQRo~Lg|(|b?ke^T>r+bIY}`@8wpXu8?OY{rVoVnoCG;$K+SRH}44Q2ip*3I)cJbKz#_2nKbvFqIJ%O!YZJM=VBqwAD z$Xf5gDQrO4=mNFarL6j8dD&kqx;5Bn)t~X*X1832Rz}k~*ae1B`KHg0=JdLAqZ*dZ z_KY0ZgisLnN!TxcsSh^Itg;IpY{QyyH!$NcB3RY%$0zh*`r2Q2Qomql_CvYdbV#ZM zQn|T4QdPp?PvA`htw@QMKT|g-flu_Ltj}sGk-3u^wzuPJ0k_)9=yG|_l3nS;XwDnIx;K-ZDlJ{lEqu(qjD&|~ zj;&1lh3EBP$R=vU=)SE-+o0-BcDgv?NTy-H7aNr*%PFrS;xT47gI~~h!L(H}@uvgt z3xAV<^SRT(8^>JjOC@^bye`iv>x0l623ZaFP=dGUC^`C&f{B5HrqH)YZhGraycke)wKzr+c#;9{lbpQ~uji(YTbJ;}vbsOh3_z=Sn zU99h@OG9!`6b@*_K4&^Nx~)?+n^-O1cJoY1Qo~p99*gVVLty6MK6`kIFu7j^RpxV& zuvnQkq-L=|=FfhUNvHrDUE77~uB)3r$On^t-*LfBjiLm|ug)9ASm>Lle+cJ4s7P-;S3TCtE2ts6t_etV3Y>MU(^(Hwbh`nP*?0$# zH72a<*I;0cyr(brq4}ArPl?Q^b^efHD47`cwp&qlU*X;Gv-id;*v;yXx!p~6G(M^6 zRfLV`2+F~y_{tBz18R6@lfG;cSg)R?0YgfN*+Pwt-Cp7PaL2%OgxVMxqbNAPmphn# zX(iDU^fn=A4-L3DBS3w*iqRbc^%9t<4h1!x-GF6wl_}U7qlg|Su3tg-;9lM5B$?cU zEgR9)xu1N#0cX^Lhp6lG9B(D`Wdk)!YGTW)*%jG|p#74@#TjfzB&XQ0TLX`pJQ5f` zdJSS$kTHlPPq`aZxp|k%&oH)aIjpy$r_-4g&V;?l3!er!FlA@*O3TP<&WeYEw3+x9zAXEdkaS*^fWM9IO0p{X|q5ILXh+*h23TjM0rX&WFH zpGxG-WaQ4K(t18e+C(C4S~5q^DXxr;uUaaZYcR`&-`E& zHaQ%eKQaf~P)oEz${}1EG2RLM6~^px;2vn9d7PbQIc!MHILs7*%L^dK?J#ngR(Dgj zQ++G6fZ4x$TF)w@2+EsT9R>B-?T-HbZLpxgC+=lib44;M+!a5q)HQjgfm2am?w&k_ zN;pZO_wQypsT@;(`voign;z^9^0ptj0myexFx~avQ-_}LR~rlAc!R#U9kRfKn3`s} zr7yX&V$-;rTe392(^{ENxSoX_ag3Jr_Vc>WUUyIc>Gp~)Z3 z98jDM5Rp*t_l*_+1O;E_|~@;GMPu~bv#m|(ex z;?vX2DTNoM%v4b&a%}k7n5CU_yCcN3KtY-@bfTDTjnZ9h#9APqNg+M0X7NGo{AHKz zRNS8DXjq|w4+p&v?@{zvojbuZY^=7ywt5!1JI^nm{~z8f)$Q#>vwQ8uM~oZCI(%u> zM2%a9thLAk3cM^>Bd`VSV+~vGU>1nXjv2f9${pwybmQ&V9}cWRqdCj+Y1QS}yu7F3 z1vV<$HqB97W?-vEvEf#Z%n-IZJD(d>gl7zPJHd6@+HR*jp;?-B1SG)qc`tSf*wqz6 zcRI)YzpaT-0{$pq@S;u<4*>qK5~&W@C@@*nVZqi^9rUI%5&ueCb?16yo}T8sx`Z9% zJudEMEas>7#(h=4g-RQ2HO~)MxbQ>3`Cj?GsJ{XDOwOdtj^Qp&M|<-&_C3b880)1Z zzdroapZwiN-tDtFwgQiL(P=8m~Jx(X15)>uQ1ezDO`))TL@g>9(&n*2Dv%ab2%Zyqbm{DW z%=eUcL5&4JymVnxyI{V}+*1uV5P}oC2o2@Nyv^uqqomjL{y`(sYMMhXVdTNIr=El6 zI|qaJsiVx&cq;Gb1(+i3O?Xr`W)0KxB4p#<_ zAuMr|fEagBmt>8r&JVH%b0zil@>3n?!YmJ+%}fr-_Qul(Eux6&S^uSdUxbAVNIP`dDXkI}UI+N&*q9#~C|p_Y zPV|BB;X#6sLgKQLEf&H(WIEkngoiYZS4^)wE|f(Sxb*lG18oEL8?Ijdy!gKXBNQ`XDfFz4l!g@t9qg|^h7&v(QK z&bK%M6V?szkqt?{KN`boZk@brh?7LJpced$(K~}SCZh@9RwQV=#E}YC{249J=Q}M& zQxiTjO^R+3G!`AbGuIrce5ke?fCg5^PKy`!V+z*fn-blm55ZZ8$EE?^fgWN?2qNZN z83syCSXw`CvJi%t$Tl`g+-6ucEP4vg*;HnqgBM}=@3JoeIcNoLuvu7FJkFBsPmb-) z33>PDBP*1#*f!Mwh}Pz;(VaR*+dIY>UGKm@Y`K`)RhW|be~*F=Sf`F2vBG!a2tj+< zE{5FL(AhE%sbml4NQmI#9x#j(R))(uF7juWwCus#yX3H&yXsy`*!P|X>Fu%M=rDL& zzEm!MKvdSh2Ouc=XvP2uMwnjLR}vP%*xYD&Q@^`J&qdL><6^iTF*DZ+IPv0oW?_5e zk*`7~+^}^}CYnfmrEoOVF$M2^Synqt14)1x@J}RP3Sjy!-sJ!n|C=!gy9(AAk6F9%OS|?3PH;4Ay6S0y_%}#$3$y9@u~)ZTXiiZ z9MYn*+Bhhw36fyvfMsmUv5;t?x8lMJbU1MRg{#G?>p?X1Y~_#5Lo#3exKmNP1Bs=t zMx;;sizKywz2SaraMX@$a$Ab7Vv^FE8h&C;t`PtTfM5oL3h|9NL=p0|MlEZFM$swj zhj=&}mO}geW&i7Ad{(MRAJI^llG=c7OSnNM=}G|e*F8GE&Vg9TcLJra$p&7#v!--t zKWQn}4Wc)GGna8Y(G3@8j+tJNYc!%e{~qUE5cpcuOLHZJOo<`_l{Vp6)-^6L`Y6HK zv-Y{Ww}n3(CXelmq^hKTc!549GWs#|5f+~Kw2&HkJtx=tg4{DL#{B$FRXku|I;H8l z>&~r4Ba~9phhNw+EZwDf+r1s3J-W0Rb?$hd z{dt%K!OoOF2@4hL&nzEYn)UbP7n$b|<&Vr_vfw{r9|McM8;-b?b)EMiKoYgj!c#mT zQ1u^@!6{RIVi<)NEoi>Z1@NyrC3Rbw@PAjTr2k`yrTb^6_^&e7X94SfGD8+uwqyP4 zGt7@Vc6kw%z~C#YKRHPUH=qK!%_FG!{YpZ%`3dX$A->Mt2MU8)@d6Az;to0JFKULa z(*6*i*pKt9Tj(7qISlu;sgxW}6ANzTRaP4M;fC_?+?pUuclrIfBK*NJ<}fks83W;= zFzT8`>+A}2sn}&DV>UfSV1E(a#GWNiFbz0XQ_*jwBwEDs%7Gk88g|L`%ZtWDb!TUZ zA-QpGSUB}#T+(Co(4=i#(tbYYMvBg&6vyfi{L&e(kbMvF626$?Hu-&)&hUhtr0?4l zenjqPf3~AHSog_{BRMJJ;I+z|JKJJ0WCHv3UtEBnk@U7g5-5|_-+k3fkfx?tqOXp{FO;v}Fgl*O7B2%Wc4 zP2ZG-OqG%O@UIVWdRE+J+8dJJ!R$Ii6B3php5QA-%AM+Nz2dMX}GRr zPSp~E&y2{AR4VP{Mwn9afm*`X4rxHvFz7q-v`a=Yay2 zS);3GbWyz@786o>Dq+=gdv^6Q0R=)y$Ej_M{jQpx^c$L=O=v4GP0%F0C4(dc^U+l3 zfm|uCi6^5e1ZH%+HnGJ)8Z{k#zY?Tj!T3t30E}p8t#kSS)^?I~qNo5*q*ytmrLQSd z7JBkn@JpV)->mDX5#_)nEY>a+hVB-uD`C57SyNoixnghY5seXk-p4Wq|;92 zvO}P=i7W9%yz$UqOGvwxzR^^-f`1@3)BTweTgZ)qjII^2*DGXfLkepXnX{@0O5LBf zvKxo3FD1i|$|BX+o@D@qhcm*`R^Wj3d>glDJd)6(f-VM=E@=a#D2m4p@Pptftz=Wz zuUJ&Old?N3=S(XIB);`n#Fn^T1s07MJB0p+97sPG1Oh1INr00T^jX6atv$}k5UJRC_y)r_vevpST~=-_e$DfoBX zWU|fuku4Pe6|5+d%DY>IY6}8vmGQW1# z@t5jybAs&?zbl|4_sd{(`7-o3z(s|$uQ;H6cN+?r^1p(Ui|w4*0!u&G-@_7Byk9?Q z5~lAC=x<;rPw@4^FZsb$$l*mb9{FYP5_kxoPG^g=Nc}g0EsD%N45$c?HKP}uBa^q# z56_q*RYaIZ;LJ1yi|tEZ=>QRfMUVfaL}tVbOS_&EMD!(Yw_AEVH-RY@4<69IM~Tz5 zM*=akpDax0#UoM@?ti0t``R6G|5xlh>IcT~Ugdm00vTP*Frkrlgtn}WmGcm5KQZg; zKkq=qAav}MIcOCr<8;vmd^xK;t&7_giiA-;CARvcOT>QsHH^&sq?|Ukh9681MhGI& z(*bxoBSn)J$$jl4MSOPWs0>t`ch=c0pELV&I^elH`af&W@kQF_>d-8brwa4G4pIDP z4SFN?Hh-BJh#PcVSAXzV9lvv>tM~0sFG4R-rF__KQm0O=qi}~6XGft#Nfx67IWXn~ z4CwyZ&xmbG;o*bCVCf@;u6=~7Y+q!sPB$ff;H zmy)XI{{<8%M1o3!1qmJ?^b(^wx;Ha#@8a*Hx+IH@+pM3BHugJe@bJq$93{rdsqr+S zQgoO3-U>(424SCN2&YmZ=;@zCNB4XU)aa| zHo0JxzEh$+r%*9G-4l+KBqz)N5r^$~UeJV8cJsFWuk@z4Dad(gC=0He~Y z#D@^ETAF0x5B6-4@jR2vlOf~8^Srbl*bZ^HxYe$1OtKj;Zpn4LH8AFL{IU#BDF_jX zWTS*i$pknM0H^?DFy&%1;-udjhklPm=yl@d`v7Eqi6eoM1g)%9JR=0FlrVfrn8f&f z8(WF9OtlAr>QlV@Atz(Z%37lzKzMLMco2GN^xLHpB~hl#-gu^0GGA&7Kt{h!~A90H_p8Ki4t!~x->QExIF-&lR9FVvhW|#pQi&-eUGECb90LRkh==qKhUXzCw|ah^N57AA_UJ~?zkCkTuaf@v^To%Y z|FaGFe{=c2I^n;r1^Xm+C`z%7ym!x`eeQvYwk>hI_kKNuz#3?x;!hsWQZ5ljgJx}x zh9$9Dg5-TD@ZjYc0;e_0a~s~*Zf9%``Qt3^cEWN@MoHgs-|3`{^kqEEn1gy*x}SAm}Wp&FHBTZRSB$ z+rTI=U|$f!x3g{rdTyJRy1>uTk5rO>C*gQR;Zc^#q;AC;5YW0f1}fGV&{#i0Sb9L_ zyXYUj{P}WX*kOl&bvT^cl%gAT)&HnX8vUM% zft`>Fr4Clo5e&GKqhVx1{4Md3E^H;+of)$|p>_2`j#Xz>>p&_Q-Fd1CW-^6ON!Y?*T4?7}Jfase4=r9Z7&mTQXw2-?a?M z<&0!XNpc5K>GA>eir~~^OvLX(P50hPajUqqrt7(@wCR&{FnPbd_Fok-25_pPRwkzW zDu1OMNrx%7E<)OU0fZ=Y6_#^*Kz!dy@sgr7FA}bn-B8O{HwS>-A<#RrHt^t!3*#DF z!&r5l*V&gIqV&^yGswq8j2>Eqpc2hGamjYqUCx z{*i$jx^R8#WxJyO=_O5lnS+}#E7G(rMR4?lwJ`!uBciI>*_7!Y zjaS6CKj7J+6w~HSP;>kMdlc<6o7&WQwHeZyGrMs*UNUfz0d#>tDp}UD(C^QvvqVO; z^^ku^Q+4@h+Ebw<3Kx}n*i)MQYk*{CS`Z8?(MaOlSTy@_*k&z^3BTmgw@qPy}U*O zt|OcBwxt)hP$K*rE=lN!6lC?ks%+n9poy({Q{a|06Tv06HDp@f-&M*xh$X@6j1B?q zOPs2bLyN~}i10QeTDatY36Vw%Y0z3?s_zZWRp}5jy69&DQo)7K8VAB%*+Qp_&<&WF z$NC}M?G{OZ6fmL)DqBJ`p{4-fBDN3mWf-#tQZZkIW^*yV@=obeFbItnn}us(FA`Mf zLFC9uYL|a=k^-c+h7&egco-DdlV9Az)_a?( zu;913mAn@!&n!o)m2B*BcI}?|X5kTKUU*~U8>}w|sMe6*-{>R+ zK}XuPvCybD`Sz-~8yHL7Q8DJHSwG+$^#J(#d z?Aqc7JR;lL#k&^KVg%Bcu8On_;nwX@@K2Z}dGrfeG1Z5y6hPrAJA!93LERlnhoGsR z4~gEyk^Syel~7|++-U;NO0#`|X!0rZKs)V3`tJ11bigeN4GC8nsI1kxlNi#&1xIV=7 z%~RGpYkH|>{Z%|r^&n3pGdCRQU2Vf4GxAtF)J5|x6NsIp(zvoZ;N|QWH&t>6B|nki z%P(9qxX3FxM0$Fr&K16KTmboT$B*o{o(+)JW;5{MWAlGd`oEgE?s&Go_n#`YOA)h1 z?X6mhlG-G;h}zVyU3-u6p*5<6ST$;kStC_qRB2JG2&rAO#NL};@JjNch9Nh2$96wK^#uCDB;?jY&!+LZOtz}uQ-G}#z z2Dr-_Z>zn@<#}{e$}jo*kmHyT==FAjc9ir3vs|25rRBPa{;UiXGR0hLY%klHY`X8KvxC4JHJ%ZTxsOx$~K%DHu^yY$_6avw{*Gt=^|hK+m#j;vKX{>|DY z%FpnXm;ip!c!7ENM4eoxl@slH9EjZ*xJwl{x#l=&j3JPoRpLW2K82EwzRR9;7%~)N z@HPLD7z1=fKAxF!M4Aa%hiHV?MrKla-n;kM>NSQ!r>E+zowvjECmIO_+yjO3z;1S~LiIDD zK+U;x&76BPa&KLC${oIKkQdKR#`axJKT&Dsc^u}FHR3(S_8jH%!&ei~n3bJGYU%h$ z5J|}JN06wS(UIl)rz{wokXtn-bZ7vpcH`S!t3tK-hHNP0aIOn~Tk))x3rD*$m}8kU zmc%o*W>@)=C&_0G0%|}ieqH1p42*s%1J8!)U?S2W<}%4BhKo+7chBfSV73CzC}@1X zoQkT%RnPkc!qmjRj}i?qySA_9=S#J<9eL*y0f})Dhd)yY3yONDu@4SI{mqwLQ-sos zH%0(D&tAh>XLr-n;F&Ki7r8;1158qdJJsP%hAQMxm&NzR-J|z)zRI}t2|IH5g1*Hf zN^ddyFXf+(hdn^d`>Op0GI+`fz+GnKYn~xfb`y5oYwb<*-BOEI6Q43>;t zElqy9tL&QwKJ4`nw&yWzqaT2W+p~1di0+d43QuvM82@@%##FIcosuM1tzpO9dg6vD z@~Bfc8!OzxmC1E!Lwq(L1TF+q?6)%xxo)a{U`FScNF6$pGU?l^M(~Dow`awfzBh6@ zM)X$r47i*hi6Dd6FMQgH<3QqRX$LbwJ{wOhji7Cd2eK)>xD$-Qe9w?P$6;A4@L>1t zlMk}E{T+HCIluZj1`G2(vvE+1fq&0 zR8z;hcpq@dSqpm1^*3Zn*cl1xH}@TPQjw-)9=lud(!$_BA5thonPRNLf}Q`3t|}mI zRv|lGhut4oNCDRdRH>Nw*L=4kiUyaiU<3e2PH(`1>A%~Z3p#vt0*vRs9;ahE{pYUx zZZJ16f*W7laAA{RaI%OZIP8WYiziP+6wO*eFEt&9WYVo54KCwHE`df@6Nx5nJ)}`F zPGkRMGu_)Hh$wny45iW}t@7b+hhzJBu#3|4y?{;iE|=L7|y)Z7Km;oVS;tb zr5nhF-qU2|f=%AZVw`K~ESJyyV{^m_J`=DR6hi9)lVyvrsndfo-_S zk|4Ln=$1+(gPy4uOOo7nol2XlcK+ZQ&!Lxk&b7}ms@y@!T*XZAJu$9u+uwE)uwvn+qWyX8De(?oEKD|d;;X7^sJg5e8nEkB^vdLyA z2@3tabpl<(d~nB#B!Wul((XxYAtY<6#(9-4W>33|uI$BLo}OHcZu$hIGp|dLXX(>- zKqu^YKa4Qolwx=!bwXr9(Jz)8f{aN0#J$8m*OiwJ!FKV(;=zH8v#MEv&+{ED6}n!w zl2n_{<&iAcHhuE@mCUuB$MT~?B#ACV@z3O;sc^C@>bn5+gNlus^Oj|5`O zMjKhqH3X-5jzx`SO1}k3Cjr1XWnRM;{>zK0K5`k+IHQsU)nzYOXFGe?v1d`h~|DqyzjuJovE~f@o(JN(YVW&RxE^dcbEcl4|~I&rsvNxK|I?#=6b+N4XIUdK|gp9ep)rADVzZM-z`u4u{5)G9xiLF@#VmYw+V zCJS%&{laW6B>br1`pcrmX)j9yfLzjtJs4y0X^zSl^H_|(vM&{Dlh&sQ!kNk-82!Y8 zaR=zw#@h0DoF>8sN=!vlQ6Ld5ZkIon&C= z1|(rj2EIpJR}kV`4lKuLv2sR43OB1t#?+glM--h&Fg$FZXq^20x%}})``DGS7Mgil z`}hryhb3ihGEmNTDAz?;HZ5wPwdvO9goKx7AbopLMAC3lLh3zgo*8=GF({0{tiUjK zl>y?U7fW&%G9~>7MQ#Wv48j;%%*&_T$@*2dpS-^K<*yej7+u%$o(*-hE3pc{> z6+GB_3qpiXKqSF#avGmGZ|M4f-(7rHsA|!hJz;^7Mfhe8upqwj{~&=P^d=v|4J0A_ zA9$!10NqoZ`&i-D#crzqWG;+{t!L7;NnwMR&4vE&N3ju&<#Nt+`(jg%myM1VuYacH z)m1?g-#2A5p%=mC;w|(S)>01Zl(KhJ>LEU&EyP;y{AN+t$=mYR z73jIE*hvk>;+LKL-#CX|~ z3au({szlq3O*Va$@nI}yRjtqD5x-xXeJ*ZvW3jIFJe0V4quzcr3i6!|DwqNrB8-2y zD)rd(;WB{XgR1sn%Q?#4@#KBPcdJ`=Mu-l!HPu(>2z>U-Mp-~4beYN%VXMn6^{PQd7s z>r&2~A`KMHQDl*8jeh(5N@!T&hx35+B|@#eAj4U|p+3MG`Drv4+ z`ZBsAsgKt?qOHa;KRA+AVxp5#EFC~4!J4A8MPG$_k`J#YYpSV-9jLpzKin`~r3$XF z-eGZ0>Y_{@bd7X(t9<(~6qT zD?efvyw3)@#+i(i8%sBA+a_Vwm1Oko9MyIkdQGa4M-z`jGRQ z3u)Zaq@IcuIxDvLXjLR9Q(^jr5Ot zz&Tdp_p4e)vt?R)s^Di7_kLCyOE9-@H8Rg$QMK`DEJ>x?@6mB(CGe^2K>}v!m27JX z9ObzA$b|gZlWeF<#v)_!bb;{CR5B3jpp3Qyt@?UJS;NcaH^vgjzwHoxV6JZ0Y+|`D zz)&#U-mOI3=ERTOi|2kEy6b2+b)~T639oHC{pg-$o}flvtEXlra6lmy&qe2Gho8UA z(_@F8{!$Lxxrk1W0nyybkMM3wr7Yuq(5|P^vHacaYzw*bj_PXMXm{=N!94uW;0Ebe z(vng#uyoAA+JbEGdATmTB5?3NbGe_t#TvV6UV42i2NIh+W+(kFy&hd@RZ8cR#ItRZ z7$|}8p`-0M8+fu!v)hFVjEl7spH_QZf=r6XG>FxQ(mx~q)AYyqqj&XcJwBq0B87ug ze}v}NzGx`udF*ax0nM!W;wbC%EK^0`iH2HMT18Y>VS;f#ckZiDCf$8oV}E~Z7D$1Q zNHCV2)5b;`&T)*;8(jVwsDv0oz?eI=TGbuYW4To<2e1hSAN>=PUj$5UGm#VS*B0d_ zM?Z-Li{}7h72{Wdo8i8mWa`^XKJ&VF6UE#Cl8D*&#VyXixOkB*))How&R!igGq&ry2 z8h1L!xA|EGv<4h0#DH-hmfh>pj=i`Mu#AzB*;((>c+K|p+mKcr*gl&u1XRuOH&YU? z++e>+W7+|d{!aVIk32W^=;IUBEDkoxCT0mbF^kV}S-p)4i9v;%ykt{*lOPT_>g~D^ zjeeoyD+$xH%zASZIdG+u+@amR%nYN%BNuJB^QuMoTWaB4XP43uiSFJEllROz%QPm6 z@!GMJ#w-_^#k(1mj@;YuhV>q~I;BpR)ITQ+cOpit{el+>3&oJGj8cquTmm+eK0Dod zC?*1ShC3jKYHH-=y3O6CiQlZQaY>h)lHO)9L(R-Hv95u4H%Le54484fwOVrfOc{eum#xI_|5EcZioNs`2_5e}Obz)ezePm{ z-_sD5dkHLaZts><8gb03(eGtUC4^n|vo>0ootT}wVSPHJb~{}{n{;;x%Kjwh>okEL zrD2?+wSiF&%esHWJXJQG*C@Y2WlZ=A!7-rsf5flLj&(xPn$HL0E4|j?&TO#)B00qi zmsbLGf8TVct=yG26Kiid{zE)L2T{r_8f+6UXGI(V86+V&_X!}aTkz(M#sWxs`dE2E zsy)*^E1)4$qnY>dbgKU7RnE}4cW}{%YXpS10FtnVS@HvWwKUJ@pBy42>+C~2+Upf zwa)$<&!V91Fw#pIBZkbj_K-{{iCKTs@J?m{T)u_+azP!&9eolo=kK3PrMs0-`?CsM z&a6o*lc(=91g)u*C(AICi)YhKHW)5O%pEMWUT@{g_*9=AJcYDQ%D=vQOB7CQl75ue zMO+hXz=4zkL!I2eDR}c7w*76hKAUBgJvhE9xwYrc3VM4mdHaxzs67QVP3OjcI;L~> zPf%ctwu_nt`SIt{wQ}#p=ci8uBR@ah1-v69=7@t$SYaR8uiI%mBYvttsRg}>kRW9B(bGwQKupRR05 z@VJJzsH;XF-n&-MGdrDZXkb46^bFUcg3^Ja^Pbil%gyB}{Vio@3!?Qn)n(YFEscGU znpF2r-eEf$oA!!m?<+0`J46EkJqvd&+m!$SBa6Ze%TnRJ@JfmFiZ3k%BSW!k5!Yaj zUkcYg5*?_Ix~OceG~i);8(&4*EAK^1R|2V_09Bnd_@3Hqt07p*@HUq#cvn{~x{_S2 z&3NA7@TIafqgR&P@J~Y83o>U;N5w~FTp`Co4B^=qtG5sa6K~1g(t-+hRDXYHZV4Vj z%(}Od8TlVt%p%I&PW|;8`{z!CYO`l@eWe*Uk2(}KVlELFKc82%%Z`lH^_{xxnrSCu z?y|2enI!ct-Wiol)TSDJzBq|03L7&)$T}#^D=8t}PV)g!QT5Kp$;Q!n#ex@mVkRcq zE1mfv_pcW2{$B|F=?>gm2_}qwZDL|#7U$(d3XmW8NKPXAd<8`?3;KzTCY7K*v(NNqR zrFbLR@E^Udf7KiC@J75Vq6i=6;~W0f>-z6X66QLmDD3&=l+FBzTJXu*IbR}MaJX;1 zo{-kXRadF@YYRawyD*Svua{J z!Soxw-t{_nSPAKy_|s2pC~l=Fe^=_Sro2N7rV@Hdq$b%ztqhIxEKgh0i1(Y;F!Sa1 zMg8y<*H&Fmx?b$g*p>JGHE-*X@Da19kJI9V`PYSmH&#UkdYw7_v^Dx4PVqAL7L9F) zZcVhc>F6F1H!ABswY~$0fL@0e@0rZW8IbUFeVSGWf2v}rhjX7Z6hU^^%u0w}}Tp4E7Otbi(w+&-kPw#17quKe-ACMQwH5`H~rf@k#V9wd*B*Hr`;rjlR`G|sBh);L9bG{4>z{m z-#Apb(%3KDSva=xXn(q`#l;T<@E@*jK|vLvtAE%NyqBv2;d^7?2QYMOt0>P><#t!> zuQHJ~-8A!qE(YrnZTLTZo@Ql!&2g;WE|)!NJ;i->F9fp~7K^FHg-Q&X&^+pFcdnv~ z%+D*V+G_ce9fhpt?Ld+O!vy(j3q99>Vgf)h+XtvER;~>8WQ zG>kX(DXqr4G_ERrbAq0j?7T|`JwbpTF6B9HX5c{dC!rHvKd~Usne*JNw>8Aa1^H`O zpckRR;%6EQf9p`2C-TmGGqU(n!y<#mZ8CxHJDy{oeq)GF>d!gcK=S#b4xhihEp<9R zrqXEGJ{It;zptlMeObo1KSV0SdcxAK8yjSnP*vK@|JxledXo2{A5^ggJr+Q@$t?Gv zH29Mp6bb(AsTwF{J$&)JLQK~*rx9J;v%dUh>(NLLr43fl(XcykYT)vu^I^IJlQKI) z+oF|Ju{^Q;CKS=Pl2c_!KIwPdP-}(!a9+FX)M4QLqM|9pJ2T80nD_6aj)pvtbhu@! zAM`1G=OKwwuIqcZ430^+J+N>Sq)vG!sn%GS1V43m6Xo$Pd-HN$tb{N5ff{(H#?KkR zN4&IN1T-ICrk-UeB;!4fUEt7v@bS%FmtK$kz&ikdp0h~c+?w6Z$>RvB8cX|s(HA42 z6yS>y5&p7x5x<`KC5_PIJ)YnrDgs67BDUf^Y|TV{I5ocMkY7@BRB!ZmeKHhRfq0yv6ucNmWOsTIogPe*qdS B6FL9@ literal 0 HcmV?d00001 diff --git a/addons/web/doc/widget.rst b/addons/web/doc/widget.rst index 483c6a6a418..7c4a2374b3e 100644 --- a/addons/web/doc/widget.rst +++ b/addons/web/doc/widget.rst @@ -93,7 +93,7 @@ The DOM root can also be defined programmatically by overridding Any override to :js:func:`~openerp.web.Widget.renderElement` which does not call its ``_super`` **must** call :js:func:`~openerp.web.Widget.setElement` with whatever it - generated or the widget's behavior is undefined.r + generated or the widget's behavior is undefined. .. note:: From 1c2b769a1fc3e978ded0211af8a119b4a24c35f3 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 18 Feb 2013 16:06:04 +0100 Subject: [PATCH 5/5] [IMP] final section of the module guide bzr revid: xmo@openerp.com-20130218150604-pjd9lkmc834tmbiz --- addons/web/doc/module.rst | 43 +++++++++++++++++++++++++++- addons/web/doc/module/26 | 14 +++++++-- addons/web/doc/module/27 | 28 ++++++++++++++++++ addons/web/doc/module/28 | 13 +++++++++ addons/web/doc/module/29 | 37 ++++++++++++++++++++++++ addons/web/doc/module/series | 3 ++ addons/web/doc/module/testing_1.png | Bin 0 -> 36183 bytes addons/web/doc/testing.rst | 2 ++ 8 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 addons/web/doc/module/27 create mode 100644 addons/web/doc/module/28 create mode 100644 addons/web/doc/module/29 create mode 100644 addons/web/doc/module/testing_1.png diff --git a/addons/web/doc/module.rst b/addons/web/doc/module.rst index 138a8e91a4c..7971a919780 100644 --- a/addons/web/doc/module.rst +++ b/addons/web/doc/module.rst @@ -385,12 +385,53 @@ which for us means going to query the user's times. We don't have any records to get in our test, and we don't want to test the initialization yet! So let's cheat a bit: we can manually -:js:func:`set a widget's DOM `, let's create a basic DOM matching what each method expects then call the method: .. patch:: +The next group of patches (in terms of setup/complexity) is RPC tests: +testing components/methods which perform network calls (RPC +requests). In our module, ``start`` and ``watch_stop`` are in that +case: ``start`` fetches the user's recorded times and ``watch_stop`` +creates a new record with the current watch. + +By default, tests don't allow RPC requests and will generate an error +when trying to perform one: + +.. image:: module/testing_1.png + :align: center + +To allow them, the test case (or the test suite) has to explicitly opt +into :js:attr:`rpc support ` by adding the ``rpc: +'mock'`` option to the test case, and providing its own "rpc +responses": + +.. patch:: + +.. note:: + + By defaut, tests cases don't load templates either. We had not + needed to perform any template rendering before here, so we must + now enable templates loading via :js:attr:`the corresponding + option `. + +Our final test requires altering the module's code: asynchronous tests +use :doc:`deferred ` to know when a test ends and the other +one can start (otherwise test content will execute non-linearly and +the assertions of a test will be executed during the next test or +worse), but although ``watch_stop`` performs an asynchronous +``create`` operation it doesn't return a deferred we can synchronize +on. We simply need to return its result: + +.. patch:: + +This makes no difference to the original code, but allows us to write +our test: + +.. patch:: + .. [#DOM-building] they are not alternative solutions: they work very well together. Templates are used to build "just DOM", sub-widgets are used to build DOM subsections diff --git a/addons/web/doc/module/26 b/addons/web/doc/module/26 index 4085350db24..ec0b345a908 100644 --- a/addons/web/doc/module/26 +++ b/addons/web/doc/module/26 @@ -2,7 +2,7 @@ Index: web_example/static/src/tests/timer.js =================================================================== --- web_example.orig/static/src/tests/timer.js +++ web_example/static/src/tests/timer.js -@@ -42,4 +42,25 @@ openerp.testing.section('timer', functio +@@ -42,4 +42,33 @@ openerp.testing.section('timer', functio w.format_time(84092336), '23:21:32'); }); @@ -18,13 +18,21 @@ Index: web_example/static/src/tests/timer.js + w.update_counter(22733958); + // And check the DOM matches + strictEqual($fixture.text(), '06:18:53'); -+ ++ + w.update_counter(73451828) + strictEqual($fixture.text(), '20:24:11'); + }); + test('display_record', function (instance, $fixture) { + var w = new instance.web_example.Action(); + $fixture.append('
          ') -+ ok(false, "test display records"); ++ w.setElement($fixture); ++ ++ w.display_record({time: 41676639}); ++ w.display_record({time: 84092336}); ++ ++ var $lis = $fixture.find('li'); ++ strictEqual($lis.length, 2, "should have printed 2 records"); ++ strictEqual($lis[0].textContent, '11:34:36'); ++ strictEqual($lis[1].textContent, '23:21:32'); + }); }); diff --git a/addons/web/doc/module/27 b/addons/web/doc/module/27 new file mode 100644 index 00000000000..2061b7007e6 --- /dev/null +++ b/addons/web/doc/module/27 @@ -0,0 +1,28 @@ +Index: web_example/static/src/tests/timer.js +=================================================================== +--- web_example.orig/static/src/tests/timer.js ++++ web_example/static/src/tests/timer.js +@@ -71,4 +71,23 @@ openerp.testing.section('timer', functio + strictEqual($lis[0].textContent, '11:34:36'); + strictEqual($lis[1].textContent, '23:21:32'); + }); ++ test('start', {templates: true, rpc: 'mock', asserts: 3}, function (instance, $fixture, mock) { ++ // Rather odd-looking shortcut for search+read in a single RPC call ++ mock('/web/dataset/search_read', function () { ++ // ignore parameters, just return a pair of records. ++ return {records: [ ++ {time: 22733958}, ++ {time: 84092336} ++ ]}; ++ }); ++ ++ var w = new instance.web_example.Action(); ++ return w.appendTo($fixture) ++ .then(function () { ++ var $lis = $fixture.find('li'); ++ strictEqual($lis.length, 2); ++ strictEqual($lis[0].textContent, '06:18:53'); ++ strictEqual($lis[1].textContent, '23:21:32'); ++ }); ++ }); + }); diff --git a/addons/web/doc/module/28 b/addons/web/doc/module/28 new file mode 100644 index 00000000000..800e7a6f084 --- /dev/null +++ b/addons/web/doc/module/28 @@ -0,0 +1,13 @@ +Index: web_example/static/src/js/first_module.js +=================================================================== +--- web_example.orig/static/src/js/first_module.js ++++ web_example/static/src/js/first_module.js +@@ -66,7 +66,7 @@ openerp.web_example = function (instance + user_id: instance.session.uid, + time: time, + }; +- this.model.call('create', [record]).done(function () { ++ return this.model.call('create', [record]).done(function () { + self.display_record(record); + }); + }, diff --git a/addons/web/doc/module/29 b/addons/web/doc/module/29 new file mode 100644 index 00000000000..509d4b78662 --- /dev/null +++ b/addons/web/doc/module/29 @@ -0,0 +1,37 @@ +Index: web_example/static/src/tests/timer.js +=================================================================== +--- web_example.orig/static/src/tests/timer.js ++++ web_example/static/src/tests/timer.js +@@ -90,4 +90,32 @@ openerp.testing.section('timer', functio + strictEqual($lis[1].textContent, '23:21:32'); + }); + }); ++ test('watch_stop', {templates: true, rpc: 'mock', asserts: 3}, function (instance, $fix, mock) { ++ var created = false; ++ mock('web_example.stopwatch:create', function (args, kwargs) { ++ created = true; ++ // return a fake id (unused) ++ return 42; ++ }); ++ mock('/web/dataset/search_read', function () { ++ return {records: []}; ++ }); ++ ++ var w = new instance.web_example.Action(); ++ return w.appendTo($fix) ++ .then(function () { ++ // Virtual start point 5s before 'now' ++ w._start = new Date() - 5000; ++ return w.watch_stop(); ++ }) ++ .done(function () { ++ ok(created, "should have called create()"); ++ strictEqual($fix.find('.oe_web_example_timer').text(), ++ '00:00:05', ++ "should have updated the timer"); ++ strictEqual($fix.find('li')[0].textContent, ++ '00:00:05', ++ "should have added the new time to the list"); ++ }); ++ }); + }); diff --git a/addons/web/doc/module/series b/addons/web/doc/module/series index 491e7dc3c59..ff1a909a6af 100644 --- a/addons/web/doc/module/series +++ b/addons/web/doc/module/series @@ -22,3 +22,6 @@ 24 25 26 +27 +28 +29 diff --git a/addons/web/doc/module/testing_1.png b/addons/web/doc/module/testing_1.png new file mode 100644 index 0000000000000000000000000000000000000000..40cf32497eb0403f11b4b2bd827af9d7bd1ed883 GIT binary patch literal 36183 zcma&N1x#f@(>BO3xVyvP?(T4bK?k_FySuv$?hNkk4uiY9ySux)F7v+m^6&1KP4-D{ zQe9nLZPm}IzBwlpC?f_Bg98Hs0s=1~E+P*C0%`#Q@=X*9^h?unCp!QF5=kH-0#I~a zIm-Z8+>BuiW-&~?zk_;%HYQn)dvKhHt{1~XGi=aG3bvu;fQ6yuhtz=wLP=_%5ktVe zpfF)otD@-yc;|XpkzZp;o20tuJh>8hhZh};adO2zdl*mP`MfWFJT6*ZR|$OH{_z=L z=Ft3eCwq7!rpYDvR{;!qgkkMf zPW)iP{#OBtn(|*wwjc37i~ncAl(ES*S=I_FqdgD#%eCSZ zLszJZq>hi)_=mTP8t%g0U+UoQGm2q8xVr0nj$Foh*1$ZClU`$~X0#fev*t2HbHoJ7 z$LB8n{iJI}T*RBU$$8V}JTFbQ%D|`X`Dox}3JdcAEPIKDQ|_NvF8=CMp%il$-h$>R z#>|G(oBm}1-G2jv_)Wu)O_+YUusWhy36M;h6*@Syd97(B+|9Wzwb!Y|GrPC_or&op z|9}c!fwaiBFYN!czH|&wkqowS7_sE}>S6g{wXkeniT3G`260;(3yx&!N?e}fB!98O z!Q``#80uz~no>o6ec9ndl}hZX3AL#&-@?eWIjJ&MpfdY*T>O`ogkgRiAsK>rct6x+ z_1LK7qf?nVbNgYPVuu&ICgRvldZ!Gp`VR=vMth`%q`C@bwTvr3Z|X6!3dZul;1Fh@ zs_o@<&j8njm(>T!!4FGmFI;UHeIZ91HQGXOBHKgDVSz^q3_$dzBH;NZS4! zJjOpS9BOSvM^=r^08q~-xg(0e1|xAxHQPnYSHG5Qtbjg>nweHiORbfT`K*MXW{Ask z52sjT9vCp_jlfCrK9v4*N6y@A8&+^5A45&**qAff&iEliFMS{uWBvBBzzmdMdafUa zhm50AZfd`c#FTM^kRf#=dxde}ufOay;8?0I8VOS$j#PXE$@iIMePicV?o>n44*P6r zi;_J|Fp}YIfZL^q#seOv?AE_p7zlT}?L-AL&e7)PN|!E4e=sZLGR`Ug_)Rc8pVf00 zG`&-T8~q80Zt>1k-HQ#sH%E&l%}mtR=1U5BEQY_Put5gnA_5NtTH0m2jc8M)bTle7 zkJ?Ww&8Ox*l-IMUm_PDZ6(n&C+Oy*DQkyR(NW>+(dDLnbjV~w&v;8_uNZ?^twcyhR z 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 ^Dqp6?hWlIa;Rb)nA&28sO~dv zgoK*j;x9wS{N4jV!d#LZw3y09Q?)fDwZTu6sHor!U-X#FY3Q2*91Wvu_fAHzd z5QJ%$+qyV=y-j{Ag?4T?aUY(zSuPr5{qqbZ@X_wDo27T5*ph!8kkcKnPH3AZ+*47$e9K z;G?J3&3|{UU#V#$XoYjT8+z2GzpvwyWX)V_D=r$mJGyJG;Bq|WV0bE7abEK)#!!u_ zIzb*had9#MsNJ92FD?8~G+W*%Xu93$W+t?>aoG)2rK>NQ-fO&hv8&F$gC=0JVY_@^ zKIiw?{(y7%eN6wW<#er7LV9{F-t%sC$){`)x|hQ7x}{_2gDzfYZ-7+)Q-^PZ@Juwl zIZVP>de(shiByH;+A4Pb-K-95HPEPYV>-u%!?lA-*-WWb^C!uo5ta46YuI8q#dDhc z!fU+7_OQ*P9b*S46J~31KH=p(CeZEw3eyNI_jNbQPD%o*+pqjPMQtdRRyQj6-C9Ly zDC$#4H<8!6l!*2;^|5PD-)fLNcWBDr~RbovU9mz;kjefC2js^6E@jpg@VjR#bt zey=iSt?tvnF8=q%r(XKRdaRD4<;2nXu5j%;t*XvOWz^esqiy@UE6iOIv=uhJd){!| zWozR7ar#XyK1l0)7}+z==?^iXTc?Fq`>b)tPY9WNOu)9kIuuu$HBEQ1jK@wjXY#BX z>tV-rhwCJnc663n0d5nP9quR%n=DsDa>xiP$xaktgI$i(^9KD+GbfY z>abKE!_`rC^~nz=eTy4T!bTDb0nBl&u6ZGXrn>~v0X*8FkD38z{pJyz#YL_f(H*f7(7nEaF8d%s)J1oKtfEwyro_&cp= z++8PR9d!6M|IdFJJvq*kJKEiR=+yR;<2tS!SLZNa)6WjsU}nm$UK~-8(M&{c61XkB z$`*nX>{orBW*uaL5me-fEw4xxIVROk)XBxS$^gk`ugN#y>tXj!#39vzBPc98+|f2a z?`2I@A56=2>?i)r1SA+jfbF*sy~#f;wU8waR$}t-5=0I4r84?J>_t{%UF3 z1D=G+f6r1;3}T9mgJCLiy`Icc`Wi+g{or+=5CLWi(8~y_b@!+1j$;4G(M0Z{^R>Rx z8%7(NCV|5WfNjFE!yRo7>B9F09s9vFTg;%!gj`VwC_4|;JWC%Lvg)*CJ}@Rfw@_zC zX3Y-DT3cvn$f+bH%`c1uxSR$1X`O0VC4?slKu{&$`?jS1`M z%y0%XoP>>TQwVGhxFopl&jAxl3`rQBzYdgY6VCMycLY|4JAxey8YgHt$dh5!!6q(M zmCQ%Mnq-nr*L9>mGpUR`~7c!U7WId5wG?)e@WE#m&Ib| z*Lc#`a`~>j#AHX7x*d5<@-kTQtYTzP* z+CjOOKdJs`@aJ0d`>?~?Y1^H$O~qaX_U&K)zPbkQah}qFc6!d1jV-MdMA6tZ2AM2^ zDlOCC%x1ltWM?hcP7Z1&X8uU+iLPO$NZ@`#t^Tt3uE7+XJImYhUOekP({u^{L_s@p z45Ps%g4%>-2Rouc^Yl#K=rY~H3V~(c1n-Zz+THM2J6{J&i;q`F#ifE-dFV%+^;|7E zk6sL_PJj&a!B^LZbfEGVS`2g7Jb5=?AIFph?{kTWgpDK=0-FOYnVz-DogXl<%#aeV zE;y_@&z*TooAB}G2<`~34tE52GCWS$aNz6!RR@|F@J|Ubf$ETd!aff(5g0*%$yR>D zCS2c~7V9Gkx=q1X^yiT9<_A^fShCt=tEYbQ3>v&2#z<6u|7)sO&#nokR-YzW`B1Al z+c)@bKHa6+Pcl?o-;Ru?#_8dvFq)~ceG=&WV@6y z=51Q~uuUj4Gh=SE%c3mHEpmcMz(04;%om$Q+)LWDWsk`O=>%MVGu zO}H?7ylZwMpd+9<*b(4hGUI&vSu7(aTpjXH*ykZ8K7L{Z<>g^y+^aS;z2I*N*${d; z`6*`t0IEX??pa5l921*A_$ps1b-Ydp@k@g^v1DW{QWO(`5ftymeLHMd@jMI}MN9mw z4)M#wo+htR^txQ_P)r0yP>i4m1OkD;2#OIDfj}S-2#lZz1OkCTU<5@V5C{YUBPd2t z1OkCTUb=AP@)yM$osXf2a71Vgv$#Kwt!wr+-bS2Lu9vKp-%J z%F;jHy*nTf2m}Iw5%eSIpI?7{Kp+qZ1Og-I$32PW$GP}U6oEh>5Ewy`kdTm&kUner z)!)DTl6>}BKte)7LPC;`py@Z?kk4KVNJvOXNJ!EV^swJ=>UHwjD*_1#2?+^FGJ@v# z@yGPrZ&@7u;tTTGYXk`i2?+`5wg`$qAP@)yLLUDC5z06rfBsZ*00000NkvXXu0mjf DeqCyV literal 0 HcmV?d00001 diff --git a/addons/web/doc/testing.rst b/addons/web/doc/testing.rst index 600d379aa02..7ecf64288e1 100644 --- a/addons/web/doc/testing.rst +++ b/addons/web/doc/testing.rst @@ -327,6 +327,8 @@ a test case (or its containing test suite) through :js:attr:`~TestOptions.rpc`, and can be one of two modes: ``mock`` or ``rpc``. +.. _testing-rpc-mock: + Mock RPC ++++++++